Search code examples
cfreertos

Event-based task management using FreeRTOS


I'm trying to pick up C, using an esp32. While looking at exactly how FreeRTOS works, I found the following page regarding how to use the tasks, and best practices etc.

https://www.freertos.org/implementing-a-FreeRTOS-task.html

According to this page, to prevent starvation, tasks should be event based. Regarding what I am trying to achieve, I will try to provide a very simplified example.

Background

I have a LCD screen, which should display data from a sensor. The data shown on the LCD will be done using a task, which according to the documentation, should never exit and should be event driven to prevent starvation.

I have a way of controlling the data shown on the LCD screen, which would be a rotary encoder. This encoder can be clicked, which should refresh the sensor's data.

Question

How would I implement the event based tasks, which are described on the FreeRTOS page, in this specific context? I had a look at the documentation and the "simple" example projects on their github, but as a beginner within C and embedded, they were extremely overwhelming.

Simple demo code

void update_sensor_task(void *pvParameters)
{
    // Ensure the task keeps on running
    for( ; ; )
    {

        if(event_update_sensor) // How would I be able to notify the task that this should be run?
        {
            // update the data
        }
        
    }

    // Tasks should not be returning, but if they happen to do so, ensure a clean exit
    vTaskDelete(NULL);
}

void screen_temperature_task(void *pvParameters)
{
    for(; ;)
    {
        if(event_sensor_updated)
        {
            // Update the lcd screen with the new data
        }
    }

    vTaskDelete(NULL);
}

void on_rotary_clicked(void *pvParameters)
{
    // Notify the sensor task that it should be updating?
} 

EDIT:

By using what has been marked as the correct answer, I have managed to get it to work by implementing it the following way:

/* Queue used to send and receive the data */
QueueHandle_t xStructQueue = NULL;

/* Struct which shall be used to hold and pass around the data for the LCD screen*/
struct LcdData
{
    int current_temp;
    int current_humidity;
} xLcdData;

void initialize_queues(void)
{
    xLcdData.current_humidity = 0;
    xLcdData.current_temp = 0;

    xStructQueue = xQueueCreate(
        /* The maximum number of items the queue can hold*/
        5,
        /* The size of each struct, which the queue should be able to hold */
        sizeof( xLcdData )
    );

    if(xStructQueue == NULL)
    {
        ESP_LOGE(TAG, "Queue has not been initialized successfully");
    }
}

void screen_temperature_task_simplified(void *pvParameters)
{
    int counter = 0;
    for(; ;)
    {
        struct LcdData xReceivedStructure;

        BaseType_t result;
        result = xQueueReceive(xStructQueue, &xReceivedStructure, ( TickType_t ) 10);

        if(result == pdPASS)
        {
            counter = counter + 1;

            char snum_current_counter[12];
            sprintf(snum_current_counter, "%d", counter);

            i2c_lcd1602_clear           (lcd_info);
            i2c_lcd1602_write_string    (lcd_info, snum_current_counter);
        }
    }

    vTaskDelete(NULL);
}

void update_sensor_struct(void)
{
    xLcdData.current_temp = DHT11_read().temperature;
    xLcdData.current_humidity = DHT11_read().humidity;

    // Log the results in the console
    printf("Temperature is %d \n", xLcdData.current_temp);
    printf("Humidity is %d\n", xLcdData.current_humidity);
    ESP_LOGI(TAG, "Data has been updated");
}

void on_rotary_clicked_simplified()
{
    ESP_LOGI(TAG, "Rotary encoder has been clicked!");

    // Update the struct which holds the data
    update_sensor_struct();

    /* Send the entire struct to the queue */
    xQueueSend(
        /* The handle of the queue */
        xStructQueue,
        /* The adress of the struct which should be sent */
        (void *) &xLcdData,
        /* Block time of 0 says don't block if the queue is already full.
        Check the value returned by xQueueSend() to know if the message
        was sent to the queue successfully. */
        ( TickType_t ) 0
    );
} 

Solution

  • I use FRTOS and event driven development.

    The typical flow here would be:

    for(;;)
    {
        BaseType_t result;
        result = xQueueReceive(LCD_Event_Queue, &someLCDEvent, QUEUE_TIMEOUT);
        if (result == pdPASS)
        {
            /* We have new event data in someLCDEvent; Use that data to update the LCD */
        }
        else
        {
            /* No new event, do some brief idle-time processing if necessary */
        }
    }
    

    In brief, wait up to QUEUE_TIMEOUT time for a new event to arrive.
    If a new event arrives within that timeframe successfully, then process the data in that event and update your screen.
    If a new event does not arrive, you have an opportunity to do some other maintenance work.

    Designing and defining the structure-type of someLCDEvent, and putting data into the queue is a big topic, and will depend a lot on your specific project.