产品整体实物图:
产品功能视频展示:
一、设计背景及意义
该设计旨在为家庭的日常生活安全提供额外的保障;
随着科技的发展与进步,人们家庭的智能设备和产品越来越多,接入电网的设备也会越来越多,量变导致质变,家庭设备总用电监控,能极大的帮助人们了解日常用电情况;
该产品的功能,基于以下的问题点进行设计:
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开源物联网操作系统。
- 核心板采用的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 安全系统检测控制一体电路板实物,如下:
4.2.3 安全系统检测控制一体电路板原理图和PCB
五、原理图各功能模块原理介绍
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是为了在断电时,停止给外部设备供电,电池电路只用于应急照明使用,延长使用时间;
六、产品软件部分
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 等),可助力物联网终端设备及业务快速接入腾讯云物联网平台。
该实时操作系统的特点如下:
- 小体积:最小内核 RAM 0.6KB,ROM 1.8KB 典型 LoraWAN 及传感器应用:RAM 3.3KB,ROM 12KB
- 低功耗:休眠最低功耗低至2 uA 支持外设功耗管理框架
- 丰富的 IoT 组件:集成主流IoT协议栈 多种通信模组SAL层适配框架; 支持OTA升级 提供简单易用端云API,加速用户业务接入腾讯云
- 可靠的安全框架:多样化的安全分级方案 均衡安全需求&成本控制
- 良好的可移植性:内核及 IoT 组件高度解耦,提供标准适配层 提供自动化移植工具,提升开发效率
- 便捷的调试手段:提供云化的最后一屏调试功能 故障现场信息自动上传云平台,方便开发人员调试分析
6.3、产品代码实现
6.3.1、软件流程图
6.3.2、主程序入口
代码语言:javascript复制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数据,以及实时打包和上报设备信息到云端;下面附上比较主要的代码;
代码语言:javascript复制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 = %dn", 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 = %dn", 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);
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);
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);
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);
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);
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_tmp0);
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_tmp0);
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, "}}"rn");
LPUART_WriteBlocking(LPUART2, upload_buff, strlen(upload_buff));
}
}
6.3.4、lvgl显示部分
lvgl主入口先对其内核进行初始化,初始化LCD驱动,还有输入的外设驱动,然后显示系统主页面信息,开启lvgl_task为处理lvgl任务以及为其提供时钟基准,开启update_task用于实时更新显示信息;
代码语言:javascript复制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初始化,是用来检测电池电量的,目前将电池电量监测放到电能检测的功能中执行;
代码语言:javascript复制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、温湿度传感器软件部分
该部分主要是要弄懂传感器单总线传输的时序问题,软件部分比较简单,我也在讨论区发过帖子,这个就不在这里展开了,贴上主要代码:
代码语言:javascript复制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中,代码实现较为简单,如下:
代码语言:javascript复制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 1n");
break;
case 2:
printf("beep recv type 2n");
break;
case 4:
printf("beep recv type 3n");
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开发暂时还不会,所以将就用着先,还有该面板目前不能显示小数部分。
7.2、产品运行过程介绍
产品显示部分的ui比较简洁,设计ui,做到高端美观,这个是需要一定的技术和要花费比较多的时间找素材,剪辑,设计ui,这部分虽然略懂一点点皮毛,但是时间也比较紧促,所以暂时做这一个简洁的界面,将系统的所有参数显示出来即可;
八、总结
硬件部分是本项目中富有挑战的部分,正如人们所说的,七分硬件三分软件,硬件在设计和调试中都比较繁杂,改版与调试,电路的验证,都是会花费许多时间和精力的;本次的比赛作品控制板硬件部分,从一开始方案选型,电路设计和仿真,还是实际电路焊接调试验证方案可行性,到最后画原理图和layout,途中的改版,整板元器件焊接调试,都是我一手完成,时间算下来,真还占了整个项目七成的时间,而且还有完善的余地。此次比赛的作品还算不上成熟的项目,由于时间的原因,途中只改版过一次硬件,硬件整体方案可行性是没问题的,在设计和元器件选型上还有瑕疵,不够完美。而且在产品显示ui上面,还没有时间去美化,腾讯连连的面板涉及到H5的开发,这个暂时还不会,所以app控制面板上直接使用官方提供的标准面板,希望在之后的空余时间自己能将其完善。最后,感谢腾讯和NXP团队联合举办的基于 TencentOS AIoT应用创新大赛,衷心感谢群里各位小伙伴以及腾讯和NXP工程师的帮助。
文末附上个人gitee代码链接:https://gitee.com/zhouqiaowen/tencent-os-aiot-match.git