描述
大部分图片来源:正点原子HAL库课程
专栏目录:记录自己的嵌入式学习之路-CSDN博客
目录
0 概述
1 原理(用于个人理解的,非常重要!)
1.1 DMA请求
1.2 循环模式与DMA请求的关系
1.3 传输完成中断(TC)发生的时机
2 硬件情况
3 作用
4 DMA的优先级
4.1 软件优先级
4.2 硬件优先级
4.3 优先级的作用
5 数据流方向
5.1 外设到存储器
5.2 存储器到外设
5.3 存储器到存储器
5.4 配置
6 外设/存储器地址增量模式
7 传输的数据宽度
8 传输结束时机(循环与否)
8.1 中断的不同
8.2 对循环模式的支持
9 其他特性
9.1 最大传输数
9.2 内存到内存的传输
10 相关HAL库函数
11 重要结构体
12 配置步骤
13 注意事项
0 概述
DMA,全称为:Direct Memory Access,即直接存储器访问。
DMA牛逼的地方应该就是将一个外设的数据内容直接映射到内存中的一堆数据中,通过读数据就读到了外设通过GPIO输入的某些值,就不需要使用CPU费劲巴拉地去访问寄存器获取这些值。
1 原理(用于个人理解的,非常重要!)
1.1 DMA请求
如果外设想要通过DMA来传输数据,必须先给DMA控制器发送DMA请求,DMA收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且DMA控制器收到应答信号之后,就会启动DMA的传输,直到传输完毕。
1.2 循环模式与DMA请求的关系
我的理解是:
在非循环模式下,外设发出了DMA请求,若此时CNDTR寄存器的值不为0,则DMA根据设定的数据宽度执行一次数据搬运;而如果CNDTR寄存器的值已经为0了,DMA将不会响应外设的数据搬运请求。
而在循环模式下,CNDTR寄存器清零后会重新置为用户设定的值,并继续响应外设的DMA请求。
1.3 传输完成中断(TC)发生的时机
CNDTR寄存器的值减到0的时候。并不是一个数据宽度搬运完毕的时候。
2 硬件情况
STM32F1有两个DMA控制器:DMA1和DMA2。其分别拥有7个和5个通道。各通道对应不同的外设,见下图:
- 可在《STM32F10xxx参考手册》查询。
3 作用
DMA传输将数据从一个地址空间复制到另一个地址空间。
其无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。减轻了CPU的负担。
4 DMA的优先级
4.1 软件优先级
程序可设置DMA通道的软件优先级,有最高、高、中、低四个优先级。
4.2 硬件优先级
在软件优先级相同的情况下,硬件的通道编号越小,优先级越高。且DMA1优先级比DMA2高。
4.3 优先级的作用
多个请求通过逻辑或输入到DMA控制器,只能有一个请求有效。所以需要优先级处理这个情况。
5 数据流方向
5.1 外设到存储器
当我们使用从外设到存储器传输时,以ADC采集为例。DMA外设寄存器的地址对应的就是ADC数据寄存器的地址,DMA存储器的地址就是我们自定义的变量(用来接收存储AD采集的数据)的地址。方向我们设置外设为源地址。
5.2 存储器到外设
当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。DMA外设寄存器的地址对应的就是串口数据寄存器的地址,DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。
5.3 存储器到存储器
当我们使用从存储器到存储器传输时,以内部FLASH向内部SRAM复制数据为例。DMA外设寄存器的地址对应的就是内部FLASH(我们这里把内部FALSH当作一个外设来看)的地址,DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部FLASH的数据)的地址。方向我们设置外设(即内部FLASH)为源地址。跟上面两个不一样的是,这里需要把DMA_CCR位14:MEM2MEM:存储器到存储器模式配置为1,启动M2M模式。
5.4 配置
寄存器中通过MEM2MEM、DIR两个寄存器对数据的流向进行配置:
6 外设/存储器地址增量模式
就是传输过程中有一个指针指向外设/存储器,在写完一个后自动往后移动指针。对于串口传输的情况,由于存储器是需要传输完一个自动往下再传输,因此应该使能存储器地址增量模式。而对于外设串口,因为只是往其DR寄存器中传输数据,并没有指针移动的操作,因此就不需要使能外设地址增量模式。
野火HAL库教程的解释:以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地址指针就应该加1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。
7 传输的数据宽度
在寄存器可以设置存储器和外设的数据宽度,一般来说,两者应该设置为一致的,一般以外设的为准。例如串口的数据寄存器是8位的,因此外设和存储器的数据宽度都要设置为8位。
当实在是需要源数据和目标数据宽度不一致的情况时,需要查阅《STM32F10xxx参考手册》,如下图的表格。
8 传输结束时机(循环与否)
数据什么时候传输完成,我们可以通过查询标志位或者通过中断的方式来鉴别。每个DMA通道在DMA传输过半、传输完成和传输错误时都会有相应的标志位,如果使能了该类型的中断后,则会产生中断。有关各个标志位的详细描述请参考DMA中断状态寄存器DMA_ISR的详细描述。
传输完成还分两种模式,是一次传输还是循环传输,一次传输很好理解,即是传输一次之后就停止,要想再传输的话,必须关断DMA使能后再重新配置后才能继续传输。循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输,不断的重复。具体的由DMA_CCR寄存器的CIRC循环模式位控制。
8.1 中断的不同
在循环模式下,每次DMA传输完成都会触发对应通道的中断服务函数;
⚠️但在非循环模式下,DMA的公共中断服务函数会调用__HAL_DMA_DISABLE_IT将中断失能,因此如果需要其始终起作用,就需要在中断服务函数中加上__HAL_DMA_ENABLE_IT。
8.2 对循环模式的支持
⚠️存储器到存储器的数据流向是不支持循环模式的。
9 其他特性
9.1 最大传输数
65535个字节。
在寄存器中,有一个寄存器存放当前剩余传输数目。
在非循环模式下,用户使能DMA并对该寄存器进行赋值N后,DMA会发送N个字节的数据,等N被倒数到0后,数据发送完毕。要再次发送就需要重新使能DMA并赋值N。
若在循环模式下N在减到0后自己就会循环回去N,从而循环发送。
9.2 内存到内存的传输
F1系列所有的DMA及DMA通道都支持内存到内存的传输。
10 相关HAL库函数
11 重要结构体
12 配置步骤
- 非循环模式下,发送数据的准备步骤
- __HAL_DMA_DISABLE(…);
- 设置数量寄存器,如:dma_handle.Instance->CNDTR = len;
- __HAL_DMA_ENABLE(…);
- 查询通道状态的标志位
- __HAL_DMA_GET_FLAG(&g_dma_handler, DMA_FLAG_TC1)
- 这里的TC指的就是Transmit Complete,发送完成。
- 清除通道状态的标志位
- __HAL_DMA_CLEAR_FLAG(&g_dma_handler, DMA_FLAG_TC1)
- 失能DMA
- HAL_UART_DMAStop()
- ⚠️⚠️⚠️__HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD__, __DMA_HANDLE__)
- 该宏函数用于将外设和DMA连接起来,让DMA知道处理哪个外设的数据,其中:
- __HANDLE__为外设的句柄,__PPP_DMA_FIELD__为外设句柄中DMA句柄的名称(进入外设句柄的定义里能看到,如下图例子),__DMA_HANDLE__为DMA的句柄。
- 需要注意的是:__HAL_LINKDMA宏函数的最后一个参数(即DMA句柄)无需加地址符,因为其内部就有了,因此调用的形式为:
__HAL_LINKDMA(&adc_handle, DMA_Handle, dma_handle);
- ⚠️⚠️⚠️HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
- HAL_DMA_Start_IT函数的SrcAddress和DstAddress分别表示DMA数据的来源和目标地址,一般就是外设的DR寄存器或者是变量或数组。若是要传入数组的话会相对比较麻烦,见下图array是个数组:
也可以这样转:
(uint32_t)&array - 调用该函数时最好SrcAddress和DstAddress都强转成uint32_t再传入,像上图那样,否则会有警告。
- HAL_DMA_Start_IT函数的SrcAddress和DstAddress分别表示DMA数据的来源和目标地址,一般就是外设的DR寄存器或者是变量或数组。若是要传入数组的话会相对比较麻烦,见下图array是个数组:
13 注意事项
- HAL_DMA_Init函数没有对应的MSP初始化函数,因此其时钟的开启代码要自己放好执行。
- 如果有外设需要使用DMA,就要确认其使用的是DMA1还是DMA2,还要确认其使用的通道,具体就是查15.2里面的那个表就可以了。
- 在设置DMA的参数(如传输数目CNDTR)时,需要确保DMA不在传输状态,常用while配合CCR的位0判断,或使用while配合DMA的传输完成TC标志做判断: