改造智能风扇之——BLDC风扇改造软件篇

一、开发前准备
本次软件开发主要是基于涂鸦CBU模组,需要先授权再接入涂鸦IoT云平台,刚好涂鸦的最近的活动比较多,可以参加涂鸦的活动获(bai)取(piao)模组。
涂鸦的SDK编译一般是在Linux下进行编译开发的,建议先安装虚拟机,windows也可以的,但是编译速度有点慢。

下面是开发中会使用到的平台和资料库:
涂鸦IoT云平台
CBU开发SDK环境
本项目代码GitHub地址
CBU模组规格书

二、功能规划

序号功能
1三种模式风(正常风,自然风:忽大忽小间隔15秒,睡眠风:每隔一小时自动降档,最后降到最低档)
2编码器旋转控制风扇,顺时针旋转风速+,逆时针旋转风速- 。
3编码器按钮短按切换模式,长按设备复位。
44颗指示灯显示风速,4颗指示灯指示8档风速,闪烁代表1档,常量代表2档。4颗指示灯还复用本地定时指示。
5本地定时功能,时间到自动关机。定时按键:无定时->1小时->2小时->3小时->4小时->无定时。
6电源按键,风扇工作状态按下后关闭风扇,风扇处于关闭状态按下后打开风扇。
7长按 WiFi 按键,设备进入配网模式。
8LED有8颗,除指示风速4颗LED外,还有4颗指示灯。一颗 WiFi 指示灯,指示 WiFi 状态;其他三颗指示当前风扇模式
9led指示灯亮度可通过APP调整,正常亮度,较暗亮度。
10断电记忆

三、产品创建
涂鸦IoT平台 ,创建产品->标准类目->小家电->风扇->风扇->填入产品信息->选择DP点->选择面板->选择涂鸦标准模组SDK开发,选择对应的模组。

代码编译完成后将生成的固件上传,具体信息填入,参考该链接下文档 。上传完固件后,后期也可以更新固件进行OTA,这个功能用起来也是十分方便的。

四、烧录、授权
前面说过了如果想上涂鸦云是模组是需要授权的,那么如何烧录授权呢?这是关于WB系列模组烧录的介绍,这里在开发过程中强烈推荐使用烧录授权分立方案,即烧录UA(应用区)固件。
QIO、UA、UG文件是什么意思上面的官方文档里已经有了详细的介绍,我这里就不再复述了。
芯片烧录工具下载地址
在这里插入图片描述
五、功能开发
1、配网及配网指示灯显示
调用该函数tuya_iot_wf_gw_unactive() 以进入配网模式,涂鸦 SDK 对于网络状态的定义有以下几种:

typedef BYTE_T GW_WIFI_NW_STAT_E;
#define STAT_LOW_POWER          0   // idle status,use to external config network
#define STAT_UNPROVISION        1   // smart config status
#define STAT_AP_STA_UNCFG       2   // ap WIFI config status
#define STAT_AP_STA_DISC        3   // ap WIFI already config,station disconnect
#define STAT_AP_STA_CONN        4   // ap station mode,station connect
#define STAT_STA_DISC           5   // only station mode,disconnect
#define STAT_STA_CONN           6   // station mode connect
#define STAT_CLOUD_CONN         7   // cloud connect
#define STAT_AP_CLOUD_CONN      8   // cloud connect and ap start
#define STAT_REG_FAIL           9   // register fail
#define STAT_OFFLINE            10   // offline
#define STAT_MQTT_ONLINE        11
#define STAT_MQTT_OFFLINE       12
#define STAT_UNPROVISION_AP_STA_UNCFG		13 //smart-cfg and ap-cfg concurrent config status

长按进入配网模式功能实现:

STATIC VOID wifi_key_process(TY_GPIO_PORT_E port,PUSH_KEY_TYPE_E type,INT_T cnt)
{
    PR_DEBUG("port:%d,type:%d,cnt:%d",port,type,cnt);
    OPERATE_RET op_ret = OPRT_OK;
    UCHAR_T ucConnectMode = 0;

    if (port = WIFI_KEY_PIN) {
        if (LONG_KEY == type) { //press long enter linking network
            PR_NOTICE("key long press");
            /* 手动移除设备 */
            tuya_iot_wf_gw_unactive();
        } else if (NORMAL_KEY == type) {
            PR_NOTICE("key normal press");
        } else {
            PR_NOTICE("key type is no deal");
        }
    }

    return;
}

STATIC VOID wifi_config_init(VOID)
{
    OPERATE_RET op_ret = OPRT_OK;

    /* LED 相关初始化 */ 
    tuya_gpio_inout_set(WIFI_LED_PIN, FALSE);
    tuya_set_led_light_type(wifi_led_handle, OL_HIGH, 0, 0); //关闭 led

    /* LED 相关初始化 */ 
    op_ret = tuya_create_led_handle(WIFI_LED_PIN, TRUE, &wifi_led_handle);
    if (op_ret != OPRT_OK) {
        PR_ERR("key_init err:%d", op_ret);
        return;
    }
    tuya_set_led_light_type(wifi_led_handle, OL_HIGH, 0, 0);

    /* 按键相关初始化 */
    KEY_USER_DEF_S key_def;

    op_ret = key_init(NULL, 0, WIFI_KEY_TIMER_MS);
    if (op_ret != OPRT_OK) {
        PR_ERR("key_init err:%d", op_ret);
        return;
    }

    /* 初始化 key 相关参数 */
    memset(&key_def, 0, SIZEOF(key_def));
    key_def.port = WIFI_KEY_PIN;                            //按键引脚
    key_def.long_key_time = WIFI_KEY_LONG_PRESS_MS;         //长按时间配置
    key_def.low_level_detect = WIFI_KEY_LOW_LEVEL_ENABLE;   //TRUE:低电平算按下,FALSE:高电平算按下
    key_def.lp_tp = LP_ONCE_TRIG;   //
    key_def.call_back = wifi_key_process;                   //按键按下后回调函数
    key_def.seq_key_detect_time = WIFI_KEY_SEQ_PRESS_MS;    //连按间隔时间配置

    /* 注册按键 */
    op_ret = reg_proc_key(&key_def);
    if (op_ret != OPRT_OK) {
        PR_ERR("reg_proc_key err:%d", op_ret);
    }

    return;
}

这里对于wifi 状态的做出的提示如下:

STATIC VOID wifi_state_led_reminder(IN CONST GW_WIFI_NW_STAT_E cur_stat)
{
    switch (cur_stat)
    {
        case STAT_LOW_POWER:    //wifi 连接超时,进入低功耗模式
            tuya_set_led_light_type(wifi_led_handle, OL_HIGH, 0, 0); //关闭提示灯
        break;

        case STAT_UNPROVISION: //SamrtConfig 配网模式,等待连接
            tuya_set_led_light_type(wifi_led_handle, OL_FLASH_HIGH, WIFI_LED_FAST_FLASH_MS, 0xffff); //led 快闪
        break;

        case STAT_AP_STA_UNCFG: //ap 配网模式,等待连接
            tuya_set_led_light_type(wifi_led_handle, OL_FLASH_HIGH, WIFI_LED_LOW_FLASH_MS, 0xffff); //led 慢闪
        break;

        case STAT_AP_STA_DISC:
        case STAT_STA_DISC:     //SamrtConfig/ap 正在连接中
            tuya_set_led_light_type(wifi_led_handle, OL_HIGH, 0, 0); //关闭 led 
        break;

        case STAT_CLOUD_CONN:
        case STAT_AP_CLOUD_CONN: //连接到涂鸦云
            tuya_set_led_light_type(wifi_led_handle, OL_LOW, 0, 0); //led 常量
        break;

        default:
        break;
    }
}

2、风扇模式功能开发:
由于是通过 PWM 对 BLDC 进行控制,所以风扇的控制函数如下:

VOID_T fan_speed_set(UINT_T speed)
{
    UINT_T  fan_speed_pwm_duty_cycle = 0;

    if (speed <= 0) {
        vSocPwmSetDuty(BLDC_PWM_ID, (BLDC_PWM_FAN_OFF));
        return;
    }

    //由于电机在30%以下工作时间过长会出现异常,这里对 PWM 输出进行一些处理,使输出的 PWM 在 30%-99% 之间
    fan_speed_pwm_duty_cycle = (UINT_T)(BLDC_PWM_FAN_MIN + ((BLDC_PWM_FAN_MAX - BLDC_PWM_FAN_MIN) * (speed / 100.0)));

    vSocPwmSetDuty(BLDC_PWM_ID, (fan_speed_pwm_duty_cycle));

    return;
}

普通模式:

static VOID_T fan_mode_normal(VOID_T)
{
    INT_T opRet = LIGHT_OK;

    //关闭睡眠模式的定时器,防止干扰普通模式的运行
    opRet = opSocSWTimerStop(SLEEP_MODE_TIMER);
    if (opRet != LIGHT_OK) {
        PR_ERR("stop sleep timer error");
    }

    //关闭自然模式的定时器,防止干扰普通模式的运行
    opRet = opSocSWTimerStop(NATURAL_MODE_TIMER);
    if (opRet != LIGHT_OK) {
        PR_ERR("stop natural timer error");
    }

    fan_speed_set(fan_state.speed);
    PR_NOTICE("+++ normal mode fan_state.speed : %d", fan_state.speed);
}

自然风模式:

static VOID_T fan_mode_natural_timer_cb(VOID_T)
{
    //如果关机,不执行任何操作
    if (fan_state.on_off == FALSE) {
        opSocSWTimerStop(NATURAL_MODE_TIMER);
        return;
    }

    if (natural_speed_low_flag) {
        PR_NOTICE("natural mode low speed");
        fan_speed_set(1);
    } else {
        PR_NOTICE("natural mode high speed");
        fan_speed_set(fan_state.speed);
    }
    natural_speed_low_flag = ~(natural_speed_low_flag);
    opSocSWTimerStart(NATURAL_MODE_TIMER, NATURAL_SPEED_CHANGE_TIME * 1000, fan_mode_natural_timer_cb);
}

static VOID_T fan_mode_natural(VOID_T)
{
    INT_T opRet = LIGHT_OK;

    //关闭睡眠模式的定时器,防止干扰自然模式的运行 
    opRet = opSocSWTimerStop(SLEEP_MODE_TIMER);
    if (opRet != LIGHT_OK) {
        PR_ERR("stop sleep timer error");
    }

    natural_speed_low_flag = ~(0x00);
    fan_speed_set(fan_state.speed);

    opSocSWTimerStart(NATURAL_MODE_TIMER, NATURAL_SPEED_CHANGE_TIME * 1000, fan_mode_natural_timer_cb);
}

睡眠风模式:

static VOID_T fan_sleep_mode_task(VOID_T)
{
    UINT8_T cur_gear;

    PR_NOTICE("enter fan_sleep_mode_task!");
    //判断当前是不是最低档。若为最低档,不再降速 
    if (fan_state.speed <= g_fan_speed_gear[0]) {
        fan_speed_set(g_fan_speed_gear[0]);
        change_fan_state();
        opSocSWTimerStop(SLEEP_MODE_TIMER);
        return;
    }

    cur_gear = get_cur_gear();
    PR_NOTICE("current gear is %d.", cur_gear);
    fan_state.speed = g_fan_speed_gear[--cur_gear];

    //改变档位转速
    fan_speed_set(fan_state.speed);
    fan_speed_led_set(get_cur_gear()+1);
    PR_NOTICE("speed change to %d.", fan_state.speed);
    //写入风扇状态到falsh中
    write_flash_fan_state();

    //启动睡眠模式,1h 减一档
    opSocSWTimerStart(SLEEP_MODE_TIMER, SLEEP_SPEED_CHANGE_TIME * 1000, fan_sleep_mode_task);
}

static VOID_T fan_mode_sleep(VOID_T)
{
    UINT8_T cur_gear;
    INT_T opRet = LIGHT_OK;
    SHORT_T i;

    //关闭自然模式的定时器,防止干扰睡眠模式模式的运行 
    opRet = opSocSWTimerStop(NATURAL_MODE_TIMER);
    if (opRet != LIGHT_OK) {
        PR_ERR("stop sleep timer error");
    }
    opRet = opSocSWTimerStop(SLEEP_MODE_TIMER);
    if (opRet != LIGHT_OK) {
        PR_ERR("stop sleep timer error");
    }

    //判断当前档位
    cur_gear = get_cur_gear();
    fan_state.speed = g_fan_speed_gear[cur_gear];
    //改变档位转速
    fan_speed_set(fan_state.speed);
    PR_NOTICE("speed change to %d.", fan_state.speed);
    //写入风扇状态到falsh中
    write_flash_fan_state();

    opSocSWTimerStart(SLEEP_MODE_TIMER, SLEEP_SPEED_CHANGE_TIME * 1000, fan_sleep_mode_task);
}

3、编码器及其他按键功能开发
按键初始化:

VOID_T fan_key_init(VOID_T)
{
    OPERATE_RET opRet;

    tuya_gpio_inout_set(KEY_ROTARY_A, TRUE);
    tuya_gpio_inout_set(KEY_ROTARY_B, TRUE);

    /* 旋钮正反转检测初始化 */
    BkGpioEnableIRQ(KEY_ROTARY_A, IRQ_TRIGGER_FALLING_EDGE, knod_key_cb, NULL);

    opRet = key_init(NULL, 0, 0);
    if (opRet != OPRT_OK) {
        PR_ERR("key_init err:%d", opRet);
        return;
    }

    memset(&KEY_DEF_T, 0, SIZEOF(KEY_DEF_T));
    KEY_DEF_T.port = KEY_ROTARY_N;
    KEY_DEF_T.long_key_time = 3000;
    KEY_DEF_T.low_level_detect = TRUE;
    KEY_DEF_T.lp_tp = LP_ONCE_TRIG;
    KEY_DEF_T.call_back = key_press_cb;
    KEY_DEF_T.seq_key_detect_time = 400;
    opRet = reg_proc_key(&KEY_DEF_T);
    if (opRet != OPRT_OK) {
        PR_ERR("reg_proc_key err:%d", opRet);
        return;
    }

    KEY_DEF_T.port = KEY_TIMER;
    opRet = reg_proc_key(&KEY_DEF_T);
    if (opRet != OPRT_OK) {
        PR_ERR("reg_proc_key err:%d", opRet);
        return;
    }

    KEY_DEF_T.port = KEY_POWER;
    KEY_DEF_T.long_key_time = 10000;
    opRet = reg_proc_key(&KEY_DEF_T);
    if (opRet != OPRT_OK) {
        PR_ERR("reg_proc_key err:%d", opRet);
        return;
    }
}

按键功能回调函数:

编码器回调函数,编码器功能的功能实现,简单的使用的外部中断触发后,开始判断A,B两个引脚电平是否相同来确认是顺时针旋转还是逆时针旋转。

STATIC VOID_T knod_key_cb(VOID_T)
{
    INT8_T current_gear;
    //如果关机,不执行任何操作
    if (fan_state.on_off == FALSE) {
        return;
    }

    BkGpioFinalize(KEY_ROTARY_A);

    //得到当前档位
    current_gear = get_cur_gear();

    if(tuya_gpio_read(KEY_ROTARY_A) != tuya_gpio_read(KEY_ROTARY_B)) {
        PR_DEBUG("A != B"); //顺时针方向
        current_gear++;
        if (current_gear > (MAX_GEAR_NUMBER-1)) {
            current_gear = (MAX_GEAR_NUMBER-1);
        }
        fan_state.speed = g_fan_speed_gear[current_gear];

    } else {
        PR_DEBUG("A == B"); //逆时针方向
        current_gear--;
        if (current_gear < 0) {
            current_gear = 0;
        }
        fan_state.speed = g_fan_speed_gear[current_gear];
    }

    /* 改变风扇状态:风速,模式,LED */
    change_fan_state();
    write_flash_fan_state();
    
    PR_DEBUG("fan current_gear is : %d", current_gear);

    /* 旋钮正反转检测初始化 */
    BkGpioEnableIRQ(KEY_ROTARY_A, IRQ_TRIGGER_FALLING_EDGE, knod_key_cb, NULL);
}

编码器使用上面外部中断的方式后发现偷懒不成,编码器转的快,触发太快容易导致程序卡死,软件重启。于是改为了下面使用线程检测的偷懒方式。

void key_rotary_task(void)
{
    INT8_T current_gear;

    while(1) {
        //得到当前档位
        current_gear = get_cur_gear();
        if((tuya_gpio_read(KEY_ROTARY_A) == FALSE) && (fan_state.on_off != FALSE)) {
            while(tuya_gpio_read(KEY_ROTARY_A) == FALSE);
            if(tuya_gpio_read(KEY_ROTARY_A) != tuya_gpio_read(KEY_ROTARY_B)) {
                PR_NOTICE("A != B"); //顺时针方向
                current_gear++;
                if (current_gear > (MAX_GEAR_NUMBER-1)) {
                    current_gear = (MAX_GEAR_NUMBER-1);
                }
                fan_state.speed = g_fan_speed_gear[current_gear];
            } else {
                PR_NOTICE("A == B"); //逆时针方向
                current_gear--;
                if (current_gear < 0) {
                    current_gear = 0;
                }
                fan_state.speed = g_fan_speed_gear[current_gear];
            }
            /* 改变风扇状态:风速,模式,LED */
            change_fan_state();
            write_flash_fan_state();
            
            PR_NOTICE("fan current_gear is : %d", current_gear);
        }
        tuya_hal_system_sleep(50);
    }
}

VOID_T fan_key_init(VOID_T)
{
    OPERATE_RET opRet;

    tuya_gpio_inout_set(KEY_ROTARY_A, TRUE);
    tuya_gpio_inout_set(KEY_ROTARY_B, TRUE);
    ...
    tuya_hal_thread_create(NULL, "key_rotary_task", 512*4, TRD_PRIO_5, key_rotary_task, NULL);
}

其他普通按键回调函数:

STATIC VOID_T key_press_cb(TY_GPIO_PORT_E port,PUSH_KEY_TYPE_E type,INT_T cnt)
{
    PR_DEBUG("port: %d, type: %d, cnt: %d", port, type, cnt);

    /* 旋钮按键 */
    if (port == KEY_ROTARY_N) {
        if (fan_state.on_off == FALSE) {
            return;
        }
        switch (type) {
            case NORMAL_KEY:
                PR_DEBUG("knod press.");

                if (fan_state.mode == NORMAL_MODE) {
                    fan_state.mode = NATURAL_MODE;
                } else if (fan_state.mode == NATURAL_MODE) {
                    fan_state.mode =SLEEP_MODE;
                } else {
                    fan_state.mode = NORMAL_MODE;
                }
                change_fan_state();
                break;
            case LONG_KEY:
                PR_DEBUG("knod long press.");
                /* 复位,删除所有用户信息,恢复到默认模式 */
                fan_state = fan_default_state;
                change_fan_state();
                write_flash_fan_state();
                break;

            case SEQ_KEY:
                PR_DEBUG("knod SEQ press, the count is %d.", cnt);
                break;

            default:
                break;
        }
    } 

    /* 定时按键 */
    if (port == KEY_TIMER) {
        if (fan_state.on_off == FALSE) {
            return;
        }
        switch (type) {
            case NORMAL_KEY:
                PR_DEBUG("timer press.");
                if (fan_state.local_timing == 0xFF) {
                    fan_state.local_timing = 1;
                } else if (fan_state.local_timing >= 4) {
                    fan_state.local_timing = 0xFF; //取消定时
                } else {
                    fan_state.local_timing++;
                }
                fan_local_timing_shutdown();
                break;
            case LONG_KEY:
                PR_DEBUG("timer long press.");

                break;
            case SEQ_KEY:
                PR_DEBUG("timer SEQ press, the count is %d.", cnt);

                break;
            default:
                break;
        }
    } 

    /* 开关按键 */
    if (port == KEY_POWER) {
        switch (type) {
            case NORMAL_KEY:
                if (fan_state.on_off == FALSE) {
                    fan_state.on_off = TRUE;
                    PR_DEBUG("Turn on");
                } else {
                    fan_state.on_off = FALSE;
                    PR_DEBUG("Turn off");
                }
                change_fan_state();
                break;
            case LONG_KEY:
                PR_DEBUG("power long press.");

                break;
            case SEQ_KEY:
                PR_DEBUG("power SEQ press, the count is %d.", cnt);

                break;
            default:
                break;
        }
    } 
    write_flash_fan_state();
}

4、本地定时
本地定时功能简单的调用了一个软件定时器来实现:

VOID_T fan_timing_cd(VOID_T)
{
    fan_state.local_timing--;
    opSocSWTimerStop(SHUTDOWN_TIMER);
    if (fan_state.local_timing == 0 || fan_state.on_off == FALSE) {
        fan_turn_off();
    } else {
        PR_NOTICE("fan_state.local_timing ======== %d", fan_state.local_timing);
        write_flash_fan_state();
        opSocSWTimerStart(SHUTDOWN_TIMER, (SINGLE_TIMING*1000), fan_timing_cd);
        fan_local_timing_led_set(fan_state.local_timing);
    }
}

VOID_T fan_local_timing_shutdown(VOID_T)
{
    fan_local_timing_led_set(fan_state.local_timing);
    if (fan_state.local_timing > 4) { //无定时
        opSocSWTimerStop(SHUTDOWN_TIMER);
        return;
    }
    PR_NOTICE("run shutdown timer");
    opSocSWTimerStop(SHUTDOWN_TIMER);
    opSocSWTimerStart(SHUTDOWN_TIMER, (SINGLE_TIMING*1000), fan_timing_cd);
}

5、断电记忆功能
断电记忆功能依赖于flash,每次状态改变后,都将当前设备信息存储到flash中,在设备上电后首先读取flash中的数据对设备状态进行设定。

VOID_T read_flash_fan_state(VOID_T)
{
    INT_T opRet, i;
    UCHAR_T fan_state_data_crc;
    UCHAR_T before_fan_power_off_state[FAN_STATE_STORAGE_LEN]; //断电前风扇状态

    opRet = uiSocFlashRead(SAVE_TYP1, FAN_STATE_OFFSET, FAN_STATE_STORAGE_LEN*SIZEOF(UCHAR_T), before_fan_power_off_state);
    if (opRet != FAN_STATE_STORAGE_LEN) {
        PR_ERR("read data error for flash");
        return;
    }

    //判断头部数据是否正确 
    if (before_fan_power_off_state[0] != FAN_DATA_HEAD) {
        PR_ERR("data head error");
        return;
    }

    fan_state_data_crc = get_crc_8(before_fan_power_off_state, (FAN_STATE_STORAGE_LEN - 1)*SIZEOF(UCHAR_T));
    //校验数据是否正确
    if (fan_state_data_crc != before_fan_power_off_state[FAN_STATE_STORAGE_LEN - 1]) { 
        PR_ERR("crc error, before_fan_power_off_state[%d] = %02x, crc data = %02x.", FAN_STATE_STORAGE_LEN - 1, before_fan_power_off_state[FAN_STATE_STORAGE_LEN - 1], fan_state_data_crc);
        return;
    }

    //将从 flash 读取到的数据,存放到结构体中
    fan_state.on_off    = before_fan_power_off_state[FLASH_FAN_STATE_ON_OFF];
    fan_state.mode      = before_fan_power_off_state[FLASH_FAN_STATE_MODE];
    fan_state.speed     = before_fan_power_off_state[FLASH_FAN_STATE_SPEED];
    fan_state.local_timing = before_fan_power_off_state[FLASH_FAN_STATE_TIMING];

    return;
}

VOID_T write_flash_fan_state(VOID_T) 
{
    INT_T opRet, i;
    UCHAR_T fan_state_buffer[FAN_STATE_STORAGE_LEN];

    fan_state_buffer[0] = FAN_DATA_HEAD;
    fan_state_buffer[1] = fan_state.on_off;
    fan_state_buffer[2] = fan_state.mode;
    fan_state_buffer[3] = fan_state.speed;
    fan_state_buffer[4] = fan_state.local_timing;
    fan_state_buffer[5] = get_crc_8(fan_state_buffer, (FAN_STATE_STORAGE_LEN - 1)*SIZEOF(UCHAR_T));
    
    for (i=0; i<FAN_STATE_STORAGE_LEN; i++) {
        PR_NOTICE(" +++ fan_state_buffer is [%d] : %02x", i, fan_state_buffer[i]);
    }

    opRet = opSocFlashWrite(SAVE_TYP1, FAN_STATE_OFFSET, fan_state_buffer, FAN_STATE_STORAGE_LEN * SIZEOF(UCHAR_T)); 
    if (opRet != LIGHT_OK) {
        PR_ERR("write flash error");
    }
    
    return;
}

6、设备复位功能
设备复位功能的实现是将 flash 中存储的设备状态更改为初始化值,或者你也可以直接将存储区域给擦除掉:

VOID_T erase_flash_fan_state(VOID_T) 
{
    INT_T opRet, i;
    UCHAR_T fan_state_buffer[FAN_STATE_STORAGE_LEN];

    fan_state.on_off = FALSE;
    fan_state.mode   = NORMAL_MODE;
    fan_state.speed  = 1;

    fan_state_buffer[0] = FAN_DATA_HEAD;
    fan_state_buffer[1] = FALSE;    //fan_state.on_off
    fan_state_buffer[2] = NORMAL_MODE; //fan_state.mode
    fan_state_buffer[3] = 1;        //fan_state.speed
    fan_state_buffer[4] = 0xFF;     // fan_state.local_timing
    fan_state_buffer[5] = get_crc_8(fan_state_buffer, (FAN_STATE_STORAGE_LEN - 1)*SIZEOF(UCHAR_T));

    for (i=0; i<FAN_STATE_STORAGE_LEN; i++) {
        PR_NOTICE(" +++ fan_state_buffer is [%d] : %02x", i, fan_state_buffer[i]);
    }

    opRet = opSocFlashWrite(SAVE_TYP1, FAN_STATE_OFFSET, fan_state_buffer, FAN_STATE_STORAGE_LEN * SIZEOF(UCHAR_T)); 
    if (opRet != LIGHT_OK) {
        PR_ERR("write flash error");
    }

    return;
}

7、风扇状态改变
当通过 APP 或 按键想要改变设备状态时调用该函数对设备进行改变

VOID_T change_fan_state(VOID_T)
{
    if (fan_state.on_off == FALSE) {
        fan_turn_off();
        hw_report_all_dp_status();
        PR_NOTICE("stop sleep & natural timer");
        return;
    }

    if (fan_state.bright == 1) {
        fan_led_dimmer(100);
    } else {
        fan_led_dimmer(900);
    }

    if (fan_state.mode == SLEEP_MODE) {
        PR_NOTICE("enter sleep mode"); 
        fan_mode_sleep(); 
    } else if (fan_state.mode == NATURAL_MODE) {
        PR_NOTICE("enter natural mode"); 
        fan_mode_natural(); 
    } else {
        PR_NOTICE("enter normal mode");
        fan_mode_normal();  
    }

    hw_report_all_dp_status();
    
    /* speed LED set */
    fan_speed_led_set(get_cur_gear()+1);
    fan_mode_led_set();

    return;
}

到这里智能风扇改装的软件部分就已经完成了,感兴趣的同学可以动手试一下。改造后的智能风扇支持APP&按键双控制,多种风速模式调整,定时关机,同时也可以和家里的语音控制设备联动实现语音控制,当然如果你有其它需求也可以自定义。有任何疑问欢迎留言讨论~

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值