打印
[其他ST产品]

基于Cubemx与HAL库串口DMA收发经验分享

[复制链接]
412|28
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
jcky001|  楼主 | 2023-4-3 10:20 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
STM32 DMA简介

DMA,全称为:Direct Memory Access,即直接存储器访问, DMA 传输将数据从一个 地址空间复制到另外一个地址空间。当 CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器 来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的 内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工 作。

DMA 传输对于高效能嵌入式系统算法和网络是很重要的。DMA 传输方式无需 CPU 直接 控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备 开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高。

如图 所示, 两个 DMA 控制器有 12 个通道(DMA1 有 7 个通道, DMA2有5个通道)。DMA 控制器和 Cortex-M3 核心共享系统数据总线,执行直接存储器数据传输。当 CPU 和 DMA 同时访问相同的目标(RAM 或外设) 时, DMA 请求会暂停 CPU 访问系统总线达若干个周期,总线仲裁器执行循环跳读,以保证CPU 至少可以得到一半的系统总线带宽。




  • DMA 处理

在发送一个事件后,外设向 DMA 发送一个请求信号 的箭头。DMA 控制器根据通道的优先权处理请求。当 DMA 需 送请求的外设时, DMA 控制器立即发送给它一个应答信号。外 号后,立即释放它的请求, 同时 DMA 控制器撤销应答信号


  • 仲裁器

一个 DMA 控制器对应 8 个数据流,数据流包含要传输数据的源地址、目标地址、数据等信息。如果我们需要同时使用同一个 DMA 控制器多个外设请求时,那必然需要同时使用多个数据流,其中哪个数据流优先, 此时由仲裁器来选定。

仲裁器管理数据流方法分为两个阶段。第一阶段属于软件阶段,我们在配置数据流时可以通过寄存器设定它的优先级别,可以在 DMA_CCRx 寄存器中设置, 有最高优先级、高优先级、中等优先级和低优先级四个等级。第二阶段是硬件,如果两个请求有相同的软件优先级,则较低编号的通道比高编号的通道有较高的优先权。例如:通道 2 优先于通道 4。


  • DMA 通道

每个通道都可以在由固定地址的外设寄存器和存储器之间执行DMA 传输。DMA 的传输数据量是可编程, 可以通过 DMA_CCRx 寄存器中的PSIZE 和 MSIZE 位来进行编程,数据量最大可以达到 65535。DMA 的外设繁多, 例如 DMA1 控制器,从外设产生 7 个请求,通过逻辑或(例如通道 1 的三个 DMA 请求,这几个是通过逻辑或到通道 1 的,这样我们在同一时间,就只能使用其中的一个)输入到 DMA1 控制器,此时只有一个请求有效。




STM32 的 DMA 有以下一些特性:

●每个通道都直接连接专用的硬件 DMA 请求,每个通道都同样支持软件触发。这些功能 通过软件来配置。

● 在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如 在相等优先权时由硬件决定(请求 0 优先于请求 1,依此类推) 。

● 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和 目标地址必须按数据传输宽度对齐。

● 支持循环的缓冲器管理

● 每个通道都有 3 个事件标志(DMA 半传输, DMA 传输完成和 DMA 传输出错),这 3 个 事件标志逻辑或成为一个单独的中断请求。

● 存储器和存储器间的传输

● 外设和存储器,存储器和外设的传输

● 闪存、 SRAM、外设的 SRAM、 APB1 APB2 和 AHB 外设均可作为访问的源和目标。

● 可编程的数据传输数目:最大为 65536


STM32串口DMA使用详解

本次我们使用的硬件环境是之前开源的板子,falling-star board,使用串口1。


使用特权

评论回复
沙发
jcky001|  楼主 | 2023-4-3 10:21 | 只看该作者
cubemx配置

关于时钟配置、串口基本配置请参看:cubemx的正确打开方式一文

接下来直接进入配置串口DMA:

选择串口1,基本参数如图,都是老生常谈了,easy~




选择DMA Settings,主要有一下几个地方,基本上不需要改动,根据自己的使用情况确认即可,需要注意的是,发送和接收并不是一定要成对出现的,可以只选择DMA发送或者DMA接收




中断设置,DMA中断可以配置,可以不配置,同样也是根据自己的实际需求情况,串口中断需要配置,下面会用到,优先级根据自己任务的优先级确定,分好“轻重缓急”即可




配置非常简单,主要是在此前串口功能基础上添加DMA功能,over


使用特权

评论回复
板凳
jcky001|  楼主 | 2023-4-3 10:21 | 只看该作者
串口DMA代码设计串口DMA源码API介绍

上面提到的配置项,都封装在一个结构体里面

  • /** @defgroup DMA_Exported_Types DMA Exported Types
  •   * @{
  •   */
  • /**
  •   * @brief  DMA Configuration Structure definition
  •   */
  • typedef struct
  • {
  •   uint32_t Direction;                 /*!< Specifies if the data will be transferred from memory to peripheral,
  •                                            from memory to memory or from peripheral to memory.
  •                                            This parameter can be a value of @ref DMA_Data_transfer_direction */
  •   uint32_t PeriphInc;                 /*!< Specifies whether the Peripheral address register should be incremented or not.
  •                                            This parameter can be a value of @ref DMA_Peripheral_incremented_mode */
  •   uint32_t MemInc;                    /*!< Specifies whether the memory address register should be incremented or not.
  •                                            This parameter can be a value of @ref DMA_Memory_incremented_mode */
  •   uint32_t PeriphDataAlignment;       /*!< Specifies the Peripheral data width.
  •                                            This parameter can be a value of @ref DMA_Peripheral_data_size */
  •   uint32_t MemDataAlignment;          /*!< Specifies the Memory data width.
  •                                            This parameter can be a value of @ref DMA_Memory_data_size */
  •   uint32_t Mode;                      /*!< Specifies the operation mode of the DMAy Channelx.
  •                                            This parameter can be a value of @ref DMA_mode
  •                                            @NOTE The circular buffer mode cannot be used if the memory-to-memory
  •                                                  data transfer is configured on the selected Channel */
  •   uint32_t Priority;                  /*!< Specifies the software priority for the DMAy Channelx.
  •                                            This parameter can be a value of @ref DMA_Priority_level */
  • } DMA_InitTypeDef;

复制代码


关于DMA的函数不多,本次用到DMA的初始化,开始发送函数




串口中关于DMA的部分主要有这几个函数,还有一些关于中断、DMA标志等的一些宏定义,就不在一一列举了,需要用的时候大家知道去库函数中去找就可以了




串口DMA初始化部分:

  • void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
  • {
  •   GPIO_InitTypeDef GPIO_InitStruct = {0};
  •   if(uartHandle->Instance==USART1)
  •   {
  •   /* USER CODE BEGIN USART1_MspInit 0 */
  •   /* USER CODE END USART1_MspInit 0 */
  •     /* USART1 clock enable */
  •     __HAL_RCC_USART1_CLK_ENABLE();
  •     __HAL_RCC_GPIOA_CLK_ENABLE();
  •     /**USART1 GPIO Configuration
  •     PA9     ------> USART1_TX
  •     PA10     ------> USART1_RX
  •     */
  •     GPIO_InitStruct.Pin = User_UART_TX_Pin;
  •     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  •     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  •     HAL_GPIO_Init(User_UART_TX_GPIO_Port, &GPIO_InitStruct);
  •     GPIO_InitStruct.Pin = User_UART_RX_Pin;
  •     GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  •     GPIO_InitStruct.Pull = GPIO_NOPULL;
  •     HAL_GPIO_Init(User_UART_RX_GPIO_Port, &GPIO_InitStruct);
  •     /* USART1 DMA Init */
  •     /* USART1_TX Init */
  •     hdma_usart1_tx.Instance = DMA1_Channel4;
  •     hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
  •     hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
  •     hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
  •     hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  •     hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  •     hdma_usart1_tx.Init.Mode = DMA_CIRCULAR;
  •     hdma_usart1_tx.Init.Priority = DMA_PRIORITY_MEDIUM;
  •     if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)
  •     {
  •       Error_Handler();
  •     }
  •     __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx);
  •     /* USART1_RX Init */
  •     hdma_usart1_rx.Instance = DMA1_Channel5;
  •     hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
  •     hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
  •     hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
  •     hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  •     hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  •     hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
  •     hdma_usart1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;
  •     if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
  •     {
  •       Error_Handler();
  •     }
  •     __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);
  •     /* USART1 interrupt Init */
  •     HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
  •     HAL_NVIC_EnableIRQ(USART1_IRQn);
  •   /* USER CODE BEGIN USART1_MspInit 1 */
  •   /* USER CODE END USART1_MspInit 1 */
  •   }
  • }

复制代码


DMA发送设计

发送非常简单,都不知道该怎么介绍...发送对应DMA的方向是内存到外设,我们只需要把数据装入相应的内存中,调用发送的API即可:

  • /******************************************************
  • * Brief     : DMA传输函数
  • * Parameter :
  • *           *pData: 要传输的数据
  • * Return    : None.
  • *******************************************************/
  • void User_uartdma_Transmit(const char *pData)
  • {
  • HAL_UART_Transmit_DMA(&huart1,(uint8_t *)pData,strlen(pData));
  • }

复制代码


根据DMA的原理,我们发送的时候是不影响CPU的正常工作的,效果应该类似于操作系统的多任务执行,现在我们只需要在初始化时候打开DMA发送数据,串口助手监控,主函数什么都不干,数据不断在发送

此时如果添加两个LED流水灯,是完全不受发送数据影响的,效果就不放了,相信小飞哥...




不信你看~




发送的是非常简单的,好像这里也没体现出来,使用DMA发送有什么好处,其实在LCD驱动的时候,当有图片等大数据量的数据需要传输的时候,使用DMA是种非常好的方式




使用特权

评论回复
地板
jcky001|  楼主 | 2023-4-3 10:22 | 只看该作者
DMA接收设计

聊起串口数据接收,我们最先接触的可能是,中断接收,来一字节进一次中断,通过定时器超时判断一帧数据结束。

但是小伙伴们有没有考虑过一个事情,数据量很大,串口一直进中断,对MCU带来的负荷是非常大的,这种方式就显得不那么美好了。

哲学上讲,矛盾是推动社会进步的源泉,没错,鉴于此种情况,我们换一种方式来处理,DMA+串口空闲中断的方式,我相信,这种方式你一用就会喜欢上~

具体的设计思路是:

1、开启串口1中断

2、开启串口1空闲中断

3、打开串口DMA接收

4、判断空闲中断标志是否置位

5、数据接收完成,主函数打印接收到的数据

先来封装几个函数:

  • /******************************************************
  • * Brief     : 串口DMA 初始化,初始化除了cubemx配置之外的部分
  • * Parameter :
  • *           :
  • * Return    : None.
  • *******************************************************/
  • void User_uartdma_Init(void)
  • {
  • //失能串口DMA传输
  • HAL_UART_DMAStop(&huart1);
  • //使能串口1接收中断
  • __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
  • //使能串口1空闲中断
  • __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
  • //使能串口DMA接收
  • HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen);
  • }

复制代码

  • /**
  •   * @brief  DMA接收函数
  •   * @param  htim TIM Base handle
  •   * @retval HAL status
  •   */
  • void User_uartdma_Receive(uint8_t *recData,uint16_t rec_len)
  • {
  • HAL_UART_Receive_DMA(&huart1,recData,rec_len);
  • }


复制代码


主函数:

  • /**
  •   * @brief This function handles USART1 global interrupt.
  •   */
  • void USART1_IRQHandler(void)
  • {
  •   /* USER CODE BEGIN USART1_IRQn 0 */
  • uint32_t idle_flag_temp = 0;
  • uint16_t len_temp = 0;
  •   /* USER CODE END USART1_IRQn 0 */
  •   HAL_UART_IRQHandler(&huart1);
  •   /* USER CODE BEGIN USART1_IRQn 1 */
  • idle_flag_temp = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);
  • if(idle_flag_temp)
  • {
  •   __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);
  •   HAL_UART_DMAStop(&huart1);
  •   len_temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
  •   UserUartDma.RecDat_len = Max_RecLen - len_temp;
  •   UserUartDma.rec_endFlag = 1;
  • }
  •   __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
  •   HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen);
  •   /* USER CODE END USART1_IRQn 1 */
  • }

复制代码


演示效果:



[size=0.83em]
微信图片_20230402181811.png (529.25 KB, 下载次数: 0)
下载附件
[color=rgb(153, 153, 153) !important]昨天 18:20 上传





使用特权

评论回复
5
Stahan| | 2023-4-4 23:55 | 只看该作者
DMA是不是只支持硬件iic?

使用特权

评论回复
6
MessageRing| | 2023-4-5 22:50 | 只看该作者
Stahan 发表于 2023-4-4 23:55
DMA是不是只支持硬件iic?

模拟的要用cpu控制时序。当然不行

使用特权

评论回复
7
maqianqu| | 2023-5-10 20:06 | 只看该作者
DMA功能能够实现串口接收数据直接存储到Flash中吗

使用特权

评论回复
8
earlmax| | 2023-5-10 20:37 | 只看该作者
stm32usb和串口dma哪个快

使用特权

评论回复
9
updownq| | 2023-5-10 21:08 | 只看该作者
stm32 dma串口发送和接收怎么配置

使用特权

评论回复
10
mmbs| | 2023-5-10 21:14 | 只看该作者
如何使stm32用dma进行串口发送

使用特权

评论回复
11
hearstnorman323| | 2023-5-10 21:33 | 只看该作者
stm32dma填入的内容怎么修改

使用特权

评论回复
12
qiufengsd| | 2023-5-10 22:03 | 只看该作者
用dma传输adc数据怎么为0              

使用特权

评论回复
13
tabmone| | 2023-5-11 15:45 | 只看该作者
使用dma接受串口数据,怎样清空串口buffer

使用特权

评论回复
14
backlugin| | 2023-5-11 15:54 | 只看该作者
STM32的DMA串口循环模式如何设置发送周期?

使用特权

评论回复
15
zerorobert| | 2023-5-11 16:26 | 只看该作者
怎样将数据放入一个缓冲区stm32

使用特权

评论回复
16
benjaminka| | 2023-5-11 16:57 | 只看该作者
stm32 串口 能用dma同时收发吗

使用特权

评论回复
17
updownq| | 2023-5-11 18:29 | 只看该作者
stm32的spi1的dma可以和usart3的dma一起用么

使用特权

评论回复
18
tabmone| | 2023-5-11 18:49 | 只看该作者
STM32串口通信三种方式(查询,中断,DMA)各自的特点以及实现方法是什么

使用特权

评论回复
19
ingramward| | 2023-5-11 19:55 | 只看该作者
stm32单片机dma 通道之间有影响吗

使用特权

评论回复
20
earlmax| | 2023-5-11 22:08 | 只看该作者
DMA方式发送 一个数 ,程序怎么写?

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1414

主题

3814

帖子

5

粉丝