- #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即可。