cancel
Showing results for 
Search instead for 
Did you mean: 

UART with DMA, not working with TCIE (Transmission Complete Interrupt Enable)

Anesh
Associate III

Dear together, 

For the past dew days Im stuck kwith UART communication with DMA.

To start with, I am implementing TX now. Later I will implement RX. I have managed for TX to work without interrupt. As per my application, I need Interrupt to inform the micro controller when transmission is completed.

But when I enabled the DMA_CCR_TCIE bit in  DMA1_Channel2->CCR, micro controller hangs. For your information, I have provided my code here. 

This is the function to Initialize DMA

void Init_DMA(void)
{
	// Enable clock for DMA1 peripheral by setting the DMA1EN bit in RCC AHBENR register
	RCC->AHBENR |= RCC_AHBENR_DMA1EN;

	// Enable DMA1_Channel2_3 interrupt in NVIC
	NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);

	// Set priority for DMA1_Channel2_3 interrupt (Preemption = 0, Sub = 0)
	NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0);
}

 

This is the function to Initialize UART1

void Init_UART1(void)
{
    // 1. Enable clocks for GPIOB, USART1, and DMA1
    RCC->AHBENR  |= RCC_AHBENR_GPIOBEN;
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

    // 2. Configure PB5 as DE output (push-pull, high speed)
    GPIOB->MODER   &= ~(3 << (5 * 2));
    GPIOB->MODER   |=  (1 << (5 * 2));       // Output mode
    GPIOB->OTYPER  &= ~(1 << 5);             // Push-pull
    GPIOB->OSPEEDR |=  (3 << (5 * 2));       // High speed
    GPIOB->PUPDR   &= ~(3 << (5 * 2));       // No pull
    GPIOB->ODR     &= ~(1 << 5);             // Set DE LOW (receive mode)

    // 3. Configure PB6 as USART1_TX (AF0)
    GPIOB->MODER   &= ~(3 << (6 * 2));
    GPIOB->MODER   |=  (2 << (6 * 2));       // Alternate function
    GPIOB->OTYPER  &= ~(1 << 6);             // Push-pull
    GPIOB->OSPEEDR |=  (3 << (6 * 2));       // High speed
    GPIOB->PUPDR   &= ~(3 << (6 * 2));
    GPIOB->PUPDR   |=  (1 << (6 * 2));       // Pull-up
    GPIOB->AFR[0]  &= ~(0xF << (6 * 4));
    GPIOB->AFR[0]  |=  (0x0 << (6 * 4));     // AF0 = USART1

    // Reset USART1
    USART1->CR1 = 0;
    USART1->CR2 = 0;
    USART1->CR3 = 0;

    USART1->BRR = Set_USART1_BRR;             // Set baud rate

    switch (Set_Parity)
    {
        case 0: // 8N2 (No parity, 2 stop bits)
            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; // 2 stop bits
            break;
        case 1: // 8E1 (Even parity, 1 stop bit)
            USART1->CR1 |=  USART_CR1_PCE;
            USART1->CR1 &= ~USART_CR1_PS;
            USART1->CR1 |=  USART_CR1_M;
            USART1->CR2 &= ~USART_CR2_STOP;  // 1 stop bit
            break;
        case 2: // 8O1 (Odd parity, 1 stop bit)
            USART1->CR1 |=  USART_CR1_PCE;
            USART1->CR1 |=  USART_CR1_PS;
            USART1->CR1 |=  USART_CR1_M;
            USART1->CR2 &= ~USART_CR2_STOP;  // 1 stop bit
            break;
    }

    // 1 stop bit: CR2 STOP[1:0] = 00 (default, so we can skip)
    // Enable transmitter and receiver
    USART1->CR1 |= USART_CR1_TE | USART_CR1_RE;

    // Enable USART
    USART1->CR1 |= USART_CR1_UE;
}

 

This is the function used to transfet the data throgh DMA

void MB_Frame_Transmit(volatile uint8_t *data, uint16_t len)
{
    uart_tx_busy = 1;
    SET_DE;  // Enable RS485 Transmit mode

    // Disable DMA channel before reconfiguring
    DMA1_Channel2->CCR &= ~DMA_CCR_EN;

    // Clear DMA interrupt flags for Channel 2
    DMA1->IFCR = DMA_IFCR_CTCIF2 | DMA_IFCR_CTEIF2;

    // Configure DMA Channel 2 for USART1_TX
    DMA1_Channel2->CNDTR = len;
    DMA1_Channel2->CPAR  = (uint32_t)&USART1->TDR;
    DMA1_Channel2->CMAR  = (uint32_t)data;

    // Reconfigure DMA Channel 2
    DMA1_Channel2->CCR =
        DMA_CCR_MINC |         // Memory increment
        DMA_CCR_DIR  |         // Read from memory (TX)
        /*DMA_CCR_TCIE |         // Enable transfer complete interrupt*/
        DMA_CCR_PL_1;          // High priority (optional)

    // Enable USART1 TX DMA mode
    USART1->CR3 |= USART_CR3_DMAT;

    // Enable DMA Channel 2
    DMA1_Channel2->CCR |= DMA_CCR_EN;
}

 

This is the ISR

void DMA1_Channel2_3_IRQHandler(void)
{
    // Check Transfer Complete interrupt for Channel 2
    if (DMA1->ISR & DMA_ISR_TCIF2)
    {
        // Clear transfer complete flag
        DMA1->IFCR = DMA_IFCR_CTCIF2;

        // Disable DMA Channel 2 and USART DMA mode
        DMA1_Channel2->CCR &= ~DMA_CCR_EN;
        USART1->CR3 &= ~USART_CR3_DMAT;

        uart_tx_busy = 0;
        CLEAR_DE; // Set RS485 to Receive mode
    }

    // Optional: handle transfer error (TEIF2)
    if (DMA1->ISR & DMA_ISR_TEIF2)
    {
        DMA1->IFCR = DMA_IFCR_CTEIF2;
        // You can set an error flag or retry here

        Switch_Reset_LED_ON;
    }
}

 

I call the DMA1_Channel2_3_IRQHandler() function from main once in 600 mS. With this loop delay. I will not re initiate the DMA transfer before previous transfer is completed. delay_ms() is the blocking function implemented by timer 14.

Now the interesting part: If I disable the TCIE bit inise the MB_Frame_Transmit() function, DMA transfer happens perfectly once in 600ms. I probe this in my oscilloscope and confirm.

But if I enable TCIE, transmission happens only for first time when micro controller is restarted. Then it hangs.

 

Few more observations. This is my main function 

int main(void)
{
	Config_System_Clock();
	Init_GPIOs();
	Init_DMA();
	Init_UART1();
	Init_Tim14_1ms();

	 __enable_irq();

	for (uint8_t i = 0; i < 10; ++i) { TX_Array[i] = i; }

	//UART1_Transmit_DMA(TX_Array, 10);

	SET_DE;
	//Switch_Reset_LED_ON;

	while(1)
	{
		MB_Frame_Transmit(TX_Array, 10);
		Switch_Status_LED_ON; delay_ms(300);
		Switch_Status_LED_OFF; delay_ms(300);
	}
}

When the TCIE is enabled, the status LED is turned ON then not turned OFF. When means just for one time Switch_Statsu_LED_ON funtion is executed once then nothing further. Also the Reset LED placed insde Error handler is not turned, ON. which means means that part of ISR is also not executed. 

Please let me know, what is the issue in my code.

Thank you, Anesh S.

 

1 ACCEPTED SOLUTION

Accepted Solutions
waclawek.jan
Super User

> But why ISR is called immediately at time <= (length-2) of transmission?

That's when DMA finished its job.

The UART transmitter is initially empty and requests data from DMA through UART_SR.TXE, which indicates "UART TX holding register empty". DMA transfers that data into UART's holding register. UART transfers that byte rapidly from holding register into its shift register and starts to shift the bits out (i.e. transmit - note that UART baudrate is several orders of magnitude slower than the mcu system clock, so transmitting one single *bit* takes much much longer than transferring a whole byte by DMA). That means, that the UART holding register is empty again, UART_SR.TXE goes up, which triggers another byte to be transferred by DMA to the holding register, where it waits until the first byte gets completely shifted out from the shift register. It means, that DMA already transferred 2 bytes and UART barely started to transmit the first byte's startbit.

For longer arrays, this goes on, DMA is virtually always ahead of UART by 2 bytes, so it finishes its transfer 2 bytes before UART transmits the last byte's stopbit.

JW

View solution in original post

10 REPLIES 10

Probably not hanging, but stuck in an IRQ storm. If sources of interrupts are not cleared it will keep re-entering the IRQ Handler and no foreground code execution will occur.

Check pending interrupts.

Toggle GPIO at every IRQ Handler entry, scope pin as it will be happening very fast.

Perhaps have a count, and stop and inspect in the debugger. Determine WHERE it is stuck.

Check perhaps also HT (Half Transfer)

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

Hello, I modified the ISR code to turn ON an LED and toggle another port pin. But none of these lines are executed. Before that I enabled TCIE.
I tried to debug code by STM32CubeIDE. Debug is not proceeding above the function MB_Frame_Transmit(TX_Array, 10);. Anyhow I am not fluent in debugging process.
Please let me know what is the next step to debug.

 

void DMA1_Channel2_3_IRQHandler(void)
{
	 Switch_Reset_LED_ON;
	 Toggle_Status_LED;

	// Check Transfer Complete interrupt for Channel 2
    if (DMA1->ISR & DMA_ISR_TCIF2)
    {
        // Clear transfer complete flag
        DMA1->IFCR = DMA_IFCR_CTCIF2;

        // Disable DMA Channel 2 and USART DMA mode
        DMA1_Channel2->CCR &= ~DMA_CCR_EN;
        USART1->CR3 &= ~USART_CR3_DMAT;

        uart_tx_busy = 0;
        CLEAR_DE; // Set RS485 to Receive mode
    }

    // Optional: handle transfer error (TEIF2)
    if (DMA1->ISR & DMA_ISR_TEIF2)
    {
        DMA1->IFCR = DMA_IFCR_CTEIF2;
        // You can set an error flag or retry here
    }
}

 

Like I said, STOP the code in the DEBUGGER, and determine WHERE it is stuck.

Instrument things like Error_Handler() and HardFault_Handler(), or any place you have silent while(1) loops where it can stuck and die.

Add other instrumentation so you know what's going on without the debugger, these things are happening faster than you can react, so provide continuous output rather than dead-stop with break-points and single-stepping.

DMA will only reload / retrigger in CIRCULAR mode.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

I surfed internet and I could add few functions of error handlers. Please pardon me if they are useless.

__attribute__((naked)) void HardFault_Handler(void)
{
    __asm volatile
    (
        "movs r0, #4         \n"
        "mov r1, lr          \n"
        "tst r0, r1          \n"
        "beq _msp_used       \n"
        "mrs r0, psp         \n"
        "b HardFault_Handler_C \n"
    "_msp_used:              \n"
        "mrs r0, msp         \n"
        "b HardFault_Handler_C \n"
    );
}

void HardFault_Handler_C(uint32_t *stacked_regs)
{
    // Extract stacked CPU registers at fault time
    volatile uint32_t r0  = stacked_regs[0];
    volatile uint32_t r1  = stacked_regs[1];
    volatile uint32_t r2  = stacked_regs[2];
    volatile uint32_t r3  = stacked_regs[3];
    volatile uint32_t r12 = stacked_regs[4];
    volatile uint32_t lr  = stacked_regs[5];  // Link Register
    volatile uint32_t pc  = stacked_regs[6];  // Program Counter
    volatile uint32_t psr = stacked_regs[7];  // Program Status Register

    // Optional: Place breakpoint here to examine values in debugger
    // You can also send them over UART or log to flash

    (void)r0;
    (void)r1;
    (void)r2;
    (void)r3;
    (void)r12;
    (void)lr;
    (void)pc;
    (void)psr;

    // Stay here or call reset
    Error_Handler();
}

void Error_Handler(void)
{
    // Blink PB4 forever to indicate error
    while (1)
    {
    	Toggle_Reset_LED;
        for (volatile uint32_t i = 0; i < 100000; i++); // Delay loop
    }
}

I thought atleast Reset LED will be toglling after the microcontroller is trapped by some error. But that did not happen.

 

Then I tried debugging with step in function. I tried to step into loine by line of MB_Frame_Transmit(). But at end of that function, even debug action is not able to step anyother location. all the options except cloing debug session will be greyed. I made a screen recording, uploaded in Youtube and shared the link below:

https://youtu.be/Jk6v769iWK8

 

Further, I did not enable the DMA_CCR_TCIE bit and debugged. Now some issue happens inside my delay function. I uploaded this video too. The link is below:

https://youtu.be/lE05HerJKNc

 

Please pardon me if I dont understand even basics.

 

 

I think I have find the issue.

In startup_stm32f030c8tx.s I searched for ISR name "DMA1_Channel2_3_IRQHandler". That is not found. I used this wrong function name from ChatGPT :) 

I could find similar function name as DMA1_CH2_3_IRQHandler. I used that. now ISR is called. 

But now I have another issue. I make DE pin low inside ISR. I expect DE pin toi be logic low adter all the 10 elements are transmitted. But after 8 elements are transmitted the DE pin is made low. 

Then I placed a blocking while function inside ISR as below: 

void DMA1_CH2_3_IRQHandler(void)
{
	 //Switch_Reset_LED_ON;
	 //Toggle_Status_LED;

	// Check Transfer Complete interrupt for Channel 2
    if (DMA1->ISR & DMA_ISR_TCIF2)
    {
        // Clear transfer complete flag
        DMA1->IFCR = DMA_IFCR_CTCIF2;

        // Disable DMA Channel 2 and USART DMA mode
        DMA1_Channel2->CCR &= ~DMA_CCR_EN;
        USART1->CR3 &= ~USART_CR3_DMAT;

        while (!(USART1->ISR & USART_ISR_TC));

        uart_tx_busy = 0;
        CLEAR_DE; // Set RS485 to Receive mode
    }

    // Optional: handle transfer error (TEIF2)
    if (DMA1->ISR & DMA_ISR_TEIF2)
    {
        DMA1->IFCR = DMA_IFCR_CTEIF2;
        // You can set an error flag or retry here
    }
}

 

The DE is made logic 0 after all the frames are completed. I am not able to understand this behaviuour. PLease refer the plot wothout while loop.

When I debugged step by step, DE pin is handled correctly. why?

I dont want the blocking while function inside the interrupt. Infact I dont want blocking fuctions anywhere.

Please let me know whats going on wrong here.

Thank you, Anesh S.

Anesh
Associate III

For your reference I have provided my complete code. 

My micro controller is STM32F030C8T6 

#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));

void Config_System_Clock(void);
void Init_GPIOs(void);
void Init_DMA(void);
void Init_UART1(void);
void MB_Frame_Transmit(volatile uint8_t *pData, uint16_t size);
void Init_Tim14_1ms(void);
void delay_ms(uint32_t ms);
void Error_Handler(void);
void block_delay_500ms_for_loop(void);

volatile uint32_t ms_Ticks;
volatile uint8_t Tick_1ms;
volatile uint8_t TX_Array[10]={0}, RX_Array[10]={0};

volatile uint8_t uart_tx_busy = 0;

int main(void)
{
	Config_System_Clock();
	Init_GPIOs();
	Init_DMA();
	Init_UART1();
	//Init_Tim14_1ms();

	 __enable_irq();

	for (uint8_t i = 0; i < 10; ++i) { TX_Array[i] = i; }

	//UART1_Transmit_DMA(TX_Array, 10);


	//Switch_Reset_LED_ON;

	while(1)
	{
		MB_Frame_Transmit(TX_Array, 1); block_delay_500ms_for_loop();
		//Switch_Status_LED_ON; delay_ms(300);
		//Switch_Status_LED_OFF; delay_ms(300);
		//Toggle_Reset_LED;
	}
}

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_DMA(void)
{
	// Enable clock for DMA1 peripheral by setting the DMA1EN bit in RCC AHBENR register
	RCC->AHBENR |= RCC_AHBENR_DMA1EN;

    NVIC_ClearPendingIRQ(DMA1_Channel2_3_IRQn);
    NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0);
    NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
}

void Init_UART1(void)
{
    // 1. Enable clocks for GPIOB, USART1, and DMA1
    RCC->AHBENR  |= RCC_AHBENR_GPIOBEN;
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

    // 2. Configure PB5 as DE output (push-pull, high speed)
    GPIOB->MODER   &= ~(3 << (5 * 2));
    GPIOB->MODER   |=  (1 << (5 * 2));       // Output mode
    GPIOB->OTYPER  &= ~(1 << 5);             // Push-pull
    GPIOB->OSPEEDR |=  (3 << (5 * 2));       // High speed
    GPIOB->PUPDR   &= ~(3 << (5 * 2));       // No pull
    GPIOB->ODR     &= ~(1 << 5);             // Set DE LOW (receive mode)

    // 3. Configure PB6 as USART1_TX (AF0)
    GPIOB->MODER   &= ~(3 << (6 * 2));
    GPIOB->MODER   |=  (2 << (6 * 2));       // Alternate function
    GPIOB->OTYPER  &= ~(1 << 6);             // Push-pull
    GPIOB->OSPEEDR |=  (3 << (6 * 2));       // High speed
    GPIOB->PUPDR   &= ~(3 << (6 * 2));
    GPIOB->PUPDR   |=  (1 << (6 * 2));       // Pull-up
    GPIOB->AFR[0]  &= ~(0xF << (6 * 4));
    GPIOB->AFR[0]  |=  (0x0 << (6 * 4));     // AF0 = USART1

    // Reset USART1
    USART1->CR1 = 0;
    USART1->CR2 = 0;
    USART1->CR3 = 0;

    USART1->BRR = Set_USART1_BRR;             // Set baud rate

    switch (Set_Parity)
    {
        case 0: // 8N2 (No parity, 2 stop bits)
            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; // 2 stop bits
            break;
        case 1: // 8E1 (Even parity, 1 stop bit)
            USART1->CR1 |=  USART_CR1_PCE;
            USART1->CR1 &= ~USART_CR1_PS;
            USART1->CR1 |=  USART_CR1_M;
            USART1->CR2 &= ~USART_CR2_STOP;  // 1 stop bit
            break;
        case 2: // 8O1 (Odd parity, 1 stop bit)
            USART1->CR1 |=  USART_CR1_PCE;
            USART1->CR1 |=  USART_CR1_PS;
            USART1->CR1 |=  USART_CR1_M;
            USART1->CR2 &= ~USART_CR2_STOP;  // 1 stop bit
            break;
    }

    // 1 stop bit: CR2 STOP[1:0] = 00 (default, so we can skip)
    // Enable transmitter and receiver
    USART1->CR1 |= USART_CR1_TE | USART_CR1_RE;

    // Enable USART
    USART1->CR1 |= USART_CR1_UE;
}

void MB_Frame_Transmit(volatile uint8_t *data, uint16_t len)
{
    uart_tx_busy = 1;
    SET_DE;  // Enable RS485 Transmit mode

    // Disable DMA channel before reconfiguring
    DMA1_Channel2->CCR &= ~DMA_CCR_EN;

    // Clear DMA interrupt flags for Channel 2
    DMA1->IFCR = DMA_IFCR_CTCIF2 | DMA_IFCR_CTEIF2;

    // Configure DMA Channel 2 for USART1_TX
    DMA1_Channel2->CNDTR = len;
    DMA1_Channel2->CPAR  = (uint32_t)&USART1->TDR;
    DMA1_Channel2->CMAR  = (uint32_t)data;

    // Reconfigure DMA Channel 2
    DMA1_Channel2->CCR =
        DMA_CCR_MINC |         // Memory increment
        DMA_CCR_DIR  |         // Read from memory (TX)
        DMA_CCR_TCIE |         // Enable transfer complete interrupt
        DMA_CCR_PL_1;          // High priority (optional)

    // Enable USART1 TX DMA mode
    USART1->CR3 |= USART_CR3_DMAT;

    // Enable DMA Channel 2
    DMA1_Channel2->CCR |= DMA_CCR_EN;
}

void DMA1_CH2_3_IRQHandler(void)
{
	 //Switch_Reset_LED_ON;
	 //Toggle_Status_LED;

	// Check Transfer Complete interrupt for Channel 2
    if (DMA1->ISR & DMA_ISR_TCIF2)
    {
        // Clear transfer complete flag
        DMA1->IFCR = DMA_IFCR_CTCIF2;

        // Disable DMA Channel 2 and USART DMA mode
        DMA1_Channel2->CCR &= ~DMA_CCR_EN;
        USART1->CR3 &= ~USART_CR3_DMAT;

        //while (!(USART1->ISR & USART_ISR_TC));

        uart_tx_busy = 0;
        CLEAR_DE; // Set RS485 to Receive mode
    }

    // Optional: handle transfer error (TEIF2)
    if (DMA1->ISR & DMA_ISR_TEIF2)
    {
        DMA1->IFCR = DMA_IFCR_CTEIF2;
        // You can set an error flag or retry here
    }
}

__attribute__((naked)) void HardFault_Handler(void)
{
    __asm volatile
    (
        "movs r0, #4         \n"
        "mov r1, lr          \n"
        "tst r0, r1          \n"
        "beq _msp_used       \n"
        "mrs r0, psp         \n"
        "b HardFault_Handler_C \n"
    "_msp_used:              \n"
        "mrs r0, msp         \n"
        "b HardFault_Handler_C \n"
    );
}

void HardFault_Handler_C(uint32_t *stacked_regs)
{
    // Extract stacked CPU registers at fault time
    volatile uint32_t r0  = stacked_regs[0];
    volatile uint32_t r1  = stacked_regs[1];
    volatile uint32_t r2  = stacked_regs[2];
    volatile uint32_t r3  = stacked_regs[3];
    volatile uint32_t r12 = stacked_regs[4];
    volatile uint32_t lr  = stacked_regs[5];  // Link Register
    volatile uint32_t pc  = stacked_regs[6];  // Program Counter
    volatile uint32_t psr = stacked_regs[7];  // Program Status Register

    // Optional: Place breakpoint here to examine values in debugger
    // You can also send them over UART or log to flash

    (void)r0;
    (void)r1;
    (void)r2;
    (void)r3;
    (void)r12;
    (void)lr;
    (void)pc;
    (void)psr;

    // Stay here or call reset
    Error_Handler();
}

void Error_Handler(void)
{
    // Blink PB4 forever to indicate error
    while (1)
    {
    	Toggle_Reset_LED;
        for (volatile uint32_t i = 0; i < 100000; i++); // Delay loop
    }
}

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)
    {
    //Switch_Status_LED_ON;
    TIM14->SR &= ~TIM_SR_UIF;
        ms_Ticks++;
        Tick_1ms=1;
        //Switch_Status_LED_OFF;
    }
}

void delay_ms(uint32_t ms)
{
    uint32_t start = ms_Ticks;
    while ((ms_Ticks - 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();
	}
}

When I reduced of array to 5, CLEAR_DE is executed at end of third element.

When I reduced the length to 2, the CLEAR_DE is switched off at 4 micro seconds (almost immediate). This time may be time to initiate the DMA and immediate execution of ISR. Same behaviour happens when I reduced the length to 1. So This behavior i solid.

But why ISR is called immediately at time <= (length-2) of transmission?

Please let me know what is going on wrong.

 

waclawek.jan
Super User

> But why ISR is called immediately at time <= (length-2) of transmission?

That's when DMA finished its job.

The UART transmitter is initially empty and requests data from DMA through UART_SR.TXE, which indicates "UART TX holding register empty". DMA transfers that data into UART's holding register. UART transfers that byte rapidly from holding register into its shift register and starts to shift the bits out (i.e. transmit - note that UART baudrate is several orders of magnitude slower than the mcu system clock, so transmitting one single *bit* takes much much longer than transferring a whole byte by DMA). That means, that the UART holding register is empty again, UART_SR.TXE goes up, which triggers another byte to be transferred by DMA to the holding register, where it waits until the first byte gets completely shifted out from the shift register. It means, that DMA already transferred 2 bytes and UART barely started to transmit the first byte's startbit.

For longer arrays, this goes on, DMA is virtually always ahead of UART by 2 bytes, so it finishes its transfer 2 bytes before UART transmits the last byte's stopbit.

JW

Thank you for detailed information. Now I have implemented using USART_ISR_TC of UART1. I probed all the functions by place port pin on/off commands. Both the ISRs are working fine. I think I can handle even without ISR by DMA.

Anyhow, I left it to be in code, Inside this ISR I enabled USART_ISR_TC. I hope nothing will be hurt.

I post my complete code here of TX with DMA. I request you to have a look for any potential issues. 

#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));

void Config_System_Clock(void);
void Init_GPIOs(void);
void Init_DMA(void);
void Init_UART1(void);
void MB_Frame_Transmit(volatile uint8_t *pData, uint16_t size);
void Init_Tim14_1ms(void);
void delay_ms(uint32_t ms);
void Error_Handler(void);
void block_delay_500ms_for_loop(void);

volatile uint32_t Ticks_1ms, Ticks_100ms;
volatile uint8_t Flag_1ms, Flag_100ms;

volatile uint8_t TX_Array[10]={0}, RX_Array[10]={0};

volatile uint8_t uart_tx_busy = 0;

int main(void)
{
	Config_System_Clock();
	Init_GPIOs();
	Init_DMA();
	Init_UART1();
	Init_Tim14_1ms();

	 __enable_irq();

	for (uint8_t i = 0; i < 10; ++i) { TX_Array[i] = i; }

	//UART1_Transmit_DMA(TX_Array, 10);


	//Switch_Reset_LED_ON;

	while(1)
	{
		if(Flag_100ms)
		{
			MB_Frame_Transmit(TX_Array, 10);
			Flag_100ms=0;
		}
		//block_delay_500ms_for_loop();
		//Switch_Status_LED_ON; delay_ms(300);
		//Switch_Status_LED_OFF; delay_ms(300);
		//Toggle_Reset_LED;

		if(Flag_1ms)
		{
			Toggle_Status_LED;
			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_DMA(void)
{
    // Enable clock for DMA1
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;

    // Optional: Preconfigure TX DMA Channel2 default settings (actual values set during transmit)
    DMA1_Channel2->CCR = 0; // Disable first
    DMA1_Channel2->CNDTR = 0;
    DMA1_Channel2->CPAR  = (uint32_t)&USART1->TDR;
    DMA1_Channel2->CMAR  = 0;
    DMA1_Channel2->CCR =
        DMA_CCR_MINC |         // Memory increment mode
        DMA_CCR_DIR  |         // Read from memory (TX)
        DMA_CCR_TCIE |         // Transfer complete interrupt
        DMA_CCR_PL_1;          // High priority

    // Clear any pending DMA interrupt flags
    DMA1->IFCR = DMA_IFCR_CTCIF2 | DMA_IFCR_CTEIF2;

    // Configure NVIC for DMA Channel 2/3 interrupt
    NVIC_ClearPendingIRQ(DMA1_Channel2_3_IRQn);
    NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0);
    NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
}

void Init_UART1(void)
{
    // 1. Enable clocks
    RCC->AHBENR  |= RCC_AHBENR_GPIOBEN;
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

    // 2. Configure PB5 as DE (output, push-pull, high speed)
    GPIOB->MODER   &= ~(3 << (5 * 2));
    GPIOB->MODER   |=  (1 << (5 * 2));
    GPIOB->OTYPER  &= ~(1 << 5);
    GPIOB->OSPEEDR |=  (3 << (5 * 2));
    GPIOB->PUPDR   &= ~(3 << (5 * 2));
    GPIOB->ODR     &= ~(1 << 5); // Set DE low initially

    // 3. Configure PB6 as USART1_TX (AF0)
    GPIOB->MODER   &= ~(3 << (6 * 2));
    GPIOB->MODER   |=  (2 << (6 * 2));  // Alternate function
    GPIOB->OTYPER  &= ~(1 << 6);
    GPIOB->OSPEEDR |=  (3 << (6 * 2));
    GPIOB->PUPDR   &= ~(3 << (6 * 2));
    GPIOB->PUPDR   |=  (1 << (6 * 2));  // Pull-up
    GPIOB->AFR[0]  &= ~(0xF << (6 * 4));
    GPIOB->AFR[0]  |=  (0x0 << (6 * 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;
            USART1->CR1 &= ~USART_CR1_PS;
            USART1->CR1 |=  USART_CR1_M;
            USART1->CR2 &= ~USART_CR2_STOP;
            break;
        case 2: // 8O1
            USART1->CR1 |=  USART_CR1_PCE;
            USART1->CR1 |=  USART_CR1_PS;
            USART1->CR1 |=  USART_CR1_M;
            USART1->CR2 &= ~USART_CR2_STOP;
            break;
    }

    // 8. Enable TX and RX
    USART1->CR1 |= USART_CR1_TE | USART_CR1_RE;

    // 9. Enable USART1 interrupt (for TCIE used in TX complete)
    NVIC_ClearPendingIRQ(USART1_IRQn);
    NVIC_SetPriority(USART1_IRQn, 1);
    NVIC_EnableIRQ(USART1_IRQn);

    // 10. Enable USART1
    USART1->CR1 |= USART_CR1_UE;
}


void MB_Frame_Transmit(volatile uint8_t *data, uint16_t len)
{
	//Switch_Status_LED_ON;

	uart_tx_busy = 1;
    SET_DE;  // Enable RS485 transmit mode

    // Disable DMA channel before reconfiguration
    DMA1_Channel2->CCR &= ~DMA_CCR_EN;

    // Clear DMA Channel 2 interrupt flags
    DMA1->IFCR = DMA_IFCR_CTCIF2 | DMA_IFCR_CTEIF2;

    // Configure DMA for USART1_TX
    DMA1_Channel2->CPAR  = (uint32_t)&USART1->TDR;
    DMA1_Channel2->CMAR  = (uint32_t)data;
    DMA1_Channel2->CNDTR = len;

    DMA1_Channel2->CCR =
        DMA_CCR_MINC  |    // Memory increment mode
        DMA_CCR_DIR   |    // Read from memory
        DMA_CCR_TCIE  |    // Enable transfer complete interrupt
        DMA_CCR_PL_1;      // High priority (optional)

    // Enable USART1 DMA TX
    USART1->CR3 |= USART_CR3_DMAT;

    // Enable DMA channel
    DMA1_Channel2->CCR |= DMA_CCR_EN;

    //Switch_Status_LED_OFF;
}


void DMA1_CH2_3_IRQHandler(void)
{
    uint32_t isr = DMA1->ISR;

    if (isr & DMA_ISR_TCIF2)
    {
    	//Switch_Status_LED_ON;

    	// Clear transfer complete flag and disable DMA
        DMA1->IFCR = DMA_IFCR_CTCIF2;
        DMA1_Channel2->CCR &= ~DMA_CCR_EN;
        USART1->CR3 &= ~USART_CR3_DMAT;

        // Enable USART TC interrupt to detect final byte transmission on the line
        USART1->CR1 |= USART_CR1_TCIE;

        //Switch_Status_LED_OFF;
    }

    if (isr & DMA_ISR_TEIF2)
    {
        DMA1->IFCR = DMA_IFCR_CTEIF2;
        // Optional: set error flag or retry logic
    }
}


void USART1_IRQHandler(void)
{
    uint32_t isr = USART1->ISR;
    volatile uint32_t dummy;

    // Handle Transmission Complete
    if (isr & USART_ISR_TC)
    {
    	//Switch_Status_LED_ON;

    	USART1->ICR = USART_ICR_TCCF;        // Clear Transmission Complete
        USART1->CR1 &= ~USART_CR1_TCIE;      // Disable TC interrupt

        CLEAR_DE; // Return RS485 to receive mode
        uart_tx_busy = 0;

        Toggle_Reset_LED;

        //Switch_Status_LED_OFF;
    }

    // Just clear **all** USART error flags if any error is present
    if (isr & (USART_ISR_PE | USART_ISR_FE | USART_ISR_NE | USART_ISR_ORE))
    {
        USART1->ICR = USART_ICR_PECF | USART_ICR_FECF | USART_ICR_NCF | USART_ICR_ORECF; // Clear all errors
        dummy = USART1->RDR;  // Read RDR to clear error conditions
        (void)dummy;
    }
}


__attribute__((naked)) void HardFault_Handler(void)
{
    __asm volatile
    (
        "movs r0, #4         \n"
        "mov r1, lr          \n"
        "tst r0, r1          \n"
        "beq _msp_used       \n"
        "mrs r0, psp         \n"
        "b HardFault_Handler_C \n"
    "_msp_used:              \n"
        "mrs r0, msp         \n"
        "b HardFault_Handler_C \n"
    );
}

void HardFault_Handler_C(uint32_t *stacked_regs)
{
    // Extract stacked CPU registers at fault time
    volatile uint32_t r0  = stacked_regs[0];
    volatile uint32_t r1  = stacked_regs[1];
    volatile uint32_t r2  = stacked_regs[2];
    volatile uint32_t r3  = stacked_regs[3];
    volatile uint32_t r12 = stacked_regs[4];
    volatile uint32_t lr  = stacked_regs[5];  // Link Register
    volatile uint32_t pc  = stacked_regs[6];  // Program Counter
    volatile uint32_t psr = stacked_regs[7];  // Program Status Register

    // Optional: Place breakpoint here to examine values in debugger
    // You can also send them over UART or log to flash

    (void)r0;
    (void)r1;
    (void)r2;
    (void)r3;
    (void)r12;
    (void)lr;
    (void)pc;
    (void)psr;

    // Stay here or call reset
    Error_Handler();
}

void Error_Handler(void)
{
    // Blink PB4 forever to indicate error
    while (1)
    {
    	Toggle_Reset_LED;
        for (volatile uint32_t i = 0; i < 100000; i++); // Delay loop
    }
}

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

Now I started implementing RX via DMA. 

Thank you.

While Tx is fully under the mcu's control and so DMA is relatively straighforward; Rx through DMA is tricky, as it operates on data from outside, which may be corrupted. Some folks combine Rx DMA with timeouts (often in the form of the UART's IDLE feature), but I personally simply don't use DMA and handle it in interrupt. UART is relatively slow.

JW