基于STM32的DMA共性技术分析及应用
2023-08-10奚圣鑫王宜怀李跃华
奚圣鑫 王宜怀 李跃华
1(苏州大学计算机科学与技术学院 江苏 苏州 215000) 2(南通大学信息科学与技术学院 江苏 南通 226019)
0 引 言
直接存储器访问(DMA)是一种数据传输的方式[1],它可以将数据不经过CPU直接从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器与存储器之间的高速传输。在嵌入式微处理器的实际应用过程中,无时无刻都需要数据的传输,我们正常的方法都是查询方式进行编程来控制I/O的输入输出,或者通过中断来处理数据传输[2]。虽然采取中断来控制I/O比查询方式控制I/O更加有效和省时,但是仍然需要CPU的干预才能实现存储器与I/O模块之间的数据进行传输。任何I/O设备与存储器间的数据传输必须通过CPU,因此大大降低了CPU的效率。这两种形式的I/O存在以下两种缺点:(1) I/O数据传输的速度受处理器性能和I/O设备所提供服务速度的限制;(2) 处理器负责控制I/O数据的传输时必须要执行一些指令,这就浪费了CPU的时间。为了弥补这两种数据传输方式的缺点,可以采用嵌入式处理器中的DMA数据传输方式。当使用DMA传输数据时,这个动作本身是由DMA控制器(DMAC)来实现和完成的,无须CPU的介入和控制,也就不需要CPU先把所有数据复制到暂存器,然后把它们再写回到目的地址去[3]。同时也没有中断处理I/O方式那样需要保留现场和传输完成后的恢复现场的过程。DMA方式的数据传输与查询方式访问I/O和中断驱动I/O相比,具有传输速度快、I/O响应时间短、CPU额外开销小的明显优点[4]。现在越来越多的嵌入式微处理器都具有DMA技术以提供外设和存储器之间的高速数据传输,但却很少会去使用它。本文将基于STM32L431RC芯片来介绍DMA控制器的基本原理及技术,在此基础上提供一种DMA构建的封装方式,并将其与UART结合提供具体的应用实例。
1 DMA控制器原理
1.1 DMA功能介绍
STM32中的DMA控制器(DMAC)包含2个DMA端口(DMA1,DMA2),每个端口都拥有7个通道,每个通道都可以执行DMA传输。其传输数据量是可编程的,最大可以达到65 535[5]。DMAC和STM32核心共享系统的数据总线,在DMA传输时,DMA请求会暂停CPU访问系统总线若干个周期,此阶段由DMAC直接掌管总线,因此,存在着一个总线控制权转移过程。此过程是由总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽[6]。另外每个端口还包含一个仲裁器、中断接口和内部一些寄存器组,其结构如图1所示[7]。
图1 DMAC结构
DMAC可以将数据从源地址搬移到目的地址,在STM32中DMA操作存在三种操作模式。即存储器到外设、外设到存储器、存储器到存储器。一般的DMAC至少应该具备以下的基本功能:
(1) 能够接收外部设备发出的DMA请求,并向CPU提出总线占用请求。
(2) 在CPU对DMAC配置结束后,DMAC可以代替CPU对总线进行控制。
(3) DMAC可以独立地确定传输数据的起始地址和目的地址以及传输长度。
(4) DMA传输过程中,在发生传输错误时需要进行报错处理。需要独立判断是否传输成功,并且在传输成功后能够发出中断信号,释放对总线的控制权。
当然对于功能复杂的DMAC,除了完成以上功能之外,还会有其他的功能。一般的DMAC在传递完成一次连续地址的数据之后需要重新进行配置才能进行下一次的数据传输,但是复杂的DMAC可以内部增加逻辑加减,在传输数据时无论有多少个非连续的数据块,可以只配置一次即可完成。一次完整的DMA传输过程必须经过请求、响应、传输和结束四个过程,其工作流程如图2所示。
图2 DMA控制器工作流程
(1) CPU首先会将DMAC初始化。当存储设备I/O向DMAC发出请求DMA方式进行数据传输请求。
(2) DMAC向CPU发出总线请求,然后CPU释放总线控制权,并通过DMAC通知I/O接口开始DMA传输。
“雅颂”之声被古人视为“正声”“正体”[11]。《周颂》是人类理性精神开始自觉的产物,虽然杂糅着宗教情绪,但是非理性色彩并不浓郁。与《商颂》的“雅颂”思想拥有崇奉上帝的迷狂观念不同,《周颂》的“雅颂”思想具有伦理化特征。
(3) DMAC获得总线控制权后,即可进行数据传输。整个数据传输过程主要可以分为两个阶段,分别为从源地址中读取数据和写回数据到目的地址。读操作阶段数据从源地址中被搬运到数据总线上,然后再存放到DMAC内部FIFO中用来缓存数据。写操作是将FIFO中的缓存数据写到目的地址上去。DMAC在同一时刻只能进行一种操作。
(4) 当完成数据传输后,DMAC立即释放总线控制权,至此,整个DMA操作结束。
1.2 DMA寄存器描述
DMAC中每个通道都有相应的DMA配置寄存器(DMA_CCRx[1,2,…,7])、外设地址寄存器(DMA_CPARx[1,2,…,7])、存储器地址寄存器(DMA_CMARx[1,2,…,7])、传输数据数量寄存器(DMA_CNDTRx[1,2,…,7]),此外还有DMA中断状态寄存器(DMA_ISR)和中断标志清理寄存器(DMA_IFCR)。每个寄存器的功能如表1所示。
表1 DMA寄存器列表
其中最重要的就是DMA配置寄存器,在使用DMA传输之前必须要对其进行初始化。15到31位保留,其余各位的含义如表2所示。
表2 DMA配置寄存器各位表
外设地址存储器指定当传输时数据的地址是外设时的地址;存储器地址寄存器指定当传输时数据的地址是存储器时的地址。传输数量寄存器指定传输次数,每一次“先读后写”传输后递减,直至为0传输结束,但是如果在配置寄存器中设置为循环模式时会自动再加载之前的设定值。
1.3 DMA构件封装
软件构件技术的出现,为实现软件构件的工业化生产提供了理论与技术基石[8]。软件构件的封装性、可移植性和可复用性是软件构件的基本特性,采用构件技术设计软件,可以使软件具有更好的开放性、通用性和适应性。在此思想基础上,以STM32L431芯片为基础,提出DMA构件基础功能封装规则。
首先对DMA进行要点分析,即分析应该设计哪几个函数及函数入口参数。前面已经分析了DMA传输数据的完整步骤,所以通用的DMA构件必须封装DMA初始化函数、传输数据函数、关闭DMA函数。DMA构件由dma.h和dma.c两个文件组成,如果想要使用,只需要将这两个文件加入到工程项目中即可。
1) DMA模块初始化(DMA_Init)。使用DMA功能之前必须对其进行初始化,所以初始化函数必须提供。由于DMA端口有两个,每个端口有7个通道,且DMA传输模式有三种,是否循环输入。因此DMA初始化函数的参数为DMA端口、通道数、传输方向、是否循环输入。这样DMA初始化原型可以设计为:
2) DMA传输开始(DMA_Start)。在DMA初始化之后就可以开始DMA传输,此时需要传输数据的源地址、目的地址、数据长度,所以参数必须要有这三个,同时开始函数还要对DMA模块进行使能,只有使能之后才能正常使用。这样DMA传输开始原型可以设计为:
void DMA_Start(uint32_t SrcAddr,uint32_t DstAddr,uint32_t Length)
3) DMA模块关闭(DMA_DeInit)。在使用DMA传输数据之后,要将DMA模块关闭,反使能模块,重启传输数据寄存器以及清除所有的包含中断在内的标志位,所以此函数不需要传递参数,原型可以直接设计为:
void DMA_DeInit()
这三个函数基本满足了对DMA操作的基本需求。还有中断使能与禁止等函数可以根据需求添加和使用。
2 DMA数据传输应用实例
在软硬件的调试过程中,经常需要将数据通过串口打印出来。使用串口进行传输数据,这时就可以利用DMA技术进行数据传输。STM32L431RC芯片不仅提供了DMA基本功能,还提供了DMA请求复用器(DMAMUX)。请求复用器可以在外设和DMA控制器之间重新配置DMA请求[9],从而实现数据直接从存储器到串口UART、SPI或者I2C等外设,也可以从一个外设到另一个外设。
本文提供了DMA和串口UART复用的应用实例,可以直接通过串口将微控制器内存内数据直接发送给上位机,从而用来模拟我们日常生活中的printf函数的功能。printf函数有个缺陷,就是花费的时间太多了,虽然在日常使用中我们绝大部分人没有发现,但是在一些大型的项目对数据需要大量传输时,不能每次都让微控制器等待来显示串口,所以作者便考虑到能否用DMA来取代printf。但是用DMA来执行串口的数据打印实际时间点是会比使用printf的时间点会晚个几毫秒,因为DMA传输数据是在CPU完成当前时间周期之后把系统总线让给DMAC然后才开始数据传输,但是printf传输数据是发起后立即执行。但是这几毫秒在我们实际应用中对我们来说没有任何影响,但是对CPU来说,DMA传输并没有占用CPU,从而CPU可以运行更多的计算。DMA和串口UART复用的函数流程如图3所示。
图3 DMA串口复用函数流程
程序实现了两种串口传输以存储器为起始地址的28个字节的数据到上位机上。其核心代码如下:
int main(void)
{
uint8_t SourceData[30]=″this is a dma Sample program″;
//起始地址存放源数据
gpio_init(TIME_GPIO,1,1);
//初始化GPIO引脚
gpio_set(TIME_GPIO,0);
//GPIO输出低电平
DMA_UART(&SourceData,UART_Debug,28);
//DMA串口传输数据
gpio_set(TIME_GPIO,1);
//GPIO输出高电平
delay(1000);
//延时1 s
gpio_set(TIME_GPIO,0);
//GPIO输出低电平
printf(SourceData);
//printf传输数据
gpio_set(TIME_GPIO,1);
//GPIO输出高电平
}
经反复通过对TIME_GPIO引脚高低电平状态持续时间测试得出,在使用DMA复用串口发送一个包含了三十个字符的字符串只需要大约25 μs的CPU耗时,而printf则需要大约800 μs的CPU耗时。
3 结 语
本文实现了在STM32L431的微控制器上的DMA数据传输,并且发现在结合UART、SPI或I2C等设备时,会比我们正常使用这些设备更加方便。因为我们在使用这些设备的同时CPU会花费大量的时间在数据的传输上,如果我们采用了DMA来进行数据传输,则会大量减少CPU的工作任务,从而让CPU去执行其他工作。实践证明,在利用基于DMA共性技术的基础上封装的DMA构件,可以将繁重的数据传输工作通过DMA控制器来完成,从而提高了CPU的数据处理能力和CPU的工作效率。