20. DMAC/DTC——直接存储器访问与数据传输¶
本章配套视频介绍:
《35-DMAC DTC–直接存储器访问与数据传输(第1节)——DMAC模块框图分析》
https://www.bilibili.com/video/BV1Bk4y1U7V8/
《36-DMAC DTC–直接存储器访问与数据传输(第2节)——DMAC传输模式》
https://www.bilibili.com/video/BV12N4y1J7yh/
《37-DMAC DTC–直接存储器访问与数据传输(第3节)——DTC模块框图分析》
https://www.bilibili.com/video/BV1jA4m137Yi/
《38-DMAC DTC–直接存储器访问与数据传输(第4节)——DTC传输模式》
https://www.bilibili.com/video/BV1Pz421Q7w5/
《39-DMAC DTC–直接存储器访问与数据传输(第5节)——DMAC和DTC关键特性对比》
https://www.bilibili.com/video/BV18D421j7at/
《40-DMAC DTC–直接存储器访问与数据传输(第6节)——DMAC存储器到存储器实验(上)》
https://www.bilibili.com/video/BV1mD421V7i6/
本章参考资料:RA MCU用户硬件手册《RA6M5 Group User’s Manual: Hardware》。
主要参考章节:DMAC 和 DTC 章节。
学习本章时,配合《RA6M5 Group User’s Manual: Hardware》 一起阅读效果会更佳, 特别是涉及到寄存器说明的部分。
20.1. DMAC和DTC模块简介¶
DMAC(Direct Memory Access Controller)为直接存储器访问控制器或者直接内存访问控制器, 可以在不占用 CPU 的情况下将数据从一个内存位置传输到另一个内存位置。
DTC(Data Transfer Controller)为数据传输控制器,用于在被中断请求激活时传输数据。 DTC 也可以在不占用 CPU 的情况下将数据传输,它的功能与 DMAC 的功能其实是相似的。
DMAC 和 DTC 它们的主要功能都是用来搬数据,但是却不需要占用 CPU, 即在传输数据的时候,不需要 CPU 去读取数据,在此期间 CPU 完全可以干其他的事情,类似于多线程一样。 数据传输支持从外设到存储器或者存储器到存储器,这里的存储器可以是 SRAM 或者 FLASH。 实际上,我们前面有讲过,外设寄存器也是一种存储器。
DMAC 和 DTC 实际上还是会有区别的,正因为有所区别,在不同的情况下需要考虑是使用 DMAC 还是 DTC 更加合适。 下面就让我们来了解一下 DMAC 和 DTC 这两个模块的一些特性。
20.1.1. DMAC 特性¶
RA6M5 包括一个 8 通道的直接内存访问控制器(DMAC),可以在不需要CPU干预的情况下传输数据。 当产生 DMA 传输请求时,DMAC 将存储在传输源地址的数据传输到传输目标地址。
- DMAC 外设模块特性如下:
通道数:8 个通道(Channel 0 通道的优先级最高)
传输可寻址空间:4 GB(0x00000000 ~ 0xFFFFFFFF,不包含保留区域)
最大传输量:64 M 数据 (块传输模式下的最大传输数:1024×65536 个块)
DMAC 激活源 (每个通道单独可选):
软件触发
来自外设模块或者外部中断输入引脚的中断请求
传输数据:
单个数据单元:1 字节 (8 bits),2 字节 (16 bits),4 字节 (32 bits)
单个块的大小(Block size):1 ~ 1024 个数据单元
传输模式:
正常传输模式(Normal Transfer Mode)
触发一次 DMA 传输请求时传输 1 个数据单元
可选的自由运行功能(Free-running function)
重复传输模式(Repeat Transfer Mode)
触发一次 DMA 传输请求时传输 1 个数据单元
当指定重复大小的数据传输完成后,程序重置传输起始地址从而实现重复传输。
最大可设置重复大小:1024 个数据单元(同块大小)
可选的自由运行功能(Free-running function)
块传输模式(Block Transfer Mode)
触发一次 DMA 传输请求时传输 1 个块数据
最大可设置块大小:1024 个数据单元
可选的自由运行功能(Free-running function)
重复-块传输模式(Repeat-block Transfer Mode)
触发一次 DMA 传输请求时传输 1 个块数据
最大可设置块大小:1024 个数据单元
块传输可以重复
最大可设置重复大小:65536 个块
可选的自由运行功能(Free-running function)
扩展重复区域功能 (可选功能):
可以通过重复指定范围内的地址值来传输数据的功能,其中传输地址寄存器中的高位值是固定的
2 字节至 128M 字节的区域可单独设置为传输源和目标的扩展重复区域
中断 (中断频率):
全部传输完成后中断(传输结束中断):
传输计数器指定的传输数据量完成时产生
每次传输完成都产生中断(传输转义结束中断):
当重复大小的数据传输完成时产生
当源地址扩展重复区域溢出时产生
当目标地址扩展重复区域溢出时产生
错误响应检测中断:
DMAC 传输发生错误时产生
事件链接激活:
每次数据传输后都会生成一个事件链接请求(对于块传输,在每个块传输后)
20.1.2. DTC 特性¶
数据传输控制器(DTC)模块用于在被中断请求激活时传输数据。
- DTC 外设模块特性如下:
传输模式:
正常传输模式(Normal Transfer Mode)
单次激活时触发单个数据单元的传输
重复传输模式(Repeat Transfer Mode)
单次激活时触发单个数据单元的传输
数据传输次数达到指定的重复大小后,传输地址重置回起始地址
最大重复传输次数为 256,最大数据传输大小为 256 × 32 位(1024字节)
块传输模式(Block Transfer Mode)
单次激活时触发单个块的传输
最大的块大小为 256 × 32 位(1024字节)
传输通道:
传输通道与中断源相关联(通过来自 ICU 的 DTC 激活请求传输)
可以在单个激活源上传输多个数据单元(链式传输)
链传输可以选择在计数器为 0 时执行,也可以选择始终执行
传输可寻址空间:4 GB(0x00000000 ~ 0xFFFFFFFF,不包含保留区域)
传输数据:
单个数据单元:1 字节 (8 bits),1 半字 (16 bits),1 字 (32 bits)
单个块的大小(Block size):1 ~ 256 个数据单元
CPU 中断源:
可以在触发 DTC 激活的中断向 CPU 生成中断请求
可以在单次数据传输之后向 CPU 生成中断请求
可以在指定量的数据传输之后向 CPU 生成中断请求
错误响应检测中断:
发生 DTC 传输错误时生成
事件链接激活:
每次数据传输后都会生成一个事件链接请求(对于块传输,在每个块传输后)
20.2. DMAC 模块框图分析¶
20.2.1. DMAC 激活源¶
软件触发、来自外设模块的中断请求和外部中断请求都可以指定为 DMAC 激活源。 DMAC 激活源是在 DMTMD 寄存器的 DCTG[1:0] 位进行设置。
20.2.1.1. 通过软件激活DMAC¶
我们可以选择通过软件启动 DMA 进行传输,也就是不使用片上外设模块和外部中断的中断请求进行激活 DMAC, 而是手动地激活 DMA 进行传输。
20.2.1.2. 通过来自片上外设模块或外部中断的中断请求激活DMAC¶
除了通过软件手动激活DMAC,还可以通过片上外设模块的中断请求或外部中断请求激活DMAC。
我们可以将片上外设模块的中断请求和外部中断请求设置为 DMAC 的激活源。 可以通过 ICU 的 DELSRn 寄存器的 DELS[8:0]位 (n = 0~7) 为每个通道单独选择激活源。
20.2.2. 中断优先级¶
当存在多个DMA传输请求时,DMAC确定具有DMA传输请求的通道的优先级。 通道优先级固定如下: 通道 0 > 通道 1 > 通道 2 > 通道 3… > 通道 7 (通道 0: 优先级最高)
当在数据传输期间产生DMA传输请求时,在最终数据已传输之后开始通道仲裁, 并且开始优先级较高的通道的DMA传输。
20.2.3. 事件链接¶
每个DMAC通道在每次完成数据传输或块传输模式下的块传输时,都会输出一个事件链接请求信号(DMACn_INT)。 当传输目的地是外部总线时,在写入缓冲区操作被接受时会产生一个事件链接请求信号。 有关详细信息,可以查看 事件链接控制器(ELC) 章节。 如果写入传输的最后数据时发生总线错误,则会发生传输结束事件和错误响应检测中断(DMA_TRANSERR)。
20.3. DMAC 传输模式¶
DMAC 有 4 种传输模式:
正常模式 (Normal Mode): 在正常模式下,DMAC通道每次接收到配置的激活源时都会传输单个数据单元。 数据单元可以是1字节、2字节或4字节。 在每次传输之后,源地址和目的地址可以是固定、递增、递减,或者向下一个数据单元添加偏移量。 16位计数器在每次传输后递减。 当计数器达到0时,传输将不再由激活源触发,并且可以发出所有传输已完成的信号以中断CPU。
重复模式 (Repeat Mode): 重复模式的工作方式与正常模式相同,但长度限制为范围 [1, 1024] 内的整数。 当传输计数器达到0时,计数器被重置为其配置值,重复区域(源或目的地址)被重置到其起始地址,剩余的块计数将递减1。 当块计数达到0时,传输将不再由激活源触发,并且可配置传输完成中断。
块模式 (Block Mode): 在块模式下,每个中断传输的数据单元量可以设置为范围 [1, 1024] 内的整数。 还可以将要传输的块数配置为16位数字。 每次块传输后,重复区域(源或目的地址)将重置为原始地址,而另一个地址将递增或递减到下一个块。
重复-块模式 (Repeat-Block Mode): 在重复-块模式下,每个中断传输的数据单元量可以设置为范围[1, 1024]内的整数。 可以将要传输的块的数量配置为16位数字(最大可设置重复大小为64K,即65536)。
如果目标地址模式为偏移模式,则数据传输大小为字节的块大小(长度)的最大可配置块数为0xFFFF, 数据传输大小为半字的块大小为0x7FFF,数据大小为字的块大小为0x3FFF。 在每个块传输之后,源地址和目的地址将递增或递减到下一个块地址。
对于源地址的偏移地址模式,源地址大小是源缓冲区的总大小,之后源区域被翻转,块大小可以小于源缓冲区大小。 对于源地址模式作为偏移模式,最大可配置的源缓冲区大小为0xFFFF用于一个字节的传输数据大小, 0x7FFF用于半字的传输数据大小和0x3FFF用于字的传输数据大小。
采用重复-块模式可以实现单环形缓冲区到多环形缓冲区的传输类型设计。
20.3.1. 正常传输模式¶
在正常传输模式(Normal Transfer Mode)下,一个传输请求传输一个数据。 使用DMCRAL寄存器可以将最大65535设置为传输操作数。 当这些位设置为0x0000时,不设置特定数量的传输操作; 在传输计数器停止的情况下执行数据传输(自由运行功能)。 在正常传输模式下,设置DMCRB寄存器无效。除自由运行功能外, 在完成指定数量的传输操作后,可以生成传输结束中断请求。
寄存器 |
功能 |
通过一个传输请求完成传输后更新操作 |
---|---|---|
DMSAR |
传输源地址 |
增加/减少/固定/偏移增加 |
DMDAR |
传输目的地址 |
增加/减少/固定/偏移增加 |
DMCRAL |
传输计数 |
减一/未更新(在自由运行功能中) |
DMCRAH |
— |
不更新(正常传输模式下不使用) |
DMCRB |
— |
不更新(正常传输模式下不使用) |
20.3.2. 重复传输模式¶
在重复传输模式(Repeat Transfer Mode)下,发起一次传输请求,传输一个数据。
通过设置DMCRA寄存器,最多可以将1K数据设置为总重复传输大小;通过设置DMCRB寄存器可以将最大64K设置为重复传输操作次数。
所以,我们可以将最大64M数据(1K数据×64K重复传输操作计数)设置为总数据传输大小。
可以将传输源地址或传输目的地址设置为重复区域。当指定重复大小数据的传输完成时, DMAC将会把重复区域的地址重新设置为传输开始地址,也就是一个循环的过程。
当完成指定的重复传输次数后,可以产生传输完成中断;当每完成一次循环的过程都可以产生一次中断。
通过将DMCNT寄存器的DTE位置1,可以恢复DMA传输。
在完成指定数量的重复传输操作后,可以生成传输结束中断请求。
20.3.3. 块传输模式¶
在块传输模式下(Block Transfer Mode),单个块数据通过一个传输请求传输。
使用DMCRA寄存器,最多可以将1K数据设置为总块传输大小。
使用DMCRB寄存器可以将最大64K设置为块传输操作数; 因此,可以将最大64M数据(1K数据×64K块传输操作计数)设置为总数据传输大小。
可以将传送源或传送目的地指定为块区域。 当单个块数据的传输完成时,指定块区域(DMSAR或DMDAR)的地址返回到传输开始地址。 当单块数据在块传输模式下全部传输完毕时,可停止DMA传输,并可请求重复大小结束中断。 通过将1写入重复大小结束中断处理中的DMCNT.DTE位,可以恢复DMA传输。
传输结束中断请求可以在完成指定数量的块传输操作后生成。
20.3.4. 重复-块传输模式¶
重复-块传输模式(Repeat-Block Transfer Mode),主要在块模式基础上增加了一些功能:
重复功能:添加功能(环形缓冲区)以重复指定的地址区域。
偏移功能:可以在一个块传输中指定具有偏移的多个区域。
重复功能和偏移功能可用于重复块传输的传输源和传输目的地。
在重复块传输模式下,单个块数据通过一个传输请求传输。
使用DMACn的DMCRA可以将最多1K数据设置为总的块传输大小。 使用DMACn的DMCRB的块传输操作的数量可以设置为最大64K; 因此,可以将最大64M数据(1K数据×64K块传输操作计数)设置为总数据传输大小。
下图是在重复-块传输模式下的示例
下图是在重复-块传输模式下,带偏移增加的示例
20.4. DTC 模块框图分析¶
20.4.1. DTC 内部寄存器¶
MRA、MRB、SAR、DAR、CRA 和 CRB 这些都是属于 DTC 内部的寄存器,它们是无法通过 CPU 直接访问的。 这些 DTC 内部寄存器中设置的值作为传输信息放置在 SRAM 区域中。 当生成激活请求时,DTC 从 SRAM 区域读取传输信息,并将其设置在其内部寄存器中。 数据传输结束后,内部寄存器内容作为传输信息写回 SRAM 区域。
我们在使用 DTC 时,是通过配置传输信息来间接配置这些 DTC 内部寄存器的。
20.4.2. 事件链接¶
DTC 可以在完成一个传输请求时产生事件链接请求。 然而,当传输目的地是外部总线时,事件链接请求将在写入缓冲器完成之后发出, 而不是在写入实际传输目的地完成之后发出。
20.5. DTC 传输模式¶
DTC 模块支持三种操作模式,相对于 DMAC,少了重复-块传输模式。
正常模式: 在正常模式下,DTC每次接收到中断触发时都会传输单个数据单元。数据单元可以是1、2、4字节。 传输的长度(length)可以设置为[0, 65535]。当长度设置为0时,DTC将执行65536传输,而不是0。 在每次传输之后,源地址和目的地址可以单独设置为固定、递增或递减。 每次传输后,16位计数器递减。 当计数器达到 0 时,DTC传输将不再会被中断源触发,CPU可以被中断以通知所有传输已完成。
重复模式: 重复模式的工作方式与正常模式相同,但长度(length)限制为范围 [1, 256] 内的整数。 当传输计数器达到 0 时,计数器被重置为其配置值(length), 重复区域(源或目的地址)被重置为其起始地址,并且传输仍将会被中断触发。
块模式: 在块模式下,每个中断传输的数据单元量可以设置为范围 [1, 256] 内的整数。 还可以将要传输的块数量(num_blocks)配置为16位数字, 即可以设置为[0, 65535]。当长度设置为0时,DTC将执行65536传输,而不是0。 每次块传输后,重复区域(源或目的地址)将重置为原始地址,而另一个地址将递增或递减到下一个块。
20.5.1. 正常传输模式¶
正常传输模式(Normal Transfer Mode)允许在单个激活源上进行1字节(8位)、1半字(16位)、1字(32位)数据传输。 传输计数可以设置为 1 到 65536 (0x10000)。 传送源地址和目的地址也可以独立设置为递增、递减或固定。 该模式允许在指定的计数传输结束时生成对CPU的中断请求。
正常传输模式的内存映射如下图所示:
20.5.2. 重复传输模式¶
重复传输模式(Repeat Transfer Mode)允许在单个激活源上进行1字节(8位)、1半字(16位)或1字(32位)数据传输。 重复区域的传输源或传输目的地必须在MRB.DTS位中指定。 传输计数可以设置为从1到256。 当指定的传输计数完成时,恢复重复区域中指定的地址寄存器的初始值,恢复传输计数器的初始值,并重复传输。 另一地址寄存器连续递增或递减或保持不变。 在重复传输模式下,当传输计数器CRAL递减到0x00时,CRAL值更新为CRAH寄存器中设置的值。 因此,传输计数器不会清0,当MRB.DISEL位设置为0时,将禁用对CPU的中断请求。 当指定的数据传输完成时,会生成对CPU的中断请求。
当传输源为重复区域时,重复传输模式的内存映射如下图所示:
20.5.3. 块传输模式¶
块传输模式(Block Transfer Mode)允许在单个激活源上进行单块数据传输。 数据块区域的传输源或传输目的地必须在MRB.DTS位中指定。 块大小可以设置为1至256字节、1至256半字(2至512字节)或1至256字(4至1024字节)。 当指定块的传输完成时,恢复块区域中指定的块大小计数器CRAL和地址寄存器 (当MRB.DTS=1时为SAR寄存器或当DTS=0时为DAR寄存器)的初始值。 另一地址寄存器连续递增或递减或保持不变。 传输计数(块计数)可以设置为从 1 到 65536。 该模式允许在指定计数块传输结束时生成对CPU的中断请求。
块传输模式的内存映射如下图所示:
20.6. DMAC和DTC关键特性对比¶
FSP 库里边的传输 API 可以由 DMAC 或者 DTC 实现, 因此理论上来说我们可以在 DMAC 和 DTC 之间切换使用。 但是 DMAC 和 DTC 还是有一些不同的,因此在它们之间进行选择时,请考虑以下因素:
DMAC |
DTC |
|
---|---|---|
通道数 |
|
|
重复模式 |
|
|
块模式 |
|
|
链传输模式 |
|
|
软件触发 |
|
|
偏移地址模式 |
|
|
中断
DTC 和 DMAC 的中断行为不同:
DTC 使用配置的 IELSR 事件 IRQ 作为中断源
而每个 DMAC 通道都有自己的 IRQ
其他注意事项:
DTC 需要一定的 RAM。
DTC 将传输信息存储在RAM中,并在每次传输后写回RAM,而 DMAC 将所有传输信息存储在寄存器中。
当为多个激活源配置传输时,DTC 必须在每次中断时从RAM获取传输信息。这可能会导致传输之间的延迟更高。
DTC 使用激活源的IRQ中断CPU。每个 DMAC 通道都有自己的IRQ。
另外在传输信息的 transfer_info_t::irq 属性的设置上,根据所选模式的不同,其具体行为也略有不同。
中断模式 |
DMAC |
DTC |
---|---|---|
TRANSFER_IRQ_EACH |
N/A |
每次传输后中断 |
TRANSFER_IRQ_END |
完成最后一次传输后中断 |
完成最后一次传输后中断 |
中断模式 |
DMAC |
DTC |
---|---|---|
TRANSFER_IRQ_EACH |
每次传输后中断 |
每次传输后中断 |
TRANSFER_IRQ_END |
完成最后一次传输后中断 |
每次传输后中断 |
中断模式 |
DMAC |
DTC |
---|---|---|
TRANSFER_IRQ_EACH |
每次传输后中断 |
每次传输后中断 |
TRANSFER_IRQ_END |
完成最后一次传输后中断 |
完成最后一次传输后中断 |
中断模式 |
DMAC |
DTC |
---|---|---|
TRANSFER_IRQ_EACH |
N/A |
N/A |
TRANSFER_IRQ_END |
完成最后一次传输后中断 |
N/A |
20.7. 实验1:DMAC存储器到存储器传输¶
20.7.1. 软件设计¶
20.7.1.1. 新建工程¶
因为本实验需要用到LED,也会用到串口打印调试信息,因此我们在上一章的“实验1:UART收发回显”例程的基础上修改程序。
- 对于 e2 studio 开发环境:
拷贝一份我们之前的 e2s 工程 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “20_DMAC_Memory_To_Memory”,最后再将它导入到我们的 e2 studio 工作空间中。
- 对于 Keil 开发环境:
拷贝一份我们之前的 Keil 工程 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “20_DMAC_Memory_To_Memory”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。
工程新建好之后,在工程根目录的 “src” 文件夹下面新建 “dmac” 文件夹, 再进入 “dmac” 文件夹里面新建源文件和头文件:“bsp_dmac_m2m.c” 和 “bsp_dmac_m2m.h”。 工程文件结构如下。
20_DMAC_Memory_To_Memory
├─ ......
└─ src
├─ led
│ ├─ bsp_led.c
│ └─ bsp_led.h
├─ debug_uart
│ ├─ bsp_debug_uart.c
│ └─ bsp_debug_uart.h
├─ dmac
│ ├─ bsp_dmac_m2m.c
│ └─ bsp_dmac_m2m.h
└─ hal_entry.c
20.7.1.2. FSP配置¶
打开该工程的 FSP 配置界面。然后按如图步骤加入 DMAC。
加入 DMAC 后如下图所示。
我们单击上图中新添加的 r_dmac 框,然后在左下角的“属性”窗口配置 DMAC 模块的各个属性参数。 按照如下图所示来配置即可。
在上图中,实际上只需配置框中的那部分属性,其他的属性均按照默认即可。
DMAC 的配置项(与上图相对应):
属性 |
描述 |
---|---|
Name |
名字。 上图配置为 g_transfer_dmac0, 按照自己命名习惯设置、并且与代码中的对应即可。 |
Channel |
通道:DMA传输通道选择。这里选择通道0,通道0也是优先级最高的通道。 RA6M5有8个通道,0~7可选。其他MCU型号可能不同。 |
Mode |
模式:DMA传输模式选择。默认选择正常模式即可。 |
Transfer Size |
传输大小:传输数据单元的大小。默认设置为2字节即可。 |
Destination Address Mode |
目标地址模式:默认设置为固定。 |
Source Address Mode |
源地址模式:默认设置为固定。 |
Repeat Area (Unused in Normal Mode) |
重复区域(正常模式下不使用):源地址。 |
Destination Pointer |
目标指针:NULL(在这里不方便确定传输地址,后续在代码部分配置)。 |
Source Pointer |
源指针:NULL(在这里不方便确定传输地址,后续在代码部分配置)。 |
Number of Transfers |
传输次数:指定正常和重复模式的传输次数 或 重复-块传输模式的块大小。 |
Number of Blocks (Valid only in Repeat, Block or Repeat-Block Mode) |
块数量:指定要在重复、块或重复-块模式下传输的块数。 |
Activation Source |
触发源:选择DMAC传输开始事件。 如果未选择ELC事件,则可以使用软件启动。 |
Callback |
回调函数:触发DMAC中断时调用的回调函数。 此处设置为 dmac0_callback。 |
Context |
上下文:指向通过回调函数传递的上下文结构的指针。 |
Transfer End Interrupt Priority |
传输结束中断优先级:传输结束中断的优先级设置。 |
Interrupt Frequency |
中断频率 (选择什么时候触发中断):
|
Offset value |
偏移: 偏移值在每次传输后添加到传输地址。 (仅当地址模式为“偏移”时有效) |
Source Buffer Size |
源缓冲大小: 指定整个源缓冲区的大小。 (仅适用于带有源地址更新模式的重复-块传输模式,不适用于地址偏移模式) |
配置完成后,点击生成代码,然后开始我们的代码编写。
20.7.1.3. 定义传输源和目标存储器¶
首先,要使用 DMA 传输,就肯定要有一个源地址和一个目标地址, 这里我们定义 SRC_Buffer 数组的首地址作为源地址, DST_Buffer 数组的首地址作为DMAC传输的目标地址。 SRC_Buffer 数组由于有 const 声明为常量,因此其数据存储在内部 Flash 中, DST_Buffer 为普通的全局变量,其数据存储在 RAM 中。这两个数组的大小由宏定义 BUFFER_SIZE 来决定。
// 用户要发送的数据大小
#define BUFFER_SIZE 32
/**
* 定义 SRC_Buffer 数组作为 DMAC 传输数据源
* const 关键字将 SRC_Buffer 数组变量定义为常量类型,数据存储在内部的FLASH中
*/
const uint32_t SRC_Buffer[BUFFER_SIZE] = {
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80 };
/**
* 定义 DMAC 传输目标存储器
* 存储在内部的SRAM中
*/
uint32_t DST_Buffer[BUFFER_SIZE] = {0};
20.7.1.4. 设置源地址和目标地址函数¶
DMAC 传输的配置,可以选择在 FSP 配置界面中进行配置, 但是要注意,在 FSP 配置界面中进行配置的话是不方便配置传输地址的, 所以此时我们其实还没有配置传输的源地址和目标地址,这部分需要在代码里面配置。
/* 设置传输的源地址和目的地址 */
void set_transfer_dst_src_address( transfer_cfg_t const * const p_config,
void const * volatile p_src,
void const * volatile p_dest )
{
p_config->p_info->p_src = (void *) p_src;
p_config->p_info->p_dest = (void *) p_dest;
}
20.7.1.5. 使用FSP配置器生成的配置¶
前面我们在 FSP 配置界面上的配置其实是保存在 hal_data.c 文件中。如下图所示。
我们完全可以在 hal_data.c 文件中找到定义好的数据,重新复制一份,再改个变量名,在此基础上重新配置各个参数。 在本例程中也使用了这种方法,主要是为了使用宏去方便切换不同模式的配置代码,在代码中配置 DMAC。 在头文件 “bsp_dmac_m2m.h” 中,默认定义了宏 USE_MY_TRANSFER_INFOR_CONFIG 来选择 使用我们在代码里自定义的传输信息配置,但是现在让我们先注释掉这个宏定义,从而选择使用在 FSP 配置界面上的配置。
下面的是与我们之前在 FSP 配置界面上的配置等效的配置代码:
/* FSP配置界面的传输信息配置(正常传输模式)等效于下面 fsp_transfer_info_normal 里的配置(除了源地址和目标地址)
源地址和目标地址在FSP配置界面设置的话不太方便,我们会在 DMAC_Init 函数里设置。
关于偏移值(Offset value)和源缓冲大小(Source Buffer Size),在 transfer_info_t 里没有这两项设置,建议在FSP配置界面设置
- 偏移值只有在地址模式是偏移模式的情况下才会用到;
- 而源缓冲大小与之相关的功能本例程不会涉及到,所以暂不考虑。
下面的 fsp_transfer_info_normal 仅作为对比参考,在本例程中是没有用到的。
*/
//transfer_info_t fsp_transfer_info_normal =
//{
// .transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_FIXED, //每次传输后,目标地址指针固定不变
// .transfer_settings_word_b.repeat_area = TRANSFER_REPEAT_AREA_SOURCE, //源区域重复 (正常模式下无效)
// .transfer_settings_word_b.irq = TRANSFER_IRQ_END, //传输完成后中断
// .transfer_settings_word_b.chain_mode = TRANSFER_CHAIN_MODE_DISABLED, //不使能(DMAC没有该功能,仅DTC有)
// .transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_FIXED, //每次传输后,源地址指针固定不变
// .transfer_settings_word_b.size = TRANSFER_SIZE_2_BYTE, //每次传输2字节
// .transfer_settings_word_b.mode = TRANSFER_MODE_NORMAL, //正常传输模式
// .p_dest = (void *) DST_Buffer, //目标地址
// .p_src = (void const *) SRC_Buffer, //源地址
// .num_blocks = 0, //指定传输的块数(正常模式下无效,仅在重复、块或重复-块模式下有效)
// .length = 1, //指定传输的长度(即正常和重复模式下的传输次数 或 块和重复-块模式下传输的块大小)
//};
// 按照上述传输信息配置,期待的正确传输结果为:
uint32_t Expected_DST_Buffer[BUFFER_SIZE] = {
0x00000304,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000 };
可以看到,上述代码中的传输信息结构体变量 fsp_transfer_info_normal 被完全注释掉了, 因为在本例程中,永远不会用到该变量,即使取消了注释也是一样,虽然取消注释后编译也不会报错。 该变量在这里的目的仅是方便与我们之前在 FSP 配置界面上的传输配置进行比较,两者的配置基本上是等效的。
本例程中需要用到的是 Expected_DST_Buffer 数组, 其中保存的数据是根据源数据 SRC_Buffer 和当前的传输配置得出的正确结果, 我们在进行实际上的传输结束后也应该在 DST_Buffer 数组中获得这样的数据。 换句话说,SRC_Buffer 保存了要传输的源数据,DST_Buffer 保存了传输后的实际结果, Expected_DST_Buffer 则保存的是传输后的正确结果, 后面还会通过 DST_Buffer 与 Expected_DST_Buffer 数组中数据的对比,来判断传输是否成功。
20.7.1.6. 使用代码配置:正常传输模式¶
下面是 DMAC 在正常模式下传输的配置代码:
/* 正常传输模式 */
transfer_info_t my_transfer_info_normal =
{
.transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, //每次传输后,目标地址指针都会增加
.transfer_settings_word_b.repeat_area = TRANSFER_REPEAT_AREA_SOURCE, //源区域重复 (正常模式下无效)
.transfer_settings_word_b.irq = TRANSFER_IRQ_END, //传输完成后中断
.transfer_settings_word_b.chain_mode = TRANSFER_CHAIN_MODE_DISABLED, //不使能(DMAC没有该功能,仅DTC有)
.transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, //每次传输后,源地址指针都会增加
.transfer_settings_word_b.size = TRANSFER_SIZE_4_BYTE, //每次传输4字节
.transfer_settings_word_b.mode = TRANSFER_MODE_NORMAL, //正常传输模式
.p_dest = (void *) DST_Buffer, //目标地址
.p_src = (void const *) SRC_Buffer, //源地址
.num_blocks = 0, //指定传输的块数(正常模式下无效,仅在重复、块或重复-块模式下有效)
.length = BUFFER_SIZE, //指定传输的长度(即正常和重复模式下的传输次数 或 块和重复-块模式下传输的块大小)
};
// 按照上述传输信息配置,期待的正确传输结果为:
uint32_t Expected_DST_Buffer[BUFFER_SIZE] = {
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80 };
注:在本例程中使用以上代码需在头文件 “bsp_dmac_m2m.h” 中定义宏 USE_MY_TRANSFER_INFOR_CONFIG, 并且定义宏 DMAC_TRANSFER_MODE 等于 DMAC_TRANSFER_NORMAL_MODE 来选择传输模式为: 正常传输模式。
传输信息结构体变量 my_transfer_info_normal 用于对 DMAC 的传输进行重配置。
读者可跳到本章后面的“下载验证”小节,编译并下载程序来验证本实验例程运行结果。
20.7.1.7. 使用代码配置:重复传输模式¶
上面使用完正常传输模式,下面我们来试一下重复传输模式, 其实无非就是在正常模式下多了 可以指定重复传输的次数 的功能, 就变成了重复传输模式。可以结合地址递增模式、重复区域,来实现环形队列。
下面是 DMAC 在重复传输模式下传输的配置代码:
/* 重复传输模式 */
transfer_info_t my_transfer_info_repeat =
{
.transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, //每次传输后,目标地址指针都会增加
.transfer_settings_word_b.repeat_area = TRANSFER_REPEAT_AREA_SOURCE, //源区域重复 (正常模式下无效)
.transfer_settings_word_b.irq = TRANSFER_IRQ_END, //传输完成后中断
.transfer_settings_word_b.chain_mode = TRANSFER_CHAIN_MODE_DISABLED, //不使能(DMAC没有该功能,仅DTC有)
.transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, //每次传输后,源地址指针都会增加
.transfer_settings_word_b.size = TRANSFER_SIZE_4_BYTE, //每次传输4字节
.transfer_settings_word_b.mode = TRANSFER_MODE_REPEAT, //重复传输模式
.p_dest = (void *) DST_Buffer, //目标地址
.p_src = (void const *) SRC_Buffer, //源地址
.num_blocks = 8, //指定传输的块数(正常模式下无效,仅在重复、块或重复-块模式下有效)
.length = 4, //指定传输的长度(即正常的传输次数或重复模式下重复大小 或 块和重复-块模式下传输的块大小)
};
// 按照上述传输信息配置,期待的正确传输结果为:
uint32_t Expected_DST_Buffer[BUFFER_SIZE] = {
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10 };
注:在本例程中使用以上代码需在头文件 “bsp_dmac_m2m.h” 中定义宏 USE_MY_TRANSFER_INFOR_CONFIG, 并且定义宏 DMAC_TRANSFER_MODE 等于 DMAC_TRANSFER_REPEAT_MODE 来选择传输模式为: 重复传输模式。
传输信息结构体变量 my_transfer_info_repeat 用于对 DMAC 的传输进行重配置。
读者可跳到本章后面的“下载验证”小节,编译并下载程序来验证本实验例程运行结果。
20.7.1.8. 使用代码配置:块传输模式¶
下面是 DMAC 在块传输模式下传输的配置代码:
/* 块传输模式 */
transfer_info_t my_transfer_info_block =
{
.transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, //每次传输后,目标地址指针都会增加
.transfer_settings_word_b.repeat_area = TRANSFER_REPEAT_AREA_SOURCE, //源区域重复 (正常模式下无效)
.transfer_settings_word_b.irq = TRANSFER_IRQ_END, //传输完成后中断
.transfer_settings_word_b.chain_mode = TRANSFER_CHAIN_MODE_DISABLED, //不使能(DMAC没有该功能,仅DTC有)
.transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, //每次传输后,源地址指针都会增加
.transfer_settings_word_b.size = TRANSFER_SIZE_4_BYTE, //每次传输4字节
.transfer_settings_word_b.mode = TRANSFER_MODE_BLOCK, //块传输模式
.p_dest = (void *) DST_Buffer, //目标地址
.p_src = (void const *) SRC_Buffer, //源地址
.num_blocks = 4, //指定传输的块数(正常模式下无效,仅在重复、块或重复-块模式下有效)
.length = 8, //指定传输的长度(即正常的传输次数或重复模式下重复大小 或 块和重复-块模式下传输的块大小)
};
// 按照上述传输信息配置,期待的正确传输结果为:
uint32_t Expected_DST_Buffer[BUFFER_SIZE] = {
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20 };
注:在本例程中使用以上代码需在头文件 “bsp_dmac_m2m.h” 中定义宏 USE_MY_TRANSFER_INFOR_CONFIG, 并且定义宏 DMAC_TRANSFER_MODE 等于 DMAC_TRANSFER_BLOCK_MODE 来选择传输模式为: 块传输模式。
传输信息结构体变量 my_transfer_info_block 用于对 DMAC 的传输进行重配置。
读者可跳到本章后面的“下载验证”小节,编译并下载程序来验证本实验例程运行结果。
20.7.1.9. 使用代码配置:重复-块传输模式¶
下面是 DMAC 在重复-块传输模式下传输的配置代码:
/* 重复-块传输模式 */
transfer_info_t my_transfer_info_repeat_block =
{
.transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, //每次传输后,目标地址指针都会增加
.transfer_settings_word_b.repeat_area = TRANSFER_REPEAT_AREA_SOURCE, //源区域重复 (正常模式下无效)
.transfer_settings_word_b.irq = TRANSFER_IRQ_EACH, //每次传输完成后都触发中断
.transfer_settings_word_b.chain_mode = TRANSFER_CHAIN_MODE_DISABLED, //不使能(DMAC没有该功能,仅DTC有)
.transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, //每次传输后,源地址指针都会增加
.transfer_settings_word_b.size = TRANSFER_SIZE_4_BYTE, //每次传输4字节
.transfer_settings_word_b.mode = TRANSFER_MODE_REPEAT_BLOCK, //重复-块传输模式
.p_dest = (void *) DST_Buffer, //目标地址
.p_src = (void const *) SRC_Buffer, //源地址
.num_blocks = 4, //指定传输的块数(正常模式下无效,仅在重复、块或重复-块模式下有效)
.length = 2, //指定传输的长度(即正常的传输次数或重复模式下重复大小 或 块和重复-块模式下传输的块大小)
};
// 按照上述传输信息配置,期待的正确传输结果为:
uint32_t Expected_DST_Buffer[BUFFER_SIZE] = {
0x01020304,0x01020304,0x01020304,0x01020304,
0x01020304,0x01020304,0x01020304,0x01020304,
0x01020304,0x01020304,0x01020304,0x01020304,
0x01020304,0x01020304,0x01020304,0x01020304,
0x01020304,0x01020304,0x01020304,0x01020304,
0x01020304,0x01020304,0x01020304,0x01020304,
0x01020304,0x01020304,0x01020304,0x01020304,
0x01020304,0x01020304,0x01020304,0x01020304 };
注:在本例程中使用以上代码需在头文件 “bsp_dmac_m2m.h” 中定义宏 USE_MY_TRANSFER_INFOR_CONFIG, 并且定义宏 DMAC_TRANSFER_MODE 等于 DMAC_TRANSFER_REPEAT_BLOCK_MODE 来选择传输模式为: 重复-块传输模式。
传输信息结构体变量 my_transfer_info_repeat_block 用于对 DMAC 的传输进行重配置。
读者可跳到本章后面的“下载验证”小节,编译并下载程序来验证本实验例程运行结果。
20.7.1.10. DMAC初始化函数¶
若是使用 FSP 配置,需要设置传输地址, 通过调用 set_transfer_dst_src_address 函数来设置。
然后,调用 R_DMAC_Open 函数打开 DMAC。 需要注意,R_DMAC_Open 函数需要在 set_transfer_dst_src_address 函数之后调用, 因为在 R_DMAC_Open 函数内部会根据我们的传输信息(包括传输地址等)来配置底层寄存器。
若是使用代码配置 DMAC 的传输信息(即定义了宏 USE_MY_TRANSFER_INFOR_CONFIG), 则还需要对 DMAC 进行重配置,方法是调用 R_DMAC_Reconfigure 函数, 并传入用于 DMAC 的传输信息配置的结构体变量(比如 my_transfer_info_normal)的首地址。
本例程中使用宏 DMAC_TRANSFER_MODE 来判断传输模式, 并根据不同的传输模式选择使用以下的传输信息变量来进行重配置: my_transfer_info_normal / my_transfer_info_repeat / my_transfer_info_repeat / my_transfer_info_block / my_transfer_info_repeat_block。
DMAC 初始化函数如下所示:
/* DMAC 初始化函数 */
void DMAC_Init(void)
{
fsp_err_t err;
/* 使用 FSP 界面的配置:需要先重新设置传输的源地址和目的地址 */
#ifndef USE_MY_TRANSFER_INFOR_CONFIG
set_transfer_dst_src_address(&g_transfer_dmac0_cfg, SRC_Buffer, DST_Buffer);
#endif
/* 打开 DMAC 模块 */
err = R_DMAC_Open(&g_transfer_dmac0_ctrl, &g_transfer_dmac0_cfg);
assert(FSP_SUCCESS == err);
/* 使用我们新的自定义的传输信息:重新配置传输 */
#ifdef USE_MY_TRANSFER_INFOR_CONFIG
// 根据我们要使用的传输模式进行选择配置:
#if (DMAC_TRANSFER_MODE == DMAC_TRANSFER_NORMAL_MODE) //正常模式
err = R_DMAC_Reconfigure(&g_transfer_dmac0_ctrl, &my_transfer_info_normal);
assert(FSP_SUCCESS == err);
#elif (DMAC_TRANSFER_MODE == DMAC_TRANSFER_REPEAT_MODE) //重复模式
err = R_DMAC_Reconfigure(&g_transfer_dmac0_ctrl, &my_transfer_info_repeat);
assert(FSP_SUCCESS == err);
#elif (DMAC_TRANSFER_MODE == DMAC_TRANSFER_BLOCK_MODE) //块模式
err = R_DMAC_Reconfigure(&g_transfer_dmac0_ctrl, &my_transfer_info_block);
assert(FSP_SUCCESS == err);
#elif (DMAC_TRANSFER_MODE == DMAC_TRANSFER_REPEAT_BLOCK_MODE) //重复-块模式
err = R_DMAC_Reconfigure(&g_transfer_dmac0_ctrl, &my_transfer_info_repeat_block);
assert(FSP_SUCCESS == err);
#endif //DMAC_TRANSFER_MODE
#endif //USE_MY_TRANSFER_INFOR_CONFIG
}
20.7.1.11. DMAC中断回调函数¶
DMAC 中断回调函数如下所示:
// DMA 传输完成标志位
volatile bool dmac0_complete_transmission_sign = false;
// 传输次数计数(中断次数)
volatile uint16_t dmac0_transfer_count;
/* DMAC 中断回调函数 */
void dmac0_callback(dmac_callback_args_t *p_args)
{
(void)(p_args);
dmac0_complete_transmission_sign = true;
dmac0_transfer_count ++;
}
dmac0_transfer_count 用于记录 DMAC 产生中断的次数。
20.7.1.12. hal_entry入口函数¶
在 hal_entry 函数中,程序遵循以下步骤来执行:
调用 DMAC_Init 函数初始化 DMAC。
调用 R_DMAC_Enable 函数使能 DMAC 使之可以响应传输请求。
调用 R_DMAC_SoftwareStart 函数来发起软件触发请求信号来启动 DMAC 传输。
通过一定的延时等待所有传输完成,因为在产生多次的中断的情况下,仅通过中断标志位判断可能出错。
在传输完成之后,比较传输目标地址的数据(DST_Buffer)和期待的正确数据(Expected_DST_Buffer)是否一致。
传入 R_DMAC_SoftwareStart 函数的参数 TRANSFER_START_MODE_SINGLE 和 TRANSFER_START_MODE_REPEAT 的区别如下:
传入参数为 TRANSFER_START_MODE_SINGLE 时, 正常模式和重复模式下每发起一次软件请求只会传输一个数据单元大小(transfer_size_t size)的数据。 块模式下则是只会传输一个块大小(transfer_info_t::length)的数据。
传入参数为 TRANSFER_START_MODE_REPEAT 时, 会自动重复地触发传输,期间可能会产生多次中断,直至所有数据都传输完成为止。
hal_entry 入口函数如下所示。
/* 用户头文件包含 */
#include "led/bsp_led.h"
#include "debug_uart/bsp_debug_uart.h"
#include "dmac/bsp_dmac_m2m.h"
extern const uint32_t SRC_Buffer[BUFFER_SIZE];
extern uint32_t DST_Buffer[BUFFER_SIZE];
extern uint32_t Expected_DST_Buffer[BUFFER_SIZE];
extern volatile bool dmac0_complete_transmission_sign;
extern volatile uint16_t dmac0_transfer_count;
uint8_t BufferCompare(const uint32_t *pBuffer1, const uint32_t *pBuffer2, uint16_t BufferLength);
void BufferShow_HexData(const uint32_t *pBuffer, uint16_t BufferLength);
void hal_entry(void)
{
/* TODO: add your own code here */
fsp_err_t err = FSP_SUCCESS;
uint8_t res;
LED_Init(); // LED 初始化
Debug_UART4_Init(); // SCI4 UART 调试串口初始化
/* 初始化 DMAC */
DMAC_Init();
dmac0_complete_transmission_sign = false; //传输完成标志位清零
printf("这是一个 DMAC 存储器到存储器的传输实验例程\r\n");
printf("打开串口助手,查看接收窗口打印的相关提示信息\r\n");
printf("观察板载LED灯,本实验使用两个LED灯来指示 DMAC 传输结果\r\n");
printf("- DMA 数据传输失败,则 LED1 亮(红色)\r\n");
printf("- DMA 数据传输成功,则 LED2 亮(蓝色)\r\n");
printf("--------------------------------------------\r\n");
/* 使能 DMAC 使之可以响应传输请求 */
R_DMAC_Enable(&g_transfer_dmac0_ctrl);
/************************************/
/* 使用软件触发的方式启动 DMAC 传输 */
/************************************/
#ifndef USE_MY_TRANSFER_INFOR_CONFIG
/* 根据 FSP 配置界面的传输信息配置进行传输 */
//可以用下面这种方式:
R_DMAC_SoftwareStart(&g_transfer_dmac0_ctrl, TRANSFER_START_MODE_REPEAT);
//也可以用这种方式:
//for (uint16_t i = 0; i < 1; i++)
//{
// err = R_DMAC_SoftwareStart(&g_transfer_dmac0_ctrl, TRANSFER_START_MODE_SINGLE);
// assert(FSP_SUCCESS == err);
//}
#else // 下面的这些是使用自定义的传输配置信息配置
#if (DMAC_TRANSFER_MODE == DMAC_TRANSFER_NORMAL_MODE) //正常模式(相当于重复次数为1的重复模式)
//可以用下面这种方式:
R_DMAC_SoftwareStart(&g_transfer_dmac0_ctrl, TRANSFER_START_MODE_REPEAT);
//也可以用这种方式:
//for (uint16_t i = 0; i < BUFFER_SIZE; i++)
//{
// err = R_DMAC_SoftwareStart(&g_transfer_dmac0_ctrl, TRANSFER_START_MODE_SINGLE);
// assert(FSP_SUCCESS == err);
//}
#elif (DMAC_TRANSFER_MODE == DMAC_TRANSFER_REPEAT_MODE) //重复模式
//可以用下面这种方式:
R_DMAC_SoftwareStart(&g_transfer_dmac0_ctrl, TRANSFER_START_MODE_REPEAT);
//也可以用这种方式:
//for (uint16_t i = 0; i < BUFFER_SIZE; i++)
//{
// err = R_DMAC_SoftwareStart(&g_transfer_dmac0_ctrl, TRANSFER_START_MODE_SINGLE);
// assert(FSP_SUCCESS == err);
//}
#elif (DMAC_TRANSFER_MODE == DMAC_TRANSFER_BLOCK_MODE) //块模式
//可以用下面这种方式:
R_DMAC_SoftwareStart(&g_transfer_dmac0_ctrl, TRANSFER_START_MODE_REPEAT);
//也可以用这种方式:
//for (uint16_t i = 0; i < 4; i++)
//{
// err = R_DMAC_SoftwareStart(&g_transfer_dmac0_ctrl, TRANSFER_START_MODE_SINGLE);
// assert(FSP_SUCCESS == err);
//
// //加个小延时,确保DMAC通道0传输完成之后才再次软件触发启动,否则传输可能出错
// R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS);
//}
#elif (DMAC_TRANSFER_MODE == DMAC_TRANSFER_REPEAT_BLOCK_MODE) //重复-块模式
//可以用下面这种方式:
R_DMAC_SoftwareStart(&g_transfer_dmac0_ctrl, TRANSFER_START_MODE_REPEAT);
//也可以用这种方式:
//for (uint16_t i = 0; i < 4; i++)
//{
// err = R_DMAC_SoftwareStart(&g_transfer_dmac0_ctrl, TRANSFER_START_MODE_SINGLE);
// assert(FSP_SUCCESS == err);
//
// //加个小延时,确保DMAC通道0传输完成之后才再次软件触发启动,否则传输可能出错
// R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS);
//}
#endif //DMAC_TRANSFER_MODE
#endif //USE_MY_TRANSFER_INFOR_CONFIG
/* 判断传输完成中断(需至少触发过1次) */
while (false == dmac0_complete_transmission_sign);
/* 等待所有传输完成(如果是TRANSFER_IRQ_EACH模式,传输过程中可能会触发多次中断) */
R_BSP_SoftwareDelay(5, BSP_DELAY_UNITS_MILLISECONDS); //加上延时确保所有传输都已完成
printf("\r\n传输计数(中断次数):dmac0_transfer_count = %d\r\n", dmac0_transfer_count);
/* 将传输后的数据与我们所期待的结果相比较 */
res = BufferCompare(DST_Buffer, Expected_DST_Buffer, BUFFER_SIZE);
printf("传输结果:");
/* 根据两者数据的比较结果进行判断 */
if( res != 0)
{
/* 源数据与传输后数据不相等时,LED1 亮(红色),表示传输失败 */
LED1_ON;
printf("<传输失败>\r\n");
}
else
{
/* 源数据与传输后数据相等时,LED1 亮(蓝色),表示传输成功 */
LED2_ON;
printf("<传输成功>\r\n");
}
printf("\r\nSRC:");
BufferShow_HexData(SRC_Buffer, BUFFER_SIZE);
printf("\r\nDST:(应与 Expected_DST 一致)");
BufferShow_HexData(DST_Buffer, BUFFER_SIZE);
printf("\r\nExpected_DST:");
BufferShow_HexData(Expected_DST_Buffer, BUFFER_SIZE);
while(1)
{
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
20.7.1.13. 缓冲区数据比较函数¶
/* 缓冲区数据比较函数
返回 0 表示两个缓冲区数据一致
*/
uint8_t BufferCompare(const uint32_t *pBuffer1, const uint32_t *pBuffer2, uint16_t BufferLength)
{
/* 数据长度递减 */
while(BufferLength--)
{
/* 判断两个数据源是否对应相等 */
if(*pBuffer1 != *pBuffer2)
{
/* 对应数据源不相等马上退出函数,并返回1 */
return 1;
}
/* 递增两个数据源的地址指针 */
pBuffer1++;
pBuffer2++;
}
/* 完成判断并且两组数据完全一致 */
return 0;
}
20.7.1.14. 打印缓冲区数据函数¶
/* 打印缓冲区数据函数
打印缓冲区数据:十六进制格式
*/
void BufferShow_HexData(const uint32_t *pBuffer, uint16_t BufferLength)
{
while(BufferLength)
{
if((BufferLength % 4) == 0)
printf("\r\n\t");
printf("0x%08X ", *pBuffer);
pBuffer++;
BufferLength--;
}
printf("\r\n");
}
20.7.2. 下载验证¶
首先通过宏 USE_MY_TRANSFER_INFOR_CONFIG (在 bsp_dmac_m2m.h 文件中) 来选择是使用我们自定义的传输信息,还是使用在 FSP 配置界面配置的传输信息。
其次通过宏 DMAC_TRANSFER_MODE (在 bsp_dmac_m2m.h 文件中)来选择不同的传输模式。
最后编译工程并下载到开发板上,打开串口助手可以查看程序运行打印的提示信息。 如果 DMAC 传输成功,则蓝色 LED2 亮起,如果传输失败则红色 LED1 亮起。
20.8. 实验2:DMAC+UART串口收发¶
20.8.1. 软件设计¶
20.8.1.1. 新建工程¶
本实验新建工程的步骤与上面的实验1基本一致。
- 对于 e2 studio 开发环境:
拷贝一份我们之前的 e2s 工程 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “20_DMAC_Using_UART”,最后再将它导入到我们的 e2 studio 工作空间中。
- 对于 Keil 开发环境:
拷贝一份我们之前的 Keil 工程 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “20_DMAC_Using_UART”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。
工程新建好之后,在工程根目录的 “src” 文件夹下面新建 “dmac” 文件夹, 再进入 “dmac” 文件夹里面新建源文件和头文件:“bsp_dmac.c” 和 “bsp_dmac.h”。 工程文件结构如下。
20_DMAC_Using_UART
├─ ......
└─ src
├─ led
│ ├─ bsp_led.c
│ └─ bsp_led.h
├─ debug_uart
│ ├─ bsp_debug_uart.c
│ └─ bsp_debug_uart.h
├─ dmac
│ ├─ bsp_dmac.c
│ └─ bsp_dmac.h
└─ hal_entry.c
20.8.1.2. FSP配置¶
按照与实验1相同的步骤加入两个 r_dmac 的 Stack,如下图所示。
添加后,配置这两个 DMAC 模块,一个配置为用于 UART 发送,一个配置为用于 UART 接收。
UART4 发送的 DMAC 配置如下:
UART4 接收的 DMAC 配置如下:
20.8.1.3. 编写代码¶
hal_entry 入口函数的代码如下。
/* 用户头文件包含 */
#include "led/bsp_led.h"
#include "debug_uart/bsp_debug_uart.h"
#include "dmac/bsp_dmac.h"
#define BUFFER_SIZE (37)
uint8_t sci_tx_data[BUFFER_SIZE] = {"embedfire-野火 www.embedfire.com\r\n"};
uint8_t sci_rx_data[BUFFER_SIZE];
void hal_entry(void)
{
/* TODO: add your own code here */
LED_Init(); // LED 初始化
Debug_UART4_Init(); // SCI4 UART 调试串口初始化
printf("\r\n实验2:DMAC+UART串口收发\r\n");
printf("- 蓝、绿灯亮 - 接收成功、发送成功\r\n");
printf("- 蓝色灯亮 - 接收失败、发送成功\r\n");
printf("- 绿色灯亮 - 接收成功、发送失败\r\n");
printf("- 红色灯亮 - 接收失败、发送失败\r\n");
printf("\r\n发送内容如下:\r\n");
/* 通过CPU和中断处理程序发送数据 */
R_SCI_UART_Write(&g_uart4_ctrl, &sci_tx_data[0], BUFFER_SIZE);
/* 等待传输完成中断 - 标志位在 UART 的回调函数中 debug_uart4_callback() */
while( false == uart_send_complete_flag );
uart_send_complete_flag = false;
/* 通过 DMAC 和中断处理程序发送数据 */
/* 清零 ICU IELSR 寄存器 */
R_ICU->IELSR[SCI4_RXI_IRQn] = 0U;
R_ICU->IELSR[SCI4_TXI_IRQn] = 0U;
/* 配置串口接收DMA 源地址、目标地址、长度 */
set_transfer_dst_src_address(g_transfer_dmac_sci4_rx.p_cfg,
&R_SCI4->RDR,
&sci_rx_data[0]);
set_transfer_length(g_transfer_dmac_sci4_rx.p_cfg, BUFFER_SIZE);
/* 配置串口发送DMA 源地址、目标地址、长度 */
set_transfer_dst_src_address(g_transfer_dmac_sci4_tx.p_cfg,
&sci_tx_data[0],
(void*)&R_SCI4->TDR);
set_transfer_length(g_transfer_dmac_sci4_tx.p_cfg, BUFFER_SIZE);
/* 开启DMAC */
DMAC_Init();
/* 手动触发传输数据寄存器空中断 */
R_SCI4->SCR_b.TE = 0;
R_SCI4->SCR_b.RE = 0;
R_SCI4->SCR |= (0xF0);
// R_SCI4->SCR_b.TE = 0;
// R_SCI4->SCR_b.TIE = 0;
// uint8_t temp = (uint8_t)(R_SCI4->SCR & 0x53); //0x53 = 101 0011
// R_SCI4->SCR = (uint8_t)(0xa0 | temp); //0x0a = 1010 0000
while(1)
{
if(( 1 == dmac_sci4_tx_flag ) && ( 1 == dmac_sci4_rx_flag ))
{
//蓝、绿灯亮 - 接收成功、发送成功
LED1_OFF;
LED2_ON;
LED3_ON;
}
else if(( 1 == dmac_sci4_tx_flag ) && ( 0 == dmac_sci4_rx_flag ))
{
//蓝色灯亮 - 接收失败、发送成功
LED1_OFF;
LED2_ON;
LED3_OFF;
}
else if(( 0 == dmac_sci4_tx_flag ) && ( 1 == dmac_sci4_rx_flag ))
{
//绿色灯亮 - 接收成功、发送失败
LED1_OFF;
LED2_OFF;
LED3_ON;
}
else
{
//红色灯亮 - 接收失败、发送失败
LED1_ON;
LED2_OFF;
LED3_OFF;
}
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
/**
* 判断指定长度的两个数据源是否完全相等,
* 如果完全相等返回1,只要其中一对数据不相等返回0
*/
uint8_t Buffercmp(const uint32_t* pBuffer, uint32_t* pBuffer1, uint16_t BufferLength)
{
/* 数据长度递减 */
while(BufferLength--)
{
/* 判断两个数据源是否对应相等 */
if(*pBuffer != *pBuffer1)
{
/* 对应数据源不相等马上退出函数,并返回0 */
return 0;
}
/* 递增两个数据源的地址指针 */
pBuffer++;
pBuffer1++;
}
/* 完成判断并且对应数据相对 */
return 1;
}
在 bsp_dmac.c 文件中编写 DMAC 初始化等函数,如下所示。
/* 初始化 DMAC 模块 */
void DMAC_Init(void)
{
fsp_err_t err;
/* 配置发送 */
err = g_transfer_on_dmac.open(g_transfer_dmac_sci4_tx.p_ctrl, g_transfer_dmac_sci4_tx.p_cfg);
assert(FSP_SUCCESS == err);
err = g_transfer_on_dmac.enable(g_transfer_dmac_sci4_tx.p_ctrl);
assert(FSP_SUCCESS == err);
/* 配置接收 */
err = g_transfer_on_dmac.open(g_transfer_dmac_sci4_rx.p_ctrl, g_transfer_dmac_sci4_rx.p_cfg);
assert(FSP_SUCCESS == err);
err = g_transfer_on_dmac.enable(g_transfer_dmac_sci4_rx.p_ctrl);
assert(FSP_SUCCESS == err);
}
void set_transfer_length(transfer_cfg_t const * p_config, volatile uint16_t _length)
{
p_config->p_info->length = _length;
}
/* 设置传输的源地址和目的地址 */
void set_transfer_dst_src_address(transfer_cfg_t const * const p_config,
const volatile uint8_t * _p_src, const volatile uint8_t * _p_dest)
{
p_config->p_info->p_src = (void const * volatile) _p_src;
p_config->p_info->p_dest = (void * volatile) _p_dest;
}
在 bsp_dmac.c 文件中分别编写 DMAC 发送完成中断和接收完成中断的回调函数,如下所示。
// DMA 传输完成标志位
volatile uint8_t dmac_sci4_tx_flag = 0;
volatile uint8_t dmac_sci4_rx_flag = 0;
/* DMAC 发送中断回调函数 */
void transfer_dmac_sci4_tx_callback(dmac_callback_args_t *p_args)
{
(void)(p_args);
dmac_sci4_tx_flag = 1;
}
/* DMAC 接收回调函数 */
void transfer_dmac_sci4_rx_callback(dmac_callback_args_t *p_args)
{
(void)(p_args);
dmac_sci4_rx_flag = 1;
}
20.8.2. 实验现象¶
比较好的接收方式还是直接使用串口接收,使用环形队列,来接收不定长的数据。
运行代码时,数据字符串”embedfire-野火 www.embedfire.com”将传输两次。 第一次是通过CPU和中断,第二次是通过DMAC和中断。
然后,使用串口调试助手,向开发板发送相同字符串 “embedfire-野火 www.embedfire.com”,带回车换行字符。
如果只有蓝色灯亮则代表接收失败、发送成功,如果只有绿色灯亮则代表接收成功、发送失败, 如果只有红色灯亮则代表接收失败、发送失败,蓝灯和绿灯同时亮起则是接收和发送都成功。
触发 DMA 的关键是禁用 ICU 中断使能寄存器中的中断。 如果不这样做,则 CPU 和 DMA 之间存在争用情况,并且无法获得预期的结果。
20.9. 实验3:DTC外部中断触发传输¶
20.9.2. 软件设计¶
20.9.2.1. 新建工程¶
- 对于 e2 studio 开发环境:
拷贝一份我们之前的 e2s 工程 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “20_DTC_External_IRQ”,最后再将它导入到我们的 e2 studio 工作空间中。
- 对于 Keil 开发环境:
拷贝一份我们之前的 Keil 工程 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “20_DTC_External_IRQ”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。
工程新建好之后,在工程根目录的 “src” 文件夹下面新建 “dtc” 文件夹, 再进入 “dtc” 文件夹里面新建源文件和头文件:“bsp_dtc.c” 和 “bsp_dtc.h”。 工程文件结构如下。
20_DTC_External_IRQ
├─ ......
└─ src
├─ led
│ ├─ bsp_led.c
│ └─ bsp_led.h
├─ debug_uart
│ ├─ bsp_debug_uart.c
│ └─ bsp_debug_uart.h
├─ dtc
│ ├─ bsp_dtc.c
│ └─ bsp_dtc.h
└─ hal_entry.c
20.9.2.2. FSP配置¶
在前面的“外部中断”章节,相信大家已经学会如何配置外部中断了。 这次实验我们要使用外部中断来触发/激活 DTC 传输,而使用外部中断要用到我们的按键。
打开该工程的 FSP 配置界面。我们先加入外部中断。 点到“Pins”页面,在 ICU0 里面找到 IRQ09,这里我们选用 P004 引脚(SW2按键连接的引脚)连接到外部中断 IRQ09。
然后点到“Stacks”页面,按照“New Stack”→“Input”→“External IRQ”的步骤添加一个ICU模块来配置外部中断。 ICU 模块的配置可按照如下图所示进行配置,中断触发方式默认选择上升沿触发, 中断优先级设置比 UART 中断大一点,设置为优先级 10。
接着我们在“Stacks”页面继续加入 DTC 模块,按如下图所示步骤添加。
我们按如下图配置一下 DTC:
DTC 配置项(与上图相对应):
属性 |
描述 |
---|---|
Name |
名字。 上图配置为 g_transfer_dtc, 按照自己命名习惯设置、并且与代码中的对应即可。 |
Mode |
模式:DTC传输模式选择。默认选择正常模式即可。 |
Transfer Size |
传输大小:传输数据单元的大小。默认设置为2字节即可。 |
Destination Address Mode |
目标地址模式:默认设置为固定。 |
Source Address Mode |
源地址模式:默认设置为固定。 |
Repeat Area (Unused in Normal Mode) |
重复区域(正常模式下不使用):源地址。 |
Destination Pointer |
目标指针:NULL(在这里不方便确定传输地址,后续在代码部分配置)。 |
Source Pointer |
源指针:NULL(在这里不方便确定传输地址,后续在代码部分配置)。 |
Interrupt Frequency |
中断频率 (选择什么时候触发中断):
|
Number of Transfers |
传输次数:指定正常和重复模式的传输次数。这里设置传输1次。 |
Number of Blocks (Valid only in Block Mode) |
块数量:指定要在块模式下传输的块数。 |
Activation Source |
触发源:选择用来激活DTC传输的事件。 注意 DTC 不可使用软件触发,这里我们选由 ICU IRQ9 触发。 |
配置完成后,直接右上角点击生成代码。
注:读者可能会疑惑不需要设置 DTC 中断回调函数?
20.9.2.3. 使用FSP配置器生成的配置¶
下面的是与我们之前在 FSP 配置界面上的配置等效的配置代码:
/* FSP配置界面的传输信息配置(正常传输模式)等效于下面 fsp_transfer_info_normal 里的配置(除了源地址和目标地址)
源地址和目标地址在FSP配置界面设置的话不太方便,我们会在 DTC_Init 函数里设置。
关于偏移值(Offset value)和源缓冲大小(Source Buffer Size),在 transfer_info_t 里没有这两项设置,建议在FSP配置界面设置
- 偏移值只有在地址模式是偏移模式的情况下才会用到;
- 而源缓冲大小与之相关的功能本例程不会涉及到,所以暂不考虑。
下面的 fsp_transfer_info_normal 仅作为对比参考,在本例程中是没有用到的。
*/
//transfer_info_t fsp_transfer_info_normal =
//{
// .transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_FIXED, //每次传输后,目标地址指针固定不变
// .transfer_settings_word_b.repeat_area = TRANSFER_REPEAT_AREA_SOURCE, //源区域重复 (正常模式下无效)
// .transfer_settings_word_b.irq = TRANSFER_IRQ_END, //传输完成后中断
// .transfer_settings_word_b.chain_mode = TRANSFER_CHAIN_MODE_DISABLED, //不使能(DMAC没有该功能,仅DTC有)
// .transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_FIXED, //每次传输后,源地址指针固定不变
// .transfer_settings_word_b.size = TRANSFER_SIZE_2_BYTE, //每次传输2字节
// .transfer_settings_word_b.mode = TRANSFER_MODE_NORMAL, //正常传输模式
// .p_dest = (void *) DST_Buffer, //目标地址
// .p_src = (void const *) SRC_Buffer, //源地址
// .num_blocks = 0, //指定传输的块数(正常模式和重复模式下均无效,仅块模式下有效)
// .length = 1, //指定传输的长度(即正常的传输次数或重复模式下重复大小 或 块模式下传输的块大小)
//};
// 按照上述传输信息配置,期待的正确传输结果为:
uint32_t Expected_DST_Buffer[BUFFER_SIZE] = {
0x00000304,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000 };
20.9.2.4. 使用代码配置:正常传输模式¶
下面是 DTC 在正常模式下传输的配置代码:
/* 正常传输模式 */
transfer_info_t my_transfer_info_normal =
{
.transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, //每次传输后,目标地址指针都会增加
.transfer_settings_word_b.repeat_area = TRANSFER_REPEAT_AREA_SOURCE, //源区域重复 (正常模式下无效)
.transfer_settings_word_b.irq = TRANSFER_IRQ_END, //传输完成后中断
.transfer_settings_word_b.chain_mode = TRANSFER_CHAIN_MODE_DISABLED, //不使能(DMAC没有该功能,仅DTC有)
.transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, //每次传输后,源地址指针都会增加
.transfer_settings_word_b.size = TRANSFER_SIZE_4_BYTE, //每次传输4字节
.transfer_settings_word_b.mode = TRANSFER_MODE_NORMAL, //正常传输模式
.p_dest = (void *) DST_Buffer, //目标地址
.p_src = (void const *) SRC_Buffer, //源地址
.num_blocks = 0, //指定传输的块数(正常模式和重复模式下均无效,仅块模式下有效)
.length = 1, //指定传输的长度(即正常的传输次数或重复模式下重复大小 或 块模式下传输的块大小)
};
// 按照上述传输信息配置,期待的正确传输结果为:
uint32_t Expected_DST_Buffer[BUFFER_SIZE] = {
0x01020304,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000 };
20.9.2.5. 使用代码配置:重复传输模式¶
下面是 DTC 在重复传输模式下传输的配置代码:
/* 重复传输模式 */
transfer_info_t my_transfer_info_repeat =
{
.transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, //每次传输后,目标地址指针都会增加
.transfer_settings_word_b.repeat_area = TRANSFER_REPEAT_AREA_SOURCE, //源区域重复 (正常模式下无效)
.transfer_settings_word_b.irq = TRANSFER_IRQ_EACH, //传输完成后中断
.transfer_settings_word_b.chain_mode = TRANSFER_CHAIN_MODE_DISABLED, //不使能(DMAC没有该功能,仅DTC有)
.transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, //每次传输后,源地址指针都会增加
.transfer_settings_word_b.size = TRANSFER_SIZE_4_BYTE, //每次传输4字节
.transfer_settings_word_b.mode = TRANSFER_MODE_REPEAT, //重复传输模式
.p_dest = (void *) DST_Buffer, //目标地址
.p_src = (void const *) SRC_Buffer, //源地址
.num_blocks = 0, //指定传输的块数(正常模式和重复模式下均无效,仅块模式下有效)
.length = 2, //指定传输的长度(即正常的传输次数或重复模式下重复大小 或 块模式下传输的块大小)
};
// 按照上述传输信息配置,期待的正确传输结果为:
uint32_t Expected_DST_Buffer[BUFFER_SIZE] = {
0x01020304,0x05060708,0x01020304,0x05060708,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000 };
20.9.2.6. 使用代码配置:块传输模式¶
下面是 DTC 在块传输模式下传输的配置代码:
/* 块传输模式 */
transfer_info_t my_transfer_info_block =
{
.transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, //每次传输后,目标地址指针都会增加
.transfer_settings_word_b.repeat_area = TRANSFER_REPEAT_AREA_SOURCE, //源区域重复 (正常模式下无效)
.transfer_settings_word_b.irq = TRANSFER_IRQ_END, //传输完成后中断
.transfer_settings_word_b.chain_mode = TRANSFER_CHAIN_MODE_DISABLED, //不使能(DMAC没有该功能,仅DTC有)
.transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, //每次传输后,源地址指针都会增加
.transfer_settings_word_b.size = TRANSFER_SIZE_4_BYTE, //每次传输4字节
.transfer_settings_word_b.mode = TRANSFER_MODE_BLOCK, //块传输模式
.p_dest = (void *) DST_Buffer, //目标地址
.p_src = (void const *) SRC_Buffer, //源地址
.num_blocks = 1, //指定传输的块数(正常模式和重复模式下均无效,仅块模式下有效)
.length = BUFFER_SIZE, //指定传输的长度(即正常的传输次数或重复模式下重复大小 或 块模式下传输的块大小)
};
// 按照上述传输信息配置,期待的正确传输结果为:
uint32_t Expected_DST_Buffer[BUFFER_SIZE] = {
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80 };
20.9.2.7. DTC初始化函数¶
DTC 初始化函数如下所示:
/* DTC 初始化函数 */
void DTC_Init(void)
{
fsp_err_t err;
/* 使用 FSP 界面的配置:需要先重新设置传输的源地址和目的地址 */
#ifndef USE_MY_TRANSFER_INFOR_CONFIG
set_transfer_dst_src_address(&g_transfer_dtc_cfg, SRC_Buffer, DST_Buffer);
#endif
err = R_DTC_Open(&g_transfer_dtc_ctrl, &g_transfer_dtc_cfg);
assert(FSP_SUCCESS == err);
/* 使用我们新的自定义的传输信息:重新配置传输 */
#ifdef USE_MY_TRANSFER_INFOR_CONFIG
// 根据我们要使用的传输模式进行选择配置:
#if (DTC_TRANSFER_MODE == DTC_TRANSFER_NORMAL_MODE) //正常模式
err = R_DTC_Reconfigure(&g_transfer_dtc_ctrl, &my_transfer_info_normal);
assert(FSP_SUCCESS == err);
#elif (DTC_TRANSFER_MODE == DTC_TRANSFER_REPEAT_MODE) //重复模式
err = R_DTC_Reconfigure(&g_transfer_dtc_ctrl, &my_transfer_info_repeat);
assert(FSP_SUCCESS == err);
#elif (DTC_TRANSFER_MODE == DTC_TRANSFER_BLOCK_MODE) //块模式
err = R_DTC_Reconfigure(&g_transfer_dtc_ctrl, &my_transfer_info_block);
assert(FSP_SUCCESS == err);
#endif //DTC_TRANSFER_MODE
#endif //USE_MY_TRANSFER_INFOR_CONFIG
}
20.9.2.8. 按键外部中断回调函数¶
按键外部中断回调函数如下所示:
/* 按键按下标志 */
volatile bool key_sw2_press = false;
/* 按键中断回调函数 */
void icu_external_irq_callback(external_irq_callback_args_t *p_args)
{
/* 判断中断通道 */
if (9 == p_args->channel)
{
key_sw2_press = true; // 按键SW2按下
}
}
20.9.2.9. hal_entry入口函数¶
/* 用户头文件包含 */
#include "led/bsp_led.h"
#include "debug_uart/bsp_debug_uart.h"
#include "dtc/bsp_dtc.h"
extern const uint32_t SRC_Buffer[BUFFER_SIZE];
extern uint32_t DST_Buffer[BUFFER_SIZE];
extern uint32_t Expected_DST_Buffer[BUFFER_SIZE];
extern volatile bool dtc_complete_transmission_sign;
uint8_t BufferCompare(const uint32_t *pBuffer1, const uint32_t *pBuffer2, uint16_t BufferLength);
void BufferShow_HexData(const uint32_t *pBuffer, uint16_t BufferLength);
void hal_entry(void)
{
/* TODO: add your own code here */
uint8_t res;
LED_Init(); // LED 初始化
Debug_UART4_Init(); // SCI4 UART 调试串口初始化
/* 初始化 DTC */
DTC_Init();
printf("这是一个 DTC 存储器到存储器的传输实验例程\r\n");
printf("打开串口助手,查看接收窗口打印的相关提示信息\r\n");
printf("按下按键 SW2 激活 DTC 传输\r\n");
printf("观察板载LED灯,本实验使用两个LED灯来指示 DTC 传输结果\r\n");
printf("- DTC 数据传输失败,则 LED1 亮(红色)\r\n");
printf("- DTC 数据传输成功,则 LED2 亮(蓝色)\r\n");
printf("--------------------------------------------\r\n");
/* Open ICU module */
R_ICU_ExternalIrqOpen(&g_external_irq9_ctrl, &g_external_irq9_cfg);
/* 允许中断 */
R_ICU_ExternalIrqEnable(&g_external_irq9_ctrl);
/* 使能 DTC 模块 */
R_DTC_Enable(&g_transfer_dtc_ctrl);
/*************************************/
/* 使用按键外部中断触发激活 DTC 传输 */
/*************************************/
#ifndef USE_MY_TRANSFER_INFOR_CONFIG
/* 根据 FSP 配置界面的传输信息进行传输 */
/* 等待激活 DTC 传输 */
for (uint16_t i = 0; i < 1; i++)
{
/* 等待按键按下,按键按下一次即激活一次 DTC 传输 */
while (false == key_sw2_press);
key_sw2_press = false;
/* 等待本次传输完成 */
R_BSP_SoftwareDelay(5, BSP_DELAY_UNITS_MILLISECONDS); //加上延时确保传输完成
}
#else // 下面的这些是使用自定义的传输配置信息配置
#if (DTC_TRANSFER_MODE == DTC_TRANSFER_NORMAL_MODE) //正常模式
/* 等待激活 DTC 传输 */
for (uint16_t i = 0; i < 1; i++)
{
/* 等待按键按下,按键按下一次即激活一次 DTC 传输 */
while (false == key_sw2_press);
key_sw2_press = false;
/* 等待本次传输完成 */
R_BSP_SoftwareDelay(5, BSP_DELAY_UNITS_MILLISECONDS); //加上延时确保传输完成
}
#elif (DTC_TRANSFER_MODE == DTC_TRANSFER_REPEAT_MODE) //重复模式
/* 等待激活 DTC 传输 */
for (uint16_t i = 0; i < 4; i++)
{
/* 等待按键按下,按键按下一次即激活一次 DTC 传输 */
while (false == key_sw2_press);
key_sw2_press = false;
/* 等待本次传输完成 */
R_BSP_SoftwareDelay(5, BSP_DELAY_UNITS_MILLISECONDS); //加上延时确保传输完成
}
#elif (DTC_TRANSFER_MODE == DTC_TRANSFER_BLOCK_MODE) //块模式
/* 等待激活 DTC 传输 */
for (uint16_t i = 0; i < 1; i++)
{
/* 等待按键按下,按键按下一次即激活一次 DTC 传输 */
while (false == key_sw2_press);
key_sw2_press = false;
/* 等待本次传输完成 */
R_BSP_SoftwareDelay(5, BSP_DELAY_UNITS_MILLISECONDS); //加上延时确保传输完成
}
#endif //DTC_TRANSFER_MODE
#endif //USE_MY_TRANSFER_INFOR_CONFIG
/* 将传输后的数据与我们所期待的结果相比较 */
res = BufferCompare(DST_Buffer, Expected_DST_Buffer, BUFFER_SIZE);
printf("传输结果:");
/* 根据两者数据的比较结果进行判断 */
if( res != 0)
{
/* 源数据与传输后数据不相等时,LED1 亮(红色),表示传输失败 */
LED1_ON;
printf("<传输失败>\r\n");
}
else
{
/* 源数据与传输后数据相等时,LED1 亮(蓝色),表示传输成功 */
LED2_ON;
printf("<传输成功>\r\n");
}
printf("\r\nSRC:");
BufferShow_HexData(SRC_Buffer, BUFFER_SIZE);
printf("\r\nDST:(应与 Expected_DST 一致)");
BufferShow_HexData(DST_Buffer, BUFFER_SIZE);
printf("\r\nExpected_DST:");
BufferShow_HexData(Expected_DST_Buffer, BUFFER_SIZE);
while(1)
{
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
20.9.3. 实验现象¶
首先通过宏 USE_MY_TRANSFER_INFOR_CONFIG (在 bsp_dtc.h 文件中) 来选择是使用我们自定义的传输信息,还是使用在 FSP 配置界面配置的传输信息。
其次通过宏 DMAC_TRANSFER_MODE (在 bsp_dtc.h 文件中)来选择不同的传输模式。
最后编译工程并下载到开发板上,打开串口助手可以查看程序运行打印的提示信息。 按下启明6M5开发板上的按键 SW2,即触发 DTC 传输。 如果 DTC 传输成功,则蓝色 LED2 亮起,如果传输失败则红色 LED1 亮起。
20.10. 实验4:DTC+UART串口收发¶
20.10.1. 软件设计¶
20.10.1.1. 新建工程¶
本实验新建工程的步骤与上面的实验3基本一致。
- 对于 e2 studio 开发环境:
拷贝一份我们之前的 e2s 工程 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “20_DTC_Using_UART”,最后再将它导入到我们的 e2 studio 工作空间中。
- 对于 Keil 开发环境:
拷贝一份我们之前的 Keil 工程 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “20_DTC_Using_UART”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。
工程新建好之后,在工程根目录的 “src” 文件夹下面新建 “dtc” 文件夹, 再进入 “dtc” 文件夹里面新建源文件和头文件:“bsp_dtc.c” 和 “bsp_dtc.h”。 工程文件结构如下。
20_DTC_External_Interrupt
├─ ......
└─ src
├─ led
│ ├─ bsp_led.c
│ └─ bsp_led.h
├─ debug_uart
│ ├─ bsp_debug_uart.c
│ └─ bsp_debug_uart.h
├─ dtc
│ ├─ bsp_dtc.c
│ └─ bsp_dtc.h
└─ hal_entry.c
20.10.1.2. FSP配置¶
打开该工程的 FSP 配置界面。
在“Stacks”一栏里面我们可以看到,工程“19_UART_Receive_Send”已有 UART 模块的配置了,如下图所示。
在这个已有的 UART 模块上面,我们下一步直接在该 UART 模块的底层添加 DTC 传输驱动。如下图所示。
按照如下的步骤操作,为 UART 的发送和接收都添加 DTC 底层驱动:
UART 发送:用鼠标依次点击“Add DTC Driver for Transmission”→“New”→“Transfer (r_dtc)”。
UART 接收:用鼠标依次点击“Add DTC Driver for Reception”→“New”→“Transfer (r_dtc)”。
我们注意到:“Add DTC Driver for Transmission”之后有“[Recommended but optional]”的字眼、 “Add DTC Driver for Reception”之后有“[Not recommended]”的字眼,即: 瑞萨FSP库官方推荐我们使用 DTC 来发送UART数据(虽然也可以不使用 DTC),但不推荐使用 DTC 来接收UART数据。 后面我们再来讲讲这是为什么。
添加完成后如下图所示:
上图中显示的 UART 模块显示为红色,是报错的,对此还需要修改下 UART 模块的配置。 点击 UART 模块,在属性配置里将“Common”→“DTC Support”属性由“Disable”改为“Enable”即可。
接着顺便也点击下UART下面的 DTC 模块,会发现DTC是基本默认配置好的了,不用我们更改, 也无法更改,能改的只有名字,我们把名字改一下,就可以点击生成代码,然后编译了。
与上面使用DTC一些不同的地方是,瑞萨已经将DTC融入到UART中了, 所以我们在使用过程中基本感觉不到我们在使用DTC传输数据给UART。
比如,正常情况下会有一个open函数去开启DTC的,但现在不同自己去调用该函数, 因为我们在使用 g_uart_on_sci.open 函数去开启串口时,就已经在内部调用了 DTC 的开启函数。
我们只需调用 g_transfer_on_dtc.enable 函数进行使能就好了。
err = g_transfer_on_dtc.enable(g_uart4.p_cfg->p_transfer_rx->p_ctrl);
assert(FSP_SUCCESS == err);
使能后就可以像正常使用串口基本一样了。 区别主要在于:
使用DTC后,串口可以不用等待发送完成,只需激活DTC后自动进行搬运数据,不占用CPU;
中断开启DTC后相当于DTC接管了串口的中断。
使用 g_uart_on_sci.write 或者 R_SCI_UART_Write 函数将字符串数据发送到串口,该函数内部会采用DTC来传输。
uint8_t sci_tx_data[] = {"embedfire-野火 www.embedfire.com\r\n"};
/* 在while(1)中循环调用 */
// 可以尝试在 configuration.xml 中将 DTC 模块去除,再进行代码调试,看看会有什么区别
g_uart_on_sci.write(g_uart4.p_ctrl, (uint8_t *)(sci_tx_data), sizeof(sci_tx_data)/sizeof(sci_tx_data[0]));
20.10.1.3. 为什么不用DTC接收串口数据¶
在实际使用过程中 DTC 有一些限制:
在本实验中使用DTC发送串口数据, 但我们没有配置使用DTC接收串口数据,因为具体配置过程可能会很复杂, 而且我们并不知道串口具体要接收多少数据,而DTC是传输完成了才会产生中断, 这样就可能存在,我设定接受数据长度为128,但实际传输可能只有100,这时候就不会产生中断, 虽然接收了数据,但我们不知道。
如果使用DTC接口进行传输,则传输大小必须小于或等于64K字节。
所以,虽然在 FSP 配置页面上,显示有 DTC 这个选项,但不建议去使用DTC进行接收。
20.10.2. 实验现象¶
运行例程代码时,可以在串口助手看到字符串 “embedfire-野火 www.embedfire.com” , 以一秒传输一次的频率传输字符串,led也是一秒变换一次。