|
Branch Tables via Function Pointer Arrays in C |
|
|
|
|
Written by Hemanshu Patel
|
|
Saturday, 24 November 2007 |
|
Page 4 of 6
Communication protocols Although the keypad example is easy to appreciate, my experience in embedded systems is that communication links occur far more often than keypads. Communication protocols are a challenge ripe for a branch table solution. This is best illustrated by an example.
Last year, I worked on the design for an interface box to a very large industrial power supply. This interface box had to accept commands and return parameter values over a RS-232 link. The communications used a set of simple ASCII mnemonics to specify the action to be taken. The mnemonics consisted of a channel number (0,1, or 2), followed by a two character parameter. The code to handle a read request coming in over the serial link is shown below. The function process_read() is called with a pointer to a string fragment that is expected to consist of the three characters (null terminated) containing the required command.
const CHAR *fna(void); //Example function prototype static void process_read(const CHAR *buf) { CHAR *cmdptr; UCHAR offset; const CHAR *replyptr; static const CHAR read_str[] = "0SV 0SN 0MO 0WF 0MT 0MP 0SW 1SP 1VO 1CC 1CA 1CB 1ST 1MF 1CL 1SZ 1SS 1AZ 1AS 1BZ 1BS 1VZ 1VS 1MZ 1MS 2SP 2VO 2CC 2CA 2CB 2ST 2MF 2CL 2SZ 2SS 2AZ 2AS 2BZ 2BS 2VZ 2VS 2MZ 2MS ";
static const CHAR * (* const readfns[sizeof(read_str)/4])(void) = { fna,fnb,fnc, … }; cmdptr = strstr(read_str, buf); if (cmdptr != NULL) { /* cmdptr points to the valid command, so compute offset, in order to get entry into function jump table */ offset = (cmdptr - read_str) / 4;
/* Call function, & get pointer to reply*/ replyptr = (*readfns[offset])(); /* rest of the code goes here */ } }
The code above is quite straightforward. A constant string, read_str, is defined. The read_str contains the list of all legal mnemonic combinations. Note the use of added spaces to aid clarity. Next, we have the array of function pointers, one pointer for each valid command. We determine if we have a valid command sequence by making use of the standard library function strstr(). If a match is found, it returns a pointer to the matching substring, else it returns NULL. We check for a valid pointer, compute the offset into the string, and then use the offset to call the appropriate handler function in the jump table. Thus, in four lines of code, we have determined if the command is valid and called the appropriate function. Although the declaration of readfns[] is complex, the simplicity of the runtime code is tough to beat.
Timed task list A third area where function pointers are useful is in timed task lists. In this case, the input to the system is the passage of time. Many projects cannot justify the use of an RTOS. Instead, all that is required is that a number of tasks run at predetermined intervals. This is very simply handled as shown below.
typedef struct { UCHAR interval; /* How often to call the task */ void (*proc)(void); /* pointer to function returning void */ }TIMED_TASK;
static const TIMED_TASK timed_task[] = { {INTERVAL_16_MSEC, fnA}, {INTERVAL_50_MSEC, fnB}, {INTERVAL_500_MSEC, fnC}, … {0,NULL} };
extern volatile UCHAR tick;
void main(void) { const TIMED_TASK *ptr; UCHAR time; /* Initialization code goes here. Then enter the main loop */
while (1) { if (tick) { /* Check timed task list */ tick--; time = computeElapsedTime(tick); for (ptr = timed_task; ptr->interval !=0; ptr++) if (!(time % ptr->interval)) (ptr->proc)(); /* Time to call the function */ } } }
In this case, we define our own data type (TIMED_TASK) that consists simply of an interval and a pointer to a function. We then define an array of TIMED_TASK, and initialize it with the list of functions that are to be called and their calling interval. In main(), we have the start up code which must enable a periodic timer interrupt that increments the volatile variable tick at a fixed interval. We then enter the infinite loop.
The infinite loop checks for a non-zero tick value, decrements the tick variable and computes the elapsed time since the program started running. The code then simply steps through each of the tasks, to see whether it is time for that one to be executed and, if so, calls it via the function pointer.
If your application only consists of two or three tasks, then this approach is probably overkill. However, if your project has a large number of timed tasks, or it is likely that you will have to add tasks in the future, then this approach is rather palatable. Note that adding tasks and/or changing intervals simply requires editing of the timed_task[] array. No code, per se, has to be changed.
|
|
| |
|
|