This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

RTOS/TM4C123GE6PM: UART Multibyte RX Completion Interrupt?

Part Number: TM4C123GE6PM


Tool/software: TI-RTOS

Is there a way that people can trigger an interrupt at the end of a string of UART bytes? I have multiple devices connected to UART ports and each device transmits data in "bursts", e.g., like a NMEA string from a GPS  module.

It would be nice to delay UART data processing until after the entire message has been received on the UART, rather than after every byte I have to stop to process a single byte at a time.

My thought is perhaps something involving a timeout when a new start bit does not follow a stop bit within X time? I ask because surely someone has perfected a way of handling this sort of behavior by now and I'd like to know what others are doing.

  • Yes, the TM4c123 has a 16 byte receive FIFO and you can program an interrupt to occur at 1/8 (2nd byte), 1/4 (4th byte), 1/2 (8th byte), 3/4(12th byte) or 7/8(14th byte) FIFO full. Then also enable the "Receive time-out" interrupt. The time-out interrupt occurs when the FIFO is not empty and no further data is received over a 32 bit period.
  • It's worth mentioning that I have my UART DMA support turned on, however it is quite transparent to me, so I'm not sure what that impacts.

    I won't ever know ahead of time how many bytes I'm expecting, so turning on a used FIFO proportion interrupt won't help. However, the Receive time-out sounds interesting. I believe I might be using it in a way already. Please correct me if I'm wrong (refer to the code attached). I have a timeout value of RADIO_UART_READ_TIMEOUT = 250 (ticks? Y=milliseconds, N= ?). But I'm using it as a sort of non-blocking UART read hack. I call read(), it either returns immediately with a single byte, or it times out and continues along its way.

    I'd like to be able to call read(), and it sits there and waits indefinitely until it has 1.) started receiving data and 2.) the data has stopped arriving after a previously unknown number of bytes. Can I do this?

    UART_Params_init(&uartParams);
    uartParams.writeDataMode = UART_DATA_BINARY; // these are the defaults
    uartParams.readDataMode = UART_DATA_BINARY;
    uartParams.readReturnMode = UART_RETURN_FULL; // also default
    uartParams.readEcho = UART_ECHO_OFF;
    uartParams.readTimeout = RADIO_UART_READ_TIMEOUT; // 250 (ms?)
    uartParams.baudRate = 9600;
    uartParams.stopBits = UART_STOP_ONE;
    uart = UART_open(RADIO_UART, &uartParams);
    if (uart == NULL) {
        System_abort("Error opening the UART");
    }

    I currently wait for a while until I know the device has responded and call read() with the length of my input buffer. If I called read() earlier, would it sit and wait until it has both started receiving data in addition to the timeout of any additional data arriving (i.e., saw the last stop bit with no more bytes coming in)?

    ret = UART_read(uart, buf, len);

    I'd like to capture the complete "burst" of data as it arrives, but it is further complicated by the fact that I'm not sure how many "bursts" I'm going to be receiving.  Here's the deal:

    1.) I ask the radio to provide me info on all the nodes in the network

    2.) For each node in the network, I get a "burst" of data. I'd like to be able to capture each "burst" with a single read() call.

    3.) I'm not sure how many nodes there are, so I can't call read() and allow it to hang indefinitely if no more are coming in.

    Given my design problem, is this possible or do I need to be more brutish about assembling the incoming data one byte at a time?

  • It really depends on if you use the TI-RTOS UART driver, or do your own. If each UART is only associated with a single task, you can do the interrupt service routine yourself and have it lookout for the NMEA0183 termination character, 0x0A. I worked on a NMEA0183 hub project that collected and re-broadcast data on four different UARTs at three different baud rates. The interrupt routine for a particular UART would simply grab each character from the UART and put it into a circular buffer. It kept track of NMEA0183 strings by initializing a pointer if the character received was '$' or '!'. Then when it received the next 0x0A, it set a flag indicating that a complete NMEA string had been received. The entire interrupt routine was relatively quick since no processing of the string was done in the interrupt. I used a large circular buffer for the received characters (8192) and a smaller circular buffer of structures that contained start and length of each received string. That could be simplified into two static 80 character strings. (I believe the maximum length NMEA0183 sentence is 80 characters.) When the first has received a valid sentence, you set a flag for that buffer, then start filling the next. Meanwhile, you have an RTOS task that looks for that volatile static flag, and when it is true, processes that string.

    I am going to ask the TI-RTOS experts if they have alternative suggestions.
  • Hi Mark,
    I don't think the TI-RTOS UART driver handles the read timeout interrupt very well. It enables it, but will not unblock the caller of UART_read() if it occurs. The only thing I can suggest is to increase the FIFO interrupt threshold and use the readTimeout of the UART_Params, which I see you are already doing.
    Best regards,
    Janet
  • My testing has revealed that with the uartParams.readTimeout set, the read() call does return, but it is not based on a delay from the last character received - rather it's based on when read() was called. So, in some cases it gathers a complete burst, but more often it cuts them into multiple read() calls. And that's okay I guess.

    I suppose what I should do is create my own interrupt handler that processes data as it arrives and assembles each "burst" into an entry in a FIFO. The task can them pop them out of the FIFO as they are completed and continue happily on its way. Once the device is at this stage, it's processing TLV (type-length-value) binary data with checksums, etc. So my interrupt handler will need to key off the start character and validate the checksum before placing it in the FIFO. Again, this is okay.

    Is there a way that I can set up the UART to trigger processing of received characters in the interrupt handler without me needing to call read()? Are there any examples of this?

    I'd like to be able to uart.write(data) whenever I want, but I'd like the read/receive part to be completely driven via the interrupt handler. Let my interrupt handler flag my task process when stuff is completed, otherwise don't bother me.
  • Hi Mark,

    You could probably start with the UART Tiva driver, and modify it to do what you want.  If you're using the non-DMA UART driver, once you open the UART, interrupts are enabled, and received data goes into a Ring Buffer, if nobody has called UART_read() yet.  You could have the UART interrupt handler call a processing function (which could be passed to the driver in the 'custom' field of the UART_Params).  The processing function could post a Swi or unblock a Task to do the read.
    I'm not sure the UARTTivaDMA driver would work for this, since it sets up the DMA according to the number of bytes passed to UART_read().

    Best regards,

    Janet

  • Hmm. I have two other UARTs working on separate devices and I like them operating in DMA mode. I think I enable/disable the DMA driver globally, so should I just disable it globally?
  • Hi Mark,

    Maybe DMA mode will still work with the read timeout (I've never tried it).  In your ISR, if you get the read timeout interrupt, you could stop any ongoing DMA transfer, call your processing function, and then reprogram the DMA.

    Best regards,

    Janet

  • I ended up using the driverlib/NVIC API to set my own interrupt handler. I had to modify my *.cmd file to add the vtable entry for the linker script:

    SECTIONS
    {
        .text   :   > FLASH
        .const  :   > FLASH
        .cinit  :   > FLASH
        .pinit  :   > FLASH
        .init_array : > FLASH
    
        .data   :   > SRAM
        .bss    :   > SRAM
        .sysmem :   > SRAM
        .stack  :   > SRAM
        .vtable :   > SRAM
    }

    I then modified my source file with an interrupt handler:

    void RadioRxCallback( void )
    {
        uint32_t ui32Status;
        int32_t rxChar;
    
        // Get current interrupt flags
        ui32Status = UARTIntStatus(RADIO_UART_BASE, true); //get interrupt status
        // Reset interrupt flags
        UARTIntClear(RADIO_UART_BASE, ui32Status); //clear the asserted interrupts
    
        while(UARTCharsAvail(RADIO_UART_BASE)) //loop while there are chars
        {
            rxChar = UARTCharGetNonBlocking(RADIO_UART_BASE);
            /* Error checking stuff here - See UARTCharGetNonBlocking() and tm4c123gh6pm.pdf - pg. 906 (UARTDR register description) */
        }
    }
    

    Then I set up my UART in the Task

        // Call NVIC API commands to set up interrupt handler
        IntRegister(RADIO_UART_RX_INTERRUPT, RadioRxCallback);
        IntEnable(RADIO_UART_RX_INTERRUPT);
        IntMasterEnable();
        // Set up pins
        SysCtlPeripheralEnable(PERIPH_RADIO_UART);
        SysCtlPeripheralEnable(PERIPH_RADIO_UART_PINS);
        GPIOPinConfigure(PIN_RADIO_RX);
        GPIOPinConfigure(PIN_RADIO_TX);
        GPIOPinTypeUART(PORT_RADIO_UART, PIN_RADIO_RX | PIN_RADIO_TX);
        // Set up Baud and characteristics
        UARTConfigSetExpClk(RADIO_UART_BASE, SysCtlClockGet(), 9600,
                            (UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE));
        // Enable only enable RX and RX Timeout interrupts
        UARTIntEnable(RADIO_UART_BASE, UART_INT_RX | UART_INT_RT);

    I think I'll be good from here. It offloads the need to constantly call read() from inside my task. I can flush out the interrupt handler to be smart about how it reacts to data on the fly. Thank you for your help.