SAM4E单片机之旅——20、DMAC之使用Multi-buffer进行内存拷贝
这次使用这个DMAC的Multi-buffer传输功能 , 将两个缓冲区的内容拷贝至一个连续的缓冲区中 。
一、 DMAC在M4中 , DMA控制器(DMAC)比外设DMA控制器(PDC)要复杂 , 但是功能更加强大 。
为适应不同的传输要求 , DMAC 可以进行灵活的自定义配置 , 甚至配备了一个FIFO缓存 。比如可以为源设备和目标设备分别设定传输时 , 地址的变动方式(递增、递减或固定);以及一次传输的数据量(字节、半字或字) 。
DMAC有4个通道 , 每个通道可以进行一个传输任务 。进行传输的设备可分为“内存”及“非内存”:内存表示随时可以对该设备进行访问 , 而非内存表示需要一个信号(握手接口)来触发或控制对设备的访问 。握手接口可以选择硬件或软件的 , 并且可以在传输的过程中动态配置 。
另外 , 比起PDC只能设置下一次传输的参数(传输地址 , 数据量大小等) , DMAC可以先在内存中保存好若干次传输的参数 , 然后自动进行多次传输(Multi-buffer传输) 。
二、 Multi-buffer传输的实现机制每个通道有若干个寄存器 。其中:源地址和目的地址寄存器(SADDR和DADDR) , 描述符地址寄存器(DSCR) , 控制器存器(CTRLA和CTRLB)这几个寄存器可以根据需要进行自动修改 。在内存中有一块区域(LLI) , 连续地储存着这几个寄存器的目标设置 。然后就像一个链表一样 , DSCR表示下一个区域的地址:
在启用通道时 , 如果DSCR为0 , 则表示只需进行一次传输 , 在传输完成后就关闭通道 。
如果DSCR不为0 , 则表示进行多次传输 , 而这几个寄存器的更新过程如下:
获取DSCR指向的LLI的内容 。如果DSCR为0 , 则任务结束 。
根据当前CTRLB寄存器的内容 , 判断是否需要根据该LLI更新SADDR及DADDR 。然后根据该LLI更新其余寄存器(CTRLA , CTRLB , DSCR) 。
根据新的寄存器内容进行传输 。
传输完成后 , 将CTRLA的内容回写至内存中(传输中仅有该寄存器的BTSIZE和DONE字段会发生该变) 。
根据通道CFG寄存器的Stop On Done(SOD)字段判断是否需要重新执行以上过程 。
所以 , 在启用通道前 , 除了要设置好CFG寄存器外 , 也需要设置好CTRLB 。
三、 实现思路重申一下目标:将两个缓冲区的内容拷贝至一个连续的缓冲区中 。
由于源缓冲区有两个 , 所以我们将使用两个LLI 。其中每个LLI的SADDR指向每个源缓冲区的首地址 , 并且在每次获取LLI时 , 更新SADDR 。而由于目标缓冲区是连续的 , 所以不需要更新DADDR 。
然后在启用通道前 , 设置好DADDR 。同时 , 设置CTRLB , 该通道不从LLI中更新DADDR地址;设置好DSCR , 使其指向第一个LLI 。
四、 使用LLI定义LLI结构体 。
LLI的内存布局不复杂 , 但是使用结构体来进行操作也很有助于简化工作 。而且由于布局简单 , 也不用太关注内存对齐的细节 。(另外 , 在使用LLI时 , 需要它的地址是字对齐的 。)
1234567typedefstruct_lli{uint32_t SADDR;uint32_t DADDR;uint32_t CTRLA;uint32_t CTRLB;uint32_t DSCR;}LLI;LLI的初始化 。
由于两个LLI的设置有许多相同的部分 , 所以将共同的部分抽象出来 。
12345678910111213141516171819202122// lli: 需要初始化的LLI的地址// saddr: 源地址// btsize: 传输次数// next_lli: 下一个LLI的地址 。如果是最后一个LLI , 该参数为NULL即可voidInitLLI(LLI* lli, void* saddr, uint16_t btsize, LLI* next_lli){lli->SADDR = (uint32_t)saddr;lli->DADDR = 0; // DADDR 不会被使用 , 初始化为即可lli->DSCR = DMAC_DSCR_DSCR_Msk & (uint32_t)next_lli;lli->CTRLA =DMAC_CTRLA_BTSIZE(btsize) // 传输次数| DMAC_CTRLA_SRC_WIDTH_WORD // 源设备一次传输一个字| DMAC_CTRLA_DST_WIDTH_WORD // 目标设备一次传输一个字;lli->CTRLB =DMAC_CTRLB_SRC_DSCR_FETCH_FROM_MEM // 从LLI中更新SRC地址| DMAC_CTRLB_DST_DSCR_FETCH_DISABLE // 不更新DST地址| DMAC_CTRLB_FC_MEM2MEM_DMA_FC // 设备类型:内存至内存| DMAC_CTRLB_SRC_INCR_INCREMENTING // 传输时 , 源地址递增| DMAC_CTRLB_DST_INCR_INCREMENTING // 传输时 , 目标地址递增;}
五、 实现过程缓冲区 。
12345678// 源缓冲区uint32_t src1[2];uint32_t src2[3];// 目标缓冲区uint32_t dst[5];// 向源缓冲区时填充内容src1[0] = 50; src1[1] = 51;src2[0] = 52; src2[1] = 53; src2[2] = 54;设置LLI 。
注意 , 要确保LLI的实例在整个程序的运行过程中都是有效的 。比如如果LLI是储存在函数的栈中的话 , 那么函数退出后 , 该LLI即无效了 。所以可以选择在堆中分配LLI实例的空间 , 或是将其定义为全局变量 , 也可以在main函数中定义实例 。
123LLI first_lli, last_lli;InitLLI(&first_lli, (void*)src1, 2, &last_lli);InitLLI(&last_lli, (void*)src2, 3, 0);启用DMAC 。
12345// PMCPMC->PMC_PCER0 = 1 << ID_DMAC;DMAC->DMAC_GCFG =DMAC_GCFG_ARB_CFG_ROUND_ROBIN; // 轮转优先级DMAC->DMAC_EN = DMAC_EN_ENABLE;配置通道 。
1234567891011121314151617// 使用的通道为通道0#define DMAC_CH 0// 使DSCR指向first_lliDMAC->DMAC_CH_NUM[DMAC_CH].DMAC_DSCR =(uint32_t)(void*)(&first_lli);// 设置目标地址DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_DADDR =(uint32_t)(void*) dst;// 设置CTRLB , 使通道从LLI中更新源地址DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_CTRLB =DMAC_CTRLB_SRC_DSCR_FETCH_FROM_MEM| DMAC_CTRLB_DST_DSCR_FETCH_DISABLE;// 配置CFG寄存器DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_CFG =DMAC_CFG_SOD_DISABLE| DMAC_CFG_FIFOCFG_ALAP_CFG;启用通道 。
1DMAC->DMAC_CHER = DMAC_CHER_ENA0 << DMAC_CH;等待通道关闭 , 即传输完成 。
12constuint32_t check_bit = DMAC_CHSR_ENA0 << DMAC_CH;while( (DMAC->DMAC_CHSR & check_bit) != 0);【SAM4E单片机之旅——20、DMAC之使用Multi-buffer进行内存拷贝】
推荐阅读
- PIC12C508单片机控制灯光源程序
- pic单片机控制HT1621 LCD程序
- PIC单片机音乐播放程序
- PIC单片机系列介绍
- PIC单片机模拟读写24C01 EEPROM
- 单片机C51的计数器
- 深入理解晶振,单片机晶振脚原理是什么?
- 单片机晶振必要性探讨,单片机晶振常见问题分析
- pic单片机 TIMER0控制流水灯
- 单片机显示原理_LCD1602