电子说
考虑到DMA是一个AHB Master设备,可以同处理器内核一样,主动向总线发起传输请求,将“小脏手”伸向各总线上挂载的各外设模块,因此,我更愿意把DMA看做是处理器核心服务的一部分,甚至把它当成一个小核或者协处理器都不为过。
DMA可以在CPU之外,捕获到触发信号后,自行搬运数据从指定地址到另一个指定地址,并且还可以根据预先的配置自动计算下一次搬运的地址。使用DMA可以有效地节约CPU处理海量数据传输的负载。可以想见,如果使用中断方式处理通过外设发送或者接收数据,CPU将会在频繁切换中断服务之间花费大量的时间。
另外,DMA从DMAMUX获取的触发源,能够实现的自动读写的操作,若是某些读操作或者写操作能够产生额外的触发事件,还可以传递触发,形成触发链,最终可实现一些完全不需要硬件干预的自动化任务。总之,DMA真心是一个功能丰富的模块。
本文将介绍YTM32平台上DMA的工作机制,对关键概念展开讲解。
YTM32(以YTM32B1ME05为例)微控制器上集成的DMA控制器可以支持16个通道,并且搭配了一个多达128个选项的DMA MUX模块,可以对接任何一个通道。其中多个通道是复用同一个DMA控制器的,并且共用同一个数据搬运引擎。DMA MUX管理了可以触发DMA通道的硬件事件,每款芯片可能都不一样(YTM32B1MD14中的DMAMUX只有64个选项),在具体使用时,需要从具体的芯片手册中查表。
还需要特别注意的是,目前YTM32平台上的DMA尚未支持异步时钟模式,它使用core clock驱动,仅能在普通模式下工作,在休眠模式、深度休眠以及更低功耗的休眠模式下,均停止工作。
本DMA控制器通过多个通道的传输任务描述符(CTS)管理搬运数据的过程,并且还支持链接模式,即将多个传输任务描述符连链接在一起,形成传输任务链。
DMA控制器是一个AHB总线主机,但仍同普通的外设一样,作为一个APB总线从机,被配置成合适的工作模式。DMA控制器通过DMAMUX,可以直接收集来自片上其他外设模块发出的触发信号,进而触发DMA在地址空间搬运数据的过程。如图x所示。
图x DMA控制器的系统框图
需要注意的是,DMA的搬运过程是在地址空间内操作的,可以是从内存到内存,从外设到外设,在内存与外设之间等,对于DMA而言,只是搬数,至于数据映射到物理设备是外设还是内存,均由总线负责落实。
相对于有的DMA控制器将触发信号和搬运数据源头地址或目标地址绑定的设计,YTM32的DMA控制器将触发信号和搬运任务所使用的地址相互独立,例如,当某个定时器模块产生的触发信号触发了DMA的一个搬运数据的任务(通道),这个任务可以将ADC转换结果的数据搬运到内存中。这其中,定时器和ADC是没有直接关联的。
YTM32的DMA的各个通道,可以看做是各自独立的传输任务,每个任务都有自己的触发条件、对触发条件的响应方式、搬运数据的源地址和目的地址、搬运数据的带宽、搬运数据的数量、每次搬运完成后对搬运过程进行调整的策略等,如此看来,DMA控制器就是这些独立任务的调度器,当多个任务被同时触发时,以一定的调度策略安排他们依次运行。
DMA通道对应的这些独立的搬运任务,在DMA引擎的建模中,被称为CTS(DMA Channel Transfer Structure),其结构如图x所示。
图x DMA通道的传输任务描述符
这个结构的内容并不是以指针的方式存放在SRAM中,而是直接做在寄存器结构里,可以在DMA的寄存器清单中找到与之一一对映的寄存器。如图x所示。
图x CTS对应的每个通道各自下辖的一组寄存器
但CTS也可以存放在RAM中,若配置了DMA_CTS_CSR[RLDEN]=1
,则在大循环完成后,直接从寄存器DMA_CTS_DTO
(原来存放的是地址偏移量)存放的指针进行索引,搬运整个CTS结构体的内存覆写到CTS对应的寄存器中。
除此之外,只是借鉴CTS结构中相关的寄存器,去配置DMA传输任务的参数即可,不必受限于CTS的抽象数据结构。
YTM32的DMA控制器为每个DMA搬运任务设计了两种触发方式:软件触发和硬件触发。其中,软件触发可由CPU直接向DMA控制器的寄存器写数(DMA_CTSn_CSR[START]
),主动启动传输过程;硬件触发使用预设的硬件触发信号,当来自外设的硬件触发信号通过DMAMUX到来之时,自动启动DMA搬运任务开始搬数。DMA的每次触发,执行一次小循环的搬运过程,一个大循环可以包含多个小循环的,因此一个大循环的搬运任务可能会需要多次触发才能完成。(关于大小循环的概念,可见楼下)
DMA的软件触发是通过软件写各通道的寄存器位DMA_CTSn_CSR[START]
,或者寄存器DMA_START
中对应通道的控制位实现的,每写1次就发出一个触发信号。每次触发,执行一次小循环(one trigger loop)的搬运过程,TCNT寄存器中的计数器减1。
特别注意,软件触发是直接作用于DMA控制器的,不必配置DMAMUX的那个always_on
的选项。但由此也可知,哪怕有可用的硬件触发通过DMAMUX输入到DMA控制器,软件触发也可以生效。相当于是,软件触发和DMAMUX导入的硬件触发信号相或,然后统一输入到DMA控制器。
DMA的硬件触发信号来自于DMAMUX
,而DMAMUX
则可以从众多触发信号的源中选择其中一个适用于某个指定的通道(寄存器DMA_CHMUXn
)。具体选项可在芯片手册中查阅,如图x所示。
图x 从手册中查阅DMAMUX选项
DMAMUX选中的触发信号,还需要经过一个REQEN
的门控开关(寄存器DMA_REQEN
中对应通道的控制位),才能顺利进入DMA引擎。因此,每次使用DMA开始传输之前,如果要使用外部的硬件触发源,必须确保打开这个门控开关。另外,每个DMA通道的传输描述符中的寄存器位DMA_CTS_CSR[DREQ]=1
还可以控制在每个大循环传输完成之后,自动关闭这个门控开关。如果DMA_CTS_CSR[DREQ]=0
,则这个门控开关在大循环传输完毕后仍会保持打开。
这里提到的硬件触发信号,是直接来自于外设的DMA触发信号,通常会伴随着这些外设的某些事件的发生,大多同时也可以触发中断。以LINFlexD为例,有对应的DMA触发信号的开关,如图x所示。
图x SPI外设模块的使能DMA请求控制位
一个最完整的DMA传输,可以包含多次触发,而每次触发,会引起连续地搬运一块数据(可以是连续的多个字节)。以此,完整的DMA搬运有大循环(Major Loop)
和小循环(Minor Loop)
的概念,大循环包含小循环。
YTM32的手册中使用了Transfer Loop
和Trigger Loop
的名字:
从手册的描述中可以获知,Transfer Loop
描述的是一次触发(one trigger)执行的包含若干个transfer的搬运过程,而Trigger Loop
可以包含多个触发(many triggers),对应大循环和小循环。如图x所示。
图x DMA搬数过程中的大循环和小循环
小循环搬运的字节数,由各DMA通道的BCNT
寄存器指定,它本身也是一个递减计数器,每传输一个字节就减1,减到0时就停止搬运。
大循环的包含的小循环的次数(不是字节数,是对触发信号的计数),由各DMA通道的TCNT_KDDIS[TCNT]
寄存器字段指定,它本身也是一个递减计数器,每执行一次小循环(触发)就减1,减到0时就停止。特别注意,此处的大循环管理的仅仅是触发,而不是传输内存块,如果使用多个传输任务描述符链接起来的传输任务描述链表,则每个任务描述符(可能在不同的地址块和传输模式搬运数据)都对应属于各自的触发次数(同一个通道的触发源仍为同一个)。
大循环执行一半和完毕时都有对应的标志位(DMA_CHTLHDIF
和DMA_CHTLDIF
),这里有个特别的设计,只有启用DMA传输通道的大循环半完成和全完成的中断时(DMA_CTS_CSR[THDINT]=1
和DMA_CTS_CSR[TDINT]=1
),这两个标志位才会置位,否则哪怕对应的事件到来,也不会被置位。但另一个传输完成标志位(DMA_DONE
和DMA_CTS_CSR[DONE]
),无论是否开启对应的中断(DMA_CTS_CSR[LOOPINT]
),都能置位。这里就有一点小纠结了,如果同时启动了DMA_CTS_CSR[LOOPINT]
和DMA_CTS_CSR[TDINT]=1
,DMA_DONE
和DMA_CHTLDIF
所对应的行为将完全一样,那么在一个大循环完成后产生中断,其中的服务程序就需要同时清零这两个标志位。(这里的设计似乎有点冗余,有似乎缺了点什么。。。)
DMA外设设计了非常灵活的搬运地址更新策略,可以覆盖最大范围的应用场景。但需要整理清楚其中的概念和更新时机,才能玩转DMA,否则,一不小心产生了错误的参数配置状态,DMA也将会停止工作并报错(DMA_ERS
)。
重申一次DMA搬数中的操作单元:
Transfer
Transfer
,也可被称为Transfer Loop
或者a loop of transfers
,这也对应文中描述的小循环Minor Loop
。Minor Loop
,也可被称为Trigger Loop
或者a loop of triggers
,这对应文中描述的大循环 Major Loop
。以数据源地址指针为例(数据目标地址指针相同):
DMA_CSR_SADDR
中。这个寄存器中的值也会随着DMA搬运过程的执行变化,始终指向即将要搬运数据的地址。DMA_CTS_TCNT
寄存器的值大于等于1,表示本次DMA传输任务至少包含1次触发产生的小循环。DMA_CSR_TCR[SSIZE]
配置,可以选择1 Byte、2 Byte、4 Byte,以及16 Byte和32 Byte,这也代表了DMA使用数据总线的数据带宽。每次搬运都是从当前的数据地址开始搬运带宽指定数量的字节数。搬运过后,不对当前搬运数据地址产生影响,指针保持不变。DMA_CSR_SOFF
配置,可以是正整数,也可以是负整数(地址向前跳)。这个偏移量是作用于当前搬运数据地址指针寄存器DMA_CSR_SADDR
的,当搬运执行后,当前地址指针将会加上这个偏移量,更新成新的地址指针。DMA_CTS_BCNT
寄存器预置了本次小循环的需要数据的总长度(以字节为单位),而不是地址范围(切记,地址有可能是不连续地跳跃)。DMA控制器内部会自动递减DMA_CTS_BCNT
寄存器的数,但不会覆写到DMA_CTS_BCNT
寄存器中,因为整个小循环的搬运过程是连续执行的,用户看不到中间状态。DMA_CTS_TCNT
寄存器的值减1,并覆写到DMA_CTS_TCNT
寄存器中。如果值仍大于0,则说明当前的大循环任务还没执行完,继续等下一个触发启动一次小循环。如果值被减到0,说明大循环任务完成,此时,DMA_DONE
和DMA_CHTLDIF
(若开放中断)都会置位,同时,DMA控制器还会更新两个计数值:DMA_CSR_SOFF
配置)之后,还会立即继续叠加一个大循环完成地址偏移,由寄存器DMA_CSR_STO
配置,这也是一个可正可负的整数。计算的结果会覆写到寄存器DMA_CSR_SADDR
中。DMA_CTS_TCNTRV
寄存器中预存的重载值覆写到DMA_CTS_TCNT
寄存器中,以便于启动下次任务时无需重新配置这些计数器和地址指针。说起来,个人觉得,如果设计小循环结束后有一个地址偏移,比实现大循环结束后的地址偏移更加直观一些。大循环专门管理触发(管理触发的递减和循环),小循环管理指针(地址的递减和递增等),分工相对更明确些。这里实现的大循环的一次性偏移,也可以等价实现为等分到每次小循环之后的地址偏移。
读者可以自行进行实验,观察DMA寄存器中各计数器的变化。
图x 利用Keil的寄存器调试界面调试
DMA控制器还支持Scatter Gather模式,将多个DMA传输任务串联在一起,可以实现地址不规则的连续传输。在地址增长模式上,还有个回环递增的模式可以用。可以在具体用到的时候再深究。手册上的描述比较简略,届时仍需要用户发挥主观能动性,大胆猜想多做尝试。
dma
外设的驱动程序,以及配合其他外设使用的样例工程,例如adc_dma
、spi_slave_dma
、spi_master_dma
、sent_dma
等。arm-mcu-sdk
仓库中,也收纳了ytm_dma
驱动程序,以及对应的样例工程dma_basic
。全部0条评论
快来发表一下你的评论吧 !