Friday, November 23, 2018

Utilising Function Pointers in Embedded Projects

One of the good thing in C that I didn`t use in my early days of embedded programming was Function pointers ( although it was there in the project thanks to the vendor supplied drivers ). But in many of my projects that helped me to create efficient software. So I decided to write about that in my first technical blog.

There are three major possible use cases for function pointers – Arrays of function pointers (Lookup table), vector table ( type of lookup table ) and callback functions. We will see about each of that, one by one.

Let`s look at lookup table use cases first, to be honest I had been deliberately avoiding the use of the same in my early days as I was not confident due to its cryptic syntax. But as I worked with the projects that had multi-level menu, function pointer was one of the key feature in C that got my attention. I have developed lot of vending products with the multi-level menu over the years, in those projects function pointers not only provided efficiency, but as well as the readability, as using switch case for a menu could go on for more than one page. 

The basic declaration of arrays of function pointers in shown below.

void (*pf[])(void) =
{
fna,
fnb,
fnc,
...
fnz
};

There are few things which could be added to make the above declaration better as shown in below code snippet, which is inspired by nigel jones “Arrays of Pointers to Functions” article (Link is attached in the end of the article).

Static void (* const pf[])(void) =
{
fna,
fnb,
fnc,
...
fnz
};

 Below shows the use case of arrays of function pointers in one of my project. There will be request from another node to my target, for that request target has to invoke the appropriate callback routines. This can be done with the help of switch case, but as the number of request commands increases, the concerning switch case will become too large and will hover across more than one page, results in poor readability and also poor efficiency if the case which positioned bottom of the switch case is executed frequently. Both of this can be addressed ( In cache enabled systems efficiency may differ ) with the help of lookup table (arrays of function pointers) as you can see below.

Static void ( * const RequestHandler[]) ( void ) =
{
vDoTask1,
vDoTask2,
vDoTask3,            
vDoTaskN,
};

void AcceptRequest ( uint8_t u8RequestCommand )
{
if ( u8RequestCommand % 2 )
{
u8RequestCommand = u8RequestCommand / 2;
if ( u8RequestCommand < sizeof( RequestHandler ) / sizeof( *RequestHandler ) )
{
RequestHandler [ u8RequestCommand ]( ) ;
}
}
}

Here all the request commands are odd value and all the reply commands from target are even value, so u8RequestCommand is being divided by 2, after that we ensure the u8RequestCommand is within the limit of look-up table and that value is being used to lookup the target function in lookup table.

There are few things to be noted in this declaration:

1) Prefixing the declaration with static ensures that RequestHandler can`t be accessed outside of the module ( file ). You can put the RequestHandler declaration inside the AcceptRequest function definition as that will even prevent the access outside of the AcceptRequest function.

2)     By testing the u8RequestCommand will ensure only valid command requests will be accepted and we can be rest assured out of bound access will not happen.

3)     By declaring the RequestHandler with the const will make it more likely to be placed in ROM. This will prevent the lookup table being changed at runtime results in more reliable code.

4)     Other things like making the datatype u8RequestCommand unsigned and choosing the smallest datatype possible provide some protection as mentioned in nigel jones article.

In case if the request functions need arguments with different data type, we can change the prototype as shown below,

Static uint8_t ( * const RequestHandler[]) ( void * ) =
{
}

This provides more flexibility to the user as in this case, we just need to typecast the pointer to appropriate types in the member functions as shown below,

Void vDoTask1 ( void * pParamInput )
{
                     Uint32 u32Input = 0;
                     u32Input = * ( ( uint32_t  * ) pParamInput );
                     ….
}
Void vDoTask2 ( void * pParamInput )
{
                     float fInput = 0;
                     fInput = * ( ( float   * ) pParamInput );
                     ….
}

We can even pass the variable number of arguments with the help of structures, but use cases for these are rare.

As far as use of function pointers as a vector table is concerned most of the time we don`t need to change the startup file provided by the vendor so does the vector table, but if we want to port the project from one compiler to another ( e.g: gcc to IAR ) we may need to tweak that. But I prefer having the respective macro definition for each of the handlers I`m using in platform layer file, and by changing that macro we can switch between different compilers by just changing the startup file for the respective compiler. Below shows the vector table for SAM3S controller in startup file given by IAR.

const IntVector __vector_table[] =
{
 { .__ptr = __sfe( "CSTACK" ) },
__iar_program_start,
            NMI_Handler,
            …
            UART0_Handler,   /* 8  UART0 */
            IrqHandlerNotUsed   /* 35 not used */
};

Below shows the vector table for the same SAM3s in in startup file given by Atmel (GCC Based compiler),

IntFunc exception_table[] = {
 /* Configure Initial Stack Pointer, using linker-generated symbols */
 (IntFunc) (&_estack),
Reset_Handler,
NMI_Handler,
USART0_Handler,  /* 14 USART 0 */
Dummy_Handler    /* 35 not used */
};

As there are different handler names used in different compilers for the same IRQ, we need to map this with our handler. One example is shown below,

#define DEBUG_USART_HANDLER USART0_Handler

Void DEBUG_USART_HANDLER ( void )
{
}

Another use of function pointer is through the use of callback functions which is there in almost every embedded project.

You can refer the below article for more information which provides ample information about the use of function pointers.
  

2 comments: