stm32 hal uart_STM32 非阻塞HAL_UART_Receive_IT解析与实际应用

论坛 期权论坛 编程之家     
选择匿名的用户   2021-5-28 05:40   909   0
本文章主要探讨如何使用STM32中HAL库的 UART_Receive_IT非阻塞接收数据。其他网络教程(包括正点原点相关教程)可能个人原因无法完全理解,苦苦挣扎后才完成非阻塞UART接收。
希望可以通过不同的视角能更好的总结分享如何使用HAL库中的非阻塞UART。关于如何配置UART的问题本文不做具体介绍,需要这部分内容的可以参考其他分享者的文章。另外,如希望在MacOS或Linux平台优雅的编写、烧录STM32代码的可以参考我另外两篇文章。
Yume:MacOS在VSCode中优雅的编写STM32(HAL库)zhuanlan.zhihu.com
Yume:MacOS基于STM32CubeMX、Makefile和OpenOCD的Stm32交叉编译环境搭建zhuanlan.zhihu.com

1 通过STM32CubeMX初始化并生成工程目录

通过STM32CubeMX可以很轻松的完成配置,网络上也有很多很优秀的分享者写的教程。同时也可以参考我的另一篇文章(MacOS、Liunx用户也可作为参考)进行相关的配置

Yume:MacOS基于STM32CubeMX、Makefile和OpenOCD的Stm32交叉编译环境搭建zhuanlan.zhihu.com

2 通过CoolTerm与STM32通信

Roger Meier's Freewarefreeware.the-meiers.org

具体使用方法,可以在网络上找到很多教程,基本方法就是设置好对应的如波特率、有无奇偶校验位等。需要特别指出的是,此软件发送"ASCII"是原生发送,不会自动添加额外的结束符(也可能是我没有配置)。

3 HAL库中的非阻塞UART

可以在工程目录"Drivers/CMSIS/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_uart.c"中看到所有关于UART的HAL库函数使用方法。其中我们重点关注4个函数"UART_Receive_IT"、"HAL_UART_RxCpltCallback"、"HAL_UART_IRQHandler"、"HAL_UART_Receive_IT"

  • HAL_UART_IRQHandler

此函数是request(响应中断),即UART的RX中断入口。当有数据发送时,就会进入到这个函数中。

v2-01ec1be04e484d18d0084dc8422db08f_b.jpg
HAL_UART_IRQHandler

可以从HAL的UART库文件看到该函数的描述与具体过程。正常情况下(即errorflags = RESET)将会调用"UART_Receive_IT"进入处理数据的部分。

  • UART_Receive_IT

这个函数可以理解为RX接收数据处理的函数

v2-5ffc2f808afe1c36036ea97b3792c892_b.jpg
UART_Receive_IT

可以看到当进入到这个函数的时候,会判断当前RX接收状态(重要)。中间数据处理过程我们略过,大概知道就是将数据存入到一个特殊寄存器里。(想了解具体过程的,可以自行阅读HAL库相关文件)。再看看完成数据的转存、状态设置之类操作后会发生什么

v2-adf884864d1cb09d166cb4bc1b84ee5a_b.jpg
UART_Receive_IT

判断条件"--huart->RxXferCount"就是判断进来比特数是否到达指定的大小可以看到当满足条件是,将会关闭中断响应,同时调用"HAL_UART_RxCpltCallback"函数。这个函数时给用户的自定义的函数,指定完成数据接收后的操作。

  • HAL_UART_RxCpltCallback

上文已经提到了,这是个给用户在完成RX数据接收后自行决定怎么操作的函数。但我们还是看看在HAL库文件中是怎么定义的

v2-7d4bcf4313311cd96dcf9bf314051da2_b.jpg
HAL_UART_RxCpltCallback

可以看到void前面的"__weak"描述就是弱定义,指当用户在进行定义时,可以理解为这个定义就失效了。所以我们可以在"main.c"再定义这个函数,用于Rx完成接收后的操作。

那说到这,具体如何将Rx接收的数据进行转存、处理等操作呢?

等等,是不是发现还有一个函数没有说?

诶,通过从中断入口层层进入,始终没出现过"HAL_UART_Receive_IT"。那他究竟来干嘛的?还记得前面说到,在完成接收后"UART_Receive_IT"就会关闭中断。现在我们看看"HAL_UART_Receive_IT"

v2-d6b146a3a152953d8cd56caa76994cb6_b.jpg

可以看到,该函数UART的RX为准备状态后,就会将数据从接收数据的特殊寄存器"pRxBufferPtr"指针指向"pData"(用户创建的寄存器)和一系列包括修改标志位等操作后,重新开启UART的Receive中断。

4 使用UART的HAL库

那通过上面的一步一步从中断入口走向出口。那我们也从入口开始,那就需要先打开入口(伪代码形式)

uint8_t 

其中"huart1"是UART1的数据结构,STM32CubeMX配置时会帮我们完成,是全局变量;"rDataBuffer"是用户自定义用于转存RX接收数据的寄存器;后面的数字"1"表示接收数据为1比特(目的是接收到1比特数据就拿出来,实现通过结束符判断数据接收完成,而不是指定的字节长度)。在main中编写这段,就完成了打开中断入口的操作,同时配置"huart1"数据结构中关于RX接收信息,提供给中断入口函数"HAL_UART_IRQHandler"。那为何在这里需要用while不断循环检测返回值呢?因为可能RX处于正忙状态。如果在RX正忙状态调用HAL_UART_Receive_IT,可能就会导致不是按预期执行的。

当数据发送到RX时,触发中断,进入"HAL_UART_IRQHandler"。从第一步的到的RX接收信息可知,收到1Byte数据就会进入到"UART_Receive_IT"处理数据、关闭总断入口。再进入到用户自定义操作的函数"HAL_UART_RxCpltCallback"。所以我们需要在"main.c"中编写这个函数(函数外定义的变量是全局变量)

uint8_t 

其中,全局变量"rData"用于存放接收到的数据;"rDataBuffer"用于将接收到的数据从UART的RX特殊寄存器中转移出来;"rDataCount"用于记录收到的结束符之前(可以看到是通过"~"确认结束符,也可以根据需求自行定义)的数据字节数;"rDataFlah"是通过一个符号表示完成RX的接收工作。当然,这些符号可以根据具体需求定义,并不是必须这么做的。

注意到的是,这里还有一个条件判断"rDataBuffer[0]!=0x00",这么写的目的首先是接收到"0"不处理,还有一个原因是因为在实践过程中,不知道是什么原因,第一次发RX数据时,回显数据显示已经进入过中断,并进行RX数据接收转存。如果不加判断即使是没接收到数据("0"=0x00)也判断为有效数据,那会出现第一次RX数据前面有几字节为"0",不是预期的结果。我的判断是可能在确认分隔帧时(大概就是取样点),误进入中断导致的。但这不会影响后续RX的接收,所以及时不加也不会出现大问题。

然后接下来的判断就是检查是否收到RX接收结束符号,然后改变标志位,用于其他操作。

后面"while"就是重新打开UART的RX中断,继续接收数据。

5 结语

可以看出来,具体使用时其实就可以分为简单的两步:

  1. "HAL_UART_Receive_IT"开启中断
  2. "HAL_UART_RxCpltCallback"用户处理中断

但如何完全掌握,能在实际项目中变通运用UART。还需要仔细分析库函数的层层套娃中摸出一条线来。

还有需要注意的是,本文所写的没有关注如寄存器溢出、出现错误如何处理等细节问题,这都需要更仔细的思考解决。


分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:3875789
帖子:775174
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP