当前位置:   article > 正文

(30)STM32——DMA笔记_stm32f4的dma速度

stm32f4的dma速度

目录

学习目标

成果展示 

介绍

框图

特性

通道选择

请求映射

数据流

事务

仲裁器

指针递增

循环模式

双缓冲区模式

DMA中断 

流配置过程

配置

代码 

总结 


学习目标

        本节我们来学习有关DMA的知识,这部分知识在51中是没有接触的,也算是一个新的概念,简而言之,DMA就是一个不需要CPU的传输方式。好了,接下来我们就开始介绍有关DMA的知识吧!

成果展示 

         传输速度较快,视频也看不太清,就发一张截图了,一同发送了200条这样的数据。

介绍

        DMA,全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能为CPU减负,使 CPU 的效率大为提高。

        STM32F4 最多有 2 个 DMA 控制器(DMA1 和 DMA2),共 16 个数据流(每个控制器 8 个),每一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8 个通道(或称请求)。每个数据流通道都有一个仲裁器,用于处理 DMA 请求间的优先级。

框图

        这就是有关DMA的流程框图,一个8个数据流,每个数据流8个通道,还有仲裁器。其中的FIFO应该是先进先出队列,我们在此不做过多介绍。 

特性

  • 双 AHB 主总线架构,一个用于存储器访问,另一个用于外设访问 
  • 仅支持 32 位访问的 AHB 从编程接口
  • 每个 DMA 控制器有 8 个数据流,每个数据流有多达 8 个通道(或称请求)
  • 每个数据流有单独的四级 32 位先进先出存储器缓冲区(FIFO),可用于 FIFO 模式或直接模式。
  • 通过硬件可以将每个数据流配置为:
    • 1,支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道 。
    • 2,支持在存储器方双缓冲的双缓冲区通道
  • 8 个数据流中的每一个都连接到专用硬件 DMA 通道(请求)
  • DMA 数据流请求之间的优先级可用软件编程(4 个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求 0 的优先级高于请求 1)
  • 每个数据流也支持通过软件触发存储器到存储器的传输(仅限 DMA2 控制器)
  • 可供每个数据流选择的通道请求多达 8 个。此选择可由软件配置,允许几个外设启动 DMA 请求
  • 要传输的数据项的数目可以由 DMA 控制器或外设管理:
    • 1,DMA 流控制器:要传输的数据项的数目是 1 到 65535,可用软件编程 。
    • 2,外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过硬 件发出传输结束的信号
  • 独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA 自动封装/解封必要的传输数据来优化带宽(这个有点类似于Java里的缓冲流)。这个特性仅在 FIFO 模式下可用。
  • 对源和目标的增量或非增量寻址
  • 支持 4 个、8 个和 16 个节拍的增量突发传输。突发增量的大小可由软件配置,通常等 于外设 FIFO 大小的一半
  • 每个数据流都支持循环缓冲区管理
  • 5 个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO 错误、直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求

通道选择

        这个是通道选择,具体是哪个寄存器连接到数据流,是通过CHSEL寄存器来控制的。

 

请求映射

        这就是一一对应关系,不同的数据流和通道对应不同的外设。 

 

数据流

        8 个 DMA 控制器数据流都能够提供源和目标之间的单向传输链路。 每个数据流配置后都可以执行:

  • 常规类型事务:存储器到外设、外设到存储器或存储器到存储器的传输。
  • 双缓冲区类型事务:使用存储器的两个存储器指针的双缓冲区传输(当 DMA 正在进行自/ 至缓冲区的读/写操作时,应用程序可以进行至/自其它缓冲区的写/读操作)。

        要传输的数据量(多达 65535)可以编程,并与连接到外设 AHB 端口的外设(请求 DMA 传输)的源宽度相关。每个事务完成后,包含要传输的数据项总量的寄存器都会递减。

事务

        DMA 事务由给定数目的数据传输序列组成。要传输的数据项的数目及其宽度(8 位、16 位 或 32 位)可用软件编程。 每个 DMA 传输包含三项操作:

  • 通过 DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,从外设数据寄存器或存储器单元中加载数据。
  • 通过 DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,将加载的数据存储到外设数据寄存 器或存储器单元。
  • DMA_SxNDTR 计数器在数据存储结束后递减,该计数器中包含仍需执行的事务数。

        在产生事件后,外设会向 DMA 控制器发送请求信号。DMA 控制器根据通道优先级处理该请求。只要 DMA 控制器访问外设,DMA 控制器就会向外设发送确认信号。外设获得 DMA 控制器的确认信号后,便会立即释放其请求。一旦外设使请求失效,DMA 控制器就会释放确认信号。如果有更多请求,外设可以启动下一个事务。

仲裁器

        仲裁器为两个 AHB 主端口(存储器和外设端口)提供基于请求优先级的 8 个 DMA 数据流请求管理,并启动外设/存储器访问序列。 优先级管理分为两个阶段:

  • 软件:每个数据流优先级都可以在 DMA_SxCR 寄存器中配置。分为四个级别:非常高优先级、高优先级、中优先级、低优先级。
  • 硬件:如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据 流。例如,数据流 2 的优先级高于数据流 4。

 其实有点类似于中断的优先级的概念。

指针递增

        其实比较好理解,就是发送数据时指针自动递增,就能发送一条完整的数据流了。

循环模式

        就是是否需要循环发送数据。

双缓冲区模式

        我们可以理解为,两个数据流同时操作,比如DMA1在传输的时候,去填充DMA2的数据,等DMA1完成后,DMA2就接上DMA1的数据流继续发送。

DMA中断 

对于每个 DMA 数据流,可在发生以下事件时产生中断:

  • 达到半传输
  • 传输完成
  • 传输错误
  • FIFO 错误(上溢、下溢或 FIFO 级别错误)
  • 直接模式错误

可以使用单独的中断使能位以实现灵活性。

流配置过程

        我们在代码中详细介绍一下整个的过程。 

  1. 如果使能了数据流,通过重置 DMA_SxCR 寄存器中的 EN 位将其禁止,然后读取此位 以确认没有正在进行的数据流操作。将此位写为 0 不会立即生效,因为实际上只有所有 当前传输都已完成时才会将其写为 0。当所读取 EN 位的值为 0 时,才表示可以配置数 据流。因此在开始任何数据流配置之前,需要等待 EN 位置 0。应将先前的数据块 DMA 传输中在状态寄存器(DMA_LISR 和 DMA_HISR)中置 1 的所有数据流专用的位置 0, 然后才可重新使能数据流。
  2. 在 DMA_SxPAR 寄存器中设置外设端口寄存器地址。外设事件发生后,数据会从此地址 移动到外设端口或从外设端口移动到此地址。
  3. 在 DMA_SxMA0R 寄存器(在双缓冲区模式的情况下还有 DMA_SxMA1R 寄存器)中设 置存储器地址。外设事件发生后,将从此存储器读取数据或将数据写入此存储器。
  4. 在 DMA_SxNDTR 寄存器中配置要传输的数据项的总数。每出现一次外设事件或每出现 一个节拍的突发传输,该值都会递减。
  5. 使用 DMA_SxCR 寄存器中的 CHSEL[2:0] 选择 DMA 通道(请求)。
  6. 如果外设用作流控制器而且支持此功能,请将 DMA_SxCR 寄存器中的 PFCTRL 位置 1。
  7. 使用 DMA_SxCR 寄存器中的 PL[1:0] 位配置数据流优先级。
  8. 配置 FIFO 的使用情况(使能或禁止,发送和接收阈值)。
  9. 配置数据传输方向、外设和存储器增量 / 固定模式、单独或突发事务、外设和存储器数 据宽度、循环模式、双缓冲区模式和传输完成一半和/或全部完成,和/或 DMA_SxCR 寄存器中错误的中断。
  10. 通过将 DMA_SxCR 寄存器中的 EN 位置 1 激活数据流。 一旦使能了流,即可响应连接到数据流的外设发出的任何 DMA 请求。 一旦在 AHB 目标端口上传输了一半数据,传输一半标志 (HTIF) 便会置 1,如果传输一半中 断使能位 (HTIE) 置 1,还会生成中断。传输结束时,传输完成标志 (TCIF) 便会置 1,如果 传输完成中断使能位 (TCIE) 置 1,还会生成中断。

配置

1、使能 DMA2 时钟,并等待数据流可配置。

  1. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2 时钟使能
  2. while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}//等待 DMA 可配置

2、初始化 DMA2 数据流 7,包括配置通道,外设地址,存储器地址,传输数据量等。

void DMA_Init(DMA_Stream_TypeDef* DMAy_Streamx, DMA_InitTypeDef* DMA_InitStruct);

3、使能串口 1 的 DMA 发送。

USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口 1 的 DMA 发送 

4、使能 DMA2 数据流 7,启动传输。

  1. void DMA_Cmd(DMA_Stream_TypeDef* DMAy_Streamx, FunctionalState NewState)
  2. DMA_Cmd (DMA2_Stream7,ENABLE);

5、查询 DMA 传输状态。

  1. FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)
  2. DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7);
  3. uint16_t DMA_GetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx);
  4. DMA_GetCurrDataCounter(DMA1_Channel4);
  5. void DMA_SetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx, uint16_t Counter);

代码 

  1. #include "dma.h"
  2. #include "delay.h"
  3. //DMAx的各通道配置
  4. //chx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
  5. //par:外设地址
  6. //mar:存储器地址
  7. //ndtr:数据传输量
  8. void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr)
  9. {
  10. DMA_InitTypeDef DMA_InitStructure;
  11. if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1,数据流大于DMA2的地址
  12. {
  13. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
  14. }else
  15. {
  16. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能
  17. }
  18. DMA_DeInit(DMA_Streamx);
  19. while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}//等待DMA可配置
  20. /* 配置 DMA Stream */
  21. DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量
  22. DMA_InitStructure.DMA_Channel = chx;//通道选择
  23. DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存储器到外设模式
  24. DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;// 不使用FIFO
  25. DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;//设置FIFO范围,在此可以不设
  26. DMA_InitStructure.DMA_Memory0BaseAddr = mar;//DMA 存储器0地址
  27. DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  28. DMA_InitStructure.DMA_MemoryDataSize =DMA_MemoryDataSize_Byte;//存储器数据长度:8位
  29. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
  30. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式
  31. DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址
  32. DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//存储器突发单次传输
  33. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位
  34. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
  35. DMA_InitStructure.DMA_Priority = DMA_Priority_Low; //低优先级
  36. DMA_Init(DMA_Streamx, &DMA_InitStructure);//初始化DMA Stream
  37. }
  38. //开启一次DMA传输
  39. //DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
  40. //ndtr:数据传输量
  41. void MYDMA_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr)
  42. {
  43. DMA_Cmd(DMA_Streamx, DISABLE); //关闭DMA传输
  44. while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){} //确保DMA可以被设置
  45. DMA_SetCurrDataCounter(DMA_Streamx,ndtr); //数据传输量
  46. DMA_Cmd(DMA_Streamx, ENABLE); //开启DMA传输
  47. }

  1. #include "sys.h"
  2. #include "delay.h"
  3. #include "usart.h"
  4. #include "led.h"
  5. #include "key.h"
  6. #include "dma.h"
  7. #define SEND_BUF_SIZE 8200 //发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍.
  8. u8 SendBuff[SEND_BUF_SIZE]; //发送数据缓冲区
  9. const u8 TEXT_TO_SEND[]={"ALIENTEK Explorer STM32F4 DMA 串口实验"};
  10. int main(void)
  11. {
  12. u16 i;
  13. u8 t=0;
  14. u8 j,mask=0;
  15. u8 pro=0;//进度
  16. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
  17. delay_init(168); //初始化延时函数
  18. uart_init(115200); //初始化串口波特率为115200
  19. LED_Init(); //初始化LED
  20. KEY_Init(); //按键初始化
  21. MYDMA_Config(DMA2_Stream7,DMA_Channel_4,(u32)&USART1->DR,(u32)SendBuff,SEND_BUF_SIZE);//DMA2,STEAM7,CH4,外设为串口1,存储器为SendBuff,长度为:SEND_BUF_SIZE.
  22. //显示提示信息
  23. j=sizeof(TEXT_TO_SEND);
  24. for(i=0;i<SEND_BUF_SIZE;i++)//填充ASCII字符集数据
  25. {
  26. if(t>=j)//加入换行符
  27. {
  28. if(mask)
  29. {
  30. SendBuff[i]=0x0a;
  31. t=0;
  32. }else
  33. {
  34. SendBuff[i]=0x0d;
  35. mask++;
  36. }
  37. }else//复制TEXT_TO_SEND语句
  38. {
  39. mask=0;
  40. SendBuff[i]=TEXT_TO_SEND[t];
  41. t++;
  42. }
  43. }
  44. i=0;
  45. while(1)
  46. {
  47. t=KEY_Scan(0);
  48. if(t==KEY0_PRES) //KEY0按下
  49. {
  50. printf("\r\nDMA DATA:\r\n");
  51. USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送
  52. MYDMA_Enable(DMA2_Stream7,SEND_BUF_SIZE); //开始一次DMA传输!
  53. //等待DMA传输完成,此时我们来做另外一些事,点灯
  54. //实际应用中,传输数据期间,可以执行另外的任务
  55. while(1)
  56. {
  57. if(DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7)!=RESET)//等待DMA2_Steam7传输完成
  58. {
  59. DMA_ClearFlag(DMA2_Stream7,DMA_FLAG_TCIF7);//清除DMA2_Steam7传输完成标志
  60. break;
  61. }
  62. pro=DMA_GetCurrDataCounter(DMA2_Stream7);//得到当前还剩余多少个数据
  63. printf("%d",pro);
  64. }
  65. printf ("Transimit Finished!");//提示传送完成
  66. }
  67. i++;
  68. delay_ms(10);
  69. if(i==200)
  70. {
  71. LED0=!LED0;//提示系统正在运行
  72. i=0;
  73. }
  74. }
  75. }

总结 

        本节MDA的知识点就介绍到这了,因为我自己也不是特别了解,以后也会慢慢研究,谢谢大家观看了。

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号