风扇由于便捷、易安装、能耗低的特性,在消费品市场非但没有受空调的影响,反而新增了远程控制、自然风、暖风、清凉风、睡眠风、负离子功能等新型产品,甚至还有驱蚊、低噪风扇,具有非常高的实用价值。
随着消费者对健康的日益关注,产品设计还可以在提高空气质量、便携移动、低噪声等方向进行新品研发。
本教程主要介绍目前市场上流行 直流变频风扇 的设计思路,通过方案设计、硬件设计、涂鸦 IoT 平台 DP 设置、嵌入式开发,来讲解普通风扇智能化的研发流程。如果你也感兴趣,可以一起动手操作,制作出一款原型产品,了解物联网硬件产品的设备端开发。
功能设计 | 详细说明 |
---|---|
工作模式 |
|
风速控制 | 编码器旋转控制风速,顺时针旋转风速+,逆时针旋转风速- 。 |
模式切换 | 编码器按钮短按切换模式,长按设备复位。 |
本地定时 | 定时结束后自动关机,定时按键:无定时 -> 1 小时 -> 2 小时 -> 3 小时 -> 4 小时 -> 无定时。 |
指示灯显示 1 | 4 颗指示灯显示风速,4 颗指示灯指示 8 档风速,闪烁代表 1 档,常亮代表 2 档。4 颗指示灯还复用本地定时指示。 |
指示灯显示 2 | 除 4 颗风速指示灯外,还有 4 颗指示灯。一颗 Wi-Fi 指示灯,指示 Wi-Fi 状态;其他三颗指示当前风扇工作模式。 |
电源 | 电源按键,控制风扇的启动和关闭。 |
设备配网 | 长按 Wi-Fi 按键,设备进入配网模式。 |
指示灯调节 | 指示灯亮度可通过 App 调整,正常亮度或较暗亮度。 |
断电记忆 | 设备通电自动恢复。 |
相比传统交流电机风扇,无刷直流电机(BLDC,Brushless DC electric motor)具有中低速转矩性能好等多种优势,且使用寿命更长。因此直流电机选用的是 MINEBEA 12V 直流三相无刷电机,作为风量控制电机。
三相无刷直流电机的电流流向
驱动器主要控制直流无刷电动机(Brushless DC electric motor,BLDC)。本案例采用峰岹的 BLDC 解决方案,BLDC 的控制 MCU 使用了 FU6832—双核:8051 内核+ME,16KB flash,256 bytes IRAM, 768 bytes XRAM SSOP24 封装,集成 BLDC 控制算法。
FU6832S 芯片有 6 个指定引脚 H_PU、H_PV、H_PW、L_U、L_V、L_W 用于控制三相直流无刷电机,内置上拉电阻和下拉电阻,高电平最大值是 VCC 电压。
只要将芯片供电的输入电压和电机工作电压保持相同,就不需要再在这些引脚的外围增加电平转换电路,可直接用 3 颗驱动芯片 AO4606 驱动电机的 U、V、W 三个引脚,该芯片由一个 Nmos 和 Pmos 组成,通过通过引脚走线可设计成半桥驱动电路。
FU6832S 芯片内置 1 个可配置增益的独立运算放大器,外围增加取样电阻,便可用于实现电机过流检测保护。
电机正反转监测电路,通过电阻分压后,进入 FU6832S 芯片 14、15、16 脚。
因为 FU6832S 芯片数字电路引脚的电平是 5V,Wi-Fi 模块的引脚电平是 3.3V,因此两个通信间需要增加电平转换电路。
Wi-Fi 模组选择涂鸦 SoC 方案模组——WBRU;它由一个高集成度的无线射频芯片 W701-VA2-CG 构成,内置了 Wi-Fi 网络协议栈和丰富的库函数。WBRU 还包含低功耗的 KM4 MCU,WLAN MAC,1T1R WLAN,最高主频 100MHz,内置 256K SRAM ,芯片内置 2Mbyte flash 和丰富的外设资源。WBRU 是一个 RTOS 平台,集成了所有 Wi-Fi MAC 以及 TCP/IP 协议的函数库。用户可以基于这些开发满足自己需求的嵌入式 Wi-Fi 产品。更多详情,请参考 WBRU 模块规格书。
风量调节方案采用旋钮编码器作为风量调节旋钮,顺时针调大,逆时针调小,该编码器可根据要求,设置需要的档位数。该器件手柄支持 360°选择,结构上无限位点。
方案设计 4 个按键,按键均设置为低电平有效,按键功能:
方案设计 8 个指示灯,指示灯由 CBU Wi-Fi 模组的 I/O 口控制,设置均为低电平点亮,指示灯功能:
采用 PWM 信号作为 Wi-Fi 模组和 FU6832S 电机驱动控制芯片的交互信号。
电源插头:选用绿源电源插头,输出 12V DC/2A,用于满足风扇整体供电要求。
转换电路:
此次教程需要一个 12V DC 转 3.3V DC 的转换电路,用于 Wi-Fi 模组及外围供电。一个 12V DC 转 5V DC 的转换电路,用于 FU6832S 电机驱动控制芯片及外围供电。
转换电路采用基于矽力杰的 SY8121B 芯片的降压(Buck)电路。SY8121B 是一款高效,响应速度快的同步整流方案的 DC-DC 转换芯片。
电机驱动板和电压转换板:
Wi-Fi 模组控制板:
操作界面:
首先进入 涂鸦 IoT 平台,创建一个风扇产品。通过点击**“创建产品”->“小家电”->“风扇”进行创建,填入“产品名称”,选择通讯协议为“WiFi+蓝牙”,然后点击下方的“创建产品”**即可完成产品创建。
产品创建后,进入到开发界面,此时会弹出功能选择界面,可以直接选择需要用到的的风扇标准功能。
如果标准功能中没有合适的选项,也可以选择自定义功能。本案例选择了风扇标准功能中的 4 项功能:“开关”、“工作模式”、“风速”和”倒计时“,并对功能点属性进行相应修改,另外还自定义了 1 项功能:”灯光亮度“,最终 DP 点定义如下图所示。
完成功能定义后,我们还需要设置设备面板,可以根据喜好选择一种面板进行编辑,编辑完成后可以按照提示用“涂鸦智能 App”扫码体验手机控制。
最后进入硬件开发阶段。我们选择 “涂鸦标准模组 SDK 开发” 和 “WBR3 Wi-Fi & Bluetooth 模组”。
说明:后续烧录授权时可以点击下图中的 “免费领取 10 个激活码” 进行烧录凭证的申请。
本案例使用涂鸦 WiFi&BLE SDK 和 WBRU 云模组进行开发。
SDK 获取
在涂鸦 IoT 平台的产品创建流程中,进入 “硬件开发” 界面左下角,找到 “开发资料” 后,下载 SDK。
更多详情和步骤说明,请参考 通用 Wi-Fi SDK Demo 和 WBR 系列模组烧录授权
传统的电风扇一般有风速挡位调节、定时关机等功能,本案例在此基础上,提供了三种工作模式可选,并使用涂鸦 WiFi&BLE SDK 和 WiFi&BLE 模组实现了对电风扇的远程控制,包括开/关控制、工作模式设定、风速设定、定时关机设定和灯光亮度调节功能,另外还增加了断电记忆功能,使电风扇更加智能化、人性化。
完整 Demo 可在 tuya-iotos-embeded-demo-wifi-ble-smart-fan-wbru 中获取。
本案例提供 4 个按键,其中包括了旋钮编码器上的按键,低电平有效,其对应的引脚和功能设定如下:
名称 | 网络标号 | 引脚 | 功能 |
---|---|---|---|
电源键 | power | PA7 | 轻触,切换设备开/关 |
定时键 | timer | PA8 | 轻触,设置定时时间 |
配网键 | wifi | PA19 | 长按 10s,重置设备配网状态 |
模式键(旋钮编码器-N) | mode | PA17 | 轻触,切换工作模式;长按 3s,重置设备状态 |
涂鸦 WiFi&BLE SDK 提供了按键检测组件,因此我们只需要调用 tuya_key.h
中提供的接口进行注册即可:
/* 按键端口 */
#define P_KEY_POWER TY_GPIOA_7
#define P_KEY_TIMER TY_GPIOA_8
#define P_KEY_WIFI TY_GPIOA_19
#define P_KEY_ROTARY_N TY_GPIOA_17
/* 按键配置 */
#define KEY_SEQ_PRESS_TIME_MS 400 /* 连按时间: 400ms */
#define KEY_MODE_LONG_PRESS_TIME_MS 10000 /* 模式键(复位)长按时间: 10s */
#define KEY_WIFI_LONG_PRESS_TIME_MS 3000 /* 配网键长按时间: 3s */
#define KEY_LONG_PRESS_TIME_MS 10000 /* 默认长按时间: 10s */
/**
* @brief 按键事件回调函数
* @param port: 按键端口
* @param type: 按键触发类型
* @param cnt: 连击次数
* @return none
*/
STATIC VOID_T fan_key_press_cb(IN CONST TY_GPIO_PORT_E port, IN CONST PUSH_KEY_TYPE_E type, IN CONST INT_T cnt)
{
PR_DEBUG("port: %d, type: %d, cnt: %d", port, type, cnt);
switch (port) {
case P_KEY_POWER:
fan_key_power_handler(type, cnt);
g_fan_mag.timing_display_flag = FALSE;
break;
case P_KEY_TIMER:
fan_key_timer_handler(type, cnt);
break;
case P_KEY_WIFI:
fan_key_wifi_handler(type, cnt);
g_fan_mag.timing_display_flag = FALSE;
break;
case P_KEY_ROTARY_N:
fan_key_mode_handler(type, cnt);
g_fan_mag.timing_display_flag = FALSE;
break;
default:
break;
}
}
/**
* @brief 按键初始化
* @param none
* @return none
*/
VOID_T fan_key_init(VOID_T)
{
OPERATE_RET opRet;
/* 初始化 */
opRet = key_init(NULL, 0, 0);
if (opRet != OPRT_OK) {
PR_ERR("key_init err:%d", opRet);
return;
}
/* 用户定义初始化 */
memset(&g_key_def, 0, SIZEOF(KEY_USER_DEF_S));
g_key_def.low_level_detect = TRUE;
g_key_def.lp_tp = LP_ONCE_TRIG;
g_key_def.seq_key_detect_time = KEY_SEQ_PRESS_TIME_MS;
g_key_def.call_back = fan_key_press_cb;
/* 开关键 */
g_key_def.port = P_KEY_POWER;
g_key_def.long_key_time = KEY_LONG_PRESS_TIME_MS;
opRet = reg_proc_key(&g_key_def);
if (opRet != OPRT_OK) {
PR_ERR("reg_proc_key err:%d", opRet);
return;
}
/* 定时键 */
g_key_def.port = P_KEY_TIMER;
g_key_def.long_key_time = KEY_LONG_PRESS_TIME_MS;
opRet = reg_proc_key(&g_key_def);
if (opRet != OPRT_OK) {
PR_ERR("reg_proc_key err:%d", opRet);
return;
}
/* 配网键 */
g_key_def.port = P_KEY_WIFI;
g_key_def.long_key_time = KEY_WIFI_LONG_PRESS_TIME_MS;
opRet = reg_proc_key(&g_key_def);
if (opRet != OPRT_OK) {
PR_ERR("reg_proc_key err:%d", opRet);
return;
}
/* 模式键/复位键 */
g_key_def.port = P_KEY_ROTARY_N;
g_key_def.long_key_time = KEY_MODE_LONG_PRESS_TIME_MS;
opRet = reg_proc_key(&g_key_def);
if (opRet != OPRT_OK) {
PR_ERR("reg_proc_key err:%d", opRet);
return;
}
}
本案例提供 1 个旋钮编码器用于调节风速挡位,其对应的引脚和功能设定如下:
名称 | 网络标号 | 引脚 | 功能 |
---|---|---|---|
旋钮编码器-A(A-C 端子间) | wind_down | PA18 | 用于旋钮转向检测 |
旋钮编码器-B(B-C 端子间) | wind_up | PA0 | 用于旋钮转向检测 |
为方便后续程序扩展,编写了专用于旋钮编码器检测的组件,具体可参考driver\tuya_rotary.c
。
旋钮编码器注册和回调函数实现如下:
/* Pin */
#define P_ROTARY_A TY_GPIOA_18 /* 旋钮编码器-A */
#define P_ROTARY_B TY_GPIOA_0 /* 旋钮编码器-B */
VOID_T fan_rotary_cb(BOOL_T dir);
ROTARY_USER_DEF_S g_rotary_def = {
.port_a = P_ROTARY_A,
.port_b = P_ROTARY_B,
.call_back = fan_rotary_cb
};
/**
* @brief 旋钮编码器回调函数
* @param dir: 旋转方向 TRUE-顺时针 FALSE-逆时针
* @return none
*/
VOID_T fan_rotary_cb(BOOL_T dir)
{
if (dir) {
PR_NOTICE("The direction of rotation is CW.");
} else {
PR_NOTICE("The direction of rotation is CCW.");
}
}
/**
* @brief 旋钮编码器初始化
* @param none
* @return none
*/
VOID_T fan_rotary_init(VOID_T)
{
OPERATE_RET opRet;
/* 初始化 */
opRet = rotary_init(NULL, 0);
if (opRet != OPRT_OK) {
PR_ERR("rotary_init err:%d", opRet);
return;
}
opRet = reg_proc_rotary(&g_rotary_def);
if (opRet != OPRT_OK) {
PR_ERR("reg_proc_rotary err:%d", opRet);
}
}
本案例提供 8 个指示灯用于设备状态指示,低电平有效,其对应引脚和功能设定如下:
名称 | 网络标号 | 引脚 | 功能 |
---|---|---|---|
挡位指示灯-1 | D1 | PA9 | 挡位≥1,常亮;挡位≥2,闪烁; 定时≥1h,常亮;定时=无定时,常灭; |
挡位指示灯-2 | D2 | PA2 | 挡位≥3,常亮;挡位≥4,闪烁; 定时≥2h,常亮;定时=无定时,常灭; |
挡位指示灯-3 | D3 | PA3 | 挡位≥5,常亮;挡位≥6,闪烁; 定时≥3h,常亮;定时=无定时,常灭; |
挡位指示灯-4 | D4 | PA4 | 挡位≥7,常亮;挡位≥8,闪烁; 定时=4h,常亮;定时=无定时,常灭; |
模式指示灯-直吹风 | mode_w | TX | 直吹风模式常亮;其他模式常灭; |
模式指示灯-自然风 | mode_n | PA10 | 自然风模式常亮;其他模式常灭; |
模式指示灯-睡眠风 | mode_s | PA20 | 睡眠风模式常亮;其他模式常灭; |
配网状态指示灯 | net_led | RX | 指示配网状态 |
本案例还可以对指示灯亮度进行调节,这里采用 PWM 控制方式,对应引脚和功能设定如下:
名称 | 网络标号 | 引脚 | 功能 |
---|---|---|---|
指示灯亮度调节 | dimmer | PA12 | PWM 方式调节亮度,可设置正常亮度和较暗亮度 |
下面开始编码实现指示灯相关功能,首先初始化指示灯和指示灯亮度调节端口,这里指示灯注册调用的是 SDK 中tuya_led.h
提供的接口:
/* LED 端口 */
STATIC UCHAR_T sg_fan_led_port[] = {
TY_GPIOA_9, /* D1 */
TY_GPIOA_2, /* D2 */
TY_GPIOA_3, /* D3 */
TY_GPIOA_4, /* D4 */
TY_GPIOA_14, /* MODE_WIND */
TY_GPIOA_10, /* MODE_NATURE */
TY_GPIOA_20, /* MODE_SLEEP */
TY_GPIOA_13 /* WIFI */
};
/* LED 句柄 */
#define LED_HANDLE_D1 0
#define LED_HANDLE_D2 1
#define LED_HANDLE_D3 2
#define LED_HANDLE_D4 3
#define LED_HANDLE_MODE_WIND 4
#define LED_HANDLE_MODE_NATURE 5
#define LED_HANDLE_MODE_SLEEP 6
#define LED_HANDLE_WIFI 7
LED_HANDLE fan_led_handle[(SIZEOF(sg_fan_led_port) / SIZEOF(sg_fan_led_port[0]))];
/* LED 亮度调节 PWM */
#define P_LED_DIMMER TY_GPIOA_12
#define LED_DIMMER_PWM_PERIOD 1000 /* PWM 周期: 1ms(1kHz) */
#define LED_DIMMER_PWM_DUTY_NORMAL 900 /* PWM 占空比: 90%(正常亮度) */
#define LED_DIMMER_PWM_DUTY_DARKER 100 /* PWM 占空比: 10%(较暗亮度) */
pwmout_t g_led_dimmer_pwmout;
/**
* @brief 指示灯亮度调节 PWM 初始化
* @param none
* @return none
*/
STATIC VOID_T fan_led_dimmer_init(VOID_T)
{
pwmout_init(&g_led_dimmer_pwmout, P_LED_DIMMER);
pwmout_period_us(&g_led_dimmer_pwmout, LED_DIMMER_PWM_PERIOD);
pwmout_pulsewidth_us(&g_led_dimmer_pwmout, (LED_DIMMER_PWM_PERIOD - LED_DIMMER_PWM_DUTY_NORMAL));
}
/**
* @brief 指示灯初始化
* @param none
* @return none
*/
VOID_T fan_led_init(VOID_T)
{
INT_T i;
OPERATE_RET op_ret = OPRT_OK;
/* 指示灯初始化 */
for (i = 0; i < (SIZEOF(sg_fan_led_port) / SIZEOF(sg_fan_led_port[0])); i++) {
op_ret = tuya_create_led_handle(sg_fan_led_port[i], FALSE, &fan_led_handle[i]);
if (op_ret != OPRT_OK) {
PR_ERR("led init err:%d", op_ret);
return;
}
tuya_set_led_light_type(fan_led_handle[i], OL_HIGH, 0, 0xffff); /* 关闭 LED */
}
/* 指示灯亮度初始化 */
fan_led_dimmer_init();
}
接下来进行各指示灯指示功能的实现,首先是模式指示灯,根据当前工作模式点亮对应的指示灯:
/**
* @brief 设置模式指示灯
* @param cur_mode: 当前工作模式
* @return none
*/
VOID_T fan_set_mode_led(IN CONST UCHAR_T cur_mode)
{
/* 关闭所有模式指示灯 */
tuya_set_led_light_type(fan_led_handle[LED_HANDLE_MODE_WIND], OL_HIGH, 0, 0xffff);
tuya_set_led_light_type(fan_led_handle[LED_HANDLE_MODE_NATURE], OL_HIGH, 0, 0xffff);
tuya_set_led_light_type(fan_led_handle[LED_HANDLE_MODE_SLEEP], OL_HIGH, 0, 0xffff);
/* 根据当前模式打开对应的指示灯 */
if (cur_mode == FAN_MODE_NORMAL) { /* 直吹风 */
tuya_set_led_light_type(fan_led_handle[LED_HANDLE_MODE_WIND], OL_LOW, 0, 0xffff);
} else if (cur_mode == FAN_MODE_NATURE) { /* 自然风 */
tuya_set_led_light_type(fan_led_handle[LED_HANDLE_MODE_NATURE], OL_LOW, 0, 0xffff);
} else { /* 睡眠风 */
tuya_set_led_light_type(fan_led_handle[LED_HANDLE_MODE_SLEEP], OL_LOW, 0, 0xffff);
}
}
挡位指示灯同时作为挡位指示和定时指示,所以增加一个标志来区分当前指示的内容;挡位指示根据当前挡位确定点亮指示灯的个数和点亮模式,定时指示根据定时时间确定指示灯点亮个数:
#define LED_GEAR_NUM 4 /* 挡位指示灯数量 */
/**
* @brief 设置挡位指示灯
* @param cur_gear: 当前挡位
* @return none
*/
VOID_T fan_set_gear_led(IN CONST UCHAR_T cur_gear)
{
UCHAR_T i;
if (g_fan_mag.timing_display_flag) {
return;
}
for (i = 0; i < LED_GEAR_NUM; i++) {
if (i == (cur_gear / 2)) {
if (cur_gear % 2) {
/* LED 闪烁 */
tuya_set_led_light_type(fan_led_handle[i], OL_FLASH_HIGH, 0, 0xffff);
} else {
/* LED 常亮 */
tuya_set_led_light_type(fan_led_handle[i], OL_LOW, 0, 0xffff);
}
} else if (i < (cur_gear/2)) {
/* LED 常亮 */
tuya_set_led_light_type(fan_led_handle[i], OL_LOW, 0, 0xffff);
} else {
/* LED 常灭 */
tuya_set_led_light_type(fan_led_handle[i], OL_HIGH, 0, 0xffff);
}
}
}
/**
* @brief 设置定时指示灯(与挡位指示灯共用)
* @param hour: 定时时间
* @return none
*/
VOID_T fan_set_timing_led(IN CONST UCHAR_T hour)
{
UCHAR_T i;
if (!g_fan_mag.timing_display_flag) {
return;
}
/* 无定时,关闭所有指示灯 */
if (hour > LED_GEAR_NUM) {
for (i = 0; i < LED_GEAR_NUM; i++) {
tuya_set_led_light_type(fan_led_handle[i], OL_HIGH, 0, 0xffff);
}
return;
}
/* 点亮对应指示灯 */
for (i = 0; i < LED_GEAR_NUM; i++) {
if (i < hour) {
tuya_set_led_light_type(fan_led_handle[i], OL_LOW, 0, 0xffff);
} else {
tuya_set_led_light_type(fan_led_handle[i], OL_HIGH, 0, 0xffff);
}
}
}
最后是配网指示灯,根据当前配网状态确定指示灯状态:
#define LED_WIFI_FAST_FLASH_MS 300 /* 快闪 300ms */
#define LED_WIFI_SLOW_FLASH_MS 500 /* 慢闪 500ms */
/**
* @brief 设置配网指示灯
* @param cur_stat: 当前 wifi 状态
* @return none
*/
VOID_T fan_set_wifi_led(IN CONST GW_WIFI_NW_STAT_E cur_stat)
{
switch (cur_stat) {
case STAT_LOW_POWER: /* wifi 连接超时,进入低功耗模式 */
tuya_set_led_light_type(fan_led_handle[LED_HANDLE_WIFI], OL_HIGH, 0, 0);/* 关闭指示灯 */
break;
case STAT_UNPROVISION: /* SamrtConfig 配网模式,等待连接 */
tuya_set_led_light_type(fan_led_handle[LED_HANDLE_WIFI], OL_FLASH_HIGH, LED_WIFI_FAST_FLASH_MS, 0xFFFF);/* 指示灯快闪 */
break;
case STAT_AP_STA_UNCFG: /* ap 配网模式,等待连接 */
tuya_set_led_light_type(fan_led_handle[LED_HANDLE_WIFI], OL_FLASH_HIGH, LED_WIFI_SLOW_FLASH_MS, 0xFFFF);/* 指示灯慢闪 */
break;
case STAT_AP_STA_DISC:
case STAT_STA_DISC: /* SamrtConfig/ap 正在连接中 */
tuya_set_led_light_type(fan_led_handle[LED_HANDLE_WIFI], OL_HIGH, 0, 0);/* 关闭指示灯 */
break;
case STAT_CLOUD_CONN:
case STAT_AP_CLOUD_CONN:/* 连接到涂鸦云 */
tuya_set_led_light_type(fan_led_handle[LED_HANDLE_WIFI], OL_LOW, 0, 0); /* 指示灯常亮 */
break;
default:
break;
}
}
由于关机时,挡位指示灯和模式指示灯需要熄灭,这里再添加一个关闭所有指示灯的函数:
/**
* @brief 关闭所有指示灯(除 Wi-Fi 指示灯)
* @param none
* @return none
*/
VOID_T fan_close_all_led(VOID_T)
{
INT_T i;
for (i = 0; i < (SIZEOF(sg_fan_led_port) / SIZEOF(sg_fan_led_port[0]) - 1); i++) {
tuya_set_led_light_type(fan_led_handle[i], OL_HIGH, 0, 0xffff); /* 关闭 LED */
}
}
指示灯亮度调节通过改变 PWM 占空比实现:
/**
* @brief 设置指示灯亮度
* @param brightness: 10-1000, 越大灯越亮
* @return none
*/
VOID_T fan_set_led_dimmer(IN CONST USHORT_T brightness)
{
pwmout_pulsewidth_us(&g_led_dimmer_pwmout, (LED_DIMMER_PWM_PERIOD - brightness));
}
本案例使用无刷直流电机来实现风扇扇叶的转动,采用 PWM 信号作为 Wi-Fi 模组和电机驱动器的交互信号,其对应引脚和功能设定如下:
名称 | 网络标号 | 引脚 | 功能 |
---|---|---|---|
无刷直流电机 | PWM2 | PA11 | PWM 方式控制电机转速从而调节风速 |
初始化电机驱动 PWM,再根据风速设定调节 PWM 占空比:
/* Pin */
#define P_BLDC_MOTOR TY_GPIOA_11
/* PWM 周期 */
#define BLDC_MOTOR_PWM_PERIOD 1000
/* PWM 占空比 */
#define BLDC_MOTOR_PWM_DUTY_OFF 5
#define BLDC_MOTOR_PWM_DUTY_MIN 30
#define BLDC_MOTOR_PWM_DUTY_MAX 99
pwmout_t g_bldc_motor_pwmout;
/**
* @brief 电机初始化
* @param none
* @return none
*/
VOID_T fan_motor_init(VOID_T)
{
pwmout_init(&g_bldc_motor_pwmout, P_BLDC_MOTOR);
pwmout_period_us(&g_bldc_motor_pwmout, BLDC_MOTOR_PWM_PERIOD);
pwmout_pulsewidth_us(&g_bldc_motor_pwmout, (BLDC_MOTOR_PWM_PERIOD * BLDC_MOTOR_PWM_DUTY_OFF / 100));
}
/**
* @brief 设置风速
* @param speed: 风速
* @return none
*/
VOID_T fan_set_speed(IN CONST UCHAR_T speed)
{
UCHAR_T pwm_duty;
if (speed <= 0) {
pwmout_pulsewidth_us(&g_bldc_motor_pwmout, (BLDC_MOTOR_PWM_PERIOD * BLDC_MOTOR_PWM_DUTY_OFF / 100));
return;
}
/* 由于电机在 30%以下工作时间过长会出现异常,这里对 PWM 输出进行一些处理,使输出的 PWM 在 30%-99% 之间 */
pwm_duty = (UCHAR_T)(BLDC_MOTOR_PWM_DUTY_MIN + ((BLDC_MOTOR_PWM_DUTY_MAX - BLDC_MOTOR_PWM_DUTY_MIN) * (speed / 100.0)));
pwmout_pulsewidth_us(&g_bldc_motor_pwmout, (BLDC_MOTOR_PWM_PERIOD * pwm_duty / 100));
}
本案例通过操作电源键控制设备的开/关机:
a. 编写电源键处理函数
/**
* @brief 开关键处理函数
* @param type: 触发类型
* @param cnt: 连击次数
* @return none
*/
STATIC VOID_T fan_key_power_handler(IN CONST PUSH_KEY_TYPE_E type, IN CONST INT_T cnt)
{
switch (type) {
case NORMAL_KEY:
PR_DEBUG("power press.");
fan_set_power(fan_switch_power()); /* 设置开/关机 */
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;
}
}
b. 设备开/关时需要处理的内容如下:
/**
* @brief 风扇开关切换
* @param none
* @return TRUE-开, FALSE-关
*/
BOOL_T fan_switch_power(VOID_T)
{
if (g_fan_mag.power == FALSE) {
g_fan_mag.power = TRUE;
} else {
g_fan_mag.power = FALSE;
}
return g_fan_mag.power;
}
/**
* @brief 开/关机设置
* @param power: 开/关
* @return none
*/
VOID_T fan_set_power(IN CONST BOOL_T power)
{
if (power) {
PR_NOTICE("Power on.");
/* 有定时设置时,启动定时关机定时器 */
if (g_fan_mag.timing > FAN_TIMING_NONE) {
PR_NOTICE("The fan will shutdown after: %dh", g_fan_mag.timing);
fan_shutdown_timer_start();
}
/* 从当前模式启动 */
fan_set_mode(g_fan_mag.mode);
} else {
PR_NOTICE("Power off.");
/* 停止所有定时器 */
fan_nature_mode_timer_stop();
fan_sleep_mode_timer_stop();
fan_timing_confirm_timer_stop();
fan_shutdown_timer_stop();
/* 关闭电机和指示灯 */
fan_set_speed(0);
fan_close_all_led();
g_fan_mag.timing_display_flag = FALSE;
}
/* 上报/存储数据更新 */
fan_report_all_dp_status();
fan_write_data_to_flash();
}
本案例设置了直吹风、自然风和睡眠风 3 种工作模式,可通过轻触模式键进行切换,其工作内容如下:
工作模式 | 工作内容 |
---|---|
直吹风 | 8 档风速可调 |
自然风 | 忽大忽小间隔 5 秒 |
睡眠风 | 每隔 1 小时自动降档,最后降到最低档,初始档位可自由设定 |
下面我们来实现这些功能,首先要在模式键处理函数中加入工作模式的切替处理:
/**
* @brief 模式键处理函数
* @param type: 触发类型
* @param cnt: 连击次数
* @return none
*/
STATIC VOID_T fan_key_mode_handler(IN CONST PUSH_KEY_TYPE_E type, IN CONST INT_T cnt)
{
switch (type) {
case NORMAL_KEY:
PR_DEBUG("mode press.");
if (g_fan_mag.power == TRUE) { /* 开机时 */
fan_set_mode(fan_switch_mode()); /* 设置工作模式 */
}
break;
case LONG_KEY:
PR_DEBUG("mode long press.");
break;
case SEQ_KEY:
PR_DEBUG("mode SEQ press, the count is %d.", cnt);
break;
default:
break;
}
}
3 种模式切替顺序为直吹风->自然风->睡眠风->直吹风,模式切替后进入各模式执行相关操作:
/* 工作模式 */
typedef BYTE_T FAN_MODE_E;
#define FAN_MODE_NORMAL 0x00 /* 直吹风 */
#define FAN_MODE_NATURE 0x01 /* 自然风 */
#define FAN_MODE_SLEEP 0x02 /* 睡眠风 */
/**
* @brief 切换模式
* @param none
* @return 工作模式
*/
UCHAR_T fan_switch_mode(VOID_T)
{
/* 直吹风->自然风->睡眠风->直吹风 */
if (g_fan_mag.mode == FAN_MODE_SLEEP) {
g_fan_mag.mode = FAN_MODE_NORMAL;
} else {
g_fan_mag.mode++;
}
PR_NOTICE("fan mode changed to: %d", g_fan_mag.mode);
return g_fan_mag.mode;
}
/**
* @brief 设置工作模式
* @param mode: 工作模式
* @return none
*/
VOID_T fan_set_mode(IN CONST FAN_MODE_E mode)
{
/* 各模式入口 */
switch (mode) {
case FAN_MODE_NORMAL:
fan_mode_normal();
break;
case FAN_MODE_NATURE:
fan_mode_nature();
break;
case FAN_MODE_SLEEP:
fan_mode_sleep();
break;
default:
break;
}
/* 指示灯状态更新 */
fan_set_mode_led(mode);
fan_set_gear_led(g_fan_mag.gear);
/* 上报/存储数据更新 */
fan_report_all_dp_status();
fan_write_data_to_flash();
}
各模式的具体流程实现如下:
a. 直吹风模式
/**
* @brief 直吹风模式
* @param none
* @return none
*/
STATIC VOID_T fan_mode_normal(VOID_T)
{
/* 停止其他模式使用的定时器 */
fan_nature_mode_timer_stop();
fan_sleep_mode_timer_stop();
/* 根据当前挡位设置风速并输出 */
g_fan_mag.speed = g_fan_speed_gear[g_fan_mag.gear];
fan_set_speed(g_fan_mag.speed);
PR_NOTICE("fan current speed is : %d", g_fan_mag.speed);
}
b. 自然风模式
/* TIMER ID */
#define TIMER_ID_NATURE_MODE 0 /* 自然模式 */
/* 定时时间设置 */
#define CYCLE_MS_NATURE_MODE (5*1000) /* 5s */
/**
* @brief 自然模式定时器启动
* @param none
* @return none
*/
VOID_T fan_nature_mode_timer_start(VOID_T)
{
OPERATE_RET op_ret;
op_ret = tuya_soc_software_timer_start(TIMER_ID_NATURE_MODE, CYCLE_MS_NATURE_MODE, fan_nature_mode_timer_cb);
if (op_ret != OPRT_OK) {
PR_ERR("Start nature mode timer fails");
}
}
/**
* @brief 自然模式定时器关闭
* @param none
* @return none
*/
VOID_T fan_nature_mode_timer_stop(VOID_T)
{
OPERATE_RET op_ret;
op_ret = tuya_soc_software_timer_stop(TIMER_ID_NATURE_MODE);
if (op_ret != OPRT_OK) {
PR_ERR("Stop nature mode timer fails");
}
}
/* 自然风模式挡位设置 */
#define NATURE_MODE_SPEED_SLOW_GEAR 1
#define NATURE_MODE_SPEED_FAST_GEAR 6
/**
* @brief 自然风模式风速设置
* @param start_flag: 初始化标志
* @return none
*/
STATIC VOID_T fan_set_nature_mode_speed(IN CONST BOOL_T start_flag)
{
if (start_flag) {
g_fan_mag.gear = NATURE_MODE_SPEED_FAST_GEAR;
PR_NOTICE("natural mode fast speed");
} else {
if (g_fan_mag.gear == NATURE_MODE_SPEED_SLOW_GEAR) {
g_fan_mag.gear = NATURE_MODE_SPEED_FAST_GEAR;
PR_NOTICE("natural mode fast speed");
} else {
g_fan_mag.gear = NATURE_MODE_SPEED_SLOW_GEAR;
PR_NOTICE("natural mode low speed");
}
}
g_fan_mag.speed = g_fan_speed_gear[g_fan_mag.gear];
fan_set_speed(g_fan_mag.speed);
PR_NOTICE("fan current speed is : %d", g_fan_mag.speed);
fan_set_gear_led(g_fan_mag.gear);
}
/**
* @brief 自然风模式回调函数
* @param none
* @return none
*/
VOID_T fan_nature_mode_timer_cb(VOID_T)
{
/* 风速更新,再次启动定时器 */
fan_set_nature_mode_speed(FALSE);
fan_nature_mode_timer_start();
/* 上报/存储数据更新 */
fan_report_all_dp_status();
fan_write_data_to_flash();
}
/**
* @brief 自然风模式
* @param none
* @return none
*/
STATIC VOID_T fan_mode_nature(VOID_T)
{
/* 停止其他模式使用的定时器 */
fan_sleep_mode_timer_stop();
/* 风速设置并启动定时器 */
fan_set_nature_mode_speed(TRUE);
fan_nature_mode_timer_start();
}
c. 睡眠风模式
/* TIMER ID */
#define TIMER_ID_SLEEP_MODE 1 /* 睡眠模式 */
/* 定时时间设置 */
#define CYCLE_MS_SLEEP_MODE (60*60*1000)/* 1h */
/**
* @brief 睡眠模式定时器启动
* @param none
* @return none
*/
VOID_T fan_sleep_mode_timer_start(VOID_T)
{
OPERATE_RET op_ret;
op_ret = tuya_soc_software_timer_start(TIMER_ID_SLEEP_MODE, CYCLE_MS_SLEEP_MODE, fan_sleep_mode_timer_cb);
if (op_ret != OPRT_OK) {
PR_ERR("Start sleep mode timer fails");
}
}
/**
* @brief 睡眠模式定时器关闭
* @param none
* @return none
*/
VOID_T fan_sleep_mode_timer_stop(VOID_T)
{
OPERATE_RET op_ret;
op_ret = tuya_soc_software_timer_stop(TIMER_ID_SLEEP_MODE);
if (op_ret != OPRT_OK) {
PR_ERR("Stop sleep mode timer fails");
}
}
/**
* @brief 睡眠风模式风速设置
* @param start_flag: 初始化标志
* @return none
*/
STATIC VOID_T fan_set_sleep_mode_speed(IN CONST BOOL_T start_flag)
{
if (!start_flag) {
if (g_fan_mag.gear > 0) {
g_fan_mag.gear--;
}
}
PR_NOTICE("current gear is: %d", g_fan_mag.gear);
g_fan_mag.speed = g_fan_speed_gear[g_fan_mag.gear];
fan_set_speed(g_fan_mag.speed);
PR_NOTICE("fan current speed is : %d", g_fan_mag.speed);
fan_set_gear_led(g_fan_mag.gear);
}
/**
* @brief 睡眠风模式回调函数
* @param none
* @return none
*/
VOID_T fan_sleep_mode_timer_cb(VOID_T)
{
/* 风速更新,再次启动定时器 */
fan_set_sleep_mode_speed(FALSE);
if (g_fan_mag.gear != 0) {
fan_sleep_mode_timer_start();
}
/* 上报/存储数据更新 */
fan_report_all_dp_status();
fan_write_data_to_flash();
}
/**
* @brief 睡眠风模式
* @param none
* @return none
*/
STATIC VOID_T fan_mode_sleep(VOID_T)
{
/* 停止其他模式使用的定时器 */
fan_nature_mode_timer_stop();
/* 风速设置并启动定时器 */
fan_set_sleep_mode_speed(TRUE);
if (g_fan_mag.gear != 0) {
fan_sleep_mode_timer_start();
}
}
本案例设置 8 挡风速可调,通过旋钮转向加大或减小风速。
下面来实现挡位调节功能,只需在旋钮编码器回调函数中加入以下处理:
/**
* @brief 旋钮编码器回调函数
* @param dir: 旋转方向 TRUE-顺时针 FALSE-逆时针
* @return none
*/
VOID_T fan_rotary_cb(BOOL_T dir)
{
/* 关机时旋钮无效 */
if (g_fan_mag.power == FALSE) {
return;
}
/* 旋转旋钮时可退出定时设置 */
if (g_fan_mag.timing_display_flag) {
fan_timing_confirm_timer_stop();
fan_quit_timing_setting();
}
/* 根据转向加大或减小挡位 */
if (dir) {
PR_NOTICE("The direction of rotation is CW.");
g_fan_mag.gear = (g_fan_mag.gear >= (8-1)) ? (8-1) : (g_fan_mag.gear+1);
} else {
PR_NOTICE("The direction of rotation is CCW.");
g_fan_mag.gear = (g_fan_mag.gear <= 0) ? (0) : (g_fan_mag.gear-1);
}
PR_NOTICE("fan current gear is : %d", g_fan_mag.gear);
/* 更新风速输出 (自然风模式下挡位变化时默认切换到直吹风模式) */
if (g_fan_mag.mode == FAN_MODE_SLEEP) {
fan_set_mode(FAN_MODE_SLEEP);
} else {
fan_set_mode(FAN_MODE_NORMAL);
}
}
本案例通过操作定时键可以设置定时关机时间,由于挡位指示和定时指示共用 4 个指示灯,为方便用户确认当前设备状态,定时关机功能的具体设置方式如下:
状态 | 操作 | 响应 |
---|---|---|
未进入定时设置 | 轻触定时键 | 进入定时设置,指示灯显示当前剩余定时时间; |
已进入定时设置 | 轻触定时键 | 按照”无定时->1 小时->2 小时->3 小时->4 小时->无定时“切换定时时间,指示灯显示当前设置定时时间;开启定时确认的 2 秒计时; |
已进入定时设置,设置定时 2 秒后 | / | 确认定时时间并退出定时设置,指示灯显示当前挡位;跟据定时设置结果进行定时关机处理; |
已进入定时设置,设置定时 2 秒内 | 轻触定时键以外任意键或旋转旋钮 | 定时设置无效,退出定时设置,指示灯显示当前挡位; |
a. 编写定时键处理函数
/**
* @brief 定时键处理函数
* @param type: 触发类型
* @param cnt: 连击次数
* @return none
*/
STATIC VOID_T fan_key_timer_handler(IN CONST PUSH_KEY_TYPE_E type, IN CONST INT_T cnt)
{
switch (type) {
case NORMAL_KEY:
PR_DEBUG("timer press.");
if (g_fan_mag.power == TRUE) { /* 开机时 */
fan_set_local_timing(); /* 设置本地定时 */
}
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;
}
}
b. 本地定时设置及确认:
/* TIMER ID */
#define TIMER_ID_TIMING_CONFIRM 2 /* 定时功能确认 */
/* 定时时间设置 */
#define CYCLE_MS_TIMING_CONFIRM (2*1000) /* 2s */
/**
* @brief 定时设置确认定时器启动
* @param none
* @return none
*/
VOID_T fan_timing_confirm_timer_start(VOID_T)
{
OPERATE_RET op_ret;
op_ret = tuya_soc_software_timer_start(TIMER_ID_TIMING_CONFIRM, CYCLE_MS_TIMING_CONFIRM, fan_confirm_timer_cb);
if (op_ret != OPRT_OK) {
PR_ERR("Start confirm timer fails");
}
}
/**
* @brief 定时确认定时器关闭
* @param none
* @return none
*/
VOID_T fan_timing_confirm_timer_stop(VOID_T)
{
OPERATE_RET op_ret;
op_ret = tuya_soc_software_timer_stop(TIMER_ID_TIMING_CONFIRM);
if (op_ret != OPRT_OK) {
PR_ERR("Stop confirm timer fails");
}
}
/* 定时设置 */
#define FAN_TIMING_NONE 0
#define FAN_TIMING_MAX 4
/**
* @brief 本地定时设置
* @param none
* @return none
*/
VOID_T fan_set_local_timing(VOID_T)
{
if (g_fan_mag.timing_display_flag == FALSE) {
/* 进入定时设置,获取当前剩余定时时间 */
g_fan_mag.timing_set = g_fan_mag.timing;
g_fan_mag.timing_display_flag = TRUE;
} else {
/* 定时时间切换:无定时->1h->2h->3h->4h->无定时 */
if (g_fan_mag.timing_set >= FAN_TIMING_MAX) {
g_fan_mag.timing_set = FAN_TIMING_NONE;
} else {
g_fan_mag.timing_set++;
}
}
/* 定时设置时间显示并启动计时 */
fan_set_timing_led(g_fan_mag.timing_set);
fan_timing_confirm_timer_start();
}
/**
* @brief 退出定时设置
* @param none
* @return none
*/
VOID_T fan_quit_timing_setting(VOID_T)
{
/* 回到挡位显示 */
g_fan_mag.timing_display_flag = FALSE;
fan_set_gear_led(g_fan_mag.gear);
}
/**
* @brief 定时确认回调函数
* @param none
* @return none
*/
VOID_T fan_confirm_timer_cb(VOID_T)
{
if (g_fan_mag.timing != g_fan_mag.timing_set) {
/* 确认定时设置 */
g_fan_mag.timing = g_fan_mag.timing_set;
fan_report_all_dp_status();
/* 关闭定时 */
fan_shutdown_timer_stop();
/* 定时设置不是“无定时”时, 开启定时关机 */
if (g_fan_mag.timing > FAN_TIMING_NONE) {
fan_shutdown_timer_start();
PR_NOTICE("The fan will shutdown after: %dh", g_fan_mag.timing);
}
}
/* 退出定时设置 */
fan_quit_timing_setting();
}
c. 定时关机计时启动,计时结束时关机:
/* TIMER ID */
#define TIMER_ID_SHUTDOWN 3 /* 定时关机 */
/* 定时时间设置 */
#define CYCLE_MS_SHUTDOWN (60*60*1000)/* 1h */
/**
* @brief 定时关机定时器关闭
* @param none
* @return none
*/
VOID_T fan_shutdown_timer_stop(VOID_T)
{
OPERATE_RET op_ret;
op_ret = tuya_soc_software_timer_stop(TIMER_ID_SHUTDOWN);
if (op_ret != OPRT_OK) {
PR_ERR("Stop shutdown timer fails");
}
}
/**
* @brief 定时设置确认定时器启动
* @param none
* @return none
*/
VOID_T fan_timing_confirm_timer_start(VOID_T)
{
OPERATE_RET op_ret;
op_ret = tuya_soc_software_timer_start(TIMER_ID_TIMING_CONFIRM, CYCLE_MS_TIMING_CONFIRM, fan_confirm_timer_cb);
if (op_ret != OPRT_OK) {
PR_ERR("Start confirm timer fails");
}
}
/**
* @brief 定时关机回调函数
* @param none
* @return none
*/
VOID_T fan_shutdown_timer_cb(VOID_T)
{
g_fan_mag.timing--;
g_fan_mag.timing_set = g_fan_mag.timing;
if (g_fan_mag.timing == 0) {
fan_shutdown_timer_stop();
fan_set_power(FALSE);
} else {
PR_NOTICE("The fan will shutdown after: %dh", g_fan_mag.timing);
fan_report_all_dp_status();
fan_write_data_to_flash();
fan_shutdown_timer_start();
}
}
本案例提供一键复位功能,按下模式键 3 秒,设备状态即可恢复到默认状态。
a. 添加模式键的长按处理:
/**
* @brief 模式键处理函数
* @param type: 触发类型
* @param cnt: 连击次数
* @return none
*/
STATIC VOID_T fan_key_mode_handler(IN CONST PUSH_KEY_TYPE_E type, IN CONST INT_T cnt)
{
switch (type) {
case NORMAL_KEY:
PR_DEBUG("mode press.");
if (g_fan_mag.power == TRUE) { /* 开机时 */
fan_set_mode(fan_switch_mode()); /* 设置工作模式 */
}
break;
case LONG_KEY:
PR_DEBUG("mode long press.");
fan_reset(); /* 复位 */
break;
case SEQ_KEY:
PR_DEBUG("mode SEQ press, the count is %d.", cnt);
break;
default:
break;
}
}
b. 编写设备复位函数,重置设备状态:
/**
* @brief 复位
* @param none
* @return none
*/
VOID_T fan_reset(VOID_T)
{
g_fan_mag = g_fan_mag_default;
fan_set_bright(g_fan_mag.bright);
fan_set_power(g_fan_mag.power);
PR_INFO("fan reset.");
}
本案例设置了断电记忆功能,可在通电时恢复到上一次断电前的状态,主要通过读写 Flash 来实现此功能。
a. 确定需要记忆的状态属性,编写 Flash 读写函数:
/* 风扇数据存储偏移量 */
#define FAN_DATA_STORAGE_OFFSET 0x00
/* 风扇数据存储长度:数据头-风扇开关-风扇模式-风扇转速-定时时间-CRC_8 校验 */
#define FAN_DATA_STORAGE_LEN 6
/* 数据帧头 */
#define FAN_DATA_HEADER 0xFF
/* 风扇状态数据存放位置 */
#define FAN_STATE_POWER 1
#define FAN_STATE_MODE 2
#define FAN_STATE_SPEED 3
#define FAN_STATE_TIMING 4
/**
* @brief 读取 flash 存储的风扇数据
* @param none
* @return none
*/
VOID_T fan_read_data_from_flash(VOID_T)
{
INT_T read_cnt;
UCHAR_T fan_state_data_crc;
UCHAR_T fan_state_buffer[FAN_DATA_STORAGE_LEN];
/* 读取 flash 数据 */
read_cnt = tuya_soc_flash_read(SAVE_TYP1, FAN_DATA_STORAGE_OFFSET, FAN_DATA_STORAGE_LEN, (UCHAR_T *)fan_state_buffer);
if (read_cnt <= 0) {
PR_ERR("Reset cnt read error!");
return;
}
/* 检查数据头 */
if (fan_state_buffer[0] != FAN_DATA_HEADER) {
PR_ERR("data head error");
return;
}
/* 检查校验数据 */
fan_state_data_crc = get_crc_8(fan_state_buffer, (FAN_DATA_STORAGE_LEN - 1) * SIZEOF(UCHAR_T));
if (fan_state_data_crc != fan_state_buffer[FAN_DATA_STORAGE_LEN - 1]) {
PR_ERR("crc error, before_fan_power_off_state[%d] = %02x, crc data = %02x.", FAN_DATA_STORAGE_LEN - 1, fan_state_buffer[FAN_DATA_STORAGE_LEN - 1], fan_state_data_crc);
return;
}
/* 更新风扇数据 */
g_fan_mag.power = fan_state_buffer[FAN_STATE_POWER];
g_fan_mag.mode = fan_state_buffer[FAN_STATE_MODE];
g_fan_mag.speed = fan_state_buffer[FAN_STATE_SPEED];
g_fan_mag.timing = fan_state_buffer[FAN_STATE_TIMING];
}
/**
* @brief 将风扇数据写入 flash
* @param none
* @return none
*/
VOID_T fan_write_data_to_flash(VOID_T)
{
INT_T i;
OPERATE_RET opRet;
UCHAR_T fan_data_buffer[FAN_DATA_STORAGE_LEN];
/* 更新风扇数据到 buffer */
fan_data_buffer[0] = FAN_DATA_HEADER;
fan_data_buffer[FAN_STATE_POWER] = g_fan_mag.power;
fan_data_buffer[FAN_STATE_MODE] = g_fan_mag.mode;
fan_data_buffer[FAN_STATE_SPEED] = g_fan_mag.speed;
fan_data_buffer[FAN_STATE_TIMING] = g_fan_mag.timing;
fan_data_buffer[FAN_DATA_STORAGE_LEN-1] = get_crc_8(fan_data_buffer, (FAN_DATA_STORAGE_LEN - 1) * SIZEOF(UCHAR_T));
/* 打印 buffer */
for (i = 0; i < FAN_DATA_STORAGE_LEN; i++) {
PR_NOTICE(" +++ fan_data_buffer is [%d] : %02x", i, fan_data_buffer[i]);
}
/* 写入 flash 数据 */
opRet = tuya_soc_flash_write(SAVE_TYP1, FAN_DATA_STORAGE_OFFSET, fan_data_buffer, FAN_DATA_STORAGE_LEN);
if (opRet != OPRT_OK) {
PR_ERR("write flash error");
}
return;
}
b. 由于断电随时可能发生,所以在每次状态属性变化时就调用写 Flash 函数存储一次数据,具体调用位置已在上述各项功能代码中体现;
c. 在上电时调用读 Flash 函数加载数据:
/* 风扇数据管理 */
FAN_MANAGE_T g_fan_mag;
FAN_MANAGE_T g_fan_mag_default = {
.power = FALSE,
.mode = FAN_MODE_NORMAL,
.speed = FAN_SPEED_DEFAULT,
.bright = FAN_BRIGHT_NORMAL,
.timing = FAN_TIMING_NONE,
.timing_set = FAN_TIMING_NONE,
.timing_display_flag = FALSE,
.gear = FAN_GEAR_DEFAULT
};
/**
* @brief BLDC 风扇应用初始化程序, 被 app_init 调用
* @param none
* @return none
*/
VOID_T fan_app_init(VOID_T)
{
/* 初始化 g_fan_mag */
g_fan_mag = g_fan_mag_default;
/* 读取 flash 数据 */
fan_read_data_from_flash();
/* 首次上电时,更新默认值 */
if (g_fan_mag.power == 0xFF) {
g_fan_mag = g_fan_mag_default;
fan_write_data_to_flash();
} else {
g_fan_mag.power = FALSE;
}
}
本案例可通过长按 10 秒配网键重置设备配网状态。添加配网键处理,调用 SDK 提供的接口进行解绑:
/**
* @brief 配网键处理函数
* @param type: 触发类型
* @param cnt: 连击次数
* @return none
*/
STATIC VOID_T fan_key_wifi_handler(IN CONST PUSH_KEY_TYPE_E type, IN CONST INT_T cnt)
{
switch (type) {
case NORMAL_KEY:
PR_DEBUG("wifi press.");
break;
case LONG_KEY:
PR_DEBUG("wifi long press.");
tuya_iot_wf_gw_unactive(); /* 手动移除设备 */
break;
case SEQ_KEY:
PR_DEBUG("wifi SEQ press, the count is %d.", cnt);
break;
default:
break;
}
}
在设备配网后,就可以使用 App 来控制设备和查看设备上报的数据,下面是数据上报的实现过程。
/* DP ID */
#define DP_ID_SWITCH 1
#define DP_ID_MODE 2
#define DP_ID_FAN_SPEED 3
#define DP_ID_BRIGHT_MODE 101
#define DP_ID_COUNTDOWN_SET 22
/* DP amount */
#define FAN_DP_AMOUNT 5
/**
* @brief 上报所有 dp 点
* @param none
* @return none
*/
VOID_T fan_report_all_dp_status(VOID_T)
{
OPERATE_RET op_ret = OPRT_OK;
/* 没有连接到路由器,退出 */
GW_WIFI_NW_STAT_E wifi_state = STAT_LOW_POWER;
get_wf_gw_nw_status(&wifi_state);
if (wifi_state <= STAT_AP_STA_DISC || wifi_state == STAT_STA_DISC) {
return;
}
TY_OBJ_DP_S *dp_arr = (TY_OBJ_DP_S *)Malloc(FAN_DP_AMOUNT*SIZEOF(TY_OBJ_DP_S));
if(NULL == dp_arr) {
PR_ERR("malloc failed");
return;
}
memset(dp_arr, 0, FAN_DP_AMOUNT*SIZEOF(TY_OBJ_DP_S));
dp_arr[0].dpid = DP_ID_SWITCH;
dp_arr[0].type = PROP_BOOL;
dp_arr[0].time_stamp = 0;
dp_arr[0].value.dp_bool = g_fan_mag.power;
dp_arr[1].dpid = DP_ID_MODE;
dp_arr[1].type = PROP_ENUM;
dp_arr[1].time_stamp = 0;
dp_arr[1].value.dp_enum = g_fan_mag.mode;
dp_arr[2].dpid = DP_ID_FAN_SPEED;
dp_arr[2].type = PROP_VALUE;
dp_arr[2].time_stamp = 0;
dp_arr[2].value.dp_value = g_fan_mag.speed;
dp_arr[3].dpid = DP_ID_BRIGHT_MODE;
dp_arr[3].type = PROP_ENUM;
dp_arr[3].time_stamp = 0;
dp_arr[3].value.dp_enum = g_fan_mag.bright;
dp_arr[4].dpid = DP_ID_COUNTDOWN_SET;
dp_arr[4].type = PROP_ENUM;
dp_arr[4].time_stamp = 0;
dp_arr[4].value.dp_enum = g_fan_mag.timing;
op_ret = dev_report_dp_json_async(NULL, dp_arr, FAN_DP_AMOUNT);
Free(dp_arr);
if (OPRT_OK != op_ret) {
PR_ERR("dev_report_dp_json_async relay_config data error, err_num: %d", op_ret);
}
PR_DEBUG("dp_query report_all_dp_data");
}
在 App 上改变设备状态时,会从云端下发控制数据,设备在接收到数据后进行如下处理,即可实现云端任务。
/**
* @brief 处理 dp 信息
* @param root: dp 信息
* @return none
*/
VOID_T fan_deal_dp_proc(IN CONST TY_OBJ_DP_S *root)
{
UCHAR_T dpid;
dpid = root->dpid;
PR_NOTICE("dpid:%d", dpid);
switch(dpid) {
case DP_ID_SWITCH:
PR_NOTICE("root->value.dp_bool:%d", root->value.dp_bool);
fan_set_cloud_power(root->value.dp_bool);
break;
case DP_ID_MODE:
PR_NOTICE("root->value.dp_enum:%d", root->value.dp_enum);
fan_set_cloud_mode((FAN_MODE_E)root->value.dp_enum);
break;
case DP_ID_FAN_SPEED:
PR_NOTICE("root->value.dp_value:%d", root->value.dp_value);
fan_set_cloud_speed((UCHAR_T)root->value.dp_value);
break;
case DP_ID_BRIGHT_MODE:
PR_NOTICE("root->value.dp_enum:%d", root->value.dp_enum);
fan_set_cloud_bright((FAN_BRIGHT_E)root->value.dp_enum);
break;
case DP_ID_COUNTDOWN_SET:
PR_NOTICE("root->value.dp_enum:%d", root->value.dp_enum);
fan_set_cloud_timing((UCHAR_T)root->value.dp_enum);
break;
default:
break;
}
}
各属性的处理函数如下:
/**
* @brief 云端开关设置
* @param power: 开关
* @return none
*/
VOID_T fan_set_cloud_power(IN CONST BOOL_T power)
{
if (power != g_fan_mag.power) {
g_fan_mag.power = power;
fan_set_power(g_fan_mag.power);
}
}
/**
* @brief 云端模式设置
* @param mode: 模式
* @return none
*/
VOID_T fan_set_cloud_mode(IN CONST FAN_MODE_E mode)
{
if (!g_fan_mag.power) {
return;
}
if (mode != g_fan_mag.mode) {
g_fan_mag.mode = mode;
fan_set_mode(g_fan_mag.mode);
}
}
/**
* @brief 云端速度设置
* @param speed: 速度值
* @return none
*/
VOID_T fan_set_cloud_speed(IN CONST UCHAR_T speed)
{
/* 关机时设置无效 */
if (!g_fan_mag.power) {
return;
}
/* 速度和挡位更新 */
fan_set_speed(speed);
g_fan_mag.speed = speed;
g_fan_mag.gear = fan_get_cur_gear_according_to_speed(speed);
fan_set_gear_led(g_fan_mag.gear);
PR_NOTICE("fan current speed is : %d", g_fan_mag.speed);
PR_NOTICE("fan current gear is : %d", g_fan_mag.gear);
/* 模式如果是自然风则更新为直吹风 */
if (g_fan_mag.mode == FAN_MODE_NATURE) {
g_fan_mag.mode = FAN_MODE_NORMAL;
fan_set_mode_led(g_fan_mag.mode);
fan_nature_mode_timer_stop();
}
/* 模式如果是睡眠风则更新重新计时 */
if (g_fan_mag.mode == FAN_MODE_SLEEP) {
fan_sleep_mode_timer_stop();
fan_sleep_mode_timer_start();
}
/* 更新记忆值 */
fan_write_data_to_flash();
}
/**
* @brief 亮度设置
* @param bright: 亮度
* @return none
*/
STATIC VOID_T fan_set_bright(IN CONST FAN_BRIGHT_E bright)
{
switch (bright) {
case FAN_BRIGHT_NORMAL:
fan_set_led_dimmer(LED_DIMMER_PWM_DUTY_NORMAL);
break;
case FAN_BRIGHT_DARKER:
fan_set_led_dimmer(LED_DIMMER_PWM_DUTY_DARKER);
break;
default:
break;
}
}
/**
* @brief 云端亮度设置
* @param bright: 亮度
* @return none
*/
VOID_T fan_set_cloud_bright(IN CONST FAN_BRIGHT_E bright)
{
if (bright != g_fan_mag.bright) {
g_fan_mag.bright = bright;
fan_set_bright(bright);
}
}
/**
* @brief 云端定时设置
* @param timing: 定时值
* @return none
*/
VOID_T fan_set_cloud_timing(IN CONST UCHAR_T timing)
{
if ((!g_fan_mag.power) || (timing == g_fan_mag.timing)) {
return;
}
if (timing > FAN_TIMING_MAX) {
g_fan_mag.timing = FAN_TIMING_MAX;
} else {
g_fan_mag.timing = timing;
}
fan_shutdown_timer_stop();
if (g_fan_mag.timing > 0) {
fan_shutdown_timer_start();
}
}
至此,智能电风扇就完成了,它可以通过 App 或者按键控制,具有 3 种模式风、风速调节、定时关机、断电记忆、灯光亮度调节等功能。在这款智能电风扇的基础上还有很多功能可以深入开发,使体验更加人性化,智能化。同时您可以基于 涂鸦 IoT 平台 丰富它的功能,也可以更加方便地搭建更多智能产品原型,加速智能产品的开发流程。