AIoT应用创新大赛-基于TencentOS Tiny的家庭安全监控系统

产品整体实物图:

产品整体实物图

产品功能视频展示:

产品功能展示

一、设计背景及意义

该设计旨在为家庭的日常生活安全提供额外的保障;

随着科技的发展与进步,人们家庭的智能设备和产品越来越多,接入电网的设备也会越来越多,量变导致质变,家庭设备总用电监控,能极大的帮助人们了解日常用电情况;

该产品的功能,基于以下的问题点进行设计:

1.家庭大功率设备众多,大多数人对产品的实际功率并不了解,会出现同时使用导致跳闸问题;

2.家庭部分的安全开关老化失效,过载后不能快速切断电源,损害产品以及电路;

3.家庭中部分老旧设备存在漏电情况,但平时并不了解漏电情况,只有触电后才发觉;

4.家庭中漏电开关老化或反映迟钝,导致断开不迅速,威胁家人健康;

5.帮助人们了解市电的基本信息(电压、频率等);

6.使用天然气、煤气的家庭,容易出现忘记关,有害气体泄漏的情况;

7.帮助人们实时了解家里温湿度信息;

二、家庭安全监控系统的功能介绍

系统功能如下:

1.双重切断市电功能(继电器、保险丝);

2.支持漏电电流检测;

3.实时检测市电电压,频率,实际使用有功功率,电流,用电量等;

4.实时检测室内空气状态,以及温湿度信息;

5.支持异常情况声光报警功能;

6.支持电池供电,停电后自动切换电池为系统供电,同时提供基本照明;

7.支持电池充放电管理,保证电池寿命;

三、系统的整体结构框图

系统框图

四、产品硬件介绍

4.1、产品硬件框图

硬件框图

4.2、产品硬件组成

4.2.1、 开发套件

本次开发使用的是腾讯提供的TencentOS Tiny AIoT开发套件,该套件包含了RT1062开发板、E53智慧灯模块、ESP8266模组、ov5640摄像头以及4.3寸LCD显示屏,如下图:

TencentOS Tiny AIoT开发套件

开发板特性:

  • 内置TencentOS Tiny开源物联网操作系统。
  • 核心板采用的RT1062处理器属于i.MX RT 系列 MCU,是由 NXP 推出的跨界处理器,跨界是指该系列MCU的定位既非传统的微控制器、也非传统的微处理器,i.MX RT 系列 MCU 则综合了两者的优势,既具备高频率(最高主频600M)、高处理性能,也具备中断响应迅速、实时性高的特点。
  • 1M RAM 16M SDRAM 64MB qspi flash 128MB spi flash。
  • 板载Type-C接口CMSIS DAP仿真器。
  • 板载PCIE接口,可扩展4G类物联网模组。
  • 板载物联网俱乐部WAN Interface接口,可支持NB-IoT、WiFi、4G cat1、LoRa等模组。
  • 板载物联网俱乐部E53 Interface接口,可扩展全系E53传感器。
  • 板载标准24P DVP摄像头接口,可支持最高500万像素摄像头。
  • 板载RGB显示接口,可转换HDMI输出。
  • 板载高性能音频解码芯片,可做语音识别测试。
  • 预留SD卡、用户按键、SPI Flash。

4.2.2 安全系统检测控制一体电路板实物,如下:

控制板正面
控制板背面
控制板3d图

4.2.3 安全系统检测控制一体电路板原理图和PCB

原理图
PCB Layout

五、原理图各功能模块原理介绍

5.1、系统弱电总供电部分

该部分采用型号为HLK-10M05的电源模块,该电源模块能将电压从220v AC转为5v DC,提供满载20w的功率,该电源的主要特点是全隔离降压,为弱电部分提供安全稳定的保障。后端还有3.3v稳压IC AMS1117为系统提供额外供电,在图中,可能会对两个肖特基二极管(D3、D4)产生疑惑,这个两个采用的是低压降,高速的肖特基二极管,作用是防止系统220v断电后,切换电池为系统部分电路供电时,电流反向导致电池电量过快损耗;

系统弱电总供电原理图

5.2、电量统计芯片供电部分

该部分采用的是HLK-B0505S DC5v-DC5v隔离电源模块,再通过稳压IC AMS1117S将电压降到3.3v给电量统计芯片供电;

电量统计芯片供电原理图

5.3、电量统计芯片及外围电路部分

该部分是该系统比较重要的部分,也是调试过程最危险的部分,芯片左端的电路接入的全是强电,对电路知识不太了解的话,容易短路,或触电;BL0939是上海贝岭的一块比较新型的芯片,目前网络上几乎没能找到对应的使用教程,唯一能看到的官网提供的文档,该部分原理图参考了官方文档的部分电路连接,感兴趣的朋友可以去了解一下,官网的文档有很详情的解释,链接如下:https://www.belling.com.cn/product_info.html?id=368;BL0939右边的电路,如U7,是一款数字隔离传输芯片,保证主控和BL0939能够隔离通讯,防止与强电部分直接连接,损坏主控;而下面U8、U9、U10是PC817线性光耦,作用同样也是实现强弱电隔离,将BL0939部分波形信号隔离输出给主控;

电量统计芯片外围电路

5.4、继电器驱动电路部分

继电器属于感性器件,在感性器件的驱动电路中,一定要添加二极管为其续流,用于吸收断电瞬间的反向电流,保护后端器件不被击穿,图中D1正是作用于此,该部分的二极管选用高速的肖特基二极管最佳;SS8050为NPN三极管,在该电路中用作继电器的开关作用,理论上主控通过控制三极管基极即可控制继电器,但为了保证前端异常损坏的情况也不会影响主控芯片,毕竟主控比较贵,这里还是采用了光耦做为隔离,保证万无一失;

继电器驱动电路

5.5、蜂鸣器和电磁门销驱动部分

该部分采用NPN三极管进行驱动,但是电磁铁门销部分瞬间电路会比较大,所以用到了PMOS作为二级驱动,Q7的既能作为前极驱动PMOS的栅极,还有电平转换的功能,要知道我们的主控是3.3v供电的,而这个电磁铁门销是5v驱动的;

蜂鸣器和电磁铁门销驱动电路

5.6、电池电源管理部分电路

该部分电路是系统能够自动切换供电,和电池电源自动充放电的核心电路,该电路的实现花了我好长一段时间去设计以及实际仿真,搭建电路实际去测试才得出来的。该部分电路的特点是纯硬件实现,稳定性高,反应快速。电池充电部分我采用了一颗型号为TP4054的充电IC,这款IC市面上用的比较多,资料也好找。充电IC的输入供电端,同样是由一颗PMOS控制,用于电池慢点后,直接切断充电IC供电;Q4前端受U14控制,U14是一款双路比较器,市面上型号也比较多,这里用的是一款LM2903的比较器,熟悉比较器的朋友应该可以通过比较器的外围电路,看出这是一个比较器的迟滞控制电路,大家有兴趣可以网上搜索迟滞比较器的原理,这部分理论挺多的,就不在这里展开了。这里简单描述下原理,比较器的异名端(-IN),通过电阻R29和可调电阻R28,分压得到3.89v,同名端(+IN)直接通过电阻R31和R30将电池电压分压后输入,电阻R31/R30等于1/12.5(实际计算得出)。当电池电压过低时,同名端的电压会比异名端电压低,比较器输出端(OUT)将管脚拉低,Q4导通,电池开始充电;当电池充到一定电压,使得同名端电压高于异名端电压,比较器输出端拉高,Q4截止,电池停止充电,当电池电压又低于某个限值时,又开始充电,从而实现自动充电的电路逻辑,以上的参数理论得出来的电池充放电范围是3.8-4.2v,在电池充电循环次数固定的情况下,尽可能延长电池寿命;U11是一款超低压差稳压芯片,用于将电池电压稳压到3.3v供系统使用,当家里停电时,5v供电直接消失,此时PMOS Q3栅极直接接地,瞬间导通,自动切换电池为系统供电;

电池电源管理部分电路

5.7、蓄能电路部分

该部分用了一颗1F的电容用于蓄电,R44用于电容蓄电过程中的限流作用,停电后,PMOS Q9 直接导通,将C15直接接入电路,保证5.6部分电路切换电池供电过程稳定完成,是一个辅助电路,当然去掉也是能正常工作的;

5.8、应急照明部分电路

该部分的PMOS控制方式和前面的是一样的,停电后,PMOS瞬间导通,电池直接给LED供电,该LED用的是比较大电流的高亮LED,所以选择MOS管去驱动;

应急照明部分电路

5.9、外部3.3v供电电路

该部分电路是用于给控制板以外的外接设备供电,这里指的是我们的开发套件,加入了NMOS是为了在断电时,停止给外部设备供电,电池电路只用于应急照明使用,延长使用时间;

外部3.3v供电电路

六、产品软件部分

6.1、开发软件简介(MCUXpresso IDE)

本次使用的TencentOS Tiny AIoT开发套件中的主控芯片型号为NXP RT1062,所以为了能够快速开发,为项目的其他难点挤出时间,我直接使用官方提供的IDE,群里看小伙伴们都在各种吐槽官方IDE各种卡顿,然后折腾各种编译工具。虽然有些问题的确和小伙伴吐槽的一样,但官方IDE有官方的好处,上手快,支持丰富的例程,配置管脚等非常的方便,而且还支持在线仿真,这个对排查问题非常重要,远远比打log找bug快速有效。相信官方会将软件优化得越来越好,使用的人也越来越多。

6.2、腾讯TencentOS tiny 实时操作系统简介

TencentOS tiny 是腾讯面向物联网领域开发的实时操作系统,具有低功耗,低资源占用,模块化,安全可靠等特点,可有效提升物联网终端产品开发效率。TencentOS tiny 提供精简的 RTOS 内核,内核组件可裁剪可配置,可快速移植到多种主流 MCU (如 STM32 全系列)及模组芯片上。而且,基于 RTOS 内核提供了丰富的物联网组件,内部集成主流物联网协议栈(如 CoAP/MQTT/TLS/DTLS/LoRaWAN/NB-IoT 等),可助力物联网终端设备及业务快速接入腾讯云物联网平台。

TencentOS tiny 整体架构

该实时操作系统的特点如下:

  1. 小体积:最小内核 RAM 0.6KB,ROM 1.8KB 典型 LoraWAN 及传感器应用:RAM 3.3KB,ROM 12KB
  2. 低功耗:休眠最低功耗低至2 uA 支持外设功耗管理框架
  3. 丰富的 IoT 组件:集成主流IoT协议栈 多种通信模组SAL层适配框架; 支持OTA升级 提供简单易用端云API,加速用户业务接入腾讯云
  4. 可靠的安全框架:多样化的安全分级方案 均衡安全需求&成本控制
  5. 良好的可移植性:内核及 IoT 组件高度解耦,提供标准适配层 提供自动化移植工具,提升开发效率
  6. 便捷的调试手段:提供云化的最后一屏调试功能 故障现场信息自动上传云平台,方便开发人员调试分析

6.3、产品代码实现

6.3.1、软件流程图

软件流程图

6.3.2、主程序入口

int main(void)
{
    BOARD_ConfigMPU();
    BOARD_InitPins();
    BOARD_BootClockRUN();
    BOARD_InitLcdifPixelClock();
    BOARD_InitDebugConsole();
    BOARD_InitLcd();

    APP_ELCDIF_Init();
    BOARD_EnableLcdInterrupt();

    /* Clear the frame buffer. */
    memset(s_frameBuffer, 0, sizeof(s_frameBuffer));

    ELCDIF_EnableInterrupts(APP_ELCDIF, kELCDIF_CurFrameDoneInterruptEnable);
    ELCDIF_RgbModeStart(APP_ELCDIF);

    tos_knl_init();

	mqttclient_iot_start();
    lvgl_main_start();
    beep_main_start();
    dht22_main_start();
	relay_main_start();
	bl0939_main_start();
	door_main_start();

    tos_knl_start();
}

主程序入口我一般会设计得比较简洁,主要用来系统启动时,配置一些系统的信息,如初始化时钟,外设等,然后初始化LCD配置信息,设置显示模式,开启中断,接下来就初始化TencentOS tiny内核,启动每一个功能模块的任务,启动任务调度就完成了主函数的运行;

6.3.3、腾讯云接入部分

该部分主要分两部分,第一是解析模组发来的AT指令,由于用官方提供的ESP8266模组和固件,配合TencentOS的mqtt框架,老是连不上网,其中的问题太多,时间不够,我只能用ESP32代替,自己修改模组内的程序,丢弃原有mqtt框架,自己实现AT命令解析功能,保证百分百能连接上云端;第二是处理云端下发的json数据,以及实时打包和上报设备信息到云端;下面附上比较主要的代码;

void mqttclient_iot_start(void)
{
	int err;

	err = tos_task_create(&AT_task, "AT_task", Atmodule_Task, NULL, 2, AT_stack, AT_SIZE, 10);
    if(err != K_ERR_NONE)
    {
    	PRINTF("task create err = %d\n", err);
    }
    err = tos_task_create(&mqtt_task, "mqtt_task", application_entry, NULL, 2, mqtt_stack, TASK_SIZE, 10);
    if(err != K_ERR_NONE)
    {
    	PRINTF("task create err = %d\n", err);
    }
}

void AT_Client_Start()
{
	k_err_t err;
    uint8_t recv_data=0;
	void *recv_ptr=NULL;
	static uint8_t index=0;
	err = tos_msg_q_pend(&msg_q, &recv_ptr, 10000);
	if (err != K_ERR_NONE) 
	{
		atCurPos = 0;
		return;
	}
	recv_data = *(char*)recv_ptr;
    switch (recv_data)
    {
        case 0x0a: /* Enter */
        case 0x0d: /* Enter */
        	if(atCurPos <= 0) break;
            atCmdBuffer[strlen(atCmdBuffer)] = 0;
			memset(ask_buff[index], 0, 256);
			strcpy(ask_buff[index], atCmdBuffer);

			tos_msg_q_post(&ask_msg, ask_buff[index]);
			index = (index+1)%3;
			atCurPos = 0;
            break;

        default: /* other characters */
            atCmdBuffer[atCurPos++] = recv_data;
            atCmdBuffer[atCurPos] = 0x00;
            break;
    }
  
}
void Atmodule_Task( void *args )
{
	tos_msg_q_create(&msg_q, msg_pool, RECV_MAX);
	tos_msg_q_create(&ask_msg, ask_pool, MESSAGE_MAX);
	tos_task_delay(1000);
	uart2_init();
    for ( ;; )
    {
        AT_Client_Start();
    }
}

void application_entry(void *arg)
{
	k_err_t err;
	uint8_t ret=0;
	void *recv_ptr;
	tos_task_delay(2000);
	while(1)
	{
		ret = wifi_get_status();
		if(ret) break;
		tos_task_delay(1000);
	}
	tos_task_delay(100);
	while(1)
	{
		ret = device_set_info();
		if(ret) break;
		tos_task_delay(1000);
	}
	tos_task_delay(100);

	while(1)
	{
		ret = mqtt_connect_set_info();
		if(ret) break;
		tos_task_delay(1000);
	}
	tos_task_delay(1000);

	while(1)
	{
		ret = mqtt_subscribe_topic();
		if(ret) break;
		tos_task_delay(1000);
	}
	tos_task_delay(100);

    while (1) {
        err = tos_msg_q_pend(&ask_msg, &recv_ptr, 10000);
		if(err == K_ERR_NONE)
		{
			recv_data_handle(recv_ptr);
			continue;
		}
		upload_data_handle();
    }
}
void upload_data_handle(void)
{
	static uint8_t air_sta=0xFF;
	static uint8_t switch_sta=0xFF;
	static int humidity_tmp=0xFFFF;
	static int temperature_tmp=0xFFFF;
	static uint16_t v_tmp=0xFFFF,i_tmp=0xFFFF;
	static uint16_t power_tmp=0xFFFF;
	static uint16_t fre_tmp=0xFFFF;
	static uint32_t kwh_tmp=0xFFFFFFFF;
	char tmp[32]={0};
	char upload_buff[512]="AT+TCMQTTPUB=\"$thing/up/property/xxxxxxxxxx/device1\",0,\"{\"method\":\"report\",\"params\":{";
	uint16_t len_record = strlen(upload_buff);

	if(switch_sta != relay_state)
	{
		switch_sta = relay_state;
		strcat(upload_buff, relay_state?"\"switch\":1,":"\"switch\":0,");
	}
	if(air_sta != air_state)
	{
		air_sta = air_state;
		strcat(upload_buff, air_state?"\"air_status\":1,":"\"air_status\":0,");
	}
	if(humidity_tmp != humidity)
	{
		humidity_tmp = humidity;
		sprintf(tmp, "%d.%d,",humidity/10,humidity%10);
		strcat(upload_buff,"\"humidity\":");
		strcat(upload_buff,tmp);
	}
	if(temperature_tmp != temperature)
	{
		temperature_tmp = temperature;
		memset(tmp, 0, sizeof(tmp));
		sprintf(tmp, "%d.%d,",temperature/10,temperature%10);
		strcat(upload_buff,"\"temp\":");
		strcat(upload_buff,tmp);
	}
	if(v_tmp != (uint16_t)(V_Real*10))
	{
		v_tmp = (uint16_t)(V_Real*10);
		memset(tmp, 0, sizeof(tmp));
		sprintf(tmp, "%d.%d,",v_tmp/10,v_tmp%10);
		strcat(upload_buff,"\"V_real\":");
		strcat(upload_buff,tmp);
	}
	if(i_tmp != (uint16_t)(I_Real*10))
	{
		i_tmp = (uint16_t)(I_Real*10);
		memset(tmp, 0, sizeof(tmp));
		sprintf(tmp, "%d.%d,",i_tmp/10,i_tmp%10);
		strcat(upload_buff,"\"I_real\":");
		strcat(upload_buff,tmp);
	}
	if(power_tmp != (uint16_t)(Active_Power_Real*10))
	{
		power_tmp = (uint16_t)(Active_Power_Real*10);
		memset(tmp, 0, sizeof(tmp));
		sprintf(tmp, "%d.%d,",power_tmp/10,power_tmp%10);
		strcat(upload_buff,"\"watt\":");
		strcat(upload_buff,tmp);

	}
	if(fre_tmp != (uint16_t)(fre_real*100))
	{
		fre_tmp = (uint16_t)(fre_real*100);
		memset(tmp, 0, sizeof(tmp));
		sprintf(tmp, "%d.%d,",fre_tmp/100,fre_tmp%100);
		strcat(upload_buff,"\"V_fre\":");
		strcat(upload_buff,tmp);
	}
	if(kwh_tmp != (uint32_t)(Total_KWh*100))
	{
		kwh_tmp = (uint32_t)(Total_KWh*100);
		memset(tmp, 0, sizeof(tmp));
		sprintf(tmp, "%d.%d,",kwh_tmp/100,kwh_tmp%100);
		strcat(upload_buff,"\"KWh\":");
		strcat(upload_buff,tmp);
	}
	upload_buff[strlen(upload_buff)-1] = 0;

	if(len_record+5 < strlen(upload_buff));
	{
		strcat(upload_buff, "}}\"\r\n");
		LPUART_WriteBlocking(LPUART2, upload_buff, strlen(upload_buff));
	}
}

6.3.4、lvgl显示部分

lvgl主入口先对其内核进行初始化,初始化LCD驱动,还有输入的外设驱动,然后显示系统主页面信息,开启lvgl_task为处理lvgl任务以及为其提供时钟基准,开启update_task用于实时更新显示信息;

void lvgl_main_start(void)
{
    lv_init();
    lv_port_disp_init();
    lv_port_indev_init();
    device_info_page1();

    tos_task_create(&lvgl_task, "lvgl_task", lvgl_main_task, NULL, 0,lvgl_stack, LVGL_TASK_SIZE, 10);
    tos_task_create(&update_task, "update_task", lvgl_update_task, NULL, 2,update_stack, UPDATE_TASK_SIZE, 10);
}
void lvgl_update_task(void *param)
{
	while(1)
	{
		label_v_set_val((uint16_t)(V_Real*10));
		label_i_set_val((uint16_t)(I_Real*10));
		label_w_set_val((uint16_t)(Active_Power_Real*10));
		label_f_set_val((uint16_t)(fre_real*100));
		label_l_set_val((uint16_t)(l_leak*10));
		label_wh_set_val((uint32_t)(Total_KWh*10));

		label_temp_set_val(temperature);
		label_humidity_set_val(humidity);
		label_air_set_val(air_state);
		label_relay_set_val(relay_state);

		tos_task_delay(1500);
	}
}
void device_info_page1(void)
{
    int16_t height_offset = GAP_SIZE;

    page1_title = lv_label_create(lv_scr_act());
    lv_label_set_text(page1_title, "家庭用电安全系统");
    lv_obj_set_style_text_font(page1_title, &myFont, 0);
    lv_obj_align(page1_title, LV_ALIGN_TOP_MID, 0, height_offset);

    title1 = lv_label_create(lv_scr_act());
    lv_label_set_text(title1, "用电信息");
    lv_obj_set_style_text_font(title1, &myFont, 0);
    lv_obj_align(title1, LV_ALIGN_TOP_LEFT, 30, 150);
    lv_obj_set_size(title1, 32, 200);

    height_offset += (GAP_SIZE+GAP_SIZE+FONT_HEIGHT);
    lab_v = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(lab_v, "电压:%d V", 220);
    lv_obj_set_style_text_font(lab_v, &myFont, 0);
    lv_obj_align(lab_v, LV_ALIGN_TOP_LEFT, 80, height_offset);

    height_offset += (GAP_SIZE+FONT_HEIGHT);
    lab_i = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(lab_i, "电流:%d A", 220);
    lv_obj_set_style_text_font(lab_i, &myFont, 0);
    lv_obj_align(lab_i, LV_ALIGN_TOP_LEFT, 80, height_offset);

    height_offset += (GAP_SIZE+FONT_HEIGHT);
    lab_w = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(lab_w, "功率:%d w", 1000);
    lv_obj_set_style_text_font(lab_w, &myFont, 0);
    lv_obj_align(lab_w, LV_ALIGN_TOP_LEFT, 80, height_offset);

    height_offset += (GAP_SIZE+FONT_HEIGHT);
    lab_f = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(lab_f, "频率:%d Hz", 50);
    lv_obj_set_style_text_font(lab_f, &myFont, 0);
    lv_obj_align(lab_f, LV_ALIGN_TOP_LEFT, 80, height_offset);

    height_offset += (GAP_SIZE+FONT_HEIGHT);
    lab_l = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(lab_l, "漏电流:%d mA", 50);
    lv_obj_set_style_text_font(lab_l, &myFont, 0);
    lv_obj_align(lab_l, LV_ALIGN_TOP_LEFT, 80, height_offset);

    height_offset += (GAP_SIZE+FONT_HEIGHT);
    lab_wh = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(lab_wh, "用电量:%d kw/h", 50);
    lv_obj_set_style_text_font(lab_wh, &myFont, 0);
    lv_obj_align(lab_wh, LV_ALIGN_TOP_LEFT, 80, height_offset);


    height_offset = (GAP_SIZE);
    title2 = lv_label_create(lv_scr_act());
    lv_label_set_text(title2, "系统信息");
    lv_obj_set_style_text_font(title2, &myFont, 0);
    lv_obj_align(title2, LV_ALIGN_TOP_LEFT, 450, 150);
    lv_obj_set_size(title2, 32, 200);

    height_offset += (GAP_SIZE+GAP_SIZE+FONT_HEIGHT);
    lab_temp = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(lab_temp, "温度: %d ℃", 50);
    lv_obj_set_style_text_font(lab_temp, &myFont, 0);
    lv_obj_align(lab_temp, LV_ALIGN_TOP_LEFT, 500, height_offset);

    height_offset += (GAP_SIZE+FONT_HEIGHT);
    lab_humidity = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(lab_humidity, "湿度: %d %%", 50);
    lv_obj_set_style_text_font(lab_humidity, &myFont, 0);
    lv_obj_align(lab_humidity, LV_ALIGN_TOP_LEFT, 500, height_offset);

    height_offset += (GAP_SIZE+FONT_HEIGHT);
    lab_air = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(lab_air, "空气状态: %s", "良好");
    lv_obj_set_style_text_font(lab_air, &myFont, 0);
    lv_obj_align(lab_air, LV_ALIGN_TOP_LEFT, 500, height_offset);

    height_offset += (GAP_SIZE+FONT_HEIGHT);
    lab_batt = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(lab_batt, "电池电压: %d V", 370);
    lv_obj_set_style_text_font(lab_batt, &myFont, 0);
    lv_obj_align(lab_batt, LV_ALIGN_TOP_LEFT, 500, height_offset);

    height_offset += (GAP_SIZE+FONT_HEIGHT);
    lab_relay = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(lab_relay, "继电器状态: %s", "断开");
    lv_obj_set_style_text_font(lab_relay, &myFont, 0);
    lv_obj_align(lab_relay, LV_ALIGN_TOP_LEFT, 500, height_offset);
}

6.3.5、电能计量芯片部分软件

BL0939这款电能计量芯片虽然比较新,网络上用的人比较少,但是官方的资料已经够用了,数据的读取用到串口以及一些外部输入io。主入口初始化的定时器,用来测量电网频率,串口用来读取其他的电量信息,剩下的ADC初始化,是用来检测电池电量的,目前将电池电量监测放到电能检测的功能中执行;

void bl0939_main_start(void)
{
	timer3_init();
	bl0939_io_init();
	uart5_init();
	adc_io_init();
	tos_task_create(&bl0939_task, "bl0939_task", bl0939_main_task, NULL, 2, bl0939_stack, bl0939_TASK_SIZE, 10);
}

void FRE_IO_IRQ_HANDLER(void)
{
	static uint8_t flag=0;
	static uint8_t count=0;
	tos_knl_irq_enter();
	if(GPIO_GetPinsInterruptFlags(FRE_IO_GPIO) & (1<<FRE_IO_PIN) )
	{
    	count++;
		if(count >= 200)
		{
			QTMR_StopTimer(TMR3, kQTMR_Channel_1);
			uint32_t cur_tick = QTMR_GetCurrentTimerCount(TMR3, kQTMR_Channel_1);
			cur_tick *= 0.000853;
			cur_tick += (_50ms_count*50);
			fre_real = 500.0*count/(float)cur_tick;
			count = 0;
			_50ms_count = 0;
			QTMR_StartTimer(TMR3, kQTMR_Channel_1, kQTMR_PriSrcRiseEdge);
		}
	}
	GPIO_ClearPinsInterruptFlags(FRE_IO_GPIO, 1U << FRE_IO_PIN);
	tos_knl_irq_leave();
    SDK_ISR_EXIT_BARRIER;
}
void bl0939_frame_analysis(uint8_t *data)
{
	IA_FAST_RMS = (data[3]<<16) | (data[2]<<8) | data[1];
	IA_RMS = (data[6]<<16) | (data[5]<<8) | data[4];
	IB_RMS = (data[9]<<16) | (data[8]<<8) | data[5];
	V_RMS = (data[12]<<16) | (data[11]<<8) | data[10];
	IB_FAST_RMS = (data[15]<<16) | (data[14]<<8) | data[13];
	A_WATT = (data[18]<<16) | (data[17]<<8) | data[16];
	B_WATT = (data[21]<<16) | (data[20]<<8) | data[19];
	CFA_CNT = (data[24]<<16) | (data[23]<<8) | data[22];
	CFB_CNT = (data[27]<<16) | (data[26]<<8) | data[25];
	TPS1 = (data[29]<<8) | data[28];
	TPS2 = (data[32]<<8) | data[31];

	I_Real = (double)IA_RMS * 1.218 / 324004.0;
	V_Real = (double)V_RMS * 1.218 * 1950.51 / 40764810.0;
	Active_Power_Real = (double)A_WATT * 0.0014023186285365;
	Single_KWh = (double)CFA_CNT * 0.0001633819620263;
}

6.3.6、温湿度传感器软件部分

该部分主要是要弄懂传感器单总线传输的时序问题,软件部分比较简单,我也在讨论区发过帖子,这个就不在这里展开了,贴上主要代码:

void gpio_set_dir(GPIO_Type *gpio, uint32_t pin, uint8_t dir)
{
    gpio_pin_config_t gpio_config = {kGPIO_DigitalOutput, 0, kGPIO_NoIntmode};
    gpio_config.direction = dir;
    GPIO_PinInit(gpio, pin, &gpio_config);
}

#define DHT22_GPIO	GPIO3
#define DHT22_PIN	17U
#define DHT22_PIN_OUT() 	gpio_set_dir(DHT22_GPIO, DHT22_PIN, kGPIO_DigitalOutput)
#define DHT22_PIN_IN() 	    gpio_set_dir(DHT22_GPIO, DHT22_PIN, kGPIO_DigitalInput)
#define DHT22_PIN_HIGH()	GPIO_PinWrite(DHT22_GPIO, DHT22_PIN, 1U)
#define DHT22_PIN_LOW()		GPIO_PinWrite(DHT22_GPIO, DHT22_PIN, 0U)
#define DHT22_PIN_READ()	GPIO_PinRead(DHT22_GPIO, DHT22_PIN)
#define DHT22_DELAY_US(x)	SDK_DelayAtLeastUs(x, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY)
#define DHT22_DELAY_MS(x)	SDK_DelayAtLeastUs(1000*x, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY)
uint8_t buff[5] = {0};
int humidity=0;
int temperature=0;
void dht22_get_data(void)
{
	int i,j;
	uint8_t bit=0;
	uint8_t temp=0;

	DHT22_PIN_OUT(); //SET PA0 OUTPUT
	DHT22_PIN_HIGH(); //拉高
	DHT22_DELAY_MS(100);
	DHT22_PIN_LOW();
	DHT22_DELAY_US(1000);    //拉低1000us
    DHT22_PIN_HIGH(); //DQ=1
    DHT22_DELAY_US(50);     //50US
	DHT22_PIN_IN();
	while(DHT22_PIN_READ())
	{
		DHT22_DELAY_US(1);
	}
	while(!DHT22_PIN_READ())
	{
		DHT22_DELAY_US(1);
	}
	while(DHT22_PIN_READ())
	{
		DHT22_DELAY_US(1);
	}

	for(i=0; i<5; i++)
	{
		temp = 0;
		for(j=0; j<8; j++)
		{
			while(!DHT22_PIN_READ())
			{
				DHT22_DELAY_US(1);
			}
			DHT22_DELAY_US(35);
			if(DHT22_PIN_READ())
			{
				bit = 1;
				while(DHT22_PIN_READ())
				{
					DHT22_DELAY_US(1);
				}
			}else
			{
				bit = 0;
			}
			temp <<= 1;
			temp |= bit;
		}
		buff[i] = temp;
	}
	humidity = buff[0]<<8|buff[1];
	temperature = buff[2]<<8|buff[3];
}
void dht22_main_task(void *param)
{
	while(1)
	{
		tos_knl_sched_lock();
		dht22_get_data();
		tos_knl_sched_unlock();
		tos_task_delay(10000);
	}
}

void dht22_main_start(void)
{
	tos_task_create(&dht22_task, "dht22_task", dht22_main_task, NULL, 2, dht22_stack, dht22_TASK_SIZE, 10);
}

6.3.7、蜂鸣器、电磁门销、继电器部分软件

主要的功能都是等待接收设备状态来直接控制外设的,空气状态的获取并没有新开一个task,我把它归纳到继电器的控制task中,代码实现较为简单,如下:

void beep_main_start(void)
{
    beep_io_init();
    tos_event_create(&beep_event, 0);
	tos_task_create(&beep_task, "beep_task", beep_main_task, NULL, 2, beep_stack, BEEP_TASK_SIZE, 10);
}
void beep_main_task(void *param)
{
    uint32_t type=0;
    int  ret=0;
	while(1)
	{
        ret = tos_event_pend(&beep_event,0xFF,&type,TOS_TIME_FOREVER,TOS_OPT_EVENT_PEND_CLR|TOS_OPT_EVENT_PEND_ANY);
        if(ret != K_ERR_NONE) continue;
        switch(type)
        {
            case 1:
                GPIO_PinWrite(BEEP_GPIO, BEEP_PIN, 1);
                tos_task_delay(100);
                GPIO_PinWrite(BEEP_GPIO, BEEP_PIN, 0);
				printf("beep recv type 1\n");
            break;
            case 2:
				printf("beep recv type 2\n");
            break;
            case 4:
				printf("beep recv type 3\n");
            break;		
        }
	}    			
}
void door_main_start(void)
{
	door_io_init();
	tos_event_create(&door_event, 0);
	tos_task_create(&door_task, "door_task", door_main_task, NULL, 2, door_stack, DOOR_TASK_SIZE, 10);
}
void door_main_task(void* param)
{
	int ret;
	uint32_t onoff;

	while(1)
	{
		ret = tos_event_pend(&door_event,0xFF,&onoff,TOS_TIME_FOREVER,TOS_OPT_EVENT_PEND_CLR|TOS_OPT_EVENT_PEND_ANY);
		if(ret != K_ERR_NONE) continue;

		GPIO_PinWrite(DOOR_GPIO, DOOR_PIN, 0);
		tos_task_delay(1500);
		GPIO_PinWrite(DOOR_GPIO, DOOR_PIN, 1);
	}
}

void relay_main_start(void)
{
	relay_io_init();
	tos_event_create(&relay_event, 0);
	tos_task_create(&relay_task, "relay_task", relay_main_task, NULL, 2, relay_stack, RELAY_TASK_SIZE, 10);
}
void relay_main_task(void* param)
{
	int ret;
	uint32_t onoff;
	while(1)
	{
		ret = tos_event_pend(&relay_event,0xFF,&onoff,1000,TOS_OPT_EVENT_PEND_CLR|TOS_OPT_EVENT_PEND_ANY);

		if(GPIO_PinRead(AIR_GPIO, AIR_PIN))
		{
			air_state = 0;
		}else
		{
			ret = K_ERR_NONE;
			onoff = 0;
			air_state = 1;
			beep_set_type(1);
		}
		if(ret == K_ERR_NONE)
		{
			if(onoff == 1)
			{
				relay_state = 1;
				GPIO_PinWrite(RELAY_GPIO, RELAY_PIN, 0);
			}
			else
			{
				relay_state = 0;
				GPIO_PinWrite(RELAY_GPIO, RELAY_PIN, 1);
			}
		}
	}
}

至此,软件的关键部分,也差不多介绍完了,看起来很多,但是每个功能点独立分好,其实是比较简单的。代码中,虽然简单的控制部分,也将其独立出一个源文件,这样子代码结构比较清晰,能快速定位到你要修改的相应功能,但是实际结构上还是有很多能优化的地方,简单的说,就是上面的几个简单的控制,是可以归纳到一个控制的task中,通过事件组去分别控制就好,这样资源会占用少一点,代码逻辑也更加紧凑。

七、产品使用介绍

7.1、手机app控制界面

APP界面用的是腾讯提供的标准面板,有些控件和一些设备参数属性不太匹配,做到完全匹配需要自己开发面板,涉及到的H5开发暂时还不会,所以将就用着先,还有该面板目前不能显示小数部分。

手机app控制界面

7.2、产品运行过程介绍

产品显示部分的ui比较简洁,设计ui,做到高端美观,这个是需要一定的技术和要花费比较多的时间找素材,剪辑,设计ui,这部分虽然略懂一点点皮毛,但是时间也比较紧促,所以暂时做这一个简洁的界面,将系统的所有参数显示出来即可;

产品运行中的图片

八、总结

硬件部分是本项目中富有挑战的部分,正如人们所说的,七分硬件三分软件,硬件在设计和调试中都比较繁杂,改版与调试,电路的验证,都是会花费许多时间和精力的;本次的比赛作品控制板硬件部分,从一开始方案选型,电路设计和仿真,还是实际电路焊接调试验证方案可行性,到最后画原理图和layout,途中的改版,整板元器件焊接调试,都是我一手完成,时间算下来,真还占了整个项目七成的时间,而且还有完善的余地。此次比赛的作品还算不上成熟的项目,由于时间的原因,途中只改版过一次硬件,硬件整体方案可行性是没问题的,在设计和元器件选型上还有瑕疵,不够完美。而且在产品显示ui上面,还没有时间去美化,腾讯连连的面板涉及到H5的开发,这个暂时还不会,所以app控制面板上直接使用官方提供的标准面板,希望在之后的空余时间自己能将其完善。最后,感谢腾讯和NXP团队联合举办的基于 TencentOS AIoT应用创新大赛,衷心感谢群里各位小伙伴以及腾讯和NXP工程师的帮助。

文末附上个人gitee代码链接:https://gitee.com/zhouqiaowen/tencent-os-aiot-match.git

合智互联客户成功服务热线:400-1565-661

admin
admin管理员

上一篇:迅为国产RK3568开发板Android移植 LCD 屏幕
下一篇:【TRTC小程序】跨房连麦功能实现(不混流实现)

留言评论

暂无留言