开源一夏 | 在 STM32L051 上使用 RT-Thread (四、无线温湿度传感器 之 串口通讯)
前言
在上一篇文章,我们实现了温湿度驱动移植,根据我们最初的基本设计思路,还有必须要实现的无线模块串口通讯,本文就来移植一下无线模块的串口通讯驱动。
<font color=#0033FF>再次说明一下,本应用篇重点在于理解在 RT-Thread 上的设计思路 以及 在小内存芯片上的注意事项,所以基础的驱动代码的实现并不会详细的分析说明,但是博主在把本系列更新完以后会把最后的整个项目上传,所以实在想看驱动实现的朋友到时候也可以去下载。
本 RT-Thread 专栏记录的开发环境:
RT-Thread 记录(一、RT-Thread 版本、RT-Thread Studio 开发环境 及 配合 CubeMX 开发快速上手)
https://xie.infoq.cn/article/44be1057caace7a6a2c4c4b59
RT-Thread 记录(二、RT-Thread 内核启动流程 — 启动文件和源码分析)
https://xie.infoq.cn/article/44be1057caace7a6a2c4c4b59
RT-Thread 内核篇系列博文链接:
RT-Thread 记录(三、RT-Thread 线程操作函数及线程管理与 FreeRTOS 的比较)
https://xie.infoq.cn/article/1d2e8e030ae689d6b8ee44b05
RT-Thread 记录(四、RT-Thread 时钟节拍和软件定时器)
https://xie.infoq.cn/article/3198c9b741782036bfd6e54e9
RT-Thread 记录(五、RT-Thread 临界区保护
https://xie.infoq.cn/article/7a41020e03184950664df7391
RT-Thread 记录(六、IPC 机制之信号量、互斥量和事件集)
https://xie.infoq.cn/article/1f49bfd6c69377deb9eee838f
RT-Thread 记录(七、IPC 机制之邮箱、消息队列)
https://xie.infoq.cn/article/360b04e7bc6024917afecef1d
RT-Thread 记录(八、理解 RT-Thread 内存管理)
https://xie.infoq.cn/article/bd5d8f8a19fa11ea9feacf34d
RT-Thread 记录(九、RT-Thread 中断处理与阶段小结)
https://xie.infoq.cn/article/3da9530cfac06feece3523a0c
RT-Thread 应用篇系列博文连接:
在 STM32L051 上使用 RT-Thread (一、无线温湿度传感器 之 新建项目)
https://xie.infoq.cn/article/49028076c9f97e06b119e67d4
在 STM32L051 上使用 RT-Thread (二、无线温湿度传感器 之 CubeMX 配置)
https://xie.infoq.cn/article/b8695fecf630f5c93262b0dd8
在 STM32L051 上使用 RT-Thread (三、无线温湿度传感器 之 I2C 通讯)
一、设计思路说明
我们 STM32L051C8 与无线模块通讯的串口是LPUART1
(对应 pin to pin 的 STM32F103C8 是串口 3),使用的是中断方式接收,所以当时在 CubeMX 设置的时候我们就需要使能中断。
STM32 串口中断接收是很基础问题,本文的目的不在于说明 STM32 如何进行串口通讯,所以并不会详细阐述如何使用串口接收,我们只做简单说明:
对于 STM32 的串口接收中断,我们一般会使能UART_IT_RXNE
或者UART_IT_IDLE
。
简单说明一下,如果串口收到一个字节就会产生RXNE
中断,如果接收完成一帧数据,就会产生IDLE
中断。
根据我自己使用的经验,这个 IDLE
中断所谓的一帧数据,是 stm32 内部自身根据自己主频,波特率等一些参数作为判断依据,本质上也是认为在多长的时间内没有收到新的数据就当成一帧。
再根据以前的使用经验和测试,我使用的无线通讯模块给 STM32 发送一帧数据,会触发两次IDLE
中断,这个具体也没有深究,因为本身无线通讯模块也是靠自己的单片机内核发送数据,或许模块内部有点特殊处理,通过这个问题我也发现IDLE
不是万能的一帧数据,在遇到一些特殊模块的时候也会有问题。当然,在我使用的其他设备,所有串口通讯的传感器上,使用IDLE
作为一帧数据接收完成的判断都是没问题的。
当时的测试博文:STM32L051测试 (五、Enocean模块串口通讯问题)
所以,虽然我们本次模块接收的是不定长度,但是我们这里还是不使用IDLE
作为一帧数据的判断,我们只使用RXNE
中断。
为了更加直观的理解我们的设计思路,我也不准备使用 DMA 通方式。
综上,本次应用的设计思路是:只使能RXNE
中断,当接收到第一个字节,释放一个信号量。
另外一边,串口接收线程一直在等待这个信号量,如果获取到信号量,等待一定时间(等待一定时间是为了保证接收到完整的一帧数据,一般就是几个 ms),然后进行数据处理。
发送数据的话就简单,写好驱动,组包发送即可。
为了接收数据,我们需要定义个全局变量数组作为缓冲区。
后来在实际使用的时候这个思路稍微修改了一下,因为每次产生中断,都会发送一次信号量,那么一帧数据有多少个字节,就会发送多少个信号量,那么等待信号量的线程就会不停的获取到信号量,那么我们怎么来处理?且看下文……
二、驱动移植
因为博主使用的 Enocean 模块并不是通用主流的通讯模块,是公司需要使用的,考虑到各种问题,也不方便把模块详细的介绍一遍,但是这并不影响我说明程序的移植和串口的使用思路,大家不需要在意内部的具体实现,只要明白我在 STM32L051C8 上 RT-Thread 是如何处理串口数据的即可。
首先我们把需要保存接收数据的数组定义一下:
这个全局数据可是直接占用了一大块 RAM 空间,enoncean_buff 多大就占用多大,可是这是没有办法的!
然后根据上一篇文章的说明,我们直接把.c 和.h 文件拷贝到我们的 mydrivers 目录下:
编译一下看看,
其中,上面的警告提示我们还需要实现 1 个函数,就是串口发送数据的函数,我们也移植过来,加在 CubMX 串口驱动文件中:
实现好这些基本上整体上没问题了,然后需要做一些移植过来代码的细节修改,细节修改这里就不一一说明,我们使用一个典型的地方举个例子。裸机中,除了中断所有的操作都是先后进行的,所以有很多延时函数是干等,在使用 RT-Thread 的时候这些函数都得去掉,比如
三、信号量的处理
信号量的处理算得上本文的重点了,本来我们在 RT-Thread Nano 上使用串口通讯,完全可以按照裸机的方式来,定义全局变量然后线程轮询接收函数,但是这里我还是想着没有消息的时候线程轮询完全是浪费资源,我得发挥操作系统的优势。 至少做到,只有消息来的时候线程才会唤醒去执行,其他时候都是阻塞状态。
虽然信号量处理的方式并不是最优的,当然也是本着测试的原则,来尝试一下。
再次注意,我们在本应用第一篇就说明过,只能使用静态初始化的方式创建对象,信号量也不例外,测试时候还在这里出错了。 = =!
3.1 释放信号量
首先初始化信号量,中断响应函数中做基本的处理:
注意,有一个 HAL 库的基本使用问题,什么时候才会调用HAL_UART_RxCpltCallback
中处理,我们用户的处理可以在原始的中断向量表对应的函数LPUART1_IRQHandler
中处理,也可以在上图的HAL_UART_RxCpltCallback
中处理。
同时,数据处理的方式要注意,标志着缓冲数组个数的 Enocean_Data 是否需要先++,也是需要注意。
这些其实是 STM32 HAL 库使用的基本知识,在下面《4.2 串口通讯细节问题》会有说明。
如果在LPUART1_IRQHandler
中处理也可以,数据处理是放在HAL_UART_IRQHandler(&hlpuart1);
前面还是后面也是有讲究的:
3.2 获取信号量
考虑到内存不够,不想再新建线程作为数据接收处理了,直接把数据接收处理放在主函数线程里面,这里给出基本的框架:
☆(其实两句代码就是本文核心框架~ ~)☆
四、基本测试
我们先测试基本的接收函数移植是否正常,然后再测试发送函数。
4.1 接收测试
完成上面的步骤,我们基于上面的框架,应该是可以用起来了,比如最初的上电需要读取通讯模块的 ID,得到 ID 以后发送一次无线报文,实现的代码如下:
结果如下:
在解决了上图所说的 ID 读取异常问题之后(就在下面《4.2 串口通讯细节问题》,这是 STM32 HAL 库的使用问题),我们再添加一些框架代码:
看下测试结果,上电 ID 读取正确,按键线程正常,接收报文也正常:
4.2 串口通讯细节问题
具体问题描述:
上面的第一次的 ID 读取截图有问题,检查了一段时间,后来发现接收额数据与实际的有一位的差别,然后解决了这个问题,在接收数据的时候还是发现每次接收数据会丢失第一个字节。
这是 STM32 HAL 库基本使用导致的,因为博主开始直接使用程序移植,有些细节的地方没有第一时间发现。
其实最根本的原因在于,串口开启之时是如何使能中断接收的!
是用__HAL_UART_ENABLE_IT
宏定义使能中断
还是HAL_UART_Receive_IT
这个函数使能中断?
这是 STM32 的基础使用问题,复制的分析调整过程这里就省略了,我只把最后的结论和使用方法说明一下,其实使用HAL_UART_Receive_IT
内部会调用__HAL_UART_ENABLE_IT
。
__HAL_UART_ENABLE_IT
先来说说使用__HAL_UART_ENABLE_IT
的情况,正确的流程图如下:
这是一种效率比较高的方式,使用__HAL_UART_ENABLE_IT
使能无法进入HAL_UART_RxCpltCallback
函数(有问题请指出),所以我们得在LPUART1_IRQHandler
进行数据处理。
HAL_UART_Receive_IT
一般使用方式
如果串口初始化以后就使用函数HAL_UART_Receive_IT
开启接收中断,大部分网络文章教程说明使用流程如下图:
其他方式说明一
当然,我们的数据处理可以不在HAL_UART_RxCpltCallback
函数中,也可以学习上面在LPUART1_IRQHandler
中处理,比如:
上面图中的注意事项,原因是因为HAL_UART_IRQHandler(&hlpuart1);
处理过程会关闭一些中断,之后才调用HAL_UART_RxCpltCallback
,我们在HAL_UART_RxCpltCallback
最后的函数HAL_UART_Receive_IT
又会重新打开,所以可以正常走流程。如果我们在LPUART1_IRQHandler
中处理,在执行 HAL_UART_IRQHandler(&hlpuart1);
的时候会关闭中断,如果在此之后不再次使能,就无法继续响应下次中断了!
其他方式说明二
开始使用函数HAL_UART_Receive_IT
开启接收中断,其他地方使用完全和使用__HAL_UART_ENABLE_IT
的情况一样也是可以的:
至于原因,是在HAL_UART_Receive_IT
函数最后会使能RXNE
中断,就和使用__HAL_UART_ENABLE_IT
是一样的:
两种方式都可以实现串口中断处理,然后两种方式结合也是可以的,但是并不建议,除非你完全理解 HAL 库的内部实现方式,你完全知道自己在做什么!
4.3 发送测试
发送测试其实在我们前面发送学习报文已经得到过验证了,能够正常的发送学习报文表明发送功能没有问题。
我们这里要做的就是把温湿度的数据封包至无线报文中发送出去,这里发送函数的话按理来说也可以新建一个线程专门处理,收到特定的信号量进行发送,但是考虑到内存问题而且我们本应用功能比较简单,所以我们直接在温湿度读取线程里面进行,以前是读取了数据打印出来,现在是封包至无线协议通过报文发送出去。
发送操作直接放在温湿度读取线程里面进行处理:
测试结果正常,如图:
还好我把发送功能加入到温湿度读取的线程中,并不需要增加线程栈空间就可以正常运行。
经过上面的折腾,在串口细节处理上花了不少的时间,不过好在结局还算圆满,接收和发送都测试正常!
五、时刻关注占 RAM 大小
串口的应用我们并没有新建线程,但是因为串口需要缓存区和与串口处理相关的一些全局变量,还有信号量也需要占用 RAM 空间,所我们的内存占用又变大了。
那么还是老样子,今天测试完成以后和以前占用空间的对比图上一下:
串口通讯的流程实现完后,程序运行时候需要占用 RAM 的大小: 7416 字节,我们的芯片 RAM:8192 字节。
结语
本文我们使用信号量实现了串口通讯,虽然也不是复杂的过程,还是遇到了不少的问题,使得本来昨天能够完成的博文,不得不晚一天,在基本的 STM32 串口通讯问题上画了一些时间调试,移植虽然可以省去大部分工作,但是细节问题不容忽视。
本次测试,也算是让自己再次总结了一下 STM32 HAL 库中的串口中断接收方式。然后信号量接收的方式居然和自己考虑的一样完美的实现接收一帧数据,还是有点小惊喜的!
其实本次应用篇到这里已经算是实现了一个单品传感器了,结束? 既然是应用篇,那么当初计划的功能还是得完善一下,比如,按键操作,短按长按的动作,至少把按键驱动移植完。定时器,使用定时器作为传感器采集的时间机制,那么下一篇就决定了,按键驱动移植,如果顺利把简单的定时器也顺带加上~ ~!
没想到本次测试比上一篇还累 = =! 继续希望小伙伴多多支持,多多指教!
好了,本文就到这,谢谢大家!
版权声明: 本文为 InfoQ 作者【矜辰所致】的原创文章。
原文链接:【http://xie.infoq.cn/article/550465aa9a61bf7f4bf3258b2】。文章转载请联系作者。
评论