多功能智能感应台灯设计(嵌入式)

您所在的位置:网站首页 智能台灯介绍文案 多功能智能感应台灯设计(嵌入式)

多功能智能感应台灯设计(嵌入式)

2024-06-25 21:55| 来源: 网络整理| 查看: 265

一、产品创建

进入涂鸦智能IoT平台,点击创建产品,选择照明->氛围照明->台灯。 创建产品

选择自定义方案,输入产品名称,选择通讯协议为WIFI+蓝牙,点击创建产品。

根据要实现的设备功能,创建好DP功能点。 创建功能点

设定完功能点后,下一步点击设备面板,选择app的面板样式。推荐选择开发调试面板,比较直观,且可以开到dp数据包的接收和发送,方便开发阶段调试使用。

至此,产品的创建基本完成,可以正式开始嵌入式软件部分的开发。

二、软件方案介绍

嵌入式代码基于BK7231n平台,使用涂鸦通用Wi-Fi SDK进行SOC开发,具体代码可下载查看demo例程。

本demo完整例程:点击获取 1.应用层入口

打开demo例程,其中的apps文件夹内就是demo的应用代码。应用代码结构如下:

├── src | ├── app_driver | | ├── lamp_pwm.c //台灯PWM驱动相关文件 | | ├── sh1106.c //OLED屏驱动相关文件 | | ├── bh1750.c //光照强度传感器驱动相关文件 | | └── app_key.c //触摸按键相关代码文件 | ├── app_soc //tuya SDK soc层接口相关文件 | ├── tuya_device.c //应用层入口文件 | ├── app_lamp.c //主要应用层 | └── lamp_control.c //按键相关逻辑 | ├── include //头文件目录 | ├── app_driver | | ├── lamp_pwm.h | | ├── sh1106.h | | ├── bh1750.h | | └── app_key.h | ├── app_soc | ├── tuya_device.h | ├── app_lamp.h | └── lamp_control.h | └── output //编译产物

打开tuya_device.c文件,找到device_init函数:

OPERATE_RET device_init(VOID_T) { OPERATE_RET op_ret = OPRT_OK; TY_IOT_CBS_S wf_cbs = { status_changed_cb,\ gw_ug_inform_cb,\ gw_reset_cb,\ dev_obj_dp_cb,\ dev_raw_dp_cb,\ dev_dp_query_cb,\ NULL, }; op_ret = tuya_iot_wf_soc_dev_init_param(WIFI_WORK_MODE_SEL, WF_START_SMART_FIRST, &wf_cbs, NULL, PRODECT_ID, DEV_SW_VERSION); if(OPRT_OK != op_ret) { PR_ERR("tuya_iot_wf_soc_dev_init_param error,err_num:%d",op_ret); return op_ret; } op_ret = tuya_iot_reg_get_wf_nw_stat_cb(wf_nw_status_cb); if(OPRT_OK != op_ret) { PR_ERR("tuya_iot_reg_get_wf_nw_stat_cb is error,err_num:%d",op_ret); return op_ret; } op_ret = app_lamp_init(APP_LAMP_NORMAL); if(OPRT_OK != op_ret) { PR_ERR("app init err!"); return op_ret; } return op_ret; }

在BK7231平台的SDK环境中,该函数为重要的应用代码入口,设备上电后平台适配层运行完一系列初始化代码后就会调用该函数来进行应用层的初始化操作。

该函数做了三件事:

调用tuya_iot_wf_soc_dev_init_param()接口进行SDK初始化,配置了工作模式、配网模式,同时注册了各种回调函数并存入了PID(代码中宏定义为PRODECT_KEY)。 TY_IOT_CBS_S wf_cbs = { status_changed_cb,\ gw_ug_inform_cb,\ gw_reset_cb,\ dev_obj_dp_cb,\ dev_raw_dp_cb,\ dev_dp_query_cb,\ NULL, }; op_ret = tuya_iot_wf_soc_dev_init_param(WIFI_WORK_MODE_SEL, WF_START_SMART_FIRST, &wf_cbs, NULL, PRODECT_ID, DEV_SW_VERSION); if(OPRT_OK != op_ret) { PR_ERR("tuya_iot_wf_soc_dev_init_param error,err_num:%d",op_ret); return op_ret; } 调用tuya_iot_reg_get_wf_nw_stat_cb()接口注册设备网络状态回调函数。 op_ret = tuya_iot_reg_get_wf_nw_stat_cb(wf_nw_status_cb); if(OPRT_OK != op_ret) { PR_ERR("tuya_iot_reg_get_wf_nw_stat_cb is error,err_num:%d",op_ret); return op_ret; } 调用应用层初始化函数 op_ret = app_lamp_init(APP_LAMP_NORMAL); if(OPRT_OK != op_ret) { PR_ERR("app init err!"); return op_ret; } 2.应用结构

本demo应用代码主要分三层来实现:

最底层为一些外设、传感器的驱动代码,例如光照传感器、OLED屏幕、微波雷达、触摸按键、灯板等,封装出常用接口;第二层为控制逻辑部分的代码,调用驱动层的各类接口,实现各个组件的控制逻辑,封装出数据处理轮询接口;第一层为主要应用层,创建应用任务调用第二层的接口,同时处理DP点数据的上报和接受解析。

第一层就是在app_lamp.c文件中实现的,大致内容如下:

app_lamp_init() 调用第二层封装出的设备初始化接口,创建应用任务; OPERATE_RET app_lamp_init(IN APP_LAMP_MODE mode) { OPERATE_RET op_ret = OPRT_OK; if(APP_LAMP_NORMAL == mode) { lamp_device_init(); //create ADC sensor data collection thread tuya_hal_thread_create(NULL, "thread_data_get", 512*4, TRD_PRIO_4, sensor_data_get_thread, NULL); tuya_hal_thread_create(NULL, "thread_data_deal", 512*4, TRD_PRIO_4, sensor_data_deal_thread, NULL); tuya_hal_thread_create(NULL, "key_scan_thread", 512*4, TRD_PRIO_4, key_scan_thread, NULL); tuya_hal_thread_create(NULL, "thread_data_report", 512*4, TRD_PRIO_4, sensor_data_report_thread, NULL); }else { //not factory test mode } return op_ret; } app_report_all_dp_status()用于上报所有DP数据: VOID app_report_all_dp_status(VOID) { OPERATE_RET op_ret = OPRT_OK; INT_T dp_cnt = 0; dp_cnt = 5; TY_OBJ_DP_S *dp_arr = (TY_OBJ_DP_S *)Malloc(dp_cnt*SIZEOF(TY_OBJ_DP_S)); if(NULL == dp_arr) { PR_ERR("malloc failed"); return; } memset(dp_arr, 0, dp_cnt*SIZEOF(TY_OBJ_DP_S)); dp_arr[0].dpid = DPID_DELAY_OFF; dp_arr[0].type = PROP_BOOL; dp_arr[0].time_stamp = 0; dp_arr[0].value.dp_value = lamp_ctrl_data.Lamp_delay_off; dp_arr[1].dpid = DPID_LIGHT_MODE; dp_arr[1].type = PROP_ENUM; dp_arr[1].time_stamp = 0; dp_arr[1].value.dp_value = lamp_ctrl_data.Light_mode; dp_arr[2].dpid = DPID_SIT_REMIND; dp_arr[2].type = PROP_BOOL; dp_arr[2].time_stamp = 0; dp_arr[2].value.dp_value = lamp_ctrl_data.Sit_remind; dp_arr[3].dpid = DPID_AUTO_LIGHT; dp_arr[3].type = PROP_BOOL; dp_arr[3].time_stamp = 0; dp_arr[3].value.dp_value = lamp_ctrl_data.Auto_light; dp_arr[4].dpid = DPID_LOW_POW_ALARM; dp_arr[4].type = PROP_BOOL; dp_arr[4].time_stamp = 0; dp_arr[4].value.dp_value = lamp_ctrl_data.Low_pow_alarm; op_ret = dev_report_dp_json_async(NULL,dp_arr,dp_cnt); Free(dp_arr); if(OPRT_OK != op_ret) { PR_ERR("dev_report_dp_json_async relay_config data error,err_num",op_ret); } PR_DEBUG("dp_query report_all_dp_data"); return; } 任务函数,任务内循环调用的lamp_get_sensor_data()、lamp_key_poll()、lamp_ctrl_handle()都是第二层的接口,实现在lamp_control.c文件中,分别负责传感器数据的采集,触摸按键扫描轮询及数据处理和功能逻辑实现轮询: STATIC VOID sensor_data_get_thread(PVOID_T pArg) { while(1) { PR_DEBUG("sensor_data_get_thread"); lamp_get_sensor_data(); tuya_hal_system_sleep(TASKDELAY_SEC/2); } } STATIC VOID key_scan_thread(PVOID_T pArg) { while(1) { lamp_key_poll(); tuya_hal_system_sleep(25); } } STATIC VOID sensor_data_deal_thread(PVOID_T pArg) { while(1) { lamp_ctrl_handle(); tuya_hal_system_sleep(TASKDELAY_SEC); } } STATIC VOID sensor_data_report_thread(PVOID_T pArg) { while(1) { tuya_hal_system_sleep(TASKDELAY_SEC*10); app_report_all_dp_status(); } } deal_dp_proc()处理接受到的DP数据,通过识别DP id来进行相应的数据接收处理: VOID deal_dp_proc(IN CONST TY_OBJ_DP_S *root) { UCHAR_T dpid; dpid = root->dpid; PR_DEBUG("dpid:%d",dpid); switch (dpid) { case DPID_DELAY_OFF: PR_DEBUG("set led switch:%d",root->value.dp_bool); lamp_ctrl_data.Lamp_delay_off = root->value.dp_bool; break; case DPID_LIGHT_MODE: PR_DEBUG("set light mode:%d",root->value.dp_enum); lamp_ctrl_data.Light_mode = root->value.dp_enum; break; case DPID_SIT_REMIND: PR_DEBUG("set sit remind switch:%d",root->value.dp_bool); lamp_ctrl_data.Sit_remind = root->value.dp_bool; break; case DPID_AUTO_LIGHT: PR_DEBUG("set auto switch:%d",root->value.dp_bool); lamp_ctrl_data.Auto_light = root->value.dp_bool; break; case DPID_LOW_POW_ALARM: PR_DEBUG("set low power alarm switch:%d",root->value.dp_bool); lamp_ctrl_data.Low_pow_alarm = root->value.dp_bool; break; default: break; } app_report_all_dp_status(); return; }

实现了上述的几个函数后,应用层代码的大概结构就已经确定下来了,接下来就需要实现上面提到的被调用的第二层接口,这些接口都放在本demo的lamp_control.c文件中。在下面的内容里,本篇文档将根据实现的具体功能解说demo例程。

3.触摸按键

​ 本demo硬件设计上留出了四个触摸按键,而需要实现灯的开关、灯光颜色切换、档位调光和无极调光、静音模式开启关闭、延时关灯等等按键功能,仅靠四个按键单一的一次触发对应一种功能是远远不够的。因此,需要实现长按、短按和组合键三种不同的触发方式来应对多种按键功能。

​ 在app_key.c文件中,封装了app_key_init()、app_key_scan()两个函数。app_key_init()用于初始化按键IO,app_key_scan()用于扫描按键按下情况获取键值。

void app_key_scan(unsigned char *trg,unsigned char *cont) { unsigned char read_data = 0x00; read_data = (tuya_gpio_read(KEY_SWITCH_PIN)=10) { user_pwm_duty -= 10; lamp_pwm_set(lamp_ctrl_data.Light_mode,user_pwm_duty); } } key_old = KEY_CODE_DOWN; break; case KEY_CODE_SET_BEEP: vTaskDelay(10); app_key_scan(&key_trg,&key_cont); if(key_cont == KEY_CODE_SET_BEEP) { key_buf = KEY_CODE_SET_BEEP; } break; case KEY_CODE_DELAY_OFF: break; default: break; } } 4.时间显示

​ 本demo通过tuya SDK的接口可以在联网后获取本地时间,并显示在OLED屏幕上。屏幕SH1106的驱动和封装的接口都在sh1106.c文件中,以软件实现的iic来驱动屏幕。封装的接口有以下几个:

tuya_sh1106_init()屏幕驱动初始化,传参为指定做为SDA、SCL脚的IO口: UCHAR_T tuya_sh1106_init(sh1106_init_t* param) { UCHAR_T error = 0; int opRet = -1; i2c_pin_t i2c_config = { .ucSDA_IO = param ->SDA_PIN, .ucSCL_IO = param ->SCL_PIN, }; opRet = opSocI2CInit(&i2c_config); /* SDA&SCL GPIO INIT */ PR_NOTICE("SocI2CInit = %d",opRet); UCHAR_T i; for(i = 0; i < 25; i++) { sh1106_send_cmd(oled_init_cmd[i]); } } tuya_sh1106_full()、tuya_sh1106_clear()显示屏幕全亮或清空显示内容: VOID tuya_sh1106_full(VOID) { UCHAR_T i,j,k; UCHAR_T *p; for(i = 0; i < 4; i++) { for(j = 0; j < 16; j++) { OLED_GRAM[i][j] = full_buff; } } for(i = 0; i < OLED_PAGE_NUMBER; i++) { sh1106_page_set(i); sh1106_column_set(0); for(j = 0; j < (OLED_COLUMN_NUMBER/8); j++) { p = OLED_GRAM[i][j]; for(k = 0; k < 8; k++) { sh1106_send_data(*p); p++; } } } } VOID tuya_sh1106_clear(VOID) { UCHAR_T i,j,k; UCHAR_T *p; for(i = 0; i < 4; i++) { for(j = 0; j < 16; j++) { OLED_GRAM[i][j] = clear_buff; } } for(i = 0; i < OLED_PAGE_NUMBER; i++) { sh1106_page_set(i); sh1106_column_set(0); for(j = 0; j < (OLED_COLUMN_NUMBER/8); j++) { p = OLED_GRAM[i][j]; for(k = 0; k < 8; k++) { sh1106_send_data(*p); p++; } } } } tuya_sh1106_gram_point_set()按坐标点更改显存内容,改变将要显示的图案,传参分别为页数、行数及字模缓存数组的首地址: VOID tuya_sh1106_gram_point_set(UCHAR_T x, UCHAR_T y, CONST UCHAR_T *ptr_pic) { UCHAR_T i; UCHAR_T *p; if((x < 4)&&(y < 16)) { OLED_GRAM[x][y] = ptr_pic; } p = OLED_GRAM[x][y]; sh1106_page_set(x); sh1106_column_set(y*8); for(i = 0; i < 8; i++) { sh1106_send_data(*p); p++; } } tuya_sh1106_display()根据显存内容显示图像: VOID tuya_sh1106_gram_point_set(UCHAR_T x, UCHAR_T y, CONST UCHAR_T *ptr_pic) { UCHAR_T i; UCHAR_T *p; if((x < 4)&&(y < 16)) { OLED_GRAM[x][y] = ptr_pic; } p = OLED_GRAM[x][y]; sh1106_page_set(x); sh1106_column_set(y*8); for(i = 0; i < 8; i++) { sh1106_send_data(*p); p++; } } tuya_sh1106_on()、tuya_sh1106_off()点亮屏幕和熄灭屏幕,由于屏幕的点亮和熄灭需要时间,所以调用前后需要至少150ms的延时: VOID tuya_sh1106_on(VOID) { sh1106_send_cmd(0x8D); sh1106_send_cmd(0x14); sh1106_send_cmd(0xAF); } VOID tuya_sh1106_off(VOID) { sh1106_send_cmd(0x8D); sh1106_send_cmd(0x10); sh1106_send_cmd(0xAE); }

完成屏幕驱动后,就可以使用取模软件将时间转换出字模来显示。

要获取本地时间,首先需包含头文件uni_time.h。定义一个本地时间结构体变量,然后作为传参调用uni_local_time_get()接口获取时间: POSIX_TM_S cur_time; if( uni_local_time_get(&cur_time) != OPRT_OK ) { PR_NOTICE("cant get local time"); } lamp_ctrl_data.time_hour = cur_time.tm_hour; lamp_ctrl_data.time_min = cur_time.tm_min; 按位解析时间,将对应字模缓存传入显存,然后开启显示: for(i = 4; i < 8; i++) { tuya_sh1106_gram_point_set(0,i,&diplay_buffer_time[(i+14)*OLED_PIX_HEIGHT]); tuya_sh1106_gram_point_set(1,i,&diplay_buffer_time[(i+14)*OLED_PIX_HEIGHT+8]); } if(lamp_ctrl_data.time_hour < 10) { tuya_sh1106_gram_point_set(0,9,&diplay_buffer_time[0]); tuya_sh1106_gram_point_set(1,9,&diplay_buffer_time[8]); }else { tuya_sh1106_gram_point_set(0,9,&diplay_buffer_time[(lamp_ctrl_data.time_hour/10)*OLED_PIX_HEIGHT]); tuya_sh1106_gram_point_set(1,9,&diplay_buffer_time[(lamp_ctrl_data.time_hour/10)*OLED_PIX_HEIGHT+8]); } tuya_sh1106_gram_point_set(0,10,&diplay_buffer_time[(lamp_ctrl_data.time_hour%10)*OLED_PIX_HEIGHT]); tuya_sh1106_gram_point_set(1,10,&diplay_buffer_time[(lamp_ctrl_data.time_hour%10)*OLED_PIX_HEIGHT+8]); //flicker effect of ':' tuya_sh1106_gram_point_set(0,11,&diplay_buffer_time[10*OLED_PIX_HEIGHT]); tuya_sh1106_gram_point_set(1,11,&diplay_buffer_time[10*OLED_PIX_HEIGHT+8]); if(lamp_ctrl_data.time_min < 10) { tuya_sh1106_gram_point_set(0,12,&diplay_buffer_time[0]); tuya_sh1106_gram_point_set(1,12,&diplay_buffer_time[8]); }else { tuya_sh1106_gram_point_set(0,12,&diplay_buffer_time[(lamp_ctrl_data.time_min/10)*OLED_PIX_HEIGHT]); tuya_sh1106_gram_point_set(1,12,&diplay_buffer_time[(lamp_ctrl_data.time_min/10)*OLED_PIX_HEIGHT+8]); } tuya_sh1106_gram_point_set(0,13,&diplay_buffer_time[(lamp_ctrl_data.time_min%10)*OLED_PIX_HEIGHT]); tuya_sh1106_gram_point_set(1,13,&diplay_buffer_time[(lamp_ctrl_data.time_min%10)*OLED_PIX_HEIGHT+8]); tuya_sh1106_display(); 5.电量显示和低电告警

​ 本 demo 通过ADC读取电池电压的1/2分压,根据读到的ADC值换算回电压值并将剩余电压用百分比的方式显示在OLED屏幕上,同时当电压值低于一定水平时驱动蜂鸣器实现低电量告警。

调用tuya hal接口,初始化adc,获取adc值: USHORT_T adc_value = 0; float adc_voltage = 0.0; tuya_hal_adc_init(&tuya_adc); tuya_hal_adc_value_get(TEMP_ADC_DATA_LEN, &adc_value); PR_NOTICE("------------------adc_value = %d----------------",adc_value); adc_voltage = 2.4*((float)adc_value/2048); PR_NOTICE("------------------adc_voltage = %f----------------",adc_voltage); tuya_hal_adc_finalize(&tuya_adc); 根据计算出的电压值估算电池大概的剩余电量,并在低电量时驱动蜂鸣器: if(adc_voltage > 1.95) { lamp_ctrl_data.Battery_remain = 100; return ; } if(adc_voltage > 1.92) { lamp_ctrl_data.Battery_remain = 80; return ; } if(adc_voltage > 1.89) { lamp_ctrl_data.Battery_remain = 60; return ; } if(adc_voltage > 1.86) { lamp_ctrl_data.Battery_remain = 40; return ; } if(adc_voltage > 1.8) { lamp_ctrl_data.Battery_remain = 20; if(lamp_ctrl_data.Low_pow_alarm) { __ctrl_beep(300); } return ; } 根据剩余电量,解析出对应字模,显示在屏幕上: STATIC VOID lamp_display_power(VOID) { if(lamp_ctrl_data.Lamp_switch != TRUE) { return ; } UCHAR_T i = 0; for(i = 4; i < 9; i++) { tuya_sh1106_gram_point_set(2,i,&diplay_buffer_time[(i+8)*OLED_PIX_HEIGHT]); tuya_sh1106_gram_point_set(3,i,&diplay_buffer_time[(i+8)*OLED_PIX_HEIGHT+8]); } //flicker effect of ':' tuya_sh1106_gram_point_set(2,9,&diplay_buffer_time[17*OLED_PIX_HEIGHT]); tuya_sh1106_gram_point_set(3,9,&diplay_buffer_time[17*OLED_PIX_HEIGHT+8]); if(lamp_ctrl_data.Battery_remain == 100) { tuya_sh1106_gram_point_set(2,10,&diplay_buffer_time[OLED_PIX_HEIGHT]); tuya_sh1106_gram_point_set(3,10,&diplay_buffer_time[OLED_PIX_HEIGHT+8]); tuya_sh1106_gram_point_set(2,11,&diplay_buffer_time[0]); tuya_sh1106_gram_point_set(3,11,&diplay_buffer_time[8]); tuya_sh1106_gram_point_set(2,12,&diplay_buffer_time[0]); tuya_sh1106_gram_point_set(3,12,&diplay_buffer_time[8]); }else { tuya_sh1106_gram_point_set(2,10,&diplay_buffer_time[17*OLED_PIX_HEIGHT]); tuya_sh1106_gram_point_set(3,10,&diplay_buffer_time[17*OLED_PIX_HEIGHT+8]); tuya_sh1106_gram_point_set(2,11,&diplay_buffer_time[(lamp_ctrl_data.Battery_remain/10)*OLED_PIX_HEIGHT]); tuya_sh1106_gram_point_set(3,11,&diplay_buffer_time[(lamp_ctrl_data.Battery_remain/10)*OLED_PIX_HEIGHT+8]); tuya_sh1106_gram_point_set(2,12,&diplay_buffer_time[(lamp_ctrl_data.Battery_remain%10)*OLED_PIX_HEIGHT]); tuya_sh1106_gram_point_set(3,12,&diplay_buffer_time[(lamp_ctrl_data.Battery_remain%10)*OLED_PIX_HEIGHT+8]); } tuya_sh1106_gram_point_set(2,13,&diplay_buffer_time[11*OLED_PIX_HEIGHT]); tuya_sh1106_gram_point_set(3,13,&diplay_buffer_time[11*OLED_PIX_HEIGHT+8]); } 6.光照传感器

​ 为了实现后续的自动开关灯功能,硬件上还需借助光照传感器来检测当前的亮暗程度,从而判断当前是否需要允许自动开灯。选用的传感器型号为BH1750,通过I2C协议与SOC进行通信,相关接口封装都在bh1750.c文件中。模块具体使用流程如下:

调用tuya_bh1750_init初始化模组: VOID lamp_device_init(VOID) { ...... tuya_bh1750_init(&bh1750_int_param); ...... } 调用tuya_bh1750_get_bright_value获取光照强度值: VOID lamp_light_detect(VOID) { lamp_ctrl_data.Light_intensity = tuya_bh1750_get_bright_value(); PR_NOTICE("light_intensity_value = %d",lamp_ctrl_data.Light_intensity); } 7.坐姿检测和自动开关灯

​ 本 demo 采用的微波雷达通过串口不间断的向soc发送包含运动状态、距离和能量的不定长字符串,而soc则根据这些参数来实现简易的坐姿检测,并配合环境光照强度实现自动开关灯。

通过检索特定的符号字符来读取需要的参数: VOID lamp_get_sensor_data(VOID) { UCHAR_T data[50]; memset(data, 0, sizeof(data)); CHAR_T opt; opt = get_radar_data(data,50); if(opt == 0){ UCHAR_T i; if((data[0] == 'S')&&(data[6] == ':')) { if(data[8] == '[') { lamp_ctrl_data.Radar_sensor = TRUE; }else { lamp_ctrl_data.Radar_sensor = FALSE; lamp_ctrl_data.Human_distance = 0; PR_NOTICE("--------NO MAN AROUND-----------"); } } if(lamp_ctrl_data.Radar_sensor == FALSE) { return ; } for(i=0;i= '0')&&(data[i+8]


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3