Download as pdf or txt
Download as pdf or txt
You are on page 1of 5

Page 1 of 5

Reduce C language coding errors with X macros: Part 3


Andrew Lucas- March 2, 2013 Editor's note: This article, last in a series of discussions of x macro usage, examines how to use x macros to automate the task of configuring jump tables and lookup tables used by communication handlers. Part 2 in this series showed how to develop the following x macro table with the caveat that the command codes needed to be contiguous: /* ------ NAME ------- FUNCTION --- CODE --- */ #define COMMAND_TABLE \ ENTRY(COMMAND0, command0, 0x00) \ ENTRY(COMMAND1, command1, 0x01) \ ENTRY(COMMAND2, command2, 0x02) \ ... ENTRY(COMMANDX, commandX, 0x0X) One nice thing about having contiguous codes is that the test for index validity is simple: ASSERT(command < N_COMMANDS); command_jump_table[command](); When command codes are not contiguous, you need to ensure that you dont jump to a non-existing function. The simplest way to do this is to make the jump table large enough to support every possible index. However, this is at the expense of memory. You will need 512 bytes (assuming an 8-bit command code size) if the architecture uses 16-bit pointers or 1024 bytes if it uses 32-bit pointers. With this implementation, instead of the simple ASSERT statement, define an error handling function that can be embedded into the jump table for all invalid commands. Here is how to do that with x macros, together with some other preprocessor trickery: #define INIT_X1 #define INIT_X2 #define INIT_X4 #define INIT_X8 #define INIT_X16 #define INIT_X32 #define INIT_X64 #define INIT_X128 #define INIT_X256 process_reserved, INIT_X1 INIT_X1 INIT_X2 INIT_X2 INIT_X4 INIT_X4 INIT_X8 INIT_X8 INIT_X16 INIT_X16 INIT_X32 INIT_X32 INIT_X64 INIT_X64 INIT_X128 INIT_X128

http://www.embedded.com/print/4408127

3/15/2013

Page 2 of 5

#define EXPAND_JUMP_TABLE(a,b,c) [c] = process_##b, static const p_func_t command_jump_table[256] = { /* initialize all pointer to the reserved function */ INIT_X256, /* overwrite pointers to valid functions */ COMMAND_TABLE(EXPAND_AS_JUMP_TABLE) }; This requires the use of a C99 compiler for the designated initializer syntax, furthermore the compiler may warn you that you are overriding a previously initialized value. If a C99 compiler is not available, then the valid function pointers can be updated at run time, at the expense of not being able to place the jump table in ROM. Here's how to this: #define EXPAND_JUMP_TABLE(a,b,c) \ command_jump_table[c] = process_##b; /* during run-time initialization */ COMMAND_TABLE(EXPAND_AS_JUMP_TABLE) Optimizing jump tables For many applications where memory is limited, this implementation is not practical. What can be done instead is to add an extra level of indirection as follows: command_jump_table[command_offset_table[command]](); In this case a second table is utilized that has one entry for every possible command code. Each entry contains an offset into the actual jump table. The memory requirements for this implementation are significantly less than the initial implementation. We need 256 bytes for our offset table, but only 2 or 4 bytes for each function pointer in the jump table. Therefore implementing a communications handler with a dozen commands would need 280/304 bytes instead of 512/1024. One side-effect of this implementation is that we need to reserve one of the command codes since invalid commands need to resolve to a valid offset so that the correct error handler can be called. The most logical offset value to use is zero (simplifies initialization) and thus the new command table looks like this: /* ------ NAME ------- FUNCTION --- CODE --- */ #define COMMAND_TABLE \ ENTRY(RESERVED, reserved, 0x00) \ ENTRY(COMMANDA, commandA, 0x02) \ ENTRY(COMMANDB, commandB, 0x09) \ ... ENTRY(COMMANDZ, commandZ, 0xef) A nice side-effect of this implementation is that it is actually easier to automate the creation of these two separate tables than it is to automate the creation of the single jump table.

http://www.embedded.com/print/4408127

3/15/2013

Page 3 of 5

The creation of the jump table has not changed from our original example with contiguous command codes. The caveat being that we can no longer use the table directly via the command code. #define EXPAND_AS_JUMP_TABLE(a,b,c) process_##b, static const p_func_t command_jump_table[N_COMMANDS] = { COMMAND_TABLE(EXPAND_AS_JUMP_TABLE) }; The creation of the offset table is as follows (recall the following struct from last month): #define EXPAND_AS_STRUCT(a,b,c) uint8_t b, typedef struct{ COMMAND_TABLE(EXPAND_AS_STRUCT) } size_struct_t; #define N_COMMANDS sizeof(size_struct_t) This struct can be used for multiple purposes. In addition to taking the size of the struct, we can calculate the offsets of each element in the struct to use when initializing the offset table: #define EXPAND_AS_OFFSET_TABLE_INITIALIZER(a,b,c) \ [c] = (uint8_t) offsetof(size_struct_t, b); where offsetof() is a standard library macro defined in "stddef.h". If the reader is unfamiliar with the offsetof() macro I highly recommend reading the article by Nigel Jones, Learn a new trick with the offsetof() macro. Use this new x macro do the following: /* statically declare our offset table, valid commands initialized to the correct offsets and invalid command initialized to 0 */ uint8_t command_offset_table[256] = { COMMAND_TABLE(EXPAND_AS_OFFSET_TABLE_INITIALIZER) }; As before, this macro expansion relies on a C99 compiler. If a C99 is not available, do the following: uint8_t command_offset_table[256] = {0}; #define EXPAND_AS_OFFSET_TABLE_INITIALIZER(a,b,c) \ command_offset_table[c] = (uint8_t) offsetof(size_struct_t, b); /* during run-time initialization */ COMMAND_TABLE(EXPAND_AS_OFFSET_TABLE_INITIALIZER) A nice side-effect of the offset table implementation is that if we need to implement a function which identifies if a given code is a valid command; the resulting function is a one-liner: bool command_is_valid(uint8_t command){

http://www.embedded.com/print/4408127

3/15/2013

Page 4 of 5

return command_offset_table[command]; }

Page 2 of 2 Timeout Tables Another common table that can be similarly initialized is a timeout table. In the case of a master processor instead of a slave processor, you need to allow a set period of time for the slave to respond before determining that a communications fault has occurred. Usually different commands will have differing requirements for this time, so implementing a look-up table for each command is necessary. Modifying the x macro table, we can add this functionality easily: /* ------ NAME ------- FUNCTION --- CODE --- TIMEOUT(ms) ---*/ #define COMMAND_TABLE \ ENTRY(RESERVED, reserved, 0x00, 0) \ ENTRY(COMMANDA, commandA, 0x02, 100) \ ENTRY(COMMANDB, commandB, 0x09, 500) \ ... ENTRY(COMMANDZ, commandZ, 0xef, 20) #define EXPAND_AS_TIMEOUT_TABLE_INITIALIZER(a,b,c,d) d, uint16_t timeout_table[N_COMMANDS] = { COMMAND_TABLE(EXPAND_AS_TIMEOUT_TABLE_ININTIALIZER) }; Here is how I would use this table: int get_command_timeout(uint8_t command) { return timeout_table[command_offset_table[command]]; }; GPIO Configuration One final area is the use of x macros to allow for code reuse by making it simple to change what gpio pins are used by a module. As an example consider the following table: #define SWITCHES_GPIO_TABLE(ENTRY) \ ENTRY(SWITCHA, GPIOE, GPIO_Pin_0) \ ENTRY(SWITCHB, GPIOA, GPIO_Pin_0) ENTRY(SWITCHC, GPIOC, GPIO_Pin_5) ENTRY(SWITCHD, GPIOE, GPIO_Pin_1) I will leave it to the reader as an exercise, but from this table, as long as the supporting code follows a few conventions, all you have to do is change the port and pin name in the table and all of the initialization code adjusts accordingly, making it easy to port over to a new design. Final Thoughts

http://www.embedded.com/print/4408127

3/15/2013

Page 5 of 5

I hope you have enjoyed this overview of x macros and that you have learned something new in the process. Though an advanced technique, once in place the use of x macros can reduce errors and make the embedded programmer's life easier. I would love to read in the comments how readers are using x macros in their designs. Read Part 1 Read Part 2 Andrew Lucasleads a team of firmware developers at NCR Canada, where he is responsible for the firmware architecture of intelligent deposit modules for NCR's line of ATMs.

http://www.embedded.com/print/4408127

3/15/2013

You might also like