I have a write_string_to_uart() function, which can be called from more than one task. To avoid that one write can interfere with another I’m seeking for a way to either lockout the writes from each other of
queue each write in a manner that they get executed first in first out.
How do I implement this mechanism best?
Thinking of it I may have misinterpreted the meaning of a queue. It seems it’s just a fifo buffer, nothing to do with multitasking.
Could I write a function pointer into the queue and execute the function the pointer points to? But how would I pass parameters to that function?
Similar to what Richard wrote, the “standard” technique for this situation is as follows:
Define a queue of pointers to blobs of data. The queue must be protected simultaneously by a semaphore and a mutex.
Spawn a “puller” thread that is responsible for writing to the UART. It will dequeue pointers from the queue and write the associated data to the UART. It blocks, waiting on semaphore.
Spawn other “pusher” threads that will take turns enqueing pointers to blobs of data into the queue.
Pusher acquires the queue.
Pusher enqueues pointer to data.
Pusher releases the queue.
Pusher raises semaphore by 1.
Puller unblocks, which automatically causes lowering of the semaphore by 1.
Puller acquires the queue.
Puller dequeues pointer to blob of data from the queue.
Puller releases the queue.
Puller actually writes the data denoted by pointer to the UART.
Note that it is a bad idea for the puller to acquire the queue for the purpose of “peeking” into it to see if any data available. That is the purpose of the semaphore, to tell it when data is available. Also note that a pusher should not raise the semaphore until after it has released the queue. Were pushers to acquire/enqueue/raise/release, in that order, it might cause situation where puller comes out of wait between the raise/release; tries to acquire the queue to see what is going on; is unable, because pusher still has the lock; go back to sleep… you get the idea… a quantum allocation would be wasted by waking the puller prematurely. Better to let the pushers be 100% finished doing what they need to do before raising the semaphore.
The terminal model for this situation requires that the puller block while waiting, simultaneously, for the semaphore and some other event that tells it to exit, perhaps by a main thread. But this technique requires that OS provide the ability to wait for multiple synchronization objects simultaneously, (WFMO), which FreeRTOS lacks, so as a workaround, your puller thread should not block indefinitely, but add a delay to the wait, so that it is given the opportunity to poll some other variable to know when it is time to exit. Same goes for the pusher threads, for that matter.
I don’t get the need of the additional semaphore and mutex. When using a FreeRTOS queue I think none of them are really needed. It’s all handled by the queue.
However, when using by reference resp. by pointer queue items an additional mechanism (for instance a custom item allocator protected by a mutex) is required. It’s not needed when using a by value queue.
I just took a look at the model used by FreeRTOS [I have never programmed FreeRTOS], and it appears that the queuing mechanism integrates semaphore/mutex as you stated.
That said, my first inclination would be to bypass this integrated model, and use my model instead, as I would need to change only a small amount of my C++ code to get the behavior that I want on any OS, whereas with the FreeRTOS model…