cancel
Showing results for 
Search instead for 
Did you mean: 

UART RX DMA Implementation Issue

Anesh
Associate III

Hello,

I am using STM32F030C8T6 to implement Modbus RTU protocol which uses UART and RS485 as physical layer.
I have already implemented the complete protocol with UART's RXNE and TCIE interrupts.
Now I am trying to implement through DMA by which I will free micro-controller core from lot of interrupts.
With great support from ST forum, I already implemented Tx using DMA.

Now I m trying to implement the RX logics using UART through DMA.
As usual I am stuck somewhere and I request your support. Please help me to fix this issue too.

As per the Modbus protocol, my hardware as a slave will read sequence bytes, process and then respond.
Length of sequence will not be known during reception of the bytes.
Slave must decide the length of frame when there a silence in the Rx for more than 3.5 times of character length.
For this logic, I have used IDLE Line interrupt to detect initial silence then, inside the IDLE ISR I start Timer 3 in OPM for 3.6ms (time depends Baud rate and number of 11 bits streams).
So if there is silence for more than 3.6ms, slave will decide that end of Rx is received, and further processing will be started.

Here I have provided you the complete code related to RX to analyze further.

#include <stdint.h> #include <stddef.h> #include "stm32f030x8.h" #define Set_USART1_BRR (40000000/9600) // 9600 Baudrate #define Set_Parity 0x01 // EVen #define DMA1_Channel2_ISR_TCIF DMA_ISR_TCIF2 // Transfer complete flag for channel 2 #define DMA1_Channel2_IFCR_CTCIF DMA_IFCR_CTCIF2 // Clear transfer complete flag channel 2 #define Switch_Reset_LED_ON (GPIOB->BSRR = GPIO_BSRR_BS_10) // PB10 (Set pin) #define Switch_Status_LED_ON (GPIOB->BSRR = GPIO_BSRR_BS_4) // PB4 (Set pin) #define Switch_RXD_LED_ON (GPIOB->BSRR = GPIO_BSRR_BS_3) // PB3 (Set pin) #define Switch_TXD_LED_ON (GPIOA->BSRR = GPIO_BSRR_BS_15) // PA15 (Set pin) #define Switch_Reset_LED_OFF (GPIOB->BSRR = GPIO_BSRR_BR_10) // PB10 (Set pin) #define Switch_Status_LED_OFF (GPIOB->BSRR = GPIO_BSRR_BR_4) // PB4 (Set pin) #define Switch_RXD_LED_OFF (GPIOB->BSRR = GPIO_BSRR_BR_3) // PB3 (Set pin) #define Switch_TXD_LED_OFF (GPIOA->BSRR = GPIO_BSRR_BR_15) // PA15 (Set pin) #define Toggle_Reset_LED (GPIOB->ODR ^= GPIO_ODR_10) // PB10 (Set pin) #define Toggle_Status_LED (GPIOB->ODR ^= GPIO_ODR_4) #define CLEAR_DE (GPIOB->ODR &= ~(1 << 5)); #define SET_DE (GPIOB->ODR |= (1 << 5)); #define MB_Buffer_Size 256 volatile uint8_t CMD_1[] = {0x03, 0x0F, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x7F, 0x4C}; volatile uint8_t CMD_2[] = {0x03, 0x0F, 0x00, 0x00, 0x00, 0x08, 0x01, 0x01, 0xBE, 0x8C}; volatile uint8_t CMD_3[] = {0x03, 0x0F, 0x00, 0x00, 0x00, 0x08, 0x01, 0x02, 0xFE, 0x8D}; volatile uint8_t CMD_4[] = {0x03, 0x0F, 0x00, 0x00, 0x00, 0x08, 0x01, 0x04, 0x7E, 0x8F}; volatile uint8_t CMD_5[] = {0x03, 0x0F, 0x00, 0x00, 0x00, 0x08, 0x01, 0x08, 0x7E, 0x8A}; volatile uint8_t CMD_6[] = {0x03, 0x0F, 0x00, 0x00, 0x00, 0x08, 0x01, 0x10, 0x7E, 0x80}; volatile uint8_t CMD_7[] = {0x03, 0x0F, 0x00, 0x00, 0x00, 0x08, 0x01, 0x20, 0x7E, 0x94}; volatile uint8_t CMD_8[] = {0x03, 0x0F, 0x00, 0x00, 0x00, 0x08, 0x01, 0x40, 0x7E, 0xBC}; volatile uint8_t CMD_9[] = {0x03, 0x0F, 0x00, 0x00, 0x00, 0x08, 0x01, 0x80, 0x7E, 0xEC}; void Config_System_Clock(void); void Init_GPIOs(void); void Init_DMA(void); void Init_UART1(void); void Init_Tim14_1ms(void); void delay_ms(uint32_t ms); void Error_Handler(void); void block_delay_500ms_for_loop(void); void Init_TIM3_RX_EOF_Detection(void); void Init_DMA_USART1_RX(void); volatile uint32_t Ticks_1ms, Ticks_100ms; volatile uint8_t Flag_1ms, Flag_100ms; volatile uint8_t TX_Array[MB_Buffer_Size]={0}, RX_Array[MB_Buffer_Size]={0}; volatile uint16_t RX_Array_Size=0; volatile uint8_t uart_tx_busy = 0; int main(void) { Config_System_Clock(); Init_GPIOs(); Init_UART1(); Init_DMA_USART1_RX(); Init_TIM3_RX_EOF_Detection(); Init_Tim14_1ms(); __enable_irq(); for (uint8_t i = 0; i < 10; ++i) { TX_Array[i] = i; } while(1) { if(Flag_100ms) { Toggle_Reset_LED; Flag_100ms=0; } if(Flag_1ms) { Flag_1ms=0; } } } void Init_GPIOs(void) { RCC->AHBENR |= RCC_AHBENR_GPIOAEN; // Enable GPIOA clock RCC->AHBENR |= RCC_AHBENR_GPIOBEN; // Enable GPIOB clock RCC->AHBENR |= RCC_AHBENR_GPIOCEN; // Enable GPIOC clock /* Settings for ONB_Reset_LED: PB10 */ GPIOB->MODER &= ~GPIO_MODER_MODER10; // Clear mode for PB10 GPIOB->MODER |= GPIO_MODER_MODER10_0; // Set as Output (01) GPIOB->OTYPER &= ~GPIO_OTYPER_OT_10; // Push-pull (0) GPIOB->OSPEEDR &= ~GPIO_OSPEEDR_OSPEEDR10; // Clear speed for PB10 GPIOB->OSPEEDR |= GPIO_OSPEEDR_OSPEEDR10_0; // Medium speed (01) GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR10; // No pull-up, no pull-down (00) /* Settings for ONB_Status_LED: PB4 */ GPIOB->MODER &= ~GPIO_MODER_MODER4; // Clear mode for PB4 GPIOB->MODER |= GPIO_MODER_MODER4_0; // Set as Output (01) GPIOB->OTYPER &= ~GPIO_OTYPER_OT_4; // Push-pull (0) GPIOB->OSPEEDR &= ~GPIO_OSPEEDR_OSPEEDR4; // Clear speed for PB4 GPIOB->OSPEEDR |= GPIO_OSPEEDR_OSPEEDR4_0; // Medium speed (01) GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR4; // No pull-up, no pull-down (00) /* Settings for ONB_RXD_LED: PB3 */ GPIOB->MODER &= ~GPIO_MODER_MODER3; // Clear mode for PB3 GPIOB->MODER |= GPIO_MODER_MODER3_0; // Set as Output (01) GPIOB->OTYPER &= ~GPIO_OTYPER_OT_3; // Push-pull (0) GPIOB->OSPEEDR &= ~GPIO_OSPEEDR_OSPEEDR3; // Clear speed for PB3 GPIOB->OSPEEDR |= GPIO_OSPEEDR_OSPEEDR3_0; // Medium speed (01) GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR3; // No pull-up, no pull-down (00) /* Settings for ONB_TXD_LED: PA15 */ GPIOA->MODER &= ~GPIO_MODER_MODER15; // Clear mode for PA15 GPIOA->MODER |= GPIO_MODER_MODER15_0; // Set as Output (01) GPIOA->OTYPER &= ~GPIO_OTYPER_OT_15; // Push-pull (0) GPIOA->OSPEEDR &= ~GPIO_OSPEEDR_OSPEEDR15; // Clear speed for PA15 GPIOA->OSPEEDR |= GPIO_OSPEEDR_OSPEEDR15_0; // Medium speed (01) GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR15; // No pull-up, no pull-down (00) } void Init_UART1(void) { // 1. Enable clocks RCC->AHBENR |= RCC_AHBENR_GPIOBEN; RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // 2. Configure PB5 as DE (optional: set as output if needed) GPIOB->MODER &= ~(3 << (5 * 2)); GPIOB->MODER |= (1 << (5 * 2)); // Output GPIOB->OTYPER &= ~(1 << 5); // Push-pull GPIOB->OSPEEDR |= (3 << (5 * 2)); // High speed GPIOB->PUPDR &= ~(3 << (5 * 2)); GPIOB->ODR &= ~(1 << 5); // Set DE low initially // 3. Configure PB7 as USART1_RX (AF0) GPIOB->MODER &= ~(3 << (7 * 2)); GPIOB->MODER |= (2 << (7 * 2)); // Alternate function GPIOB->OTYPER &= ~(1 << 7); GPIOB->OSPEEDR |= (3 << (7 * 2)); // High speed GPIOB->PUPDR &= ~(3 << (7 * 2)); GPIOB->PUPDR |= (1 << (7 * 2)); // Pull-up GPIOB->AFR[0] &= ~(0xF << (7 * 4)); GPIOB->AFR[0] |= (0x0 << (7 * 4)); // AF0 // 4. Reset USART1 registers safely (only if USART is disabled) if (!(USART1->CR1 & USART_CR1_UE)) { USART1->CR1 = 0; USART1->CR2 = 0; USART1->CR3 = 0; } // 5. Clear status flags volatile uint32_t tmp; tmp = USART1->ISR; tmp = USART1->RDR; (void)tmp; // 6. Set baud rate USART1->BRR = Set_USART1_BRR; // 7. Configure parity and stop bits switch (Set_Parity) { case 0: // 8N2 USART1->CR1 &= ~USART_CR1_PCE; USART1->CR1 &= ~USART_CR1_M; USART1->CR2 &= ~USART_CR2_STOP; USART1->CR2 |= USART_CR2_STOP_1 | USART_CR2_STOP_0; break; case 1: // 8E1 USART1->CR1 |= USART_CR1_PCE; // Parity enable USART1->CR1 &= ~USART_CR1_PS; // Even parity USART1->CR1 |= USART_CR1_M; // 9-bit for parity USART1->CR2 &= ~USART_CR2_STOP; // 1 stop bit break; case 2: // 8O1 USART1->CR1 |= USART_CR1_PCE; // Parity enable USART1->CR1 |= USART_CR1_PS; // Odd parity USART1->CR1 |= USART_CR1_M; // 9-bit for parity USART1->CR2 &= ~USART_CR2_STOP; // 1 stop bit break; } // 8. Clear IDLE flag before enabling interrupt tmp = USART1->ISR; tmp = USART1->RDR; (void)tmp; // 9. Enable RX and IDLE interrupt (no TX) USART1->CR1 |= USART_CR1_RE | USART_CR1_IDLEIE; //USART1->CR1 |= USART_CR1_IDLEIE; // 10. Enable USART1 interrupt in NVIC NVIC_ClearPendingIRQ(USART1_IRQn); NVIC_SetPriority(USART1_IRQn, 1); NVIC_EnableIRQ(USART1_IRQn); // 11. Enable USART1 USART1->CR1 |= USART_CR1_UE; } void Init_TIM3_RX_EOF_Detection(void) { RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; TIM3->CR1 = 0; //TIM3->PSC = 39; //TIM3->ARR = 3640; //TIM3->CNT = 0; TIM3->SR = 0; TIM3->DIER = 0; // No interrupt yet TIM3->EGR = TIM_EGR_UG; // Update event to preload TIM3->SR &= ~TIM_SR_UIF; // Clear update flag TIM3->DIER |= TIM_DIER_UIE; // Now enable interrupt TIM3->CR1 |= TIM_CR1_OPM; // One Pulse Mode NVIC_EnableIRQ(TIM3_IRQn); } void Init_DMA_USART1_RX(void) { // 1. Enable DMA1 clock RCC->AHBENR |= RCC_AHBENR_DMA1EN; // 2. Disable DMA channel before configuring DMA1_Channel5->CCR &= ~DMA_CCR_EN; // 3. Clear any pending flags for DMA1 Channel 5 (optional, platform dependent) // For STM32F0, check DMA1 ISR and IFCR if needed. // 4. Set peripheral address (USART1 RDR register) DMA1_Channel5->CPAR = (uint32_t)&USART1->RDR; // 5. Set memory address (destination buffer) DMA1_Channel5->CMAR = (uint32_t)RX_Array; // 6. Set number of bytes to transfer DMA1_Channel5->CNDTR = MB_Buffer_Size; // 7. Configure DMA channel: // - Memory increment enabled // - Circular mode enabled (continuous reception) // - Transfer complete interrupt enabled (optional) // - Priority high (bit1 = 1, bit0 = 0) // - Direction = peripheral to memory (default 0) DMA1_Channel5->CCR = DMA_CCR_MINC // Memory increment mode | DMA_CCR_CIRC // Circular mode (optional, remove if single-shot) //| DMA_CCR_TCIE // Transfer complete interrupt enable (optional) | DMA_CCR_PL_1 // Priority level high (bit1=1, bit0=0) // | DMA_CCR_DIR // Not set, so direction = peripheral to memory (correct for RX) ; // 8. Enable DMA channel DMA1_Channel5->CCR |= DMA_CCR_EN; // 9. Enable DMA interrupt in NVIC if TCIE is enabled // Example: // NVIC_EnableIRQ(DMA1_Channel4_5_6_7_IRQn); } void USART1_IRQHandler(void) { uint32_t isr = USART1->ISR; volatile uint32_t dummy; // --- IDLE line detected (end of Modbus RTU frame) --- if (isr & USART_ISR_IDLE) { Switch_Status_LED_ON; USART1->ICR = USART_ICR_IDLECF; dummy = USART1->ISR; // Clear IDLE flag by reading ISR dummy = USART1->RDR; // Clear with RDR read (void)dummy; // Restart TIM3 safely (corrected) TIM3->CR1 &= ~TIM_CR1_CEN; // Stop timer TIM3->DIER &= ~TIM_DIER_UIE; // Disable interrupt during config TIM3->SR = 0; // Clear all interrupt flags (especially UIF) TIM3->PSC = 39; // Prescaler TIM3->ARR = 3640; // Auto-reload value for 3.64 ms TIM3->CNT = 0; // Reset counter TIM3->EGR = TIM_EGR_UG; // Force update to load new ARR/PSC TIM3->SR = 0; // :repeat_button: Clear UIF again after EGR (important!) TIM3->DIER |= TIM_DIER_UIE; // Enable update interrupt TIM3->CR1 |= TIM_CR1_CEN; // Start timer Switch_Status_LED_OFF; //Toggle_Status_LED; } } void TIM3_IRQHandler(void) { // Check if update interrupt flag is set if (TIM3->SR & TIM_SR_UIF) { Switch_RXD_LED_ON; TIM3->SR &= ~TIM_SR_UIF; // Clear update interrupt flag TIM3->CR1 &= ~TIM_CR1_CEN; // Calculate number of bytes received by DMA RX_Array_Size = MB_Buffer_Size - DMA1_Channel5->CNDTR; // Stop DMA reception (disable channel) DMA1_Channel5->CCR &= ~DMA_CCR_EN; // Optional: Reset DMA transfer count and re-enable channel for next reception DMA1_Channel5->CNDTR = MB_Buffer_Size; DMA1_Channel5->CCR |= DMA_CCR_EN; // Place your end-of-frame processing code here (e.g., parsing Modbus frame) //if(RX_Array[0]==0x00) {Switch_RXD_LED_ON;} else {Switch_RXD_LED_OFF;} //if(RX_Array_Size==8) {Switch_TXD_LED_ON;} else {Switch_TXD_LED_OFF;} if(RX_Array[0]==0x03) {Switch_TXD_LED_ON;} else {Switch_TXD_LED_OFF;} Switch_RXD_LED_OFF; //Toggle_Status_LED; // Example action: toggle LED on timeout } } void Init_Tim14_1ms(void) { RCC->APB1ENR |= RCC_APB1ENR_TIM14EN; TIM14->PSC = 39; TIM14->ARR = 999; TIM14->CNT = 0; TIM14->SR &= ~TIM_SR_UIF; // Clear any pending flag TIM14->DIER |= TIM_DIER_UIE; // Update interrupt enable TIM14->CR1 |= TIM_CR1_CEN; // Counter enable NVIC_EnableIRQ(TIM14_IRQn); // Enable IRQ in NVIC } void TIM14_IRQHandler(void) { if (TIM14->SR & TIM_SR_UIF) { TIM14->SR &= ~TIM_SR_UIF; // Clear update interrupt flag Ticks_1ms++; // Optional global ms counter Flag_1ms = 1; // Set 1ms flag // Increment 100ms counter and set flag when 100ms elapsed if (++Ticks_100ms >= 100) { Flag_100ms = 1; Ticks_100ms = 0; // Reset for next 100 ms period } } } void delay_ms(uint32_t ms) { uint32_t start = Ticks_1ms; while ((Ticks_1ms - start) < ms); } void Config_System_Clock(void) { // 1. Enable HSE (High Speed External oscillator) RCC->CR |= RCC_CR_HSEON; while ((RCC->CR & RCC_CR_HSERDY) == 0); // Wait for HSE ready // 2. Configure PLL // PLL source = HSE, PLL multiplier = 5 RCC->CFGR &= ~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLMUL); // Clear PLL config RCC->CFGR |= RCC_CFGR_PLLSRC_HSE_PREDIV | RCC_CFGR_PLLMUL5; // HSE / 1 * 5 // 3. Enable PLL RCC->CR |= RCC_CR_PLLON; while ((RCC->CR & RCC_CR_PLLRDY) == 0); // Wait for PLL ready // 4. Select PLL as system clock RCC->CFGR &= ~RCC_CFGR_SW; RCC->CFGR |= RCC_CFGR_SW_PLL; while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // Wait until PLL used as system clock } void block_delay_500ms_for_loop(void) { for (uint32_t i = 0; i < 5000001; i++) { __NOP(); } }
View more

I use a Modbus Master simulation software named "QModMaster" a generate a stream of UART data.
For testing, I generate a data stream of 0x03 0x01 0x00 0x00 0x00 0x08 0x3C 0x2E.
We can confirm the data stream in oscilloscope plot too.

Inside the function void "USART1_IRQHandler()", in every IDLE interrupt, I restart the Timer 3 in OPM with 3.6mS.
To check its functionality by oscilloscope, I placed code to pulse a port pin (Switch_Status_LED_ON and Switch_Status_LED_OFF).
The oscilloscope plot confirm that, this function is properly executed when IDLE is detected.

Then after 3.6mS, Timer 3 ISR is also executed.
I confirmed this by pulsing another port pin (Switch_RXD_LED_ON and Switch_RXD_LED_OFF).
The oscilloscope plot confirm that, this time out function is also properly executed at 3.64mS from detection of IDLE.

Further to test the functionality of DMA reception, I checked RX_Array_Size, first element of Received array by simple if else function.
But data received by DMA is not as per the expectation.

I was told that, because of lesser control of micro controller over Rx and noise in Rx lines, this task may not be straight forward.
But with the implementation of Time out function to Reset DMA transfer and count, reception must be reliable. The very first data received during power up may be unknown.
From the next cycles, things must work fine.

Please let me know, what is going on wrong.

Thank you.

0 REPLIES 0