STM32F103C8T6 HAL库 USART1 DMA方式接收数据

2024-06-10 11:12

本文主要是介绍STM32F103C8T6 HAL库 USART1 DMA方式接收数据,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:        

        前面的两篇文章都说关于发送的,HAL库发送数据可以调用现成的函数,而接收数据,现成函数不太好用。这里为了记录了一下自己参考了网上几个大佬的代码,整理了一下USART1 DMA方式接受数据的代码,这里亲测了一下,传输比较稳定,也没有出现发送数据过快导致串口反应不过来的情况。

正文开始:

        Cubemx配置

        这里跟上一篇博客一样,我就不再赘述了

        额外注意的一点是记得勾选上Use MicroLIB

        代码编写:

        这里我是学习了大佬的博客,传送门

        我稍微做了一下改进,这里还是沿用上一篇文章创建的两个文件 USART_DMA.c和USART.h
按照大佬博客里教的。

        ①  定义一个结构体变量:存放接收的字节数、数据数组。        

        ②  开启DMA:让硬件自动接收数据放到缓存        

        ③  重写回调函数:当一帧数据接收好了,把缓存的数据,转存到全局结构体变量里,备用。        

        ④  在需要使用串口接收的地方,如在while中,判断接收字节数>0,  即为接收到新一帧数据了

         在USART_DMA.h中,我们声明一下我们的结构体

typedef struct						//声明一个结构体,方便管理变量
{						uint16_t		ReceiveNum;		//接受字节数;在中断回调函数中被自动赋值;只要字节数>0,即为接受到新一帧uint8_t		ReceiveData[512];	//接受到的数据uint8_t		BuffTemp[512];		//接受缓存;注意,这个数组只是一个缓存//临时缓存,在DMA空闲中断中将把一帧数据复制到ReceivedData[] 
}myUATR_TypeDef;

        然后在USART_DMA.c中定义一个自己的变量,如果要在其他.c文件引用的,extern一下就行

myUATR_TypeDef myUSART1 = {0};				//用来定义自己的变量

        我采用的方法是,直接将extern myUATR_TypeDef myUSART1;放在USART_DMA.h里面。 

        开启DMA,让硬件自动接收数据,.

        我们整个接收过程,仅使用到1个HAL库函数。只需在main()函数的初始化部分,调用HAL库函数:HAL_UARTEx_ReceiveToIdle_DMA (串口、缓存、字节数) ;        

        参数:串口、接收缓存区、最大接收字节数          

        作用:使能DMA、使能串口的空闲中断,正式进入接收状态。 

        我们需要在main.c中添加代码

HAL_UARTEx_ReceiveToIdle_DMA(&huart1, myUSART1.BuffTemp, sizeof(myUSART1.BuffTemp));  // 开启DMA空闲中断  

         调用函数后,硬件就会立刻进入自动接收状态:从RX引脚接收到的数据,会逐个字节顺序存放到指定缓存中,这里我们指定的缓存是:myUSART1.BuffTemp

        因为函数内部,开启了DMA中断、空闲中断,所以达成下列两个条件之一,就会触发中断:

        ①  DMA接收的字节数,达到了参数中的最大值      

        ②  串口发生空闲中断,即RX引脚,超过1字节的时间,没有新信号。

        当上述中断产生时,硬件自动调用其相关的中断服务函数,再继而调用回调函数。

         重写DMA空闲中断回调函数,(DMA完成中断、空闲中断,所调用的回调函数):         HAL_UARTEx_RxEventCallback(串口,接收到的字节数);      

        弱函数定义在stm32xx_hal_gpio.c文件的底部。

/******************************************************************************* 函  数: HAL_UARTEx_RxEventCallback* 功  能: DMA+空闲中断回调函数* 参  数: UART_HandleTypeDef  *huart   // 触发的串口*          uint16_t             Size    // 接收字节* 返回值: 无* 备  注: 1:这个是回调函数,不是中断服务函数。技巧:使用CubeMX生成的工程中,中断服务函数已被CubeMX安排妥当,我们只管重写回调函数*          2:触发条件:当DMA接收到指定字节数时,或产生空闲中断时,硬件就会自动调用本回调函数,无需进行人工调用;*          2:必须使用这个函数名称,因为它在CubeMX生成时,已被写好了各种函数调用、函数弱定义(在stm32xx_hal_uart.c的底部); 不要在原弱定义中增添代码,而是重写本函数*          3:无需进行中断标志的清理,它在被调用前,已有清中断的操作;*          4:生成的所有DMA+空闲中断服务函数,都会统一调用这个函数,以引脚编号作参数*          5:判断参数传进来的引脚编号,即可知道是哪个串口接收收了多少字节
******************************************************************************/
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{if (huart == &huart1)                                                                    // 判断串口{__HAL_UNLOCK(huart);                                                                 // 解锁串口状态myUSART1.ReceiveNum  = Size;                                                          // 把接收字节数,存入结构体xUSART1.ReceiveNum,以备使用memset(myUSART1.ReceiveData, 0, sizeof(myUSART1.ReceiveData));                         // 清0前一帧的接收数据memcpy(myUSART1.ReceiveData, myUSART1.BuffTemp, Size);                                 // 把新数据,从临时缓存中,复制到xUSART1.ReceiveData[], 以备使用HAL_UARTEx_ReceiveToIdle_DMA(&huart1, myUSART1.BuffTemp, sizeof(myUSART1.BuffTemp));   // 再次开启DMA空闲中断; 每当接收完指定长度,或者产生空闲中断时,就会来到这个}
}

        (1)xUSART1.ReceiveNum = Size;        

        把接收的字节数,存入结构体 xUSART1.ReceiveNum,以备使用 。            

        在程序的其它地方,判断 ReceivNum > 0, 就能知道是否收到新一帧数据了。

        (2)memset(xUSART1.ReceivedData, 0, sizeof(xUSART1.ReceivedData));        

        清0前一帧的数据缓存  

        (3) memcpy(xUSART1.ReceivedData, xUSART1.BuffTemp, Size);              

        把新数据,从临时缓存中,复制到xUSART1.ReceivedData[], 以备使用                

        从结构体和这段回调函数中,可以发现,这是一个双缓存的操作思路。                      .ReceivedData:用于存放接收后完整的一帧数据,对外使用 。              

        .BuffTemp:用于DMA接收过程,是一个中间缓存。

        (4)HAL_UARTEx_ReceiveToIdle_DMA(&huart1, xUSART1.BuffTemp, sizeof(xUSART1.BuffTemp));              

        再次开启DMA空闲中断,进入接收状态。              

        我们在main()函数的初始化部分,已调用过这个函数了,为什么要在回调函数中再次调用?              

        因为在DMA的中断服务函数里,会关闭DMA,即只接收一次。所以,在接收完一帧后,再次调用函数,就能让DMA开始工作接收下一帧。在这个位置调用 ,能让DMA不断地循环工作。              

        其实,在CubeMX配置中,DMA有一个选项 :Mode的circular, 可以让DMA进行连续地的工作,接收完成后,无需在回调函数里再次开启DMA 

         本篇的处理,是保存最后一帧数据。当有新一帧数据来了,会自动盖掉旧帧数据。

         接下来,为了验证,到底这个项目的程序到底好事使不好使,我在这里写了一个测试函数,其中的myprintf函数是用了我上一篇文章的写法

       

void USART_test()
{if(myUSART1.ReceiveNum)			//一旦接受到数据{myUSART1.ReceiveNum = 0;		//将数据清零if(strcmp((char *)myUSART1.ReceiveData,"hello\r\n") == 0)//相等,返回 0;{num++;myprintf("接受次数%d\r\n",num);}}
}

        将其放在while循环中

        

最终效果演示

        在串口助手这里,简单设置一下 ,一定要注意的是,如果点了发送新行之后,一定不要再额外在第一个箭头的后面加回车了,否则是接受不到的。

         接受的速度也很不错,没有出现过卡死的状况,100ms回应一次。

这篇关于STM32F103C8T6 HAL库 USART1 DMA方式接收数据的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1048004

相关文章

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

Linux脚本(shell)的使用方式

《Linux脚本(shell)的使用方式》:本文主要介绍Linux脚本(shell)的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述语法详解数学运算表达式Shell变量变量分类环境变量Shell内部变量自定义变量:定义、赋值自定义变量:引用、修改、删

python判断文件是否存在常用的几种方式

《python判断文件是否存在常用的几种方式》在Python中我们在读写文件之前,首先要做的事情就是判断文件是否存在,否则很容易发生错误的情况,:本文主要介绍python判断文件是否存在常用的几种... 目录1. 使用 os.path.exists()2. 使用 os.path.isfile()3. 使用

Mybatis的分页实现方式

《Mybatis的分页实现方式》MyBatis的分页实现方式主要有以下几种,每种方式适用于不同的场景,且在性能、灵活性和代码侵入性上有所差异,对Mybatis的分页实现方式感兴趣的朋友一起看看吧... 目录​1. 原生 SQL 分页(物理分页)​​2. RowBounds 分页(逻辑分页)​​3. Page

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

RedisTemplate默认序列化方式显示中文乱码的解决

《RedisTemplate默认序列化方式显示中文乱码的解决》本文主要介绍了SpringDataRedis默认使用JdkSerializationRedisSerializer导致数据乱码,文中通过示... 目录1. 问题原因2. 解决方案3. 配置类示例4. 配置说明5. 使用示例6. 验证存储结果7.

Python程序打包exe,单文件和多文件方式

《Python程序打包exe,单文件和多文件方式》:本文主要介绍Python程序打包exe,单文件和多文件方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录python 脚本打成exe文件安装Pyinstaller准备一个ico图标打包方式一(适用于文件较少的程

Python验证码识别方式(使用pytesseract库)

《Python验证码识别方式(使用pytesseract库)》:本文主要介绍Python验证码识别方式(使用pytesseract库),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全... 目录1、安装Tesseract-OCR2、在python中使用3、本地图片识别4、结合playwrigh

Spring中管理bean对象的方式(专业级说明)

《Spring中管理bean对象的方式(专业级说明)》在Spring框架中,Bean的管理是核心功能,主要通过IoC(控制反转)容器实现,下面给大家介绍Spring中管理bean对象的方式,感兴趣的朋... 目录1.Bean的声明与注册1.1 基于XML配置1.2 基于注解(主流方式)1.3 基于Java