cancel
Showing results for 
Search instead for 
Did you mean: 

How to write custom DMA USART TxHalfCmpt routine.

RSonn.706
Associate II

I'm a bit of a novice at programming, but I'm trying to develop an application that sends a continuous stream of data out of the USART using DMA, taking advantage of the DMA Transfer Half-Complete, and Transfer-Complete interrupts.  

I've got a circular DMA USART transmission working, but when I enable either of the interrupts, my code goes into an InfiniteLoop: handler.  

I'm using a BluePill (STM32F103C8) and writing in VSCode / PlatformIO with the Arduino-ststm32 framework.

I can't figure out how to tell the code where I want it to go when either of these interrupts fire.

I'm doing a lot of this programming "bare-metal" because I don't really know how to determine what core and/or library tools I have available to utilize.  I see a lot of examples on the internet, but invariably they are using a different setup (framework etc) than I am, so their code doesn't really port to my IDE.

Help?!

Here is my code (couldn't figure out how to attach it)

#include <Arduino.h>
#include "HardwareSerial.h"

//This program sends a serial stream of data from an STM32F1 BluePill to an ESP32 MCU for display on a web page.


uint8_t to_esp32_buffer[4];  //

HardwareSerial Serial3(PB11,PB10);


void setup() {
  Serial.begin(38400);
  to_esp32_buffer[0] = 0x00;
  to_esp32_buffer[1] = 0x01;
  to_esp32_buffer[2] = 0x02;
  to_esp32_buffer[3] = 0x03;
  
  RCC->AHBENR |= RCC_AHBENR_DMA1EN; //enable clock for DMA1
  RCC->APB1ENR |= RCC_APB1ENR_USART3EN;//enable clock for USART3
  NVIC_EnableIRQ(USART3_IRQn);  //enable global interrupts from USART3
  NVIC_EnableIRQ(DMA1_Channel2_IRQn); //enable global interrupts from DMA1 channel2 (which handles usart3 Tx)
  Serial3.begin(600);
  USART3->CR1 &= ~USART_CR1_UE;  //Disable USART
  USART3->CR1 |= USART_CR1_TE;  //Transmit enable
  USART3->CR1 |= USART_CR1_RE;  //Receive enable

  USART3->CR3 |= USART_CR3_DMAT;  //Set for DMA transfer
  //USART3->SR &= ~USART_SR_TC;  //Clear the transmission complete bit in the status register
  //Initially the CCR register is set to 0x0000 0000 so only non-zero values must be written
  //DMA1_Channel1->CCR  &= ~DMA_CCR_EN; //disable DMA channel before configuring
  DMA1_Channel2->CPAR = (uint32_t) & USART3->DR;  //Configure the address of the peripheral
  //DMA1_Channel2->CMAR = (uint32_t)msg;  //Configure the address of memory
  DMA1_Channel2->CMAR = (uint32_t) & to_esp32_buffer;  //Configure the address of memory
  DMA1_Channel2->CCR &= ~(DMA_CCR_PL_Msk);  //Low priority
  DMA1_Channel2->CCR &= ~(DMA_CCR_MSIZE_Msk);  //Configure "chunk" size  00=8bits, 01=16bits, 10=32bits, 11=reserved
  DMA1_Channel2->CCR &= ~(DMA_CCR_PSIZE_Msk);  //Configure Peripheral chunk size.
  DMA1_Channel2->CCR |= DMA_CCR_MINC;  //increment memory address after each byte tranferred
  DMA1_Channel2->CCR |= DMA_CCR_DIR;  //from memory to peripheral (USART Transmit)
  //I don't know how to handle these interrupts (yet)
  //DMA1_Channel2->CCR |= DMA_CCR_HTIE;  //Enable interrupt when transfer half-complete
  //DMA1_Channel2->CCR |= DMA_CCR_TCIE;  //Enable interrupt when transfer complete
  DMA1_Channel2->CCR |= DMA_CCR_CIRC;  //Enable Circular mode  (keep repeating)
  DMA1_Channel2->CNDTR = 4;  //Tell how many chunks (bytes) to transfer
  DMA1_Channel2->CCR  |= DMA_CCR_EN;  //Enable the DMA channel

  USART3->CR1 |= USART_CR1_UE;  //Enable USART

}



void loop(){
  //This is just to prove that the main loop is running
  Serial.print("x");
  delay(1);

}


 

 

1 ACCEPTED SOLUTION

Accepted Solutions

Thanks for the reply, but this guidance is still quite vague to me.

I found this in the startup_stm32f103x6.s file

g_pfnVectors:

.word _estack
.word Reset_Handler
.word NMI_Handler
...
.word DMA1_Channel1_IRQHandler
.word DMA1_Channel2_IRQHandler
.word DMA1_Channel3_IRQHandler
...

.weak DMA1_Channel2_IRQHandler
.thumb_set DMA1_Channel2_IRQHandler,Default_Handler

 

 

so I added this to the end of my main routine:

 


void DMA1_Channel2_IRQHandler(void){
  Serial.print("y");
}
 
But it still goes into an infiniteLoop handler
I've also tried adding 
extern "C" in front of the function, but that doesn't work either.  Still goes to infiniteLoop handler
 
************************************  SOLVED  ***************************************
I incremented a counter inside the IRQ handler and found (by pausing the debugger) that the counter was HUGE.
So it looked like the IRQ was just firing continuously.
Then it occurred to me that I may need to clear the interrupt flags in the handler routine.  THAT WORKED.
Here is my interrupt handler for a DMA USART Transmit in circular mode:
 
extern "C" {void DMA1_Channel2_IRQHandler(void){
  if(DMA1->ISR & DMA_FLAG_HT2){  //if the channel 2 half-transfer flag is set
    count_half++;
    //put code here to update data in the first half of the buffer
    //while the second half is being transmitted
    DMA1->IFCR |= DMA_FLAG_HT2; //reset flag
  }
  if(DMA1->ISR & DMA_FLAG_TC2){  //if the channel 2 transfer-complete flag is set
    count_full++;
    //put code here to update data in the second half of the buffer
    //while the first half is being transmitted
    DMA1->IFCR |= DMA_FLAG_TC2; //reset flag
  }
  if(DMA1->ISR & DMA_FLAG_GL2){  //if the channel 2 global flag is set
    count_global++;
    DMA1->IFCR |= DMA_FLAG_GL2; //reset flag
  }
}
}


View solution in original post

2 REPLIES 2
TDK
Guru

You need to implement the functions as they are listed in the vector table. In STM32CubeIDE, this is in the startup_*.s file, likely yours is the same or similar. Without those defined, the default handler is launched.

 

Look for a function name similar to USART3_IRQHandler and implement that. Similar for the other one. Name might be a little different, like DMA1_Channel2_3_IRQHandler.

 

If you feel a post has answered your question, please click "Accept as Solution".

Thanks for the reply, but this guidance is still quite vague to me.

I found this in the startup_stm32f103x6.s file

g_pfnVectors:

.word _estack
.word Reset_Handler
.word NMI_Handler
...
.word DMA1_Channel1_IRQHandler
.word DMA1_Channel2_IRQHandler
.word DMA1_Channel3_IRQHandler
...

.weak DMA1_Channel2_IRQHandler
.thumb_set DMA1_Channel2_IRQHandler,Default_Handler

 

 

so I added this to the end of my main routine:

 


void DMA1_Channel2_IRQHandler(void){
  Serial.print("y");
}
 
But it still goes into an infiniteLoop handler
I've also tried adding 
extern "C" in front of the function, but that doesn't work either.  Still goes to infiniteLoop handler
 
************************************  SOLVED  ***************************************
I incremented a counter inside the IRQ handler and found (by pausing the debugger) that the counter was HUGE.
So it looked like the IRQ was just firing continuously.
Then it occurred to me that I may need to clear the interrupt flags in the handler routine.  THAT WORKED.
Here is my interrupt handler for a DMA USART Transmit in circular mode:
 
extern "C" {void DMA1_Channel2_IRQHandler(void){
  if(DMA1->ISR & DMA_FLAG_HT2){  //if the channel 2 half-transfer flag is set
    count_half++;
    //put code here to update data in the first half of the buffer
    //while the second half is being transmitted
    DMA1->IFCR |= DMA_FLAG_HT2; //reset flag
  }
  if(DMA1->ISR & DMA_FLAG_TC2){  //if the channel 2 transfer-complete flag is set
    count_full++;
    //put code here to update data in the second half of the buffer
    //while the first half is being transmitted
    DMA1->IFCR |= DMA_FLAG_TC2; //reset flag
  }
  if(DMA1->ISR & DMA_FLAG_GL2){  //if the channel 2 global flag is set
    count_global++;
    DMA1->IFCR |= DMA_FLAG_GL2; //reset flag
  }
}
}