嵌入式开发(9)DMA控制器

参考STM32f4xx中文参考手册

直接存储器访问(DMA),全称 Direct MemoryAccess,用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。可以在无需任何 CPU 操作的情况下通过 DMA快速移动数据。这样节省的 CPU 资源可供其它操作使用。

DMA传输方式也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。

1、DMA运用范围

— DMA1 存储器总线
DMA 通过此总线来执行存储器数据 的传入和传出。
此总线访问的对象是数据存储器:内部 SRAM(112 KB、64 KB、16 KB) 以及通过 FSMC 的外部存储器
— DMA2 存储器总线
此总线访问的对象是数据存储器:内部 SRAM(112 KB、64 KB、16 KB) 以及通过 FSMC 的外部存储器。
— DMA2 外设总线
此总线访问的对象是 AHB 和 APB 外设以及数据存储器:内部 SRAM 以及通过 FSMC 的外部存储器。
— 以太网 DMA 总线
以太网 DMA 通过此总线向存储器存取 数据。
此总线访问的对象是数据存储器:内部 SRAM(112 KB、64 KB 和 16 KB)以及通过 FSMC 的外部存储器。
— USB OTG HS DMA 总线
USB OTG DMA 通过此总线向存储 器加载/存储数据
此总线访问的对象是数据存储器:内部 SRAM(112 KB、64 KB 和 16 KB) 以及通过 FSMC 的外部存储器
位段操作一个映射区域无效(DMA总线主接口无效)

2、寄存器汇总

● DMA 低中断状态寄存器 (DMA_LISR
● DMA 高中断状态寄存器 (DMA_HISR)
● DMA 低中断标志清零寄存器 (DMA_LIFCR)
● DMA 高中断标志清零寄存器 (DMA_HIFCR)
● DMA 数据流 x 配置寄存器 (DMA_SxCR) (x = 0…7):此寄存器用于配置相关数据流
● DMA 数据流 x 数据项数寄存器 (DMA_SxNDTR) (x = 0…7)
● DMA 数据流 x 外设地址寄存器 (DMA_SxPAR) (x = 0…7)
● DMA 数据流 x 存储器 0 地址寄存器 (DMA_SxM0AR) (x = 0…7)
● DMA 数据流 x 存储器 1 地址寄存器 (DMA_SxM1AR) (x = 0…7)
● DMA 数据流 x FIFO 控制寄存器 (DMA_SxFCR) (x = 0…7)

 	DMA_Streamx->PAR=par;       //DMA外设地址
    DMA_Streamx->M0AR=mar;      //DMA 存储器0地址
    DMA_Streamx->NDTR=ndtr;     //DMA 存储器0地址
    DMA_Streamx->CR=0;          //先全部复位CR寄存器值 
    
    DMA_Streamx->CR|=1<<6;      //存储器到外设模式
    DMA_Streamx->CR|=0<<8;      //非循环模式(即使用普通模式)
    DMA_Streamx->CR|=0<<9;      //外设非增量模式
    DMA_Streamx->CR|=1<<10;     //存储器增量模式
    DMA_Streamx->CR|=0<<11;     //外设数据长度:8位
    DMA_Streamx->CR|=0<<13;     //存储器数据长度:8位
    DMA_Streamx->CR|=1<<16;     //中等优先级
    DMA_Streamx->CR|=0<<21;     //外设突发单次传输
    DMA_Streamx->CR|=0<<23;     //存储器突发单次传输
    DMA_Streamx->CR|=(u32)chx<<25;//通道选择

DMA控制器传输作为AHB主设备操作直接存储器,它可以控制AHB总线的控制矩阵以启动AHB传送。它可以执行以下信息交换:
● 外设到内存
● 内存到外设
● 内存到内存

DMA 控制器基于复杂的总线矩阵架构,将功能强大的双 AHB 主总线架构与独立的FIFO 结合在一起,优化了系统带宽。
两个 DMA 控制器总共有 16 个数据流(每个控制器8 个),每一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8 个通道(或称请求)。每个通道都有一个仲裁器,用于处理 DMA 请求间的优先级。 DMA 传输由给定数目的数据传输序列组成。要传输的数据项的数目及其宽度(8位、16 位或 32 位)可用软件编程。
每个 DMA 传输包含三项操作:通过 DMA_SxPARDMA_SxM0AR 寄存器寻址,从外设数据寄存器或存储器单元中加载数据通过 DMA_SxPARDMA_SxM0AR 寄存器寻址,将加载的数据存储到外设数据寄存器或存储器单元DMA_SxNDTR 计数器在数据存储结束后递减,该计数器中包含仍需执行的事务数。
在产生事件后,外设会向 DMA 控制器发送请求信号。DMA 控制器根据通道优先级处理该请求。只要 DMA 控制器访问外设,DMA 控制器就会向外设发送确认信号。
外设获得DMA 控制器的确认信号后,便会立即释放其请求。一旦外设使请求失效,DMA 控制器就会释放确认信号。如果有更多请求,外设可以启动下一个事务。

3、主要特性

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 模式下可用DMA 控制器 (DMA)
● 对源和目标的增量或非增量寻址
● 支持 4 个、8 个和 16 个节拍的增量突发传输。突发增量的大小可由软件配置,通常等于 外设 FIFO 大小的一半
● 每个数据流都支持循环缓冲区管理
● 5 个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO 错误、直接 模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求
在这里插入图片描述
通道、数据流、请求选择参考以下表格,但每个芯片对应的数据流,通道可能不一样,编写代码时,看他的数据手册或者用户手册。
在这里插入图片描述
例如:DMA2_Stream1

仲裁器为两个 AHB 主端口(存储器和外设端口)提供基于请求优先级的 8 个 DMA 数据流请 求管理,
优先级管理分为两个阶段:
● 软件:每个数据流优先级都可以在 DMA_SxCR 寄存器中配置。
分为四个级别:— 非常高优先级— 高优先级— 中优先级— 低优先级
● 硬件:如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据流。例如,数据流 2 的优先级高于数据流 4。

 DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级

DMA 数据流
每个数据流配置后都可以执行:
● 常规类型事务:存储器到外设、外设到存储器或存储器到存储器的传输。当然也有外设到外设的类型。
● 双缓冲区类型事务:使用存储器的两个存储器指针的双缓冲区传输(当 DMA 正在进行自/至缓冲区的读/写操作时,应用程序可以进行至/自其它缓冲区的写/读操作)。要传输的数据量(多达 65535)可以编程,并与连接到外设 AHB 端口的外设(请求 DMA 传 输)的源宽度相关。每个事务完成后,包含要传输的数据项总量的寄存器都会递减。

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能 
DMA_DeInit(DMA2_Stream1);//等待DMA2_Stream1
while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}//等待DMA2_Stream1可配置 

4、外设到存储器模式

每次产生外设请求,数据流都会启动数据源到 FIFO 的传输。

  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; //FIFO模式

图片双缓冲模式
在这里插入图片描述

5、存储器到外设模式

每次发生外设请求,FIFO 的内容都会移出并存储到目标中。当 FIFO 的级别小于或等于预定 义的阈值级别时,将使用存储器中的数据完全重载 FIFO。图片双缓冲模式。
在这里插入图片描述

6、存储器到存储器模式

DMA 通道在没有外设请求触发的情况下同样可以工作。
注意: 使用存储器到存储器模式时,不允许循环模式和直接模式。只有 DMA2 控制器能够执行存储器到存储器的传输
在这里插入图片描述

7、指针递增

外设和存储器指针在每次传输后可以 自动向后递增或保持常量。通过单个寄存器访问外设源或目标数据时,禁止递增模式十分有用。
如果使能了递增模式,则根据在 DMA_SxCR 寄存器 PSIZEMSIZE 位中编程的数据宽 度,下一次传输的地址将是前一次传输的地址递增 1(对于字节)、2(对于半字)或 4(对 于字)

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc;//存储器增量模式

8、单次传输和突发传输

DMA 控制器可以产生单次传输或 4 个、8 个和 16 个节拍的增量突发传输。
突发大小通过软件针对两个 AHB 端口独立配置,配置时使用 DMA_SxCR 寄存器中的MBURST[1:0] 和 PBURST[1:0] 位。突发大小指示突发中的节拍数,而不是传输的字节数。为确保数据一致性,形成突发的每一组传输都不可分割:在突发传输序列期间,AHB 传输会锁定,并且 AHB 总线矩阵的仲裁器不解除对 DMA 主总线的授权。根据单次或突发配置的情况,每个 DMA 请求在 AHB 外设端口上相应地启动不同数量的传输。
● 当 AHB 外设端口被配置为单次传输时,根据 DMA_SxCR 寄存器 PSIZE[1:0] 位的值, 每个 DMA 请求产生一次字节、半字或字的数据传输。
● 当 AHB 外设端口被配置为突发传输时,根据 DMA_SxCR 寄存器 PBURST[1:0] 和PSIZE[1:0] 位的值,每个 DMA 请求相应地生成 4 个、8 个或 16 个节拍的字节、半字或字的传输。
在直接模式下,数据流只能生成单次传输,而 MBURST[1:0] 和 PBURST[1:0] 位由硬件强制配置。
必须选择地址指针(DMA_SxPAR 或 DMA_SxM0AR 寄存器),以确保一个突发块内的所有 传输在等于传输大小的地址边界对齐。
选择突发配置必须要遵守 AHB 协议,即突发传输不得越过 1 KB 地址边界,因为可以分配给 单个从设备的最小地址空间是 1 KB。这意味着突发块传输不应越过 1 KB 地址边界,否则就 会产生一个 AHB 错误,并且 DMA 寄存器不会报告这个错误。

注意: 仅在使能指针递增模式时允许突发模式:— 当 PINC 位为“0”时,也应将 PBURST 位清为“00”— 当 MINC位为“0”时,也应将 MBURST 位清为“00

9、DMA循环模式

循环模式可用于处理循环缓冲区和连续数据流(例如 ADC 扫描模式)。当激活循环模式时,要传输的数据项的数目在数据流配置阶段自动用设置的初始值进行加载,并继续响应 DMA 请求。
STM32 自带了一个双缓冲模式设置,普通 DMA 的使用是在 DMA 的数据流中进行的,设置好 DMA 的起点和终点以及传输的数据量即可开启 DMA 传输。DMA开启后就开始从起点将数据搬运至终点,每搬一次,传输数据量大小就减 1,当传输数据量为 0 时,DMA 停止搬运。DMA 的传输模式如果是普通模式,则当传输数据量为 0 时,需要程序中手动将传输数据量重新设置满才能开启下一次的 DMA 数据传输。本文设计的 DMA 的传输模式是循环模式,当传输数据量为 0 时,会自动将传输数据量设置为满的,这样数据就能不断的传输。
在这里插入图片描述

 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循环模式 

注意: 在循环模式下,如果为存储器配置了突发模式,必须遵循下列规则: DMA_SxNDTR = ((Mburst 节拍) ×(Msize)/(Psize)) 的倍数,如果不遵循此公式,则 DMA 行为和数据完整性得不到保证。NDTR还必须是外设突发大小与外设数据大小乘积的倍数,否则会导致错误的 DMA 行为。

10、FIFO模式

先进先出结构,类似于。例如:当外设到存储器,可以把他当作一个缓冲区,DMA从外设搬运的数据先放在FIFO缓冲区,先进先出原理,再核对完,数据再搬运到存储器。
用于在源数据传输到目标之前临时存储这些数据。因为MCU读取的数据可能没有外设传输的速率快,当你写入传输到的数据速率和读取的速率不匹配,就会造成数据的丢失,所以多了个FIFO当作缓冲区保证数据的准确性

 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; //FIFO模式 
 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;//使用全FIFO 

FIFO 阈值与突发配置

FIFO 阈值指向的内容必须与整数个存储器突发传输完全 匹配。如果不是这样,当使能数据流时将生成一个 FIFO 错误,然后将自动禁止数据流。
所有这些情况下,突发大小与数据大小的乘积不得超过 FIFO 大小(数据大小可以为:1(字 节)、2(半字)或 4(字)

FIFO 刷新

当复位 DMA_SxCR 寄存器中的 EN 位来禁止数据流,以及配置数据流来管理外设到存储器 或存储器到存储器的传输时,可以刷新 FIFO。因为如果禁止数据流时仍有某些数据存留在 FIFO 中,DMA 控制器会将剩余的数据继续传输到目标(即使已经有效禁止了数据流)

11、直接模式

默认情况下,FIFO 以直接模式操作(将 DMA_SxFCR 中的 DMDIS 位置 1),不使用 FIFO 阈值级别。如果在每次 DMA 请求之后,系统需要至/自存储器的立即和单独传输,这种模式 非常有用。当在直接模式(禁止 FIFO)下将 DMA 配置为以存储器到外设模式传输数据时,DMA 会将一 个数据从存储器预加载到内部 FIFO,从而确保一旦外设触发 DMA 请求时则立即传输数据。为了避免 FIFO 饱和,建议使用高优先级配置相应的数据流。该模式仅限以下方式的传输:
● 源和目标传输宽度相等,并均由 DMA_SxCR 中的 PSIZE[1:0] 位定义(MSIZE[1:0] 位的 状态是“无关”)
● 不可能进行突发传输(DMA_SxCR 中的 PBURST[1:0] 和 MBURST[1:0] 位的状态是“无 关”)当实现存储器到存储器传输时不得使用直接模式

12、双缓冲机制

除了有两个存储器指针之外,双缓冲区数据流的工作方式与常规(单缓冲区)数据流的一样。使能双缓冲区模式时,将自动使能循环模式,并在每次事务结束时交换存储器指针。在此模式下,每次事务结束时,DMA 控制器都从一个存储器目标交换为另一个存储器目标。 这样,软件在处理一个存储器区域的同时,DMA 传输还可以填充/使用第二个存储器区域。
之前做毕业设计两个stm32直接传视频图像,因为是做无线wifi的,而且还是传图像,为了是图像数据获取和传输的更快,绞尽脑汁,方法之一采取了DMA双缓冲区获取图像数据。
图像的显示采取 RGB 模式(那时候jpg压缩算法没搞出来),图像数据量庞大,单片机芯片直接从单个内存区提取数据显示到 LCD 屏幕上,会造成很大延迟性能,占用 CPU,并且访问一次缓冲区并不能完整的写入待显示的图像数据,造成很大的闪烁不连贯、花屏效果,不能保证实时性。使用的双缓冲器内存,先将图像中间结果从另一个缓冲器同时读取另一个缓冲器的最后影像数据。读取完其他缓冲器影像数据后将完整的图形保存到该缓冲器内,该缓冲器的图形数据将同时显示在 lcd 屏幕上,不断循环自动切换内存区,合理运用先进先出的原理,提高图像获取的完整性和减少延时。由于主控芯片系统自带的内存并不足够,因此,增加了外部 1Mb 的 SRAM 储存区域,将两个缓冲区的区域设置在外部 SRAM 中,解决了主控芯片内存不充足的问题。
在这里插入图片描述

采取中断获取数据的方法,当捕获一帧数据,即内存区域 1 存在数据,将自动转换成内存区域 2 进行存储数据,并开始下一帧的采集;采取回调函数进行读取数据,设置标志位偏移到有效数据末尾,如果内存区域 1 已满,内存区域 2 正常获取数据,读取内存区域 1 里面的数据,反之,内存区域 2 已满,内存区域 1 正常获取数据,读取内存区域 2 里面的数据。

13、流控制器

控制要传输的数据数目的实体称为流控制器。此流控制器使用 DMA_SxCR 寄存器中的PFCTRL 位针对每个数据流独立配置。
流控制器可以是:
● DMA 控制器:在这种情况下,要传输的数据项的数目在使能 DMA 数据流之前由软件编 程到 DMA_SxNDTR 寄存器。
● 外设源或目标:当要传输的数据项的数目未知时属于这种情况。当所传输的是最后的数据时,外设通过硬件向 DMA 控制器发出指示。仅限能够发出传输结束信号的外设支持 此功能,也就是:— SDIO

注意: 当在存储器到存储器模式下配置时,DMA 始终是流控制器,而 PFCTRL 位由硬件强制置为 0。在外设流控制器模式下禁止循环模式。

14、DMA 中断

对于每个 DMA 数据流,可在发生以下事件时产生中断:
● 达到半传输
● 传输完成
● 传输错误
● FIFO 错误(上溢、下溢或 FIFO 级别错误)
● 直接模式错误可以使用单独的中断使能位以实现灵活性,如表 所示。
在这里插入图片描述

15、DMA 传输完成

以下各种事件均可以结束传输过程,并将 DMA_LISRDMA_HISR 状态寄存器中的 TCIFx 位置 1:
● 在 DMA 流控制器模式下:
— 在存储器到外设模式下,DMA_SxNDTR 计数器已达到零— 传输结束前禁止了数据流(通过将 DMA_SxCR 寄存器中的 EN 位清零),并在传输是外设到存储器或存储器到存储器的模式时,所有的剩余数据均已从 FIFO 刷新 到存储器
● 在外设流控制器模式下:
— 已从外设生成最后的外部突发请求或单独请求,并当 DMA 在外设到存储器模式下工 作时,剩余数据已从 FIFO 传输到存储器
— 数据流由软件禁止,并当 DMA 在外设到存储器模式下工作时,剩余数据已从 FIFO 传输到存储器

注意: 仅在外设到存储器模式下,传输的完成取决于 FIFO 中要传输到存储器的剩余数据。这种情 况不适用于存储器到外设模式。 如果是在非循环模式下配置数据流,传输结束后(即要传输的数据数目达到零),除非软件重新对数据流编程并重新使能数据流(通过将 DMA_SxCR寄存器中的 EN 位置1),否则 DMA 即会停止传输(通过硬件将 DMA_SxCR 寄存器中的 EN 位清零)并且不再响应任何 DMA 请求。

16、DMA 传输暂停

可以随时暂停 DMA 传输以供稍后重新开始;也可以在 DMA 传输结束前明确禁止暂停功能。分为两种情况:
数据流禁止传输,以后不从停止点重新开始暂停。这种情况下,只需将 DMA_SxCR 寄存 器中的 EN 位清零来禁止数据流,除此之外不需要任何其他操作。禁止数据流可能要花费 一些时间(需要首先完成正在进行的传输)。需要将传输完成中断标志(DMA_LISRDMA_HISR 寄存器中的 TCIF)置 1 来指示传输结束。现在 DMA_SxCR 中的 EN 位的 值是“0”,借此确认数据流已经终止传输。DMA_SxNDTR 寄存器包含数据流停止时 剩余数据项的数目,这样软件便可以确定数据流中断前已传输了多少数据项。

数据流在 DMA_SxNDTR 寄存器中要传输的剩余数据项数目达到 0 之前暂停传输。目的 是以后通过重新使能数据流重新开始传输。为了在传输停止点重新开始传输,软件必须在通过写入 DMA_SxCR 寄存器中的 EN 位(然后检查确认该位为‘0’)禁止数据流 之后,首先读取 DMA_SxNDTR 寄存器来了解已经收集的数据项的数目。然后:— 必须更新外设和/或存储器地址以调整地址指针— 必须使用要传输的剩余数据项的数目(禁止数据流时读取的值)更新 SxNDTR 寄 存器— 然后可以重新使能数据流,从停止点重新开始传输注意: 请注意,传输完成中断标志(DMA_LISRDMA_HISR 中的 TCIF)置 1 将指示因数据流中断而结束传输。

DMA 配置汇总
在这里插入图片描述
流配置过程配置 DMA 数据流 x(其中 x 是数据流编号,DMA2_Stream1)时应遵守下面的顺序:

  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,还会生成中断。
    警告: 要关闭连接到 DMA 数据流请求的外设,必须首先关闭外设连接 的 DMA 数据流,然后等待 EN 位 = 0。只有这样才能安全地禁 止外设。

代码

(1)DMA双缓冲+STM32F407+OV2640+LCD

外设摄像头数字接口DCMI库函数配置
硬件:正点原子最小系统板stm32f407+ov2640+tft液晶屏

void DCMI_DMA_Init_DOUBLE_BUF(u32 DMA_Memory0BaseAddr,u32 DMA_Memory1BaseAddr,u16 DMA_BufferSize,u32 DMA_MemoryDataSize,u32 DMA_MemoryInc)
{ 
    DMA_InitTypeDef  DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能 
    DMA_DeInit(DMA2_Stream1);//等待DMA2_Stream1
    while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}//等待DMA2_Stream1可配置 

    /* 配置 DMA Stream */
    DMA_InitStructure.DMA_Channel = DMA_Channel_1;  //通道1 DCMI通道 
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&DCMI->DR;//外设地址为:DCMI->DR
    DMA_InitStructure.DMA_Memory0BaseAddr = DMA_Memory0BaseAddr;//DMA 存储器0地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//外设到存储器模式
    DMA_InitStructure.DMA_BufferSize = DMA_BufferSize;//数据传输量 
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc;//存储器增量模式
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;//外设数据长度:32位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize;//存储器数据长度 
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循环模式 
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; //FIFO模式        
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;//使用全FIFO 
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//外设突发单次传输
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//存储器突发单次传输
    DMA_Init(DMA2_Stream1, &DMA_InitStructure);//初始化DMA Stream
    
    if(DMA_Memory1BaseAddr)
    {
        DMA_DoubleBufferModeCmd(DMA2_Stream1,ENABLE);//双缓冲模式
        DMA_MemoryTargetConfig(DMA2_Stream1,DMA_Memory1BaseAddr,DMA_Memory_1);//配置目标地址1
        DMA_ITConfig(DMA2_Stream1,DMA_IT_TC,ENABLE);//开启传输完成中断

        NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream1_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级2
        NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;   //子优先级3
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;     //IRQ通道使能
        NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
    } 
}

在这里插入图片描述

源码例程:
0积分下载
https://download.csdn.net/download/hyk687/86721935

(2)串口+DMA搬运数据

硬件:stm32F103

void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  //使能DMA传输
    
  DMA_DeInit(DMA_CHx);   //将DMA的通道1寄存器重设为缺省值

    DMA1_MEM_LEN=cndtr;
    DMA_InitStructure.DMA_PeripheralBaseAddr = cpar;  //DMA外设基地址
    DMA_InitStructure.DMA_MemoryBaseAddr = cmar;  //DMA内存基地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  //数据传输方向,从内存读取发送到外设
    DMA_InitStructure.DMA_BufferSize = cndtr;  //DMA通道的DMA缓存的大小
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外设地址寄存器不变
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址寄存器递增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //数据宽度为8位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  //工作在正常缓存模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级 
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输
    DMA_Init(DMA_CHx, &DMA_InitStructure);  //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
        
} 
//开启一次DMA传输
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{ 
    DMA_Cmd(DMA_CHx, DISABLE );  //关闭USART1 TX DMA1 所指示的通道      
    DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN);//DMA通道的DMA缓存的大小
    DMA_Cmd(DMA_CHx, ENABLE);  //使能USART1 TX DMA1 所指示的通道 
}     

源码例程:
原子哥官网或者论坛挺多的。

硬件:stm32F091(评测板NUCLEO-F091RC,官网可以搜的到)

#include "string.h"
extern DMA_HandleTypeDef hdma_usart1_tx;

//发送数组数据
void my_uart1_send_data(uint8_t *tdata,uint16_t tnum){
        //等待发送状态OK
        while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) HAL_Delay(1);
        //发送数据
        HAL_UART_Transmit_DMA(&huart1,tdata,tnum);
}

//发送字符串
void my_uart1_send_string(uint8_t *tdata){
        //等待发送状态OK
        while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) HAL_Delay(1);
        //发送数据
        HAL_UART_Transmit_DMA(&huart1,tdata,strlen(tdata));
}

uint8_t my_uart1_redata=0;
//开启串口接收中断
void my_uart1_enable_inpterr(){
    //开启一次中断
    //HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1);
    HAL_UART_Receive_DMA(&huart1,&my_uart1_redata,1);//设置接收缓冲区
    
}
//串口收到数据回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
    if(huart->Instance == USART1)//判断串口号
    {
        //发送
        my_uart1_send_data(&my_uart1_redata,1);
        //开启一次中断
        //HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1);
    }
}

HAL库:
在这里插入图片描述
因为我这里生成的IAR工程,可以在这里修改成MDK-ARM工程,这样就可以用KEIL打开了。

源码:
https://www.cnblogs.com/dongxiaodong/p/14275284.html

(3)ADC+DMA传输数据

硬件:stm32f103+光敏电阻模块+led


#define ADC1_DR_Address    ((u32)0x4001244C)

 u16 ADC_ConvertedValue;  //采集到的ADC值
/*配置采样通道端口 使能GPIO时钟   设置ADC采样PA2端口信号*/
 void ADC1_GPIO_Config(void)
{ GPIO_InitTypeDef GPIO_InitStructure;    
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;         //GPIO设置为模拟输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);   
}


/*配置ADC1的工作模式为DMA模式  */
 void ADC1_Mode_Config(void)
{
  DMA_InitTypeDef DMA_InitStructure;
  ADC_InitTypeDef ADC_InitStructure;    
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能MDA1时钟
    /* DMA channel1 configuration */
  DMA_DeInit(DMA1_Channel1);  //指定DMA通道
  DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;//设置DMA外设地址
  DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_ConvertedValue;  //设置DMA内存地址,ADC转换结果直接放入该地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外设为设置为数据传输的来源
  DMA_InitStructure.DMA_BufferSize = 1; //DMA缓冲区设置为1;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  DMA_Init(DMA1_Channel1, &DMA_InitStructure);
  
  /* Enable DMA channel1 */
  DMA_Cmd(DMA1_Channel1, ENABLE);  //使能DMA通道

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);  //使能ADC1时钟
     
  /* ADC1 configuration */
  ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //使用独立模式,扫描模式
  ADC_InitStructure.ADC_ScanConvMode = ENABLE;
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //无需外接触发器
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //使用数据右对齐
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  ADC_InitStructure.ADC_NbrOfChannel = 1;  // 只有1个转换通道
  ADC_Init(ADC1, &ADC_InitStructure);

  /* ADC1 regular channel11 configuration */ 
  ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 1, ADC_SampleTime_55Cycles5); //通道1采样周期55.5个时钟周期

  /* Enable ADC1 DMA */
  ADC_DMACmd(ADC1, ENABLE);  //使能ADC的DMA
  
  /* Enable ADC1 */
  ADC_Cmd(ADC1, ENABLE); //使能ADC1

  /* Enable ADC1 reset calibaration register */   
  ADC_ResetCalibration(ADC1);
  /* Check the end of ADC1 reset calibration register */
  while(ADC_GetResetCalibrationStatus(ADC1));

  /* Start ADC1 calibaration */
  ADC_StartCalibration(ADC1);
  /* Check the end of ADC1 calibration */
  while(ADC_GetCalibrationStatus(ADC1));
     
  /* Start ADC1 Software Conversion */ 
  ADC_SoftwareStartConvCmd(ADC1, ENABLE);  //开始转换
}

/*初始化ADC1 */
void ADC1_Init(void)
{
    ADC1_GPIO_Config();
    ADC1_Mode_Config();
}
int main(void)
{
    ADC1_Init();//adc初始化
    delay_init();
   while(1)
 {
      if(ADC_ConvertedValue>3000)//采集到的AD值(光敏电阻的值)大于3000表示光线暗
            GPIO_ResetBits(GPIOA,GPIO_Pin_0);//开灯
        else//光线足够
            GPIO_SetBits(GPIOA,GPIO_Pin_0);//关灯
        delay_ms(20); //延时                          
 }  
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yank_k

点个关注加分享,一起探讨学习!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值