Your getting closer, but still seem stuck in the assumption that you can start processing a second message until the first is completely sent. YES, that is how a lot of manufacture supplied drivers work, but it is less than desireable for a real-time system. The whole discussion is how to get out of that model to something where task can send messages, and normally not need to block. They only need to block if your “buffer” gets full.
This means that UART::WriteToUART can’t assume the serial port is idle when it is called, but it should just add the message to the buffer, and if needed start the transmission IF it is idle.
First design comment, always using portMAX_DELAY is bad, as it says if something goes wrong your system just “hangs” and it can be hard to figure out what happened. My equivalent to WriteToUart takes a third paramater of the maximum time to wait to obtain the mutex, and if that fails it returns an error code.
Then you can’t write the first character to the transmitter directly, as the transmitter might be busy at the moment, so all the bytes are added to the queue.
To start the transmission, I add an “internal” function called “kick” that checks if the serial port is actively sending a character, and if not starts the transmission, this might be by sending the character to the port, but more often I do it by enabling the Tx interrupt and maybe force pend a Tx interrupt request. Adding this kick funciton allows me to have a universal base class that works on many processors, and a derived class for a particular device, and makes the code easier to port to a new processor.
Kick is called after sending each character to the Queue.
In the ISR, first you really need to have your xQueueuReceiveFromISR be passed an actual value for the third parameter, as otherwise when the ISR sends a character, and makes room in the Queue, the task level loop filling the Queue won’t wake up right away. In my mind there are very few cases when you don’t want to process that wake up flag.
Second, when the xQueueReceiveFromISR returns the error for being empty, on many serial ports you need to do something to clear the TX event. Sending a character is one option, but if you don’t have another character, often you need to do. something else (often disable the Tx interrupt).
@RAc In my view it depends a LOT on what you are doing with the serial port, and what is on the other side. Adding a task just to “serialize” access is a heavy hammer, and best to avoid when not needed. This doesn’t mean that the code to process the device gets scattered all over the place, if the device has a protocol, then that is all put into a single driver, that might be used by multiple tasks. The point it makes sense to me to make a dedicated task is when the device might send a message asyncronously (without a just prior command sent to it) or if it can handle multiple requests at once, and the answers may not be in the order sent. Another is if you need to periodically querry the device, then a task for the device might also make sense.