搜索
bottom↓
回复: 0

《STM32MP1 M4裸机HAL库开发指南》 第二十八章 DMA实验

[复制链接]

出0入234汤圆

发表于 2022-11-14 10:06:16 | 显示全部楼层 |阅读模式
1)实验平台:正点原子STM32MP157开发板
2)购买链接:https://item.taobao.com/item.htm?&id=629270721801
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-318813-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子STM32MP157技术交流群:691905614 lQLPJxaFi2zaB4UWWrDAMgIsFEW2pwLb3abnwDMA_90_22.png
lQDPJxaFi2nfFizMjM0CbLCPlxn_FVheIQLb3aGrwFQA_620_140.jpg

lQLPJxaFi2nfFhLMkM0BXrDNvOUyeU_FPgLb3aGvQNIA_350_144.png
第二十八章 DMA实验

串口可以通过轮询、中断以及DMA的方式接收数据,在前面串口通信实验章节,我们学习了如何使用UART中断接收数据,本章节,我们将介绍STM32MP157的DMA,我们将利用DMA来实现串口数据传送。
本章分为如下几个小节:
28.1、 DMA简介
28.2、 硬件设计
28.3、 软件设计

28.1 DMA简介

        系统的核心是CPU,CPU无时无刻在处理着大量的事物,如复制数据、转移数据、计算等等,对于一些事情,可以让CPU以外的其它模块去执行,那么CPU就可以腾出部分资源去处理其它事情,这样可以更好地利用CPU的资源。要处理的这些事物中,例如对于转移数据,其实也可以不需要CPU的参与,可以直接使用DMA来处理。

        DMA,全称为:Direct Memory Access,即直接存储器访问。DMA传输将数据从一个地址空间复制到另外一个地址空间,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,传输动作本身是由 DMA 控制器来实行和完成,没有中断处理方式那样保留现场和恢复现场的过程,无需任何CPU操作就可以实现高速数据移动,这使空余出的CPU资源可专注于处理其他更加实用或者紧急的操作,从而使CPU的效率大大提高了。
第二十八章 DMA实验563.png
图28.1. 1数据传输方式

        DMA就是为了解决大量数据转移而过多消耗CPU资源而产生的,DMA的作用就是实现数据的直接传输,去掉了传统的数据传输需要经过CPU处理的环节,如果没有DMA,那么CPU传输还需要以内核作为中转站,特别是在转移大量数据时,占用更多的CPU资源。
        以ADC外设采集数据为例,当ADC采集到数据时,ADC外设向DMA控制器发送一个请求信号,DMA收到请求后,触发DMA工作,如果此时DMA控制器接收到多个请求信号,DMA控制器会根据通道的优先权来处理请求,优先权高的优先处理。DMA控制器从AHB外设获取ADC采集到的数据,暂时存储到DMA通道中,然后再通过AHB将DMA通道的数据传送到SRAM中,完成一次数据转移,数据转移的过程不需要CPU的参与。
28.1.1 STM32MP157 DMA简介
1. MDMA和双口DMA
STM32MP157内部有3个DMA控制器:1个高速主DMA(MDMA)和2个双口DMA(DMA1和DMA2)。M4和A7可以共享MDMA,而DMA1和DMA2只能由A7或者M4中某一个使用。
1)MDMA控制器为主存储器和外围寄存器访问(系统访问端口)提供了主AXI接口,MDMA用于实现数据流:内存内存、内存外设、外设内存之间的高速数据传输。
2)双口DMA同样可以实现数据流:内存内存、内存外设、外设内存之间的高速数据传输,双口DMA支持AHB外设之间的数据传输,具有独立的FIFO,支持不同位宽的数据传输。
这里的内存就是指片内 SRAM和Flash等存储器,外设一般是指外设的数据寄存器。本章我们用到的是双口DMA,因此接下来我们重点介绍双口DMA。DMA的数据流中,DMA数据流x外设地址寄存器(DMA_SxPAR)存放的是DMA读或者写数据的外设数据寄存器的基址,DMA数据流x存储器0地址寄存器(DMA_SxM0AR)存放的是DMA读或者写数据的存储器地址:
如果选择外设内存的数据流的形式,设置DMA_SxCR 寄存器的 DIR[1:0] 位为00,即选择数据传输方向为外设到内存,此时,DMA_SxPAR为源地址,DMA_SxM0AR为目的地址。
如果选择内存外设的数据流形式,设置DMA_SxCR 寄存器的 DIR[1:0] 位为01,即选择数据传输方向为内存到外设,此时,DMA_SxM0AR为源地址,DMA_SxPAR为目的地址。
如果选择内存内存,设置DMA_SxCR 寄存器的 DIR[1:0] 位为10,DMA_SxCR和DMA_SxM0AR的配置和外设内存的一样。
2. 双口DMA的特征
STM32MP157内部有2个双口DMA控制器(DMA1和DMA2),共16个数据流(每个控制器8个),每一个双口DMA控制器都用于管理一个或多个外设的存储器访问请求,每个数据流可以有多达116个通道(或称请求),每个数据流通道都有一个仲裁器(Arbiter),仲裁器用于处理DMA请求间的优先级,仲裁器根据通道请求的优先级来启动外设/存储器的访问。
第二十八章 DMA实验1868.png
图28.1.1. 1图 DMA控制器数据流和通道关系

STM32MP157的双口DMA有以下一些特性:
① 双AHB主总线架构,一个用于存储器访问,另一个用于外设访问;
② 仅支持32位访问的AHB从编程接口
③ 每个DMA控制器有8个数据流,每个数据流有多达116个通道(或称请求)
④ 每个数据流有单独的四级32位先进先出存储器缓冲区(FIFO),可用于FIFO模式或直接模式。
⑤ 通过硬件可以将每个数据流配置为:
1,常规通道,支持外设到内存,内存到外设和内存到内存的传输。
2,支持在存储器方双缓冲的双缓冲区通道
⑥ 8个数据流中的每一个都连接到专用硬件DMA通道(请求);
⑦ DMA 数据流请求之间的优先级可用软件编程(4个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求0的优先级高于请求1);
⑧ 每个数据流也支持通过软件触发存储器到存储器的传输;
⑨ 每个数据流的通道请求可以由DMA请求复用器(DMAMUX)选择,可供选择的通道请求数多达116个。此选择可由软件配置DMAMUX,并允许108个外设发起DMA请求;
⑩ 要传输的数据项的数目可以由DMA控制器或外设管理:
1,DMA 流控制器:要传输的数据项的数目是1到65535,可用软件编程
2,外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过硬件发出传输结束的信号
⑪ 独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA自动封装/解封必要的传输数据来优化带宽。这个特性仅在FIFO模式下可用。
⑫ 对源和目标的增量或非增量寻址
⑬ 支持4个、8个和16个节拍的增量突发传输。突发增量的大小可由软件配置,通常等于外设FIFO大小的一半
⑭ 每个数据流都支持循环缓冲区管理
⑮ 5个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO 错误、直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求
28.1.2 DMA框图
1. DMA框图
STM32MP157有两个双口DMA控制器,DMA1和DMA2,本章,我们仅针对DMA2进行介绍。下面先来学习双口DMA控制器框图,通过学习DMA控制器框图会有一个很好的整体掌握,同时对之后的编程也会有一个清晰的思路。STM32MP157的双口MDA控制器框图如下图所示:
第二十八章 DMA实验2907.png
图28.1.2. 1 DMA控制器框图

图中,我们标记了6处位置,起作用分别是:
①,DMA控制器的从机编程接口,通过该接口可以对DMA的相关控制寄存器进行设置,从而配置DMA,实现不同的功能。同时,该接口可以输出dma_it[0:7]的中断信号到NVIC(对于M4内核,中断控制器是NVIC;对于A7内核,中断控制器是GIC。本文我们讲解的是M4内核部分),以及dma_tcif[0:7]的信号到MDMA。
②,DMA控制器的外设接口,用于访问相关外设,特别的,当外设接口设置的访问地址是内存地址的时候,DMA就可以工作在内存到内存模式了。
③,DMA控制器的FIFO区,可以实现存储器接口到外设接口之间的数据长度非对齐传输。
每个数据流(总共8个数据流)都有一个独立的FIFO(先进先出存储器缓冲区),FIFO用于临时存放要传输的数据,可通过DMA_SxFCR寄存器来控制控制 FIFO的阈值,如果数据存储量达到阈值时,FIFO中的数据将传输到目标中。
④,DMA控制器的存储器接口,用于访问外部存储器,特别的当存储器地址是外设地址的时候,可以实现类似外设到外设的传输效果。
⑤,DMA控制器的仲裁器,用于仲裁数据流0~7的请求优先级,保证数据有序传输。
⑥,这是DMA控制器数据流0~7的通道请求信号,由DMAMUX的选择,每个数据流有多达116个通道请求可以选择。我们必须根据实际需求来选择对应的通道请求。
2. DMA请求复用器(DMAMUX)
STM32MP157资源丰富,外设众多,为实现正常传输,需要通过DMAMUX对通道资源进行 管理。这里重点介绍一下DMAMUX,其全称是DMA请求复用器,用于管理DMA1和DMA2的通道请求。DMAMUX总共有16个通道,其中通道0~7对应DMA1的数据流0~7,通道8~15对应DMA2的数据流0~7。DMAMUX框图如下所示:
第二十八章 DMA实验3730.png
图28.1.2. 2 DMAMUX控制器框图


        整个DMAMUX的功能比较复杂,包括:选择具体的DMA请求通道(源)、同步控制、请求生成、请求计数和中断等,本章我们只用到其最简单的应用:选择具体的DMA请求通道,即通过DMAMUX选择DMA1/DMA2的数据流通道(请求通道)。为了方便说明,我们在图中标出了几个关键点:
①,外设请求输入,即STM32MP157芯片内部外设的请求,这部分总共有p+1个外设请求,对于DMAMUX来说,总共有108个(p=107)。
②,通道选择,通过DMAMUX_CmCR寄存器的DMAREQ_ID[7:0]位来选择DMA的具体请求通道,总共有116个通道,其中①处有108个,还有8个通道来自DMAMUX内部的请求生成器,我们一般用①处的108个请求通道。
③,同步控制,用于同步DMA请求,最终输出给DMA控制器,我们本章不使用同步控制,因此可以关闭同步(通过DMAMUX_CmCR寄存器的SE位设置)。
④,输出到DMA控制器的请求信号,对DMAMUX来说总共有16个输出请求信号(m=15),DMAMUX通道0到7与DMA1通道0到7相连,DMAMUX通道8到15与DMA2通道0到7 相连。
        因此,我们一般只需要通过设置DMAMUX通道m的DMAMUX_CmCR寄存器来选择输入通道,即可完成DMA1/DMA2某个数据流的DMA输入请求通道选择。
3. DMA请求映射
        DMA通道/请求和外设不是任意连接的,需要根据DMA请求映射表来,DMAMUX的DMA请求资源分配表如下所示(下图只是截图一部分):
第二十八章 DMA实验4435.png
图28.1.2. 3 DMA请求资源分配表(部分)

        由表可知,DMAMUX的DMA请求输入总共有116个(116之后的是保留的,具体查看参考手册,这里只截图部分,列表见参考手册的Table 110),表中我们省略了一部分内容。
        举例来说,假设我们要设置DMA2数据流3的DMA输入请求通道为:串口7的TX(UART7_TX),则:
1,DMA2数据流3对应DMAMUX的通道11(从0开始算起,8~15对应的是DMA2,第4和刚好是通道11)。
2,串口7的TX对应的外设DMA请求编号为:80
        因此,我们只需要设置DMAMUX_CxCR的DMAREQ_ID[6:0]位为80即可完成DMA2数据流3的请求来自UART7_TX的设置。
        由于DMA1/DMA2数据流0~7的DMA请求全部是来自DMAMUX的设置(16个通道),DMAMUX的所有通道都可以独立设置,因此,对于STM32MP157来说,DMA1/DMA2的任何一个数据流的请求都可以来自116个通道的任意一个,因此相对于STM32其他系列来说,STM32MP157的DMA配置更加灵活,无需固定通道对应固定外设请求,可以随意设置。
28.1.3 DMA寄存器
        DMA配置参数包括:通道地址、优先级、数据传输方向、存储器/外设数据宽度、存储器/外设地址、数据传输量等。
1. DMAMUX请求线复用器通道x配置寄存器(DMAMUX_CxCR),(x = 0到15)
DMAMUX请求线复用器通道x配置寄存器描述如下图所示:
第二十八章 DMA实验5108.png
图28.1.3. 1 DMAMUX1_CxCR寄存器各位描述(部分)


本章,该寄存器我们只需要关心DMAREQ_ID[6:0]这7个位,其他位全部用不到(设置为0即可),DMAREQ_ID[6:0]用于选择输出通道x的DMA输入请求,我们需要通过这8个位选择DMA1/DMA2数据流的请求源。详细请求列表(DMA请求映射表)见参考手册的Table 110:
第二十八章 DMA实验5316.png
图28.1.3. 2 DMA请求资源分配表(部分)

2. DMA中断状态寄存器(DMA_LISR和DMA_HISR)
DMA中断状态寄存器,该寄存器总共有2个:DMA_LISR和DMA_HISR,每个寄存器管理4数据流(总共8个),DMA_LISR寄存器用于管理数据流0~3,而DMA_HISR用于管理数据流4~7。这两个寄存器各位描述都完全一模一样,只是管理的数据流不一样。
这里,我们仅以DMA_LISR寄存器为例进行介绍,DMA_LISR各位描述如下图所示:
992112BF-35D4-4bd4-B2DC-713996AF4786.png
图28.1.3. 3 DMA_LISR寄存器

如果开启了DMA_LISR中这些位对应的中断,则在达到条件后就会跳到中断服务函数里面去,即使没开启,我们也可以通过查询这些位来获得当前DMA传输的状态。这里我们常用的是TCIFx位,即数据流x的DMA传输完成与否标志。注意此寄存器为只读寄存器,所以在这些位被置位之后,只能通过其他的操作来清除。DMA_HISR寄存器各位描述通DMA_LISR寄存器各位描述完全一样,只是对应数据流4~7,这里我们就不列出来了。
3. DMA中断标志清除寄存器(DMA_LIFCR和DMA_HIFCR)
DMA中断标志清除寄存器, 该寄存器同样有2个:DMA_LIFCR和DMA_HIFCR,同样是每个寄存器控制4个数据流,DMA_LIFCR寄存器用于管理数据流0~3,而DMA_ HIFCR用于管理数据流4~7。这两个寄存器各位描述都完全一模一样,只是管理的数据流不一样。
这里,我们仅以DMA_LIFCR寄存器为例进行介绍,DMA_LIFCR各位描述如下图所示:
2C223400-13C8-4b0d-80AA-09D25BDC2BB6.png
图28.1.3. 4 DMA_LIFCR寄存器

DMA_LIFCR的各位就是用来清除DMA_LISR的对应位的,通过写1清除。在DMA_LISR被置位后,我们必须通过向该位寄存器对应的位写入1来清除。DMA_HIFCR的使用同DMA_LIFCR类似,这里就不做介绍了。
第四个是DMA数据流x配置寄存器(DMA_SxCR)(x=0~7,下同)。该寄存器的我们在这里就不贴出来了,见《STM32MP157参考手册》。该寄存器控制着DMA的很多相关信息,包括数据宽度、外设及存储器的宽度、优先级、增量模式、传输方向、中断允许、使能等都是通过该寄存器来设置的。所以DMA_ SxCR是DMA传输的核心控制寄存器。
第五个是DMA数据流x数据项数寄存器(DMA_SxNDTR)。这个寄存器控制DMA数据流x的每次传输所要传输的数据量。其设置范围为0~65535。并且该寄存器的值会随着传输的进行而减少,当该寄存器的值为0的时候就代表此次数据传输已经全部发送完成了。所以可以通过这个寄存器的值来知道当前DMA传输的进度。特别注意,这里是数据项数目,而不是指的字节数。比如设置数据位宽为16位,那么传输一次(一个项)就是2个字节。
第六个是DMA数据流x的外设地址寄存器(DMA_SxPAR)。该寄存器用来存储STM32MP157外设的地址,比如我们使用串口4(UART4),那么该寄存器必须写入0x40010028(其实就是&USART_TDR),即UART4的地址(基地址+偏移地址,基地址= 0x40010000,USART_TDR的偏移地址= 0x28)。如果使用其他外设,就修改成相应外设的地址就行了。
第二十八章 DMA实验6785.png
图28.1.3. 5 USART_TDR的地址

最后一个是DMA数据流x的存储器地址寄存器,由于STM32MP157的DMA支持双缓存,所以存储器地址寄存器有两个:DMA_SxM0AR和DMA_SxM1AR,其中DMA_SxM1AR仅在双缓冲模式下,才有效。本章我们没用到双缓冲模式,所以存储器地址寄存器就是:DMA_SxM0AR,该寄存器和DMA_CPARx差不多,但是是用来放存储器的地址的。比如我们使用SendBuf[7800]数组来做存储器,那么我们在DMA_SxM0AR中写入&SendBuff就可以了。
4. DMA数据流x外设地址寄存器(DMA_SxPAR)
DMA数据流x外设地址寄存器如下图所示:
第二十八章 DMA实验7123.png
图28.1.3. 6 DMA_SxPAR寄存器

该寄存器存放的是DMA读或者写数据的外设数据寄存器的基址。本实验,我们需要通过DMA读取ADC1转换后存放在ADC1常规数据寄存器 (ADC_DR) 的结果数据。所以我们需要给DMA_SxPAR寄存器写入ADC_DR寄存器的地址。这样配置后,DMA就会从ADC_DR寄存器的地址读取ADC的转换后的数据到某个内存空间。这个内存空间地址需要我们通过DMA_SxM0AR寄存器来设置,比如定义一个变量,把这个变量的地址值写入该寄存器。
注意:DMA_SxPAR寄存器受到写保护,只有DMA_SxCR寄存器中的EN为“0”时才可以写入,即先要禁止数据流传输才可以写入。
5. DMA数据流x存储器0地址寄存器(DMA_SxM0AR)
DMA数据流x存储器0地址寄存器描述如下图所示:
第二十八章 DMA实验7515.png
图28.1.3. 7 DMA_SxM0AR寄存器

该寄存器存放的是DMA读或者写数据的存储器的地址。这些位受到写保护,只有当禁止数据流(DMA_SxCR 寄存器中的位 EN=“0”)或使能数据流(DMA_SxCR 寄存器中的 EN=“1”)并且 DMA_SxCR 寄存器中的位 CT =“1”(在双缓冲区模式下)时才可以写入。如果用到双缓冲区模式我们还需要用到DMA_SxM1AR寄存器,本实验我们是用不到的。
6. DMA 数据流 x 配置寄存器 (DMA_SxCR) (x = 0..7)
第二十八章 DMA实验7791.png
图28.1.3. 8 DMA_SxCR寄存器

        DMA_SxCR寄存器的位比较多,我们先关注几个位,这些位将由软件置 1 和清零,这些位受到保护,只有 EN 为“0”时才可以写入。
        PL[1:0]位用于设置优先级:00:低优先级;01:中优先级;10:高优先级;11:非常高优先级。
        MSIZE[1:0]用于设置存储器数据大小:00:字节(8 位);01:半字(16 位);10:字(32 位);11:保留。注意:在直接模式下,当位 EN =“1”时,MSIZE 位由硬件强制置为与 PSIZE 相同的值。
        PSIZE[1:0]用于外设数据大小:00:字节(8 位);01:半字(16 位);10:字(32 位);11:保留。
        DIR[1:0]用于配置数据传输方向:00:外设到存储器;01:存储器到外设;10:存储器到存储器;11:保留。
28.1.4 DMA的HAL库驱动
DMA在HAL库中的驱动代码在stm32mp1xx_hal_dma.c文件(及其头文件)中。UART4相关的HAL库驱动代码在stm32mp1xx_hal_uart.c文件(及其头文件)中。
1. HAL_DMA_Init函数
DMA的初始化函数,其声明如下:
HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma);
函数描述:
用于初始化DMA1,DMA2和BDMA。
函数形参:
形参1是DMA_HandleTypeDef结构体类型指针变量,其定义如下:
typedef struct __DMA_HandleTypeDef
{
  void                              *Instance;       /* 寄存器基地址 */
  DMA_InitTypeDef                 Init;             /* DAM通信参数 */
  HAL_LockTypeDef                 Lock;             /* DMA锁对象 */
  __IO HAL_DMA_StateTypeDef     State;            /* DMA传输状态 */
  void                              *Parent;     /* 父对象状态,HAL库处理的中间变量 */
  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传输半完全内存回调 */
  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;
这个结构体内容比较多,上面已注释中文翻译,下面列出几个成员说明一下。
Instance:是用来设置寄存器基地址,例如要设置为DMA2的数据流7,那么取值为DMA2_Stream7。
Parent:是HAL库处理中间变量,用来指向DMA通道外设句柄。
StreamBaseAddress和StreamIndex是数据流基地址和索引号,这个是HAL库处理的时候会自动计算,用户无需设置。
其他成员变量是HAL库处理过程状态标识变量,这里就不做过多讲解。
接下来我们重点介绍Init,它是DMA_InitTypeDef结构体类型变量,该结构体定义如下:
typedef struct
{
  uint32_t Request;               /* 请求设置,设置是哪个外设请求的 */     
  uint32_t Direction;             /* 传输方向,例如存储器到外设DMA_MEMORY_TO_PERIPH */
  uint32_t PeriphInc;             /* 外设(非)增量模式,非增量模式DMA_PINC_DISABLE */  
  uint32_t MemInc;                 /* 存储器(非)增量模式,增量模式DMA_MINC_ENABLE */  
  uint32_t PeriphDataAlignment; /* 外设数据大小:8/16/32位 */
  uint32_t MemDataAlignment;     /* 存储器数据大小:8/16/32位 */
  uint32_t Mode;                   /* 模式:外设流控模式/循环模式/普通模式 */   
  uint32_t Priority;              /* DMA优先级:低/中/高/非常高 */
  uint32_t FIFOMode;              /* FIFO模式开启或者禁止 */
  uint32_t FIFOThreshold;        /* FIFO阈值选择 */
  uint32_t MemBurst;              /* 存储器突发模式:单次/4个节拍/8个节拍/16个节拍 */  
  uint32_t PeriphBurst;          /* 外设突发模式:单次/4个节拍/8个节拍/16个节拍 */   
}DMA_InitTypeDef;
该结构体成员变量非常多,但是每个成员变量配置的基本都是DMA_SxCR寄存器和DMA_SxFCR寄存器的相应位。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
2. 串口的DMA发送函数HAL_UART_Transmit_DMA
函数描述:
        使用 DMA 方式发送数据。
函数形参:
        形参1是UART_HandleTypeDef结构体类型指针变量;
        形参2是要发送的数据地址;
        形参3是要发送的数据大小(单位:字节)。
函数返回值:
        HAL_StatusTypeDef枚举类型的值。
串口的DMA发送实际是串口控制寄存器USART_CR3的位7来控制的,在HAL库中操作该寄存器来使能串口DMA发送的函数为HAL_UART_Transmit_DMA,该函数声明如下:
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size)
这里大家需要注意,调用该函数后会开启相应的DMA中断。
HAL库还提供了对串口的DMA发送的停止,暂停,继续等操作函数:
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart);  /* 停止 */
HAL_StatusTypeDef HAL_UART_DMAPause(UART_HandleTypeDef *huart); /* 暂停 */
HAL_StatusTypeDef HAL_UART_DMAResume(UART_HandleTypeDef *huart);/* 恢复 */
3. 串口的DMA接收函数HAL_UART_Receive_DMA
函数描述:
        使用 DMA 方式接收数据。
函数形参:
        形参1是UART_HandleTypeDef结构体类型指针变量;
        形参2是要接收的数据地址;
        形参3是要接收的数据大小(单位:字节)。
函数返回值:
        HAL_StatusTypeDef枚举类型的值。
        有DMA发送函数当然也会有DMA接收函数:
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
4. DMA中断方式启动函数HAL_DMA_Start_IT
函数描述:
使用 DMA 中断方式启动DMA,注意此函数会开启多个中断,有TC发送完成中断、TE传输错误中断、DME直接模式错误中断、HT半传输中断和FIFO错误中断。
函数形参:
        形参1是DMA_HandleTypeDef结构体类型指针变量;
        形参2是源内存缓冲区地址;
        形参3是目标内存缓冲区地址;
        形参3是要从源传输到目标的数据长度。
函数返回值:
        HAL_StatusTypeDef枚举类型的值。
注意事项:
DMA传输完成后,会执行对应的DMA中断服务函数,对应的DMA中断服务函数会调用DMA中断请求函数HAL_DMA_IRQHandler,用户可以通过自定义函数指针如XferCpltCallback、XferM1CpltCallback和XferErrorCallback等来添加自己的函数。
HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
5. DMA启动函数HAL_DMA_Start
函数描述:
在配置了源地址和目标地址以及要传输的数据长度之后,使用此函数来启动DMA传输。
函数形参:
        形参1是DMA_HandleTypeDef结构体类型指针变量;
        形参2是源内存缓冲区地址;
        形参3是目标内存缓冲区地址;
        形参3是要从源传输到目标的数据长度。
函数返回值:
        HAL_StatusTypeDef枚举类型的值。
注意事项:
        可以使用HAL_DMA_PollForTransfer函数去轮询当前传输的结束,在这种情况下,用户可以根据其应用程序配置固定超时时间。
HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
6. 其它函数和宏定义
1)函数HAL_DMA_Abort用于终止使用函数HAL_DMA_Start开启的DMA传输;
2)函数HAL_DMA_Abort_IT用于终止使用函数HAL_DMA_Start_IT开启的DMA传输;
3)使用HAL_DMA_GetState函数获取DMA状态;
        在DMA传输过程中,我们要查询DMA传输通道的状态,也可以使用:__HAL_DMA_GET_FLAG
4)使用HAL_DMA_GetError函数可以获取哪方面错误;
5)函数DMA_SetConfig用于设置源地址、目的地址和传输的数据长度;
6)几个常用的DMA宏定义(注意宏定义有参数,清根据stm32mp1xx_hal_dma.c文件的来):
__HAL_DMA_ENABLE:  使能指定的DMA Stream;
__HAL_DMA_DISABLE: 禁止指定的DMA Stream;
__HAL_DMA_GET_FS:  返回当前DMA Stream FIFO填充情况;
__HAL_DMA_ENABLE_IT: 使能指定的DMA Stream中断;
__HAL_DMA_DISABLE_IT: 禁止指定的DMA Stream中断;
__HAL_DMA_GET_IT_SOURCE: 检查指定的指定的DMA Stream中断是否发送;
__HAL_DMA_GET_COUNTER:获取当前传输剩余数据量;
__HAL_DMA_SET_COUNTER:设置对应的DMA数据流传输的数据量大小。
7. DMA回调函数
        每个DMA通道都可以在DMA传输过半、传输完成和传输错误时产生中断,中断标志位如下表所示:
lQLPJxbfGB1DkBtzzQISsLrFiIeqAz_XA26VdfMAEAA_530_115.png
表28.1.4. 1中断标志位

        DMA中断对于每个数据流都有一个中断服务函数,比如DMA2_Stream0的中断服务函数为DMA2_Stream0_IRQHandler。HAL库提供了通用DMA中断处理函数HAL_DMA_IRQHandler,在该函数内部,会对DMA传输状态进行分析,然后调用相应的中断处理回调函数:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);     /* 发送完成回调函数 */
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);/* 发送一半回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);     /* 接收完成回调函数 */
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);/* 接收一半回调函数 */
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);      /* 传输出错回调函数 */
        注意的是,DMA传输完成回调函数 UART_DMAReceiveCplt会调用HAL_UART_RxCpltCallback函数
28.2 硬件设计
1. 例程功能
        UART4以DMA方式发送数据,打开串口调试助手,可以收到DMA发送的内容,同时LED0闪烁,用于提示程序正在运行。
2. 硬件资源
        1)LED灯:LED0
        2)串口4
        3)DMA
lQLPJxbfGCpggKsozQE-sKQC_Gis266AA26Vi5kA9AA_318_40.png
表28.2. 1硬件资源

3. 原理图
        DMA属于STM32MP157内部资源,通过软件设置好就可以了,本实验通过DMA的方式向串口发送数据,通过串口上位机可以看到传输的内容。
28.3 程序设计
        本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\3、M4裸机驱动例程\ MP157-M4 HAL库V1.2\实验17 DMA实验。
28.3.1 DMA驱动代码
DMA驱动代码我们把它放在dma.c和dma.h文件中。
1. dma.h文件代码
#ifndef __DMA_H
#define __DMA_H
#include "./SYSTEM/sys/sys.h"
extern DMA_HandleTypeDef  g_dma_handle;             /* DMA句柄 */
/* 配置DMAx_CHx */
void dma_init(DMA_Stream_TypeDef *dma_stream_handle, uint32_t ch);
/* DMA请求复用器初始化 */  
void dma_mux_init(DMA_Stream_TypeDef *DMA_Streamx, uint8_t ch);
/* 使能一次DMA传输 */   
void dma_enable(DMA_Stream_TypeDef *DMA_Streamx, uint16_t ndtr);   
#endif
2. dma.c文件代码
        下面是DMA的初始化函数,其定义如下:
DMA_HandleTypeDef  g_dma_handle;              /* DMA句柄 */
extern UART_HandleTypeDef g_uart4_handle;     /* UART句柄 */
/**
* @brief     串口TX DMA初始化函数
*   @note    这里的传输形式是固定的, 这点要根据不同的情况来修改
*            从存储器 -> 外设模式/8位数据宽度/存储器增量模式
*
* @param     dma_stream_handle : DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
* @retval    无
*/
void dma_init(DMA_Stream_TypeDef *dma_stream_handle, uint32_t ch)
{
  /* 得到当前stream是属于DMA2还是DMA1 */
  if ((uint32_t)dma_stream_handle > (uint32_t)DMA2)     
  {
    __HAL_RCC_DMA2_CLK_ENABLE();                      /* DMA2时钟使能 */
  }
  else
  {
    __HAL_RCC_DMA1_CLK_ENABLE();                      /* DMA1时钟使能 */
  }
    __HAL_RCC_DMAMUX_CLK_ENABLE();                    /* 使能DMAMUX时钟 */
    /* 将DMA与USART4联系起来(发送DMA) */
    __HAL_LINKDMA(&g_uart4_handle, hdmatx, g_dma_handle);  

  /* Tx DMA配置 */
  g_dma_handle.Instance = dma_stream_handle;                /* 数据流选择 */
  g_dma_handle.Init.Request = ch;                              /* DMA通道选择 */
  g_dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH;      /* 存储器到外设 */
  g_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;           /* 外设非增量模式 */
  g_dma_handle.Init.MemInc = DMA_MINC_ENABLE;                /* 存储器增量模式 */
  g_dma_handle.Init.PeriphDataAlignment= DMA_PDATAALIGN_BYTE;/* 外设数据长度:8位 */
  g_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; /* 存储器数据长度:8位 */
  g_dma_handle.Init.Mode = DMA_NORMAL;                         /* 外设流控模式 */
  g_dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;          /* 中等优先级 */
  g_dma_handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE;         /* 关闭FIFO模式 */
  g_dma_handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;/* FIFO阈值配置 */
  g_dma_handle.Init.MemBurst = DMA_MBURST_SINGLE;             /* 存储器突发单次传输 */
  g_dma_handle.Init.PeriphBurst = DMA_PBURST_SINGLE;         /* 外设突发单次传输 */

  HAL_DMA_DeInit(&g_dma_handle);   
  HAL_DMA_Init(&g_dma_handle);
}
该函数是一个通用的DMA配置函数,DMA1/DMA2的所有通道,都可以利用该函数配置,不过有些固定参数可能要适当修改(比如位宽,传输方向等)。该函数在外部只能修改DMA数据流编号和通道号,更多的其他设置只能在该函数内部修改。对照前面的配置步骤的详细讲解来分析这部分代码即可。
3. main.c文件代码
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/BEEP/beep.h"
#include "./BSP/KEY/key.h"
#include "./USMART/usmart.h"
#include "./BSP/DMA/dma.h"
/* 要循环发送的字符串 */
const uint8_t TEXT_TO_SEND[] = {"正点原子 STM32MP1 DMA 串口实验"};
/* 发送数据长度, 等于sizeof(TEXT_TO_SEND) + 2的200倍. */  
#define SEND_BUF_SIZE       (sizeof(TEXT_TO_SEND) + 2) * 200   
uint8_t g_sendbuf[SEND_BUF_SIZE];       /* 发送数据缓冲区 */
/**
* @brief       主函数
* @param       无
* @retval      无
*/
int main(void)
{
    uint8_t  key = 0;
    uint16_t i, k;
    uint16_t len;
    uint8_t  mask = 0;

    HAL_Init();             /* 初始化HAL库  */
    /* 初始化M4内核时钟,209M */
    if(IS_ENGINEERING_BOOT_MODE())
    {
    sys_stm32_clock_init(34, 2, 2, 17, 6826);
    }
    usart_init(115200);     /* 串口初始化为115200 */
    delay_init(209);        /* 延时初始化 */
    led_init();             /* 初始化LED  */
    key_init();             /* 初始化按键 */
    dma_init(DMA2_Stream7, DMA_REQUEST_UART4_TX);  /* 初始化DMA */
    len = sizeof(TEXT_TO_SEND);
    k = 0;
    for (i = 0; i < SEND_BUF_SIZE; i++) /* 填充ASCII字符集数据 */
    {
      if (k >= len)            /* 入换行符 */
      {
        if (mask)
         {
            g_sendbuf = 0x0a;
            k = 0;
        }
        else
        {
            g_sendbuf = 0x0d;
            mask++;
        }
      }
        else        /* 复制TEXT_TO_SEND语句 */
        {
            mask = 0;
            g_sendbuf = TEXT_TO_SEND[k];
            k++;
        }
    }
    i = 0;
    while (1)
    {
      key = key_scan(0);
      if (key == KEY0_PRES)/* 进入睡眠模式 */
      {
        printf("\r\nDMA DATA:\r\n");
        /* 开始一次DMA传输! */
        HAL_UART_Transmit_DMA(&g_uart4_handle,g_sendbuf,SEND_BUF_SIZE);
        /*
        * 等待DMA传输完成,此时我们来做另外一些事情,比如点灯  
        * 实际应用中,传输数据期间,可以执行另外的任务
        */
        while (1)
        {
            /* 等待DMA1_Steam7传输完成 */
            if (__HAL_DMA_GET_FLAG(&g_dma_handle, DMA_FLAG_TCIF3_7))        
            {
            /* 清除DMA1_Steam7传输完成标志 */
            __HAL_DMA_CLEAR_FLAG(&g_dma_handle, DMA_FLAG_TCIF3_7);
            /* 传输完成以后关闭串口DMA */
            HAL_UART_DMAStop(&g_uart4_handle);                                      
            break;
            }
        }
        printf("\r\nTransimit Finished!\r\n"); /* 提示传送完成 */
    }           
    i++;
    delay_ms(10);
    if (i == 20)
     {
        LED0_TOGGLE(); /* LED0闪烁,提示系统正在运行 */
        i = 0;
     }
   }
}
main函数的流程大致是:先初始化发送数据缓冲区g_sendbuf的值,然后通过KEY0开启串口DMA发送,即使用HAL_UART_Transmit_DMA函数开启DMA发送。在发送过程中,通过__HAL_DMA_GET_FLAG函数等待DMA1_Steam7传输完成,传输完成后,通过__HAL_DMA_CLEAR_FLAG函数清除DMA1_Steam7传输完成标志,每次传输完成后,都会关闭串口DMA,当按键KEY0再次按下时,继续调用HAL_UART_Transmit_DMA函数开启串口DMA发送,如此反复。
28.3.2 编译和测试
        编译不报错,将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。按下KEY0后,串口终端打印如下内容:
第二十八章 DMA实验20189.png
图28.3. 2.1运行结果

        以上实验是将我们定义在Senbuff数组里的数据通过DMA进行发送/接收,如果想发送/接收发送自定义的数据,可以使用DMA+串口接收空闲中断(IDLE)或者其他方式来实现。
        关于使用DMA+串口接收空闲中断(IDLE)的方式,大家可以参考《【正点原子】STM32MP1 M4裸机CubeIDE开发指南》的DMA章节的实验,IDLE就是当串口收到一帧数据后发生的中断,这里的一帧数据是指给单片机一次发1个字节,或者一次发8个字节,这些一次发来的数据,就称为一帧数据,也可以叫做一包数据。和RXNE中断不同的是,当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断。实验中通过HAL_UART_Receive_DMA函数开启接收DMA接收,通过HAL_UART_Transmit_DMA启动DMA发送,通过__HAL_UART_CLEAR_IDLEFLAG函数来清除状态寄存器和串口数据寄存器的数据,并通过读取DMA_SxNDTR寄存器来检查要传输的数据项的大小。

阿莫论坛20周年了!感谢大家的支持与爱护!!

你熬了10碗粥,别人一桶水倒进去,淘走90碗,剩下10碗给你,你看似没亏,其实你那10碗已经没有之前的裹腹了,人家的一桶水换90碗,继续卖。说白了,通货膨胀就是,你的钱是挣来的,他的钱是印来的,掺和在一起,你的钱就贬值了。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-10-2 20:15

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表