blob: 788e7aead3d4f26c3fe0043119feebb1a91fad75 [file] [log] [blame]
How to Use Event Queues to Manage Multiple Events
=================================================
.. contents::
:local:
:depth: 2
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 :doc:`Event Queues <../../../os/core_os/event_queue/event_queue>`, the :doc:`Hardware Abstraction Layer <../../../os/modules/hal/hal>`, and :doc:`OS Callouts <../../../os/core_os/callout/callout>` in the OS Users 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 applications 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 :doc:`nRF52 tutorial for Blinky </tutorials/blinky/nrf52>` to create a project.
Create the Application
~~~~~~~~~~~~~~~~~~~~~~
Create the ``pkg.yml`` file for the application:
.. code-block:: console
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:
.. code-block:: c
/* 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``:
.. code-block:: c
/* 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:
.. code-block:: c
#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.
.. code-block:: c
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``:
.. code-block:: c
/* 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:
.. code-block:: c
#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:
.. code-block:: c
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:
.. code-block:: c
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``:
.. code-block:: c
/* 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:
.. code-block:: c
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:
.. code-block:: c
/* 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.
.. code-block:: c
#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);
}