Thursday, May 8, 2014

The Main Event - A Simple Event Handler for Microcontrollers

Every application has some type of event handler. Typically, an interrupt will occur, and the interrupt service routine (ISR) will then set a flag, and then the ISR will exit to return to the normal application. This approach is used to minimize the time spent in the ISR. Next, in the main application, the event flags are read, and if any were set then the application processes each event.

I've been looking through the Anaren Bluetooth Low Energy software lately, and I saw this rather cool event handler. It uses a table of function pointers that are the functions to be called when the event occurs.

The Event Handler Functions

We will create a typedef for our function pointer. All functions in our Event Handler Table need to be this type.
/* Typedef for the event handler functions that will go into our table */
typedef void (*Hal_Handler)(void);

Next, for convenience we also create a type for the event flag bitmask. For speed, make this the same as the processor's data size.
/* The type of integer that we will use for our event bitflag. uint32 = 32 possible events */
typedef uint32_t EventFlags;

We also create the global variable that will be the bitflag of any events that get set.
/* Bitflag of the events that need to be processed */
static volatile EventFlags handlerEvents = 0;

To complete the initialization, we define a few events, and create our event handler table.
#define NUM_HANDLERS 3
#define BUTTON_HANDLER_ID      0
#define TICK_HANDLER_ID        1
#define DISPATCH_HANDLER_ID    2

#define KNOB_INCREMENT_HANDLER_ID  3
#define KNOB_DECREMENT_HANDLER_ID  4

/* Table of handler function pointers */
static Event_Handler handlerTable[NUM_HANDLERS];

This table will contain function pointers for the function that gets called when the corresponding event gets called. For example, when the knob gets rotated clockwise, its ISR will set bit #3 in the handlerEvents function. Then the main loop will call the function that is located at handlerTable[3].

We initialize our list by adding event handlers to the event handler table using the method below:
 int addEventHandler(uint8_t index, Event_Handler handler)
 {
     if (index > NUM_HANDLERS)
     {
         return -1;
     }
     handlerTable[index] = handler;
     return 0;
 }

We then call this in our main() function, which we'll explain in a bit.

Set a flag when an event occurs

When an event happens, in the ISR we need to set the bitflag that corresponds to that event. This is done in the postEvent() method as shown below. This bitflag will be read later in our main event handling loop. For safety, we disable interrupts while we are modifying the handlerEvents variable.
/** Set the corresponding bitflag for the eventId */
void postEvent(uint8_t handlerId)
{
    //Note: you should check that handlerId < NUM_HANDLERS here
    IntMasterDisable();
    handlerEvents |= 1 << handlerId;
    IntMasterEnable();
}

In the ISRs, you will need to add a call to postEvent. For example, in a button ISR, add:
postEvent(BUTTON_HANDLER_ID);

Configuring The Actions

When the bitflag for an event gets set, we want the event handler to do something. It does so by calling a function pointer that points to the function that we want to get called when that event happens. For example, when a button is pressed we just want to tell the user. Same for knob, as shown below:
void buttonHandler(void)
{
    printf("Button Pressed!\r\n");
}
void knobIncrementHandler(void)
{
    printf("Knob Up!\r\n");
}
void knobDecrementHandler(void)
{
    printf("Knob Down!\r\n");
}

Now, we need these in the Event Table so in main() we add them with the function we created earlier:

    addEventHandler(BUTTON_HANDLER_ID, buttonHandler);
    addEventHandler(KNOB_INCREMENT_HANDLER_ID, knobIncrementHandler);
    addEventHandler(KNOB_DECREMENT_HANDLER_ID, knobDecrementHandler);

Now we've configured the event setting, and the event processing, so now we can implement the main event handler.

The Main Event (handler)

The Main Event Handler is fairly simple. It continually loops, and processes any events that were set by postEvent(). If a bitflag was set, then it calls the corresponding function pointer.
void eventHandler(void)
{
    IntMasterEnable();
    for (;;)
    {
        /* First, disable interrupts so we don't get messed up while we are reading events */
        IntMasterDisable();

        /* While interrupts are disabled, copy the current list of events that need to be processed */
        EventFlags events = handlerEvents;

        /* ... and clear the master bitflag of events */
        handlerEvents = 0;

        /* Now, process any events that need to be processed */
        if (events)
        {   // dispatch all current events
            IntMasterEnable();     // Enable interrupts - note that we do this before calling each handler function
            /* mask will be the bitflag that we use to check the events bitflag. Starts at 0x1, then 0x2, 0x4, 0x8 etc. */
            uint16_t mask;
            /* The numeric index of the event - 0 through 31 */
            uint8_t id;
            /** Iterate through all possible events */
            for (id = 0, mask = 0x1; id < NUM_HANDLERS; id++, mask <<= 1)
            {
                if ((events & mask) && handlerTable[id]) // If we need to process the event
                {                                      // AND there is an event handler for it
                    handlerTable[id]();                  // ... Then process the event
                }
            }
        } else {          // No events to handle, so wait for more
            /* If using a battery powered device, you could go back to sleep here. Just be sure to enable interrupts! */
            IntMasterEnable();        // Enable Interrupts
        }
    }
}





No comments:

Post a Comment