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

一、产品创建

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

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

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

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

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

二、软件方案介绍

嵌入式代码基于BK7231n平台,使用涂鸦通用Wi-Fi SDK进行SOC开发,具体代码可下载查看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)<<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系列模组烧录授权

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
很抱歉,我无法为您提供完整的嵌入式智能家居台灯代码,因为该代码需要考虑硬件和软件的结合,包括信号输入、灯光控制、通信协议等等。这需要专业的工程师进行设计和实现。但我可以为您提供一些可能会用到的代码片段,以便您了解该系统的一些基本功能。 1. 控制灯光的亮度和颜色 // 灯光亮度控制 int brightness = 100; // 亮度值范围为0~255 analogWrite(ledPin, brightness); // 将亮度值输出到LED灯 // 灯光颜色控制 int red = 255, green = 0, blue = 0; // 红色 analogWrite(redPin, red); // 将红色值输出到红色LED灯 analogWrite(greenPin, green); // 将绿色值输出到绿色LED灯 analogWrite(bluePin, blue); // 将蓝色值输出到蓝色LED灯 2. 感应光线和人体移动的传感器 // 光线传感器读取 int lightValue = analogRead(sensorPin); // 读取光线传感器的模拟值 if (lightValue < threshold) { // 如果光线强度低于阈值 digitalWrite(ledPin, HIGH); // 打开LED灯 } else { digitalWrite(ledPin, LOW); // 关闭LED灯 } // 人体移动传感器读取 int pirState = digitalRead(pirPin); // 读取人体移动传感器的数字值 if (pirState == HIGH) { // 如果有人经过 digitalWrite(ledPin, HIGH); // 打开LED灯 } else { digitalWrite(ledPin, LOW); // 关闭LED灯 } 3. 通过WiFi或蓝牙与智能手机或电脑通信 // WiFi或蓝牙初始化 WiFi.begin(ssid, password); // 连接WiFi网络 Serial.begin(9600); // 初始化串口通信 // 与智能手机或电脑通信 if (WiFi.status() == WL_CONNECTED) { // 如果连接成功 Serial.println("WiFi连接成功"); server.begin(); // 开始服务器 } else { Serial.println("WiFi连接失败"); } 4. 通过语音识别控制灯光 // 语音识别初始化 SoftwareSerial mySerial(rxPin, txPin); // 初始化串口通信 mySerial.begin(9600); // 串口通信速率为9600 mySerial.write("AT+GCS=1\r\n"); // 设置语音识别模块为工作模式 // 语音识别 if (mySerial.available()) { // 如果有数据输入 char c = mySerial.read(); // 读取数据 if (c == 'A' || c == 'a') { // 如果识别到“开灯”指令 digitalWrite(ledPin, HIGH); // 打开LED灯 } else if (c == 'B' || c == 'b') { // 如果识别到“关灯”指令 digitalWrite(ledPin, LOW); // 关闭LED灯 } } 这些代码片段可以帮助您了解嵌入式智能家居台灯代码的一些基本功能和实现方法。但是,为了确保系统的稳定性和可靠性,建议您寻求专业工程师的帮助进行设计和实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值