#申请原创#@21小跑堂
一、项目概述
现代社会,由于长期在室内活动,人们越来越关注室内空气质量。衡量室内空气质量的主要指标有:二氧化碳浓度、TVOC(总挥发性有机物)浓度、温度、湿度。研究表明:当室内CO2浓度超过1000 ppm时,人体就会出现困倦、呼吸急促、注意力缺乏等症状,当浓度逐渐升高未及时处理,还会造成脑损伤、昏倒甚至丧生。当TVOC浓度为3.0-25 mg/m3时,会产生刺激和不适,与其他因素联合作用时,可能出现头痛;当TVOC浓度大于25 mg/m3时,除头痛外,可能出现其他的神经毒性作用。温度和湿度则是最基本的环境指标,过高或过低的温湿度都会造成然提感到不适。
通风换气是改善空气质量的最简单有效的办法之一,TVOC、CO2、温读和湿度等环境监测数据可用于配置空气净化系统或智能管理通风系统运行模式,从而节省能耗,达到节能减排的目的,助力双碳目标实现。本文基于CH32V307开发板采集TVOC、CO2、温读和湿度四项环境数据,通过WIFI无线网络接入腾讯云物联网平台IoT Explorer,实现了在腾讯连连小程序上能够实时监测室内空气质量的应用。
二、系统结构
系统采用CH32V307作为控制核心,esp8266无线wifi模块用于和腾讯云物联网平台通信,SGP30模块用于采集TVOC、CO2浓度数据、SHT40模块用于采集温湿度数据,led和按键用于人机交互,用户可通过微信小程序实时查看监测数据。系统结构如图所示:
三、硬件介绍
3.1 CH32V307开发板
CH32V307是沁恒微电子基于32位RISC-V设计的高性能、低功耗互联型微控制器,产品特点:
青稞V4F处理器,最高144MHz系统主频
支持单周期乘法和硬件除法,支持硬件浮点运算(FPU)
64KB SRAM,256KB Flash
供电电压:2.5/3.3V,GPIO单元独立供电
多种低功耗模式:睡眠、停止、待机
上/下电复位、可编程电压检测器
2组18路通用DMA
4组运放比较器
1个随机数发生器TRNG
2组12位DAC转换
2单元16通道12位ADC转换,16路触摸按键TouchKey
10组定时器
USB2.0全速OTG接口
USB2.0高速主机/设备接口(480Mbps 内置PHY)
3个USART接口和5个UART接口
2个CAN接口(2.0B主动)
SDIO接口、FSMC接口、DVP数字图像接口
2组IIC接口、3组SPI接口、2组IIS接口
千兆以太网控制器ETH(内置10M PHY)
80个I/O口,可以映射到16外部中断
CRC计算单元,96位芯片唯一ID
串行2线调试接口
本次项目采用沁恒微电子官方推出的开发板,如下图所示:
3.2 SGP30传感器
SGP30是Sensirion推出的一款I2C接口低功耗、高精度多气体传感器(VOC和CO₂eq),主要参数规格如下:
本次项目采用模组如下图所示,集成了电源管理和电平转换电路:
3.3 SHT40传感器
SHT40是Sensirion推出的一款I2C接口低功耗、高精度数字温湿度传感器,主要参数规格如下:
本次项目采用模组,集成电源和电平转换:
3.4 ESP8266 WIFI
esp8266是乐鑫推出的面向物联网应用的高性价比、高度集成的 Wi-Fi MCU,本次使用模组,引出了通信串口,如图所示:
3.5 系统硬件连接
ESP8266模块连接到开发板串口2(管脚PA2、PA3),SGP30连接到开发板PA4、PA5,SHT40连接到开发板PB10、PB11,如下图所示:
四、项目亮点
支持腾讯连连小程序查看监测数据(CO2浓度、TVOC浓度、温度和湿度);
支持腾讯云IoT Explorer平台实时查看上报数据信息(CO2浓度、TVOC浓度、温度和湿度);
支持腾讯连连微信公众号信息推送(CO2浓度、TVOC浓度、温度和湿度超标告警);
采用腾讯云可视化编辑器自定义腾讯连连小程序界面;
小程序界面展示如下图所示:
五、系统实现
系统实现分为3个部分,一是腾讯云平台配置,二是ESP8266腾讯云固件烧录,三是MCU端程序设计。
5.1 腾讯云平台配置
首先需要在腾讯物联网开发平台创建项目:
这里建立一个名为环境监测的项目:
然后在该项目下面点击新建产品按钮,新建一个产品:
一个项目下面可以新建多个产品,这里建立名为室内二氧化碳监测的产品,设备类型为:设备,因为项目使用的是esp8266 wifi模块,因此通信方式选择:Wi-Fi,认证方式选择:秘钥认证,数据协议选择:物模型
建立好产品后,会看到产品的基本信息:
然后根据本项目需要实现的功能进行功能定义,腾讯云平台采用了物模型协议将现实物理世界中的设备功能进行数字化定义,便于应用更便利的管理设备。
在产品定义物模型后,设备可以根据物模型中的定义上报属性、事件,并可对设备下发控制指令,物模型协议主要包括2部分:
设备属性上报:设备端将定义的属性根据设备端的业务逻辑向云端上报。
设备远程控制:从云端向设备端下发控制指令,即从云端设置设备的可写属性。
新建自定义功能:
功能类型:属性
功能名称:二氧化碳
标识符:co2
数据类型:整型
读写类型:只读(读写表示该属性即可从设备端上报到云端也可从云端发起控制,只读表示该属性只从设备向云端上报)
数据范围等参数根据前文SGP30传感器芯片参数填写即可:
同样的方式,再新建TVOC、温度、湿度等其它自定义功能:
最后,建立好的自定义功能如下所示:
查看物模型json格式数据如下:
建立好产品后,在进行小程序界面和MCU程序开发前,可以使用虚拟在线调试测试设计的正确性:
然后配置小程序界面,在交互设计选项卡选择面板配置:
小程序界面支持3种配置方案,这里使用傻瓜化的可视化面板:
可以通过简单的拖、拽控件、关联物模型数据等方式设计自定义的小程序界面,十分方便小白使用。以CO2数据显示为例,新增一个信息展示控件,然后关联物模型co2:
同样的方式设计其它控件:
除了数据展示功能外,还能配置告警消息推送功能:
通过拖曳控件的方式完成数据流配置:
设定数据过滤规则和阈值:
设定报警推送消息内容:
定义的报警信息如下:
5.2 ESP8266腾讯云固件烧录
esp8266模块烧写腾讯云定制固件后,就能连接到腾讯云平台,实现与腾讯云平台的数据交互,能够实现WIFI账号密码配置、MQTT连接、订阅、发布等功能。
乐鑫官网提供esp8266腾讯云AT固件:
https://docs.espressif.com/projects/esp-at/zh_CN/release-v2.2.0.0_esp8266/Customized_AT_Commands_and_Firmware/Tencent_Cloud_IoT_AT/index.html
注意烧录2.2.0.0版本,实测2.3版本不能顺利连接腾讯云。
使用乐鑫提供的esptool工具烧录即可:
5.3 MCU端程序设计
MCU端软件基于腾讯物联网终端操作系统TencentOS tiny构建,TencentOS tiny提供了ch32v307的板级支持包和丰富的示例程序,MCU端软件实现传感器数据采集、无线数据接入腾讯云IoT Explorer,从而设备联网控制与数据交互功能,结构如下图所示:
设备端主要完成ch32v307的外设初始化、传感器初始化、wifi初始化、协议栈初始化、传感器数据采集、mqtt协议数据发送等功能。基于TencentOS-Tiny物联网操作系统开发,设计了3个线程,定时采集传感器数据,控制线程,mqtt发送线程将数据通过腾讯云mqtt协议发送到云平台。
软件功能简要流程如图所示:
开发板提供了常用外设的驱动程序,与云平台的交互基于例子mqtt_iot_explorer.c进行开发。主函数main创建三个线程来进行不同功能的处理,线程间采用TencentOS-tiny提供的消息队列进行通知和数据交换,实现CPU资源高效利用。线程间传递的消息队列中的数据设计如下:
typedef struct
{
int tvoc;//tvoc浓度
int co2;//co2浓度
float tem;//温度
float hum;//湿度
bool fan;//风扇启用标志
bool sleep;//睡眠模式启用标志
}msg_t;
main.c内容如下,主要完成初始化内核、创建消息队列、创建线程、启动内核功能,需要注意的是,由于使用了printf打印浮点数,需要使用__aligned(8)(8字节对齐),否则将出现异常:
#include "debug.h"
#include "tos_k.h"
#define MQTT_TASK_STK_SIZE 2048
k_task_t mqtt_task;
__aligned(8) uint8_t mqtt_task_stk[MQTT_TASK_STK_SIZE];
#define CONTROL_TASK_STK_SIZE 4096
k_task_t control_task;
__aligned(8) uint8_t control_task_stk[CONTROL_TASK_STK_SIZE];
#define DATA_COLLECT_TASK_STK_SIZE 4096
k_task_t data_collect_task;
__aligned(8) uint8_t data_collect_task_stk[DATA_COLLECT_TASK_STK_SIZE];
extern void mqtt_entry(void *arg);
extern void control_entry(void *arg);
extern void data_collect_entry(void *arg);
k_msg_q_t msg_q;
uint8_t DataMsgPool[100];
int main(void)
{
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d\r\n",SystemCoreClock);
printf("Welcome to TencentOS tiny(%s)\r\n", TOS_VERSION);
tos_knl_init();
tos_msg_q_create(&msg_q, DataMsgPool, 100);
tos_task_create(&mqtt_task, "mqtt_task", mqtt_entry, NULL, 4, mqtt_task_stk, MQTT_TASK_STK_SIZE, 0);
tos_task_create(&control_task, "control_task", control_entry, NULL, 5, control_task_stk, CONTROL_TASK_STK_SIZE, 0);
tos_task_create(&data_collect_task, "data_collect_task", data_collect_entry, NULL, 6, data_collect_task_stk, DATA_COLLECT_TASK_STK_SIZE, 0);
tos_knl_start();
printf("should not run at here!\r\n");
while(1);
}
数据采集线程实现对SGP30、SHT40传感器的初始化,定时将采集数据放入消息队列:
void data_collect_entry(void *arg)
{
uint16_t TVOC = 0, CO2 = 0;
uint8_t ID[6]={0};
while(sgp30_init() < 0)
{
printf(" sgp30 init error\r\n");
tos_sleep_ms(1000);
}
if(sgp30_get_serial_id(ID) < 0)
{
printf(" sgp30 read serial id error\r\n");
}else
{
printf("SGP30 Serial number: ");
for(int i = 0; i < 6; i++) printf("%02X", ID[i]);
printf("\r\n");
}
// SGP30模块开机需要一定时间初始化,在初始化阶段读取的CO2浓度为400ppm,TVOC为0ppd且恒定不变。
printf("wait for sgp30 init");
fflush (stdout);
do
{
if(sgp30_read(&CO2, &TVOC) < 0)
{
printf("spg30 read error\r\n");
}
else
{
printf("-");
fflush (stdout);
}
tos_sleep_ms(500);
}while(TVOC == 0 && CO2 == 400);
printf("sgp30 init ok\r\n");
int16_t error = 0;
sensirion_i2c_hal_init();
uint32_t serial_number;
int32_t temperature;
int32_t humidity;
error = sht4x_serial_number(&serial_number);
if (error)
{
printf("Error executing sht4x_serial_number(): %i\r\n", error);
}
else
{
printf("SHT40 Serial number: %u\r\n", serial_number);
}
msg_t msg={0};
while (1)
{
if(sgp30_read(&CO2, &TVOC) < 0)
{
printf(" sgp30 read error\r\n");
}
else
{
printf("CO2:%5dppm TVOC:%5dppb\r\n", CO2, TVOC);
}
error = sht4x_measure_high_precision(&temperature, &humidity);
if (error)
{
printf("Error executing sht4x_measure_high_precision(): %i\n",error);
}
else
{
msg.tvoc = TVOC;
msg.co2 = CO2;
msg.tem = temperature / 1000.0f;
msg.hum = humidity / 1000.0f;
tos_msg_q_post(&msg_q,(void*)&msg);
printf("Temperature:%0.2f℃,Humidity:%0.2f %%RH\n",temperature / 1000.0f, humidity / 1000.0f);
}
tos_sleep_ms(3000);
}
}
对接云平台,修改mqtt_iot_explorer.c文件开头的三元组信息产品 ID(ProductId)、设备名(DeviceName)、设备密钥(DeviceSecret)其中设备密钥由平台生成:
#define PRODUCT_ID "test"
#define DEVICE_NAME "test"
#define DEVICE_KEY "test"
然后修改要接入WIFI的名称和密码:
esp8266_tencent_firmware_join_ap("ssid", "passwd");
mqtt线程首先完成esp8266初始化、wifi协议栈初始化、mqtt订阅主题等功能。当接收到消息后,将采集数据依据消息发布使用的物模型Topic打包,然后通过MQTT发送出去。属性上报主题格式如下所示:
$thing/up/property/${deviceID}/${deviceName}
根据腾讯云平台配置的物模型将数据打包为json格式上传,消息体json格式如下:
#define REPORT_DATA_TEMPLATE "{\\\"method\\\":\\\"report\\\"\\,\\\"clientToken\\\":\\\"00000001\\\"\\,\\\"params\\\":{\\\"tvoc\\\":%d\\,\\\"co2\\\":%d\\,\\\"fan\\\":%d\\,\\\"sleep\\\":%d\\,\\\"temperature\\\":%0.1f\\,\\\"humidity\\\":%0.1f\\}}"
通过snprintf函数将两者连接起来:
memset(report_topic_name, 0, sizeof(report_topic_name));
size = snprintf(report_topic_name, TOPIC_NAME_MAX_SIZE, "$thing/up/property/%s/%s", product_id, device_name);
将传感器数据格式化输入到payload:
memset(payload, 0, sizeof(payload));
snprintf(payload, sizeof(payload), REPORT_DATA_TEMPLATE, tvoc,co2,0,0,tem,hum);
最后调用
tos_tf_module_mqtt_pub(report_topic_name, QOS0, payload)
函数实现MQTT消息的发布。
推送传感器数据到腾讯云物联网开发平台IoT Explorer的mqtt线程如下:
void mqtt_demo_task(void)
{
int ret = 0;
int size = 0;
mqtt_state_t state;
char *product_id = PRODUCT_ID;
char *device_name = DEVICE_NAME;
char *key = DEVICE_KEY;
device_info_t dev_info;
memset(&dev_info, 0, sizeof(device_info_t));
/**
* Please Choose your AT Port first, default is HAL_UART_2(USART2)
*/
ret = esp8266_tencent_firmware_sal_init(HAL_UART_PORT_2);
if (ret < 0) {
printf("esp8266 tencent firmware sal init fail, ret is %d\r\n", ret);
}
esp8266_tencent_firmware_join_ap("HUAWEI-DQL", "dql8837150");
strncpy(dev_info.product_id, product_id, PRODUCT_ID_MAX_SIZE);
strncpy(dev_info.device_name, device_name, DEVICE_NAME_MAX_SIZE);
strncpy(dev_info.device_serc, key, DEVICE_SERC_MAX_SIZE);
tos_tf_module_info_set(&dev_info, TLS_MODE_PSK);
mqtt_param_t init_params = DEFAULT_MQTT_PARAMS;
if (tos_tf_module_mqtt_conn(init_params) != 0) {
printf("module mqtt conn fail\n");
} else {
printf("module mqtt conn success\n");
}
if (tos_tf_module_mqtt_state_get(&state) != -1) {
printf("MQTT: %s\n", state == MQTT_STATE_CONNECTED ? "CONNECTED" : "DISCONNECTED");
}
size = snprintf(report_reply_topic_name, TOPIC_NAME_MAX_SIZE, "$thing/down/property/%s/%s", product_id, device_name);
if (size < 0 || size > sizeof(report_reply_topic_name) - 1) {
printf("sub topic content length not enough! content size:%d buf size:%d", size, (int)sizeof(report_reply_topic_name));
}
if (tos_tf_module_mqtt_sub(report_reply_topic_name, QOS0, default_message_handler) != 0) {
printf("module mqtt sub fail\n");
} else {
printf("module mqtt sub success\n");
}
memset(report_topic_name, 0, sizeof(report_topic_name));
size = snprintf(report_topic_name, TOPIC_NAME_MAX_SIZE, "$thing/up/property/%s/%s", product_id, device_name);
if (size < 0 || size > sizeof(report_topic_name) - 1)
{
printf("pub topic content length not enough! content size:%d buf size:%d", size, (int)sizeof(report_topic_name));
}
void *MsgRecv;
msg_t msg;
int tvoc;
int co2;
float tem;
float hum;
while (1)
{
//GPIO_SetBits(GPIOE,GPIO_Pin_5);
//tos_sleep_ms(2000);
//GPIO_ResetBits(GPIOE,GPIO_Pin_5);
tos_msg_q_pend(&msg_q, &MsgRecv, TOS_TIME_FOREVER);
memcpy(&msg,MsgRecv,sizeof(msg_t));
tos_mutex_pend(&mutex);
printf("tvoc:%d co2:%d temp:%d hum:%d\r\n",msg.tvoc,msg.co2,msg.tem,msg.hum);
tos_mutex_post(&mutex);
/* use AT+PUB AT command */
tvoc = msg.tvoc;
co2 = msg.co2;
tem = msg.tem;
hum = msg.hum;
memset(payload, 0, sizeof(payload));
snprintf(payload, sizeof(payload), REPORT_DATA_TEMPLATE, tvoc,co2,0,0,tem,hum);
if (tos_tf_module_mqtt_pub(report_topic_name, QOS0, payload) != 0)
{
printf("module mqtt pub fail\n");
break;
}
else
{
printf("module mqtt pub success\n");
}
}
}
程序运行正常的情况下,在云平台调试界面就可以看到设备发送的数据:
进入腾讯连连小程序连接设备后也能看到之前设计的小程序界面:
小程序收到超标告警消息:
六、视频演示
【CH32V307&tencentos-tiny接入腾讯云平台小程序数据采集展示】 https://www.bilibili.com/video/BV1xD4y177yd/?share_source=copy_web&vd_source=4b9c499d8564e7ad9169a2c39fa19a17
七、总结通过本项目体验了国产MCU、国产RTOS、国产IDE生态,了解了沁恒RISC-V内核微控制器 CH32V307VCT6应用、了解了国产物联网操作系统TencentOS-tiny和国产集成开发工具MounRiver Studio,感受到了国货的崛起。 由于本人能力水平不足,文章写的有误或者不清楚的地方敬请见谅。 八、工程源码由于大小限制,替换TencentOS-tiny\board\TencentOS_Tiny_CH32V307_EVB即可。
|