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

Posted 三明治开发社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了改造智能风扇之——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 == FA

以上是关于改造智能风扇之——BLDC风扇改造软件篇的主要内容,如果未能解决你的问题,请参考以下文章

改造智能风扇之——普通BLDC风扇拆机分析篇

改造一台可以计算滤芯使用寿命的智能空气净化器——嵌入式功能实现篇

在X79 LGA2011上改造安装利民AX120R LGA1200风扇

毕业设计 - 题目:基于stm32的智能温控风扇设计与实现

结合工程实践选题调研分析同类软件产品

如何开启主板CPU风扇的pwm智能温控功能?