64 January & February 2021 http://www.elektormagazine.com
both tasks will resume at the same time. If the two tasks are assigned
to the same CPU they will then be scheduled to execute one after the
other. If the two tasks are assigned to different CPUs then it is indeed
possible for them to both resume and execute simultaneously.
Once execution resumes in each task they can then blink their LED
in their own unique way. Once that blinking has been completed,
execution resumes at the top of the task loop by waiting for the arrival
of the next event.
Event clearing
The xEventGroupWaitBits() function call is a little bit tricky and the
flexibility that it affords does add a little complexity. So, let’s break it down:
EventBits_t xEventGroupWaitBits(
const EventGroupHandle_t xEventGroup, // Event group handle
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait
);
The first argument is the handle to the event group referenced. This
is quite straightforward. Argument two specifies the event bits that
the function wants to wait for. In the demo program, we specified the
macro EVBLK2 (line 21) for the blink2() task and macro EVBLK3 for the
blink3() task. The reason for using separate bits will be evident shortly.
The third argument xClearOnExit is a C language version of a boolean
value (pdTRUE was supplied in lines 22 and 44). This informs the function
to clear the received event bits before returning. So the blink2() task
waits for bit 0 to become set (macro EVBLK2), and the argument three
value pdTRUE informs it to clear that bit upon receipt and return. This
wait-and-clear operation is performed atomically.
In our particular case, argument four is academic but let’s examine it.
Argument xWaitForAllBits is likewise boolean and specifies when
the function should return:
> when any of the bits being waited for are set (pdFALSE), or
> only once all of the bits being waited for are set (pdTRUE).
Our example only expects a single bit (bit 0 for blink2() and bit 1
for blink3()), so indicating any or all doesn’t have a material effect.
However, if you wish to wait for a combination of bits this capability
can be quite useful.
Event control
The reason why separate event bits were needed may now be evident.
Each receiving task waits for and then clears its own event bit with a
call to xEventGroupWaitBits(). Thus there needs to be a separate
the allocated event group object. The returned data type of the handle
is EventGroupHandle_t. If the returned value is zero, your heap has
run out of storage (hence the assertion check in line 70).
0069: hevt = xEventGroupCreate();
0070: assert(hevt);
Notification
There are different choices for manipulating event groups. In
this article, we’re using one of the simpler functions named
xEventGroupSetBits().
EventBits_t xEventGroupSetBits(
EventGroupHandle_t xEventGroup, // Handle
const EventBits_t uxBitsToSet // Bits to set
);
The handle xEventGroup specifies the event group to be operated
upon, while the argument uxBitsToSet specifies the event bits
you want to atomically set. The value returned will be the event bits
that were in effect after the call to xEventGroupSetBits() returns.
However, you need to be aware that the returned value may not always
include the bits that you have just set as the receiving tasks may have
already cleared them upon receipt.
The loop() task calls the xEventGroupSetBits() function setting
bit 0 and bit 1 using the macro expression EVBLK2|EVBLK3.
0008: #define EVBLK2 0b0001
0009: #define EVBLK3 0b0010
0098: xEventGroupSetBits(
0099: hevt,
0100: EVBLK2|EVBLK3
0101: );
From this call, it can be seen that both bits 0 and 1 are set atomically
by this call.
loop() task
The loop() task delays for one second (line 97) and then sends out
an event notification (lines 98 to 101). Then, following Arduino tradition,
the loop() function exits and repeats. This results in events being
triggered approximately one second apart.
Receiving tasks
The tasks blink2() and blink3() are notified by the event group
using the saved handle (lines 12 and 69). For example, blink2() has
its execution suspended at lines 19 to 25 until an event is sent. Likewise,
blink3() has its execution suspended at lines 41 to 47 until an event
is sent. Because the events are triggered atomically by lines 98 to 101,
[1] Code for evtgrp.ino: https://bit.ly/35YrjCN
[2] W. Gay, “FreeRTOS for ESP32-Arduino”, Elektor 2020: https://bit.ly/2U2Yhg1
[3] FreeRTOS documentation: https://bit.ly/386HZL6
WEB LINKS