改造智能风扇之——BLDC风扇改造软件篇
Posted 三明治开发社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了改造智能风扇之——BLDC风扇改造软件篇相关的知识,希望对你有一定的参考价值。
一、开发前准备
本次软件开发主要是基于涂鸦CBU模组,需要先授权再接入涂鸦IoT云平台,刚好涂鸦的最近的活动比较多,可以参加涂鸦的活动获(bai)取(piao)模组。
涂鸦的SDK编译一般是在Linux下进行编译开发的,建议先安装虚拟机,windows也可以的,但是编译速度有点慢。
下面是开发中会使用到的平台和资料库:
涂鸦IoT云平台
CBU开发SDK环境
本项目代码GitHub地址
CBU模组规格书
二、功能规划
序号 | 功能 |
---|---|
1 | 三种模式风(正常风,自然风:忽大忽小间隔15秒,睡眠风:每隔一小时自动降档,最后降到最低档) |
2 | 编码器旋转控制风扇,顺时针旋转风速+,逆时针旋转风速- 。 |
3 | 编码器按钮短按切换模式,长按设备复位。 |
4 | 4颗指示灯显示风速,4颗指示灯指示8档风速,闪烁代表1档,常量代表2档。4颗指示灯还复用本地定时指示。 |
5 | 本地定时功能,时间到自动关机。定时按键:无定时->1小时->2小时->3小时->4小时->无定时。 |
6 | 电源按键,风扇工作状态按下后关闭风扇,风扇处于关闭状态按下后打开风扇。 |
7 | 长按 WiFi 按键,设备进入配网模式。 |
8 | LED有8颗,除指示风速4颗LED外,还有4颗指示灯。一颗 WiFi 指示灯,指示 WiFi 状态;其他三颗指示当前风扇模式 |
9 | led指示灯亮度可通过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风扇改造软件篇的主要内容,如果未能解决你的问题,请参考以下文章
改造一台可以计算滤芯使用寿命的智能空气净化器——嵌入式功能实现篇