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();
	}
}

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