|  **银杏科技有限公司旗下技术文档发布平台**  ||||
|技术支持电话|**0379-69926675-801**|||
|技术支持邮件|Gingko@vip.163.com|||
^  版本  ^  日期  ^  作者  ^  修改内容  ^
|  V1.0  |  2020-07-28  |  gingko  |  初次建立  | 
\\
\\
\\
===== STM32CubeMX教程十五——DMA实验 =====
\\
1. 在主界面选择File-->New Project   或者直接点击ACCEE TO MCU SELECTOR  
{{ :icore4tx:icore4tx_cube_15_1.png?direct |}}
2. 出现芯片型号选择,搜索自己芯片的型号,双击型号,或者点击Start Project进入配置
在搜索栏的下面,提供的各  种查找方式,可以选择芯片内核,型号,等等,可以帮助你查找芯片。本实验选取的芯片型号为:STM32H750IBKx。
{{ :icore4tx:icore4tx_cube_15_2.png?direct |}}
3. 配置RCC,使用外部时钟源
{{ :icore4tx:icore4tx_cube_15_3.png?direct |}}
4. 时基源选择SysTick
{{ :icore4tx:icore4tx_cube_15_4.png?direct |}}
5. 将LED对应的引脚PA10设置为GPIO_Output
{{ :icore4tx:icore4tx_cube_15_5.png?direct |}}
6. 引脚模式配置
{{ :icore4tx:icore4tx_cube_15_6.png?direct |}}
7. DMA配置
{{ :icore4tx:icore4tx_cube_15_7.png?direct |}}
8. 打开DMA2的中断
{{ :icore4tx:icore4tx_cube_15_8.png?direct |}}
9. 时钟源设置,选择外部高速时钟源,配置为最大主频
{{ :icore4tx:icore4tx_cube_15_9.png?direct |}}
10. 工程文件的设置, 这里就是工程的各种配置 我们只用到有限几个,其他的默认即可  IDE我们使用的是 MDK V5.27
{{ :icore4tx:icore4tx_cube_15_10.png?direct |}}
11. 点击Code Generator,进行进一步配置
{{ :icore4tx:icore4tx_cube_15_11.png?direct |}}
  * **Copy all used libraries into the project folder**
  * **将HAL库的所有.C和.H都复制到所建工程中**
    * 优点:这样如果后续需要新增其他外设又可能不再用STM32CubeMX的时候便会很方便
    * 缺点:体积大,编译时间很长
  * **Copy only the necessary library files**
  * **只复制所需要的.C和.H(推荐)**
    * 优点:体积相对小,编译时间短,并且工程可复制拷贝
    * 缺点:新增外设时需要重新用STM32CubeMX导入
  * **Add necessary library files as reference in the toolchain project configuration file**
  * **不复制文件,直接从软件包存放位置导入.C和.H**
    * 优点:体积小,比较节约硬盘空间
    * 缺点:复制到其他电脑上或者软件包位置改变,就需要修改相对应的路径
  * 自行选择方式即可
12. 然后点击GENERATE CODE  创建工程
{{ :icore4tx:icore4tx_cube_15_12.png?direct |}}
创建成功,打开工程。
\\
\\
\\
===== 实验十五:DMA实验——储存器到存储器的传输 =====
==== 一、 实验目的与意义 ====
  - 了解STM32 DMA结构。
  - 了解STM32 DMA 特征。
  - 掌握DMA的使用方法。
  - 掌握STM32 HAL库中DMA属性的配置方法。
  - 掌握KEILMDK 集成开发环境使用方法。
==== 二、 实验设备及平台 ====
  - iCore4TX 双核心板[[https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-22598974120.3.29da532fLkazHH&id=614919247574|点击购买]]。
  - Blaster(或相同功能的)仿真器。[[https://item.taobao.com/item.htm?id=554869837940|点击购买]]
  - Micro USB线缆。
  - Keil MDK 开发平台。
  - STM32CubeMX开发平台。
  - 装有WIN XP(及更高版本)系统的计算机。
==== 三、 实验原理 ====
=== 1、DMA简介 ===
  * 直接存储器访问(DMA),全称Direct Memory Access,用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。可以在无需任何CPU操作的情况下通过DMA快速移动数据。这样节省的CPU资源可供其它操作使用。
  * DMA控制器基于复杂的总线矩阵架构,将功能强大的双AHB主总线架构与独立的FIFO结合在一起,优化了系统带宽。两个DMA控制器总共有16个数据流(每个控制器 8 个),每一个DMA控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达8个通道(或称请求)。每个通道都有一个仲裁器,用于处理DMA请求间的优先级。
=== 2、DMA工作原理 ===
  * DMA 允许不同速度的硬件装置来沟通,而不需要依于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把他们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。
  * DMA 传输主要地将一个内存区从一个装置复制到另外一个。当 CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存去。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。所以,DMA传输对于高效能嵌入式系统算法和网络是很重要的。
=== 3、DMA主要特性 ===
  * (1) 双AHB主总线架构,一个用于存储器访问,另一个用于外设访问
  * (2) 仅支持32位访问的AHB从编程接口
  * (3) 每个DMA控制器有8个数据流,每个数据流有多达115个通道(或称请求)
  * (4) 每个数据流有四个字深的32位先进先出存储器缓冲区(FIFO),可用于FIFO模式或直接模式:
    * FIFO模式:可通过软件将阈值级别选取为FIFO大小的1/4、1/2或3/4
    * 直接模式:每个DMA请求会立即启动对存储器的传输。当在直接模式(禁止FIFO)下将DMA请求配置为以存储器到外设模式传输数据时,DMA仅会将一个数据从存储器预加载到内部FIFO,从而确保一旦外设触发DMA请求时则立即传输数据。
  * (5) 通过硬件可以将每个数据流配置为:
    * 支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道
    * 在存储器端支持双缓冲的双缓冲区通道
  * (6) 8个数据流中的每一个都连接到专用硬件DMA通道(请求)
  * (7)DMA数据流请求之间的优先级可用软件编程(4个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求0的优先级高于请求1)
  * (8) 每个数据流还支持通过软件触发的存储器间的传输
  * (9) 可通过DMAMux1在多达115个可能的通道请求中选择每个数据流请求。此选择可由软件配置,允许多个外设发起DMA请求
  * (10) 要传输的数据项的数目可以由DMA控制器或外设管理:
    * DMA流控制器:要传输的数据项的数目可用软件编程,从1至65535
    * 外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过硬件发出传输结束的信号
  * (11)独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA自动封装/解封必要的传输数据来优化带宽。这个特性仅在FIFO模式下可用
  * (12) 对源和目标的增量或非增量寻址
  * (13) 支持4个、8个和16个节拍的增量突发传输。突发增量的大小可由软件配置,通常等于外设FIFO大小的一半
=== 4、DMA框图 ===
{{ :icore4tx:icore4tx_arm_hal_15_1.png?direct |}}
 
=== 5、DMA传输 ===
  * DMA传输由给定数目的数据传输序列组成。要传输的数据项的数目及其宽度(8位、16位或32位)可用软件编程。
  * 每个DMA传输包含三项操作:
    * (1) 通过DMA_SxPAR或DMA_SxM0AR寄存器寻址,从外设数据寄存器或存储器单元中加载数据
    * (2) 通过DMA_SxPAR或DMA_SxM0AR寄存器寻址,将加载的数据存储到外设数据寄存器或存储器单元
    * (3) DMA_SxNDTR计数器在数据存储结束后递减,该计数器中包含仍需执行的事务数。
  * 在产生事件后,外设会向DMA控制器发送请求信号。DMA控制器根据通道优先级处理该请求。只要DMA控制器访问外设,DMA控制器就会向外设发送确认信号。外设获得DMA控制器的确认信号后,便会立即释放其请求。一旦外设使请求失效,DMA控制器就会释放确认信号。如果有更多请求,外设可以启动下一个事务。
==== 四、 实验程序 ====
=== 1. 主函数 ===
//定义src_buffer数组作为DMA传输数据源
//const关键字将src_buffer数组变量定义为常量类型
//表示数据存储在内部的FLASH中
const unsigned long int 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};
//定义DMA传输目标存储器
//存储在内部的SRAM中
unsigned long int dst_buffer[BUFFER_SIZE] = {0};
int main(void)
{
  int i;
  HAL_Init();
  SystemClock_Config();
  i2c.initialize();
  axp152.initialize();
  axp152.set_dcdc1(3500);//[ARM & FPGA]
  axp152.set_dcdc2(1200);//[FPGA INT]
  axp152.set_dcdc3(3300);//[DCOUT3]
  axp152.set_dcdc4(3300);//[DCOUT4]
  axp152.set_aldo1(3300);//[BK3]
  axp152.set_aldo2(3300);//[ALDOOUT2]
  axp152.set_dldo1(3300);//[BK0]
  axp152.set_dldo2(3300);//[BK1]
  HAL_Delay(200);
  MX_GPIO_Init();
  MX_DMA_Init();
  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_ON;
                    HAL_Delay(500);
                    LED_OFF;
            }
        }
}
//测试成功
  LED_ON;
  while (1)
  {
  }
}
=== 2. DMA初始化函数 ===
void MX_DMA_Init(void) 
{
  //使能DMA控制器时钟
  __HAL_RCC_DMA2_CLK_ENABLE();
  //配置DMA工作方式
  hdma_memtomem_dma2_stream0.Instance = DMA2_Stream0;
  hdma_memtomem_dma2_stream0.Init.Request = DMA_REQUEST_MEM2MEM;
  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.MemDataAlignment = DMA_MDATAALIGN_WORD;//存储器数据宽度
  hdma_memtomem_dma2_stream0.Init.Mode = DMA_NORMAL;
  hdma_memtomem_dma2_stream0.Init.Priority = DMA_PRIORITY_HIGH;//优先级别为高
  hdma_memtomem_dma2_stream0.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
  hdma_memtomem_dma2_stream0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
  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)
  {
    Error_Handler();
  }
  //DMA2中断配置
  HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}
=== 3. DMA寄存器结构体 ===
DMA相关的寄存器是通过HAL库中的结构体DMA_TypeDef和DMA_Stream_TypeDef定义的。
typedef struct  
{  
  __IO uint32_t CR;     /* DMA stream x 配置寄存器 */ 
  __IO uint32_t NDTR;   /* DMA stream x 数据寄存器数 */   
  __IO uint32_t PAR;    /* DMA stream x 外设地址寄存器 */
  __IO uint32_t M0AR;   /* DMA stream x 存储器0地址寄存器 */ 
  __IO uint32_t M1AR;   /* DMA stream x 存储器1地址寄存器 */ 
  __IO uint32_t FCR;    /* DMA stream x FIFO控制寄存器 */ 
} DMA_Stream_TypeDef;  
typedef struct  
{  
  __IO uint32_t LISR;   /* DMA低中断状态寄存器,地址偏移量:0x00 */  
  __IO uint32_t HISR;   /* DMA高中断状态寄存器,地址偏移量:0x04 */  
  __IO uint32_t LIFCR;  /* DMA低中断标志清除寄存器,地址偏移量:0x08 */  
  __IO uint32_t HIFCR;  /* DMA高中断标志清除寄存器,地址偏移量:0x0C */  
} DMA_TypeDef;  
=== 4. DMA句柄结构体DMA_HandleTypeDef ===
HAL库在DMA_TypeDef的基础上封装了一个结构体DMA_HandleTypeDef,定义如下:
typedef struct __DMA_HandleTypeDef  
{  
  void                        *Instance;      /* 注册基地址 */  
  DMA_InitTypeDef              Init;          /* DMA通讯参数 */  
  HAL_LockTypeDef             Lock;           /* DMA锁定对象 */  
  __IO HAL_DMA_StateTypeDef   State;          /* DMA传输状态 */  
  void           *Parent;                     /* 父对象状态 */  
  void          (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma);        /* DMA传输完成回调 */  
  void           (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);   /* DMA半传输完成回调 */  
  void           (* XferM1CpltCallback)( struct __DMA_HandleTypeDef * hdma);     /* DMA传输完成Memory1回调 */  
  void           (* XferM1HalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); /* DMA传输半完成Memory1回调 */  
  void           (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma);      /* DMA传输错误回调 */  
  void           (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma);      /* DMA传输中止回调 */  
 __IO uint32_t               ErrorCode;           /* DMA错误代码 */  
 uint32_t                    StreamBaseAddress;   /* DMA流基址 */   
 uint32_t                    StreamIndex;         /* DMA流索引 */  
 DMAMUX_Channel_TypeDef     *DMAmuxChannel;       /* DMAMUX通道基地址  */  
 DMAMUX_ChannelStatus_TypeDef     *DMAmuxChannelStatus;            /* DMAMUX通道状态基地址 */  
 uint32_t                          DMAmuxChannelStatusMask;        /* DMAMUX通道状态掩码 */
 DMAMUX_RequestGen_TypeDef        *DMAmuxRequestGen;               /* DMAMUX请求生成器基地址 */  
 DMAMUX_RequestGenStatus_TypeDef  *DMAmuxRequestGenStatus;         /* DMAMUX请求生成器状态地址 */  
 uint32_t                          DMAmuxRequestGenStatusMask;     /* DMAMUX请求生成器状态掩码 */  
}DMA_HandleTypeDef;  
==== 五、 实验步骤 ====
  - 把仿真器与iCore4TX的SWD调试口相连(直接相连或者通过转接器相连);
  - 把iCore4TX通过MicroUSB线与计算机相连,为iCore4TX供电;
  - 打开KeilMDK开发环境,并打开本实验工程;
  - 烧写程序到iCore4TX上;
  - 也可以进入Debug模式,单步运行或设置断点验证程序逻辑。
  * 注: DTCM虽然和其它的MCU的RAM起始地址一样,但是无法被DMA访问。也就是通用RAM的地址变了,但是编译器还是会把DTCM的地址作为通用RAM的起始地址。若用户在编写程序实验过程中,发现DMA不能传输数据。请将内存起始地址修改为D1域,操作步骤如图所示,也就是在RAM2上打勾。
{{ :icore4tx:icore4tx_arm_hal_15_2.png?direct |}}
==== 六、 实验现象 ====
实验成功LED灯常亮,实验失败LED灯闪烁。