当前位置:   article > 正文

STM32 (十一)DMA_stm32 dma

stm32 dma

一、简介

DMA的基本介绍
什么是DMA (DMA的基本定义)

DMA,全称Direct Memory Access,即直接存储器访问。

DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。

我们知道CPU有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是CPU,

CPU无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的复制和存储数据,如果我们把这部分的CPU资源拿出来,让CPU去处理其他的复杂计算事务,是不是能够更好的利用CPU的资源呢?

因此:转移数据(尤其是转移大量数据)是可以不需要CPU参与。比如希望外设A的数据拷贝到外设B,只要给两种外设提供一条数据通路,直接让数据由A拷贝到B 不经过CPU的处理,
 

         DMA就是基于以上设想设计的,它的作用就是解决大量数据转移过度消耗CPU资源的问题。有了DMA使CPU更专注于更加实用的操作–计算、控制等。

DMA定义:

        DMA用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU的干预,通过DMA数据可以快速地移动。这就节省了CPU的资源来做其他操作。

DMA传输方式:

        DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:

  •     外设到内存
  •     内存到外设
  •     内存到内存
  •     外设到外设
     

DMA传输参数

我们知道,数据传输,首先需要知道的是1 数据的源地址 2 数据传输位置的目标地址 ,3 传递数据多少的数据传输量 ,4 进行多少次传输的传输模式, DMA所需要的核心参数,便是这四个。

        当用户将参数设置好,主要涉及源地址、目标地址、传输数据量这三个,DMA控制器就会启动数据传输,当剩余传输数据量为0时 达到传输终点,结束DMA传输 ,当然,DMA 还有循环传输模式 当到达传输终点时会重新启动DMA传输。
  
也就是说只要剩余传输数据量不是0,而且DMA是启动状态,那么就会发生数据传输。 

DMA的主要特征

每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置;

  • 在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推);
  • 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐;
  • 支持循环的缓冲器管理;
  • 每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求;
  • 存储器和存储器间的传输、外设和存储器、存储器和外设之间的传输;
  • 闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标;
  • 可编程的数据传输数目:最大为65535。
     

 STM32有多少个DMA资源?

 对于大容量的STM32芯片有2个DMA控制器 两个DMA控制器,DMA1有7个通道,DMA2有5个通道。
每个通道都可以配置一些外设的地址。

①DMA1 controller

从外设(TIMx[x=1、2、3、4]、ADC1、SPI1、SPI/I2S2、I2Cx[x=1、2]和USARTx[x=1、2、3])产生的7个DMA请求,通过逻辑或输入到DMA1控制器 其中每个通道都对应着具体的外设:
在这里插入图片描述

 ② DMA2 controller

 

 

DMA工作系统框图

         上方的框图,我们可以看到STM32内核,存储器,外设及DMA的连接,这些硬件最终通过各种各样的线连接到总线矩阵中,硬件结构之间的数据转移都经过总线矩阵的协调,使各个外设和谐的使用总线来传输数据。

原理分析

我们对他来进行分析:以ADC采集的数据进行举例

下面看有与没有DMA的情况下,ADC采集的数据是怎样存放到SRAM中的?

没有DMA

        如果没有DMA,CPU传输数据还要以内核作为中转站,比如要将ADC采集的数据转移到到SRAM中,这个过程是这样的:

内核通过DCode经过总线矩阵协调,从获取AHB存储的外设ADC采集的数据,

然后内核再通过DCode经过总线矩阵协调把数据存放到内存SRAM中。

 有DMA传输

有DMA传输的过程如下:

  1. DMA传输时外设对DMA控制器发出请求。
  2. DMA控制器收到请求,触发DMA工作。
  3. DMA控制器从AHB外设获取ADC采集的数据,存储到DMA通道中
  4. DMA控制器的DMA总线与总线矩阵协调,使用AHB把外设ADC采集的数据经由DMA通道存放到SRAM中,这个数据的传输过程中,完全不需要内核的参与,也就是不需要CPU的参与
     

具体如下:

         在发生一个事件后,外设向DMA控制器发送一个请求信号。DMA控制器根据通道的优先权处理请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即发送给它一个应答信号。当从DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA控制器同时撤销应答信号。DMA传输结束,如果有更多的请求时,外设可以启动下一个周期。
 

总之,每次DMA传送由3个操作组成:

  1. 从外设数据寄存器或者从当前外设/存储器地址寄存器指示的存储器地址取数据,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元;
  2. 存数据到外设数据寄存器或者当前外设/存储器地址寄存器指示的存储器地址,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元;
  3. 执行一次DMA_CNDTRx寄存器的递减操作,该寄存器包含未完成的操作数目。

 

DMA传输方式

方法1:DMA_Mode_Normal,正常模式。

当一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次

方法2:DMA_Mode_Circular ,循环传输模式

当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式。

仲裁器的介绍

 仲裁器的作用是确定各个DMA传输的优先级

仲裁器根据通道请求的优先级来启动外设/存储器的访问。

优先权管理分2个阶段:

软件:每个通道的优先权可以在DMA_CCRx寄存器中设置,有4个等级:

  •     最高优先级
  •     高优先级
  •     中等优先级
  •     低优先级;

硬件:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优先权。

比如:如果软件优先级相同,通道2优先于通道4。

注意: 在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级。

指针递增模式

        根据 DMA_SxCR 寄存器中 PINC 和 MINC 位的状态,外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值。

        通过单个寄存器访问外设源或目标数据时,禁止递增模式十分有用。

如果使能了递增模式,则根据在 DMA_SxCR 寄存器 PSIZE 或 MSIZE 位中编程的数据宽度,下一次传输的地址将是前一次传输的地址递增 1个数据宽度、2个数据宽度或 4个数据宽度。


存储器到存储器模式

        DMA通道的操作可以在没有外设请求的情况下进行,这种操作就是存储器到存储器模式。

        当设置了DMA_CCRx寄存器中的MEM2MEM位之后,在软件设置了DMA_CCRx寄存器中的EN位启动DMA通道时,DMA传输将马上开始。当DMA_CNDTRx寄存器变为0时,DMA传输结束。存储器到存储器模式不能与循环模式同时使用。

        这里要注意仅 DMA2 的外设接口可以访问存储器,所以仅 DMA2 控制器支持存储器到存储器的传输,DMA1 不支持。存储器到存储器模式不能与循环模式同时使用。
 

DMA中断

        每个DMA通道都可以在DMA传输过半、传输完成和传输错误时产生中断。为应用的灵活性考虑,通过设置寄存器的不同位来打开这些中断。


        使能开启,我们也可以通过查询这些位来获得当前 DMA 传输的状态。这里我们常用的是 TCIFx位,即数据流 x 的 DMA 传输完成与否标志。

 

二、DMA寄存器和库函数讲解

DMA配置部分

此部分我们分为DMA寄存器和DMA库函数分别介绍:

1.DMA寄存器

DMA配置参数包括:通道地址、优先级、数据传输方向、存储器/外设数据宽度、存储器/外设地址是否增量、循环模式、数据传输量。

DMA中断状态寄存器(DMA_ISR)

        我们如果开启了 DMA_ISR 中这些中断,在达到条件后就会跳到中断服务函数里面去,即使 没开启,我们也可以通过查询这些位来获得当前 DMA 传输的状态。这里我们常用的是 TCIFx, 即通道 DMA 传输完成与否的标志。

        注意此寄存器为只读寄存器,所以在这些位被置位之后,只 能通过其他的操作来清除。


DMA中断标志清除寄存器(DMA_IFCR)

         DMA_IFCR 的各位就是用来清除 DMA_ISR 的对应位的,通过写 0 清除。在 DMA_ISR 被置位后, 我们必须通过向该位寄存器对应的位写入 0 来清除。

DMA通道x配置寄存器(DMA_CCRx)

        该寄存器控制着 DMA 的很多相关 信息,包括数据宽度、外设及存储器的宽度、通道优先级、增量模式、传输方向、中断允许、 使能等都是通过该寄存器来设置的。所以 DMA_CCRx 是 DMA 传输的核心控制寄存器

DMA通道x传输数量寄存器(DMA_CNDTRx)(x = 1…7)

         这个寄存器控制 DMA 通道 x 的每次 传输所要传输的数据量。其设置范围为 0~65535。并且该寄存器的值会随着传输的进行而减少, 当该寄存器的值为 0 的时候就代表此次数据传输已经全部发送完成了。所以可以通过这个寄存 器的值来知道当前 DMA 传输的进度

DMA通道x外设地址寄存器(DMA_CPARx)(x = 1…7)

在这里插入图片描述

         该寄存器用来存储 STM32 外设的地 址,比如我们使用串口 1,那么该寄存器必须写入 0x40013804(其实就是&USART1_DR)。如果使 用其他外设,就修改成相应外设的地址就行了。

DMA通道x配置寄存器(DMA_CMARx)

 该寄存器和 DMA_CPARx 差不多, 但是是用来放存储器的地址的。比如我们使用 SendBuf[5200]数组来做存储器,那么我们在 DMA_CMARx 中写入&SendBuff 就可以了。

DMA寄存器配置流程

通道配置过程 下面是配置DMA通道x的过程(x代表通道号):

  1.     在DMA_CPARx寄存器中设置外设寄存器的地址。发生外设数据传输请求时,这个地址将 是数据传输的源或目标。
  2.     在DMA_CMARx寄存器中设置数据存储器的地址。发生外设数据传输请求时,传输的数 据将从这个地址读出或写入这个地址。
  3.     在DMA_CNDTRx寄存器中设置要传输的数据量。在每个数据传输后,这个数值递减。
  4.     在DMA_CCRx寄存器的PL[1:0]位中设置通道的优先级。
  5.     在DMA_CCRx寄存器中设置数据传输的方向、循环模式、外设和存储器的增量模式、外 设和存储器的数据宽度、传输一半产生中断或传输完成产生中断。
  6.     设置DMA_CCRx寄存器的ENABLE位,启动该通道。

        一旦启动了DMA通道,它既可响应连到该通道上的外设的DMA请求。 当传输一半的数据后,半传输标志(HTIF)被置1,当设置了允许半传输中断位(HTIE)时,将产生 一个中断请求。在数据传输结束后,传输完成标志(TCIF)被置1,当设置了允许传输完成中断位 (TCIE)时,将产生一个中断请求。

2.DMA库函数

1.DMA初始化为默认函数

 DMA_DeInit(DMAX_ChannelX);

功能:将DMAyChannelx寄存器的初始化为其默认值

注释:RCC_ResetCmd中对DMA无定义,因此采用的直接操纵DMA寄存器的方式。

2.DMA初始化函数

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,  DMA_InitTypeDef* DMA_InitStruct) 

功能:设置要开启的通道,还有一些结构体参数,包括外设基地址,存储器基地址,传输的数据量,增量模式,数据宽度等。

结构体内容参数如下:

  1. typedef struct {
  2. uint32_t DMA_PeripheralBaseAddr;
  3. /*设置DMA源地址*/
  4. uint32_t DMA_MemoryBaseAddr;
  5. /*设置DMA目的地址*/
  6. uint32_t DMA_DIR;
  7. /* 设置数据传输方向,决定是从外设读取数据到内存还送从内存读取数 据发送到外设,也就是外设是源地还是目的地
  8. */
  9. uint32_t DMA_BufferSize;
  10. /*设置传输大小*/
  11. uint32_t DMA_PeripheralInc;
  12. /*设置ReceiveBuff地址是否自增*/
  13. uint32_t DMA_MemoryInc;
  14. /*设置传输数据时候内存地址是否递,需要开启*/
  15. uint32_t DMA_PeripheralDataSize;
  16. /*外设的数据长度是为字节传输(8bits),半 字传输(16bits) 还是字传输(32bits) */
  17. uint32_t DMA_MemoryDataSize;
  18. /*设置内存的数据长度*/
  19. uint32_t DMA_Mode;
  20. /*设置DMA的模式,正常模式/循环模式 是否循环发送*/
  21. uint32_t DMA_Priority;
  22. /*设置 DMA 通道的优先级,有低,中,高,超高四种模式*/
  23. uint32_t DMA_M2M;
  24. /*设置是否是存储器到存储器模式传输,*/ }
  25. DMA_InitTypeDef;

3.DMA使能函数

void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState  NewState);

功能:使能或者失能DMA外设

例如:DMA_Cmd(DMA1_Channel1 , ENABLE);


3.DMA中断使能函数

void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT,  FunctionalState NewState);

功能:配置指定的DMAy通道x的中断

注释:DMA_IT_TC:传输完成 DMA_IT_HT:传输一半 DMA_IT_TE:传输错误

例如:DMA_ITConfig(DMA1_Channel1 , DMA_IT_TC , ENABLE);

4.设置CNDTRx和读CNDTRx函数 (通道传输数据量)

  1. void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
  2. uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

作用:前者设置DMA通道的传输数据量(DMA处于关闭状态);后者获取当前DMA通道传输剩余数据量(DMA处于开启状态)。

DMA库函数配置过程:

  1.     使能DMA时钟:RCC_AHBPeriphClockCmd();
  2.     初始化DMA通道:DMA_Init();
  3.         //设置通道;传输地址;传输方向;传输数据的数目;传输数据宽度;传输模式;优先级;是否开启存储器到存储器。
  4.     使能外设DMA;
  5.         以串口为例:使能串口DMA发送,串口DMA使能函数。调用函数:USART_DMACmd();
  6.     使能DMA通道传输;函数:DMA_Cmd();
  7.     查询DMA传输状态。函数:DMA_GetFlagStatus();
  8.     获取当前剩余数据量大小 函数: DMA_GetCurrDataCounter(DMA1_Channel4);
     

UART DMA传输

DMA就是一个搬运工,可以将数据从一个位置搬运到另一个位置。
以UART为例,如果要接收数据,会触发UART中断,然后CPU介入,在中断中通过CPU将UART输入寄存器的值读出来,存放到内存中;
而DMA方式,产生UART中断后,DMA直接参与,把UART输入寄存器的值搬运到内存中,CPU只需要在去检查内存的值就好了,这样提高了CPU的效率。
 

DMA代码配置

① DMA初始化配置

  1. void dma_init()
  2. {
  3. DMA_InitTypeDef DMA_InitStructure;
  4. RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
  5. /*DMA配置*/
  6. DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;//串口数据寄存器地址
  7. DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SendBuff; //内存地址(要传输的变量的指针)
  8. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //方向(从内存到外设)
  9. DMA_InitStructure.DMA_BufferSize = 500; //传输内容的大小
  10. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址不增
  11. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址自增
  12. DMA_InitStructure.DMA_PeripheralDataSize =
  13. DMA_PeripheralDataSize_Byte ; //外设数据单位
  14. DMA_InitStructure.DMA_MemoryDataSize =
  15. DMA_MemoryDataSize_Byte ; //内存数据单位
  16. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ; //DMA模式:一次传输,循环
  17. DMA_InitStructure.DMA_Priority = DMA_Priority_Medium ; //优先级:高
  18. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //禁止内存到内存的传输
  19. DMA_Init(DMA1_Channel4, &DMA_InitStructure); //配置DMA14通道
  20. DMA_Cmd(DMA1_Channel4,ENABLE);
  21. DMA_SetCurrDataCounter(DMA_CH4,DMA1_MEM_LEN);//DMA通道的DMA缓存的大小
  22. DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE);//配置DMA发送完成后产生中断
  23. }
  1. void DMA1_Channel4_IRQHandler(void)
  2. {
  3. if(DMA_GetFlagStatus(DMA1_FLAG_TC4)==SET)
  4. {
  5. DMA_ClearFlag(DMA1_FLAG_TC4);
  6. }
  7. }

main函数

  1. #define SEND_BUF_SIZE 500 //发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍.
  2. u8 SendBuff[SEND_BUF_SIZE]; //发送数据缓冲区
  3. const u8 TEXT_TO_SEND[]={"STM32F1 DMA 串口实验"};
  4. uint16_t i;
  5. int main(void)
  6. {
  7. uart_init(115200); //串口初始化为115200
  8. for(i=0;i<500;i++)
  9. {
  10. SendBuff[i] =0xaf;
  11. }
  12. USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口dma传输
  13. while(1);
  14. =
  15. }

三、CubeMx工程创建

具体流程如下:

 我们以USART1 的DMA传输为例

设置高速外部时钟HSE 选择外部时钟源

2设置串口

  •  1点击USATR1
  • 2设置MODE为异步通信(Asynchronous)
  • 3基础参数:波特率为115200 Bits/s。传输数据长度为8 Bit。奇偶检验无,停止位1 接收和发送都使能
  • 4GPIO引脚自动设置 USART1_RX/USART_TX
  • 5 NVIC Settings 一栏使能接收中断
     

 

3 DMA设置

在这里插入图片描述

根据DMA通道预览可以知道,我们用的USART1 的TX RX 分别对应DMA1 的通道4和通道5

  •     点击DMASettings 点击 Add 添加通道
  •     选择USART_RX USART_TX 传输速率设置为中速
  •     DMA传输模式为正常模式
  •     DMA内存地址自增,每次增加一个Byte(字节)
     

一、DMA基础设置

 1.DMA RequestDMA传输的对应外设

注意: 如果你是在DMA设置界面添加DMA 而没有开启对应外设的话 ,默认为MENTOMEN

2.Channel DMA传输通道设置

DMA1 : DMA1 Channel 0~DMA1 Channel 7
DMA2: DMA2 Channel 1~DMA1 Channel 5

3.Dirction : DMA传输方向

四种传输方向:

  • 外设到内存 Peripheral To Memory
  • 内存到外设 Memory To Peripheral
  • 内存到内存 Memory To Memory
  • 外设到外设 Peripheral To Peripheral

4.Priority: 传输速度

  • 最高优先级 Very Hight
  • 高优先级 Hight
  • 中等优先级 Medium
  • 低优先级;Low

二、DMA传输模式

1.Normal:正常模式
当一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次

2.Circular: 循环模式

传输完成后又重新开始继续传输,不断循环永不停止

 

三、DMA指针递增设置

 Increment Address:地址指针递增。

1.左侧Src Memory 表示外设地址寄存器

功能:设置传输数据的时候外设地址是不变还是递增。如果设置 为递增,那么下一次传输的时候地址加 Data Width个字节,

2.右侧Dst Memory 表示内存地址寄存器

功能:设置传输数据时候内存地址是否递增。如果设置 为递增,那么下一次传输的时候地址加 Data Width个字节,

这个Src Memory一样,只不过针对的是内存。

        

        串口发送数据是将数据不断存进固定外设地址串口的发送数据寄存器(USARTx_TDR)。所以外设的地址是不递增。

而内存储器存储的是要发送的数据,所以地址指针要递增,保证数据依次被发出。

 串口数据发送寄存器只能存储8bit,每次发送一个字节,所以数据长度选择Byte。

 

        要注意DMA的传输方向别弄错了,到底是PERIPHERIAL到MEMORY还是MEMORY到PERIPHERIAL或者说是Memory到Memory要配置正确。尤其是在用CubeMx配置时,这里有个默认配置是PERIPHERIAL到MEMORY。如果说你的真实意图根本不是从PERIPHERIAL到MEMORY,而你无意中使用了这个默认配置,结果可想而知,DMA传输根本没法正常运行。

4时钟源设置

我的是 外部晶振为8MHz

  • 1选择外部时钟HSE 8MHz
  • 2PLL锁相环倍频9倍
  • 3系统时钟来源选择为PLL
  • 4设置APB1分频器为 /2
  • 5 使能CSS监视时钟

项目文件设置

在这里插入图片描述

 

创建工程文件

然后点击GENERATE CODE 创建工程

四、代码讲解

HAL库UARTDMA函数库介绍

1、串口发送/接收函数

    HAL_UART_Transmit();串口发送数据,使用超时管理机制
    HAL_UART_Receive();串口接收数据,使用超时管理机制
    HAL_UART_Transmit_IT();串口中断模式发送
    HAL_UART_Receive_IT();串口中断模式接收
    HAL_UART_Transmit_DMA();串口DMA模式发送
    HAL_UART_Transmit_DMA();串口DMA模式接收
    HAL_UART_DMAPause() 暂停串口DMA
    HAL_UART_DMAResume(); 恢复串口DMA
    HAL_UART_DMAStop(); 结束串口DMA
 

1.串口DMA发送数据函数

 HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

功能:串口通过DMA发送指定长度的数据。

参数:

  • UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1
  • *pData 需要发送的数据
  • Size 发送的字节数

举例:

HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Senbuff, sizeof(Senbuff));  //串口发送Senbuff数组

串口DMA接收数据

HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

功能:串口通过DMA接受指定长度的数据。

参数:

  • UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1
  • *pData 需要存放接收数据的数组
  • Size 接受的字节数

举例:

HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Recbuff, sizeof(Recbuff));  //串口发送Senbuff数组

串口DMA恢复函数

HAL_UART_DMAResume(&huart1)

作用: 恢复DMA的传输

返回值: 0 正在恢复 1 完成DMA恢复。

测试例程1:

在main.C中添加:

  1. /* USER CODE BEGIN Init */
  2. uint8_t Senbuff[] = "\r\n**** Serial Output Message by DMA ***\r\n UART DMA Test \r\n "; //定义数据发送数组
  3. /* USER CODE END Init */

while循环:

  1. while (1)
  2. {
  3. /* USER CODE END WHILE */
  4. HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Senbuff, sizeof(Senbuff));
  5. HAL_Delay(1000);
  6. /* USER CODE BEGIN 3 */
  7. }

注意:如果不开启串口中断,则程序只能发送一次数据,程序不能判断DMA传输是否完成,USART一直处于busy状态。

测试例程2

本例程功能:

使用DMA+串口接受空闲中断 实现将接收的数据完整发送到上位机的功能

STM32 IDLE 接收空闲中断

        空闲的定义是总线上在一个字节的时间内没有再接收到数据,USART_IT_IDLE空闲中断是检测到有数据被接收后,总线上在一个字节的时间内没有再接收到数据的时候发生的。

这里我们使用串口空闲中断(IDLE)和 DMA接收不定长数据。

        IDLE的中断产生条件:在串口无数据接收的情况下,不会产生,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一但接收的数据断流,没有接收到数据,即产生IDLE中断

例程代码:

uart.c

  1. volatile uint8_t rx_len = 0; //接收一帧数据的长度
  2. volatile uint8_t recv_end_flag = 0; //一帧数据接收完成标志
  3. uint8_t rx_buffer[100]={0}; //接收数据缓存数组
  1. void MX_USART1_UART_Init(void)
  2. {
  3. huart1.Instance = USART1;
  4. huart1.Init.BaudRate = 115200;
  5. huart1.Init.WordLength = UART_WORDLENGTH_8B;
  6. huart1.Init.StopBits = UART_STOPBITS_1;
  7. huart1.Init.Parity = UART_PARITY_NONE;
  8. huart1.Init.Mode = UART_MODE_TX_RX;
  9. huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  10. huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  11. if (HAL_UART_Init(&huart1) != HAL_OK)
  12. {
  13. Error_Handler();
  14. }
  15. //下方为自己添加的代码
  16. __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中断
  17. //DMA接收函数,此句一定要加,不加接收不到第一次传进来的实数据,是空的,且此时接收到的数据长度为缓存器的数据长度
  18. HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);
  19. }

uart.h

  1. extern UART_HandleTypeDef huart1;
  2. extern DMA_HandleTypeDef hdma_usart1_rx;
  3. extern DMA_HandleTypeDef hdma_usart1_tx;
  4. /* USER CODE BEGIN Private defines */
  5. #define BUFFER_SIZE 100
  6. extern volatile uint8_t rx_len ; //接收一帧数据的长度
  7. extern volatile uint8_t recv_end_flag; //一帧数据接收完成标志
  8. extern uint8_t rx_buffer[100]; //接收数据缓存数组

main.c

  1. /*
  2. *********************************************************************************************************
  3. * 函 数 名: DMA_Usart_Send
  4. * 功能说明: 串口发送功能函数
  5. * 形 参: buf,len
  6. * 返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void DMA_Usart_Send(uint8_t *buf,uint8_t len)//串口发送封装
  10. {
  11. if(HAL_UART_Transmit_DMA(&huart1, buf,len)!= HAL_OK) //判断是否发送正常,如果出现异常则进入异常中断函数
  12. {
  13. Error_Handler();
  14. }
  15. }
  16. /*
  17. *********************************************************************************************************
  18. * 函 数 名: DMA_Usart1_Read
  19. * 功能说明: 串口接收功能函数
  20. * 形 参: Data,len
  21. * 返 回 值: 无
  22. *********************************************************************************************************
  23. */
  24. void DMA_Usart1_Read(uint8_t *Data,uint8_t len)//串口接收封装
  25. {
  26. HAL_UART_Receive_DMA(&huart1,Data,len);//重新打开DMA接收
  27. }

while循环

  1. while (1)
  2. {
  3. /* USER CODE END WHILE */
  4. /* USER CODE BEGIN 3 */
  5. if(recv_end_flag == 1) //接收完成标志
  6. {
  7. DMA_Usart_Send(rx_buffer, rx_len);
  8. rx_len = 0;//清除计数
  9. recv_end_flag = 0;//清除接收结束标志位
  10. // for(uint8_t i=0;i<rx_len;i++)
  11. // {
  12. // rx_buffer[i]=0;//清接收缓存
  13. // }
  14. memset(rx_buffer,0,rx_len);
  15. }
  16. HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);//重新打开DMA接收
  17. }

stm32f1xx_it.c中

  1. #include "usart.h"
  2. void USART1_IRQHandler(void)
  3. {
  4. uint32_t tmp_flag = 0;
  5. uint32_t temp;
  6. tmp_flag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //获取IDLE标志位
  7. if((tmp_flag != RESET))//idle标志被置位
  8. {
  9. __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
  10. //temp = huart1.Instance->SR; //清除状态寄存器SR,读取SR寄存器可以实现清除SR寄存器的功能
  11. //temp = huart1.Instance->DR; //读取数据寄存器中的数据
  12. //这两句和上面那句等效
  13. HAL_UART_DMAStop(&huart1); //
  14. temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 获取DMA中未传输的数据个数
  15. //temp = hdma_usart1_rx.Instance->NDTR;//读取NDTR寄存器 获取DMA中未传输的数据个数,
  16. //这句和上面那句等效
  17. rx_len = BUFFER_SIZE - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数
  18. recv_end_flag = 1; // 接受完成标志位置1
  19. }
  20. HAL_UART_IRQHandler(&huart1);
  21. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/584451
推荐阅读
相关标签
  

闽ICP备14008679号