基于Cubemx与HAL库串口DMA收发经验分享
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 至少可以得到一半的系统总线带宽。https://shequ.stmicroelectronics.cn/data/attachment/forum/202304/02/182013vfjczdsx9zsbnojg.png
[*]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 控制器,此时只有一个请求有效。
https://shequ.stmicroelectronics.cn/data/attachment/forum/202304/02/182108wyuzsj8tsjsj4jux.png
STM32 的 DMA 有以下一些特性:●每个通道都直接连接专用的硬件 DMA 请求,每个通道都同样支持软件触发。这些功能 通过软件来配置。● 在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如 在相等优先权时由硬件决定(请求 0 优先于请求 1,依此类推) 。● 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和 目标地址必须按数据传输宽度对齐。● 支持循环的缓冲器管理● 每个通道都有 3 个事件标志(DMA 半传输, DMA 传输完成和 DMA 传输出错),这 3 个 事件标志逻辑或成为一个单独的中断请求。● 存储器和存储器间的传输● 外设和存储器,存储器和外设的传输● 闪存、 SRAM、外设的 SRAM、 APB1 APB2 和 AHB 外设均可作为访问的源和目标。● 可编程的数据传输数目:最大为 65536
STM32串口DMA使用详解本次我们使用的硬件环境是之前开源的板子,falling-star board,使用串口1。
cubemx配置关于时钟配置、串口基本配置请参看:cubemx的正确打开方式一文接下来直接进入配置串口DMA:选择串口1,基本参数如图,都是老生常谈了,easy~
https://shequ.stmicroelectronics.cn/data/attachment/forum/202304/02/182013rnobbcmz7770isnd.png
选择DMA Settings,主要有一下几个地方,基本上不需要改动,根据自己的使用情况确认即可,需要注意的是,发送和接收并不是一定要成对出现的,可以只选择DMA发送或者DMA接收
https://shequ.stmicroelectronics.cn/data/attachment/forum/202304/02/182013rnctycxlne2zcqqr.png
中断设置,DMA中断可以配置,可以不配置,同样也是根据自己的实际需求情况,串口中断需要配置,下面会用到,优先级根据自己任务的优先级确定,分好“轻重缓急”即可
https://shequ.stmicroelectronics.cn/data/attachment/forum/202304/02/182012soiokfrzjooiwioq.png
配置非常简单,主要是在此前串口功能基础上添加DMA功能,over
串口DMA代码设计串口DMA源码API介绍上面提到的配置项,都封装在一个结构体里面
[*]/** @defgroup DMA_Exported_Types DMA Exported Types
[*]* @{
[*]*/
[*]
[*]/**
[*]* @briefDMA 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的初始化,开始发送函数
https://shequ.stmicroelectronics.cn/data/attachment/forum/202304/02/182013g03zeqbqat24sjmz.png
串口中关于DMA的部分主要有这几个函数,还有一些关于中断、DMA标志等的一些宏定义,就不在一一列举了,需要用的时候大家知道去库函数中去找就可以了
https://shequ.stmicroelectronics.cn/data/attachment/forum/202304/02/182012ddgjfgpkdgdggb5p.png
https://mmbiz.qpic.cn/mmbiz_png/xKL2b0whmJMxAWWN8KhmU9AHAnQ9d6Q4LBCesYZYaMgEfyfSGjoCrAAdEBx2j5tiaFK3iaqUu3wZBPyTKPae8eMA/640?wx_fmt=png串口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流水灯,是完全不受发送数据影响的,效果就不放了,相信小飞哥...
https://shequ.stmicroelectronics.cn/data/attachment/forum/202304/02/182011a2m3pqr6m6m2323p.png
不信你看~
https://shequ.stmicroelectronics.cn/data/attachment/forum/202304/02/182011a49jn3m5b81axx47.png
https://mmbiz.qpic.cn/mmbiz_png/xKL2b0whmJMxAWWN8KhmU9AHAnQ9d6Q4V0z38TRib1b2iamhbuQXzuaE8e5yFzLSOnjIdMMT3yuKxq2Lyy8bnWZA/640?wx_fmt=png发送的是非常简单的,好像这里也没体现出来,使用DMA发送有什么好处,其实在LCD驱动的时候,当有图片等大数据量的数据需要传输的时候,使用DMA是种非常好的方式
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);
[*]}
复制代码
[*]/**
[*]* @briefDMA接收函数
[*]* @paramhtim 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 */
[*]}
复制代码
演示效果:
https://shequ.stmicroelectronics.cn/data/attachment/forum/202304/02/182012osiptrundiiz7xuu.png微信图片_20230402181811.png (529.25 KB, 下载次数: 0)下载附件昨天 18:20 上传
DMA是不是只支持硬件iic? Stahan 发表于 2023-4-4 23:55
DMA是不是只支持硬件iic?
模拟的要用cpu控制时序。当然不行 DMA功能能够实现串口接收数据直接存储到Flash中吗 stm32usb和串口dma哪个快 stm32 dma串口发送和接收怎么配置 如何使stm32用dma进行串口发送 stm32dma填入的内容怎么修改 用dma传输adc数据怎么为0 使用dma接受串口数据,怎样清空串口buffer STM32的DMA串口循环模式如何设置发送周期? 怎样将数据放入一个缓冲区stm32 stm32 串口 能用dma同时收发吗 stm32的spi1的dma可以和usart3的dma一起用么 STM32串口通信三种方式(查询,中断,DMA)各自的特点以及实现方法是什么 stm32单片机dma 通道之间有影响吗 DMA方式发送 一个数 ,程序怎么写?
页:
[1]
2