前阵子开源了一个基于TencentOS tiny
物联网操作系统的危险气体探测仪项目,这次,我们再来开源一个新的项目 - 甲醛检测仪,但是做项目之前,有必要了解下接下来要做的一些模块以及如何来进行集成。
1、简介
WZ-S 型甲醛检测模组是英国达特公司开发的,是用于将环境中甲醛的含量转换成浓度值,标准化数字输出,便于系统集成。
2、特点
3、典型应用场景
4、硬件引脚及技术指标
5、传感器通讯协议
该传感器采用的是串行通讯方式,也就是我们常用的串口,串口配置参数如下:
波特率:9600
数据位:8 位
停止位:1 位
校验位:无
传感器在出厂后默认为主动上报,每隔 1s 上报一次浓度值,命令行格式如下:
一般情况下我们直接拿来用即可。
6、软件编程(以 STM32 为例)
以下开发板为 TOS_EVB_GO 开发板,也就是前阵子 TencentOS 公众号发表的一篇文章的那个,链接如下:
基于 TencentOS Tiny 接入腾讯连连微信小程序,打造您自己的智能家居产品
TOS_EVB_G0 开发板是由腾讯 TencentOS-tiny 团队设计的一款物联网开发板,板载资源如下:
- 主控芯片采用 STM32G070RB,Flash 空间仅有 128KB、RAM 空间仅有 20KB;板载腾讯云定制固件版 ESP8266 WIFI 模组;板载 E53 传感器标准接口,方便连接各种 E53 传感器;板载 0.91'OLED 显示屏幕;板载 8MB SPI Flash,可用于固件升级;板载 CH340 转串口连接,可以使用一根 USB 线连接至电脑,查看串口日志;
基于该开发板编写的达特传感器驱动例程位于:
https://gitee.com/morixinguan/bear-pi.git
以上拓展模块是腾讯基于 E53 接口设计的一个传感器模块,所以小熊派也是支持的,如下:
由于在小熊派上使用比较顺手,所以现在我已经对它爱不释手了,无论是工作做实验还是平时练习,以下配置、编程基于小熊派开发板。
6.1、STM32CubeMX 关于传感器的配置
配置 DMA 接收,个人习惯 DMA+空闲中断的方式。
6.2、其它配置
6.2.1 时钟
6.2.2 SWD 调试口
6.2.3 调试串口
6.2.4 SPI OLED 配置
其余的部分直接复用之前文章的一些接口即可,然后生成工程:
6.3 程序编写
在程序编写之前先来了解一些基本的概念,有助于我们后面产品的实现。
(1)ppm、ppb、ppt 是什么?
表达溶液的浓度时,1ppm=1ug/mL;表达固体中成分含量时,1ppm 即为 1ug/g 或 1g/t。
所以 1ppb=1ppm 的千分之一,ppm 即百万分之一,ppb 即 1 亿分之一,ppt 即千亿分之一。
所以 ppm 是 10 的 -6 次方,ppb 是 10 的 -9 次方,ppt 是 10 的 -12 次方
(2)浓度及浓度单位换算
1ppm = 1000ppb
1ppb = 1000ppt
ppm 即:mg/L(毫克 / 升)
ppm 即:mg/L(毫克 / 升)
ppm 即:mg/L(毫克 / 升)
6.3.1 达特传感器通讯协议解析
由于达特甲醛传感器出厂时固定是发 9 个字节,所以我们可以直接用下面这个结构体来表示:
/*甲醛传感器协议*/
typedef struct
{
/*起始位*/
uint8_t start_bit ;
/*气体名称*/
uint8_t gas_name ;
/*单位*/
uint8_t unit ;
/*小数位数*/
uint8_t decimal_places ;
/*气体浓度高位*/
uint8_t gas_density_high ;
/*气体浓度低位*/
uint8_t gas_density_low ;
/*满量程高位*/
uint8_t full_range_high ;
/*满量程低位*/
uint8_t full_range_low ;
/*校验值*/
uint8_t checksum_value ;
}Dart_Sensor_Procol_TypeDef ;
针对以上结构体我们很容易根据官方手册说明写出如下解析函数:
Dart_Sensor_Procol_TypeDef Dart_Sensor_Data_Parse(uint8_t *Data)
{
uint16_t temp = 0 ;
uint16_t check_sum = 0 ;
uint16_t check_sum_negate = 0 ;
Dart_Sensor_Procol_TypeDef dart_sensor ;
/*将接收到的协议数据直接转到结构体里进行存储*/
memcpy(&dart_sensor,Data,sizeof(Dart_Sensor_Procol_TypeDef));
/*计算校验值*/
check_sum = dart_sensor.gas_name + dart_sensor.unit +
dart_sensor.gas_density_low + dart_sensor.gas_density_high +
dart_sensor.full_range_high + dart_sensor.full_range_low + dart_sensor.decimal_places ;
check_sum_negate = ~check_sum ;
temp = check_sum_negate + 1 ;
if((temp & 0xff) != dart_sensor.checksum_value)
{
memset(&dart_sensor,0,sizeof(Dart_Sensor_Procol_TypeDef));
return dart_sensor ;
}
return dart_sensor ;
}
关于这个 Data 是怎么直接转结构体的,可以参考我的一位朋友邓工最近发表的一篇文章,里面图文并茂的说明了这种骚操作,文章链接如下,点击即可跳转:
【进阶】"结构体嵌入共联体"在协议解析中的神操作!
在用户层次,用户不需要关心协议是怎么解析的,所以我们只需要给用户提供一个获取数据的结构体和函数即可,然后通过头文件dart_sensor.h
提供给用户,而协议解析部分直接放在dart_sensor.c
文件里就可以了,如下:
#ifndef __DART_SENSOR_H
#define __DART_SENSOR_H
#include
#include
/*
1ppm = 1000ppb
1ppb = 1000ppt
ppm = mg/L(毫克 / 升)
ppb = ug/L(微克 / 升)
ppt = ng/L(纳克 / 升)
*/
typedef struct
{
/*气体浓度*/
float gas_density ; //ppm
/*满量程*/
float full_range ;
}Dart_Sensor ;
Dart_Sensor Get_Dart_Sensor_Density(uint8_t *Data);
#endif //__DART_SENSOR_H
获取浓度Get_Dart_Sensor_Density
函数的实现:
我看过的大多数浓度单位标识都是 ppm,也就是 xxx/mg/L 的这种表示方法,所以这个接口就设计成下面这样。
/*
获取气体浓度
Data: 传感器数据
return: xxx ppm
*/
Dart_Sensor Get_Dart_Sensor_Density(uint8_t *Data)
{
Dart_Sensor sensor ;
Dart_Sensor_Procol_TypeDef dart_sensor ;
dart_sensor = Dart_Sensor_Data_Parse(Data);
/*计算浓度,单位为 ppm*/
sensor.gas_density = ((dart_sensor.gas_density_high << 8) + (dart_sensor.gas_density_low))/1000.0 ;
/*当前传感器量程*/
sensor.full_range = ((dart_sensor.full_range_high << 8) + (dart_sensor.full_range_low))/1000.0 ;
return sensor ;
}
6.3.2 达特传感器通讯库封装
既然用户不需要关心过程,那我们可以给这个简单的解析过程做一个 lib,这样就相当于一个模块,提供 .h 和 .lib 即可,接下来建立一个 STM32L431 的工程,然后将 .c 和 .h 放在一个文件夹内,通过 Keil 包含进来
然后在 Output 下选择创建库,接下来点击编译即可生成:
注意,这里建立的这个库仅在该环境下适用。思考一下,如何做到平台通用呢?
6.3.3 案例编写
(1)开启串口空闲中断
/*开启空闲中断*/
__HAL_UART_ENABLE_IT(uartHandle, UART_IT_IDLE);
// 开启 DMA 接收
memset(sensor_handler.SensorU3Buffer, 0, SENSOR_U3_BUFFER_SIZE);
HAL_UART_Receive_DMA(&huart3, (uint8_t*)sensor_handler.SensorU3Buffer, SENSOR_U3_BUFFER_SIZE);
(2)串口空闲中断处理 串口接收数据结构:
// 固定 9 个字节
#define SENSOR_U3_BUFFER_SIZE 9
typedef struct
{
/*表示接收到了*/
uint8_t BufferReady;
/*数据缓存区*/
uint8_t SensorU3Buffer[SENSOR_U3_BUFFER_SIZE];
}Sensor_HandleTypeDef;
extern Sensor_HandleTypeDef sensor_handler ;
以下是数据采集过程,非常简单:
/**
* @brief This function handles USART3 global interrupt.
*/
void USART3_IRQHandler(void)
{
/* USER CODE BEGIN USART3_IRQn 0 */
if(RESET != __HAL_UART_GET_FLAG(&huart3, UART_FLAG_IDLE))
{
__HAL_UART_CLEAR_IDLEFLAG(&huart3);
HAL_UART_DMAStop(&huart3);
sensor_handler.BufferReady = 1 ;
}
/* USER CODE END USART3_IRQn 0 */
HAL_UART_IRQHandler(&huart3);
/* USER CODE BEGIN USART3_IRQn 1 */
/* USER CODE END USART3_IRQn 1 */
}
当接收到空闲中断时,代表数据已经接收到了,此时sensor_handler.BufferReady
置 1,代表数据已经接收完成。
(3)数据解析处理与应用逻辑 在 while 循环中编写如下代码:
while (1)
{
/*接收到一帧数据*/
if(1 == sensor_handler.BufferReady)
{
/*接收标志位清 0*/
sensor_handler.BufferReady = 0 ;
/*判断包头数据是否正确*/
if(sensor_handler.SensorU3Buffer[0] == 0xFF && sensor_handler.SensorU3Buffer[1] == 0x17)
{
// 调用解析函数
sensor = Get_Dart_Sensor_Density(sensor_handler.SensorU3Buffer);
/*业务逻辑开始*/
sprintf(display_buf, "%.3fmg/L", sensor.gas_density);
LCD_ShowCharStr(70, 100, 170, display_buf, BLACK, WHITE, 24);
/*业务逻辑结束*/
// 重新打开 DMA 继续接收新的一帧数据
memset(sensor_handler.SensorU3Buffer, 0, SENSOR_U3_BUFFER_SIZE);
HAL_UART_Receive_DMA(&huart3, (uint8_t*)sensor_handler.SensorU3Buffer, SENSOR_U3_BUFFER_SIZE);
}
}
}
如何判断接收到的这帧数据到底对不对呢?我们只需要根据协议手册判断前两个字节是否为 0xff 和 0x17 即可。
7、运行结果
8、指标衡量
模块咱们用起来了,如何判断来衡量甲醛含量的技术指标呢?下面这张图截的是淘宝上某个商家对检测标准的说明:
9、思考与练习
前面我开源了一个基于TencentOS tiny
的危险气体探测仪项目,是否能在那个项目上稍微改改,变成一个新的产品级项目,让一个新项目:甲醛探测仪迅速开发出来呢?
淘宝上其实已经有很多优秀的产品案例,如下所示,界面做得相当漂亮了:
是否能做出一个跟以上界面类似的开源项目呢?
如下图所示,小熊派开源生态社区工作小组的阿正大佬已经做出来了一个类似的作品,给他点赞!
同时也希望更多热爱开源的小伙伴加入我们的小熊派开源生态社区工作小组,该工作小组为高质量社区,不同于一般群,只玩技术不闲聊,不接受潜水大佬,所以人不在多而在于精;无论小伙伴们玩的是什么平台(不局限于小熊派),只要是热爱开源,有创意有想法,乐于持续分享,且目前在码云 /Github 等社区有作品的玩家即可(私聊我的微信,拉你入群)
本节代码已同步到码云的代码仓库中,获取方法如下:
1、新建一个文件夹
2、使用 git clone 远程获取小熊派例程存放的代码仓库
项目开源仓库:
https://gitee.com/morixinguan/bear-pi.git
我还将之前做的一些项目以及练习例程在近期内全部上传完毕,与大家一起分享交流: