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.
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:
// // Enable the UART interrupt. // MAP_IntEnable(INT_UART0); MAP_UARTIntEnable(UART0_BASE, UART_INT_TX);
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:
//! 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.
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.
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)) { k_sem_give(&uartTxDone); } } void initUART(void) { k_sem_init(&uartTxDone, 0, 1); k_mutex_init(&uartTxGuard); IRQ_CONNECT(5,0, UART0IntHandler, NULL, 0); irq_enable(5); IRQ_CONNECT(46,0, udma_tiva_isr_software, NULL, 0); irq_enable(46); IRQ_CONNECT(47,0, udma_tiva_isr_error, NULL, 0); irq_enable(47); SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA); uDMAEnable(); uDMAControlBaseSet(ui8ControlTable); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0); GPIOPinConfigure(GPIO_PA0_U0RX); GPIOPinConfigure(GPIO_PA1_U0TX); GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1); UARTClockSourceSet(UART0_BASE, UART_CLOCK_SYSTEM); UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), 115200, UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE); UARTFIFOLevelSet(UART0_BASE, UART_FIFO_TX4_8, UART_FIFO_RX4_8); UARTEnable(UART0_BASE); UARTDMAEnable(UART0_BASE, UART_DMA_TX); uDMAChannelAttributeDisable(UDMA_CHANNEL_UART0TX, UDMA_ATTR_ALTSELECT | UDMA_ATTR_HIGH_PRIORITY | UDMA_ATTR_REQMASK ); 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); } void send(char * txt, const uint32_t len) { k_mutex_lock(&uartTxGuard, K_FOREVER); uDMAChannelTransferSet(UDMA_CHANNEL_UART0TX | UDMA_PRI_SELECT, UDMA_MODE_BASIC, txt, // source (void *)(UART0_BASE + UART_O_DR), // dest len); uDMAChannelEnable(UDMA_CHANNEL_UART0TX); k_sem_take(&uartTxDone, K_FOREVER); k_mutex_unlock(&uartTxGuard); } void sendTxt(char * txt) { send(txt, strlen(txt)); } void sendPoll(char * txt) { uint32_t c; c = strlen(txt); while(c) { UARTCharPutNonBlocking(UART0_BASE, *txt); txt++; c--; } } void main(void) { initUART(); sendPoll("start done!\n\r"); for (;;) { sendTxt("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ hello!\n\r"); k_sleep(K_SECONDS(1)); } }
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
#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 // ui32Status = uDMAErrorStatusGet(); // // If there is a uDMA error, then clear the error and increment // the error counter. // if(ui32Status) { uDMAErrorStatusClear(); } } 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)) { k_sem_give(&uartTxDone); } } void initUART(void) { k_sem_init(&uartTxDone, 0, 1); k_mutex_init(&uartTxGuard); IRQ_CONNECT(5,0, uart0IntHandler, NULL, 0); irq_enable(5); IRQ_CONNECT(46,0, udma_tiva_isr_software, NULL, 0); irq_enable(46); IRQ_CONNECT(47,0, udma_tiva_isr_error, NULL, 0); irq_enable(47); SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA); while (false == SysCtlPeripheralReady(SYSCTL_PERIPH_UDMA)){ } uDMAEnable(); uDMAControlBaseSet(ui8ControlTable); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0); while (false == SysCtlPeripheralReady(SYSCTL_PERIPH_UART0)){ } GPIOPinConfigure(GPIO_PA0_U0RX); GPIOPinConfigure(GPIO_PA1_U0TX); GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1); //UARTClockSourceSet(UART0_BASE, UART_CLOCK_SYSTEM); UARTClockSourceSet(UART0_BASE, UART_CLOCK_PIOSC); UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), 115200, UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE); UARTFIFOLevelSet(UART0_BASE, UART_FIFO_TX4_8, UART_FIFO_RX4_8); UARTIntEnable(UART0_BASE, UART_INT_DMATX); UARTIntClear(UART0_BASE, 0xFFFFFFFF); UARTEnable(UART0_BASE); uDMAErrorStatusClear(); UARTDMAEnable(UART0_BASE, UART_DMA_TX); uDMAChannelAttributeDisable(UDMA_CHANNEL_UART0TX, UDMA_ATTR_ALTSELECT | UDMA_ATTR_HIGH_PRIORITY | UDMA_ATTR_REQMASK ); 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); } void send(char * txt, const uint32_t len) { k_mutex_lock(&uartTxGuard, K_FOREVER); uDMAChannelTransferSet(UDMA_CHANNEL_UART0TX | UDMA_PRI_SELECT, UDMA_MODE_BASIC, txt, // source (void *)(UART0_BASE + UART_O_DR), // dest len); uDMAChannelEnable(UDMA_CHANNEL_UART0TX); k_sem_take(&uartTxDone, K_FOREVER); k_mutex_unlock(&uartTxGuard); } void sendTxt(char * txt) { send(txt, strlen(txt)); } void sendPoll(char * txt) { uint32_t c; c = strlen(txt); while(c) { UARTCharPutNonBlocking(UART0_BASE, *txt); txt++; c--; } } void main(void) { initUART(); sendPoll("start done!\n\r"); for (;;) { sendTxt("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ hello!\n\r"); k_sleep(K_SECONDS(1)); } }
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
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);
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.
#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" //***************************************************************************** // //! \addtogroup example_list //! <h1>uDMA (udma_demo)</h1> //! //! This example application demonstrates the use of the uDMA controller to //! transfer data between memory buffers, and to transfer data to and from a //! UART. The test runs for 10 seconds before exiting. //! //! UART0, connected to the FTDI virtual COM port and running at 115,200, //! 8-N-1, is used to display messages from this application. // //***************************************************************************** //***************************************************************************** // // The number of SysTick ticks per second used for the SysTick interrupt. // //***************************************************************************** #define SYSTICKS_PER_SECOND 100 //***************************************************************************** // // The size of the memory transfer source and destination buffers (in words). // //***************************************************************************** #define MEM_BUFFER_SIZE 1024 //***************************************************************************** // // The size of the UART transmit and receive buffers. They do not need to be // the same size. // //***************************************************************************** #define UART_TXBUF_SIZE 256 //256 #define UART_RXBUF_SIZE 256 //***************************************************************************** // // The source and destination buffers used for memory transfers. // //***************************************************************************** static uint32_t g_ui32SrcBuf[MEM_BUFFER_SIZE]; static uint32_t g_ui32DstBuf[MEM_BUFFER_SIZE]; //***************************************************************************** // // The transmit and receive buffers used for the UART transfers. There is one // transmit buffer and a pair of recieve ping-pong buffers. // //***************************************************************************** static uint8_t g_ui8TxBuf[UART_TXBUF_SIZE]; //***************************************************************************** // // The count of uDMA errors. This value is incremented by the uDMA error // handler. // //***************************************************************************** static uint32_t g_ui32uDMAErrCount = 0; //***************************************************************************** // // The count of times the uDMA interrupt occurred but the uDMA transfer was not // complete. This should remain 0. // //***************************************************************************** static uint32_t g_ui32BadISR = 0; //***************************************************************************** // // The count of memory uDMA transfer blocks. This value is incremented by the // uDMA interrupt handler whenever a memory block transfer is completed. // //***************************************************************************** static uint32_t g_ui32MemXferCount = 0; //***************************************************************************** // // The number of seconds elapsed since the start of the program. This value is // maintained by the SysTick interrupt handler. // //***************************************************************************** static uint32_t g_ui32Seconds = 0; //***************************************************************************** // // The control table used by the uDMA controller. This table must be aligned // to a 1024 byte boundary. // //***************************************************************************** #if defined(ewarm) #pragma data_alignment=1024 uint8_t ui8ControlTable[1024]; #elif defined(ccs) #pragma DATA_ALIGN(ui8ControlTable, 1024) uint8_t ui8ControlTable[1024]; #else uint8_t ui8ControlTable[1024] __attribute__ ((aligned(1024))); #endif //***************************************************************************** // // The error routine that is called if the driver library encounters an error. // //***************************************************************************** #ifdef DEBUG void __error__(char *pcFilename, uint32_t ui32Line) { while(1) { // // Hang on runtime error. // } } #endif //***************************************************************************** // // The interrupt handler for the SysTick timer. This handler will increment a // seconds counter whenever the appropriate number of ticks has occurred. It // will also call the CPU usage tick function to find the CPU usage percent. // //***************************************************************************** void SysTickHandler(void) { static uint32_t ui32TickCount = 0; // // Increment the tick counter. // ui32TickCount++; // // If the number of ticks per second has occurred, then increment the // seconds counter. // if(!(ui32TickCount % SYSTICKS_PER_SECOND)) { g_ui32Seconds++; } } //***************************************************************************** // // The interrupt handler for uDMA errors. This interrupt will occur if the // uDMA encounters a bus error while trying to perform a transfer. This // handler just increments a counter if an error occurs. // //***************************************************************************** void uDMAErrorHandler(void) { uint32_t ui32Status; // // Check for uDMA error bit // ui32Status = ROM_uDMAErrorStatusGet(); // // If there is a uDMA error, then clear the error and increment // the error counter. // if(ui32Status) { ROM_uDMAErrorStatusClear(); g_ui32uDMAErrCount++; } } //***************************************************************************** // // The interrupt handler for uDMA interrupts from the memory channel. This // interrupt will increment a counter, and then restart another memory // transfer. // //***************************************************************************** void uDMAIntHandler(void) { uint32_t ui32Mode; // // Check for the primary control structure to indicate complete. // ui32Mode = ROM_uDMAChannelModeGet(UDMA_CHANNEL_UART0TX); if(ui32Mode == UDMA_MODE_STOP) { // // Increment the count of completed transfers. // g_ui32MemXferCount++; // // Configure it for another transfer. // ROM_uDMAChannelTransferSet(UDMA_CHANNEL_UART0TX, UDMA_MODE_AUTO, g_ui32SrcBuf, g_ui32DstBuf, MEM_BUFFER_SIZE); // // Initiate another transfer. // ROM_uDMAChannelEnable(UDMA_CHANNEL_UART0TX); ROM_uDMAChannelRequest(UDMA_CHANNEL_UART0TX); } // // If the channel is not stopped, then something is wrong. // else { g_ui32BadISR++; } } //***************************************************************************** // // The interrupt handler for UART0. This interrupt will occur when a DMA // transfer is complete using the UART0 uDMA channel. It will also be // triggered if the peripheral signals an error. This interrupt handler will // switch between receive ping-pong buffers A and B. It will also restart a TX // uDMA transfer if the prior transfer is complete. This will keep the UART // running continuously (looping TX data back to RX). // //***************************************************************************** void UART0IntHandler(void) { uint32_t ui32Status; // // Read the interrupt status of the UART. // ui32Status = ROM_UARTIntStatus(UART0_BASE, 1); // // Clear any pending status, even though there should be none since no UART // interrupts were enabled. If UART error interrupts were enabled, then // those interrupts could occur here and should be handled. Since uDMA is // used for both the RX and TX, then neither of those interrupts should be // enabled. // ROM_UARTIntClear(UART0_BASE, ui32Status); // // If the UART0 DMA TX channel is disabled, that means the TX DMA transfer // is done. // if(!ROM_uDMAChannelIsEnabled(UDMA_CHANNEL_UART0TX)) { // // Start another DMA transfer to UART0 TX. // ROM_uDMAChannelTransferSet(UDMA_CHANNEL_UART0TX | UDMA_PRI_SELECT, UDMA_MODE_BASIC, g_ui8TxBuf, (void *)(UART0_BASE + UART_O_DR), sizeof(g_ui8TxBuf)); // // The uDMA TX channel must be re-enabled. // ROM_uDMAChannelEnable(UDMA_CHANNEL_UART0TX); } } //***************************************************************************** // // Initializes the UART0 peripheral and sets up the TX and RX uDMA channels. // The UART is configured for loopback mode so that any data sent on TX will be // received on RX. The uDMA channels are configured so that the TX channel // will copy data from a buffer to the UART TX output. And the uDMA RX channel // will receive any incoming data into a pair of buffers in ping-pong mode. // //***************************************************************************** void InitUART0Transfer(void) { unsigned int uIdx; // // Fill the TX buffer with a simple data pattern. // for(uIdx = 0; uIdx < UART_TXBUF_SIZE; uIdx++) { g_ui8TxBuf[uIdx] = uIdx; } // // Enable the peripherals used by this example. // ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); // // Set GPIO A0 and A1 as UART pins. // GPIOPinConfigure(GPIO_PA1_U0TX); ROM_GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_1); // // Enable the UART peripheral, and configure it to operate even if the CPU // is in sleep. // ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0); ROM_SysCtlPeripheralSleepEnable(SYSCTL_PERIPH_UART0); // // Configure the UART communication parameters. // ROM_UARTConfigSetExpClk(UART0_BASE, ROM_SysCtlClockGet(), 115200, UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE); // // Set both the TX and RX trigger thresholds to 4. This will be used by // the uDMA controller to signal when more data should be transferred. The // uDMA TX and RX channels will be configured so that it can transfer 4 // bytes in a burst when the UART is ready to transfer more data. // ROM_UARTFIFOLevelSet(UART0_BASE, UART_FIFO_TX4_8, UART_FIFO_RX4_8); // // Enable the UART for operation, and enable the uDMA interface for both TX // and RX channels. // ROM_UARTEnable(UART0_BASE); ROM_UARTDMAEnable(UART0_BASE, UART_DMA_TX); // // Enable the UART peripheral interrupts. Note that no UART interrupts // were enabled, but the uDMA controller will cause an interrupt on the // UART interrupt signal when a uDMA transfer is complete. // ROM_IntEnable(INT_UART0); // // Put the attributes in a known state for the uDMA UART0TX channel. These // should already be disabled by default. // ROM_uDMAChannelAttributeDisable(UDMA_CHANNEL_UART0TX, UDMA_ATTR_ALTSELECT | UDMA_ATTR_HIGH_PRIORITY | UDMA_ATTR_REQMASK); // // Set the USEBURST attribute for the uDMA UART TX channel. This will // force the controller to always use a burst when transferring data from // the TX buffer to the UART. This is somewhat more effecient bus usage // than the default which allows single or burst transfers. // ROM_uDMAChannelAttributeEnable(UDMA_CHANNEL_UART0TX, UDMA_ATTR_USEBURST); // // Configure the control parameters for the UART TX. The uDMA UART TX // channel is used to transfer a block of data from a buffer to the UART. // The data size is 8 bits. The source address increment is 8-bit bytes // since the data is coming from a buffer. The destination increment is // none since the data is to be written to the UART data register. The // arbitration size is set to 4, which matches the UART TX FIFO trigger // threshold. // ROM_uDMAChannelControlSet(UDMA_CHANNEL_UART0TX | UDMA_PRI_SELECT, UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_4); // // Set up the transfer parameters for the uDMA UART TX channel. This will // configure the transfer source and destination and the transfer size. // Basic mode is used because the peripheral is making the uDMA transfer // request. The source is the TX buffer and the destination is the UART // data register. //UART_O_DR is (UARTDR) register at offset 0x000 ROM_uDMAChannelTransferSet(UDMA_CHANNEL_UART0TX | UDMA_PRI_SELECT, UDMA_MODE_BASIC, g_ui8TxBuf, (void *)(UART0_BASE + UART_O_DR), sizeof(g_ui8TxBuf)); // // Now both the uDMA UART TX and RX channels are primed to start a // transfer. As soon as the channels are enabled, the peripheral will // issue a transfer request and the data transfers will begin. // uDMAChannelEnable(UDMA_CHANNEL_UART0TX); } //***************************************************************************** // // This example demonstrates how to use the uDMA controller to transfer data // between memory buffers and to and from a peripheral, in this case a UART. // The uDMA controller is configured to repeatedly transfer a block of data // from one memory buffer to another. It is also set up to repeatedly copy a // block of data from a buffer to the UART output. The UART data is looped // back so the same data is received, and the uDMA controlled is configured to // continuously receive the UART data using ping-pong buffers. // // The processor is put to sleep when it is not doing anything, and this allows // collection of CPU usage data to see how much CPU is being used while the // data transfers are ongoing. // //***************************************************************************** int main(void) { // // Enable lazy stacking for interrupt handlers. This allows floating-point // instructions to be used within interrupt handlers, but at the expense of // extra stack usage. // ROM_FPULazyStackingEnable(); // // Set the clocking to run from the PLL at 50 MHz. // ROM_SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ); // // Enable peripherals to operate when CPU is in sleep. // ROM_SysCtlPeripheralClockGating(true); // // Enable the GPIO port that is used for the on-board LED. // ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); // // Enable the GPIO pins for the LED (PF2). // ROM_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_2); // // Configure SysTick to occur 100 times per second, to use as a time // reference. Enable SysTick to generate interrupts. // ROM_SysTickPeriodSet(ROM_SysCtlClockGet() / SYSTICKS_PER_SECOND); // // Enable the uDMA controller at the system level. Enable it to continue // to run while the processor is in sleep. // ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA); ROM_SysCtlPeripheralSleepEnable(SYSCTL_PERIPH_UDMA); // // Enable the uDMA controller error interrupt. This interrupt will occur // if there is a bus error during a transfer. // ROM_IntEnable(INT_UDMAERR); // // Enable the uDMA controller. // ROM_uDMAEnable(); // // Point at the control table to use for channel control structures. // ROM_uDMAControlBaseSet(ui8ControlTable); // // Initialize the uDMA UART transfers. // InitUART0Transfer(); // Loop forever with the CPU not sleeping, so the debugger can connect. // while(1) { // // Turn on the GREEN LED. // GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, GPIO_PIN_2); // // Delay for a bit. // SysCtlDelay(SysCtlClockGet() / 20 / 3); // // Turn off the GREEN LED. // GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, 0); // // Delay for a bit. // SysCtlDelay(SysCtlClockGet() / 20 / 3); } }
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
uint32_t getRXamount(const uint32_t channel) { return sizeof(rxBuffer) - uDMAChannelSizeGet(UART0_RX_CHANNEL | UDMA_PRI_SELECT); }
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); // // Configure the UART communication parameters. // UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), 115200, UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE); // // Set both the TX and RX trigger thresholds to 4. This will be used by // the uDMA controller to signal when more data should be transferred. The // uDMA TX and RX channels will be configured so that it can transfer 4 // bytes in a burst when the UART is ready to transfer more data. // UARTFIFOLevelSet(UART0_BASE, UART_FIFO_TX4_8, UART_FIFO_RX4_8); // // Enable the UART for operation, and enable the uDMA interface for both TX // and RX channels. // UARTEnable(UART0_BASE); UARTDMAEnable(UART0_BASE, UART_DMA_TX | UART_DMA_RX); UARTIntEnable(UART0_BASE, UART_INT_RX | UART_INT_RT); }
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.
Charles Tsai 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.