2020-07-06 06:42 AM
Hello,
I don't really have a problem, but I'm asking myself questions and I didn't succeed in synthesizing them and find answers on Google.
Problem 1:
Several tasks want to use the same peripheral, lets say SPI bus for example. What is the best way to protect it.
Sol 1: Use mutex on SPI utilization
Sol2: Create a dedicated task to use SPI and tasks that want to use SPI add messages to a queue.
Problem 2:
I want to receive data from a bus, lets say UART, and process data in a task.
Sol 1: Fill a buffer in the RX callback and send a notification to the task to unlock and process the buffer (maybe unlock the task only when an end of frame is detected is better?)
Sol 2: Send the received character with notification value or queue and task is waiting for that and process that only when unlocked.
In my mind, both solutions works for my two problems, but I have no experience nor someone that taught me that so if you have some feedback or good practices, I will be so thankful!
Thank you,
Charles
2020-07-07 11:37 AM
Here are my 5ct: It depends on runtime performance vs. design simplicity.
Queue or mutex?
If you choose a queue+dedicated task for SPI, the tasks generating SPI requests loose control over the precise bus message timing. This may be acceptable (for a transmit-only LED driver as slave) or not. How to handle received messages? Which task priority should the dedicated task be given? There might be no simple answer to those questions.
Alternatively, a mutex can protect a blocking IO call like HAL_SPI_Transmit. But, those are implemented with a busy loop polling for SPI TXE flag. This is not very multi-tasking friendly but may be okay when overall perfomance is sufficient.
A third alternative would be protecting the SPI resource by a binary semaphore and use interrupt (or DMA) driven APIs. The semaphore is taken in the HAL_SPI_Transmit_IT call and given back in the interrupt completion handler. This allows for a more performant event driven design.
hth
KnarfB
2020-07-09 02:12 AM
Thank you for your answer @KnarfB .
The outcome is that "it depends" :D
Finally, I'm using a mutex to protect my SPI utilization but I will surely use task+queue solution if a don't need answer from the other device.
I will investigate your third alternative. At first sight, I don't see the difference with the mutex approach (the ressource is locked in both case from the start to the end)
Charles
2020-07-09 03:19 AM
Hi Charles,
a task aquires a mutex before accessing a resource and the same task has to return the mutex when the access is finished. This okay when the ressource (SPI) is used by a blocking call like HAL_SPI_Transmit. But what does the task do while possessing the mutex? We have the source code and see that HAL_SPI_Transmit is busy, polling a flag in a loop. This is wasting CPU cycles. Moreover, if the task has low priority, it might not be scheduled at all and hence it doesn't poll continously and not return the mutex in time.
A semaphore, in contrast, is not owned by a task. It can be used to protect the ressource (SPI) as follows: A task task1 takes the semaphore and issues a call to HAL_SPI_Transmit_IT or _DMA. That call only prepares SPI bus access and immediately returns (asynchronous IO). Later, when the SPI access has finished, an interrupt is fired. The completion interrupt handler (and not task1) gives the semaphore back. The resource is protected as well, but task1 can continue immediately after it has triggered the SPI access. Either without caring for the completion handler at all, or let say sleep until woken by additional code in the completion interrupt handler. The design is more complex but more efficient and flexible.
Mutex vs. semaphore is discussed in more detail here: https://barrgroup.com/embedded-systems/how-to/rtos-mutex-semaphore