一.概述
本次的开发者成长激励计划我打算制作一款智能家居监测和集成控制终端,本次完成了对家庭环境数据上传,主要有PM2.5环境监测,和SHT20温湿度数据监测,本来还打算监测下TVOC数据的,由于传感器原因没有做了,后期可以加上,所以我先弄了一些假数据上传演示,本次下发的控制设备我做了一个红外控制空调的操作,可以控制模式,设备温度。CH32V307开发板板载功能可以实现全部功能,而无需外加其他设备。
二.方案框架简述
大致的方案框架如上,这里面有TencentOS Tiny为我们已经准备好的一些组件了,还有一些传感器驱动需要我们自己去移植下。传感器是三个获取家庭环境的各项数据,然后通过ESP8266模块传输到我们的腾讯云平台,通过腾讯连连完成数据终端显示。同时此次加入红外设备控制,可以遥控家里的空调进行工作。摄像头是作为简单的门铃工作方式,当有人按下按键后,会把摄像头数据显示到我们的LCD显示屏幕上。
三.软硬件组成
1.硬件部分介绍
大赛提供的开发板集成了很多接口,多余的IO还通过一排排针引出了,板载4个按键,4个用户指示灯,还有一个LCD液晶屏,同时预留两个通信方式,一个是wifi,一个是网口。单片机比较有意思的是内部的SRAM和Flash可以配置,官方应该是给了4个选项。
测量环境中的PM2.5我选择了一款激光传感器,其是可以直接串口输出数据的,所以我们只需要接到我们板子的串口上即可。
SGP30是一款测量VOC和CO2的传感器,是IIC接口,但手上暂时没有先空着了。
Sht20温湿度传感器也是模块化,也是IIC接口。
红外控制空调,只需要这样的灯即可,这样的灯发出的就是红外线。硬件部分就这样了。
- 软件部分介绍
我们先看下我们的物模型数据吧,一共是两种一个是传感器数据,一个是红外控制空调的数据。其中包含了测量环境中的TVOC,CO2,PM2.5指数,温湿度数据等。
之后我们还是使用最简单的腾讯连连进行手机终端控制的开发。
新建设备,设备建立完成后我们就能做下位机开发了。
代码语言:javascript复制#define REPORT_DATA_TEMPLATE "{\"method\":\"report\"\,\"clientToken\":\"00000001\"\,\"params\":{\"Temperature\":\"%.2f\"\,\"CO2\":\"%.2f\"\,\"Humidity\":\"%.2f\"\,\"TVOC\":\"%.2f\"}}"
#define REPORT_DATA_AIRCONDITIONING "{\"method\":\"report\"\,\"clientToken\":\"00000001\"\,\"params\":{\"AirConditioningSwitch\":\"%d\"\,\"AirConditioningMode\":\"%d\"\,\"TemperatureSetting\":\"%d\"}}"
#define REPORT_DATA_PM "{\"method\":\"report\"\,\"clientToken\":\"00000001\"\,\"params\":{\"PM2_5\":\"%d\"\,\"PM10\":\"%d\"\,\"PM1_0\":\"%d\"}}"
对于物模型我建立的比较多所以这里一次是发不完的,因为会保错,尝试后我重新分成三个部分,对底层所以数据进行上传。
代码语言:javascript复制void mqtt_report_task_entry(void *arg)
{
int size = 0;
mqtt_state_t state;
char dev_status;
k_err_t err;
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));
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 failn");
} else {
printf("module mqtt conn successn");
}
if (tos_tf_module_mqtt_state_get(&state) != -1) {
printf("MQTT: %sn", 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 failn");
} else {
printf("module mqtt sub successn");
}
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));
}
while (1) {
snprintf(payload, sizeof(payload), REPORT_DATA_TEMPLATE, sht20Info.tempreture,1.4,sht20Info.humidity,1.7);
if (tos_tf_module_mqtt_pub(report_topic_name, QOS0, payload) != 0) {
printf("module mqtt pub failn");
break;
} else {
printf("module mqtt pub successn");
}
snprintf(payload, sizeof(payload), REPORT_DATA_AIRCONDITIONING, AirConditionStatus,AirConditionMode,AirConditionSettingTemperature);
if (tos_tf_module_mqtt_pub(report_topic_name, QOS0, payload) != 0) {
printf("module mqtt pub failn");
break;
} else {
printf("module mqtt pub successn");
}
snprintf(payload, sizeof(payload), REPORT_DATA_PM, PM2_5,PM10,PM1_0);
if (tos_tf_module_mqtt_pub(report_topic_name, QOS0, payload) != 0) {
printf("module mqtt pub failn");
break;
} else {
printf("module mqtt pub successn");
}
tos_task_delay(3000);
}
}
所以我开辟了一个单独任务完成上传。
#include "infrared_emission.h"
/* PWM Output Mode Definition */
#define PWM_MODE1 0
#define PWM_MODE2 1
/* PWM Output Mode Selection */
//#define PWM_MODE PWM_MODE1
#define PWM_MODE PWM_MODE2
#define CARRIER_38KHz() TIM_CtrlPWMOutputs(TIM9, ENABLE )//产生方波
#define NO_CARRIER() TIM_CtrlPWMOutputs(TIM9, DISABLE )//无波形
uint8_t AUX_OFF[13]={0xC3,0x97,0xE0,0x00,0xA0,0x00,0x20,0x00,0x00,0x00,0x00,0x05,0x00};
uint8_t AUX_ON[13]={0xC3,0x97,0xE0,0x00,0xA0,0x00,0x20,0x00,0x00,0x20,0x00,0x05,0x00};
void infrared_emission_IO_Init(u16 arr, u16 psc, u16 ccp )
{
GPIO_InitTypeDef GPIO_InitStructure={0};
TIM_OCInitTypeDef TIM_OCInitStructure={0};
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure={0};
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC | RCC_APB2Periph_TIM9, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOC, &GPIO_InitStructure );
TIM_TimeBaseInitStructure.TIM_Period = arr;
TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit( TIM9, &TIM_TimeBaseInitStructure);
#if (PWM_MODE == PWM_MODE1)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
#elif (PWM_MODE == PWM_MODE2)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
#endif
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = ccp;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OC4Init( TIM9, &TIM_OCInitStructure );
// TIM_CtrlPWMOutputs(TIM9, ENABLE );
TIM_OC1PreloadConfig( TIM9, TIM_OCPreload_Disable );
TIM_ARRPreloadConfig( TIM9, ENABLE );
TIM_Cmd( TIM9, ENABLE );
}
void Infrared_Send(uint8_t *s,int n,uint8_t cnt)
{
uint8_t i,j,temp;
CARRIER_38KHz();
Delay_Us(90);
Delay_Us(90);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
NO_CARRIER();
Delay_Us(400);
Delay_Us(400);
Delay_Us(40);
Delay_Us(40);
Delay_Us(40);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
Delay_Us(400);
for(i=0;i<n;i )
{
for(j=0;j<8;j )
{
temp=(s[i]>>j)&0x01;
if(temp==0)//发射0
{
CARRIER_38KHz();
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(150);//延时0.5ms
NO_CARRIER();
Delay_Us(150);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
}
if(temp==1)//发射1
{
CARRIER_38KHz();
Delay_Us(150);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
Delay_Us(50);//延时0.5ms
NO_CARRIER();
Delay_Us(169);//延时1.69ms 690
Delay_Us(169);//延时1.69ms 690
Delay_Us(169);//延时1.69ms 690
Delay_Us(169);//延时1.69ms 690
Delay_Us(169);//延时1.69ms 690
Delay_Us(169);//延时1.69ms 690
Delay_Us(169);//延时1.69ms 690
Delay_Us(169);//延时1.69ms 690
Delay_Us(169);//延时1.69ms 690
Delay_Us(150);//延时1.69ms 690
}
}
}
CARRIER_38KHz();//结束
Delay_Us(90);//延时0.56ms
Delay_Us(90);//延时0.56ms
Delay_Us(90);//延时0.56ms
Delay_Us(90);//延时0.56ms
Delay_Us(90);//延时0.56ms
Delay_Us(90);//延时0.56ms
NO_CARRIER(); //关闭红外发射
}
void AirConditionerON(void)
{
uint8_t i;
uint8_t checksum = 0;
uint8_t checksum_Two = 0;
for (i = 0; i < 12; i )
{
checksum = AUX_ON[i];
}
AUX_ON[12] = checksum;
Infrared_Send(AUX_ON,13,0);
}
void AirConditionerOFF(void)
{
uint8_t i;
uint8_t checksum = 0;
uint8_t checksum_Two = 0;
for (i = 0; i < 12; i )
{
checksum = AUX_OFF[i];
}
AUX_OFF[12] = checksum;
Infrared_Send(AUX_OFF,13,0);
}
红外控制部分需要我们使用定时对IO口进行38Khz方波的发送,然后进行数据发送,演示是我家奥克斯空调红外码。
代码语言:javascript复制void lcd_display_entry(void *arg)
{
uint8_t cnt = 0;
LCD_Init();
key1_init();
LCD_Fill(0,0,LCD_W,LCD_H,BLACK);
while(1)
{
if(!showCamera_flag)
{
LCD_Fill(0,0,LCD_W,LCD_H,BLACK);
//PM2.5激光传感器
LCD_ShowString(0,0,"PM10:",WHITE,BLACK,16,0);
LCD_ShowIntNum(40,0,PM10,3,WHITE,BLACK,16);
LCD_ShowString(70,0,"ug/m3",WHITE,BLACK,16,0);
LCD_ShowString(120,0,"PM1_0:",WHITE,BLACK,16,0);
LCD_ShowIntNum(165,0,PM1_0,3,WHITE,BLACK,16);
LCD_ShowString(195,0,"ug/m3",WHITE,BLACK,16,0);
LCD_ShowString(0,20,"PM2_5:",WHITE,BLACK,16,0);
LCD_ShowIntNum(50,20,PM2_5,3,WHITE,BLACK,16);
LCD_ShowString(80,20,"ug/m3",WHITE,BLACK,16,0);
if(PM2_5<35)
{
LCD_ShowString(130,20,"excellent",GREEN,BLACK,16,0);
}
else if(PM2_5>35&&PM2_5<75)
{
LCD_ShowString(140,20,"good",LIGHTGREEN,BLACK,16,0);
}
else if(PM2_5>75&&PM2_5<115)
{
LCD_ShowString(120,20,"mild contamination",BROWN,BLACK,16,0);
}
else if(PM2_5>115&&PM2_5<150)
{
LCD_ShowString(120,20,"medium pollution",BROWN,BLACK,16,0);
}
else if(PM2_5>150)
{
LCD_ShowString(120,20,"heavy pollution",BRRED,BLACK,16,0);
}
//sgp30传感器
LCD_ShowString(0,40,"TVOC:",WHITE,BLACK,16,0);
LCD_ShowFloatNum1(40,40,1.7,3,WHITE,BLACK,16);
LCD_ShowString(80,40,"ppd",WHITE,BLACK,16,0);
LCD_ShowString(120,40,"CO2:",WHITE,BLACK,16,0);
LCD_ShowFloatNum1(150,40,1.4,3,WHITE,BLACK,16);
LCD_ShowString(185,40,"ppm",WHITE,BLACK,16,0);
//温湿度显示
LCD_ShowString(0,60,"TEMP:",WHITE,BLACK,16,0);
LCD_ShowFloatNum1(55,60,sht20Info.tempreture,4,WHITE,BLACK,16);
LCD_ShowString(100,60,"C",WHITE,BLACK,16,0);
LCD_ShowString(120,60,"HUMI:",WHITE,BLACK,16,0);
LCD_ShowFloatNum1(165,60,sht20Info.humidity,4,WHITE,BLACK,16);
LCD_ShowString(210,60,"%",WHITE,BLACK,16,0);
LCD_ShowChinese(0,90,"空调状态",WHITE,BLACK,16,0);
LCD_ShowString(100,90,":",WHITE,BLACK,16,0);
if(AirConditionStatus)
{
LCD_ShowChinese(120,90,"开",WHITE,BLACK,16,0);
}
else {
LCD_ShowChinese(120,90,"关",WHITE,BLACK,16,0);
}
LCD_ShowChinese(0,110,"空调模式",WHITE,BLACK,16,0);
LCD_ShowString(100,110,":",WHITE,BLACK,16,0);
if(AirConditionMode == 0)
{
LCD_ShowChinese(120,110,"制冷",WHITE,BLACK,16,0);
}
else if (AirConditionMode == 1) {
LCD_ShowChinese(120,110,"制热",WHITE,BLACK,16,0);
}
else if (AirConditionMode == 2) {
LCD_ShowChinese(120,110,"除湿",WHITE,BLACK,16,0);
}
LCD_ShowChinese(0,130,"设定温度",WHITE,BLACK,16,0);
LCD_ShowString(100,130,":",WHITE,BLACK,16,0);
LCD_ShowIntNum(120,130,AirConditionSettingTemperature,2,WHITE,BLACK,16);
LCD_ShowChinese(140,130,"℃",WHITE,BLACK,16,0);
//连接wifi显示
if(connect_wifi_flag == 0)
{
LCD_ShowString(30,140 16 16 16,"connect wifi...",WHITE,BLACK,16,0);
}
else if(connect_wifi_flag == 1)
{
LCD_ShowString(30,140 16 16 16,"connect wifi fail",WHITE,BLACK,16,0);
}
else if(connect_wifi_flag == 2)
{
connect_wifi_flag = -1;
LCD_ShowString(30,140 16 16 16,"connect wifi ok",WHITE,BLACK,16,0);
}
tos_task_delay(3000);
}
else
{
if(cnt>5)
{
cnt = 0;
// LCD_Fill(0,0,LCD_W,LCD_H,BLACK);
showCamera_flag = 0;
}
tos_task_delay(1000);
cnt ;
}
}
}
显示我也单独开了一个线程,为了模拟摄像头作为门铃效果,我对显示部分做了些处理,显示摄像头数据事,不刷新我们的采集数据。
代码语言:javascript复制void uart7_receive_entry(void *arg)
{
uart7_init(9600);
u16 checkSum = 0;
while (1)
{
if(Uart7_ReceiveComplete_flag)
{
if(Uart7_RX_Buff[0]==0x32 && Uart7_RX_Buff[1]==0x3D)
{
for(int i = 0; i<30;i )
{
checkSum =Uart7_RX_Buff[i];
}
if(Uart7_RX_Buff[30]<<8|Uart7_RX_Buff[31] == checkSum)
{
if(Uart7_RX_Buff[2]<<8|Uart7_RX_Buff[3] == 28)
{
PM1_0 = Uart7_RX_Buff[4]<<8|Uart7_RX_Buff[5];
PM2_5 = Uart7_RX_Buff[6]<<8|Uart7_RX_Buff[7];
PM10 = Uart7_RX_Buff[8]<<8|Uart7_RX_Buff[9];
printf("PM1_0:%drn",PM1_0);
printf("PM2_5:%drn",PM2_5);
printf("PM10:%drn",PM10);
}
}
checkSum = 0;
}
Uart7_ReceiveComplete_flag = 0;
Uart7_RX_CNT = 0;
}
tos_task_delay(1000);
}
}
之后就是读取我们的PM传感器串口过来的数据,也是一个单独线程。
void default_message_handler(mqtt_message_t* msg)
{
printf("callback:rn");
printf("---------------------------------------------------------rn");
printf("ttopic:%srn", msg->topic);
printf("tpayload:%srn", msg->payload);
printf("---------------------------------------------------------rn");
cJSON* cjson_root = NULL;
cJSON* cjson_method = NULL;
cJSON* cjson_status = NULL;
cJSON* cjson_params = NULL;
cJSON* cjson_switch = NULL;
cJSON* cjson_mode = NULL;
cJSON* cjson_settemp = NULL;
char* status = NULL;
char* method = NULL;
k_event_flag_t event_flag = report_fail;
/* 使用cjson解析上报响应数据 */
cjson_root = cJSON_Parse(msg->payload 1);
if (cjson_root == NULL) {
printf("report reply message parser fail1rn");
event_flag = report_fail;
goto exit;
}
/* 提取method */
cjson_method = cJSON_GetObjectItem(cjson_root, "method");
method = cJSON_GetStringValue(cjson_method);
if (cjson_method == NULL || method == NULL) {
printf("report reply status parser fail2rn");
event_flag = report_fail;
goto exit;
}
if (strstr(method, "control"))
{
cjson_params = cJSON_GetObjectItem(cjson_root, "params");
cjson_switch = cJSON_GetObjectItem(cjson_params, "AirConditioningSwitch");
cjson_mode = cJSON_GetObjectItem(cjson_params, "AirConditioningMode");
cjson_settemp = cJSON_GetObjectItem(cjson_params, "TemperatureSetting");
if (cjson_params == NULL )
{
printf("control data parser fail4rn");
cJSON_Delete(cjson_root);
return;
}
if(cjson_switch != NULL)
{
AirConditionStatus = cjson_switch->valueint;
if(AirConditionStatus)
{
AirConditionerON();
}
else
{
AirConditionerOFF();
}
}
if(cjson_settemp != NULL)
{
AirConditionSettingTemperature = cjson_settemp->valueint;
AUX_ON[1] = ((0x40>>3) (AirConditionSettingTemperature-16))<<3|0x07;
AUX_OFF[1] = AUX_ON[1] ;
if(AirConditionStatus)
{
AirConditionerON();
}
}
if(cjson_mode != NULL)
{
AirConditionMode = cjson_mode->valueint;
if(AirConditionMode == 0)
{
AUX_ON[6] = 0x20 ;
AUX_OFF[6] = 0x20;
if(AirConditionStatus)
{
AirConditionerON();
}
}
else if(AirConditionMode == 1)
{
AUX_ON[6] = 0x80 ;
AUX_OFF[6] = 0x80;
if(AirConditionStatus)
{
AirConditionerON();
}
}
else if(AirConditionMode == 2)
{
AUX_ON[6] = 0x40;
AUX_OFF[6] = 0x40;
if(AirConditionStatus)
{
AirConditionerON();
}
}
}
cJSON_Delete(cjson_root);
return;
}
exit:
cJSON_Delete(cjson_root);
cjson_root = NULL;
status = NULL;
method = NULL;
}
对于空调的控制我们需要通过Cjson进行解析,然后根据解析内容发送对应红外码。
初步效果,具体效果看演示视频。
四.演示视频
五.总结
从开发到上手目前在TencentOS Tiny移植帮助下,很快就掌握了整个开发流程,之后在移植驱动上,发现目前的RISC-V处理器的开发上没有什么问题,基本开发都和ARM内核的单片机都是一致的。所以本次上手体验整体流程还是比较舒服的,很感谢这次提供的测试机会。
六.代码
https://gitee.com/liuxingkeji/smartHome-Demo-Project