Errata e2582 UART flow control timing issue can result in loss of characters

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Errata e2582 UART flow control timing issue can result in loss of characters

Jump to solution
1,638 Views
michaelguntli
Contributor IV

Dear Freescale Community

Task to solve:

Kinetis K60 (K60P144M150SF3) has to receive data over UART (hardware flow control) with a high baud rate (576000kbps), data is sent from an i.MX6 with a UART-TX DMA transfer.

Known errata for Kinetis:

- http://www.freescale.com/files/microcontrollers/doc/errata/KINETIS_4N30D.pdf

Suggested workaround according to errata:

"For UARTs without a FIFO (or if the FIFO is disabled): Delay might need to be added between characters on the transmit side in order to allow time for the negation of /RTS to be recognized before the next character is sent."

--> not applicable, the i.MX6 uses a DMA transfer for the UART data.

Problems:

  1. UART with FIFO buffers are not available, only UART4 which have a depth of 1 byte are available
  2. Service the UART with a traditional "1 interrupt per byte" is too slow, which result in data loss according to the errata
  3. Due to the errata e2584 (UART: Possible conflicts between UART interrupt service routines and DMA), I cannot properly use a DMA RX transfer with idle timeout
    DISCUSSION: Missing critical RX UART DMA Event Functionality in Freescale Parts
  4. Suggested workaround to add a transfer delay on the transmitter is not acceptable, because this is a DMA transfer started from the i.MX6

Proposed solution for discussion:

I can see currently a single solution to the problem, which is kind of ugly but would at least guarantee the correct data receiption:

  1. Create a ringbuffer with a fixed size (e.g. 1024 bytes).
    Head is read position from software, Tail is DMA-write position
    ring-buffer-ex1.jpg

  2. Configure a UART-RX-DMA for each byte, and write data to into ringbuffer
    1. Set the destination address:
      • DMA_TCD_DADDR = address of ringbuffer[0]
    2. Configure buffer size 1024:
      • DMA_TCD_BITER_ELINK = 1024
      • DMA_TCD_CITER_ELINK = 1024
    3. Transfer size 1 byte for source and destination:
      • DMA_TCD_ATTR = 0 (DSIZE = 000 (8-bit), SSIZE = 000 (8-bit))
    4. Each request starts a single transfer:
      • DMA_TCD_NBYTES_ML = 1
    5. DMA source is UART4 data register
      • DMA_TCD_SADDR = 0x400EA007
    6. Source not increment:
      • DMA_TCD_SOFF = 0
    7. Destination incremented:
      • DMA_TCD_DOFF = 1
    8. Set DMA to auto reload when reached the end of the buffer (=ringbuffer mode):
      • DMA_TCD_SLAST = 0
      • DMA_TCD_DLASTSGA = -1024
    9. No interrupt notifications from DMA:
      • DMA_TCD_CSR = 0

  3. Poll and read from the ringbuffer within software in a given interval:
    1. Read Tail once from current DMA-write position into a variable, and consume the data until Head == Tail
    2. On next poll read again until Head == Tail

Question:

Is the DMA_TCD_CITER_ELINK the correct register to read the current DMA write position?

pmtmjbcswitzerland Maybe one of you could have a look at my proposed solution?

Labels (1)
Tags (4)
0 Kudos
1 Solution
815 Views
mjbcswitzerland
Specialist V

Michael

The uTasker project includes K60 Rx DMA ring buffer support - either circular as you have suggested as solution, full-buffer or half-buffer interrupt (less useful though). Full source available at:

http://www.utasker.com/forum/index.php?topic=1721.0

Usage: http://www.utasker.com/docs/uTasker/uTaskerUART.PDF

The ring buffer size can be any length (doesn't need to be a modulo length).

With Rx DMA there is little point in HW flow control since the DMA ensures that no reception is lost at the byte level. You just need to ensure that the application polls at a rate that doesn't allow the ring buffer to overflow - the K60 has quite a lot of RAM so the buffer size can be set fairly large to bridge the worst case.

Personnally I have used the method in a communication product using multiple UARTs in the 1.5MBaud region (specifically to route IP data over serial based RF links) and know of no issues (in production for around 3 years and several thousand in the field). In parallel there are Ethernet and USB operations taking place.

Typical usage:

    // Configuration
    //
    TTYTABLE tInterfaceParameters;                                       // table for passing information to driver
    tInterfaceParameters.Channel = 0;                                    // set UART channel for serial use
    tInterfaceParameters.ucSpeed = SERIAL_BAUD_921600;                   // baud rate
    tInterfaceParameters.Rx_tx_sizes.RxQueueSize = (4 * 1024);           // input buffer size
    tInterfaceParameters.Rx_tx_sizes.TxQueueSize = (1 * 1024);           // output buffer size
    tInterfaceParameters.Config = (CHAR_8 + NO_PARITY + ONE_STOP + CHAR_MODE);
    tInterfaceParameters.ucDMAConfig = (UART_TX_DMA  | UART_RX_DMA);     // activate DMA on transmission and use free-running DMA on rx
    if ((SerialPortID = fnOpen(TYPE_TTY, ucDriverMode, &tInterfaceParameters)) != NO_ID_ALLOCATED) { // open or change the channel with defined configurations (initially inactive)
        fnDriver(SerialPortID, (TX_ON | RX_ON), 0);                      // enable rx and tx
    }

    // In polling task
    //
    unsigned char ucRxBuffer[RX_BUFFER_SIZE];
    unsigned long ulLength = fnRead(SerialPortID, ucRxBuffer, RX_BUFFER_SIZE); // extract max RX_BUFFER_SIZE size from the UART input buffer
    if (ulLength != 0) {
        // process ulLength of reception
    }

Although it would be nice to have some other mechanisms in the HW to signal the DMA progress you shouldn't have any practical issues with this on the K60.

Regards

Mark

View solution in original post

0 Kudos
3 Replies
816 Views
mjbcswitzerland
Specialist V

Michael

The uTasker project includes K60 Rx DMA ring buffer support - either circular as you have suggested as solution, full-buffer or half-buffer interrupt (less useful though). Full source available at:

http://www.utasker.com/forum/index.php?topic=1721.0

Usage: http://www.utasker.com/docs/uTasker/uTaskerUART.PDF

The ring buffer size can be any length (doesn't need to be a modulo length).

With Rx DMA there is little point in HW flow control since the DMA ensures that no reception is lost at the byte level. You just need to ensure that the application polls at a rate that doesn't allow the ring buffer to overflow - the K60 has quite a lot of RAM so the buffer size can be set fairly large to bridge the worst case.

Personnally I have used the method in a communication product using multiple UARTs in the 1.5MBaud region (specifically to route IP data over serial based RF links) and know of no issues (in production for around 3 years and several thousand in the field). In parallel there are Ethernet and USB operations taking place.

Typical usage:

    // Configuration
    //
    TTYTABLE tInterfaceParameters;                                       // table for passing information to driver
    tInterfaceParameters.Channel = 0;                                    // set UART channel for serial use
    tInterfaceParameters.ucSpeed = SERIAL_BAUD_921600;                   // baud rate
    tInterfaceParameters.Rx_tx_sizes.RxQueueSize = (4 * 1024);           // input buffer size
    tInterfaceParameters.Rx_tx_sizes.TxQueueSize = (1 * 1024);           // output buffer size
    tInterfaceParameters.Config = (CHAR_8 + NO_PARITY + ONE_STOP + CHAR_MODE);
    tInterfaceParameters.ucDMAConfig = (UART_TX_DMA  | UART_RX_DMA);     // activate DMA on transmission and use free-running DMA on rx
    if ((SerialPortID = fnOpen(TYPE_TTY, ucDriverMode, &tInterfaceParameters)) != NO_ID_ALLOCATED) { // open or change the channel with defined configurations (initially inactive)
        fnDriver(SerialPortID, (TX_ON | RX_ON), 0);                      // enable rx and tx
    }

    // In polling task
    //
    unsigned char ucRxBuffer[RX_BUFFER_SIZE];
    unsigned long ulLength = fnRead(SerialPortID, ucRxBuffer, RX_BUFFER_SIZE); // extract max RX_BUFFER_SIZE size from the UART input buffer
    if (ulLength != 0) {
        // process ulLength of reception
    }

Although it would be nice to have some other mechanisms in the HW to signal the DMA progress you shouldn't have any practical issues with this on the K60.

Regards

Mark

0 Kudos
815 Views
michaelguntli
Contributor IV

Thanks Mark, but the codebase of uTasker is huge (11'000 lines of code for kinetis.c).

I guess I found the define (SERIAL_SUPPORT_DMA_RX_FREERUN), that's the mode I am looking for.

I would like to write here a summary of the register configurations, limited to the information what's written in the reference manual.

Configuration: UART4 RX should generate a DMA request on channel 0

  1. Configure UART to trigger a DMA request
    • UART_C5:
      • RDMAS = 1
    • UART_C2:
      • RIE = 1
  2. Enable clock of DMAMUX0
    • SIM_SCGC6
      • DMAMUX0 = 1
  3. Configure Channel 0 for UART4 RX Interrupt in DMAMUX0
    • DMAMUX0_CHCFG0
      • Source = 0x0A
      • ENBL = 1
  4. Configure DMA Transfer Control Descriptor
    • Set the destination address:
      • DMA_TCD_DADDR = address of ringbuffer[0]
    • Configure buffer size 1024:
      • DMA_TCD_BITER_ELINK = 1024
      • DMA_TCD_CITER_ELINK = 1024
    • Transfer size 1 byte for source and destination:
      • DMA_TCD_ATTR = 0 (DSIZE = 000 (8-bit), SSIZE = 000 (8-bit))
    • Each request starts a single transfer:
      • DMA_TCD_NBYTES_ML = 1
    • DMA source is UART4 data register
      • DMA_TCD_SADDR = 0x400EA007
    • Source not increment:
      • DMA_TCD_SOFF = 0
    • Destination incremented:
      • DMA_TCD_DOFF = 1
    • Set DMA to auto reload when reached the end of the buffer (=ringbuffer mode):
      • DMA_TCD_SLAST = 0
      • DMA_TCD_DLASTSGA = -1024
    • No interrupt notifications from DMA:
      • DMA_TCD_CSR = 0
  5. Enable Request signal
    • DMA_ERQ
      • ERQ0 = 1
0 Kudos
815 Views
mjbcswitzerland
Specialist V

Michael

That looks right.

This is the generic code that does the same in the uTasker driver:

Channel = 0..5

UART_DMA_RX_CHANNEL[Channel] specifies the DMA channel used by the UART

pars->Rx_tx_sizes.RxQueueSize is the ring buffer size

ptrStart is the start address of the ring buffer

usDMA_progress[] is used later to monitor the DMA progress

        KINETIS_DMA_TDC *ptrDMA_TCD = (KINETIS_DMA_TDC *)eDMA_DESCRIPTORS;
        ptrDMA_TCD += UART_DMA_RX_CHANNEL[Channel];                      // set the DMA channel register
        uart_reg->UART_C5 |= UART_C5_RDMAS;                              // use DMA rather than interrupts for reception
        POWER_UP(6, SIM_SCGC6_DMAMUX0);                                  // enable DMA multiplexer 0
        *(unsigned char *)(DMAMUX0_BLOCK + UART_DMA_RX_CHANNEL[Channel]) = ((DMAMUX_CHCFG_SOURCE_UART0_RX + (2 * Channel)) | DMAMUX_CHCFG_ENBL); // connect UART rx to DMA channel
        ptrDMA_TCD->DMA_TCD_BITER_ELINK = ptrDMA_TCD->DMA_TCD_CITER_ELINK = pars->Rx_tx_sizes.RxQueueSize; // the length of the input buffer in use
        ptrDMA_TCD->DMA_TCD_SOFF = 0;                                    // source not increment
        ptrDMA_TCD->DMA_TCD_DOFF = 1;                                    // destination incremented
        ptrDMA_TCD->DMA_TCD_ATTR = (DMA_TCD_ATTR_DSIZE_8 | DMA_TCD_ATTR_SSIZE_8); // transfer sizes always single bytes
        ptrDMA_TCD->DMA_TCD_SADDR = (unsigned long)&(uart_reg->UART_D);  // source is the UART's data register
        ptrDMA_TCD->DMA_TCD_NBYTES_ML = 1;                               // each request starts a single transfer
        ptrDMA_TCD->DMA_TCD_CSR = 0;
        usDMA_progress[Channel] = ptrDMA_TCD->DMA_TCD_BITER_ELINK;
        ptrDMA_TCD->DMA_TCD_SLAST = 0;                                   // no change to address when the buffer has filled
        ptrDMA_TCD->DMA_TCD_DLASTSGA = (-pars->Rx_tx_sizes.RxQueueSize); // when the buffer has been filled set the destination back to the start of it

        ptrDMA_TCD->DMA_TCD_DADDR = (unsigned long)ptrStart;             // destination is the input tty buffer
        DMA_ERQ |= (DMA_ERQ_ERQ0 << UART_DMA_RX_CHANNEL[channel]);       // enable request source
        fnRxOn(channel);                                                 // configure receiver pin and enable reception and its interrupt/DMA

Regards

Mark

0 Kudos