一、产品创建

  • 进入涂鸦智能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 threadtuya_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)<<3)|(tuya_gpio_read(KEY_SET_PIN)<<2)|(tuya_gpio_read(KEY_UP_PIN)<<1)|(tuya_gpio_read(KEY_DOWN_PIN));*trg = (read_data & (read_data ^ (*cont)));*cont = read_data;}

该函数会检测四个按键的按下情况,然后将键值赋值给传参,其中trg是在整个按键按下动作中只出现一次的键值,而cont是只在按键松手后才会复位的键值。通过这两种不同特效的键值,就可以实现长短按和组合键的功能。

按键触发后对应的具体功能实现在lamp_control.c中,分两个函数:

STATIC VOID lamp_key_event(UINT8_T key_event)
{if(key_event == KEY_CODE_SWITCH) {PR_NOTICE("--------------POWER ON!!!!!!!-------------");if(lamp_ctrl_data.Lamp_switch == FALSE) {lamp_ctrl_data.Lamp_switch = TRUE;lamp_pwm_set(lamp_ctrl_data.Light_mode,user_pwm_duty); }else{lamp_ctrl_data.Lamp_switch = FALSE;lamp_pwm_off();    }}else if(key_event == KEY_CODE_SET_LIGHT_COLOR) {lamp_ctrl_data.Light_mode++;if(lamp_ctrl_data.Light_mode > 2){lamp_ctrl_data.Light_mode = 0;}lamp_pwm_set(lamp_ctrl_data.Light_mode,user_pwm_duty);PR_NOTICE("-----------change light mode to %d-------------",lamp_ctrl_data.Light_mode);}else if(key_event == KEY_CODE_UP) {if(user_pwm_duty != 600) {if(user_pwm_duty > 400){user_pwm_duty = 600;}else{user_pwm_duty += 200;}lamp_pwm_set(lamp_ctrl_data.Light_mode,user_pwm_duty);PR_NOTICE("-----------PWM_VALUE UP ONCE-------------");}}else if(key_event == KEY_CODE_DOWN) {if(user_pwm_duty != 0) {if(user_pwm_duty < 200){user_pwm_duty = 0;}else{user_pwm_duty -= 200;}lamp_pwm_set(lamp_ctrl_data.Light_mode,user_pwm_duty);PR_NOTICE("-----------PWM_VALUE DOWN ONCE-------------");}}else if(key_event == KEY_CODE_SET_BEEP) {lamp_ctrl_data.Silent_mode = !lamp_ctrl_data.Silent_mode;PR_NOTICE("-----------SET BEEP-------------");}__ctrl_beep(100);
}VOID lamp_key_poll(VOID)
{app_key_scan(&key_trg,&key_cont);switch (key_cont){case KEY_CODE_RELEASE:if(key_buf != 0) {lamp_key_event(key_buf);}key_buf = 0;key_old = KEY_CODE_RELEASE;break;case KEY_CODE_SWITCH:vTaskDelay(10);app_key_scan(&key_trg,&key_cont);if(key_cont == KEY_CODE_SWITCH) {key_buf = KEY_CODE_SWITCH;}key_old = KEY_CODE_SWITCH;break;case KEY_CODE_SET_LIGHT_COLOR:if(lamp_ctrl_data.Lamp_switch == FALSE) {key_buf = 0;return ;}vTaskDelay(10);app_key_scan(&key_trg,&key_cont);if(key_cont == KEY_CODE_SET_LIGHT_COLOR) {key_buf = KEY_CODE_SET_LIGHT_COLOR;}key_old = KEY_CODE_SET_LIGHT_COLOR;break;case KEY_CODE_UP:if(lamp_ctrl_data.Lamp_switch == FALSE) {key_buf = 0;return ;}if(key_old == KEY_CODE_UP) {key_delay_cont++;}else{key_delay_cont = 0;}if(key_delay_cont >= 2) {key_buf = KEY_CODE_UP;}if(key_delay_cont >= 40) {key_buf = 0;if(user_pwm_duty <= 590) {user_pwm_duty += 10;lamp_pwm_set(lamp_ctrl_data.Light_mode,user_pwm_duty);}}key_old = KEY_CODE_UP;break;case KEY_CODE_DOWN:if(lamp_ctrl_data.Lamp_switch == FALSE) {key_buf = 0;return ;}if(key_old == KEY_CODE_DOWN) {key_delay_cont++;}else{key_delay_cont = 0;}if(key_delay_cont >= 2) {key_buf = KEY_CODE_DOWN;}if(key_delay_cont >= 40) {key_buf = 0;if(user_pwm_duty>=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<50;i++) {if(data[i]=='R') {if((data[i+8] >= '0')&&(data[i+8] <= '9')) {lamp_ctrl_data.Human_distance = ((data[i+7] - '0') * 10) + (data[i+8] - '0');}else {lamp_ctrl_data.Human_distance = (data[i+7] - '0');}PR_NOTICE("--------Human_distance = %d-----------",lamp_ctrl_data.Human_distance);return ;        }    }}
}
  • 在app开启或按键开启坐姿提醒功能的前提下,当距离小于一定数值时,触发坐姿告警:
VOID STATIC lamp_sit_remind(VOID)
{if(lamp_ctrl_data.Sit_remind != TRUE) {alert_count = 0;return ;}if((lamp_ctrl_data.Human_distance <= 5)&&(lamp_ctrl_data.Radar_sensor == TRUE)) {PR_NOTICE("------------enter sit remind-------------");alert_count++;if(alert_count >= 3) {__ctrl_beep(300);}}else{alert_count = 0; }
}
  • 在app开启自动开关灯功能的前提下,当雷达检测到有人靠近时,若当前环境光照强度很弱则自动打开灯光,同时当检测到无人在附近一定时间后,自动关闭灯光:
VOID STATIC lamp_light_control(VOID)
{   if((light_mode_old != lamp_ctrl_data.Light_mode)&&(lamp_ctrl_data.Lamp_switch == TRUE)) {lamp_pwm_set(lamp_ctrl_data.Light_mode,user_pwm_duty);}light_mode_old = lamp_ctrl_data.Light_mode;if(lamp_ctrl_data.Auto_light != TRUE) {return ;}if(lamp_ctrl_data.Radar_sensor == FALSE) {lamp_ctrl_data.Lamp_switch = FALSE;lamp_pwm_off();}else if((lamp_ctrl_data.Human_distance <= DISTANCE_THRESHOLD)&&\\(lamp_ctrl_data.Light_intensity <= LIGHT_INTENSITY_THRESHOLD)) {if(lamp_ctrl_data.Lamp_switch == FALSE) {lamp_ctrl_data.Lamp_switch = TRUE;lamp_pwm_set(lamp_ctrl_data.Light_mode,user_pwm_duty);}}
}

8.编译和烧录

在linux终端输入指令运行SDK环境目录下的build_app.sh脚本来编译代码生成固件,指令格式为 sh build_app.sh APP_PATH APP_NAME APP_VERSION

若出现下图所示提示,则表示编译成功,固件已经生成:

固件生成路径为:apps->APP_PATH->output

将固件烧录至模组即可开始功能调试阶段,有关烧录和授权方式请参照文档: WB系列模组烧录授权

多功能智能感应台灯设计(嵌入式)相关推荐

  1. 多功能智能感应台灯设计(FAQ)

    1.样机设计中遇到充电很慢的原因以及解决办法 多功能智能感应台灯充电很慢的原因分析:当台灯锂电池电量很低时,由于所带负载需要消耗的电流又较大,导致充电的电流无法支撑负载消耗的电流,因此会导致锂电池充电 ...

  2. 多功能智能感应台灯设计(硬件)

    随着智能化的普及,方便人们日常生活应用.在台灯普通照明功能的基础上,我们设计了一种更加自动化.智能化.人性化的台灯.使台灯能够更加贴心,更能懂人所想. 在设计中融合了微波雷达.光照强度等一些传感器,让 ...

  3. 基于51单片机多功能智能心率检测仪设计(毕设课设)

    这是一款基于51单片机的心率检测仪,可以在LCD1602液晶显示屏上显示你的心率,而且误差非常小,代码结构简单,易于理解,非常适合大众. 而且想做毕业设计的同学,我也附上了我自己写的设计报告,可供大家 ...

  4. 基于DSP的智能轮椅仿真设计

    关键词:轮椅  DSP  老龄化  弱势群体  智能 针对需要特殊护理人群的市场需求,设计了一种基于人机工程学的多功能智能轮椅.通过市场调研和社会调查,在老龄化日趋严重的当今社会,现在社会对轮椅需求量 ...

  5. 基于51单片机多功能智能台灯设计 视力灯 坐姿矫正套件 台灯 人体感应 包括实物+电路原理图+程序+proteus仿真

    基于51单片机多功能智能台灯设计 视力灯 坐姿矫正套件 台灯 人体感应 包括实物+电路原理图+程序+proteus仿真 ID:69100646120443268太白路自信的桔梗

  6. 多功能智能台灯(附源码)——用于参加课程设计,电子竞赛等

    多功能智能台灯 一.介绍 二.组件 三.接线表 四.源码 五.现象 一.介绍 该台灯有两种模式,分别为自动模式和手动模式. 自动模式:在自动模式下,可以实现人走灯灭进入待机状态,人来继续进入上一次功能 ...

  7. 基于单片机病房呼叫系统数码管显示房号设计-基于单片机工业生产现场的光照强度控制设计-基于单片机多功能智能台灯设计-基于单片机二维码LCD显示设计-基于单片机多功能时钟闹钟万年历控制系统设计【毕设分享】

    1641基于单片机病房呼叫系统数码管显示房号设计 设计思路:此设计实现的功能通过按键模拟房号,当按键按下时会将房号编号发送到数目管上进行显示,并通过声光进行提示.包含的电路有:数码管显示电路.按键电路 ...

  8. 基于单片机的教室智能照明台灯控制系统的设计与实现

    功能介绍 以51单片机作为主控系统: LCD1602液晶显示当前时间.年月日.时分秒.前教室人数: 2路红外探头用来感应当前教室进出人数: 按键可以设置当前时间.自动手动模式.开启和关闭教室灯光时间: ...

  9. 基于STM32蓝牙控制的app智能台灯设计

    提示:记录毕设 文章目录 前言 一.任务书 1.1设计(研究)目标: 1.2设计(研究)内容: 二.代码思路 三.硬件设计 3.1总体设计 3.2蓝牙部分设计 四.联系我们 五.部分代码 喜欢请点赞哦 ...

最新文章

  1. 如何使用阿里云服务器
  2. opencv上gpu版surf特征点与orb特征点提取及匹配实例
  3. vim 与系统剪切板
  4. java 共享内存获取写权限_跨进程访问共享内存的权限问题
  5. 《HTML5 2D游戏编程核心技术》——第1章,第1.3节特别功能
  6. 三星Galaxy S22 Ultra发布推迟:此前至少5款骁龙8平台旗舰亮相
  7. UNIX 环境高级编程(四)—— dirent.h
  8. Mac安装IntelliJ IDEA配置go语言环境
  9. IOS之UI--自定义按钮实现代理监听点击事件
  10. 错误解决办法:‘NULL’ was not declared in this scope
  11. CSDN如何赚积分及C币
  12. 深入分析一个经典的单片机供电电路
  13. Android开发-视图view讲解
  14. mysql isnum()_mysql 一些基础的语法和命令
  15. 修改Chrome默认的搜索引擎
  16. ppt中加入html,如何在ppt中插入html网页.ppt
  17. blt功能_BitBlt介绍
  18. 开源免费的图片压缩软件,从50M到50K,极力安利
  19. 驱动 | Linux | NVMe | 2. nvme_probe
  20. TuneUp2007修改启动界面的教程

热门文章

  1. 内网服务器通过代理服务器访问外网
  2. 机器视觉:GIGE VISION 2.0标准和多相机应用
  3. Java算法篇之打印小星星和成绩名次统计
  4. 新手上路,嘀嘀嘀。。
  5. starling性能优化
  6. Dubbo接口测试原理及多种方法
  7. openstack的分布式应用工具包Tooz
  8. 抖音 关注动画 android,Android仿抖音、微视关注动画
  9. 用于PCBA插件焊接电磁焊锡泵优点介绍
  10. 奥的斯电梯tt服务器使用表_OTIS TT电梯操作器操作指南