STM32: Implementing UART in DMA Mode

I am trying to implement a UART in DMA mode to transfer a simple string every time a button is pressed.

So, I used CubeMX to generate the code, and I configured the UART2 TX DMA in normal (non-circular) mode as well as no FIFO and no packet.

Whenever I run the code in debug mode, I see the first time when I try to send a string, it works fine and sends the string, but inside the IRQ DMA handler, it calls TxHalfCpltCallback, not TxCpltCallback, and also UART gState will remain in BUSY, so I cannot use it to transfer more string.

My question is why is it calling TxHalfCpltCallback and not TxCpltCallback? and how should I handle it (because the HAL link says it expects to send the second half of the buffer! what?)

And also after sending the next half of the data, pass the gState UART?

I would like to ask someone to give us an example of setting up UART in a project.

+3


source to share


4 answers


If you are using DMA, then you will have two interrupts: one when half of the buffer is being transmitted, and the other when the other half is being transmitted (integer).

Both of them should be running if everything is ok. The reason for this is that when sending a huge amount of data, you can start loading new data into the first half of the buffer in TxHalfCpltCallback

, and the second half of the buffer is being transferred to DMA. Again, you can load new data into the second half of the buffer TxCpltCallback

while the first half is being transmitted.

The advantage is that you don't have to wait for the entire transfer to complete before copying the next chunk of data to the buffer, but you can already start downloading while the transfer is still in progress.

Here's an example:

In this example, 2000 bytes will be transferred using DMA, Transmit Half Complete and Transmit Complete interrupts for maximum performance.



The first half of the transmit buffer is loaded with new CPU data in the Transmit Half Complete interrupt callback , and the second half of the buffer is transferred to DMA in the background.

Then, in Transmit Complete, the second half of the transmit buffer is loaded with new CPU data, and the first half (previously updated) is transferred to DMA in the background.

#include "stm32f4xx.h"

uint8_t dma_buffer[2000];
volatile uint8_t toggle = 0;

UART_HandleTypeDef huart2;
DMA_HandleTypeDef hdma_usart2_tx;

void uart_gpio_init()
{
  GPIO_InitTypeDef GPIO_InitStruct;

  __GPIOA_CLK_ENABLE();

  /**USART2 GPIO Configuration
  PA2     ------> USART2_TX
  PA3     ------> USART2_RX
  */
  GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

void uart_dma_init()
{
  /* DMA controller clock enable */
  __DMA1_CLK_ENABLE();

  /* Peripheral DMA init*/
  hdma_usart2_tx.Instance = DMA1_Stream6;
  hdma_usart2_tx.Init.Channel = DMA_CHANNEL_4;
  hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
  hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
  hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE;
  hdma_usart2_tx.Init.PeriphDataAlignment = DMA_MDATAALIGN_BYTE;
  hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  hdma_usart2_tx.Init.Mode = DMA_NORMAL;
  hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW;
  hdma_usart2_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
  HAL_DMA_Init(&hdma_usart2_tx);

  __HAL_LINKDMA(&huart2,hdmatx,hdma_usart2_tx);

  /* DMA interrupt init */
  HAL_NVIC_SetPriority(DMA1_Stream6_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Stream6_IRQn);
}

void uart_init()
{
  __USART2_CLK_ENABLE();

  huart2.Instance = USART2;
  huart2.Init.BaudRate = 115200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  HAL_UART_Init(&huart2);

  /* Peripheral interrupt init*/
  HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(USART2_IRQn);
}

/* This function handles DMA1 stream6 global interrupt. */
void DMA1_Stream6_IRQHandler(void)
{
  HAL_DMA_IRQHandler(&hdma_usart2_tx);
}

void USART2_IRQHandler(void)
{
  HAL_UART_IRQHandler(&huart2);
}

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  uint16_t i;
  toggle = !toggle;

  for(i = 1000; i < 1998; i++)
  {
    if(toggle)
      dma_buffer[i] = '&';
    else
      dma_buffer[i] = 'z';
  }

  dma_buffer[1998] = '\r';
  dma_buffer[1999] = '\n';
}

void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart)
{
  uint16_t i;

  for(i = 0; i < 1000; i++)
  {
    if(toggle)
      dma_buffer[i] = 'y';
    else
      dma_buffer[i] = '|';
  }
}

int main(void)
{
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  uart_gpio_init();
  uart_dma_init();
  uart_init();

  uint16_t i;

  for(i = 0; i < 1998; i++)
  {
    dma_buffer[i] = 'x';
  }

  dma_buffer[1998] = '\r';
  dma_buffer[1999] = '\n';

  while(1)
  {
    HAL_UART_Transmit_DMA(&huart2, dma_buffer, 2000);
  }
}

      

The example was written for the STM32F4 detection board (STM32F407VG). Corresponding DMA instance, UART-DMA channel, GPIO parameters and alternative functions must be changed according to the used STM32 microcontroller.

+5


source


If you use the function

HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) 

      



in the library CubeMX

, it will enable all DMA interrupts. You can disable the half-transfer interrupt by clearing the HTIE bit in the register DMA_SxCR

.

0


source


Your problem looks the same as https://community.st.com/thread/40080-dma-uart-with-hal-remain-busy-bug

You should enable HAL_UART_IRQHandler ()

i.e. inside "main.c" (or wherever you initialize your hardware) add:

HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);                                        
HAL_NVIC_EnableIRQ(USART2_IRQn);

      

inside "stm32f4xx_it.c":

void USART2_IRQHandler(void)
{ 
  HAL_UART_IRQHandler(&huart2);
}

      

0


source


It is much easier to code a DMA transfer (and receive, of course) when you are using an open case approach rather than a HAG monster monster.

STM32F446 example (assuming reset values ​​in registers)

DMA1_Stream6 -> NDTR = nTransfers;
DMA1_Stream6 -> PAR = (uint32_t)&(USART2 -> DR);
DMA1_Stream6 -> M0AR = (uint32_t)&dataBuff;
DMA1_Stream6 -> CR = DMA_SxCR_CHSEL_2 | DMA_SxCR_MINC | DMA_SxCR_DIR_0 | DMA_SxCR_TCIE; // you can enable half transfer enable as well

USART2 -> BRR = FCLK / LOWSPEED;
USART2 -> CR3 |= USART_CR3_DMAT;
USART2 -> CR1 = (USART_CR1_TE | USART_CR1_RE | USART_CR1_UE);
DMA1_Stream6 -> CR |= DMA_SxCR_EN;

      

pretty easy - isn't it?

void DMA1_Stream6_IRQHandler(void) {  // now it does nothing only clears the flag
    if(DMA1 -> HISR & (DMA_HISR_TCIF6)) {
        DMA1 -> HIFCR |= DMA_HISR_TCIF6;
        while(!(USART2 -> SR & USART_SR_TC));
    }
}

      

-1


source







All Articles