STM32学习笔记五
STM32 DMA
简介
DMA的概念
直接存储器访问 (DMA) :用于在外设与存储器之间以及存储器与存储器之间进行高速数据传输。DMA传输过程的初始化和启动由CPU完成,传输过程由DMA控制器来执行,无需CPU参与,从而节省CPU资源,提高利用率。
DMA数据传输的四个要素:
- 传输源 :DMA数据传输的来源
- 传输目标:DMA数据传输的目的
- 传输数量:DMA传输数据的数量
- 触发信号:启动一次DMA数据传输的动作
DMA在很多情况下都能够帮助提高系统的性能,尤其是在处理大量数据传输时。它可以用来传输数据到/从外设,例如ADC、DAC、SPI和UART等,也可以用来进行内存间的数据传输。
DMA传输方式
- 外设到内存
- 内存到外设
- 内存到内存
- 外设到外设
DMA的循环模式和正常模式
BDMA主要有两种模式,一个是Normal正常模式,传输一次后就停止传输;另一种是Circular循环模式,会一直循环的传输下去,即使有DMA中断,传输也是一直在进行的。
这两种模式各有用途。
Normal正常模式
适合用于单次传输,比如存储器到存储器的数据复制粘贴,又比如串口的数据单次发送,下次还需要发送的时候,使能下即可。
Circular循环模式
适合用于需要连续传输的场合,比如定时器触发BDMA实现任意IO的PWM输出。
指针递增模式
源地址指针递增 目标地址指针同步递增
适用于:内存到内存 外设到内存
源地址递增 目标指针不动
适用于 内存到外设 如 串口
HAL库函数介绍
HAL_DMA_Start : 开始DMA传输
1 | HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength) |
第一个参数: DMA句柄
第二个参数: 源内存地址
第三个参数: 目标内存地址
第四个参数: 传输数据的长度
需要乘以siezof(uint32_t)
__HAL_DMA_GET_FLAG: 判断DMA状态
1 |
参数一: DMA句柄
参数二:要查看的状态
有以下几种状态:
DMA_FLAG_TCIFx:传输完成标志。
DMA_FLAG_TIFx:半传输完成标志。
DMA_FLAG_EIFx:传输错误标志。
DMA_FLAG_DMEIFx:直接模式错误标志。
DMA_FLAG_FEIFx:FIFO错误标志。
这里的X需要换成DMA对应的通道
任务1:内存到内存
任务要求:将数组A的内容 通过DMA搬运到数组B 并在串口打印数组B
配置DMA
选择第二个选项卡 MenToMem (内存到内存)
点击Add
配置模式为 正常模式
使用DMA1 通道0
选择指针的递增模式 同步递增
Data Width 为数据宽度 这里使用word 字
其他 暂时不要管
在 STM32F103 系列没有 Fifo阈值的设置
代码编写:
以下操作仅在H7系列需要操作
F1 F4 系列不需要
attribute((section(".DisplayBuffer"))) 这段话只在H7 需要加
H7 使用的时候可以在DMA.c 文件里 将Filo 关闭
hdma_memtomem_dma1_stream0.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
我使用的是 H7 系列 DMA比较复杂 需要额外设置 如下: 其他系列 F1 F4 不需要做
这是总线访问权限的图
可以看到 DMA1 DMA2 可以访问SRAM1 -SRAM3 的内容
所以需要 指定内存
我使用的是Clion+CubaMX 需要做如下设置
首先找到xxx.Id添加如下代码
1 | ._User_data : |
准备工作完成 指定变量存储去见下方
准备原始数据
1 | /* USER CODE BEGIN PM */ |
发送数据
1 | /* USER CODE BEGIN 2 */ |
程序结果:
任务2:内存到外设
使用DMA 将内存的数据发送到串口
使用到的库函数
串口DMA方式发送函数:HAL_UART_Transmit_DMA
Cuba MX的设置
打开Usart1 的DMA 所有参数默认即可 这里 Mode 选择单次 只发送一次
最后的效果图 可以看出两种 mode的区别 这里 像是串口类型的只需要发送一次选择Normal 就可以
像adc需要持续采集数据的需要设置为循环 Circular
检测NVIC的 DMA1 的中断是否打开
程序编写
准备数据
1 | /* USER CODE BEGIN PM */ |
发送数据
1 | /* USER CODE BEGIN 2 */ |
程序效果
Mode 为单次
Mode 为循环
任务3: 外设到内存
使用到的库函数
串口DMA方式接收函数:HAL_UART_Receive_DMA
**获取未传输数据个数函数:__HAL_DMA_GET_COUNTER**
**关闭DMA数据流:__HAL_DMA_DISABLE**
任务目标
实现不定长数据的收发
任务内容
利用串口调试助手,从PC上发送任意长度的字符到开发板,开发板收到后原样发回到PC。
设计思路
使能IDLE中断
在串口1的中断服务程序USART1_IRQHandler中添加对IDLE中断的判断,该函数位于stm32h7xx_it.c文件;
设置传输模式为普通模式,启动DMA传输。串口一旦接收到数据,则触发DMA操作,将数据存放到用户定义的接收缓冲区;
当一帧数据发送完成后,线路处于IDLE状态,将触发IDLE中断,调用IDLE中断回调函数,设置数据接收完成标志;
主程序检测到接收完成标志置位后,将接收的一帧数据原样发回到PC,并禁能DMA,以触发DMA中断。DMA中断将调用接收中断回 调函数,在回调函数中重新启动DMA传输
CubaMx 的 配置
添加接收的DMA
检查中断 将 串口中断 和 DMA的中断打开
程序编写
准备数据接收区
在main.c下编写
1 | /* USER CODE BEGIN PV */ |
在 main.h 下编写
1 |
在stm32h7xx_it.c 下编写
1 | /* USER CODE BEGIN PV */ |
准备接受数据
开启空闲中断 在main.c 文件下
H750 上电就进入空闲中断 就会打印一次 长度为0 的数据 一下两种方法可以解决
while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)==RESET)
或
__HAL_UART_CLEAR_IDLEFLAG(&huart1); __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
1 | /* USER CODE BEGIN 2 */ |
开始数据接受 在stm32h7xx_it.c 下编写
1 | void USART1_IRQHandler(void) |
数据处理
在main.c 编写
1 | /* Infinite loop */ |
程序效果
参考文献:
https://blog.csdn.net/wallace89/article/details/117001405
(132条消息) STM32H743不能使用DMA的问题_Jaken5213的博客-CSDN博客_stm32h743dma指定地址
(132条消息) STM32_H750串口接收不定长数据(IDLE+DMA)及初始化之后便进入idle中断的解决方法_MY_QuinTA的博客-CSDN博客_h750 串口空闲+dma 一直进接收中断