4 相关操作
4.1 执行DMA转换
4.1.1 单次请求
比方说,我们需要传输16
个字节的数据。eDMA可编程为一个大循环,每次传输16
字节。在源内存有个1字节宽度的内存接口,地址为0x1000
。数据终点地址为0x2000
,有32位的接口。地址偏移量以增量方式编程以匹配传输大小,读取四次数据,才会写一次数据。相关配置程序如下:
TCDn_CITER = TCDn_BITER = 1 TCDn_NBYTES = 16 TCDn_SADDR = 0x1000 TCDn_SOFF = 1 TCDn_ATTR[SSIZE] = 0 TCDn_SLAST = -16 TCDn_DADDR = 0x2000 TCDn_DOFF = 4 TCDn_ATTR[DSIZE] = 2 TCDn_DLAST_SGA= –16 TCDn_CSR[INTMAJ] = 1 TCDn_CSR[START] = 1 (should be written last after all other fields have been initialized) All other TCDn fields = 0
以上的配置,会按照如下顺序执行:
- 用户将
TCDn_CSR[START]
写入数据进行通道服务请求 - 通过通道仲裁后,该通道被选择
- eDMA引擎写入:
CHn_CSR[DONE] = 0 TCDn_CSR[START] = 0 CHn_CSR[ACTIVE] = 1
- eDMA引擎读取:从局部内存中读取通道的TCD数据到内部寄存器文件
- 从源头到终点的传输流程如下:
从0x1000
、0x1001
、0x1002
、0x1003
各读取一个字节的数据。
写32位的数据到0x2000
,进行小循环第一次迭代
从0x1004
、0x1005
、0x1006
、0x1007
各读取一个字节的数据。
写32位的数据到0x2004
,进行小循环第二次迭代
从0x1008
、0x1009
、0x100A
、0x100B
各读取一个字节的数据。
写32位的数据到0x2008
,进行小循环第三次迭代
从0x100C
、0x100D
、0x100E
、0x100F
各读取一个字节的数据。
写32位的数据到0x200C
,进行小循环第四次迭代,大循环迭代完成 - eDMA引擎写入:
TCDn_SADDR = 0x1000
,TCDn_DADDR = 0x2000
,TCDn_CITER = 1 (TCDn_BITER)
。 - eDMA引擎写入:
CHn_CSR[ACTIVE] = 0
,CHn_CSR[DONE] = 1
,CHn_INT[INT] = 1
。 - 通道退出,
eDMA
进入空闲状态,或者服务下一个通道。
具体流程大致如下图所示:
4.1.2 多次请求
如果说我们需要搬运32
字节的数据,这个时候就需要两次硬件请求,其他都和上述的单次请求一致。这个时候我们只需要修改主循环的次数和最后的地址偏移。变成两次主循环,每次搬运16
字节数据,最后我们通过CHn_CSR[ERQ]
寄存器字段使能硬件请求,可初始化从机的通道服务请求。配置如下:
TCDn_CITER = TCDn_BITER = 2 TCDn_SLAST = –32 TCDn_DLAST_SGA = –32
执行的基本流程如下:
- 第一个硬件通道请求
- 通过通道仲裁后,该通道被选择
- eDMA引擎写入:
CHn_CSR[DONE] = 0 TCDn_CSR[START] = 0 CHn_CSR[ACTIVE] = 1
- eDMA引擎读取:从局部内存中读取通道的TCD数据到内部寄存器文件
- 从源头到终点的传输流程如下:
从0x1000
、0x1001
、0x1002
、0x1003
各读取一个字节的数据。
写32位的数据到0x2000
,进行小循环第一次迭代
从0x1004
、0x1005
、0x1006
、0x1007
各读取一个字节的数据。
写32位的数据到0x2004
,进行小循环第二次迭代
从0x1008
、0x1009
、0x100A
、0x100B
各读取一个字节的数据。
写32位的数据到0x2008
,进行小循环第三次迭代
从0x100C
、0x100D
、0x100E
、0x100F
各读取一个字节的数据。
写32位的数据到0x200C
,小循环迭代完成 - eDMA引擎写入:
TCDn_SADDR = 0x1010
,TCDn_DADDR = 0x2010
,TCDn_CITER = 1
。 - eDMA引擎写入:
CHn_CSR[ACTIVE] = 0
。 - 通道退出,主循环的一次迭代结束,
eDMA
进入空闲状态,或者服务下一个通道。 - 第二个硬件通道请求
- 通过通道仲裁后,该通道被选择
- eDMA引擎写入:
CHn_CSR[DONE] = 0 TCDn_CSR[START] = 0 CHn_CSR[ACTIVE] = 1
- eDMA引擎读取:从局部内存中读取通道的TCD数据到内部寄存器文件
- 从源头到终点的传输流程如下:
从0x1010
、0x1011
、0x1012
、0x1013
各读取一个字节的数据。
写32位的数据到0x2010
,进行小循环第一次迭代
从0x1014
、0x1015
、0x1016
、0x1017
各读取一个字节的数据。
写32位的数据到0x2014
,进行小循环第二次迭代
从0x1018
、0x1019
、0x101A
、0x101B
各读取一个字节的数据。
写32位的数据到0x2018
,进行小循环第三次迭代
从0x101C
、0x101D
、0x101E
、0x101F
各读取一个字节的数据。
写32位的数据到0x201C
,进行小循环第四次迭代,大循环完成 - eDMA引擎写入:
TCDn_SADDR = 0x1000
,TCDn_DADDR = 0x2000
,TCDn_CITER = 2 (TCDn_BITER)
。 - eDMA引擎写入:
CHn_CSR[ACTIVE] = 0
,CHn_CSR[DONE] = 1
,CHn_INT[INT] = 1
。 - 通道退出,主循环结束,
eDMA
进入空闲状态,或者服务下一个通道。
具体流程大致如下图所示:
4.2 监视描述符状态
4.2.1 测试小循环是否完成
两种方法可以测试小循环(次循环)的状态
- 读取
TCDn_CITER
字段,并测试其是否有更改 - 读取下表相应字段,根据数值判断状态
当使用硬件发起的(即外设发起的)服务请求时,测试小循环完成的最佳方法是读取TCDn_CITER
字段并测试更改。硬件请求和确认握手信号在程序员的模型中是不可见的。
TCD状态字段为硬件激活的通道执行以下序列:
4.2.2 读取激活通道的传输描述符状态
如果在通道执行时读取TCDn_SADDR
、TCDn_DADDR
和TCDn_NBYTES
的值,eDMA
将回读它们的真实值。SADDR
、DADDR
和NBYTES
的真实值是eDMA
引擎当前在其内部寄存器文件中使用的值,而不是该通道的TCD
本地内存中的值。
4.2.3 检查通道抢占状态
抢占的情况是启用了抢占的通道正在执行,而高优先级的请求变为活动的情况。
启用轮循仲裁模式时,通道优先级为0的优先级被视为相等,即不断轮换。
被抢占通道的CHn_CSR[ACTIVE]
字段在整个抢占过程中保持断言。当抢占通道执行一次主循环迭代时,被抢占的通道被暂时挂起。如果在全局TCD映射中同时设置了两个CHn_CSR[ACTIVE]
字段,则高优先级通道正在主动抢占低优先级通道。
4.3 通道连接(通道链接)
通道连接(或链接)是一种机制,其中一个通道设置另一个通道(或其自身)的TCDn_CSR[START]
字段,从而为该通道发起服务请求。在配置得当时,eDMA
引擎会在大循环或小循环完成时自动执行此操作。
注:相关寄存器/字段解释
START:如果此标志为1
,则通道正在请求服务。eDMA
硬件在通道开始执行后自动将此标志清除为0
。
小循环通道连接发生在小循环(或大循环的一次迭代)完成时。TCDn_CITER[ELINK]
字段决定是否请求小环路连接。启用后,除了最后一次迭代之外,每次大循环迭代之后都会建立通道链接。当主环路耗尽时,仅使用大环路通道link字段来确定是否应该建立通道链路。例如,若采用如下配置:
TCDn_CITER[ELINK] = 1 TCDn_CITER[LINKCH] = 0xC TCDn_CITER[CITER] value = 0x4 TCDn_CSR[MAJORELINK] = 1 TCDn_CSR[MAJORLINKCH] = 0x7
则执行过程如下:
小循环完成——>设置TCD12_CSR[START]
字段
小循环完成——>设置TCD12_CSR[START]
字段
小循环完成——>设置TCD12_CSR[START]
字段
小循环完成,大循环完成——>设置TCD7_CSR[START]
字段
注:相关寄存器/字段解释
TCDn_CITER[CITER]:Current Major Iteration Count
表示通道的当前主循环计数。每次通道完成一个服务请求并将其写回TCD
内存时,它都会减少。
从以上配置来看,一个大循环包含了四个小循环。
下面的表格总结了一个DMA
通道如何连接到其他DMA
通道,也就是在一次循环结束后,使用其他通道的TCD
。
4.4 动态程序设计
在通道的执行过程中,也可以改变eDMA的一些配置。
4.4.1 动态改变通道优先级
若想改变组或者通道的优先级可以这样做:
- 通过将
CSR[HALT]
字段写1
来终止DMA写入 - 改变想改变的组或者通道的优先级
- 通过将
CSR[HALT]
字段写0
来重新启动DMA操作
4.4.2 动态通道连接(动态通道链接)
动态通道连接是通设置TCDn_CSR[MAJORELINK]
字段来实现的,这个字段从TCD
的局部内存中读取的,因此可以在DMA
通道运行的时使用该特性。为了保险起见,建议采用下面的步骤。
- 给
TCDn_CSR[MAJORELINK]
字段写1
- 读取
TCDn_CSR[MAJORELINK]
字段的值 - 测试
TCDn_CSR[MAJORELINK]
请求状态
如果为1
,则动态连接尝试成功
如果为0
,则动态连接尝试失败
4.4.3 动态分散/聚集
自动加载一个新的TCD到一个通道,允许同时使用多个TCD,然后进行多个DMA数据的搬运。
有两种方法可以实现动态分散/聚集
Method 1 (不使用大循环连接)
如果通道没有使用大循环连接,则可以使用动态分散/收集请求。
Method 2 (使用大循环连接)
如果通道使用了大循环连接,则仍然可以使用动态分散/收集请求。此方法使用TCDn_DLAST_SGA
字段作为TCD的标识。
注:字段/寄存器解释
TCDn_DLAST_SGA :TCD Last Destination Address Adjustment / Scatter Gather Address
调整要加载到此通道的下一个传输控制描述符的最后一个目标地址或内存地址。
4.5 挂起/重启一个已经激活的通道
挂起一个被激活的通道:
- 停止其
DMA
请求服务; - 通过读取
DMA
的Hardware Request Status (HRS)
确保没有服务请求的通道被挂起。
如果需要挂起一个DMA/DSPI
传输环路。需要执行以下步骤:
- 通过将
DSPI_RSER[TFFF_RE]
字段置0
来禁用中断服务请求。 - 验证
DSPI_RSER[TFFF_RE]
为0
,确保没有来自DSPI
的DMA
服务请求被执行。如果没有被执行,则通过将ERQ
字段置零来禁用硬件服务请求。
注:相关术语解释
DSPI :Dual Serial Peripheral Interface(不能100%确实是这个意思)
双线串行外设接口
我们发现标准SPI
通信时发送和接收时主机和从机都只能使用自己的那根数据线进行数据传输,Dual SPI
无论是接收还是发送都是使用两根数据线进行的,所以单向数据传输速度上是标准SPI
的双倍。
关于什么是标准的SPI通信,可以看这篇帖子:SPI通信协议详解
5 后记
通道仲裁的基本逻辑与《操作系统》中的调度算法非常类似,相关内容可以看这篇帖子:《操作系统》第二章 2.2处理机调度
没有比人更高的山,没有比脚更长的路。在学习eDMA
的过程中,遇到了比较多的困难,关于这种特殊的DMA
网上的资料并不多,只能硬着头皮去看官网的文档,一点一点地分析,比较,推理。但这也是一条充满乐趣的道路!
------------------------------------------------------------------------END------------------------------------------------------------------------