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.

 

2 REPLIES 2

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
    }
}