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.

TM4C123GH6PGE: How to do the most efficient setup for the uart to do full duplex comms, with unknown message sizes?

Part Number: TM4C123GH6PGE
Other Parts Discussed in Thread: EK-TM4C123GXL

Hey ho
This is more a general question. I like to improve my simple generic uart zephyr driver. This driver listens to the uart rx interrupts , then copies the rx data to a ringbuffer and sets a semaphore to activate the processing thread. As well tx is done via pulling the bytes out.

Since I am using a RTOS, I like to offload the MCU from the data copying and do other stuff in the background.

So I am looking for the least MCU load generating way.

Guess DMA is a good option, but I didn't used that before. Especially for rx I like to know how to do the "end of message burst" detection. For example I am setting up the DMA to rx 1000Bytes. Now a message of 666Bytes is send. As the DMA didn't get 1000Bytes its still waiting for 334Bytes. No intr etc. How to do that kind of interruption + a buffer flip so we don't loose any data, just in case some data comes in while doing the buffer flip.

  • Hello Stefan,

    I definitely understand your goal here, but using the uDMA to receive characters only works well if the number of characters to receive is also known before the transfer starts. If not, such as when an arbitrary length string is ended with a termination character, then using interrupts is better since you are able to identify the termination character.

    The uDMA doesn't really have a feature like the end of message burst you described, you'd be getting interrupts based on size limits being hit instead.

    I wonder if there could be a middle ground though since you do have the RTOS architecture. Would it be possible to have the initial data transfer be handled like today and have a size of packet byte included in it, and then you could configure the DMA to receive the rest of the packet? That would let you get around the uDMA issue and leverage it to offload the CPU. But it definitely relies on a bit of a closed loop system where you know that you'd always get initial 'number of characters' marker every new transmission.

    Best Regards,

    Ralph Jacobi 

  • hey ho
    thx for the quick answer. Lets try to advance a bit.

    For the stm32 port of a Zephyr uart driver I used a mixture of dma and uart interrupts.

    For the rx it worked like that:
    1.) setup the DMA buffer and start the DMA in ringbuffer mode
    2.) use the DMA half and full complete interrupt for data processing
    3.) use the UART interrupt when it goes to idle (do data processing)

    Here is my stm32 code (https://github.com/StefJar/zephyr_stm32_uart3_dma_driver/blob/master/uart3_dma.c). It was the base for the Zephyr async uart api. So you can get a better idea what I am talking about.

    What we need for the UART+DMA RX is:

    1.) a function that gives us the current state of the dma transfer. I am looking for the offset of the dma rx data pointer.
    2.) there is already the UART_TXINT_MODE_EOT mode that should give us the end of transmission interrupt. SO everything fine to detect "bursts"

    I like to try an experiment and maybe you TI guys can help:
    1.) setup the DMA for UART0 rx in ringbuffer mode, buffer size 1024Bytes
    2.) setup the UART0 with the end of transmission intr enabeled

    Would be great if you can copy or point me to the right code snippets ;)

  • Hello Stefan,

    Thanks for sharing more details and thoughts about how you can implement this successfully. It does sound like there are options here that will get you in the direction you need.

    I like to try an experiment and maybe you TI guys can help:
    1.) setup the DMA for UART0 rx in ringbuffer mode, buffer size 1024Bytes
    2.) setup the UART0 with the end of transmission intr enabeled

    I reviewed one of our TivaWare examples which I think gives a good bit of this. The example is udma_demo and can be found at [Install Path]\TivaWare_C_Series-2.2.0.295\examples\boards\ek-tm4c123gxl\udma_demo. This example includes using two RX buffers. They default to 256 but you can change UART_RXBUF_SIZE to be 1024 bytes if you want! That said, the DMA buffers are already at 1024 and maybe that is more what you were wanting to adjust. It should be easy to play around with the buffer sizes.

    What that project doesn't have is the UART end of transmission interrupt setup as this is doing a circular loopback application so the uDMA is transmitting and receiving on the same peripheral.

    To configure UART0 TX interrupt (rest of UART config is in udma_demo) you would use these APIs:

    Fullscreen
    1
    2
    3
    4
    5
    //
    // Enable the UART interrupt.
    //
    MAP_IntEnable(INT_UART0);
    MAP_UARTIntEnable(UART0_BASE, UART_INT_TX);
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    Best Regards,

    Ralph Jacobi

  • got the latest TI HAL(2.2.0.295) and x compiled it. yeah

    Quick question on the DMA side. How do I get the current DMA write pointer/offset?

  • Hi Stefan,

    uDMAChannelSizeGet can get the current transfer size information:

    Fullscreen
    1
    2
    3
    4
    5
    //! This function is used to get the uDMA transfer size for a channel. The
    //! transfer size is the number of items to transfer, where the size of an item
    //! might be 8, 16, or 32 bits. If a partial transfer has already occurred,
    //! then the number of remaining items is returned. If the transfer is
    //! complete, then 0 is returned.
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    Best Regards,

    Ralph Jacobi

  • For my understanding lets assume an example.

    DMA buffer addr=1000 size=1024B
    UDMA init RX with addr=1000 and size=1024
    ....
    UART0 RX -> 100Bytes in one burst
    ....
    uart rx line stays calm
    ....

    UART0 INTR -> UART_TXINT_MODE_EOT
    UART0 ISR -> offs = uDMAChannelSizeGet()

    because the DMA rxed 100 bytes offs is now 100?

    now assume we get the next message burst

    UART0 RX -> 300Bytes in one burst
    ....
    uart rx line stays calm
    ....

    UART0 INTR -> UART_TXINT_MODE_EOT
    UART0 ISR -> offs = uDMAChannelSizeGet()

    is offs now 100 + 300 = 400?

  • Hello Stefan,

    Sorry to inform but I am currently out of office until Wednesday. My colleague Charles returns on Monday though. I will ask him to review and reply to you on Monday.

    Best Regards,

    Ralph Jacobi

  • hey Ralph

    Hope you had a good time. Currently I try to progress on the TX via DMA as a first step. Later I like to add RX via DMA. So I setup a an "empty" project. Only the pll will be init to 80MHz.

    Currently this code only prints something on UART0 via poll out. The DMA does not start. Maybe you can have a look and tell what went wrong.

    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    struct k_sem uartTxDone;
    struct k_mutex uartTxGuard;
    uint8_t ui8ControlTable[1024] __attribute__ ((aligned(1024)));
    static void udma_tiva_isr_error(const struct device *dev) {
    }
    static void udma_tiva_isr_software(const struct device *dev) {
    }
    static void UART0IntHandler(const struct device *dev) {
    uint32_t ui32Status;
    uint32_t ui32Mode;
    ui32Status = UARTIntStatus(UART0_BASE, 1);
    UARTIntClear(UART0_BASE, ui32Status);
    if(false == uDMAChannelIsEnabled(UDMA_CHANNEL_UART0TX)) {
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    my blueprint is "/TivaWare_C_Series-2.2.0.295/examples/boards/ek-tm4c123gxl/udma_demo"

  • HI,

      You wrote below lines of code.

    RQ_CONNECT(5,0, UART0IntHandler, NULL, 0);
    irq_enable(5);

    IRQ_CONNECT is your own API. Shouldn't you be using 21 instead of 5 as the vector number for UART0? In TI-RTOS, you would specify 21, not 5. You should check with your RTOS which one to take. 

    If you place a breakpoint at UART0IntHandler() do you see the processor stop there?

    Perhaps another question to ask is what RTOS are you using. If you were to use TI-RTOS, you must not use the TivaWare IntRegister() API to plug the interrupt vector. This will mess up the vector table that is managed by TI-RTOS. I don't know what RTOS you use and I don't know if using IRQ_CONNECT is the recommended way to plug interrupt vector manually for your RTOS. If your RTOS is fine then you can disregard this question. See this post. https://e2e.ti.com/support/microcontrollers/arm-based-microcontrollers-group/arm-based-microcontrollers/f/arm-based-microcontrollers-forum/849627/faq-can-i-update-the-vector-table-with-intregister-when-using-ti-rtos?tisearch=e2e-sitesearch&keymatch=faq%3Atrue

    Another thing after I compare your code with /TivaWare_C_Series-2.2.0.295/examples/boards/ek-tm4c123gxl/udma_demo is that you didn't have the below line. 

    //
    // Enable the UART DMA TX interrupts.
    //
    ROM_UARTIntEnable(UART0_BASE,UART_INT_DMATX);

  • thx
    good point with the intr but as said before - I am using the Zephyr framework/toolchain. They use the real intr number (see datasheet see p.105 table 2.9) not the vector number. The TI HAL adds 16 to get the number ;)
    In Zephyr the intr vector generation is done by the toolchain. IRQ_CONNECT is the right macro to connect the ISRs and the vector table.
    I done the an uart driver before (catching rx and tx intrs). The intr init stuff worked quite well then.

    I altered the code a bit

    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #include <stddef.h>
    #include <string.h>
    #include <errno.h>
    #include <zephyr.h>
    #include <logging/log.h>
    #include <logging/log_ctrl.h>
    #include <power/reboot.h>
    #include "../drv/tiva/cfg_tiva.h"
    #include "../drv/tiva/sysctl_tiva.h"
    struct k_sem uartTxDone;
    struct k_mutex uartTxGuard;
    uint8_t __aligned(1024) ui8ControlTable[1024];
    static void udma_tiva_isr_error(const struct device *dev) {
    uint32_t ui32Status;
    //
    // Check for uDMA error bit
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    I checked the alignment of the ui8ControlTable variable. Its placed with an 8K offset. So it guess that is fine.

    Shortly after calling "uDMAChannelEnable(UDMA_CHANNEL_UART0TX);"
    I get an dma error intr. The status is 1.
    So I guess something is wrongly setup.

    Maybe you can run the code against your toolchain (please substitute the intr handlers) and check if it works?

    The dma uart example only does an internal loopback. Let's get an example where it is used to put some longer sting out of the uart! ;)

  • Shortly after calling "uDMAChannelEnable(UDMA_CHANNEL_UART0TX);"
    I get an dma error intr. The status is 1.

    If you get a DMA error interrupt, it means there is a bus error. A bus error most likely means DMA is reading from or writing to an address that is illegal or unimplemented. I will suggest you do either one of below suggestions for debugging:

      - Run the udma_demo as is. udma_demo uses UART1 instead of UART0. Capture UART1 registers and uDMA register settings and ui8ControlTable. Modify your own code for UART1 and do the same capture of all the register and compare between them. 

    OR:

      - Modify udma_demo for UART0. First make sure it works first. Capture UART0, uDMA register settings and ui8ControlTable and compare with your own code. 

    The dma uart example only does an internal loopback. Let's get an example where it is used to put some longer sting out of the uart!

    All you need is to comment out below line to disable loopback. 

    //    HWREG(UART1_BASE + UART_O_CTL) |= UART_CTL_LBE;

    And replace with below lines to configure the UART1 pins. 

    //
    // Configure GPIO Pins for UART mode.
    //
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);

    ROM_GPIOPinConfigure(GPIO_PB0_U1RX);
    ROM_GPIOPinConfigure(GPIO_PB1_U1TX);
    ROM_GPIOPinTypeUART(GPIO_PORTB_BASE, GPIO_PIN_0 | GPIO_PIN_1);

  • As you see, my code is the reduced version udma. The changes are made is:
    - using UART0 instead of UART1

    - no RX setup

    - no loopback we want some real io

    Guess it has to deal with the setup

    Fullscreen
    1
    2
    3
    4
    5
    uDMAChannelAttributeEnable(UDMA_CHANNEL_UART0TX, UDMA_ATTR_USEBURST);
    uDMAChannelControlSet(UDMA_CHANNEL_UART0TX | UDMA_PRI_SELECT,
    UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE |
    UDMA_ARB_4);
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    Maybe UDMA_ARB_4/UDMA_ATTR_USEBURST is wrong. My idea is to have a not aligned(uint32) pointer that is passed to the send function.

    Maybe you know better how to configure the udma controller and its channels.

    I am looking for the minimal tx example ;)

  • Hi,

      Why don't try the below example? I modified the TivaWare udma_demo example for UART0 and it is working for me. 

    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #include <stdint.h>
    #include <stdbool.h>
    #include "inc/hw_ints.h"
    #include "inc/hw_memmap.h"
    #include "inc/hw_types.h"
    #include "inc/hw_uart.h"
    #include "driverlib/fpu.h"
    #include "driverlib/gpio.h"
    #include "driverlib/interrupt.h"
    #include "driverlib/pin_map.h"
    #include "driverlib/rom.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/systick.h"
    #include "driverlib/uart.h"
    #include "driverlib/udma.h"
    #include "utils/cpu_usage.h"
    #include "utils/uartstdio.h"
    #include "utils/ustdlib.h"
    //*****************************************************************************
    //
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    I can clearly see data come out on the TX pin.

    Data is also output to terminal. Since I output non-ASCII data, some of the characters are unreadable. 

  • thx

    I finally figured it out. The dma controller can't transfer data from flash.

    my test string was placed into flash memory. If I am putting it into RAM like

    char testTxt [] = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ hello!\n\r";

    it works fine.

    So follow up question: How do I configure the DMA controller to transfer data from the flash memory?

  • Hi Stefan,

      It is not possible. Please refer to the datasheet. 

    The μDMA controller can transfer data to and from the on-chip SRAM. However, because the Flash
    memory and ROM are located on a separate internal bus, it is not possible to transfer data from the
    Flash memory or ROM with the μDMA controller.

  • thx for clarifying

    next question
    If I want to use UDMA for UART4. Since there is nothing like UDMA_CHANNEL_UART0TX but there is  UDMA_CH19_UART4TX which looks like the right channel to me. Can I just replace UDMA_CHANNEL_UART0TX with UDMA_CH19_UART4TX or are there more things to consider.

    I am now starting to get the RX via DMA implemented

  • Yes, please also make sure you call as follows to assign UART4RX and UART4TX to channel 18 and 19 because GPTtimer0A and GPTimer0B are the default peripherals on these two channels. 

    uDMAChannelAssign(UDMA_CH18_UART4RX);

    uDMAChannelAssign(UDMA_CH19_UART4TX);

  • I am now having the RX via DMA running. Via

    Fullscreen
    1
    2
    3
    uint32_t getRXamount(const uint32_t channel) {
    return sizeof(rxBuffer) - uDMAChannelSizeGet(UART0_RX_CHANNEL | UDMA_PRI_SELECT);
    }
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX


    I can check how many bytes are rx via DMA controller.

    Now I need to get to know when the UART peripheral gets data rxed and when the rx data stream finishes.

    So I adjusted my uart init to catch the RX and RT interrupt

    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    void initUART(void){
    IRQ_CONNECT(5,0, uart0IntHandler, NULL, 0);
    irq_enable(5);
    //
    // Enable the peripherals used by this example.
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    //
    // Set GPIO A0 and A1 as UART pins.
    //
    GPIOPinConfigure(GPIO_PA1_U0TX);
    GPIOPinConfigure(GPIO_PA0_U0RX);
    GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_1 | GPIO_PIN_0);
    //
    // Enable the UART peripheral, and configure it to operate even if the CPU
    // is in sleep.
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    Currently I am getting only the RX DMA stop interrupt but not the RX or RT interrupt. Any idea what I need to change to get these interrupts?

  • I am getting only the RX DMA stop interrupt but not the RX or RT interrupt. Any idea what I need to change to get these interrupts?

    This is described in the datasheet. 

    When μDMA is enabled for a peripheral, the μDMA controller stops the normal transfer interrupts
    for a peripheral from reaching the interrupt controller (the interrupts are still reported in the peripheral's
    interrupt registers). Thus, when a large amount of data is transferred using μDMA, instead of receiving
    multiple interrupts from the peripheral as data flows, the interrupt controller receives only one interrupt
    when the transfer is complete. Unmasked peripheral error interrupts continue to be sent to the
    interrupt controller.

  • thx for clarifying
    I understand that the udma controller catches and handles the uart intr.
    Is there a possibility to catch the RT intr? Can I catch the unmask intr? Is there an other way to detect if the uart gets active/deactivated(syscontrol/power etc)?

  • Is there a possibility to catch the RT intr?

    That is a good question. I read the datasheet for UART and below is what I found. I think what you need to do is in the UART interrupt ISR, check the DMACHIS register to see if flag for the corresponding UART is set. If set, read the UART interrupt status register.

    The UART can also be configured to stop using DMA for the receive
    channel if a receive error occurs. If the DMAERR bit of the UARTDMACR register is set and a receive
    error occurs, the DMA receive requests are automatically disabled. This error condition can be
    cleared by clearing the appropriate UART error interrupt.

    When transfers are performed from a FIFO of the UART using the μDMA, and any interrupt is
    generated from the UART, the UART module's status bit in the DMA Channel Interrupt Status
    (DMACHIS) register must be checked at the end of the interrupt service routine. If the status bit is
    set, clear the interrupt by writing a 1 to it.

    Is there an other way to detect if the uart gets active/deactivated(syscontrol/power etc)?

    I don't think I understand the question. You activate uart by software so you should know it is active. If it deactivated due to error then refer to the answer above by reading the DMACHIS register. What do you mean about syscontrol and power? Enabling UART's clock via syscontrol is a software step. If uart loses power then the entire device loses power. 

  • hey ho
    I played abit around. So far I didn't managed to catch any intr that indicates that the uart rxed a burst of bytes(via DMA). I can perfectly check from time to time the DMA state via uDMAChannelSizeGet.

    the core intension of the question was to setup the UART rx in that way that it transfers in the background(via DMA) the rx bytes, till the buffer runs over or the uart rx falls silent.

    It comes down to a burst detection of uart messages with unknown length.

    Idea was to start the DMA rx and listen to the UART INTR like RT. If the RT intr hits we can check via uDMAChannelSizeGet how many bytes we rxed.  If we exceed the dma buffer we consider that something went wrong.

    From my current understanding, we can perfectly setup the RX via DMA. But we can't detect when the UART falls silent(correct me please if I am wrong). This is due to the fact that the DMA controller takes possession over the UART intrs. Means that the NVIC does not fire any uart intr. Thats why we can't read back the RX or RT intr.

    We also figured out, that I can use the DMA for TX. The constraints here are that the memory needs to be inside the RAM. So direct flash transfers are failing.

    For me the current best solution to my problem is:
    TX -> UART + UDMA + UART DMA INTR
    RX -> UART + FIFO + UART RX & RT INTR

    Maybe you can point to an example that describes the most efficient way(= lowest MCU load) to do the RX via FIFO and intr.

    Please add a comment and I am happy to close this question.

  • Idea was to start the DMA rx and listen to the UART INTR like RT. If the RT intr hits we can check via uDMAChannelSizeGet how many bytes we rxed.  If we exceed the dma buffer we consider that something went wrong.

    Let's first try to understand what RT (Receive Timeout) means in a non-uDMA environment. Please read below description from the d/s. This normally means the processor was perhaps too busy servicing other higher priority tasks and did not read the remaining data left in the RXFIFO in time. Can this condition happen to uDMA? Yes, if uDMA is busy serving other channels and didn't transfer data from RXFIFO in time. Suppose you want to transfer 1024 bytes from UART RXFIFO to a buffer and it happens that only 1020 are transferred. There are still 4 remaining in RXFIFO. If you call uDMAChannelSizeGet it will report the remaining number of bytes that are yet to be transferred which is 4 in this case. I don't understand why said you want to check if you exceed the dma buffer. The buffer will be 1024 and you will not exceed it if you have RT situation. Earlier I was suggesting you to read DMACHIS. Did you give it a try? Why don't you do an experiment? Setup uDMA to transfer from UART for 256 bytes. However, let you external UART device actually transfer 257 bytes. This means there will be 1 byte remaining in RXFIFO that the uDMA is unware of. Will you get any interrupt if you enable RT interrupt?

    The receive timeout interrupt is asserted when the receive FIFO is not empty, and no further data
    is received over a 32-bit period when the HSE bit is clear or over a 64-bit period when the HSE bit
    is set. The receive timeout interrupt is cleared either when the FIFO becomes empty through reading
    all the data (or by reading the holding register), or when a 1 is written to the corresponding bit in the
    UARTICR register.

    31.2.3.14 uDMAChannelSizeGet
    Gets the current transfer size for a uDMA channel control structure.
    Prototype:
    uint32_t
    uDMAChannelSizeGet(uint32_t ui32ChannelStructIndex)
    Parameters:
    ui32ChannelStructIndex is the logical OR of the uDMA channel number with either
    UDMA_PRI_SELECT or UDMA_ALT_SELECT.

    Description:
    This function is used to get the uDMA transfer size for a channel. The transfer size is the
    number of items to transfer, where the size of an item might be 8, 16, or 32 bits. If a partial
    transfer has already occurred, then the number of remaining items is returned. If the transfer
    is complete, then 0 is returned.
    Returns:
    Returns the number of items remaining to transfer.

  • at the RT intr: I used it before in the non fifo mode to detect the end of burst. I probably misunderstand it.

    my testing so far: I activated DMA for RX and TX and put a breakpoint into the UART0 isr. For TX I saw when the DMA was done with the transfer.
    For RX I didn't saw any intr, except when the rx buffer was fully filled by the dma controller. As you said before the DMA controler takes over the intr. So this makes sense.

    I now felt back to the TX via DMA and the RX via intr and FIFO. That works quite fine. And as said before I am sure that with that MCU its not possible to do RX DMA + RX burst detection(Like I did with a STM32F412 before).

    Thanks for the effort to explain it to me.