STM32:在DMA模式下实现UART

时间:2017-04-08 18:16:46

标签: stm32 uart dma hal

我试图在DMA模式下实现UART,每次按下按钮时都会发送一个简单的字符串。

所以我使用CubeMX生成代码,我在正常(非圆形)模式下配置了UART2 TX DMA,也没有FIFO和没有突发。

每当我在调试模式下运行代码时,我看到我第一次尝试发送字符串,它工作正常并发送字符串,但在DMA IRQ处理程序中,它调用TxHalfCpltCallback而不是TxCpltCallback以及UART gState将保持在忙碌模式,所以我不能用它来传输更多字符串。

我的问题是为什么它调用TxHalfCpltCallback而不是TxCpltCallback?我应该如何处理它(因为HAL引用说等待发送缓冲区的后半部分!什么?)

而且,发送下一半数据是否会重新关联UART的gState?

我想请某人给我们一个在项目中配置UART的示例。

7 个答案:

答案 0 :(得分:12)

如果您使用DMA,那么您将有两个中断:一个是缓冲区的一半传输,另一个是传输后半区(整个)。

如果一切正常,应该解雇他们两个。这背后的原因是,当发送大量数据时,您可以开始将新数据加载到TxHalfCpltCallback缓冲区的前半部分,而缓冲区的后半部分由DMA传输。再次,您可以在传输上半部分时将新数据加载到TxCpltCallback缓冲区的后半部分。

优点是您不必在将下一个数据块复制到缓冲区之前等待整个传输完成,但您可以在传输仍在进行时开始加载它。

以下是一个例子:

在此示例中,将使用DMA传输2000个字节,传输半完成传输完成中断,以实现最佳性能。

发送缓冲区的前半部分由CPU在发送半完成中断回调中加载新数据,而后半部分缓冲区由DMA在后台发送。

然后,在发送完成中,发送缓冲区的后半部分由CPU加载新数据,而后半部分(先前更新)由DMA在后台发送。

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

该示例是为STM32F4 Discovery板(STM32F407VG)编写的。应根据所使用的STM32微控制器更改相应的DMA实例,UART-DMA通道,GPIO和备用功能设置。

答案 1 :(得分:3)

您的问题与https://community.st.com/thread/40080-dma-uart-with-hal-remain-busy-bug

类似

您应该启用HAL_UART_IRQHandler()

即。 在“main.c”内(或初始化硬件的地方)添加:

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

在“stm32f4xx_it.c”里面:

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

答案 2 :(得分:1)

对于那些将 STM32CubeIDE 与 FreeRTOS 一起使用的人,问题可能在于中断优先级。 FreeRTOS 使用 https://companyname.workfront.com/attask/api/v9.0/ctgy/search?fields=['*'] 设置最高中断优先级,从中可以调用中断安全的 FreeRTOS API 函数。该值默认设置为 5,如果 DMA 和 UART 中断具有相同的优先级,它们将不会触发!

通常 DMA 和 UART 中断函数不会调用 FreeRTOS API 函数,因此可以更高。 STM32 微控制器 4 到 0 的意思。

要在 SM32CubeIDE 中实现此目的,您需要取消勾选 NVIC 配置中的使用 FreeRTOS 功能选项,然后相应地设置 DMA 和 UART 中断的优先级:

NVIC example configuration

答案 3 :(得分:0)

如果您使用该功能

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

CubeMX库中,它将启用所有DMA中断。您可以通过清除DMA_SxCR寄存器中的HTIE位来禁用半传输中断。

答案 4 :(得分:0)

对我来说,使用DMA时出现传输错误。通过启用TXE中断解决了该问题:

void sas_write(char* buf, uint16_t size)
{
    HAL_UART_Transmit_DMA(&uart_handle, buf, size) ;
    __HAL_UART_ENABLE_IT(&uart_handle, UART_IT_TXE) ;
}

答案 5 :(得分:0)

如果您使用STM32CubeMX生成项目代码,您还可以在USARTx模式和配置面板上启用USARTx全局中断-> NVIC设置以解决gState保持繁忙时的问题。

答案 6 :(得分:-1)

当你使用裸注册方法代替主宰HAL怪物时,编码DMA传输(当然还有接收)要容易得多。

示例STM32F446(假设寄存器中的复位值)

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;
很容易 - 不是吗?

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