Simple dispatcher for an embedded system This code is something that I have used for about 3-4 simple embedded systems projects.

The idea is something like this:


This is non-OS based implementation, everything is done in an outer infinite loop. No dynamic memory involved or advised.
It's an interrupt-driven system, polling for peripherals is not used anywhere.
Peripherals like Timer, UART, I2C, etc loads a particular value based on the event, in a Queue-like variable.
Non-trivial tasks like blinking an LED is done in a common Idle task.
Dispatcher loads the function handlers based on the event added in the queue.
(UPDATE) All read/write activities on CPU registers are managed in the respective Interrupt Service Routine. Adding the event in queue is possibly the last thing the code does in the ISR.


Enum for possible events:

typedef enum {

    TASK_EMPTY = 0,
    TASK_INITIALIZE = 1,
    TASK_IDLE,
    TASK_SOMETHING_1,
    TASK_SOMETHING_2,
    TASK_SOMETHING_3,
    TASK_SOMETHING_4,
    TASK_SOMETHING_N,

    MAX_TASKS,
    TASK_INVALID = -1,

} eTasks_t ;


A constant array which contains the event mapped to the handler:

const stTask_t GlobalTasksList[MAX_TASKS] =
{
    { TASK_EMPTY,              Placeholder             },
    { TASK_INITIALIZE,         SystemInitialization    },
    { TASK_IDLE,               Idle                    },
    { TASK_SOMETHING_1,        Handler_1               },
    { TASK_SOMETHING_2,        Handler_2               },
    { TASK_SOMETHING_3,        Handler_3               },
    { TASK_SOMETHING_4,        Handler_4               },
    { TASK_SOMETHING_N,        Handler_N               },
};


I talked about a Queue:

eTasks_t Queue[MAX_TASKS];
eTasks_t qTasksToAdd[MAX_TASKS * 2];


There is a structure which binds an event and it's handler together:

typedef struct stTask {

    eTasks_t TaskID;
    bool (*handler) (void);   // Would return true for success, and false for failure

} stTask_t; 

stTask_t Task;


A Dispatcher function will take care of queued events:

void Dispatcher(void)
{
    while(1)
    {
        // Assign the task handler
        Task.handler = GlobalTasksList[Task.TaskID].handler;

        //Invoke handler
        if(FAILURE == Task.handler())
        {
            //Something went wrong with handler, Error!
            // Error handling

        }

        //Does the task needs a retry, or is a recurring task?
        // Reload if yes, else discard and load the next
        if(bTaskRetry == FALSE)
        {
            Queue_DiscardTask0();
        }

        //Assign the 0th task from Queue
        Task.TaskID = Queue[0];
    }
}


Let's look at an example of adding the event in the Queue from an ISR:

#pragma vector=PORT2_VECTOR
__interrupt void PORT2_Interrupt(void)
{
    // Clear interrupt
    CLR_BIT(P2IFG, 2);  //P2IFG is a CPU register which indicates the Port pin that has generated an interrupt. 
    // Check if the queue is overflowing
    if(qIndexTasksToAdd &lt; MAX_TASKS-1)
    {
        // Add event
        qTasksToAdd[qIndexTasksToAdd++] = TASK_SOMETHING_1;
    }
}


As of now, this strategy works fine without any questionable latency.
I have used the same skeleton for systems that could have as many as 20 events lined-up, and it works as per requirement to say the least.

Another advantage of this implementation that I felt is in regards with the efficiency this has when I am debugging an issue. Everything is routed through the handler/dispatcher, so I precisely know where to have a debug point/print setup.

The concern that I have with this implementation is the fixed memory footprint is has. For example, this won't be memory efficient for a LED blinking project.

Any thoughts where I could improve on this?
.... have as many as 20 events lined-up, and it works as per requirement
  Any thoughts where I could improve on this?


Stress test

The queue approach is non-deterministic and would benefit in knowing how much extra CPU is left.

Presently the processor is ample and the cumulative task list is low.  There is spare CPU power.  Yet let us stress test this design to find out how close and how code handles falling behind.

Add code to detect when Dispatcher() is starving.  Did every task get serviced at least once every 0.1s say?

Append a do-nothing task that consumes a x% of CPU time, call it Administratium.    Did the queue ever fail to add because it was full?  What was the maximum queue usage?  Was task latency too great?

Armed with this adjustable idle task, preliminary testing can gauge the degree of margin in the design.