Sometimes the need arises to re-use existing code in other platforms or other compilers. To do this you need to "port" existing code to work with the platform you are using. Most times it becomes what is known as a "cluster-mess" due to the lack of organization when using someone else's logic. Often I find myself looking at other people's libraries and wonder to myself if the person ever had any wish to use their code on anything other than the primary target.
Those people who use a fair amount of forethought and separate themselves to be truly professional are those people who show some level of organization to their processes. The holy grail of libraries however, is those that include a porting guide that supports and describes ways of running their library on other platforms. With this in mind, we are going to explore the porting process on the MikroElektronika libraries and how they might be used on unsupported platforms.
https://github.com/MikroElektronika
Structure of the MikroE. Libraries
Libraries are divided into 3 modules, HAL (hardware access layer), HW( hardware layer), and API
HAL Layer
#if defined( __MIKROC_PRO_FOR_ARM__ ) || defined( __MIKROC_PRO_FOR_AVR__ ) || defined( __MIKROC_PRO_FOR_PIC__ ) || defined( __MIKROC_PRO_FOR_PIC32__ ) || defined( __MIKROC_PRO_FOR_DSPIC__ ) || defined( __MIKROC_PRO_FOR_8051__ ) || defined( __MIKROC_PRO_FOR_FT90x__ ) extern sfr sbit MAGNETO_CS; #endif
Declaring a variable extern forces the user of the library to define the MAGNETO_CS pin in an external module. In this case we have defined the chip select for the magneto click board. Notice that the sfr sbit data type. This is a compiler specific data type. The HAL layer is the ONLY place compiler specific, non-ANSI C or hardware independent functions are allowed. The purpose of the HAL is to be an intermediary layer between the logic of the sensor and the underlying hardware of the host. Therefore, any MCU specific or compiler specific code is retained in the HAL.
Like the variable definitions, functions are also wrapped in defines to separate the various platforms.
void magneto_hal_set_slave( uint8_t slave_address ) { #if defined( __MIKROC_PRO_FOR_ARM__ ) || defined(__MIKROC_PRO_FOR_FT90x__) i2c_address = slave_address; #else i2c_address = ( slave_address << 1 ); #endif }
Adding a new platform to this function would be to add an additional #elseif and then a project definition.
Hardware Layer
int magneto_init( magneto_mode_t mode, uint8_t *address, uint8_t num_of_devices ) { if( num_of_devices > MAX_DEVICES || num_of_devices < MIN_DEVICES || mode > MAGNETO_PWM ) return -1; device_count = num_of_devices; mode_current = mode; memcpy( device_addresses, address, num_of_devices ); if( magneto_hal_init( mode, *address, ( mode == MAGNETO_I2C ) ? 1 : num_of_devices ) ) return -1; magneto_get_zero_positions( 0 ); if( magneto_error() ) magneto_get_errors( 0 ); return 0; }
All access to the host hardware is facilitated through the HAL layer. It starts by including the *_hal.h into the layer.
#include "magneto.h" #include "magneto_hal.h" #include
Inclusion of this module gives access to the underlying hardware.
int16_t *magneto_get_zero_positions( uint8_t device_num ) { int16_t high[MAX_DEVICES]; int16_t low[MAX_DEVICES]; int i; if( mode_current == MAGNETO_I2C ) { for( i = 0; i < device_count; i++ ) { if( device_addresses[i] == 0x00 ) break; magneto_hal_set_slave( device_addresses[i] ); if( magneto_hal_read( AS5048A_OTP_REGISTER_ZERO_POS_HIGH, &high[i] ) || magneto_hal_read( AS5048A_OTP_REGISTER_ZERO_POS_LOW, &low[i] ) ) error_flag = true; } } else { if( magneto_hal_read( AS5048A_OTP_REGISTER_ZERO_POS_HIGH, high ) || magneto_hal_read( AS5048A_OTP_REGISTER_ZERO_POS_LOW, low ) ) error_flag = true; } for( i = 0; i < device_count; i++ ) { zero[i] = high[i] << 6; zero[i] |= low[i] & 0x1f; } if( device_num != 1 && device_num != 0 ) zero[ 0 ] = zero[ device_num - 1 ]; return zero; }
If the sensor itself has many registry entries to define and / or they are going to be used in several modules, a separate file *_defs.h is recommended.
API Layer
void nrf_sb_init( nrf_addr_map_t *address, nrf_operation_mode_t operational_mode ) { nrf_hw_init( operational_mode ); /* First close all radio pipes. Pipe 0 and 1 open by default */ nrf_close_pipe( NRF_ALL ); /* Operates in 16bits CRC mode */ nrf_set_crc_mode( NRF_CRC_16BIT ); /* Disables auto retransmit */ nrf_set_auto_retr( 0, NRF_RETRANS_DELAY ); /* 5 bytes address width */ nrf_set_address_width( NRF_AW_5BYTES ); nrf_clear_irq_flag( NRF_MAX_RT ); /**< Max retries interrupt */ nrf_clear_irq_flag( NRF_TX_DS ); /**< TX data sent interrupt */ nrf_clear_irq_flag( NRF_RX_DR ); nrf_flush_rx(); nrf_flush_tx(); /* Flush FIFOs */ if( address ) { //uint8_t *ptr = ( uint8_t * )address; /* Set device's addresses */ nrf_set_address( NRF_PIPE0, address->p0 ); /* Open pipe0, without/autoack */ nrf_open_pipe( NRF_PIPE0, false ); if( address->p1[0] > 0 ) { nrf_set_address( NRF_PIPE1, address->p1 ); nrf_open_pipe( NRF_PIPE1, false ); } if( address->p2[0] > 0 ) { nrf_set_address( NRF_PIPE2, address->p2 ); nrf_open_pipe( NRF_PIPE2, false ); } if( address->p3[0] > 0 ) { nrf_set_address( NRF_PIPE3, address->p3 ); nrf_open_pipe( NRF_PIPE3, false ); } if( address->p4[0] > 0 ) { nrf_set_address( NRF_PIPE4, address->p4 ); nrf_open_pipe( NRF_PIPE4, false ); } if( address->p5[0] > 0 ) { nrf_set_address( NRF_PIPE5, address->p5 ); nrf_open_pipe( NRF_PIPE5, false ); } /* Sets recieving address on pipe0 */ if( operational_mode == NRF_PTX ) { nrf_set_address( NRF_TX, address->p0 ); nrf_set_operation_mode( NRF_PTX ); } else { nrf_set_address( NRF_TX, address->tx ); nrf_set_operation_mode( NRF_PRX ); nrf_set_rx_pload_width( ( uint8_t )NRF_PIPE0, NRF_PAYLOAD_LENGTH ); } }
Without the API, the application would have to call each one of the HW functions individually. With an API we have defined one function that initializes all layers and sets the default behavior by calling those HW functions related to the defaults.
Conclusion
There you have it, a grand tour of the various layers you will find in the MikroE. libraries. To add your own platform or compiler to it, you are going to be modifying the HAL layer only. Since all logic in the upper layers have strictly adhered to ANSI C standards, using those layers on Linux or any other unsupported platform becomes an easy exercise. And of course any errors found in the libraries would appreciate the occasional pull request on the repositories.