How to Use Event Queues to Manage Multiple Events

Introduction

The event queue mechanism allows you to serialize incoming events for your task. You can use it to get information about hardware interrupts, callout expirations, and messages from other tasks.

The benefit of using events for inter-task communication is that it can reduce the number of resources that need to be shared and locked.

The benefit of processing interrupts in a task context instead of the interrupt context is that other interrupts and high priority tasks are not blocked waiting for the interrupt handler to complete processing. A task can also access other OS facilities and sleep.

This tutorial assumes that you have read about Event Queues, the Hardware Abstraction Layer, and OS Callouts in the OS User’s Guide.

This tutorial shows you how to create an application that uses events for:

  • Inter-task communication
  • OS callouts for timer expiration
  • GPIO interrupts

It also shows you how to:

  • Use the Mynewt default event queue and application main task to process your events.
  • Create a dedicated event queue and task for your events.

To reduce an application’s memory requirement, we recommend that you use the Mynewt default event queue if your application or package does not have real-time timing requirements.

Prerequisites

Ensure that you have met the following prerequisites before continuing with this tutorial:

  • Install the newt tool.
  • Install the newtmgr tool.
  • Have Internet connectivity to fetch remote Mynewt components.
  • Install the compiler tools to support native compiling to build the project this tutorial creates.
  • Have a cable to establish a serial USB connection between the board and the laptop.

Example Application

In this example, you will write an application, for the Nordic nRF52 board, that uses events from three input sources to toggle three GPIO outputs and light up the LEDs. If you are using a different board, you will need to adjust the GPIO pin numbers in the code example.

The application handles events from three sources on two event queues:

  • Events generated by an application task at periodic intervals are added to the Mynewt default event queue.
  • OS callouts for timer events are added to the my_timer_interrupt_eventq event queue.
  • GPIO interrupt events are added to the my_timer_interrupt_eventq event queue.

Create the Project

Follow the instructions in the nRF52 tutorial for Blinky to create a project.

Create the Application

Create the pkg.yml file for the application:

pkg.name: apps/eventq_example
pkg.type: app

pkg.deps:
    - kernel/os
    - hw/hal
    - sys/console/stub

Application Task Generated Events

The application creates a task that generates events, at periodic intervals, to toggle the LED at pin TASK_LED. The event is queued on the Mynewt default event queue and is processed in the context of the application main task.

Declare and initialize the gen_task_ev event with the my_ev_cb() callback function to process the event:

/* Callback function for application task event */
static void my_ev_cb(struct os_event *);

/* Initialize the event with the callback function */
static struct os_event gen_task_ev = {
    .ev_cb = my_ev_cb,
};

Implement the my_ev_cb() callback function to process a task generated event and toggle the LED at pin TASK_LED:

/* LED 1 (P0.17 on the board) */
#define TASK_LED        17

/*
 * Event callback function for events generated by gen_task. It toggles
 * the LED at pin TASK_LED.
 */
static void my_ev_cb(struct os_event *ev)
{
    assert(ev);
    hal_gpio_toggle(TASK_LED);
    return;
}

Create a task that generates an event at periodic intervals and adds, using the os_eventq_put() function, the event to the Mynewt default event queue:

#define GEN_TASK_PRIO       3
#define GEN_TASK_STACK_SZ   512

static os_stack_t gen_task_stack[GEN_TASK_STACK_SZ];
static struct os_task gen_task_str;

/*
 * Task handler to generate an event to toggle the LED at pin TASK_LED.
 * The event is added to the Mynewt default event queue.
 */
static void
gen_task(void *arg)
{
    while (1) {
        os_time_delay(OS_TICKS_PER_SEC / 4);
        os_eventq_put(os_eventq_dflt_get(), &gen_task_ev);
    }
}

static void
init_tasks(void)
{

    /* Create a task to generate events to toggle the LED at pin TASK_LED */

    os_task_init(&gen_task_str, "gen_task", gen_task, NULL, GEN_TASK_PRIO,
                 OS_WAIT_FOREVER, gen_task_stack, GEN_TASK_STACK_SZ);

      ...

}

Implement the application main() function to call the os_eventq_run() function to dequeue an event from the Mynewt default event queue and call the callback function to process the event.

int
main(int argc, char **argv)
{
    sysinit();

    init_tasks();

    while (1) {
       os_eventq_run(os_eventq_dflt_get());
    }
    assert(0);
}

OS Callout Timer Events

Set up OS callout timer events. For this example, we use a dedicated event queue for timer events to show you how to create a dedicated event queue and a task to process the events.

Implement the my_timer_ev_cb() callback function to process a timer event and toggle the LED at pin CALLOUT_LED:

/* LED 2 (P0.18 on the board) */
#define CALLOUT_LED     18

/* The timer callout */
static struct os_callout my_callout;

/*
 * Event callback function for timer events. It toggles the LED at pin CALLOUT_LED.
 */
static void my_timer_ev_cb(struct os_event *ev)
{
    assert(ev != NULL);

    hal_gpio_toggle(CALLOUT_LED);

    os_callout_reset(&my_callout, OS_TICKS_PER_SEC / 2);
}

In the init_tasks() function, initialize the my_timer_interrupt_eventq event queue, create a task to process events from the queue, and initialize the OS callout for the timer:

#define MY_TIMER_INTERRUPT_TASK_PRIO  4
#define MY_TIMER_INTERRUPT_TASK_STACK_SZ    512

static os_stack_t my_timer_interrupt_task_stack[MY_TIMER_INTERRUPT_TASK_STACK_SZ];
static struct os_task my_timer_interrupt_task_str;

static void
init_tasks(void)
{
    /* Use a dedicate event queue for timer and interrupt events */

    os_eventq_init(&my_timer_interrupt_eventq);

    /*
     * Create the task to process timer and interrupt events from the
     * my_timer_interrupt_eventq event queue.
     */
    os_task_init(&my_timer_interrupt_task_str, "timer_interrupt_task",
                 my_timer_interrupt_task, NULL,
                 MY_TIMER_INTERRUPT_TASK_PRIO, OS_WAIT_FOREVER,
                 my_timer_interrupt_task_stack,
                 MY_TIMER_INTERRUPT_TASK_STACK_SZ);
     /*
      * Initialize the callout for a timer event.
      * The my_timer_ev_cb callback function processes the timer events.
      */
    os_callout_init(&my_callout, &my_timer_interrupt_eventq,
                    my_timer_ev_cb, NULL);

    os_callout_reset(&my_callout, OS_TICKS_PER_SEC);

}

Implement the my_timer_interrupt_task() task handler to dispatch events from the my_timer_interrupt_eventq event queue:

static void
my_timer_interrupt_task(void *arg)
{
    while (1) {
        os_eventq_run(&my_timer_interrupt_eventq);
    }
}

Interrupt Events

The application toggles the LED each time button 1 on the board is pressed. The interrupt handler generates an event when the GPIO for button 1 (P0.13) changes state. The events are added to the my_timer_interrupt_eventq event queue, the same queue as the timer events.

Declare and initialize the gpio_ev event with the my_interrupt_ev_cb() callback function to process the event:

static struct os_event gpio_ev {
    .ev_cb = my_interrupt_ev_cb,
};

Implement the my_interrupt_ev_cb() callback function to process an interrupt event and toggle the LED at pin GPIO_LED:

/* LED 3 (P0.19 on the board) */
#define GPIO_LED     19

/*
 * Event callback function for interrupt events. It toggles the LED at pin GPIO_LED.
 */
static void my_interrupt_ev_cb(struct os_event *ev)
{
    assert(ev != NULL);

    hal_gpio_toggle(GPIO_LED);
}

Implement the my_gpio_irq() handler to post an interrupt event to the my_timer_interrupt_eventq event queue:

static void
my_gpio_irq(void *arg)
{
    os_eventq_put(&my_timer_interrupt_eventq, &gpio_ev);
}

In the init_tasks() function, add the code to set up and enable the GPIO input pin for the button and initialize the GPIO output pins for the LEDs:

/* LED 1 (P0.17 on the board) */
#define TASK_LED        17

/*  2 (P0.18 on the board) */
#define CALLOUT_LED     18

/* LED 3 (P0.19 on the board) */
#define GPIO_LED        19

/* Button 1 (P0.13 on the board) */
#define BUTTON1_PIN     13

void
init_tasks()

    /* Initialize OS callout for timer events. */

          ....

    /*
     * Initialize and enable interrupts for the pin for button 1 and
     * configure the button with pull up resistor on the nrf52dk.
     */
    hal_gpio_irq_init(BUTTON1_PIN, my_gpio_irq, NULL, HAL_GPIO_TRIG_RISING, HAL_GPIO_PULL_UP);

    hal_gpio_irq_enable(BUTTON1_PIN);

    /* Initialize the GPIO output pins. Value 1 is off for these LEDs.  */

    hal_gpio_init_out(TASK_LED, 1);
    hal_gpio_init_out(CALLOUT_LED, 1);
    hal_gpio_init_out(GPIO_LED, 1);
}

Putting It All Together

Here is the complete main.c source for your application. Build the application and load it on your board. The task LED (LED1) blinks at an interval of 250ms, the callout LED (LED2) blinks at an interval of 500ms, and the GPIO LED (LED3) toggles on or off each time you press Button 1.

#include <os/os.h>
#include <bsp/bsp.h>
#include <hal/hal_gpio.h>
#include <assert.h>
#include <sysinit/sysinit.h>


#define MY_TIMER_INTERRUPT_TASK_PRIO  4
#define MY_TIMER_INTERRUPT_TASK_STACK_SZ    512

#define GEN_TASK_PRIO       3
#define GEN_TASK_STACK_SZ   512

/* LED 1 (P0.17 on the board) */
#define TASK_LED        17

/* LED 2 (P0.18 on the board) */
#define CALLOUT_LED     18

/* LED 3 (P0.19 on the board) */
#define GPIO_LED        19

/* Button 1 (P0.13 on the board) */
#define BUTTON1_PIN     13


static void my_ev_cb(struct os_event *);
static void my_timer_ev_cb(struct os_event *);
static void my_interrupt_ev_cb(struct os_event *);

static struct os_eventq my_timer_interrupt_eventq;

static os_stack_t my_timer_interrupt_task_stack[MY_TIMER_INTERRUPT_TASK_STACK_SZ];
static struct os_task my_timer_interrupt_task_str;

static os_stack_t gen_task_stack[GEN_TASK_STACK_SZ];
static struct os_task gen_task_str;

static struct os_event gen_task_ev = {
    .ev_cb = my_ev_cb,
};

static struct os_event gpio_ev = {
    .ev_cb = my_interrupt_ev_cb,
};


static struct os_callout my_callout;

/*
 * Task handler to generate an event to toggle the LED at pin TASK_LED.
 * The event is added to the Mynewt default event queue.
 */

static void
gen_task(void *arg)
{
    while (1) {
        os_time_delay(OS_TICKS_PER_SEC / 4);
        os_eventq_put(os_eventq_dflt_get(), &gen_task_ev);
    }
}

/*
 * Event callback function for events generated by gen_task. It toggles the LED at pin TASK_LED.
 */
static void my_ev_cb(struct os_event *ev)
{
    assert(ev);
    hal_gpio_toggle(TASK_LED);
    return;
}

/*
 * Event callback function for timer events. It toggles the LED at pin CALLOUT_LED.
 */
static void my_timer_ev_cb(struct os_event *ev)
{
    assert(ev != NULL);

    hal_gpio_toggle(CALLOUT_LED);
    os_callout_reset(&my_callout, OS_TICKS_PER_SEC / 2);
}

/*
 * Event callback function for interrupt events. It toggles the LED at pin GPIO_LED.
 */
static void my_interrupt_ev_cb(struct os_event *ev)
{
    assert(ev != NULL);

    hal_gpio_toggle(GPIO_LED);
}

static void
my_gpio_irq(void *arg)
{
    os_eventq_put(&my_timer_interrupt_eventq, &gpio_ev);
}



static void
my_timer_interrupt_task(void *arg)
{
    while (1) {
        os_eventq_run(&my_timer_interrupt_eventq);
    }
}

void
init_tasks(void)
{

    /* Create a task to generate events to toggle the LED at pin TASK_LED */

    os_task_init(&gen_task_str, "gen_task", gen_task, NULL, GEN_TASK_PRIO,
        OS_WAIT_FOREVER, gen_task_stack, GEN_TASK_STACK_SZ);


    /* Use a dedicate event queue for timer and interrupt events */
    os_eventq_init(&my_timer_interrupt_eventq);

    /*
     * Create the task to process timer and interrupt events from the
     * my_timer_interrupt_eventq event queue.
     */
    os_task_init(&my_timer_interrupt_task_str, "timer_interrupt_task",
                 my_timer_interrupt_task, NULL,
                 MY_TIMER_INTERRUPT_TASK_PRIO, OS_WAIT_FOREVER,
                 my_timer_interrupt_task_stack,
                 MY_TIMER_INTERRUPT_TASK_STACK_SZ);

    /*
     * Initialize the callout for a timer event.
     * The my_timer_ev_cb callback function processes the timer event.
     */
    os_callout_init(&my_callout, &my_timer_interrupt_eventq,
                    my_timer_ev_cb, NULL);

    os_callout_reset(&my_callout, OS_TICKS_PER_SEC);

    /*
     * Initialize and enable interrupt for the pin for button 1 and
     * configure the button with pull up resistor on the nrf52dk.
     */
    hal_gpio_irq_init(BUTTON1_PIN, my_gpio_irq, NULL, HAL_GPIO_TRIG_RISING, HAL_GPIO_PULL_UP);

    hal_gpio_irq_enable(BUTTON1_PIN);

    hal_gpio_init_out(TASK_LED, 1);
    hal_gpio_init_out(CALLOUT_LED, 1);
    hal_gpio_init_out(GPIO_LED, 1);
}

int
main(int argc, char **argv)
{
    sysinit();

    init_tasks();

    while (1) {
       os_eventq_run(os_eventq_dflt_get());
    }
    assert(0);
}