LPUART_RTOS_Receive() does not receive the full data

My issue
I have written a programm that receives data from an external device via LPUART. It’s been working all fine with an example code that receives manual user input, but when I receive data that is being send by another programm (thus, very fast), every once in a while a byte goes missing and I’m running out of ideas how to fix this problem.

My system

  • I use the MIMXRT1060 EVK
  • I use the lpuart freertos driver
  • I work with LPUART2
  • I slightly modified LPUART_RTOS_Receive so that I can call it with a timeout
  • I use a ring buffer for receiving data. A buffer overflow does never occur, so that’s not the problem.

My thoughts and what I’ve tried so far
Because there is no buffer overflow whatsover, the problem must be somewhere closer to the hardware. It seems to me that interrupts are lost. But there’s the big question: Why and where?

  • fsl_lpuart_freertos uses an event group to recognize interrupts. I figured that maybe the interrupt handling takes too long so that some interrupts might not be noticed. So, I created a counting semaphore to ensure every interrupt is being recognized. But this did not fix the problem.
  • The IRQ priority of LPUART2 is set to 5 befor initialization. Changing the value (e.g. to configMAX_SYSCALL_INTERRUPT_PRIORITY + 1) did not fix the problem either.
  • Increasing configTICK_RATE_HZ did not help.

Has anyone got another idea where I could have missed some vital configuration or what could be the cause of the problem? Keep in mind that the code has been working fine with slow data flows, but it starts losing data when the transmission rate is very high.
Thanks in advance

Hi codingFoxy,

I’m not familiar with LPUARTs (whatever that is), but I suppose they’re a lot like “regular” UARTs.

If so, first thing I’d look at is the hardware overflow flag when you are in the ISR. If set, that means either that your ISR takes too long to process the receive (as you already suspected), or higher pri ISRs or liberal usage of crit sections or too slow a CPU prevent the ISR from timely flushing the receive buffer (you have already considered a few of those possibilities).

If that is the case and you can not counter that by means of code, you may consider using UART handshaking, either Soft (XON/XOFF) or hard (DTR,CTS pinnage) if your PHY and underlying protocol supports that.

You may also want to consider running your ISR on a priority > MAX_SYSCALL and using non FreeRTOS sync mechanisms to further process your data (we once did that successfully, eg that ISR sets a “data ready” flag that is being polled by the processor task).

If you don’t see hardware overflows, your error must logically by in the further processing of the data. Impossible to say how and where if we don’t know the control flow in your architecture past the ISR.

I just tried rewriting the code and using the max IRQ priority. Unfortunately, the result was the same.

For more context, here is what happens with the received data: I write every received byte into a test buffer that is big enough to hold all the data I will receive (2048 characters, to be precise). LPUART_RTOS_Receive never returns a buffer overrun error in a normal run.
But it has proven to work when I add a breakpoint in the code, thus it’s highly unlikely a buffer overflow will go unnoticed.

int error = 0;
size_t n = 0;

error = LPUART_RTOS_Receive( &LPUARThandle, u8LPUARTrecvBuf, LPUART_RECV_BUFFER_SIZE, &n, LPUART_RECEIVE_TIMEOUT );
if (kStatus_LPUART_RxHardwareOverrun == error)
{
    return notifyHardwareOverrun();
}
else if (kStatus_LPUART_RxRingBufferOverrun == error)
{
    return notifyRingBufferOverrun();
}
else if ((kStatus_LPUART_Timeout == error && IPCdataBuffCnt > 0) || IPCdataBuffCnt == IPC_DATA_BUFFER_SIZE)
{
    error = IPC_send( E_IPC_TASK_ID_GUI, E_IPC_MSG_TYPE_TERMINAL_MSG, IPCdataBuff, IPCdataBuffCnt, 0 );
    IPCdataBuffCnt = 0;
}
else if (n > 0)
{
    testBuf[testBufCnt++] = u8LPUARTrecvBuf[0];
}

As mentioned earlier, I use a slightly modified version of the library function LPUART_RTOS_Receive. This is what happens inside the function:

/* Non-blocking call */
LPUART_TransferReceiveNonBlocking(handle->base, handle->t_state, &handle->rxTransfer, &n);

ev = xEventGroupWaitBits(
        handle->rxEvent, RTOS_LPUART_RING_BUFFER_OVERRUN | RTOS_LPUART_HARDWARE_BUFFER_OVERRUN,
        pdTRUE, pdFALSE, 0 );

if (ev & RTOS_LPUART_HARDWARE_BUFFER_OVERRUN)
{
    /* Stop data transfer to application buffer, ring buffer is still active */
    LPUART_TransferAbortReceive(handle->base, handle->t_state);
    /* Prevent false indication of successful transfer in next call of LPUART_RTOS_Receive.
       RTOS_LPUART_COMPLETE flag could be set meanwhile overrun is handled */
    xEventGroupClearBits(handle->rxEvent, RTOS_LPUART_COMPLETE);
    retval         = kStatus_LPUART_RxHardwareOverrun;
    local_received = 0;
}
else if (ev & RTOS_LPUART_RING_BUFFER_OVERRUN)
{
    /* Stop data transfer to application buffer, ring buffer is still active */
    LPUART_TransferAbortReceive(handle->base, handle->t_state);
    /* Prevent false indication of successful transfer in next call of LPUART_RTOS_Receive.
       RTOS_LPUART_COMPLETE flag could be set meanwhile overrun is handled */
    xEventGroupClearBits(handle->rxEvent, RTOS_LPUART_COMPLETE);
    retval         = kStatus_LPUART_RxRingBufferOverrun;
    local_received = 0;
}
else if (xSemaphoreTake( xCountingSemaphore, atimeout ) == pdPASS)
{
    retval          = kStatus_Success;
    local_received  = length;
}
else
{
    retval         = kStatus_LPUART_Timeout;
    local_received = 0;
}

I’m not sure I understand your code. It can’t be ISR code because none of the functions you use are xxxFromISR() functions, but how do you think you can process incoming characters fast enough if you are not in an ISR?

The ISR function is another one from the library, it basically just sets flags and then leaves, so no processing is happening here.

static void LPUART_RTOS_Callback(LPUART_Type *base, lpuart_handle_t *state, status_t 
status, void *param)
{
    lpuart_rtos_handle_t *handle = (lpuart_rtos_handle_t *)param;
    BaseType_t xHigherPriorityTaskWoken, xResult;

    xHigherPriorityTaskWoken = pdFALSE;
    xResult                  = pdFAIL;

    if (status == kStatus_LPUART_RxIdle)
    {
        xResult = xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
    }
    else if (status == kStatus_LPUART_TxIdle)
    {
        xResult = xEventGroupSetBitsFromISR(handle->txEvent, RTOS_LPUART_COMPLETE, &xHigherPriorityTaskWoken);
    }
    else if (status == kStatus_LPUART_RxRingBufferOverrun)
    {
        xResult =
        xEventGroupSetBitsFromISR(handle->rxEvent, 
           RTOS_LPUART_RING_BUFFER_OVERRUN, &xHigherPriorityTaskWoken);
    }
    else if (status == kStatus_LPUART_RxHardwareOverrun)
    {
        /* Clear Overrun flag (OR) in LPUART STAT register */
        LPUART_ClearStatusFlags(base, kLPUART_RxOverrunFlag);
        xResult =
        xEventGroupSetBitsFromISR(handle->rxEvent, 
            RTOS_LPUART_HARDWARE_BUFFER_OVERRUN, &xHigherPriorityTaskWoken);
    }

     if (xResult != pdFAIL)
    {
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

The processing is done later, when I call LPUART_RTOS_Receive(). To make sure no data is lost, my background buffer holds 1024 bytes. This seems to be enough, as this buffer never overflows.

what do you mean by “background buffer?” Where in the ISR does the incoming character gets stored into that buffer? Or is that some kind of implicit DMA allocated/maintained buffer? If so, can you make a copy of that buffer at ISR time and then match the copy against your testBuf?

Here’s the documentation of the LPUART handle and the initialization of the connection. Unfortunaley, I the forum doesn’t allow me to post links yet, so you’ll have to add the mcuxpresso.nxp (dot) com in the beginning.

/api_doc/dev/210/group__lpuart__freertos__driver.html#structlpuart__rtos__config__t

I can’t tell exactly how the background buffer works, but maybe this helps with a general understanding of the library.

Found another comment in the source code on how the function receives data:

/* How to get data:
   1. If RX ring buffer is not enabled, then save xfer->data and xfer->dataSize
      to lpuart handle, enable interrupt to store received data to xfer->data. When
      all data received, trigger callback.
   2. If RX ring buffer is enabled and not empty, get data from ring buffer first.
      If there are enough data in ring buffer, copy them to xfer->data and return.
      If there are not enough data in ring buffer, copy all of them to xfer->data,
      save the xfer->data remained empty space to lpuart handle, receive data
      to this empty space and trigger callback when finished. */

xfer->data is a buffer of size 1 that is being passed to the function. What the function then does is disabling interrupts, fill up xfer->data and then enable interrupts again when the xfer->data is full.

    /* If RX ring buffer is used. */
    if (NULL != handle->rxRingBuffer)
    {
        /* Disable LPUART RX IRQ, protect ring buffer. */
        LPUART_DisableInterrupts(base, (uint32_t)kLPUART_RxDataRegFullInterruptEnable);

        /* How many bytes in RX ring buffer currently. */
        bytesToCopy = LPUART_TransferGetRxRingBufferLength(base, handle);

        if (0U != bytesToCopy)
        {
            bytesToCopy = MIN(bytesToReceive, bytesToCopy);

            bytesToReceive -= bytesToCopy;

            /* Copy data from ring buffer to user memory. */
            for (i = 0U; i < bytesToCopy; i++)
            {
                xfer->data[bytesCurrentReceived] = handle->rxRingBuffer[handle->rxRingBufferTail];
                bytesCurrentReceived++;

                /* Wrap to 0. Not use modulo (%) because it might be large and slow. */
                if (((uint32_t)handle->rxRingBufferTail + 1U) == handle->rxRingBufferSize)
                {
                    handle->rxRingBufferTail = 0U;
                }
                else
                {
                    handle->rxRingBufferTail++;
                }
            }
        }

        /* If ring buffer does not have enough data, still need to read more data. */
        if (0U != bytesToReceive)
        {
            /* No data in ring buffer, save the request to LPUART handle. */
            handle->rxData        = xfer->data + bytesCurrentReceived;
            handle->rxDataSize    = bytesToReceive;
            handle->rxDataSizeAll = bytesToReceive;
            handle->rxState       = (uint8_t)kLPUART_RxBusy;
        }
        /* Enable LPUART RX IRQ if previously enabled. */
        LPUART_EnableInterrupts(base, (uint32_t)kLPUART_RxDataRegFullInterruptEnable);

        /* Call user callback since all data are received. */
        if (0U == bytesToReceive)
        {
            if (NULL != handle->callback)
            {
                handle->callback(base, handle, kStatus_LPUART_RxIdle, handle->userData);
            }
        }
    }

I’ll try and catch the content of the ringbuffer directly to see if the result will be any different…

Edit:
First success! The data is complete in the ring buffer. So something inside the logic of LPUART_RTOS_Receive must go wrong…

I suspected that much! Congrats, the rest should be fairly straightforward to debug…

Turned out the problem was a slow IPC handler that I had programmed using xQueue. Came up with a solution that uses direct task notification and static buffers instead. Posted a link to the code in the forum under the tag community media…