This article continues the discussion in Part 1 regarding the use of X macros. Here I will examine several more uses of X macros that relieve the programmer from having to manually enforce dependencies and perform data structure sizing calculations.

Counting Entries

Consider the following use case for populating enumerations and jump tables:

/* declare an enumeration of state codes */

enum{

STATE_TABLE(EXPAND_AS_ENUMERATION)

NUM_STATES

}

/* declare a table of function pointers */

p_func_t jumptable[NUM_STATES] = {

STATE_TABLE(EXPAND_AS_JUMPTABLE)

}

There is one thing that I still don’t like about this implementation; the NUM_STATES hack. I call it a hack because although a common technique for defining a count of items, NUM_STATES does not belong inside the enumeration. We could define it as a separate macro, but this requires the developer to remember to update it whenever the enumeration changes.

We can use X macros to place the NUM_STATES macro outside of the enumeration, while at the same time have the pre-processor update it automatically. We start off by declaring a struct as follows:

#define EXPAND_AS_STRUCT(a,b) uint8_t b,

/* declare a struct with a 1-byte field for every entry */

typedef struct{

STATE_TABLE(EXPAND_AS_STRUCT)

} size_struct_t ;

Now if we apply the size of operator to this struct, the compiler will calculate the number of states, since each state is represented by a one-byte field and the sizeof operator returns the total number of bytes:

#define NUM_STATES sizeof(size_struct_t)

The concept is that there are valid reasons to define a struct but not instantiate it. We don’t need to create a variable of type size_struct_t for it to be useful.

A little more verbose perhaps than the previous hack, but we succeeded in having the preprocessor automatically generate a NUM_STATES macro without having to pollute the enumeration. A note of caution; this method depends on the compiler not inserting any padding bytes into size_struct_t , so understand your compiler.

Communication Handlers

In my mind this is the “killer app” when it comes to X macro usage. The benefit it provides to the programmer is significant. Here is the general technique:

First create a table containing command names and codes. For our purposes we will ensure that command codes are contiguous:

/* —— NAME ——- FUNCTION — CODE — */

#define COMMAND_TABLE

ENTRY(COMMAND0, command0, 0x00)

ENTRY(COMMAND1, command1, 0x01)

ENTRY(COMMAND2, command2, 0x02)

…

ENTRY(COMMANDX, commandX, 0x0X

Both uppercase and lowercase names are in the table, because the upper case names will be used for enumerations while the lowercase will be used for function names.

Next define the command code enumeration:

#define EXPAND_AS_COMMAND_CODE_ENUM(a,b,c) a##_CMD = c,

enum{

COMMAND_TABLE(EXPAND_AS_COMMAND_CODE_ENUM)

};

If you are not familiar with token concatenation syntax, the ‘ ## ’ simply appends ‘ _CMD ’ onto the end of each enumeration name. In this case the table would be expanded as follows:

enum{

COMMAND0_CMD,

COMMAND1_CMD,

COMMAND2_CMD,

…

COMMANDX_CMD,

};

Next define structs for each command and response:

typedef struct {uint8_t command; …}command1_cmd_t;

typedef struct {uint8_t command; …}command2_cmd_t;

etc…

typedef struct {uint8_t command; …}command1_resp_t;

typedef struct {uint8_t command; …}command2_resp_t;

etc…

Only the command code fields are shown, but presumably there will be other fields defined for each command (length field, optional data fields, crc field etc.).

Now use X macros to define an enumeration of command and response lengths by applying the sizeof operator to the structs. These enumerations will be used in the message handler functions to verify that incoming message lengths are correct and to populate potential length fields in outgoing messages:

#define EXPAND_AS_COMMAND_LEN_ENUM(a,b,c)

a##_CMD_LEN = sizeof(b##_cmd_t),

enum{

COMMAND_TABLE(EXPAND_AS_COMMAND_LEN_ENUM)

};

#define EXPAND_AS_RESPONSE_LEN_ENUM(a,b,c)

a##_RESP_LEN = sizeof(b##_resp_t),

enum{

COMMAND_TABLE(EXPAND_AS_RESPONSE_LEN_ENUM)

};



As previously explained, determine how many commands there are:

#define EXPAND_AS_STRUCT(a,b,c) uint8_t b,

typedef struct{

COMMAND_TABLE(EXPAND_AS_STRUCT)

} size_struct_t;

#define NUMBER_OF_COMMANDS sizeof(size_struct_t)

Finally generate the table of function pointers and prototypes. As a reminder, the jumptable will only be correct if the command codes are contiguous.

#define EXPAND_AS_JUMPTABLE(a,b,c) process_##b,

#define EXPAND_AS_PROTOTYPES(a,b,c) void process_##b(void);

p_func_t jump_table[NUMBER_OF_COMMANDS] = {

COMMAND_TABLE(EXPAND_AS_JUMPTABLE)

}

COMMAND_TABLE(EXPAND_AS_PROTOTYPES)



With this implementation, when a new command needs to be added the following three steps should be followed.

Add a new row to COMMAND_TABLE . Define the structure of the command and response messages called commandX_cmd_t and commandX_resp_t . Write a function to handle the new command called “ process_commandX() ”.

The beauty of this is that even though there are three steps, if any are forgotten we will get warnings or errors when attempting to compile. If the structures are not defined, the compile will fail during creation of the enumeration of data lengths. If the command handler function is not written, depending on the compiler it may fail during compilation, but if not it should provide a warning during linking that there is an unresolved external.

One More Thing

Every comms handler is going to have buffers it uses to hold the sent and received messages. The question is, how big should the buffers be? Here is yet another instance where X macros can do the heavy lifting for us.

Since we have defined the format of every command and response, what we want is for the compiler to determine what the largest message is so we can size the buffers appropriately. Here is how you can do that.

#define EXPAND_AS_BUFFERS(a,c,b) uint8_t b##_buf[sizeof(b##_cmd_t)];

typedef union{

COMMAND_TABLE(EXPAND_AS_BUFFERS)

}tx_buf_t

In this case we are using a union, because by definition the size of a union is as large as its largest member. Inside the union we drop via X macros an array of bytes for each command, where the size of the array is the size of the command’s struct . This union is like the size struct – it is not instantiated. Instead we can use the sizeof operator to declare our transmit buffer size.

/* declare transmit buffer */

uint8_t tx_buf[sizeof(tx_buf_t)];

Now my transmit buffer tx_buf is the optimal size and as I add commands to the comms handler table, my buffer will always be adjusted to the perfect size.

Next Time

In my next article I will discuss how to handle a case where command codes are not contiguous. I will also examine a memory optimization technique where we can use X macros to save valuable memory when jump tables are sparsely populated.

Read Part 1

Read Part 3

Andrew Lucas leads a team of firmware developers at NCR Canada. He is responsible for the firmware architecture of their intelligent deposit modules found inside NCRs line of ATMs.

Share this: Twitter

Facebook

LinkedIn

More

Reddit

Tumblr



Pinterest

WhatsApp



Skype

Pocket



Telegram

