随着智能家居的深入拓展,智能窗帘凭借成熟的技术和产品,逐步成为了智能家居家庭中的标配,在家装智能化市场有很高的应用价值。
目前智能窗帘的产品主要以电动窗帘产品为主,该产品主要通过电机驱动实现对窗帘的操控,从安装上讲,该产品更适合前装市场,因为需要结合用户户型、门窗大小预留安装空间和电源接口。对于后装市场,往往需要专业人员上门丈量确认是否符合改装要求,大大增加了安装成本,因此目前市场上出现了小型的窗帘机器人,完美解决了后装市场的痛点,使得普通窗帘秒变智能窗帘。
4000mAh的可充电锂电池(3.7V)
南麟 XT2052
3PEAK TLV700F33
德州仪器 OPT3004
意法半导体 LIS2DW12
该方案基于涂鸦智能的一款低功耗嵌入式 BLE 模组 模组作为控制单元和无线连接单元,通过照度传感器检测当前环境的光照强度,通过加速度传感器识别手动拽拉窗帘的状态,由电机带动窗帘运动实现对窗帘的打开和关闭。
主供电电源
为保证产品能稳定可靠地运行,也为了避免在窗帘附近拉电线为产品提供电源,该方案选用4000mAh的可充电锂电池(3.7V)作为主供电电源。当电量不足时,可拆卸后进行充电。
电源管理芯片
我们选用南麟的XT2052作为锂电池充电管理芯片,该芯片的特点:
选用3PEAK的TLV700F33 作为3.3V稳定输出的LDO器件,该芯片的特点:
参考电路图
充电电流最大为1A。
两个指示灯用于指示充电状态和充电完成状态,红灯亮表示充电状态中,绿灯亮表示充电完成。
VBAT是电池电压,并直接用于驱动电机运行;3.3V是LDO稳压后的稳定3.3V直流电压,用于蓝牙模块和传感器的供电。
可感应光照强度的器件主要有光敏电阻,光敏二极管和照度传感器芯片等。其中,光敏电阻和光敏二极管采集的是模拟量,还需要通过电压值和光照强度的对应表,才能转换成相应的光照强度,所以该方案中我们选择的是TI的OPT3004传感器芯片,它可通过数字接口直接输出当前的光照强度值,非常适合在App端实时显示光照强度值。对比与光敏电阻和光敏二极管,OPT3004有以下几个有点:
参考电路
需要检测到人为的拉窗帘动作,自动关拉窗帘,而人为的拉拽窗帘动作可拆分为水平方向的力,可以选择3轴加速度传感器来识别水平方向上受到的力,因此本次选择的是ST的LIS2DW12加速度传感器。在低功耗模式下,其工作电流<1uA。
参考电路
加速度传感器中,我们选用的数字接口为I2C接口,它可通过芯片的CS引脚外围电路选择通信接口为I2C或SPI。
由于LIS2DW12的地址码和上文照度传感芯片OPT3004的地址码不一样,所以两个传感器可以共用一个I2C总线接口。
机器人通过转轮和窗帘导轨的摩擦力带动机器人和窗帘运动,因此需要大扭矩,而速度可以相对低些,所以本方案选择减速电机。
减速电机就是减速器与电机的集成体,电机的高速低扭矩经过齿轮减速增扭以后再带动发动机,它的优点是耗电量小,扭矩大。
本方案电机驱动选择合泰的HT7K1201驱动芯片,它是单通道H桥驱动器,输入电压:1.8~6V;峰值电流:1.3A;休眠电流:<0.1uA;导通电阻:0.5R,正转,反转,制动和待机4种状态;欠压保护(1.5V);过流保护(1.3A);输出短路保护(1.9A);热关机保护。
本方案选用涂鸦智能的一款低功耗嵌入式BLE模组作为主控板,并基于该模组进行SoC开发,实现BLE直连、电机控制和传感器数据采集。
核心控制单元原理图中需要增加一个指示灯和按键,指示灯用来指示模块联网状态,按键用来重置模块联网信息。
GitHub 仓库
tuya_ble_sdk_Demo_Project_tlsr8253,克隆代码库到本地后,请仔细阅读 README.md
文件。
环境搭建
TLSR825x 蓝牙单点开发使用的是泰凌微官方 IDE,请访问 IDE for TLSR8 Chips。
下载后默认安装即可。接下来进行项目导入,可参考下载 IDE 的链接。
工程开发
修改PID。
修改auth_key、device_id、mac。
开始编译。
烧录工具(下载链接)
芯片平台选择8258 EVK,编译完成后单击 File 找到对应的bin文件,路径位于 tuya_ble_sdk_Demo_Project_tlsr8253\telink_kite_ble_sdk_v3.4.0_20190816\ble_sdk_multimode\8258_module\8258_module.bin
烧录完成后点击 Reset 程序即可运行。
烧录器使用Telink烧录器,SWM口接芯片的SWS口即可烧录。
注意:
- GPIO读取到高电平返回的是一个大于1的数,1、2、128都有可能。
- SDK默认串口日志打印为TL_C2,波特率为230400,由于IO口资源有限,该项目中将日志打印口改为TL_D3,修改完成后编译即可。修改路径为
tuya_ble_sdk_Demo_Project_tlsr8253\telink_kite_ble_sdk_v3.4.0_20190816\ble_sdk_multimode\vendor\8258_module\app_config.h
。
将第47行:
#define DEBUG_INFO_TX_PIN GPIO_PC2
修改为:
#define DEBUG_INFO_TX_PIN GPIO_PD3
功能名称 | 功能详细解释 | 备注 |
---|---|---|
普通控制 | 有打开、关闭和停止三个功能。 | 停止按钮在任何情况下都能保证使电机停转。 |
百分比控制 | 按照窗帘长度的百分比来控制。 | |
自动测量窗帘长度 | 自动测量窗帘机器人跑全程所需时间。 | |
电机正反转 | 改变电机运行时默认的正反转方向。 | 可以将电机打开窗帘的方式改变为正转或反转 |
动作检测 | 检测到手动开,将窗帘全部打开。检测到手动关,将窗帘全部关闭。 | APP上名称为:自启动开关 |
光照强度显示 | 显示测量到的当前光照强度。 | 单位为:lux。 |
光照控制 | 支持设置最低光照值和最高光照值,当光照强度低于最低值,或高于最高值时,窗帘自动关闭。 | 设置的光照值为lux单位下除以十。例:光照值设置为10,当检测到光照值低于100lux 时将会关闭窗帘。说明:如果用户不愿意关闭窗帘,打开两次将会自动关闭该功能。 |
电量显示 | 显示电机当前的电量。 |
普通控制和百分比控制都需要在已经知道窗帘的长度的情况下来进行实现的,由于电机的转速是一定的,在测量窗帘长度的时候其实是测试从 0% 到 100% 需要多少时间,然后根据电机当前所处位置和要到达的位置来计算需要运行的时间,即可实现控制窗帘机器人移动到指定位置。
百分比控制在接收到app下发的DP点后先调用void curtain_percent_control(unsigned char current_position, unsigned char target_position)
函数使窗帘机器人向对应的方向运行,将需要运行的时间存入全局变量中,供停止任务函数使用。void curtain_percent_control_stop_task(void)
函数需要被一直执行,当检测到运行时间已达到的时候,停止电机运行,将当前位置存入到FLASH中,上报当前电机位置。
百分比控制部分代码示例:
void curtain_percent_control(unsigned char current_position, unsigned char target_position)
{
unsigned long total_time;
if ((current_position == target_position) || \
(current_position < 0) || (current_position > 100) || \
(target_position < 0) || (target_position > 100)) {
TUYA_APP_LOG_ERROR("input error");
*curtain_robot.dp_percent_state.dp_data = current_position;
return;
}
if (curtain_robot.motor_run_mode == RUN_MODE_IDLE) {
curtain_robot.motor_run_mode = RUN_MODE_PERCENT_CONTROL;
} else {
TUYA_APP_LOG_ERROR("motor_run_mode != RUN_MODE_IDLE");
*curtain_robot.dp_percent_state.dp_data = current_position;
return;
}
total_time = curtain_robot.dp_time_total.dp_data[0];
total_time = (total_time << 8) | curtain_robot.dp_time_total.dp_data[1];
if (total_time == 0) {
*curtain_robot.dp_percent_state.dp_data = current_position;
return;
}
if (current_position < target_position) {
curtain_robot.run_end_time = ((target_position - current_position) * (total_time * 10)); //*10=*1000/100,ms->us
curtain_robot.run_start_time = clock_time();
curtain_close();
} else {
curtain_robot.run_end_time = (current_position - target_position) * (total_time * 10);
curtain_robot.run_start_time = clock_time();
curtain_open();
}
/* percent control state target position */
percent_target_value = target_position;
*curtain_robot.dp_percent_control.dp_data = target_position;
/* percent control */
dp_update(curtain_robot.dp_percent_control.dp_id, \
curtain_robot.dp_percent_control.dp_type, \
curtain_robot.dp_percent_control.dp_data, \
curtain_robot.dp_percent_control.dp_data_len);
}
void curtain_percent_control_stop_task(void)
{
if (((curtain_robot.motor_run_mode == RUN_MODE_PERCENT_CONTROL)) && \
(clock_time_exceed(curtain_robot.run_start_time, curtain_robot.run_end_time))) {
curtain_pause();
curtain_robot.motor_run_mode = RUN_MODE_IDLE;
save_device_data();
/* percent state */
*curtain_robot.dp_percent_state.dp_data = percent_target_value;
dp_update(curtain_robot.dp_percent_state.dp_id, \
curtain_robot.dp_percent_state.dp_type, \
curtain_robot.dp_percent_state.dp_data, \
curtain_robot.dp_percent_state.dp_data_len);
}
}
自动测量窗帘长度实际上是测量窗帘杆子的长度,那么如何判断窗帘机器人是否到达终点也就是两端是测量的关键,控制电机运动的功能实现都需要获取测量得到的总时长来实现的。
判断窗帘机器人到达终点,在该硬件上有两种方法来判断。
二是通过三轴加速度传感器 lis2dw12 来判断是否到达终点。根据 lis2dw12 在硬件上的放置位置来看,判断窗帘机器人的运行状态主要依靠三轴加速度传感器中的x轴来判断。下图为窗帘机器人在运行过程中三轴加速度传感器x轴的数据,静止状态基本无波动,启动后到到达终点前上下波动较大,在到达终点撞击的那一刻x轴数据明显的一个凸起的上升,到达终点后电机未停止运行可以看到x轴的数据比静止时平均要高,且数据上下浮动比运行过程中要较小。
通过上面的两种方式可以看到,使用电压比较的方式来判断是否到达终点相较于使用 lis2dw12 来判断来看是较为简单。但使用三轴加速度计 lis2dw12 可以清晰的知道当前电机处于何种运行状态。
通过观察波形可以知道,当你向某一方向拉窗帘时,总体来看虽呈波动状态,但最开始时的值与拉取的方向有关,所以该部分功能的实现是通过判断窗帘机器人被拉动后最开始的一部分数值来确定将要跑动的方向。
示例代码:
short x_data_buf[100] = {0};
unsigned int clean_x_buf_count = 0;
unsigned char x_data_index = 0;
void auto_power_task(void)
{
short x_axis_data = 0;
unsigned char i = 0;
unsigned char open_count = 0, close_count = 0;
if (*(curtain_robot.dp_auto_power.dp_data) == TRUE) {
if (clock_time_exceed(curtain_robot.get_lis2dw12_data_time, 10 * 1000)) {
curtain_robot.get_lis2dw12_data_time = clock_time();
if (curtain_robot.motor_run_mode != RUN_MODE_IDLE) {
return;
}
x_axis_data = get_lis2dw12_x_value();
if (x_axis_data > 100 || x_axis_data < -100) {
x_data_buf[x_data_index] = x_axis_data;
x_data_index++;
clean_x_buf_count = 0;
} else {
clean_x_buf_count++;
}
if ((clean_x_buf_count > 20) && (x_data_index >= 20)) { //At this point it has levelled off
for (i=0; i < 7; i++) {
if (x_data_buf[i] >= 0) {
open_count++;
} else {
close_count++;
}
}
if (*curtain_robot.dp_percent_state.dp_data == 0) {
curtain_robot.auto_power_state = AUTO_POWER_CLOSE;
} else if (*curtain_robot.dp_percent_state.dp_data == 100) {
curtain_robot.auto_power_state = AUTO_POWER_OPEN;
} else if (open_count < close_count) {
curtain_robot.auto_power_state = AUTO_POWER_CLOSE;
} else {
curtain_robot.auto_power_state = AUTO_POWER_OPEN;
}
//clean flag
clean_x_buf_count= 0;
x_data_index=0;
}
if (curtain_robot.auto_power_state != AUTO_POWER_IDLE && clean_x_buf_count>= 100) {
if (curtain_robot.auto_power_state == AUTO_POWER_OPEN) {
curtain_percent_control(*curtain_robot.dp_percent_state.dp_data, 0);
} else if (curtain_robot.auto_power_state == AUTO_POWER_CLOSE) {
curtain_percent_control(*curtain_robot.dp_percent_state.dp_data, 100);
}
curtain_robot.auto_power_state=AUTO_POWER_IDLE;
clean_x_buf_count = 0;
}
if (clean_x_buf_count > 500) {
clean_x_buf_count = 0;
x_data_index=0;
}
}
}
}
检测光照强度的传感器使用的是opt3004,通过IIC协议,通过读取 opt3004 的 result 寄存器获取当前的光照值。
示例代码:
#define I2C_CLK_SPEED 200000
short lsb_size_tab[] = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048};
unsigned char opt3004_init(void)
{
unsigned char device_id_data_buf[2] = {0};
unsigned char manufacturer_id_data_buf[2] = {0};
unsigned char write_data[2] = {0xcc, 0x10};
unsigned long delay_time = 0;
i2c_gpio_set(I2C_GPIO_GROUP_C0C1);
i2c_master_init(OPT3004_I2C_ADDR_W, (unsigned char)(CLOCK_SYS_CLOCK_HZ / (4 * I2C_CLK_SPEED)));
i2c_write_series(OPT3004_CONFIG_REGISTER_ADDR, 1, write_data, 2);
i2c_master_init(OPT3004_I2C_ADDR_R, (unsigned char)(CLOCK_SYS_CLOCK_HZ / (4 * I2C_CLK_SPEED)));
i2c_read_series(OPT3004_DEVICE_ID_REGISTER_ADDR, 1, device_id_data_buf, 2);
delay_time = clock_time();
while (!clock_time_exceed(delay_time, 100)); //100us delay
i2c_read_series(OPT3004_MANUFACTURER_ID_REGISTER_ADDR, 1, manufacturer_id_data_buf, 2);
if ((((device_id_data_buf[0]<<8) + device_id_data_buf[1]) != DEVICE_ID) || \
(((manufacturer_id_data_buf[0]<<8) + manufacturer_id_data_buf[1]) != MANUFACTURER_ID)) {
return 0;
}
return 1;
}
short get_opt3004_value(void)
{
short ret_value = -1;
int result_value = 0;
short result_data_e = 0, result_data_r = 0;
unsigned char opt3004_cfg_data[2] = {0};
unsigned char opt3004_result_data[2] = {0};
i2c_master_init(OPT3004_I2C_ADDR_R, (unsigned char)(CLOCK_SYS_CLOCK_HZ / (4 * I2C_CLK_SPEED)));
i2c_read_series(OPT3004_CONFIG_REGISTER_ADDR, 1, opt3004_cfg_data, 2);
if (opt3004_cfg_data[1]&0x80) {
i2c_read_series(OPT3004_RESULT_REGISTER_ADDR, 1, opt3004_result_data, 2);
result_value = (opt3004_result_data[0]<<8) + opt3004_result_data[1];
result_data_e = ((result_value & 0xF000) >> 12);
result_data_r = (result_value & 0x0FFF);
ret_value = lsb_size_tab[result_data_e] * result_data_r / 100;
TUYA_APP_LOG_DEBUG("ret_value:%d", ret_value);
}
return ret_value;
}
App 面板使用涂鸦IoT平台提供的公版单开帘面板。
到这里,一款智能窗帘机器人的原型已经开发完成,如果您需要开发更精细化的产品,可以参考以下硬件和软件可优化的部分。
结构上设计留电池可拆卸结构,方便拆卸锂电池充电,同时可以在产品上省去充电管理芯片,可降低成本。
如果不需要准确显示光照强度值,可替换为光敏电阻或光敏二极管。
目前是通过电机堵转时,电机电流与工作时电流不一致判别电机到达终点,可尝试用加速度传感器在堵转和正常运行的数据不一致来判别是否已经到达终点。
碰撞检测使用的是通过采集窗帘电机电压的方式来判断的,可以通过三轴加速度计lis2dw12来判断
光照开关只有下限关闭窗帘,功能还不够完善,可以通过设置上下限的方式自动开启关闭窗帘
辅助拉窗帘会出现误触导致自动开自动关,对该功能还需优化。