这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » STM32 » STM32的串口DMA发送

共1条 1/1 1 跳转至

STM32的串口DMA发送

院士
2026-03-19 00:47:37     打赏

在嵌入式系统开发中,串口通信是一种极为常见的数据传输方式,广泛应用于设备调试、数据采集与外设交互等应用场景。然而,传统的串口发送方式依赖CPU轮询或中断触发,在大数据量传输场景下会占用大量CPU资源,导致系统实时性下降。DMA(Direct Memory Access,直接内存访问)技术的出现为解决这一问题提供了有效方案,它能够在无需CPU干预的情况下,实现内存与外设之间的数据直接传输,显著提升系统实时运行效率。

在上一篇帖子的介绍下,本次我们使用ST公司的STM32F407为例为大家分享串口发送DMA的工作原理,详细讲解硬件连接与软件配置步骤,并通过实战案例展示其具体应用。

STM32F407配备了两个DMA控制器(DMA1和DMA2),每个控制器拥有8个通道,可支持多种外设的数据传输请求。DMA控制器能够独立于CPU完成数据传输,其工作流程主要包括以下几个阶段:

  1. 1. 请求触发:当外设(如串口)需要发送数据时,向DMA控制器发送传输请求。

  2. 2. 通道配置:DMA控制器根据预设的通道配置信息,确定数据源地址、目标地址、传输数据量及传输模式。

  3. 3. 数据传输:DMA控制器直接从内存读取数据,并将其发送至串口数据寄存器,完成数据传输。

  4. 4. 传输完成:当数据传输完成或发生错误时,DMA控制器向CPU发送中断请求(若开启中断),通知CPU进行后续处理。

考虑到我们是为了学习DMA的配置过程,所以,我的代码没有使用现在推荐的HAL库代码,而是使用了LL库。

/**
 * @brief USART1 Initialization Function
 * @param None
 * @retval None
 */
static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  LL_USART_InitTypeDef USART_InitStruct = {0};

  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* Peripheral clock enable */
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_USART1);
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA2);
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA);
  /**USART1 GPIO Configuration
  PA9   ------> USART1_TX
  PA10   ------> USART1_RX
  */
  GPIO_InitStruct.Pin = LL_GPIO_PIN_9 | LL_GPIO_PIN_10;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_7;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USART1 DMA Init */

  /* USART1_TX Init */
  LL_DMA_SetChannelSelection(DMA2, LL_DMA_STREAM_7, LL_DMA_CHANNEL_4);

  LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_STREAM_7, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);

  LL_DMA_SetStreamPriorityLevel(DMA2, LL_DMA_STREAM_7, LL_DMA_PRIORITY_LOW);

  LL_DMA_SetMode(DMA2, LL_DMA_STREAM_7, LL_DMA_MODE_NORMAL);

  LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_STREAM_7, LL_DMA_PERIPH_NOINCREMENT);

  LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_STREAM_7, LL_DMA_MEMORY_INCREMENT);

  LL_DMA_SetPeriphSize(DMA2, LL_DMA_STREAM_7, LL_DMA_PDATAALIGN_BYTE);

  LL_DMA_SetMemorySize(DMA2, LL_DMA_STREAM_7, LL_DMA_MDATAALIGN_BYTE);

  LL_DMA_DisableFifoMode(DMA2, LL_DMA_STREAM_7);

  LL_DMA_SetPeriphAddress(DMA2, LL_DMA_STREAM_7, (uint32_t)&USART1->DR);

  LL_DMA_SetMemoryAddress(DMA2, LL_DMA_STREAM_7, (uint32_t)uart_tx_buf);

  /* USART1 interrupt Init */
  NVIC_SetPriority(USART1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 0));
  NVIC_EnableIRQ(USART1_IRQn);

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  USART_InitStruct.BaudRate = 115200;
  USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B;
  USART_InitStruct.StopBits = LL_USART_STOPBITS_1;
  USART_InitStruct.Parity = LL_USART_PARITY_NONE;
  USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX;
  USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE;
  USART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_16;
  LL_USART_Init(USART1, &USART_InitStruct);
  LL_USART_ConfigAsyncMode(USART1);
  LL_USART_Enable(USART1);
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */
}

上面这段代码首先配置了传统的串口配置参数,如GPIO的初始化,Usart的参数设置,而在源代码中的第36行即开始对DMA进行配置。根据STM32CubeMX生成的代码可以看到,对于Usart1的TX发送DMA是使用的DMA2的Channel4通道。在配置过程中,我们也可以看到DMA模块的作用是将内存usart_tx_buf的数据按字节的方式传输到串口usart1的DR数据寄存器里面,对于传输字节的长度,我们未进行配置,而留在了发送前进行配置。

void uart_dmasend(const char *buf, uint16_t length)
{
  memcpy(uart_tx_buf, buf, length);
  LL_DMA_DisableStream(DMA2, LL_DMA_STREAM_7)
  LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_7, length);
  LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_7);
}

上面这段代码是发送函数的API。其实,代码只是配置了本次DMA传输所需要的字节数量,并通过软件手动开启了DMA传输。

DMA的配置并不难,使用起来也没有太复杂的知识点可言,但其对系统整体性能的提升还是十分显著。建议大家在项目里面首选DMA传输。





关键词: STM32     串口     发送     DMA    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]