精华内容
下载资源
问答
  • 本系列教程将 对应外设原理,HAL库与STM32CubeMX结合在一起讲解,使您可以更快速的学会各个模块的使用 所用工具: 1、芯片: STM32F407ZET6/ STM32F103ZET6 2、STM32CubeMx软件 3、IDE: MDK-Keil软件 4、STM32F1xx/...

    前言:
    本系列教程将 对应外设原理,HAL库与STM32CubeMX结合在一起讲解,使您可以更快速的学会各个模块的使用

    所用工具:

    1、芯片: STM32F407ZET6/ STM32F103ZET6

    2、STM32CubeMx软件

    3、IDE: MDK-Keil软件

    4、STM32F1xx/STM32F4xxHAL库

    知识概括:

    通过本篇博客您将学到:

    DMA工作原理

    STM32CubeMX创建DMA例程

    HAL库定时器DMA函数库

    注意:关于cubemx的DMA配置,在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

    从外设(TIMx[5、6、7、8]、ADC3、SPI/I2S3、UART4、DAC通道1、2和SDIO)产生的5个请求,经逻辑或输入到DMA2控制器,其中每个通道都对应着具体的外设:
    在这里插入图片描述
    在这里插入图片描述

    这些在下方系统框图中也可以清晰地看到

    DMA工作系统框图

    在这里插入图片描述
    上方的框图,我们可以看到STM32内核,存储器,外设及DMA的连接,这些硬件最终通过各种各样的线连接到总线矩阵中,硬件结构之间的数据转移都经过总线矩阵的协调,使各个外设和谐的使用总线来传输数据。
    我们对他来进行一点一点的分析:

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

    没有DMA

    1.如果没有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个操作组成:

    • 从外设数据寄存器或者从当前外设/存储器地址寄存器指示的存储器地址取数据,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元;
    • 存数据到外设数据寄存器或者当前外设/存储器地址寄存器指示的存储器地址,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元;
    • 执行一次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库函数配置过程:

    1、使能DMA时钟:RCC_AHBPeriphClockCmd();

    2、初始化DMA通道:DMA_Init();

    //设置通道;传输地址;传输方向;传输数据的数目;传输数据宽度;传输模式;优先级;是否开启存储器到存储器。

    3、使能外设DMA;

    4、使能DMA通道传输;

    5、查询DMA传输状态。

    关于DMA的介绍我们仅介绍到这里,如果需要更详细的了解DMA原理 DMA寄存器以及库函数 可以参考这篇文章
    《【STM32】 DMA原理,步骤超细详解,一文看懂DMA》




    下面我们将介绍CubeMx 如何创建DMA

    具体流程如下:
    在这里插入图片描述
    我们以USART1 的DMA传输为例

    工程创建

    1设置RCC
    在这里插入图片描述
    设置高速外部时钟HSE 选择外部时钟源

    2设置串口
    在这里插入图片描述

    • 1点击USATR1
    • 2设置MODE为异步通信(Asynchronous)
    • 3基础参数:波特率为115200 Bits/s。传输数据长度为8 Bit。奇偶检验无,停止位1 接收和发送都使能
    • 4GPIO引脚自动设置 USART1_RX/USART_TX
    • 5 NVIC Settings 一栏使能接收中断
      ​​
      在这里插入图片描述
      关于串口部分的讲解可以参考: 【STM32】HAL库 STM32CubeMX教程四—UART串口通信详解

    3 DMA设置

    在这里插入图片描述
    根据DMA通道预览可以知道,我们用的USART1 的TX RX 分别对应DMA1 的通道4和通道5

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

    1DMA基础设置

    右侧点击System Core 点击DMA
    在这里插入图片描述
    DMA RequestDMA传输的对应外设

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

    Channel DMA传输通道设置
    DMA1 : DMA1 Channel 0~DMA1 Channel 7
    DMA2: DMA2 Channel 1~DMA1 Channel 5

    Dirction : DMA传输方向
    四种传输方向:

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

    Priority: 传输速度

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

    2DMA传输模式
    在这里插入图片描述
    Normal:正常模式
    当一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次

    Circular: 循环模式

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

    3DMA指针递增设置
    在这里插入图片描述
    Increment Address:地址指针递增(上方有介绍)。

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

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

    右侧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监视时钟

    32的时钟树框图 如果不懂的话请看《【STM32】系统时钟RCC详解(超详细,超全面)》

    5项目文件设置

    在这里插入图片描述

    • 1 设置项目名称
    • 2 设置存储路径
    • 3 选择所用IDE

    在这里插入图片描述
    5创建工程文件

    然后点击GENERATE CODE 创建工程

    配置下载工具
    新建的工程所有配置都是默认的 我们需要自行选择下载模式,勾选上下载后复位运行

    在这里插入图片描述

    测试例程1

    在main.C中添加:

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

    while循环:

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

    串口助手测试正常:
    在这里插入图片描述

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

    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

    因为这部分函数在讲解USART的时候就已经讲解了,所以我们这里不做过多介绍,如果不同的话请看UART的对应博文,很详细。

    串口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恢复

    测试例程2

    STM32 IDLE 接收空闲中断

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

    本例程功能:

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

    例程代码:

    uart.c

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

    uart.h

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

    main.c

    /*
    *********************************************************************************************************
    * 函 数 名: DMA_Usart_Send
    * 功能说明: 串口发送功能函数
    * 形  参: buf,len
    * 返 回 值: 无
    *********************************************************************************************************
    */
    void DMA_Usart_Send(uint8_t *buf,uint8_t len)//串口发送封装
    {
     if(HAL_UART_Transmit_DMA(&huart1, buf,len)!= HAL_OK) //判断是否发送正常,如果出现异常则进入异常中断函数
      {
       Error_Handler();
      }
    
    }
    
    
    
    /*
    *********************************************************************************************************
    * 函 数 名: DMA_Usart1_Read
    * 功能说明: 串口接收功能函数
    * 形  参: Data,len
    * 返 回 值: 无
    *********************************************************************************************************
    */
    void DMA_Usart1_Read(uint8_t *Data,uint8_t len)//串口接收封装
    {
    	HAL_UART_Receive_DMA(&huart1,Data,len);//重新打开DMA接收
    }
    

    while循环

     while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
    		 if(recv_end_flag == 1)  //接收完成标志
    		{
    			
    			
    			DMA_Usart_Send(rx_buffer, rx_len);
    			rx_len = 0;//清除计数
    			recv_end_flag = 0;//清除接收结束标志位
    //			for(uint8_t i=0;i<rx_len;i++)
    //				{
    //					rx_buffer[i]=0;//清接收缓存
    //				}
    				memset(rx_buffer,0,rx_len);
      }
    		HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);//重新打开DMA接收
    }
    

    stm32f1xx_it.c中

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

    测试正常:
    在这里插入图片描述

    完整例程下载:ZXiaoxuanSTM32-DMA-IDLE

    历程详解

    因为本文章已经一万余字了,足足写了好几天,翻手册也翻了好久,所以我们将本例程的详解分到另一篇文章中去,使整体结构更好一点,也方便大家阅读

    正在码字中

    详解包括:

    • 中断原理讲解
    • 例程流程详解
    • 库函数分析详解
    • 对应寄存器介绍
    • 对应函数介绍

    已经更新,请参看:
    STM32 HAL CubeMX 串口IDLE接收空闲中断+DMA
    在这里插入图片描述

    正在码字中…在这里插入图片描述

    展开全文
  • STM32 HAL DMA串口接收不定长度实现

    千次阅读 2020-01-09 21:30:26
    3.就是启动了,在串口初始化之后默认是没有开启串口DMA接收的。所以增加几行代码如下图所示 4.写串口1中断函数: 这个函数就是实现不等长的精髓所在了。这里用到了类似环形队列的方法,至于什么是环形队列这个不...

    1.使用STM32Cube 直接先配置串口1的基础信息。

    2.然后配置DMA传输

    3.就是启动了,在串口初始化之后默认是没有开启串口DMA接收的。所以增加几行代码如下图所示

    4.写串口1中断函数:

    这个函数就是实现不等长的精髓所在了。这里用到了类似环形队列的方法,至于什么是环形队列这个不知道童鞋自行Google了。

    void USART1_IRQHandler(void)
    {
        if(__HAL_UART_GET_FLAG(&systemUart, UART_FLAG_IDLE) != RESET)
        {
            __HAL_UART_FLUSH_DRREGISTER(&systemUart); //清除 IDLE中断
        }
      HAL_UART_IRQHandler(&systemUart);
    }

    首先将中断函数写出来,如上所示了。这时候帧中断的时候会进这个中断,数据用DMA接收。

    第二步建立对应变量

    #define UART1_DMA_DATA_LEN 100//数据最大长度

    static UART_HandleTypeDef systemUart;
    DMA_HandleTypeDef hdma_usart1_rx;
    static uint8_t _uartDmaDataBuffer[UART1_DMA_DATA_LEN]; //缓冲区就是初始化的时候用到的
    static int dmaDataTail = 0;       //将DMA数据传输Buffer 虚拟成一个队列缓冲区
    static int dmaDataHead = 0;
    static int dmaDataLenght = 0;

    第三步实现类似环形队列功能

    void USART1_IRQHandler(void)
    {
        if(__HAL_UART_GET_FLAG(&systemUart, UART_FLAG_IDLE) != RESET)
        {
            __HAL_UART_FLUSH_DRREGISTER(&systemUart); //清除 IDLE中断
            
            dmaDataTail  = hdma_usart1_rx.Instance->NDTR; 
            dmaDataTail  = UART1_DMA_DATA_LEN - dmaDataTail;
            dmaDataLenght = (dmaDataTail - dmaDataHead+UART1_DMA_DATA_LEN) % UART1_DMA_DATA_LEN; //求数据长度
        }
      HAL_UART_IRQHandler(&systemUart);
    }

    注意里面 hdma_usart1_rx.Instance->NDTR的NDTR不同的型号可能不同可以在DMA的结构体里找到对应的名字,如下图所示

     

    这个NDTR的值是从UART1_DMA_DATA_LEN开始递减的,因为启动DMA的时候设置了传输长度为UART1_DMA_DATA_LEN

    所以我们接收到当前的数据下标就是UART1_DMA_DATA_LEN - hdma_usart1_rx.Instance->NDTR这个。这个其实就是当前接收到数据的结束下标。有了数据结束下标,我们还需要数据起始下标,以及数据长度。

    ①数据起始下标: 等于上一次的dmaDataTail 所以每次处理完数据都有dmaDataHead = dmaDataTail;

    ②数据长度:这个利用环形队列求长度的做法:

    dmaDataLenght = (dmaDataTail - dmaDataHead+UART1_DMA_DATA_LEN) % UART1_DMA_DATA_LEN;

    现在我们有了数据起始下标 数据长度那么我们就可以访问我们的数据了。当然方法也和环形队列差不多

    完成的中断函数就是这样的。可以接收什么返回什么了,要想自己处理可以在这里增加缓冲区或者调用处理函数了。

    void USART1_IRQHandler(void)
    {
        if(__HAL_UART_GET_FLAG(&systemUart, UART_FLAG_IDLE) != RESET)
        {
            __HAL_UART_FLUSH_DRREGISTER(&systemUart); //清除 IDLE中断
            dmaDataTail  = hdma_usart1_rx.Instance->NDTR; 
            dmaDataTail  = UART1_DMA_DATA_LEN - dmaDataTail;
            dmaDataLenght = (dmaDataTail - dmaDataHead+UART1_DMA_DATA_LEN) % UART1_DMA_DATA_LEN; //求数据长度
            for(int i = 0; i < dmaDataLenght; i++)
            {
                int index = (dmaDataHead+i)%UART1_DMA_DATA_LEN;
                printf("%c",_uartDmaDataBuffer[index]);
            }

            printf("\r\n");
            dmaDataHead = dmaDataTail;
        }
      HAL_UART_IRQHandler(&systemUart);
    }

    最后来张测试结果图分别是一顿乱操作发不同的数据长度和数据

    最后注意一点的就是这个程序如果你一下子发超过100个字节那估计就不行了。因为超了缓冲区长度数据被覆盖了,当然增加长度就可以解决了。

     

     

    展开全文
  • 项目是用STM32CubeMX V5.4.0自动生成,MDK 5.27编译,运行HAL_UART_Receive_DMA 只能接收最后一个数据。 自动生成的程序代码后,初始化顺序要调整一下。 /* Initialize all configured peripherals */ ...

    项目中要用到串口的DMA功能,但几天调试都不成功!

    项目是用STM32CubeMX V5.4.0自动生成,MDK 5.27编译,运行 HAL_UART_Receive_DMA 只能接收最后一个数据。

     

    自动生成的程序代码后,初始化顺序要调整一下。

      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_I2C1_Init();
      //MX_USART2_UART_Init();//这条关闭
      MX_DMA_Init();

      /* Initialize interrupts */
      MX_NVIC_Init();
      /* USER CODE BEGIN 2 */
        
      MX_USART2_UART_Init();    //CubeMX自动生成的初始化顺序有问题,现将前面关闭了,最后串口初始化。
        //(原DMA故障现象是DMA中断后只接收到最后一字符)

    故障造成原因:STM32 HAL库的DMA时钟配置跟串口,dma通道配置在两个文件里,结果先进行了串口配置,DMA通道配置,然后才是把DMA时钟打开了。

    展开全文
  • STM32 HALDMA接收数据丢失 一、STM32 DMA串口中断一般配置方法 /** * @brief This function handles USART6 global interrupt. */ void USART6_IRQHandler(void) { /* USER CODE BEGIN USART6_IRQn 0 */ /*...

    STM32 HAL库 DMA接收数据丢失

    一、STM32 DMA串口中断一般配置方法

    
    /**
      * @brief This function handles USART6 global interrupt.
      */
    void USART6_IRQHandler(void)
    {
      /* USER CODE BEGIN USART6_IRQn 0 */
    
      /* USER CODE END USART6_IRQn 0 */
      HAL_UART_IRQHandler(&huart6;
      /* USER CODE BEGIN USART6_IRQn 1 */
        if((__HAL_UART_GET_FLAG(&huart6,UART_FLAG_IDLE) != RESET))//当发生空闲中断时
        {
            __HAL_UART_CLEAR_IDLEFLAG(&huart6);//清除标志位
            HAL_UART_DMAStop(&huart6); //停止接收
            //总计数减去未传输的数据个数,得到已经接收的数据个数;  
            //__HAL_DMA_GET_COUNTER(&hdma_usart6_rx);// 获取DMA中未传输的数据个数
            rx6_len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart6_rx);
            HAL_UART_Receive_DMA(&huart6,rx6_buffer,BUFFER_SIZE);//重新打开DMA接收,BUFFER_SIZE大小为1024字节
        }
      /* USER CODE END USART6_IRQn 1 */
    }
    

    工作原理:
    当发生空闲中断时,就会将数据接收到rx2_buffer中,若再次发生空闲中断时数据会从头覆盖rx2_buffer。

    遇到的问题:
    这种接收一般的数据没有任何问题,但是有一种情况会造成数据丢失:
    当外界传输进来一段数据如:
    [0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08]
    但是这段数据之间时间间隔比较大,变成:
    [0x01,0x02,0x03,0x04,0x05,0x06,20ms,0x07,0x08] //因为某种原因中间多了20ms
    这段数据的头尾有时间间隔,对于STM32 DMA来说发生了两次空闲中断,第二段数据就会覆盖掉第一段数据,变成:
    [0x07,0x08,0x03,0x04,0x05,0x06,0x00,0x00]
    则接收到的数据就丢失了。

    二、解决方法

    void USART6_IRQHandler(void)
    {
      /* USER CODE BEGIN USART6_IRQn 0 */
        static unsigned int add_len = 0; //累加数据
      /* USER CODE END USART6_IRQn 0 */
      HAL_UART_IRQHandler(&huart6);
      /* USER CODE BEGIN USART6_IRQn 1 */
        
        if((__HAL_UART_GET_FLAG(&huart6,UART_FLAG_IDLE) != RESET))//idle标志被置位
        {
            recv6_end_flag = 1; // 接受完成标志位置1
            __HAL_UART_CLEAR_IDLEFLAG(&huart6);//清除标志位
    
            HAL_UART_DMAStop(&huart6); 
            
            add_len = rx6_len;
            
           //总计数减去未传输的数据个数,得到已经接收的数据个数;  
           //__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 获取DMA中未传输的数据个数     
            rx6_len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart6_rx); 
            if((add_len + rx6_len) >= BUFFER_SIZE)
            {
                add_len = 0;
            }
            memcpy(&temp_buf[add_len],rx6_buffer,rx6_len);
    
            add_len += rx6_len;
            
            rx6_len = add_len;
            
            HAL_UART_Receive_DMA(&huart6,rx6_buffer,BUFFER_SIZE);//重新打开DMA接收
        }
    
    
      /* USER CODE END USART6_IRQn 1 */
    }
    

    解析:
    将数据不断的累加进temp_buf数组里,使用的时候我们HAL_Delay()一段时间去取temp_buf中的数据,这样就会将所有的数据接收进来等待处理。

    前后的对比:
    1、前一段处理方式:可以正常接收,但是当一段数据中的数据片段之间时间间隔比较大就会多次触发空闲中断,导致数据覆盖而丢失。
    2、后一段处理方式:将数据叠加在一起,根据实际情况延时取数据,然后再进行处理,防止数据丢失。
    3、文中有许多细节未处理,不要太过纠结,只提供思路,其实还有其他的方式如回调函数等等。。。还有就是串口中断中不宜添加大量的代码如printf()函数等,否则也会丢数据。

    展开全文
  • STM32F4与STM32L4,SPI DMA HAL 关闭片选 时机探讨 我使用STM32F407,标准库 + SPI + DMA 通信,发送接收数据。 当我们配置好SPI,DMA发送模式后,首先开启 SPI-NSS(片选端子),然后使能SPI ,使能DMA,就可以进行...
  • HAL库——UART的DMA接收中的一些问题

    千次阅读 多人点赞 2019-10-23 15:39:46
    上篇简单的说明了如何通过DMA的方式接收UART数据,看着这个UART的DMA...启动UART的DMA接收(这里面还定义了DMA回调函数):HAL_UART_Receive_DMA 接收完成后,请求DMA中断(判断中断的类型):HAL_DMA_IRQHandler ...
  • stm32F4XX之UART&DMA HAL

    千次阅读 2018-11-11 17:15:30
    1:串口相关的初始化操作 我们都知道stm32相关外设的...HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart);这个函数就是对串口的初始化操作,形参是一个结构体指针,该结构体主要包含哪些部分,下面会...
  • 这两天好好整理了一下STM32的串口通信,主要测试DMA方式发送与接收,以及配合串口空闲中断接收不定长数据。前后在F103和F767上都测试通过了。不过依然有一些问题想不明白,算了不甩它,暂且先能实现功能就好。 本文...
  • STM32 HAL CubeMX 串口IDLE接收空闲中断+DMA

    万次阅读 多人点赞 2020-03-22 11:01:56
    【STM32】HAL库 STM32CubeMX教程十一—DMA (串口DMA发送接收) 本篇文章我们仅针对例程进行详解剖析 本篇文章提供两种方法: 一种是 :IDLE 接收空闲中断+DMA 一种是: IDLE 接收空闲中断+RXNE接收数据中断 都可完成...
  • HAL库——UART的DMA发送(对比接收

    千次阅读 2019-10-23 20:29:05
    启动UART的DMA接收(这里面还定义了DMA回调函数):HAL_UART_Receive_DMA 接收完成后,请求DMA中断(判断中断的类型):HAL_DMA_IRQHandler 调用DMA接收完成回调函数(同时关闭了DMA接收):UART_DMAReceiveCplt ...
  • STM32 HAL库 串口DMA接收不定长数据 整体思路:我是用的CUBEMX软件生成的工程,使能了两个串口,串口2用来接收不定长的数据,串口1用来发送串口2接收到的数据;串口2我找了一个UBLOX卫星模块,每秒输出不定长的定位...
  • DMA串口这个东西,前几年都是寄存器混合HAL库方可食用,因为那时候底层不太完善,我用中断它不香吗?(资源不紧张的情况下中断速度稍微快一点点) 但是该学的还是要学学,说不定哪个项目能用上呢~(到底哪个项目资源...
  • STM32 HAL库+串口DMA+空闲中断(IDLE)实现不定长数据接收,可以用来参考学习使用,简单易懂。
  • 串口DMA接收+消息队列
  • http://www.stmcu.org.cn/module/forum/thread-614657-1-1.html uart3 dma 源码下载 https://download.csdn.net/download/qq_20395637/12478089
  • HAL库——UART的DMA接收

    千次阅读 2019-10-22 16:55:20
    启动UART的DMA接收(这里面还定义了DMA回调函数):HAL_UART_Receive_DMA 接收完成后,请求DMA中断(判断中断的类型):HAL_DMA_IRQHandler 调用DMA接收完成回调函数(同时关闭了DMA接收):UART_...
  • STM32 HAL库IDLE检查DMA接收完成程序中ORE问题HAL库IDLE检查DMA接收完成程序流程说明ORE产生原因ORE消除办法(未验证) HAL库IDLE检查DMA接收完成程序流程说明 1.配置串口,配置DMA接收通道(我只用DMA接收),...
  • 在将STM32F103的代码移植到HK32F103时发生问题。原来STM32使用DMA接收串口数据,为了实现不定长接收,开启了串口...在看HAL库的串口DMA相关函数时,发现一个新的库函数, HAL_UARTEx_ReceiveToIdle_DMA() /** * @

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,851
精华内容 1,140
关键字:

dmahal接收