c - STM32:以DMA模式实现UART

标签 c stm32 uart dma hal

我正在尝试在 DMA 模式下实现 UART,以便在每次按下按钮时传输一个简单的字符串。

因此,我使用 CubeMX 生成代码,并将 UART2 TX DMA 配置为正常(非循环)模式,并且没有 FIFO 和突发。

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

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

还有,发送下半部分数据会释放UART的gState吗?

请问有人给我们一个在项目中配置UART的例子

最佳答案

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

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

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

这是一个例子:

在此示例中,将使用 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 微 Controller 更改适当的 DMA 实例、UART-DMA channel 、GPIO 和备用功能设置。

关于c - STM32:以DMA模式实现UART,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43298708/

相关文章:

c - sprintf 多个字节使用十六进制作为参数

c - 输出 C 端错误

c - 在哪里可以找到 stdio.h 函数实现?

c - STM32 DMA 传输错误同时设置 FIFO 和传输错误标志

c - 跳转到stm32f4上的第二个固件

c - 模拟 getchar() 的 UART 驱动程序

c - 如何在末尾添加节点(单链表)

c - 将sizeof()的结果分配给ssize_t

embedded - 与 stm32 接口(interface)的网络摄像头

ada - STM32F4 UART 半字寻址