Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions doc/guides/advanced_tutorials/event_queue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---
title: Working with Event Queues
description: This tutorial provides a quick introduction to using event queues in RIOT OS.
code_folder: examples/guides/event_queue/
---

## Introduction
An event queue is a FIFO (First In First Out) data structure that holds events
to be processed by an event handler.

Each event has exactly one callback function called the `handler`,
that is executed when the event is processed.
Posting an event to a queue triggers the execution of the event's handler
on the thread that owns the event queue.
Posting an event to a queue can be done from any thread or interrupt context.

This guide will give a quick introduction to using event queues in RIOT OS.
We will cover the following topics:
- [Regular Events](#regular-events)
- [Custom Events](#custom-events)
- [Handling Events](#handling-events)
- [Periodic Events](#periodic-events)

:::note
The whole source code for this guide can be found
[HERE](https://github.com/RIOT-OS/RIOT/tree/master/examples/guides/event_queue).

If your project is not working as expected,
you can compare your code with the code in this repository to see if you missed anything.
:::

### Regular Events
Creating and posting regular events is straightforward in RIOT.
First, we define the event handler function:
```c title="main.c"
static void regular_handler(event_t *event)
{
(void) event;
printf("\tTriggered regular event.\n");
}
```
Then, we can create an instance of the event:
```c title="main.c"
static event_t event = { .handler = regular_handler };
```
This event can now be posted to an event queue:
```c title="main.c"
event_post(&my_queue, &event);
```
:::tip
See [Handling Events](#handling-events) for information about setting up an event queue.
:::
### Custom Events
Custom events allow us to pass additional data to the event handler.
To create a custom event, we need to define a new struct that contains
an `event_t` member and any additional data we want to pass.
For example, let's create a custom event that passes a string message:
```c time="main.c"
typedef struct {
event_t super;
const char *text;
} custom_event_t;
```
Next, we define the event handler function for the custom event:
```c title="main.c"
static void custom_handler(event_t *event)
{
/* The handler receives a pointer to the base event structure.
* We need to get the pointer to our custom event structure.
*/
custom_event_t *custom_event = container_of(event, custom_event_t, super);
printf("\tTriggered custom event with text: \"%s\".\n", custom_event->text);
}
```
Notice how we do not get passed the `custom_event_t` pointer directly,
but rather the embedded `event_t` pointer.
To access the additional fields of our custom event, we use the `container_of`
macro to access the parent structure.

Then, we can create an instance of our custom event:
```c title="main.c"
static custom_event_t custom_event = { .super.handler = custom_handler,
.text = "CUSTOM EVENT" };
```

Posting a custom event means posting the embedded `event_t` member:
```c title="main.c"
event_post(&my_queue, &custom_event.super);
```
:::tip
See [Handling Events](#handling-events) for information about setting up an event queue.
:::
### Handling Events
It is common to have a dedicated thread for handling events.
A simple thread function that processes incoming events looks like this:
```c title="main.c"
void *event_handler_thread(void *arg)
{
assert(arg != NULL);
event_queue_t *queue = (event_queue_t *)arg;

/* A thread must own an event queue to process its events.
* Ownership is initially assigned to the thread that initializes the queue.
* It can later be transferred by calling `event_queue_claim`.
*/
event_queue_init(queue);

puts("Event handler thread listening for events...");
event_loop(queue);
return NULL;
}
```
:::note
The thread calling `event_queue_init` becomes the owner of the event queue. Only the owner can
process events from the queue. The ownership can be transferred to another thread using the
`event_queue_claim` function.
:::
Once the thread is started and has initialized the event queue, we can post events to it
from any other thread or interrupt context.
The handler functions of the posted events will be executed on the event handler thread.

Since using a dedicated thread for handling events is common,
RIOT provides a module which sets up event queues and a handler thread for us.

To use it, add the module to your application's Makefile:
```makefile
USEMODULE += event_thread
```

Now we can post to the `EVENT_PRIO_HIGHEST`, `EVENT_PRIO_MEDIUM`, and `EVENT_PRIO_LOWEST` queues.
A single thread will be automatically created that handles events posted to the queues
according to their priority.
Posting an event to one of these queues is as simple as:
```c title="main.c"
event_post(EVENT_PRIO_MEDIUM, &event);
```
## Periodic Events
Periodic events are events that are posted to an event queue at regular intervals.
First we need to include the module in our application's Makefile:
```makefile
USEMODULE += event_periodic
```

To create a periodic event, we need to declare and initialize a `event_periodic_t` structure.
```c title="main.c"
/* This initializes the periodic event. We set the timer it should use,
* the queue it should post to, and the event it should post.
*/
event_periodic_init(&periodic_event, ZTIMER_SEC, EVENT_PRIO_MEDIUM, &event);
```
Once the periodic event is initialized, we can start it:
```c title="main.c"
event_periodic_start(&periodic_event, 1);
```
:::note
A periodic event can be set to repeat only a certain number of times using the
`event_periodic_set_count` function.
:::
1 change: 1 addition & 0 deletions doc/starlight/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export default defineConfig({
"advanced_tutorials/creating_modules",
"advanced_tutorials/device_drivers",
"advanced_tutorials/porting_boards",
"advanced_tutorials/event_queue",
],
},
],
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,4 @@ Here is a quick overview of the examples available in the RIOT:
| [saul](./guides/saul/README.md) | Teaches you how to interact with sensors and actuators through the SAUL interface. [SAUL](https://guide.riot-os.org/c_tutorials/saul/) tutorial |
| [threads](./guides/threads/README.md) | Teaches you how to create and manage multiple execution threads in your RIOT application. [Threads](https://guide.riot-os.org/c_tutorials/threads/) tutorial |
| [timers](./guides/timers/README.md) | Teaches you how to use timers for periodic tasks and time measurement in RIOT. [Timers](https://guide.riot-os.org/c_tutorials/timers/) tutorial |
| [event_queue](./guides/event_queue/README.md) | Teaches you how to use the event queue. [Event Queue](https://guide.riot-os.org/advanced_tutorials/event_queue/) guide |
28 changes: 28 additions & 0 deletions examples/guides/event_queue/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# name of your application
APPLICATION = event_queue_example

# Change this to your board if you want to build for a different board
BOARD ?= native

# This has to be the absolute path to the RIOT base directory:
# If you are following the tutorial, your RIOT base directory will
# most likely be something like RIOTBASE ?= $(CURDIR)/RIOT
# instead of this
RIOTBASE ?= $(CURDIR)/../../..

# Add the e
USEMODULE += event_callback
USEMODULE += event_periodic
USEMODULE += event_thread

USEMODULE += ztimer_sec

# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
DEVELHELP ?= 1

# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1

include $(RIOTBASE)/Makefile.include
12 changes: 12 additions & 0 deletions examples/guides/event_queue/Makefile.ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
BOARD_INSUFFICIENT_MEMORY := \
arduino-duemilanove \
arduino-nano \
arduino-uno \
atmega328p \
atmega328p-xplained-mini \
atmega8 \
nucleo-f031k6 \
nucleo-l011k4 \
samd10-xmini \
stm32f030f4-demo \
#
3 changes: 3 additions & 0 deletions examples/guides/event_queue/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Event Queue Guide
This guide demonstrates how to use the event queue system to handle events in a structured manner.
Please refer to `doc/guides/advanced_tutorials/event_queue.md` for a more detailed explanation.
101 changes: 101 additions & 0 deletions examples/guides/event_queue/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* SPDX-FileCopyrightText: 2025 TU Dresden
* SPDX-License-Identifier: LGPL-2.1-only
*/

#include <stdio.h>
#include "event.h"
#include "event/thread.h"
#include "event/periodic.h"
#include "ztimer.h"

/* A custom event structure that extends the base event structure
* the custom event carries an additional text field.
*/
typedef struct {
event_t super;
const char *text;
} custom_event_t;

/* Function to handle regular events */
static void regular_handler(event_t *event)
{
(void) event;
printf("\tTriggered regular event.\n");
}

/* Function to handle custom events */
static void custom_handler(event_t *event)
{
/* The handler receives a pointer to the base event structure.
* We need to get the pointer to our custom event structure.
*/
custom_event_t *custom_event = container_of(event, custom_event_t, super);
printf("\tTriggered custom event with text: \"%s\".\n", custom_event->text);
}

/* Defining a regular event and setting its handler */
static event_t event = { .handler = regular_handler };
/* Defining a custom event and setting its handler and text */
static custom_event_t custom_event = { .super.handler = custom_handler,
.text = "CUSTOM EVENT" };
/* Declaring a periodic event */
static event_periodic_t periodic_event;
/* Declaring an event queue */
static event_queue_t my_queue;

/* This thread will handle events from the event queue */
void *event_handler_thread(void *arg)
{
assert(arg != NULL);
event_queue_t *queue = (event_queue_t *)arg;

/* A thread must own an event queue to process its events.
* Ownership is initially assigned to the thread that initializes the queue.
* It can later be transferred by calling `event_queue_claim`.
*/
event_queue_init(queue);

puts("Event handler thread listening for events...");
event_loop(queue);
return NULL;
}

/* Stack for the event handler thread */
char _event_handler_thread_stack[THREAD_STACKSIZE_MAIN];

int main(void)
{

/* Starting the event handler thread */
thread_create(_event_handler_thread_stack, sizeof(_event_handler_thread_stack),
THREAD_PRIORITY_MAIN - 1, 0, event_handler_thread, &my_queue,
"Event Handler Thread");

puts("Posting regular event...");
/* Posting a regular event to the queue handled by our event handler thread */
event_post(&my_queue, &event);
ztimer_sleep(ZTIMER_SEC, 1);

puts("Posting custom event...");
/* Posting a custom event to the queue handled by our event handler thread */
event_post(&my_queue, &custom_event.super);
ztimer_sleep(ZTIMER_SEC, 1);

puts("Posting regular event to the medium priority event thread...");
/* Posting a regular event to medium priority queue.
* This queue will initialized and handled by the `event_thread` module.
*/
event_post(EVENT_PRIO_MEDIUM, &event);
ztimer_sleep(ZTIMER_SEC, 1);

puts("Starting periodic event that posts regular events every second...");
/* This initializes the periodic event. We set the timer it should use,
* the queue it should post to, and the event it should post.
*/
event_periodic_init(&periodic_event, ZTIMER_SEC, EVENT_PRIO_MEDIUM, &event);
/* This starts the periodic event with a period of 1 second. */
event_periodic_start(&periodic_event, 1);

return 0;
}