Queue - Resource organization

Dear, hello!

I have recently been working with RTOS-based applications for microcontrollers and I have doubts about the best way to organize the software architecture.

I will describe my scenario to clarify my doubts.

Simply put, I have 3 tasks on my embedded system.

Task A: is responsible for checking the system time and analyzing if there are any schedules to be executed. If there is a schedule to be executed, write the data (output status and color of the LEDs) in two queues in which Task B and Task C are monitoring, respectively.

Task B: is responsible for managing the system outputs. It is monitoring a queue to receive and change the output status.

Task C: is responsible for managing the system’s LEDs. It is monitoring a queue to receive and change the status of the leds.

From what I studied (and I may be wrong) the queuing resources, belong to the system, so that the software modules only use this. Queues are started before tasks and are passed as parameters during task startup.

/ ***** init code below ****** /
void main_task (void * pvParameter) {

/ * handle to queue. * /
xQueueHandle fast_led_queue;

/ * Create the fast led queue. * /
fast_led_queue = xQueueCreate (5, sizeof (fast_led_animation_request_t));

fast_led_config_t m_fast_led_config = {
.led_strip_length = 8,
.rmt_channel = RMT_CHANNEL_0,
.gpio = GPIO_NUM_12,
.refresh_time_ms = 50,
.fast_led_queue = fast_led_queue
};

/ * *************
* CREATE TASKS *
* ************** /

/ * Create a task to management the alarms. * /
xTaskCreate (& alarm_output_task, "alarm_output", 16384, & alarm_output_config, 5, NULL);

/ * Create a task to management the fast led strip. * /
xTaskCreate (& fast_led_task, "fast_led_task", 8192, & m_fast_led_config, 5, NULL);

/ * Delet task main * /
vTaskDelete (NULL);

}
/ ***** end code below ****** /

The data exchanged in the queues has the following structure:
typedef struct {
uint8_t evt;
uint32_t p_data *;
} Queue_parameters;

however, the data type referenced in the pointer has to be cast to be interpreted.

My question is as follows:

Based on a clean architecture, task A has to know what is the content of the structure that task B understands to be pointed out in p_data.

In this way, the correct thing would be to have a global .h file with the definition of all the structures that travel in the system so that it is included by all tasks or in the respective .h that declares task b to have the structure that it should receive by the queue and include this header in the file that declares task A?

My question may be a little confusing, but I appreciate your kindness for any answer.

My first comment is that you committed you mapping of ‘tasks’ (as in things to do) into FreeRTOS primitives (like Tasks and Queues) too early.

I would start by dividing the system into logical modules, each with an interface for how they interact with the rest of the system. For example, your ‘Task C’ becomes an LED Module, likely interfaced to through a ‘setLed’ function call, that take parameters of what to do to what LED. Whether or not it is backed by a task, or uses a Queue to communicate to said task is a private detail. (there is also an initLed() function that the main calls prescheduler to create the needed items). Do the same for each piece of your system, defining what services it will provide to the system, what services it will need (and make sure the other needed module provides those services) and what sort of things it needs to do to get the information it needs.

Sometimes you have options, a module may need to be periodically updated on the state of another module, and you need to decide should it poll the other module as it needs the information, or should the second module inform it when the status changes (or a bit of both).

Only once you have the system broken down into these sorts of modules, do you the define tasks. Not every module might create a task, for instance, if the LED module just needed to statically turn on or off LEDs, or send a message out to a controller, it might be just a subroutine. If it needs to scan an LED Matrix, maybe it is a task, or it might be a timer callback, or maybe operate largely though an ISR, but that decision is INTERNAL to the LED module, and nobody else needs to know.

Also, I highly avoid the idea of a ‘main task’ that initializes the system then deletes itself. Create every resource you can pre-scheduler, and do the initializations that need to be done with the scheduler active as parts of long running task that you would be creating anyway, probably as part of the task that will be related to the divice that needs initialization.

I tend not to use the void* parameter they way you are doing, unless I am creating multiple copies of the same task function which need some config information (like there are two separate LED Panels, showing different data, but similar drivers, then I might pass a descriptor block with the info about the display that way)