RT_Thread_串口数据解包

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,还是不对劲嘛。

这个上位机测海拔,有时候还跳到负几十米,算了,本来海拔这样计算就不准的,不纠结这个了。

  • 5
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值