From 60b9f49a245360b13a0d6d3ceb5ce06369a0dbd2 Mon Sep 17 00:00:00 2001 From: LeonardHerbst Date: Thu, 20 Nov 2025 13:37:31 +0100 Subject: [PATCH] doc/guides/advanced_tutorials: added event queue guide --- doc/guides/advanced_tutorials/event_queue.md | 158 +++++++++++++++++++ doc/starlight/astro.config.mjs | 1 + examples/README.md | 1 + examples/guides/event_queue/Makefile | 28 ++++ examples/guides/event_queue/Makefile.ci | 12 ++ examples/guides/event_queue/README.md | 3 + examples/guides/event_queue/main.c | 101 ++++++++++++ 7 files changed, 304 insertions(+) create mode 100644 doc/guides/advanced_tutorials/event_queue.md create mode 100644 examples/guides/event_queue/Makefile create mode 100644 examples/guides/event_queue/Makefile.ci create mode 100644 examples/guides/event_queue/README.md create mode 100644 examples/guides/event_queue/main.c diff --git a/doc/guides/advanced_tutorials/event_queue.md b/doc/guides/advanced_tutorials/event_queue.md new file mode 100644 index 000000000000..3d0a1a880a2c --- /dev/null +++ b/doc/guides/advanced_tutorials/event_queue.md @@ -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. +::: \ No newline at end of file diff --git a/doc/starlight/astro.config.mjs b/doc/starlight/astro.config.mjs index fd5d00f8bcb9..1afdcbcc5003 100644 --- a/doc/starlight/astro.config.mjs +++ b/doc/starlight/astro.config.mjs @@ -137,6 +137,7 @@ export default defineConfig({ "advanced_tutorials/creating_modules", "advanced_tutorials/device_drivers", "advanced_tutorials/porting_boards", + "advanced_tutorials/event_queue", ], }, ], diff --git a/examples/README.md b/examples/README.md index 4e3c429c1032..f36b004f42a3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -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 | diff --git a/examples/guides/event_queue/Makefile b/examples/guides/event_queue/Makefile new file mode 100644 index 000000000000..65c394b097c3 --- /dev/null +++ b/examples/guides/event_queue/Makefile @@ -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 diff --git a/examples/guides/event_queue/Makefile.ci b/examples/guides/event_queue/Makefile.ci new file mode 100644 index 000000000000..4854eb24dda9 --- /dev/null +++ b/examples/guides/event_queue/Makefile.ci @@ -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 \ + # \ No newline at end of file diff --git a/examples/guides/event_queue/README.md b/examples/guides/event_queue/README.md new file mode 100644 index 000000000000..6f289ee40e9f --- /dev/null +++ b/examples/guides/event_queue/README.md @@ -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. \ No newline at end of file diff --git a/examples/guides/event_queue/main.c b/examples/guides/event_queue/main.c new file mode 100644 index 000000000000..06e176330ce1 --- /dev/null +++ b/examples/guides/event_queue/main.c @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2025 TU Dresden + * SPDX-License-Identifier: LGPL-2.1-only + */ + +#include +#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; +}