Starting project with FreeRTOS

Hi, This is my first project with RTOS. I readed a lot of docummentation ant tried some examples so now want starts my own project.
Ofcourse i’m starting from example project as base: [ble_app_server](esp-idf/examples/bluetooth/bluedroid/ble/ble_spp_server at b0150615dff529662772a60dcb57d5b559f480e2 · espressif/esp-idf (github.com))
As i readed, I should firstly plan everything and than write code. So i would ask for help with planning, that it could be optimal use of communication objects like mutexes, queues, task notifications, etc.

My project is RF receiver with one relay output, one microswitch input, one LED output, EEprom, independent RF module and BLE stack for transmision configuration data
I do not take into account in this analysis bluetooth communication, since it will be independent from other tasks (only get and put data from/to eeprom via ble).

Operating:

  1. When push button in receiver and transmit from RF transmitter, it saves transmitter to eeprom.
  2. When receive correct signal from transmitter, check if it is registered in eeprom and turn on Output relay for specified time.

I think that my project needs 8 tasks:

  1. Task_1 (ISR) for serve data input stream from RF module - data comes asynchronously (PWM coding). Restarts buffer if frame error detected. When consistent frame detected, trigger Task_2 (data analise),
  2. Task_2 for analise received data (if triggered), put it into structure and trigger Task_3 if data correct (detected correct flag, CRC, etc.).
  3. Task_3 for checking eeprom for specific data. If flag_1=0 (microswitch depressed - see pt. 5) AND specific data found, trigger Task_4. If flag_1=0 (microswitch depressed - see pt. 5) AND specific data not found, do nothing more. If flag_1=1 (microswitch pressed - see pt.5) trigger Task_6 (does not matter if specific data found or not).
  4. Task_4 for service relay output on/off (or trigger ON for some fixed time - also can trigger Task_7 that counts time)
  5. Task_5 for serve one input for microswitch. If press detected, set flag_1. If depressed, clear flag_1.
  6. Task_6 for checking free record in eeprom and write data to it.
  7. Task_7 for counting time if Task_4 triggers output ON for specified time.
  8. Task_8 for driving LED (blinking slow, blinking fast, ON or OFF)

Now, i need communication between tasks (and there i’m not sure how to do it):

  1. Queue_1 for data collected by Task_1. Data should be received by Task_2, but only, when there is whole frame received in Task_1, not single byte, so i’m not sure… or meaby Task_2 should receive each byte-by-byte from Task_1 and collect it in internal buffer? But what if frame will be inconsistient in the middle of receiving and buffer will be cleared in Task_1. Can it send notification to Task_2 to clear his buffer?
  2. Queue_2 for data structured and sending by Task_2 and received by Task_3
  3. Queue_3 for sending data by Task_3 and receivig by Task_6 (data for writing to EEPROM). Data sended are the same data that was received from Queue_2.
  4. Now should i use mutexes or TaskNotifications rather? I readed that task notifications are lighter and can only notify from exactly Task_x to exactly Task_y. Also can send from ISR (my Task_1). I think that can be usable in my project. So should i use it or rather semaphores and/or mutexes?
    I need it for:
  • signaling of microswitch pressing/depressing (that, what i named “flag_1” in tasks planing). Sending from Task_5 to Task_3.
  • sending LED action_id for Task_8 (blinking slow / blinking fast / on / off). It can be sended from some tasks but only to Task_8 (e.g from Task_5 - microswitcg pressed/depressed or from Task_4 (output ON/OFF) or from Task_7 (output ON time ends - OFF).
  • signaling from Task_1 to Task_2 that data is corrupted and Task_2 must clear receiving buffer. Or meaby better signal that there are whole data in buffer for receiving by Task_2? Is it possible to not receiving from queue until notification from Task_1 occures.
  • Task_2 sending notification to Task_3 that there is structured data in queue or it is not necessery, because Task_3 can be set to work only when data is in queue?
  • Task_4 sends notification to Task_7 that have to count.
  • Task_7 sends notification to Task_4 that it must output OFF.

Additional questions:

  1. How can it be done that Task_n receives data from buffer only when there are whole data sended by Task_x, not each byte? Or meaby always Task_n should receive byte-by-byte? I found something like uxQueueMessagesWaiting() so Task_n can check it and decide if reading or not yet? What is the best practise?

  2. There is xTicksToWait parameter. “Block time – the maximum amount of time that the
    task will remain in the Blocked state to wait for data to be available.”
    xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );
    Can I set it that task will be block all the time except when data is available in buffer?

  3. I don’t see “vTaskStartScheduler();” in my example project (in ble_server_demo.c). Why? Where is it?

Conclusion. I used:

  • 8 tasks
  • 3 Queues
  • a lot of notifications

I’m afraid I haven’t completely switched my mindset yet from bare-metal to RTOS way.

Lets just start with the number of tasks - 8 sounds like a lot to me.

Tasks and ISRs are different things. Tasks are under the control of software and only run when no interrupts are running. Interrupts run in response to hardware events and always take precedence over tasks.

This does sound like a good candidate for a task though if your intent is to encapsulate the communication protocol handling within this task. That would also make the code re-usable in any project that uses the same communication protocol. It is a common pattern for a task to manage a protocol, buffering received data, and then unblock a task when there is a complete message that needs processing.

As for the other tasks - do all these things happen in parallel? I would doubt it. If they are not in parallel then the different functionality you are intending to allocate to different tasks can just be called sequentially from the same task. You can keep the modularity by having the functionals that get called all be implemented in their own modules - but I doubt there would be a reason to have each module assigned to a different task.

For example, does task 3 really need to trigger task 4 and task 7 rather than just turn the relay on itself, then start a software timer and have the timer callback (which executes when the timer expires) turn the relay off again?

Thank You for answare. I’m beginner in RTOS and thanks for some important tips.

Firstly i would like to determine receiving from RF module. I’m little confused, how much should do in ISR. From one side, it should be as short as possible, but form other side it shouldn’t frequently send data (bits) to task since it is unproductive to run task for do nothing.

I want start a hardware timer with interrupt enabled and sampling data input in ISR.
Due to variety of data coding formats in frame, I want to put switch-case (local state machine) in ISR and the last state, when data are received , it can push it to buffer for task.
Variety of data coding formats means that frame starts with 50% duty cycle pramble, next is header that is ten 0’s and than data in PWM coding (triplets 110-logic ‘0’ and 100-logic ‘1’). I would manage it in local state machine inside ISR and put out via xQueueSendToBackFromISR() when whole frame received. Is it correct according to You?

It doesn’t really matter which piece of code (ISR vs. task) the CPU is executing and a context switch is not that expensive.
It’s usually much better to offload as much of post-processing of hardware events/servicing to a task as possible. Remember while an ISR executes other IRQs might be blocked, which increases interrupt response time for other peripherals or timers.
This is usually not what you want.
Depending on the speed (baud rate) at the RF module interface I’d use a stream buffer to transfer the serial data as it arrives to a post-processing task which does all the more or less heavy lifting handling the protocol, the frame formats and maybe errors.
A queue is also fine, of course. But stream buffers are a bit cheaper (faster).

Thank You for reply.
If I take whole post-processing out of ISR, than i can only pass single sampled bits from ISR to Task. I’m not sure if it is good way.
Timer period (and ISR also) should be 16us. What is the best method for transfer 1 bit for 16us from ISR to task? Is it good to run task every 16us?
I think i have to receive frame inside ISR, because of necessity of checking time dependencies that must be met before and while receiving this part of frame that consists data. it’s not just stream with desired baudrate that i can sample, buffer and than run task.

16 us is a pretty high interrupt rate. Depending on the speed/clock of your MCU this could be a problem. And it’s probably not a good option to forward each bit (?) to a post-processing task in this case.
I think you should try your approach and assemble the frames in the ISR.