| **银杏科技有限公司旗下技术文档发布平台** |||| |技术支持电话|**0379-69926675-801**||| |技术支持邮件|Gingko@vip.163.com||| ^ 版本 ^ 日期 ^ 作者 ^ 修改内容 ^ | V1.0 | 2020-07-04 | gingko | 初次建立 | ===== 实验十一:DMA实验——存储器到存储器的传输 ===== ==== 一、 实验目的与意义 ==== - 了解STM32 DMA结构。 - 了解STM32 DMA特征。 - 掌握DMA的使用方法。 - 掌握STM32 HAL库中DMA属性的配置方法。 - 掌握KEIL MDK 集成开发环境使用方法。 ==== 二、 实验设备及平台 ==== - iCore4 双核心板[[https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-22598974120.15.5923532fsFrHiE&id=551864196684|点击购买]]。 - JLINK(或相同功能)仿真器[[https://item.taobao.com/item.htm?id=554869837940|点击购买]]。 - Micro USB线缆。 - Keil MDK 开发平台。 - STM32CubeMX开发平台。 - 装有WIN XP(及更高版本)系统的计算机。 ==== 三、 实验原理 ==== === 1、DMA简介 === * 直接存储器访问(DMA)用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。可以在无需任何CPU操作的情况下通过DMA快速移动数据。这样节省的CPU资源可供其它操作使用。DMA控制器基于复杂的总线矩阵架构,将功能强大的双AHB主总线架构与独立的FIFO结合在一起,优化了系统带宽。 * 两个DMA控制器总共有16个数据流(每个控制器8个),每一个DMA控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达8个通道(或称请求)。每个通道都有一个仲裁器,用于处理DMA请求间的优先级。 === 2、DMA的主要特性 === *  双AHB主总线架构,一个用于存储器访问,另一个用于外设访问 *  仅支持32位访问的AHB从编程接口 *  每个DMA控制器有8个数据流,每个数据流有多达8个通道(或称请求) *  每个数据流有四个字深的32位先进先出存储器缓冲区(FIFO),可用于FIFO模式或直接模式: * FIFO模式:可通过软件将阈值级别选取为FIFO大小的1/4、1/2或3/4 * 直接模式:每个DMA请求会立即启动对存储器的传输。当在直接模式(禁止FIFO)下将DMA请求配置为以存储器到外设模式传输数据时,DMA仅会将一个数据从存储器预加载到内部FIFO,从而确保一旦外设触发DMA请求时则立即传输数据。 *  通过硬件可以将每个数据流配置为: * 支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道 * 在存储器端支持双缓冲的双缓冲区通道 *  8个数据流中的每一个都连接到专用硬件DMA通道(请求) *  DMA数据流请求之间的优先级可用软件编程(4个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求0的优先级高于请求1) *  每个数据流也支持存储器到存储器传输的软件触发(仅限DMA2控制器) *  可供每个数据流选择的通道请求多达8个。此选择可由软件配置,允许几个外设发起DMA请求 *  要传输的数据项的数目可以由DMA控制器或外设管理: * DMA流控制器:要传输的数据项的数目可用软件编程,从1至65535 * 外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过硬件发出传输结束的信号 *  独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA自动封装/解封必要的传输数据来优化带宽。这个特性仅在FIFO模式下可用 *  对源和目标的增量或非增量寻址 *  支持4个、8个和16个节拍的增量突发传输。突发增量的大小可由软件配置,通常等于外设FIFO大小的一半 *  每个数据流都支持循环缓冲区管理 *  5个事件标志(DMA半传输、DMA传输完成、DMA传输错误、DMAFIFO错误、直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求 === 3、DMA框图 === {{ :icore4:icore4_arm_hal_11_1.png?direct |}} * DMA控制器执行直接存储器传输:做为一个AHB主设备,它可以控制AHB总线矩阵来发起AHB传输。它可以执行下列传输: *  外设到存储器的传输 *  存储器到外设的传输 *  存储器到存储器的传输 * DMA控制器提供两个AHB主端口:AHB存储器端口(用于连接存储器)和AHB外设端口(用于连接外设)。但是,要执行存储器到存储器的传输,AHB外设端口必须也能访问存储器。AHB从端口用于对DMA控制器进行编程(它仅支持32位访问)。DMA1控制器AHB外设端口连接到总线矩阵的方式与DMA2控制器不同,因此,仅DMA2数据流能够执行存储器到存储器的传输。 === 4、DMA传输 === * DMA传输由给定数目的数据传输序列组成。要传输的数据项的数目及其宽度(8位、16位或32位)可用软件编程。每个DMA传输包含三项操作: *  通过DMA_SxPAR或DMA_SxM0AR寄存器寻址,从外设数据寄存器或存储器单元中加载数据 *  通过DMA_SxPAR或DMA_SxM0AR寄存器寻址,将加载的数据存储到外设数据寄存器或存储器单元 *  DMA_SxNDTR计数器在数据存储结束后递减,该计数器中包含仍需执行的事务数 * 在产生事件后,外设会向DMA控制器发送请求信号。DMA控制器根据通道优先级处理该请求。只要DMA控制器访问外设,DMA控制器就会向外设发送确认信号。外设获得DMA控制器的确认信号后,便会立即释放其请求。一旦外设使请求失效,DMA控制器就会释放确认信号。如果有更多请求,外设可以启动下一个事务。 * DMA (直接存储器访问)传输不需要占用CPU,可以在存储器至存储器实现高速的数据传输。本实验采用DMA2控制器的数据流0,选用通道0进行数据传输。通过LED的颜色来判断传输是否成功。 ==== 四、 实验程序 ==== === 1、主函数 === int main(void) { int i; /* MCU 配置*/ /* 重置所有外围设备,初始化Flash接口和Systick */ HAL_Init(); /* 配置系统时钟 */ SystemClock_Config(); /*初始化所有已配置的外围设备 */ MX_GPIO_Init(); dma.initialize(); //测试DMA,测试成功蓝色灯闪烁,测试失败,红色灯闪烁 //初始化DMA2 hdma_memtomem_dma2_stream0.Instance = DMA2_Stream0; hdma_memtomem_dma2_stream0.Init.Channel = DMA_CHANNEL_0;//通道0 hdma_memtomem_dma2_stream0.Init.Direction = DMA_MEMORY_TO_MEMORY; //传输方向,存储器到存储器 hdma_memtomem_dma2_stream0.Init.PeriphInc = DMA_PINC_ENABLE; //外设增量模式 hdma_memtomem_dma2_stream0.Init.MemInc = DMA_MINC_ENABLE;//存储器增量模式 hdma_memtomem_dma2_stream0.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; //外设数据大小 hdma_memtomem_dma2_stream0.Init.Mode = DMA_NORMAL; //正常模式 hdma_memtomem_dma2_stream0.Init.Priority = DMA_PRIORITY_HIGH; //DMA优先级:高 hdma_memtomem_dma2_stream0.Init.FIFOMode = DMA_FIFOMODE_ENABLE; //FIFO模式开启 hdma_memtomem_dma2_stream0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; //FIFO阈值完整配置 hdma_memtomem_dma2_stream0.Init.MemBurst = DMA_MBURST_SINGLE; //存储器突发模式:单次传输 hdma_memtomem_dma2_stream0.Init.PeriphBurst = DMA_PBURST_SINGLE; //外设触发模式:单次传输 if(HAL_DMA_Init(&hdma_memtomem_dma2_stream0) != HAL_OK) //初始化DMA { while(1); } /* 开始DMA传输 */ HAL_DMA_Start(&hdma_memtomem_dma2_stream0,(unsigned long int)src_buffer,(unsigned long int)dst_buffer,(unsigned long int)BUFFER_SIZE); while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma2_stream0,DMA_FLAG_TCIF0_4) == SET); for(i = 0;i < BUFFER_SIZE;i++){ if(dst_buffer[i] != src_buffer[i]){ //测试失败,红灯闪烁 while(1){ HAL_Delay(500); LED_RED_ON; HAL_Delay(500); LED_RED_OFF; } } } //测试成功,蓝灯闪烁 while (1) { HAL_Delay(500); LED_BLUE_ON; HAL_Delay(500); LED_BLUE_OFF; } } === 2、使能DMA2时钟 === * DMA的时钟使能是通过 AHB1ENR寄存器来控制的,需要要先使能时钟,才可以配置 DMA相关寄存器。主函数中使用定义的函数dma.initialize()来使能时钟,实际上也是调用HAL库的__HAL_RCC_DMA2_CLK_ENABLE()函数。 DMA_T dma = { .initialize = initialize }; DMA_HandleTypeDef hdma_memtomem_dma2_stream0; static int initialize(void) { _HAL_RCC_DMA2_CLK_ENABLE(); //使能时钟 return 0; } === 3、DMA开始传输函数 === HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma,uint32_t SrcAddress,uint32_t DstAddress,uint32_t DataLength) { HAL_StatusTypeDef status = HAL_OK; assert_param(IS_DMA_BUFFER_SIZE(DataLength)); /* 检查参数 */ _HAL_LOCK(hdma); /* 进程锁定 */ if(HAL_DMA_STATE_READY == hdma->State) { hdma->State = HAL_DMA_STATE_BUSY; /* 更改DMA外设状态 */ hdma->ErrorCode = HAL_DMA_ERROR_NONE; /* 初始化错误代码 */ /* 配置源,目标地址和数据长度 */ DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength); _HAL_DMA_ENABLE(hdma); /* 使能外围设备 */ } else { _HAL_UNLOCK(hdma); /* 进程解锁 */ status = HAL_BUSY; /* 返回错误状态 */ } return status; } === 4、查询 DMA 传输状态 === * 在DMA传输过程中,我们要查询DMA传输通道的状态,使用的方法是: _HAL_DMA_GET_FLAG(&hdma_memtomem_dma2_stream0,DMA_FLAG_TCIF0_4 * 主函数中即是通过此方法查询DMA传输通道的状态。 ==== 五、 实验步骤 ==== - 把仿真器与iCore4的SWD调试口相连(直接相连或者通过转接器相连); - 把iCore4通过Micro USB线与计算机相连,为iCore4供电; - 打开Keil MDK 开发环境,并打开本实验工程; - 烧写程序到iCore4上; - 也可以进入Debug 模式,单步运行或设置断点验证程序逻辑。 ==== 六、 实验现象 ==== * 实验成功蓝色LED灯闪烁,实验失败红色LED灯闪烁。