2025-05-23 1:07 PM
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();
}
}
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.