1、背景
之前买过一个传感器模块,可以输出光照强度、温湿度、大气压强、海拔,看着好玩就买了,刚好现在辞职了就在家研究研究,RT_Thread我也是最近在家这段时间开始学习的,恍然间都更新了23篇了,希望自己可以多学一些,而不是拿着自己现在会的知识打一份工,找工作不是看是否轻松是否挣钱,工作是我们生命中的一大部分时光,一定要找到一份热爱的工作,不然就太可惜了。
这个模块(GY39)的数据可以由串口输出,格式如下:
USB转TTL连接模块和串口助手,接收数据如下:(循环发送0x15和0x45的帧,只截取一段)
2、解包方法
2.1、大缓存查找解包(有一些弊端)
对于这样循环发送过来的数据,我之前了解过的解包方法是这样的,先开一个大一点空间(得保证有完整的数据帧)的数组,然后串口接收到的数据就往里存,存满了之后开始从前到后循环查找,找到帧头0x5A,再依次把后面数据按照通讯的格式解包出来。
这样做,一定有数据的丢失,而且极端一点,上面一次是24个字节,如果你开辟的空间刚好是24或者48之类的,有木有一种可能,你每次只解析到第一帧的光照强度数据,后面的数据你主动丢弃了而解析不到呢?当然你可以针对这些情况开辟某一种大小的空间,数据不丢弃一直查到到最后的字节.......
2.2、根据格式解包
首先,只需要开辟最长一帧大小的数组;(数据是不定长的)
然后,每次接收到数据后,根据解包的阶段来判断是否丢弃数据或写入,解包的各个阶段用enum来定义,具体流程如下:
这部分代码在gy39_thread_entry函数里;
- FRAME_STEP_START1阶段,等待第一个帧头0x5A,没等到就等,等到了进入下一阶段;
- FRAME_STEP_START2阶段,判断数据是否是0x5A,是则说明有两个0x5A帧头正确,进入下一阶段;如果不是0x5A回到第一阶段。
- FRAME_STEP_TYPE阶段,判断帧类型是否正确,如果是已知的帧类型进入下一阶段,否则回到第一阶段。
- FRAME_STEP_DATALEN阶段,判断是否超过最长的数据长度,否进入下一阶段,是回到第一阶段。
- FRAME_STEP_DATA阶段,循环接收数据,接收完进入下一阶段。
- FRAME_STEP_CHECK_ANALYSE阶段,检查校验和,正确则解析数据然后回到第一阶段,不正确则直接回到第一阶段。
//处理一帧的各个阶段
typedef enum {
FRAME_STEP_START1 = 0, //等待第一个帧头
FRAME_STEP_START2, //等待第二个帧头
FRAME_STEP_TYPE, //判断帧类型
FRAME_STEP_DATALEN, //判断帧数据长度
FRAME_STEP_DATA, //循环接收帧数据
FRAME_STEP_CHECK_ANALYSE, //检查校验和,解析数据
}gy39_frame_step;
接收到数据后进行的这些操作并不复杂,只是一些判断和计算,并不影响下一个字节的接收。如果比较复杂的话,可以改进,最后一个阶段检查校验和和解析数据可以分开,检查校验和对了最后的解析数据部分可以放到其他地方操作,但是如果发送的频率过快,解析不过来也没用呀。
3、代码
3.1、GY39模块的头文件
#ifndef APPLICATIONS_GY39_GY39_H_
#define APPLICATIONS_GY39_GY39_H_
#include <rtthread.h>
//GY39模块用的串口
#define GY39_UART "uart3"
#define FRAME_START 0x5A //帧头
#define FRAME_ALL_MAXLEN 15 //整个一帧的最大长度
#define FRAME_DATA_MAXLEN 10 //一帧中数据的最大长度
//帧中,特定位置的下标
#define FRAME_TYPE_POS 2 //数据类型的下标
#define FRAME_DATALEN_POS 3 //数据长度的下标
#define FRAME_LUX_POS 4 //光照强度数据开始的下标
#define FRAME_TEMP_POS 4 //温度数据开始的下标
#define FRAME_AP_POS 6 //气压数据开始的下标
#define FRAME_HUM_POS 10 //湿度数据开始的下标
#define FRAME_ALTITUDE_POS 12 //海拔数据开始的下标
#define FRAME_IIC_POS 4 //IIC地址的下标
//帧类型
#define FRAME_TYPE_LUX 0x15 //光照强度
#define FRAME_TYPE_TEMP_HUM_AP 0x45 //温湿度、压强、海拔
#define FRAME_TYPE_IIC 0x55 //IIC地址
//处理一帧的各个阶段
typedef enum {
FRAME_STEP_START1 = 0, //等待第一个帧头
FRAME_STEP_START2, //等待第二个帧头
FRAME_STEP_TYPE, //判断帧类型
FRAME_STEP_DATALEN, //判断帧数据长度
FRAME_STEP_DATA, //循环接收帧数据
FRAME_STEP_CHECK_ANALYSE, //检查校验和,解析数据
}gy39_frame_step;
//记录GY39模块的信息
typedef struct {
double lux; //光照强度(0.045 ~ 188000lux)
double temperature; //温度(-40℃ ~ 85℃)
double humidity; //湿度(0% ~ 100%)
double atmos_pressure; //气压(300 ~ 1100hpa)
double altitude; //海拔
uint8_t IIC_address; //IIC地址
}GY39_Information;
GY39_Information GY39_Info;
//开启GY39模块
int gy39_module_uart3_startup();
//打印GY39模块的所有信息
void printf_GY39_Info();
//外部获取GY39模块的各个信息
double gy39_get_lux();
double gy39_get_temperature();
double gy39_get_humidity();
double gy39_get_atmos_pressure();
double gy39_get_altitude();
uint8_t gy39_get_IIC_address();
#endif /* APPLICATIONS_GY39_GY39_H_ */
3.2、GY39模块的源文件
#include "gy39.h"
static void init_GY39_Info()
{
GY39_Info.lux = 0;
GY39_Info.temperature = 0;
GY39_Info.humidity = 0;
GY39_Info.atmos_pressure = 0;
GY39_Info.altitude = 0;
GY39_Info.IIC_address = 0;
}
void printf_GY39_Info()
{
rt_kprintf("temperature = %.2f\n",GY39_Info.temperature);
rt_kprintf("lux = %.2f\n",GY39_Info.lux);
rt_kprintf("humidity = %.2f\n",GY39_Info.humidity);
rt_kprintf("atmos_pressure = %.2f\n",GY39_Info.atmos_pressure);
rt_kprintf("altitude = %.2f\n",GY39_Info.altitude);
rt_kprintf("IIC_address = %d\n",GY39_Info.IIC_address);
}
//GY39的校验和,arr是待校验的数组,length是要校验的数据长度
static char gy39_checksum(char* arr,uint8_t length)
{
char res = 0;
for (int var = 0; var < length; ++var) {
res += arr[var];
}
return res;
}
/* 用于接收消息的信号量 */
static struct rt_semaphore gy39_rx_sem;
static rt_device_t gy39_serial;
/* 接收数据回调函数 */
static rt_err_t uart3rxd_callback(rt_device_t dev, rt_size_t size)
{
/* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
rt_sem_release(&gy39_rx_sem);
return RT_EOK;
}
static void gy39_thread_entry(void *parameter)
{
char ch;
gy39_frame_step frame_step = FRAME_STEP_START1;
char buffer[FRAME_ALL_MAXLEN]; //一帧的缓存
uint8_t offset = 0; //帧的偏移地址
uint8_t frame_data_length = 0; //帧中数据的长度
while (1){
/* 从串口读取一个字节的数据,没有读取到则等待接收信号量 */
while (rt_device_read(gy39_serial, -1, &ch, 1) != 1){
/* 阻塞等待接收信号量,等到信号量后再次读取数据 */
rt_sem_take(&gy39_rx_sem, RT_WAITING_FOREVER);
}
switch (frame_step){
case FRAME_STEP_START1:{
offset = 0;
if(ch == FRAME_START){ //第一个帧头
buffer[offset++] = ch;
frame_step = FRAME_STEP_START2;
}
break;
}
case FRAME_STEP_START2:{
if(ch == FRAME_START){ //第二个帧头
buffer[offset++] = ch;
frame_step = FRAME_STEP_TYPE;
}else{
frame_step = FRAME_STEP_START1;
}
break;
}
case FRAME_STEP_TYPE:{
if((ch==FRAME_TYPE_LUX)||(ch==FRAME_TYPE_TEMP_HUM_AP)||(ch==FRAME_TYPE_IIC)){ //正确的帧数据类型
buffer[offset++] = ch;
frame_step = FRAME_STEP_DATALEN;
}else{ //帧数据类型错误,丢弃
frame_step = FRAME_STEP_START1;
}
break;
}
case FRAME_STEP_DATALEN:{
if(ch <= FRAME_DATA_MAXLEN){ //一帧中数据的最大长度
buffer[offset++] = ch;
frame_data_length = ch;
frame_step = FRAME_STEP_DATA;
}else{ //数据长度 错误,丢弃
frame_step = FRAME_STEP_START1;
}
break;
}
case FRAME_STEP_DATA:{
buffer[offset++] = ch;
frame_data_length--;
if(frame_data_length==0){ //数据已读完,进入下一段
frame_step = FRAME_STEP_CHECK_ANALYSE;
}
break;
}
case FRAME_STEP_CHECK_ANALYSE:{ //校验和
buffer[offset] = ch;
if(gy39_checksum(buffer,FRAME_DATALEN_POS+1+buffer[FRAME_DATALEN_POS])==ch){
switch(buffer[FRAME_TYPE_POS]){
case FRAME_TYPE_LUX:{
uint32_t temp = (buffer[FRAME_LUX_POS]<<24)|(buffer[FRAME_LUX_POS+1]<<16)|(buffer[FRAME_LUX_POS+2]<<8)|buffer[FRAME_LUX_POS+3];
GY39_Info.lux = (double)(temp*1.0/100);
break;
}
case FRAME_TYPE_TEMP_HUM_AP:{
uint32_t temp = (buffer[FRAME_TEMP_POS]<<8)|buffer[FRAME_TEMP_POS+1];
GY39_Info.temperature = (double)(temp*1.0/100);
temp = (buffer[FRAME_AP_POS]<<24)|(buffer[FRAME_AP_POS+1]<<16)|(buffer[FRAME_AP_POS+2]<<8)|buffer[FRAME_AP_POS+3];
GY39_Info.atmos_pressure = (double)(temp*1.0/100);
temp = (buffer[FRAME_HUM_POS]<<8)|buffer[FRAME_HUM_POS+1];
GY39_Info.humidity = (double)(temp*1.0/100);
temp = (buffer[FRAME_ALTITUDE_POS]<<8)|buffer[FRAME_ALTITUDE_POS+1];
GY39_Info.altitude = (double)(temp*1.0);
break;
}
case FRAME_TYPE_IIC:{
GY39_Info.IIC_address = buffer[FRAME_IIC_POS];
break;
}
}
frame_step = FRAME_STEP_START1;
}else{
frame_step = FRAME_STEP_START1;
}
break;
}
}
}
}
int gy39_module_uart3_startup()
{
rt_err_t ret = RT_EOK;
init_GY39_Info();
/* 查找系统中的串口设备 */
gy39_serial = rt_device_find(GY39_UART);
if (!gy39_serial){
rt_kprintf("find GY39_UART failed!\n");
return RT_ERROR;
}
/* 初始化信号量 */
rt_sem_init(&gy39_rx_sem, "gyrx_sem", 0, RT_IPC_FLAG_FIFO);
/* 以中断接收及轮询发送模式打开串口设备 */
rt_device_open(gy39_serial, RT_DEVICE_FLAG_INT_RX);
/* 设置接收回调函数 */
rt_device_set_rx_indicate(gy39_serial, uart3rxd_callback);
/* 创建 gy39_serial 线程 */
rt_thread_t thread = rt_thread_create("gy39_serial", gy39_thread_entry, RT_NULL, 1024, 25, 10);
/* 创建成功则启动线程 */
if (thread != RT_NULL) {
rt_thread_startup(thread);
}else{
ret = RT_ERROR;
}
return ret;
}
//外部获取GY39模块的各个信息
double gy39_get_lux()
{
return GY39_Info.lux;
}
double gy39_get_temperature()
{
return GY39_Info.temperature;
}
double gy39_get_humidity()
{
return GY39_Info.humidity;
}
double gy39_get_atmos_pressure()
{
return GY39_Info.atmos_pressure;
}
double gy39_get_altitude()
{
return GY39_Info.altitude;
}
uint8_t gy39_get_IIC_address()
{
return GY39_Info.IIC_address;
}
3.3、main
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "gy39.h"
#define LED0_PIN GET_PIN(F, 9)
int main(void)
{
//设置LED为推挽输出
rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);
//开启GY39模块
gy39_module_uart3_startup();
while (1)
{
rt_pin_write(LED0_PIN, PIN_HIGH); //设置高电平
rt_thread_mdelay(500);
rt_pin_write(LED0_PIN, PIN_LOW); //设置低电平
rt_thread_mdelay(500);
//1s打印一次GY39模块的信息
printf_GY39_Info();
}
}
4、测试到的结果
这是刚刚测的结果,温度22摄氏度,光照强度869lux,湿度71%,大气压强101308Pa,海拔1m。
这是我手机上的天气预报,温度22摄氏度,湿度72%,压强1016hPa.?这两个的温湿度差不多的,压强不太一样。
?
标准大气压强是101325Pa,而现在测出来的大气压强是101308Pa,又根据海拔越高气压越低,模块计算出海拔1m大概差不多。
我们自己算一下,按照海拔每上升12m,大气压减少133Pa来计算,是0.09m/Pa。海拔=(101325-101308)*0.09=1.53m,另外这个模块海拔的输出单位是m所以是没有小数的。
大气压强受诸多因素的影响,每天有日变化,除了海拔还有空气的温湿度、密度、纬度等等吧,如果压强不准海拔肯定不准,就算压强准确,也不能以压强来计算海拔,所以这个海拔可以用很不准,不能用来形容,刚才那个还行,但是昨晚我测出来压强是10858Pa,手机上显示是1019hPa,压强差不多没错吧,但是按照这样计算海拔出又是48米,模块读出的数据是64592,卖家的数据说明如下。
下面是卖家的软件测出来的数据,海拔数据在0m和-1之间来回跳,他的“收码显示”里海拔那两个字节是00 00或者FF FF,所以他海拔的数值是有符号的但是说明书里没写,FFFF是以补码的形式存储就是-1,但是64592转换成负数是-944,还是不对劲嘛。
这个上位机测海拔,有时候还跳到负几十米,算了,本来海拔这样计算就不准的,不纠结这个了。
|