1、方案概述
近年来,清洁的室内空气成为诸多重视健康生活人士的新需求。评价室内空气质量的重要指标有2个:VOC(挥发性有机化合物):VOC 是在室温或更高温度下蒸发的含碳物质。短期接触会导致刺激、头晕或哮喘恶化;长期接触则可能会导致肺癌或损害肝脏、肾脏或神经系统。温湿度:40-60%的相对湿度是人类理想的舒适度范围。极度干燥的空气会刺激呼吸道,而过分潮湿的空气会导致冷凝,进而引发霉菌滋生,其他影响可能包括头痛甚至偏头痛。改善室内空气质量有两种方式:通风和净化。对于家庭或小型封闭空间而言,如果周围的室外空气干净,理想选择是打开窗户或使用智能通风系统进行通风。室内空气质量的监测数据可用于配置空气净化系统或智能管理通风系统,本文基于CH32V307开发板利用腾讯云物联网平台IoT Explorer 和腾讯连连小程序开发了能够实时监测室内空气质量的应用。
2、系统结构
系统采用CH32V307作为控制核心,esp8266无线wifi模块用于和腾讯物联网平台通信,LCD用于实时显示传感器监测数据,svm40模块用于采集室内VOC指数、温湿度,led和按键用于人机交互,用户可通过微信小程序实时查看监测数据。系统结构如图所示:
3、硬件介绍
3.1 SVM40
SVM40是Sensirion基于VOC传感器SGP40和数字温湿度传感器SHT40的传感器模块。SGP40主要参数如下:
SHT40主要参数如下:
SVM40模块内置了一颗STM32G0微控制器,通过I2C接口连接2颗传感器,Sensirion自定义了通信协议,模块对外提供串口和I2C接口,模块结构如图所示:
模块提供I2C和UART两种接口,本文采用UART接口,模块引脚含义如图所示:
3.2 CH32V307_EVB_AIOT
- 内置TencentOS Tiny开源物联网操作系统
- 开发板采用沁恒RISC-V MCU CH32V307VCT6芯片,CH32V305/7系列是基于沁恒自研RISC-V架构微处理器青稞V4系列设计的32位工业级互联型微控制器,配备了硬件堆栈区、快速中断入口,在标准RISC-V基础上大大提高了中断响应速度。加入单精度浮点指令集,扩充堆栈区,具有更高的运算性能。扩展串口UART数量到8组,定时器到10组,其中4组高级定时器。提供USB2.0高速接口(480Mbps)并内置了PHY收发器,以太网MAC升级到千兆并集成了10M-PHY模块。
- 64KB SRAM,256KB Flash
- 板载Type-C接口WCH-LINK仿真器
- 板载esp8266 WiFi模组,支持腾讯云固件
- 板载以太网接口
- 板载物联网俱乐部WAN Interface接口,可支持NB-IoT、WiFi、4G cat1、LoRa等模组
- 板载物联网俱乐部E53 Interface接口,可扩展全系E53传感器以及音频模块;
- 板载标准24P DVP摄像头接口,可支持最高500万像素摄像头;
- 板载1.54寸 IPS高清显示屏,支持240*240分辨率;
- 预留SD卡、用户按键、SPI Flash,
- 扩展IO口,方便开发者扩展硬件模块
3.3 硬件连接
SVM40模块连接到开发板闲置的串口7,如图所示:
4、系统亮点
- 支持腾讯连连小程序查看数据(VOC指数、温度、湿度)
- 支持腾讯云IoT Explorer平台实时查看上报数据信息(VOC指数、温度、湿度)
- 支持腾讯连连微信公众号信息推送(VOC超标告警)
- 采用腾讯云可视化编辑器自定义腾讯连连小程序界面
5、系统实现
系统实现分为2个部分,一是云端产品建立、小程序界面配置,二是MCU端编程。
在腾讯物联网开发平台IoT Explorer创建产品:
数据模型定义:
小程序界面可视化配置,除了选择公版界面外,还可以选择自定义界面:
小程序界面根据自己喜好进行定义,然后关联数据模型:
在进行设备端开发前,可以使用虚拟在线调试检验设计正确性:
设备端开发主要完成ch32v307的外设初始化、传感器数据读取、LCD显示功能、mqtt协议数据发送功能。基于encentOS-Tiny物联网操作系统开发,设计了3个线程,定时采集传感器数据,lcd显示线程将数据显示到lcd上,mqtt发送线程将数据通过腾讯云mqtt协议发送到云平台。软件流程如图所示:
esp8266模块烧写腾讯云定制固件后,就能很方便的连接到云平台。乐鑫官网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
开发板提供了常用外设的驱动程序,与云平台的交互基于例子mqtt_iot_explorer.c进行开发。
svm40官方提供了串口驱动程序参考,只需要匹配串口初始化、发送数据、读取数据、微秒级延时四个接口就行了,具体就是填充sensirion_uart_hal.c文件里面的驱动接口。
串口初始化:
代码语言:javascript复制int16_t sensirion_uart_hal_init()
{
uart7_init(115200);
return NO_ERROR;
}
串口发送数据:
代码语言:javascript复制int16_t sensirion_uart_hal_tx(uint16_t data_len, const uint8_t* data)
{
for (int i = 0; i < data_len; i )
{
USART_SendData(UART7, data[i]);
while(USART_GetFlagStatus(UART7, USART_FLAG_TC) == RESET);
}
//USART_ClearFlag(UART7, USART_FLAG_TC);
return data_len;
}
串口接收数据基于中断:
代码语言:javascript复制extern volatile int rx_cnt;
extern uint8_t RxBuffer[200];
int16_t sensirion_uart_hal_rx(uint16_t max_data_len, uint8_t* data)
{
if(rx_cnt>0)
{
rx_cnt = 0;
memcpy(data,RxBuffer,max_data_len);
memset(RxBuffer,0,max_data_len);
}
return max_data_len;
}
ch32v30x_it.c文件串口中断服务函数:
代码语言:javascript复制extern uint8_t RxBuffer[200];
extern volatile int rx_cnt;
void UART7_IRQHandler(void)
{
if(USART_GetITStatus(UART7, USART_IT_RXNE) != RESET)
{
RxBuffer[rx_cnt ] = USART_ReceiveData(UART7);
USART_ClearFlag(UART7, USART_IT_RXNE);
}
}
延时接口:
代码语言:javascript复制void sensirion_uart_hal_sleep_usec(uint32_t useconds)
{
Delay_Us(useconds);
}
在main.c中创建三个线程:
代码语言:javascript复制#define SENSOR_TASK_STK_SIZE 4096
k_task_t sensor_task;
__aligned(4) uint8_t sensor_task_stk[SENSOR_TASK_STK_SIZE];
#define DISPLAY_TASK_STK_SIZE 4096
k_task_t display_task;
__aligned(4) uint8_t display_task_stk[DISPLAY_TASK_STK_SIZE];
#define MQTT_TASK_STK_SIZE 4096
k_task_t mqtt_task;
__aligned(4) uint8_t mqtt_task_stk[MQTT_TASK_STK_SIZE];
初始化内核、创建线程、启动内核:
代码语言:javascript复制 tos_knl_init();
tos_task_create(&sensor_task, "sensor_task", sensor_entry, NULL, 4, sensor_task_stk, SENSOR_TASK_STK_SIZE, 0);
tos_task_create(&display_task, "display_task", display_entry, NULL, 4, display_task_stk, DISPLAY_TASK_STK_SIZE, 0);
tos_task_create(&mqtt_task, "mqtt_task", mqtt_entry, NULL, 4, mqtt_task_stk, MQTT_TASK_STK_SIZE, 0);
tos_knl_start();
数据采集线程,初始化svm40后定时采集数据:
代码语言:javascript复制void sensor_entry(void *arg)
{
k_err_t err;
int16_t error = 0;
error = sensirion_uart_hal_init();
if (error)
{
printf("Error initializing UART: %in", error);
return;
}
error = svm40_device_reset();
if (error)
{
printf("Error executing svm40_device_reset(): %in", error);
return;
}
uint8_t serial_number[32];
uint8_t serial_number_size = 32;
error = svm40_get_serial_number(&serial_number[0], serial_number_size);
if (error)
{
printf("Error executing svm40_get_serial_number(): %in", error);
}
else
{
printf("Serial number: %sn", serial_number);
}
uint8_t product_type[32];
uint8_t product_type_size = 32;
error = svm40_get_product_type(&product_type[0], product_type_size);
if (error)
{
printf("Error executing svm40_get_product_type(): %in", error);
}
else
{
printf("Product type: %sn", product_type);
}
uint8_t product_name[32];
uint8_t product_name_size = 32;
error = svm40_get_product_name(&product_name[0], product_name_size);
if (error)
{
printf("Error executing svm40_get_product_name(): %in", error);
}
else
{
printf("Product name: %sn", product_name);
}
uint8_t firmware_major;
uint8_t firmware_minor;
bool firmware_debug;
uint8_t hardware_major;
uint8_t hardware_minor;
uint8_t protocol_major;
uint8_t protocol_minor;
error = svm40_get_version(&firmware_major, &firmware_minor, &firmware_debug,
&hardware_major, &hardware_minor, &protocol_major,
&protocol_minor);
if (error)
{
printf("Error executing svm40_get_version(): %in", error);
}
else
{
printf("Firmware: %i.%i Debug: %in", firmware_major, firmware_minor,firmware_debug);
printf("Hardware: %i.%in", hardware_major, hardware_minor);
printf("Protocol: %i.%in", protocol_major, protocol_minor);
}
if (firmware_major < 2)
{
printf("Your SVM40 firmware is out of date!n");
}
else
{
uint8_t t_offset_buffer[2];
uint8_t t_offset_size = 2;
error = svm40_get_temperature_offset_for_rht_measurements(&t_offset_buffer[0], t_offset_size);
int16_t t_offset = sensirion_common_bytes_to_int16_t(&t_offset_buffer[0]);
if (error)
{
printf("Error executing svm40_get_temperature_offset_for_rht_measurements(): %dn",error);
}
else
{
printf("Temperature Offset: %d ticksn", t_offset);
}
}
retry:
// Start Measurement
error = svm40_start_continuous_measurement();
if (error)
{
printf("Error executing svm40_start_continuous_measurement(): %in",error);
goto retry;
}
for (;;)
{
error = svm40_read_measured_values_as_integers(&voc_index, &humidity,&temperature);
if (error)
{
printf("Error executing svm40_read_measured_values_as_integers()n");
}
else
{
msg->voc_index = voc_index/10;
msg->temperature = temperature/200;
msg->humidity=humidity/100;
}
tos_task_delay(2000);
}
}
lcd显示线程,显示传感器数据:
代码语言:javascript复制void display_entry(void *arg)
{
k_err_t err;
char buf[64];
LCD_Fill(0,0,LCD_W,LCD_H,BLACK);
while (1)
{
sprintf(buf,"VOC:%d",msg->voc_index);
LCD_ShowString(10, 10, buf, BLUE, BLACK, 32, 0);
LCD_ShowString(10, 40, "temp:", RED, BLACK, 32, 0);
LCD_ShowIntNum(90,40,msg->temperature,2,RED, BLACK, 32);
LCD_ShowFloatNum1(122,40,msg->temperature,2,RED, BLACK, 32);
LCD_ShowString(10, 80, "hum:", GREEN, BLACK, 32, 0);
LCD_ShowIntNum(74,80,msg->humidity,2, GREEN, BLACK, 32);
LCD_ShowFloatNum1(106,80,msg->humidity,2, GREEN, BLACK, 32);
tos_task_delay(3000);
}
}
mqtt发送线程,发送传感器数据:
代码语言:javascript复制void mqtt_entry(void *arg)
{
mqtt_demo_task();
while (1)
{
printf("This is a mqtt demo!rn");
tos_task_delay(1000);
}
}
对接云平台,修改mqtt_iot_explorer.c文件开头的三元组信息产品 ID(ProductId)、设备名(DeviceName)、设备密钥(DeviceSecret)其中设备密钥由平台生成。
代码语言:javascript复制#define PRODUCT_ID "your_product_id"
#define DEVICE_NAME "your_dev_name"
#define DEVICE_KEY "your_dev_key"
然后修改要接入WIFI的名称和密码:
代码语言:javascript复制esp8266_tencent_firmware_join_ap("your_ssid", "your_password");
根据腾讯云控制台设定的物模型将数据打包为json格式上传:
代码语言:javascript复制#define REPORT_DATA_TEMPLATE "{\"method\":\"report\"\,\"clientToken\":\"00000001\"\,\"params\":{\"voc_index\":%d\,\"temperature\":%0.1f\,\"humidity\":%0.1f\}}"
推送传感器数据到腾讯云物联网开发平台IoT Explorer :
代码语言:javascript复制 memset(payload, 0, sizeof(payload));
snprintf(payload, sizeof(payload), REPORT_DATA_TEMPLATE, voc_index,temperature,humidity);
if (tos_tf_module_mqtt_pub(report_topic_name, QOS0, payload) != 0)
{
printf("module mqtt pub failn");
break;
}
else
{
printf("module mqtt pub successn");
}
程序运行正常的情况下,在云平台调试界面就可以看到设备发送的数据,进入腾讯连连小程序连接设备后也能看到之前设计的小程序界面。
小程序界面如下:
7、项目PPT
8、视频演示
9、活动总结
很幸运能够参加这次比赛,通过活动学习到了腾讯物联网平台的强大功能和非常简单傻瓜化的开发方式,体验了国产物联网操作系统TencentOS-tiny,熟悉了沁恒RISC-V芯片MCUCH32V307VCT6基本应用和国产集成开发工具MounRiver Studio(改变了我对eclipse系慢、卡、难用的印象)。由于本人能力水平不足,文章写的有误或者不清楚的地方敬请见谅,可在评论区留言以改正。
10、工程源码
由于大小限制,替换TencentOS-tinyboardTencentOS_Tiny_CH32V307_EVB即可。