UART TX in particular seems to have a race condition with scheduler running

I am noticing that particularly with the transmission of bytes over UART while scheduler is running, either some bytes go missing or the entire set of bytes aren't sent or received. (I'm seeing the UART stuff in the serial port). Though, I can send bytes from the serial port just fine: the ISR is triggered as soon as I type in something for each byte

So for according to the code snippet, I am printing Hello at the start of the app and often times I see Ello being printed and sometimes none of it

I'm wondering if there's a race condition somewhere and any assistance shall be greatly appreciated.

 

void Uart::TxByte(uint8_t byte)
{
        setNrfEvent(NRF_UART_EVENT_TXDRDY, 0);
        pUARTx->TXD = byte;     // triggers the IRQ handler by writing the first byte
}


void Uart::StartTx(uint8_t *buffer, size_t bytesToSend, bool blockingTx)
{
    setNrfEvent(NRF_UART_TASK_STARTTX, 1);
    
    fifoTx.enqueElems(buffer, bytesToSend);
    uint8_t val = fifoTx.deque();
    TxByte(val);

}

void Uart::PrintToTerminal(const char *format, ...)
{
    char buffer[100] = {0};
    va_list args;

    va_start (args, format);
    vsnprintf (buffer, sizeof(buffer), format, args);
    strcat(buffer, "\r\n");
    size_t bytesToSend = strlen(buffer);

    StartTx((uint8_t*) buffer, bytesToSend);
    va_end (args);
}

void UARTE0_UART0_IRQHandler(void)
{
    bool isTxdRdyIrqSet	   = getIrqRegStatus(NRF_UART_INT_MASK_TXDRDY);
    bool isTxdRdyEvntSet   = getNrfEventStatus(NRF_UART_EVENT_TXDRDY);
    
    if (isTxdRdyIrqSet && isTxdRdyEvntSet)
    {   
        setNrfEvent(NRF_UART_EVENT_TXDRDY, 0);	    // clear the TXDRDY bit
        
        // continue to send bytes till the FIFO is empty
        if (!fifoTx.isEmpty())      
        {
    	  uint8_t byte = fifoTx.deque();
    	  TxByte(byte);
        }
        else
        {
    	  // transmission ended
    	  setNrfEvent(NRF_UART_TASK_STOPTX, 1);
        }  
    }
}

bool Uart::getNrfEventStatus(nrf_uart_event_t reg) const
{
    return (bool) *(volatile uint32_t *)((uint8_t *)pUARTx + (uint32_t)reg);	
}

bool Uart::getIrqRegStatus(uint32_t mask)
{
    return (bool) (pUARTx->INTENSET & mask);
}

int main()
{
    uart.PrintToTerminal("Hello");
    // ...
    vTaskStartScheduler();	
}

Parents
  • Hi

    Can you confirm that you are using the nRF52840 device?

    First off you should use the UARTE peripheral rather than the UART peripheral in order to ensure that no bytes are lost. 

    Secondly, you should use the nrfx_uarte driver rather than using the peripheral directly. These drivers have been developed by us over time to configure the peripherals optimally, and have been thoroughly tested to ensure they work well in different applications. 

    Best regards
    Torbjørn

  • Yes, this is nRF62840.

    doesn't UARTE use DMA additionally? why would that ensure no bytes are lost?

    i am actually writing my own driver. do you see whether there's a possibility of a race condition in my snippet?

  • Understood your point about DMA but like you also mentioned, it's mostly used for stuff that requires high throughput like SPI.

    Understood about being offered limited support for custom stuff and it's totally fair. It's just if you could see any issues with the logic and if not, it's fine and if so, great!


    I find the fact that you clear the TXDRDY event both in the interrupt handler and in the TxByte function a bit worrying. 

    my understanding is: TxByte() sends the first byte only and is never invoked after since the first-byte transmission should enable the TXDRDY event which the interrupt should handle from then on.

    Possibly it will work better if you only clear this in StartTx and in the interrupt. 

    why inside StartTx? What difference does clearing inside StartTx make than inside TxByte?

  • Hi 

    morpho said:
    my understanding is: TxByte() sends the first byte only and is never invoked after since the first-byte transmission should enable the TXDRDY event which the interrupt should handle from then on.

    You call the TxByte() function inside the interrupt handler, as long as there is data in the FIFO. Wouldn't this mean it's called for every transmitted byte, except for the last?

    morpho said:
    why inside StartTx? What difference does clearing inside StartTx make than inside TxByte?

    Then you ensure that the TXDRDY event is cleared before you start a new transaction, but leave it up to the interrupt handler to clear it for every transmitted byte. 

    Technically it should be enough just to clear it in the interrupt handler, so this might also work. 

    Best regards
    Torbjørn

  • Hi

    Thanks. It does make sense to clear the TXDRDY event inside StartTx so that when the interrupt is fired, it's already enabled which can be disabled then and it does seem to do the trick in this case.

    However, I'm seeing an interesting behavior when I call PrintToTerminal() inside a sensor thread which is called as soon as a sensor value is read in a constant loop with a delay of 3 seconds.

    That's how I'm invoking PrintToTermina().

    snprintf(_notification.msg, 50, "temp: %u", tmpValue); // temp: 20 (for e.g)
    _uart.PrintToTerminal(_notification.msg);

    seems like every byte is printed every 3 seconds or so as opposed to the each message being printed rightaway so every 3 seconds, we should be seeing a new message. Do you see what could be going wrong here?

    in case you can't see the video: https://imgur.com/a/FKmEPrp

  • Hi 

    Any chance you are calling _uart.PrintToTerminal() from interrupt context, with higher or equal interrupt priority to the UART interrupt?

    Best regards
    Torbjørn

  • No, _uart.PrintToTermina() is invoked within a thread context and not ISR I believe.
    I am setting TWI_IRQ_PRIORITY as the interrupt priority for the I2C sensor and for UART, I'm passing in  APP_IRQ_PRIORITY_LOWEST as the priority. So perhaps it causes the UART interrupt to be preempted by the I2C's? 

    Although I don't read I2C values until after 3 seconds and the UART transmission shouldn't take that long before the next I2C interrupt occurs no?

    void twi_handler(nrf_drv_twi_evt_t const * p_event, void * p_context)	   
    {
        MCP9808 *obj = static_cast<MCP9808*>(p_context);
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
        switch (p_event->type)
        {
            case NRF_DRV_TWI_EVT_DONE:
                if (p_event->xfer_desc.type == NRF_DRV_TWI_XFER_RX)
                {
                    // unblock the waiting thread (mainThread) once RX XFER is done
                    vTaskNotifyGiveFromISR(obj->taskHandle, &xHigherPriorityTaskWoken); 
                }
                else if (p_event->xfer_desc.type == NRF_DRV_TWI_XFER_TX)
                {
                    NRF_LOG_INFO("TX transfer done...\n");
                    m_xfer_done = true; 
                }
                break;
            default:
                break;
        }
    }
    
    void MCP9808::mainThread()
    {
        xferData(tmpBuffer, 1);  
        vTaskDelay(pdMS_TO_TICKS(2000));
        uint32_t notifiedValue;
    
        while(true)
        {   
            uint32_t val = i2cRead(); // invokes TWI interrupt
    
            uint32_t taskNotify = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);  // blocks the current thread by decrementing ulNotifiedValue to 0 (taking a semaphore)
            if (taskNotify != 0)
            {
    	        NRF_LOG_WARNING("Transmission ended as expected...\n");
            }
            else
            {
    	        NRF_LOG_WARNING("The call to ulTaskNotifyTake timed out\n");
            }
            
            // if here, ISR is done executing and signalling to unblock this thread
    
            readTempInC();
    
            notify(this);       // calling _uart.PrintToTermina() within notify()
      
            vTaskDelay(pdMS_TO_TICKS(3000));
        }
    }

Reply
  • No, _uart.PrintToTermina() is invoked within a thread context and not ISR I believe.
    I am setting TWI_IRQ_PRIORITY as the interrupt priority for the I2C sensor and for UART, I'm passing in  APP_IRQ_PRIORITY_LOWEST as the priority. So perhaps it causes the UART interrupt to be preempted by the I2C's? 

    Although I don't read I2C values until after 3 seconds and the UART transmission shouldn't take that long before the next I2C interrupt occurs no?

    void twi_handler(nrf_drv_twi_evt_t const * p_event, void * p_context)	   
    {
        MCP9808 *obj = static_cast<MCP9808*>(p_context);
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
        switch (p_event->type)
        {
            case NRF_DRV_TWI_EVT_DONE:
                if (p_event->xfer_desc.type == NRF_DRV_TWI_XFER_RX)
                {
                    // unblock the waiting thread (mainThread) once RX XFER is done
                    vTaskNotifyGiveFromISR(obj->taskHandle, &xHigherPriorityTaskWoken); 
                }
                else if (p_event->xfer_desc.type == NRF_DRV_TWI_XFER_TX)
                {
                    NRF_LOG_INFO("TX transfer done...\n");
                    m_xfer_done = true; 
                }
                break;
            default:
                break;
        }
    }
    
    void MCP9808::mainThread()
    {
        xferData(tmpBuffer, 1);  
        vTaskDelay(pdMS_TO_TICKS(2000));
        uint32_t notifiedValue;
    
        while(true)
        {   
            uint32_t val = i2cRead(); // invokes TWI interrupt
    
            uint32_t taskNotify = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);  // blocks the current thread by decrementing ulNotifiedValue to 0 (taking a semaphore)
            if (taskNotify != 0)
            {
    	        NRF_LOG_WARNING("Transmission ended as expected...\n");
            }
            else
            {
    	        NRF_LOG_WARNING("The call to ulTaskNotifyTake timed out\n");
            }
            
            // if here, ISR is done executing and signalling to unblock this thread
    
            readTempInC();
    
            notify(this);       // calling _uart.PrintToTermina() within notify()
      
            vTaskDelay(pdMS_TO_TICKS(3000));
        }
    }

Children
Related