前言
一、设计思路说明
二、驱动移植
三、信号量的处理
3.1 释放信号量
3.2 获取信号量
四、基本测试
4.1 接收测试
4.2 串口通讯细节问题
__HAL_UART_ENABLE_IT
HAL_UART_Receive_IT
4.3 发送测试
五、时刻关注占RAM大小
结语
在上一篇文章,我们实现了温湿度驱动移植,根据我们最初的基本设计思路,还有必须要实现的无线模块串口通讯,本文就来移植一下无线模块的串口通讯驱动。
再次说明一下,本应用篇重点在于理解在 RT-Thread 上的设计思路 以及 在小内存芯片上的注意事项,所以基础的驱动代码的实现并不会详细的分析说明,但是博主在把本系列更新完以后会把最后的整个项目上传,所以实在想看驱动实现的朋友到时候也可以去下载。
❤️
本 RT-Thread 专栏记录的开发环境:
RT-Thread记录(一、RT-Thread 版本、RT-Thread Studio开发环境 及 配合CubeMX开发快速上手)
RT-Thread记录(二、RT-Thread内核启动流程 — 启动文件和源码分析)
❤️
RT-Thread 内核篇系列博文链接:
RT-Thread记录(三、RT-Thread 线程操作函数及线程管理与FreeRTOS的比较)
RT-Thread记录(四、RT-Thread 时钟节拍和软件定时器)
RT-Thread记录(五、RT-Thread 临界区保护)
RT-Thread记录(六、IPC机制之信号量、互斥量和事件集)
RT-Thread记录(七、IPC机制之邮箱、消息队列)
RT-Thread记录(八、理解 RT-Thread 内存管理)
RT-Thread记录(九、RT-Thread 中断处理与阶段小结)
❤️
在STM32L051C8 上使用 RT-Thread 应用篇系列博文连接:
RT-Thread 应用篇 — 在STM32L051上使用 RT-Thread (一、无线温湿度传感器 之 新建项目)
RT-Thread 应用篇 — 在STM32L051上使用 RT-Thread (二、无线温湿度传感器 之 CubeMX配置)
RT-Thread 应用篇 — 在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作为一帧数据接收完成的判断都是没问题的。
所以,虽然我们本次模块接收的是不定长度,但是我们这里还是不使用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);前面还是后面也是有讲究的:
考虑到内存不够,不想再新建线程作为数据接收处理了,直接把数据接收处理放在主函数线程里面,这里给出基本的框架:
❤️(其实两句代码就是本文核心框架~ ~)❤️
四、基本测试我们先测试基本的接收函数移植是否正常,然后再测试发送函数。
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库中的串口中断接收方式。然后信号量接收的方式居然和自己考虑的一样完美的实现接收一帧数据,还是有点小惊喜的!
其实本次应用篇到这里已经算是实现了一个单品传感器了,结束? 既然是应用篇,那么当初计划的功能还是得完善一下,比如,按键操作,短按长按的动作,至少把按键驱动移植完。定时器,使用定时器作为传感器采集的时间机制,那么下一篇就决定了,按键驱动移植,如果顺利把简单的定时器也顺带加上~ ~!