I'm going to explain this topic in a very simple way, because "task events" are nothing more than waiting for a task, a software timer, or an interrupt to allow another task to execute. That is, one of the tasks waits until the corresponding bit in the bit mask is set. This allows us to have tasks communicating with others, giving us the possibility of creating programs that are a little more dynamic in my opinion.

Here is a simple view of how we can implement a task event.

#include "bsp.h"
#include "RTOS.h"

#define EVENT0  (1u << 0)   // Bit 0 of the event bit mask
#define EVENT1  (1u << 1)   // Bit 1 of the event bit mask

static OS_STACKPTR  int HPStack[128], LPStack[128];   // Task Stack
static OS_TASK  TaskCB1, TaskCB2;                     // Task control structure

static void HPTask(void);
static void LPTask(void);

int main( void )
{
    OS_Init();      // Initialize embOS (must be first)
    HAL_Init();
    OS_InitHW();   // Initialize Hardware for embOS

    OS_TASK_CREATE( &TaskCB1, "HP TASK", 100, HPTask, HPStack );     // Creating the task
    OS_TASK_CREATE( &TaskCB2, "LP TASK",  50, LPTask, LPStack );     // Creating the task

    OS_Start();     // Start multitasking

    return 0u;
}

First of all. Implement your tasks as usual, this is because the magic of the task events are inside of the tasks.

static void HPTask(void)
{
    __HAL_RCC_GPIOC_CLK_ENABLE();   // Enable GPIOC Clock

    OS_TASKEVENT MyEvents;          // This is our bit mask whith a 32 bits width
    GPIO_InitTypeDef GPIO_InitStruct;

    GPIO_InitStruct.Mode    = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Speed   = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Pull    = GPIO_NOPULL;
    GPIO_InitStruct.Pin     = GPIO_PIN_0 | GPIO_PIN_2;
    HAL_GPIO_Init( GPIOC, &GPIO_InitStruct ); 

    while(1)
    {
        MyEvents = OS_TASKEVENT_GetBlocked( EVENT0 | EVENT1 );          // Wait for event bits 0 or 1
    
        if(MyEvents & EVENT0 )
        {   // If the bit 0 is set, the EVENT0 is triggered and the pin 0 is toggle
            HAL_GPIO_TogglePin( GPIOC, GPIO_PIN_0 );
        }
        else if( MyEvents & EVENT1)
        { // If the bit 1 is set, the EVENT1 is triggered and the pin 2 is toggle
            HAL_GPIO_TogglePin( GPIOC, GPIO_PIN_2 );
        }
    }
}

In my first task, I’m gonna initialize my GPIO0 and GPIO2 and I’ll create a variable called “MyEvents” this variable is of type “OS_TASKEVENT". The “OS_TASKEVENT" data type is basically an unsigned long data type. This is because we are using a 32 bit CPU.

In this task I’ll also use the OS_TASKEVENT_GetBlocked() function. In this function we will pass as parameter the bits that will represent our events in the bit mask (bit 0 and 1 in my case).

static void LPTask(void)
{
    while(1)
    {
        OS_TASK_Delay(1000);
        OS_TASKEVENT_Set(&TaskCB1, EVENT0);   // Triggering EVENT0
        OS_TASK_Delay(1000);
        OS_TASKEVENT_Set(&TaskCB1, EVENT1);   // Triggering EVENT1   
    }
}

The other task is very simple: I simply use the "OS_TASKEVENT_Set()" function to tell the other task that it should execute the event. This function takes as parameters the task control block of the task where we have the events and the event we want to trigger.

SYSTEMVIEW

Build and flash the program and then run systemview.

Look at the timeline. You can see how every 200ms a task runs (TASK 2) and trigger the corresponding event.

Looking closer we can notice how when the “LP TASK” trigger an event the “HP TASK” executes the code according with the event.

Now, in the Event List you can see how the “LP TASK” trigger the event and then the “HP TASK” executes the corresponding event (EVENT 0) in this case.

Task Event Functions

OS_TASKEVENT OS_TASKEVENT_Clear(OS_TASK* pTask);
OS_TASKEVENT OS_TASKEVENT_ClearEx(OS_TASK* pTask, OS_TASKEVENT EventMask);
OS_TASKEVENT OS_TASKEVENT_Get(OS_CONST_PTR OS_TASK *pTask);
OS_TASKEVENT OS_TASKEVENT_GetBlocked(OS_TASKEVENT EventMask);
OS_TASKEVENT OS_TASKEVENT_GetSingleBlocked(OS_TASKEVENT EventMask);
OS_TASKEVENT OS_TASKEVENT_GetSingleTimed(OS_TASKEVENT EventMask, OS_TIME Timeout);
OS_TASKEVENT OS_TASKEVENT_GetTimed(OS_TASKEVENT EventMask, OS_TIME Timeout);
void OS_TASKEVENT_Set(OS_TASK* pTask, OS_TASKEVENT Event);

Snippets

Exercises (printf)

  1. Create three tasks, one of which will generate events to trigger the other two in sequence. That is, one task must be executed first, then the other. Each time an event is triggered, a message must be printed indicating which event is being triggered.
  2. Create two tasks and a software timer that should trigger events every 300 and 500ms (use the OS_TIMER_SetPeriod() function). Print messages each time the event is triggered and each time the callback is executed.
  3. Implement a task that increments a counter each time it receives an event. Another task should generate events every 500 ms. Upon receiving an event, print a message with the updated counter value and a message indicating receipt.
  4. Create two tasks that trigger one at a time using events. Each time one task completes its work, it should send an event to the other and display a message like "Task X triggered Task Y."

Exercises

  1. Create 3 tasks, one of them must be in charge of reading 2 buttons depending on which button has been pressed an event will be triggered to activate one of the tasks and toggle an LED
  2. Implement a simple state machine with three tasks representing stages: capture, processing, and storage. Each stage is activated only when it receives an event from the previous stage. The flow should be repeated indefinitely. An LED should light up at each stage to verify operation.
  3. Use a software timer (OS_TIMER) to simulate an interrupt. When the timer expires, you must send an event to a task that simulates servicing an ISR. The task must perform a brief action upon receiving the event.