IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 【HAL库】串口通信 -> 正文阅读

[嵌入式]【HAL库】串口通信

HAL库入门之串口通信

背景

首先因为某比赛需要,选用了STM32L4R5ZI的板子(不得不吐槽一下,不像STM32F1或者F4之类的主流板子,资料真的很难找),查全网发现官方提供的包括某宝提供的等等都是基于HAL库的代码,因此,不得不学习一下HAL库的使用。

1. STM32CubeMX(代码生成器)

STM32CubeMX 是 ST 意法半导体近几年来大力推荐的STM32 芯片图形化配置工具, 允许用户使用图形化向导生成C 初始化代码,可以大大减轻开发工作,时间和费用。(可以在官网下载)
简单地说就是,以图形化的方式进行系统时钟、串口、GPIO、中断等初始化配置,之后CubeMX会一键生成基于HAL库编程方式的基础代码,之后可以通过Keil及IAR软件进行后续编程。

1.1 基于board新建工程

CubeMX新建工程有两种方式,基于MCU/MPU以及基于board两种,基于MCU/MPU方式新建的话,包括系统时钟、LED、GPIO等方面都要全部自定义配置,对于刚入门的新手来说,个人感觉是比较困难的,因此选择了基于board新建工程的方式:
打开CubeMX后选择左上角File——>New Project,切换左上角新建工程方式为基于board的方式,下滑进度条选择NUCLEO-L4R5ZI后点击右上角Start Project,如下图所示:
在这里插入图片描述打开后,界面如下图所示:
在这里插入图片描述导航栏中选择Project Manager,自定义项目名称和工程存放目录,之后将Toolchain/IDE栏改为MDK-ARM V5,因为我后续将使用Keil进行后续代码开发,如下图所示:
在这里插入图片描述之后因为串口通信需要用到中断,在导航栏中Pinout & Configuration下的System view下选择LPUART1,之后在出来的界面中选择NVIC Settings,勾选Enable打开串口全局中断,之后点击右上角GENERATE CODE生成代码:
在这里插入图片描述

1.2 基于Keil编程

在使用STM32CubeMX生成初始代码后,使用Keil打开工程(在打开工程之前,需要下载板子对应的pack包,可网上自行下载,博主本人用的2.1.0版本,没什么别的原因,因为我只下载的到这个版本的,双击即可安装)
在这里插入图片描述
打开后,界面如下图所示,找到main.c文件中的main函数入口,可以对系统有个较为直观的认识:
在这里插入图片描述如图,main函数中依次进行了HAL库的初始化、系统时钟的初始化、GPIO的初始化、LPUART1串口串口初始化、UART3串口初始化、USB初始化。
此外引用一下另外一篇算是我的启蒙博客:https://blog.csdn.net/weixin_43186792/article/details/88759321,其中有一段正点原子的解释如下:
在这里插入图片描述里面提到了“MSP”函数和Init入口参数概念,结合打开后的keil工程后,可以理解为这个Core文件夹下,“it.c”文件主要涉及系统中断部分,“msp.c”部分即对应正点原子解释中的“MSP”部分,“main.c”中则是对应的“Init”部分,简单来说就是,串口及外设的通用部分的初始化内容,在移植代码时不需要进行更改的通用配置如波特率、奇偶校验、停止位等部分在main.c中的Init函数中实现,而串口等根据平台板子不同需要进行修改的部分,在msp.c中进行修改和实现,此处给我最大的感受就是HAL库更像是在标准库的基础上追加了“解耦”操作。
在这里插入图片描述

1.3 串口通信

之前我们勾选了LPUART1的中断,可以在it.c文件中查看(如果没有在CubeMX中勾选使能该串口,这里就看不到这个函数):
在这里插入图片描述可以看见函数中定义了一个通用的中断,hlpuart1作为入口参数,点进去之后如下:
在这里插入图片描述可以看到是关于中断处理的一些函数处理,包括串口通信时出现问题时返回ErrorCode等,相当于HAL库将串口中断处理函数帮我们一键写好了,还是比较方便的,那么,如果我们想要在串口收到数据时进行一些自定义的操作时怎么办呢?这里提供一下网上常用的方法:调用“void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)”函数进行回调,这个函数原来在stm32l4xx_hal_uart.c中定义了,如下图所示:在这里插入图片描述可见,是个“__weak”弱类型函数,意味着可以在其他地方进行重写,重写之后,执行重写之后的函数体内容,所以我们可以直接在main.c文件中进行重写。
简单步骤如下:
①在main.c文件中的串口初始化函数MX_LPUART1_UART_Init最后加一句中断注册,这里的RxBuffer是我们自己定义的数组,用来接收串口接收到的数据:
在这里插入图片描述②在main.c文件中重写callback函数,这里Transmit中的1表示发送的字节长度为1,即无论实际的RxBuffer有多大,只发送RxBuffer[0]的内容,100表示在100ms(应该是毫秒,但是我没求证过)之内发送完数据,Recevice_IT中的1表示每接收到几个字节的数据进一次中断,即调用一次callback函数(如果Receive这里写2,Transmit写1,串口发送OK,则每发送一次OK,串口返回一个O,这里可以自己多测试一下,加深一下理解,需要注意的是,最开始的是由串口初始化函数下面的终端注册决定的,进中断处理函数之后,会自动清空标志位,如果callback函数中不重新注册则只能第一次接收到数据,后面的数据接收不到):
在这里插入图片描述③将板子通过USB接到电脑端口上,板子带有一个模拟串口,不需要通过USB转TTL串口的CH340驱动再配置串口,烧写程序前,需要在keil魔术棒中的debug里添加flash烧写算法,博主用的板子对应的如下:
在这里插入图片描述④烧写程序之后,打开串口调试助手(XCOM或者SSCOM都可以),实现的功能就是,串口助手向单片机发送数据后,把收到的字节原样发送出去,这部分内容,网上的教程很多,如果在实现过程中出现问题,可以查询其他博主的帖子。

1.4 接收中断RxBuffer

1.4.1 串口收发数据方式

HAL库串口收发数据有几种不同方式,分别用到不同的函数,这部分在stm32l4xx_hal_uart.c文件中有写:
在这里插入图片描述大概意思翻译后如下:

①阻塞模式:以轮询方式进行通信,所有数据处理的HAL状态在完成数据传输后由相同的函数返回;
②非阻塞模式:通过中断或DMA方式进行通信,由这些API返回HAL状态。当使用中断模式或DMA模式下的DMA IRQ时,数据处理结束后结果将通过专用UART IRQ给出。HAL_UART_TxCpltCallback(), HAL_UART_RxCpltCallback()用户回调将分别在发送或接收过程的结尾执行,当检测到通信错误时,将执行HAL_UART_ErrorCallback()用户回调。
	阻塞模式下的API:
	(+) HAL_UART_Transmit()
	(+) HAL_UART_Receive()
	非阻塞模式下中断模式API:
	(+) HAL_UART_Transmit_IT()
	(+) HAL_UART_Receive_IT()
	(+) HAL_UART_IRQHandler()
	非阻塞模式下DMA模式API:
	(+) HAL_UART_Transmit_DMA()
	(+) HAL_UART_Receive_DMA()
	(+) HAL_UART_DMAPause()
	(+) HAL_UART_DMAResume()
	(+) HAL_UART_DMAStop()

按照全网常用串口收发配置模式,即本文1.3章节所述方式,主要采用中断方式进行数据接收,转发收到的数据的时候用的是阻塞模式发送,大概是因为不需要在发送数据的过程中调用HAL_UART_TxCpltCallback()函数进行逻辑处理所以用阻塞模式更方便一些吧,会不会产生其他问题待后续研究。

1.4.2 出现的一些疑惑

NUCLEOL4R5ZI通过USB连接至电脑串口时,在实现系统供电的同时,可以虚拟出一个串口实现与PC之间的数据通信,可以理解为自带一个类似于CH340的串口驱动。这是个非常方便的功能,LPUART1用的引脚为PG7、PG8,如果我将这两个引脚接到如L610这样的4G模块中,或者NB-IoT这样的模块中可以实现在给模块发送指令的同时,通过电脑串口进行监控,而不需要再使能额外的串口和接额外的线。但是出问题也出在这里。

①将USB转TTL模块通过PG7、PG8连接到NUCLEO板子上同时通过USB线连接板子,相当于接了两个串口驱动,但是用的同一个串口——LPUART1,(打开XCOM串口调试助手通过虚拟串口通信和SSCOM串口调试助手通过CH340进行通信,虽然通过不同的串口驱动通信,实际上都是和LPUART1进行通信,不知道读者能不能明白我的意思)测试发现,串口调试助手通过虚拟串口可以实现数据的收和发(表现为:通过串口调试助手向单片机发送数据,会在当前界面返回发送的内容,同时通过CH340驱动通信的界面也会返回刚刚发送的内容),但是通过CH340串口进行通信的时候,只能收,不能发(表现为发送数据,串口没有任何反应)。

一开始我以为是板子的PG8引脚坏了,还特意换了一个没拆封过的板子,重新测试了结果依然相同,后来我又用同样的方式测试了USART3,可以通过CH340驱动通信进行数据的收和发,证明USB转TTL模块没有问题,且串口配置没有问题,但是LPUART1就是会出现这个问题。

②Receive_IT的问题:将注册的RxBuffer里面的字节长度设置为6时,第一次只接收了4个字节就触发了接收中断,之后是每接收6个字节触发一次接收中断;字节长度设置为4时,每次都只接收2个字节就触发一次接收中断,设置为1时现象倒是正常的,发送什么返回什么,以上说的这些,用USART3串口进行同样的测试,一切正常。

综上所述,LPUART1是很奇怪的串口,不明白是什么原因,待进一步研究,希望有懂的大佬不吝赐教,为了串口通讯方便,我曾将自动生成的代码里的波特率改为115200,因为串口调试助手里的波特率没有原代码里设置的209700选项,不知道是不是这个原因,可是还是不能解释LPUART1不正常而USART3正常的问题。欢迎交流学习!

1.4.3 解决问题的思路

1.4.3.1 问题描述

使用串口通信最终的目的是与通信模块进行AT指令通信,通过通信模块返回给串口的数据进行逻辑上其他处理,如连接服务器,发送和订阅相关主题等等,这就意味着我需要对RxBuffer的内容进行处理,而不是简单的返回串口我发送的内容即可,意味着我在注册中断接收的时候设定的字节长度必须能包含我接收到全部数据,因此,使用了255长度,这就导致了一个问题,串口只有在接收到255字节长度数据后才会调用callback函数,无法及时通过串口监控程序进程。

再考虑到之前的PG8引脚无法接收数据,因此改用USART3串口的PD8、PD9引脚分别实现发送和接收,在通过USART3发送数据的时候,同时通过LPUART1发送一遍相同的数据,实现通过LPUART1串口同步监控USART3串口的通信进程。

接下来流程如:单片机通过USART3串口向L610模块发送AT指令:
在这里插入图片描述L610接收到后返回给串口一个包含“OK”的字符串,这个字符串将被存至定义好的长度为255的RxBuffer中,之后通过定义好的strx指针接收函数strstr判断的结果,如果RxBuffer中含有指定的“OK”字符串,则strx不为空,后面可以根据strx指针是否为NULL进行进一步地编程。

strx=strstr((const char*)RxBuffer,(const char*)"OK");

在进行过一次AT指令的交互后,需要对RxBuffer进行清空操作,否则可能会溢出,或者影响下一次的返回值判断:
在这里插入图片描述
这里的问题就出来了,因为HAL库良好的封装性,暂时没法从寄存器层面获得每一次字节传输的过程,即无法得知寄存器内数据长度,导致清空数据库每次都需要清255,从程序设计上将是很不合理的。如果单独写一个判断RxBuffer数据长度的函数,从RxBuffer[0]开始判断到0x00为数据长度的话,如果接收到的数据中包含空行(实际测试中确实有这种情况,返回的“AT OK”中间隔了0x00的空行)就会导致清空缓存失败,暂时没想到好的办法,因此只能每次都清255。

本来此种设计除了每次都需要清255的RxBuffer不合理之外,从其他方面考虑应该是没什么问题的,但是在实际测试的时候,发现有些AT指令很快就可以识别,有些却非常慢,甚至需要发送很多遍才能识别到RxBuffer中接收到的返回值,导致通信模块的联网过程非常慢,如下图所示:
在这里插入图片描述服务器接收到了四次数据:
在这里插入图片描述这里又是一组矛盾,如果是RxBuffer接收数据程序设计的问题,为什么有的AT指令一遍就过了?如果不是这个问题,那么为什么有的AT指令需要发送十几二十几遍才能识别出来?更有甚者,联网已经连上了,在向服务器发送数据的时候只要发送一遍,接收到“+MQTTPUB”返回值即止,然而单片机发送了好几遍,服务器也确实收到了好几遍之后程序才从RxBuffer中接收到了返回值,这就离谱,希望有懂的大佬指教一下。

1.4.3.2 解决方案

问题既然已经出现了,这么慢的联网速度也是我无法接受的,于是我在寻求其他解决方法的时候,看到了这样一篇博文:https://blog.csdn.net/weixin_44578655/article/details/104677301,其中有这样一段:
在这里插入图片描述这让我深受启发,这不是标准库中的写法么,于是按照如下步骤进行了HAL库中代码的修改:
①注释掉原来在串口初始化末尾处注册的接收中断,在同样的地方替换为引用博文中所述的使能接收中断语句:
在这里插入图片描述②在it.c文件中的USART3中断处理函数中注释掉CubeMX自动生成的通用中断函数入口(我是直接照着此前使能的LPUART1串口写的,从CubeMX中打开串口全局中断使能应该也是一样的效果),并替换为标准库写法:
在这里插入图片描述查询STM32L4的串口寄存器,引用博文中的SR寄存器在L4中对应的为ISR寄存器,DR寄存器对应的RDR寄存器,对代码进行了修改。此外,在这里借助寄存器对RxBuffer进行赋值,不仅可以逐字节进行赋值,还可以借助RxCounter得知RxBuffer的长度,在清除RxBuffer缓存的时候,可以将255替换为RxCounter提高程序运行效率。

之后烧录程序进行测试,发现程序效率奇高,效果非常好(为了作对比,虽然得知了RxBuffer的数组长度为RxCounter,但是RxBuffer清空函数我并未修改,仍为清255,但是两种方式的实际运行效果差别还是非常大的),如下图所示:
在这里插入图片描述可以看到服务器接收到了一次数据:
在这里插入图片描述

写在最后

本来只想简单的记录一下的,不知不觉就写了这么多,最主要的目的是培养自己整理学习经验的习惯,防止后人踩坑。HAL库系列的内容,后面如果继续学习会继续以【HAL】的前缀撰写学习笔记

关于HAL库,目前个人的看法是有好有坏,最大的好处是实现了“解耦”,利用代码生成器,图形化方式一键配置可以省去很多事情,在平台间的移植能力也会变强,实现“解耦”是一件不容易的事情,新事物的出现必然有其出现的理由,不能害怕也不应该抵触,勇于尝试、了解,才能扬长避短,提高自己的开发能力。至于坏处,有些人说什么编译慢、什么简单的问题复杂化之类的,其实最大的问题是资料和教程太少了,希望大佬们在学习的过程中可以分享一下学习经验吧,精卫填海,从我做起!

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-08-24 15:43:22  更:2021-08-24 15:43:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/21 0:17:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码