Synchronizing UART TX

Print function is used by multiple tasks for printing to the console which means it’d need to be guarded or synchronized to avoid race conditions.

Technically, with interrupts you don’t have to block your function as it’s doing stuff in the background.
Perhaps the synchronization mechanism should be something like:

  • A task takes that wants to use UART interface to print acquires exclusive access via xSemaphoreTake, and is only release once the TX transfer is done from IRQ Handler
  • Any other task wanting to acquire a UART interface shall get blocked until the task that acquired releases it

Right? The following snippet demonstrates it, however the application somehow ends up in a weird state and doesn’t work. The following is a call stack before it ends up in a weird state. For reference, I’m using nRF52

Other thing I noticed was the default value of sem is 0 but doesn’t 0 mean the semaphore has been taken?

   class UART
   { 
     // ...
     SemaphoreHandle_t sem;

    template<typename... Args>
    void Print(const char* format, Args... args)	
    {
        if (xSemaphoreTake(sem, portMAX_DELAY) == pdTRUE)
        {
	       char buffer[100] = {0};

	       std::snprintf(buffer, sizeof(buffer), format, args...);
	       std::strcat(buffer, "\r\n");
	       size_t length = strlen(buffer);

	      TXInterrupt(reinterpret_cast<T*> (buffer), length);
        }  
    }
  };
void UART_IRQHandler()
{
       // ...
        if (mFifoTx.IsEmpty()) 
        {	  
	      SetUARTReg(NRF_UART_TASK_STOPTX, 0);  // stop the transmission
	      BaseType_t xHigherPriorityTaskWoken = pdFALSE;
	      xSemaphoreGiveFromISR(sem, &xHigherPriorityTaskWoken); 
        }
}

if I do use a binary semaphore and initialize in the ctor sem = xSemaphoreCreateBinary();, I suspect xSemaphoreTake causes the current task to be blocked. Perhaps I was looking from a mutex perspective that gives exclusive rights to the said task rather than blocking it.

So maybe I want something like this but is this the preferred approach?

    template<typename... Args>
    void Print(const char* format, Args... args)	
    {
	       char buffer[100] = {0};

	       std::snprintf(buffer, sizeof(buffer), format, args...);
	       std::strcat(buffer, "\r\n");
	       size_t length = strlen(buffer);

	      TXInterrupt(reinterpret_cast<T*> (buffer), length);

          xSemaphoreTake(sem, portMAX_DELAY); // "wait" till the TX transmission is done
        }  
    }

The first solution you mentioned should work. Which FreeRTOS version and port are you using? What is the value of configMAX_SYSCALL_INTERRUPT_PRIORITY and what is the priority of UART interrupt?

#define _PRIO_APP_HIGH      2
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY _PRIO_APP_HIGH
#define configMAX_SYSCALL_INTERRUPT_PRIORITY            configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY

and for UART’s interrupt prio, it’s the lowest.

#define _PRIO_APP_LOWEST    7

Which probably means it’s safe to use FromISR APIs in UART’s IRQ but that also means the UART’s interrupt may get disabled during critical sections hence the lag?

This configuration does not seem correct. How many priority bits does this part implement? This is a good resource - RTOS for ARM Cortex-M

Assuming that the configuration is correct, that is right.

Are you implying UART’s IRQ priority 7 is incorrect?

What happens when UART interrupt is disabled by RTOS and a character byte is sent? Does it get lost?

No, I cannot be sure about that as you did not share the code which sets the interrupt priority. I am talking about the configMAX_SYSCALL_INTERRUPT_PRIORITY - FreeRTOS expects the value to be shifted correctly according to the number of priority bits implemented. Here is an example - https://github.com/FreeRTOS/FreeRTOS/blob/main/FreeRTOS/Demo/CORTEX_MPU_M7_NUCLEO_H743ZI2_GCC_IAR_Keil/Config/FreeRTOSConfig.h#L96C1-L118C122. Please read the link I shared above.

I will check with Nordic.

The first solution you mentioned should work
Regarding this comment, are you referring snippet in my first comment?

You can check that in the documentation. For example, nRF52840 implements 3 priority bits as mentioned here - Nordic Semiconductor Infocenter

The sketch of your control flow is basically ok, but see below. A customer of mine has used exactly that strategy successfully for a long time.

An alternative would be to use a flag along with a mutex: A task wanting to access the TX stream would claim the mutex, then clear the flag, then start the transmission and then poll the flag with a (2) delay. The flag would be set by the ISR. That strategy would use a mutex instead of a semaphore and make it easier to implement application level timeouts but of course would force a granularity to the transmission.

Note, however, that a UART in very very few instances is employed for a full duplex (or unacknowledged) protocol,and that means that there is no point in allowing several tasks to access the same UART concurrently, as each task that wants to transmit something must also synchronously wait for a response before another transmission can start. Generally it is preferrable to have only one task (typically a message pump) serve the UART exclusively (we discussed this before). The architecture to access the UART very strongly relies on the requirements and the restrictions of the protocol realized over it.

(cf Issue with uart RX data when sharing with multiple tasks - #6 by RAc)

But using a flag and a mutex is similar to just using a semaphore here no? Basically you’re using it as a signalling mechanism more than an “exclusive right” kind of thing

UART in very very few instances is employed for a full duplex (or unacknowledged) protocol,and that means that there is no point in allowing several tasks to access the same UART concurrently,

That’s an interesting point. I am using nRF52840 and seemingly it supports Full Duplex and not half
On the topic of half duplex and race conditions, does the TX followed by RX happen before the access to the UART bus is given to a different device hence no chance of race condition since there’s no interruption whereas in full duplex, TX & RX can happen simultaneously? Is there any sequence diagram handy for half-duplex?

No, using it in the way I sketched would make it a true mutual exclusion - task A and B both want to access the resource (UART), and since it is the same task context that claims and releases the resource, we have a true mutual exclusion of tasks. This is relevant because with a pure signaling mechanism there is a possibility of race conditions in which a “wrong” task may be signalled or the sequence gets out of order (coincidentally, we happen to have a similar scenario under discussion right now). Again, the price to pay is the polling of the task on the flag.

Sorry for being unclear. I am NOT talking about whether a UART supports full duplex or not, but whether the protocol running on top of it allows it. A very typical application for UART access (and in fact one of the rare ones in which concurrent access to the UART is an issue in the first place) is a multidrop protocol in which the master serves several clients on a, say, RS485 bus. The protocol MUST enforce half duplex, meaning that the master addresses one slave via an address field in the data packet, then waits for the addressed slave to respond, then serves the next slave.

It is crucially important that each request-response pair is strictly serialized, meaning that the packet TX packet AND following RX packet reception are not interrupted by another pair (complicated by the possibility that a slave is offline, so timeout conditions must be implemented). In this kind of protocol, no asynchronous Rx can happen because a slave only sends in response to a request directed to it.

That kind of protocol, again, is best being implemented by a single task serving the UART exclusively and serializing all request-response pairs (of course, theoretically each slave could be served by a dedicated task with a full mutual exclusion around the request-response pair sequences, but then you may run into problems of slaves being starved if one slave is being heavily utilized. The single thread makes it easier to also implement a scheduling strategy).

How does your blocked task get to know that the UART transfer is finished so let’s now release it without polling a flag? isn’t this where a signalling mechanism makes more sense (just like demonstrated in my example) where a task is blocked until notified from within an interrupt, hence semaphores

True but UART isn’t an address-based protocol like I2C isn’t it? Like I could use UART to send 5 bytes without expecting a response in return so how could that work as half duplex in the sense of waiting for a response?

I do however understand your point about serializing the TX and RX which means the mutual exclusion may not be required since the transfer isn’t going to be interrupted as the bus remains busy till it’s done.

That is what I was trying to point out - what we call a “UART” acts on ISO/OSI level 1, and there are many different ways higher layers can use it. A lot of the driver logic depends on how higher levels use it. If you have, say, a peer-to-peer communication over RS232, you could theoretically implement a fully asynchronous protocol, but those are very rare. You could also implement an unacknowledged protocol or one that handles acknowledgments on a packet level.

Most protocols used in practice (at least in the areas I am familiar with) enforce half-duplex (eg if the PHY layer is not RS232 but RS485 which is inherently HD due to bus arbitration) and/or synchronous transmissions over the UART, so the exact implementation of the control flow (driver or application or both) is significantly influenced by the protocol requirements.

No, you NEED mutual exclusion as once one operation has started, it needs to finish before someone else can start an operation.

To expand a bit on what RAc is talking about, we can divide communication protocols into two major groups, There are protocols that are synchronous “Speak only when spoken to”, which support this sort of multi-requestor and exclusion operation, where each requestor gets control over the link, does its thing, and then gets off. There are also asynchronous protocols where either end might send a message at any time. This needs a different sort of driver that reads all the packets, and decodes it to see who it is supposed to go to.

I agree. Maybe I was misinterpreting the info. I am thinking from a semaphore standpoint as my example demonstrates as well which blocks the current task via Print after TXInterrupt until after signalled from the ISR indicating a transfer is done so the next task can … indeed utilize the UART task.

If there is just one task that is using the UART, then you have no synchronization issues, but you talk about multiple tasks wanting to send out via the “Print” function.

There is nothing in the code that keep multiple tasks out of the print function except the semaphore, which is being used to enforce the Mutual Exclusion.

So, your code does use mutual exclusion, and it needs to keep it.

Fair. How would you set up the interrupt priority of UART though? if it’s logically lower than configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY then the task that calls taskENTER_CRITICAL() shall disable the UART interrupt which means the UART data during that task execution may get lost?