我之前在给卧室装电动卷帘时,纠结了很久:成品智能窗帘价格偏高,协议大多封闭,很难接入自己正在用的智能家居系统;自己做又担心电机选型、行程控制、限位保护这些细节踩坑。后来干脆花了两周时间,基于 ESP32 自己从零做了一套智能卷帘,从硬件接线到固件开发,再到接入 Home Assistant 定时联动,整套流程跑通之后,体验和成本都比预想中好很多。
这篇文章把整个过程整理成一份完整教程,内容包括智能卷帘的系统架构、硬件选型、电机驱动原理、ESP32 固件编写、MQTT 协议接入,以及和 Home Assistant 的联动玩法。无论你是准备给家里改造智能窗帘,还是想用一个真实项目入门 ESP32 开发,这篇文章都能直接参考。
1. 智能卷帘的核心概念与整体架构
1.1 智能卷帘到底做了什么
传统卷帘(也叫遮光帘、罗马帘)依靠手动拉拽珠链或拉绳来控制升降。智能卷帘的本质,是把“人手拉拽”这个动作替换成“电机转动”,再把“手动控制”扩展成远程开关、定时开关、光线感应联动,以及语音控制。
一个完整的智能卷帘系统,通常包含下面几个角色:
- 卷帘本体:包含卷管、面料、底杆、安装支架。这部分可以买成品,也可以买面料和卷管自己组装。
- 驱动电机:一般是直流减速电机,负责带动卷管转动,实现升降动作。
- 控制板:我选择 ESP32,它负责接收控制指令、读取限位信号、控制电机正反转。
- 电机驱动模块:因为 ESP32 的 GPIO 输出电流很小,无法直接驱动电机,需要借助驱动板来放大信号。
- 通信模块:ESP32 自带 WiFi 和蓝牙,可以直接和路由器通信,走 MQTT 或 HTTP 协议。
- 传感器与保护机制:包括限位开关、堵转检测、手动按钮、光敏传感器等。
所以,智能卷帘并不是一个有多高门槛的项目。它的难点主要在于机械结构的适配,以及软件层面的行程控制逻辑。
1.2 常见控制方案对比
在动手之前,先想清楚采用哪种通信和协议方案。目前常见的有这么几种:
| 方案 | 通信方式 | 优点 | 缺点 |
|---|---|---|---|
| WiFi + MQTT | 家庭路由器 | 接入方便,可联动智能家居平台 | 依赖路由器稳定 |
| WiFi + HTTP | 家庭路由器 | 实现简单,调试直观 | 轮询或请求响应模式不够实时 |
| 蓝牙 BLE | 手机直接连接 | 功耗低,断网可用 | 控制距离短,难以做集中自动化 |
| 433MHz 射频 | 射频遥控器 | 成本低,穿墙好 | 单向通信,无状态反馈 |
我个人推荐ESP32 + WiFi + MQTT的方案。因为 MQTT 协议天生适合这种设备状态上报和控制指令下发,而且 Home Assistant、OpenHAB 等开源智能家居平台都原生支持 MQTT。就算之后你换平台、加设备,这套架构依然能继续复用。
1.3 为什么是 ESP32 而不是其它开发板
ESP32 在智能家居 DIY 项目里几乎成了首选,原因很直接:
- 自带 WiFi 和蓝牙,不需要额外外挂通信模块。
- 主频高,GPIO 资源充足,能同时处理电机控制、按键、传感器和网络任务。
- Arduino 生态成熟,开发资料多,遇到问题很容易搜到解决方案。
- 板载 Flash,支持掉电存储,可以保存校准数据。
- 价格比较亲民,实验阶段即使烧坏也不心疼。
相比之下,Arduino Uno 需要外接 ESP8266 模块才能联网,而且性能和存储都局促;树莓派又过于重型,不适合直接放在窗帘盒里长期通电。ESP32 的功耗和尺寸控制也很好,适合嵌入墙面或窗帘轨道附近。
2. 硬件选型与环境准备
2.1 硬件清单
我按自己的实际项目列一份清单,你可以根据家里的卷帘尺寸和导轨结构微调:
| 组件 | 规格建议 | 备注 |
|---|---|---|
| 主控板 | ESP32 DevKit V1 或兼容板 | 38 引脚版本即可 |
| 电机 | 12V 直流减速电机,转速 10~30 RPM | 需要根据卷帘高度和直径推算 |
| 电机驱动 | L298N 或 L9110S 电机驱动模块 | 小功率可选用 L9110S,方便接线 |
| 电源 | 12V/2A 直流电源适配器 | 电流要留出 20% 以上余量 |
| 限位开关 | 微动开关或干簧管 | 用于检测卷帘是否到顶/到底 |
| 按键 | 自复位轻触按钮 | 做手动控制与校准入口 |
| 光敏电阻 | 光敏电阻模块 | 做光照联动,可选 |
| 外壳 | 3D 打印盒或塑料接线盒 | 注意散热与走线 |
需要说明的是,不同卷帘的安装空间和受力要求差异很大。如果你的窗帘比较重或者尺寸很大,电机扭矩需要重新计算。本文示例按家用普通遮光卷帘、电机功率在 10W 以内的场景来写,思路可以照搬。
2.2 软件开发环境
固件开发我使用 Arduino IDE,配合 ESP32 开发板支持包。Arduino IDE 对新手更友好,而且社区里的库文件很完善,适合快速跑通项目。
开发环境建议如下:
- 操作系统:Windows 10 / 11 或 macOS 均可。
- 开发工具:Arduino IDE 2.x。
- 开发板支持:在“开发板管理器”中安装 ESP32 支持包。
- 核心库:WiFi.h、PubSubClient.h、Preferences.h、ArduinoJson.h(按需)。
由于每个人的电脑环境和 ESP32 版本不同,这里不写死具体安装命令。你在 Arduino IDE 中打开“文件 -> 首选项 -> 附加开发板管理器网址”,填入 ESP32 官方的 JSON 地址后,即可在开发板管理器中搜索安装。
2.3 示例项目结构
我建议把代码按功能拆分成多个模块,方便后续维护:
smart-roller-shade/ ├── SmartRollerShade.ino // 主程序,负责初始化和主循环 ├── config.h // 网络、引脚、参数配置 ├── motor.h / motor.cpp // 电机控制封装 ├── calibrate.h / calibrate.cpp // 行程校准逻辑 ├── mqtt_manager.h / mqtt_manager.cpp // MQTT 连接与消息处理 └── memory.h / memory.cpp // NVS 存储,保存校准数据当然,Arduino 的 .ino 文件本身可以把多个文件放在同一个目录下一起编译,所以这种拆分方式是可用的。实际编写时,如果项目不大,也可以先写在单一 .ino 文件里;等代码量上来后再拆成多文件。
3. 核心原理拆解:电机驱动与行程保护
3.1 直流电机正反转原理与 PWM 调参
ESP32 控制直流电机,最经典的方案是通过电机驱动模块实现“H 桥”逻辑。以 L298N 为例,控制端子包含 IN1、IN2、ENA。
当 IN1 为高电平、IN2 为低电平时,电机正转;反过来,电机反转。ENA 引脚配合 PWM 信号,可以控制电机的转速。
接线示意:
ESP32 GPIO -> IN1 ESP32 GPIO -> IN2 ESP32 GPIO -> ENA (PWM)在 Arduino 代码中,使用 ESP32 的 LEDC 通道来产生 PWM 信号。
3.2 限位开关与行程校准
卷帘不能一直转到“没方向”,否则会拉坏布料或电机堵转。常见的做法是在卷帘轨道顶部和底部各安装一个限位开关,当卷帘运行到极限位置时,金属触发片碰到微动开关,开关信号由高电平变成低电平,程序立刻停止电机。
还有另一种方案是记录转动时长:按下升键后,电机持续运行 30 秒到顶。这种方式不需要限位开关,但每次断电后需要重新校准,而且电机老化会导致行程变化。最稳妥的是“限位开关 + 运行时长 + 堵转检测”组合。
关于校准,我的思路是:
- 首次上电进入校准模式。
- 手动或自动让卷帘运行到顶部,按下校准按键,记录“全开”位置。
- 再让卷帘运行到底部,按下校准按键,记录“全关”位置。
- 程序计算两个位置之间的运行时间,作为后续百分比控制的基准。
3.3 掉电记忆与手动控制
智能设备最怕断电之后状态丢失。比如卷帘停在 50% 的位置,断电重启后,程序不知道当前位置,如果直接发“打到底”指令,就会因为偏移导致误动作。
ESP32 的 NVS(Non-Volatile Storage)提供了掉电保存能力。我们可以把当前位置百分比、运行方向、运行时间等参数写入 NVS,设备重启后自动恢复状态。在 Arduino 框架中使用Preferences.h库非常简单。
手动控制方面,我会保留两个物理按键,一个上升、一个下降。这样即使手机没电或者 WiFi 断网,家里的人依然可以手动操作。手动按键和遥控指令在代码里应该走同一条控制函数,避免逻辑不一致。
4. 完整实战:ESP32 智能卷帘固件开发
4.1 硬件接线
下面以 ESP32 DevKit + L298N 驱动的接线为例:
| ESP32 引脚 | 驱动板/设备 | 说明 |
|---|---|---|
| GPIO 13 | IN1 | 电机方向控制 |
| GPIO 12 | IN2 | 电机方向控制 |
| GPIO 14 | ENA | PWM 调速输入 |
| GPIO 27 | 上限位开关 | 输入,内部上拉 |
| GPIO 26 | 下限位开关 | 输入,内部上拉 |
| GPIO 25 | 上升键 | 输入,内部上拉 |
| GPIO 33 | 下降键 | 输入,内部上拉 |
| GND | 驱动板 GND | 共地 |
| 5V | 驱动板 5V(可选) | 若驱动板带逻辑电源 |
所有接线都必须在断电状态下完成。L298N 的电机电源使用 12V 供电,但逻辑电源部分建议从 ESP32 板载 5V 引出,或者独立供电,避免电流干扰导致 ESP32 重启。
4.2 基础电机控制代码
先做一个最简单的电机点动控制示例,验证接线和转向是否正确。
// 文件路径:smart-roller-shade/SmartRollerShade.ino #define IN1 13 #define IN2 12 #define ENA 14 void setup() { pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT); pinMode(ENA, OUTPUT); // 使用 LEDC 通道 0,频率 1000Hz,分辨率 8 位 ledcSetup(0, 1000, 8); ledcAttachPin(ENA, 0); } void loop() { // 电机正转 2 秒 digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); ledcWrite(0, 200); delay(2000); // 电机停止 digitalWrite(IN1, LOW); digitalWrite(IN2, LOW); ledcWrite(0, 0); delay(2000); // 电机反转 2 秒 digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH); ledcWrite(0, 200); delay(2000); // 电机停止 digitalWrite(IN1, LOW); digitalWrite(IN2, LOW); ledcWrite(0, 0); delay(2000); }这段代码的作用很直白:上电后电机正转 2 秒,停 2 秒,反转 2 秒,停 2 秒,无限循环。通过这段代码先确认电机转向、驱动板电源、PWM 通道配置都是正确的。
这里有一个容易踩的坑:L298N 的 ENA 如果没有接 PWM,而是直接接高电平,很多板子的默认逻辑是锁住电机,必须把 ENA 接到高电平或者 PWM 信号才能转起来。建议直接用ledcWrite输出一个固定的占空比。
4.3 加入限位开关与状态管理
点动测试通过后,我们需要把代码升级成真正的“卷帘控制逻辑”。核心功能包括:读取限位开关、判断当前状态、执行升降动作、遇到限位时立刻停。
// 文件路径:smart-roller-shade/SmartRollerShade.ino #define IN1 13 #define IN2 12 #define ENA 14 #define LIMIT_UP 27 #define LIMIT_DOWN 26 #define MOTOR_FREQ 1000 #define MOTOR_CHANNEL 0 #define MOTOR_RESOLUTION 8 enum ShadeState { STATE_STOPPED, STATE_MOVING_UP, STATE_MOVING_DOWN }; ShadeState currentState = STATE_STOPPED; void setup() { Serial.begin(115200); pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT); pinMode(ENA, OUTPUT); pinMode(LIMIT_UP, INPUT_PULLUP); pinMode(LIMIT_DOWN, INPUT_PULLUP); ledcSetup(MOTOR_CHANNEL, MOTOR_FREQ, MOTOR_RESOLUTION); ledcAttachPin(ENA, MOTOR_CHANNEL); stopMotor(); } void loop() { // 检测限位开关 bool upTriggered = (digitalRead(LIMIT_UP) == LOW); bool downTriggered = (digitalRead(LIMIT_DOWN) == LOW); if (currentState == STATE_MOVING_UP && upTriggered) { stopMotor(); Serial.println("Reached top limit"); } if (currentState == STATE_MOVING_DOWN && downTriggered) { stopMotor(); Serial.println("Reached bottom limit"); } // 简单演示:串口输入命令,u 上升,d 下降,s 停止 if (Serial.available() > 0) { char cmd = Serial.read(); switch (cmd) { case 'u': moveUp(); break; case 'd': moveDown(); break; case 's': stopMotor(); break; } } delay(20); } void moveUp() { currentState = STATE_MOVING_UP; digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); ledcWrite(MOTOR_CHANNEL, 180); } void moveDown() { currentState = STATE_MOVING_DOWN; digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH); ledcWrite(MOTOR_CHANNEL, 180); } void stopMotor() { currentState = STATE_STOPPED; digitalWrite(IN1, LOW); digitalWrite(IN2, LOW); ledcWrite(MOTOR_CHANNEL, 0); }这段代码加入了两个限位开关的判断:上升途中如果触发了上限位,就立刻停止;下降途中触发下限位也立刻停止。这样能有效防止卷帘拉到顶后继续拉拽导致电机卡死。
注意这里使用了INPUT_PULLUP,也就是把 GPIO 内部上拉电阻打开,限位开关另一端接 GND。当限位开关被触发时,引脚读到低电平。
4.4 行程校准与 NVS 掉电存储
为了让卷帘支持“打开 30%”这种百分比控制,我们需要校准全开行程和全关行程,并把校准结果保存下来。
先定义一个校准数据结构:
struct ShadeCalibration { uint32_t fullOpenTimeMs; // 从下到上运行时间 uint32_t fullCloseTimeMs; // 从上到下运行时间 bool calibrated; };校准流程放在手动模式下执行:
- 按下降键,让卷帘运行到底部。
- 按停止键,然后长按“校准键”,把当前位置记为“全关位置”。
- 按上升键,让卷帘运行到顶部。
- 按停止键,再长按“校准键”,程序根据两次运行时间计算行程。
写入 NVS 的示例:
#include <Preferences.h> Preferences prefs; void saveCalibration(ShadeCalibration& cal) { prefs.begin("shade", false); prefs.putUInt("open_ms", cal.fullOpenTimeMs); prefs.putUInt("close_ms", cal.fullCloseTimeMs); prefs.putBool("calibrated", cal.calibrated); prefs.end(); } bool loadCalibration(ShadeCalibration& cal) { prefs.begin("shade", true); bool exists = prefs.getBool("calibrated", false); if (exists) { cal.fullOpenTimeMs = prefs.getUInt("open_ms", 0); cal.fullCloseTimeMs = prefs.getUInt("close_ms", 0); cal.calibrated = true; } prefs.end(); return exists; }在核心控制函数中执行百分比开合时,把目标百分比换算成运行时间:
void moveToPercent(int percent) { if (percent < 0) percent = 0; if (percent > 100) percent = 100; // 计算当前状态不在代码中简化,这里给出核心换算思路 unsigned long targetRuntimeMs = cal.fullOpenTimeMs * percent / 100; // 然后根据当前位置决定电机方向,并启动定时停止任务 }百分比控制的精度依赖运行时间的稳定。实际项目中,卷帘在运行过程可能存在惯性滑落、机械摩擦变化等误差。一个更稳健的设计是把“预计时间”与“限位开关信号”结合:在运行过程中,如果提前触发限位,就立即停止并更新当前位置;如果按计划时间运行结束还没触发限位,也要停止,并记下偏差。
4.5 接入 MQTT 协议
设备端的控制逻辑写好后,接下来就是把卷帘接入 MQTT,让手机端和智能家居平台都能给它发指令。
假设你的局域网里已经运行了 MQTT Broker,例如 Mosquitto,或者使用 Home Assistant 自带的 MQTT 集成。ESP32 端的代码用PubSubClient库来实现客户端的连接和消息接收。
#include <WiFi.h> #include <PubSubClient.h> const char* ssid = "YOUR_WIFI_SSID"; const char* password = "YOUR_WIFI_PASSWORD"; const char* mqttServer = "192.168.1.100"; const int mqttPort = 1883; const char* topicCommand = "home/shade/command"; const char* topicState = "home/shade/state"; WiFiClient espClient; PubSubClient mqttClient(espClient); void setup() { Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("WiFi connected"); mqttClient.setServer(mqttServer, mqttPort); mqttClient.setCallback(callback); connectMQTT(); } void connectMQTT() { while (!mqttClient.connected()) { Serial.print("Attempting MQTT connection..."); if (mqttClient.connect("ESP32Shade")) { Serial.println("connected"); mqttClient.subscribe(topicCommand); } else { delay(2000); } } } void callback(char* topic, byte* payload, unsigned int length) { String message; for (unsigned int i = 0; i < length; i++) { message += (char)payload[i]; } Serial.printf("Command received: %s\n", message.c_str()); if (message == "OPEN") { moveUp(); } else if (message == "CLOSE") { moveDown(); } else if (message == "STOP") { stopMotor(); } else if (message.startsWith("SET_PERCENT")) { int percent = message.substring(11).toInt(); moveToPercent(percent); } } void loop() { if (!mqttClient.connected()) { connectMQTT(); } mqttClient.loop(); // 状态上报可以在这里按需发布 mqttClient.publish(topicState, "{\"position\": 50, \"state\": \"stopped\"}"); delay(1000); }这段代码的核心逻辑是:订阅home/shade/command主题,收到不同字符串指令后,调用对应的电机控制函数。设备自己运行的状态,比如当前开合百分比,会发布到home/shade/state主题,方便平台端同步显示。
MQTT 指令协议的设计不必追求复杂,字符串指令足够直观,也方便调试。如果以后要扩展更多设备,可以考虑在 payload 里使用 JSON 格式,带上 deviceId、cmd、value 等字段,但接入单台设备时直接使用简单字符串反而更省心。
5. 接入 Home Assistant 与自动化联动
5.1 在 Home Assistant 中配置 MQTT Cover
Home Assistant 中,专门有一个mqttcover 平台,天然适合控制卷帘类设备。在configuration.yaml里新增如下配置:
mqtt: cover: - name: "Living Room Shade" unique_id: living_room_shade command_topic: "home/shade/command" state_topic: "home/shade/state" position_topic: "home/shade/state" position_open: 100 position_closed: 0 payload_open: "OPEN" payload_close: "CLOSE" payload_stop: "STOP" optimistic: false这里的思路是:Home Assistant 把home/shade/command作为指令下发通道,把home/shade/state作为状态订阅通道。点击前端界面的“打开”“关闭”“停止”按钮时,HA 会向 command_topic 发布 payload_open、payload_close、payload_stop,触发设备端对应的 MQTT 回调。
如果你的设备端上报的 JSON 状态里包含"position": 50字段,HA 可以通过json_attributes_topic或position_topic解析百分比,并在界面上显示滑块位置。
5.2 定时与光线联动自动化
接入 HA 之后,自动化就很简单了。下面这个自动化表示“每天日出时打开卷帘”:
automation: - alias: "Open shade at sunrise" trigger: platform: sun event: sunrise action: - service: cover.open_cover target: entity_id: cover.living_room_shade再比如“当室内光照低于某个阈值时自动关闭卷帘”,可以在自动化里使用光照传感器实体。这里给出核心思路:
automation: - alias: "Close shade when too dark" trigger: platform: numeric_state entity_id: sensor.bedroom_illuminance below: 200 action: - service: cover.close_cover target: entity_id: cover.living_room_shade光敏传感器如果直接接在 ESP32 上,也可以把光照读数通过 MQTT 发到 HA,再由 HA 做判断。但建议把简单联动逻辑放在设备端本地执行(比如光线极暗时直接关帘),这样即使 HA 不可用,设备也能完成基本自动化。
5.3 语音控制与 App 控制
Home Assistant 接入后,语音控制可以通过以下方式实现:
- HomeKit Bridge:在 HA 中启用 HomeKit 配置,cover 实体自动暴露给 iPhone 的“家庭”App,可以使用 Siri 控制。
- Alexa / 小度 / 小爱:如果使用的是对应生态,需要 HA 的云组件或桥接插件。
- HA App:直接手机安装 Home Assistant App,内网环境下打开实体页面就能操作。
实现语音控制不需要在 ESP32 端写任何代码,所有上层智能都交给 HA 处理。这也是当初选择 MQTT 协议而不是自制私有 App 的原因:标准协议带来的生态兼容性,远比自己重复造轮子要划算。
6. 常见问题与排查思路
6.1 常见问题速查表
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| ESP32 上电后反复重启 | 电源供电不足或电流毛刺 | 单独给驱动板供电,确认 12V 电源电流余量 |
| 电机不转 | 驱动板 ENA 未接 PWM,或逻辑电源未接 | 检查 ENA 引脚,确认驱动板逻辑电源灯亮 |
| 卷帘到顶后还在转 | 限位开关接错或未触发 | 用万用表或串口打印确认引脚电平变化 |
| 百分比控制不准 | 行程校准数据丢失或电机转速漂移 | 重新校准,结合限位开关做闭环停止 |
| WiFi 经常掉线 | ESP32 天线位置差或电源波动 | 检查天线位置,给电源加滤波电容 |
| MQTT 收不到指令 | Broker 地址、端口或主题错误 | 用 MQTT 客户端工具订阅主题进行验证 |
6.2 电源问题导致的自动重启
ESP32 设备最典型的问题,就是电机启动瞬间电流过大,把供电电压拉低,导致开发板复位。这个问题在 L298N 双路驱动大电流电机时非常常见。
解决思路有三种:
- 独立给电机供电:电机供电和 ESP32 供电完全分开,只共地。
- 使用更大电流的适配器:12V/2A 起步,给电机启动电流留出余量。
- 在电源输入端并联大容量电解电容,比如 470uF 或 1000uF,吸收瞬时电流冲击。
6.3 行程校准不稳定的排查步骤
如果你发现卷帘每次运行后的停止位置都不一样,按照下面的顺序排查:
- 确认限位开关安装位置是否松动,触发片是否每次都能稳定碰到开关。
- 检查电机的减速比,如果电机转速偏高,惯性会让卷帘在断电后继续滑一段距离。
- 重新校准,并且增加“到位后反转 0.2 秒刹车”的逻辑,减少惯性误差。
- 如果采用纯时间控制,检查卷帘的负载是否恒定,负载变化会直接影响转速。
7. 最佳实践与工程建议
7.1 机械结构优先于代码
智能卷帘项目里,软件逻辑写不好可以改,但机械结构如果安装不稳定,后面基本无法正常使用。安装卷帘支架时,一定要确保卷管水平、两端固定牢固,否则卷帘在升降过程中会偏移,导致限位开关触发异常。
另外,电机和卷管之间的连接方式也很重要。最常见的方案是使用联轴器连接电机输出轴和卷管转轴。如果卷管内径较大,可以加装一个配件,把电机输出轴套在卷管接头里,再用螺丝锁紧。这一部分在 DIY 项目里最容易翻车,建议先在不装布料的情况下空转测试,确认传动稳定后再装上帘布。
7.2 固件侧的容错与日志设计
设备写进固件里最好的习惯,就是“所有动作都要有可追踪的日志”。我建议在代码里统一封装一个logState()函数,定期把当前状态、当前位置、限位状态、WiFi 信号强度输出到串口,同时通过 MQTT 发布到home/shade/log主题。后期排错时,翻一下 MQTT 日志记录,就能知道设备当时发生了什么。
电机控制函数也不要一封到底。建议保留一个“手动停止”优先级:任何控制指令在执行过程中,如果收到 STOP 指令或按键按下,都立即停住。这个优先级要写进控制状态机,不能依赖循环的自然结束。
7.3 安全建议
智能卷帘长期通电,而且电机属于运动部件,安全方面必须注意:
- 220V 转 12V 电源适配器要选择有 3C 认证的产品,做好隔离。
- 所有高压和低压接线分开布线,避免线皮磨损导致短路。
- 卷帘运行区域不要有儿童玩耍时容易被缠住的风险,如果有小孩,优先选择无拉绳设计。
- 代码里设置“单次最大运行时间”,比如 60 秒,防止限位开关失效后电机一直运转烧毁。
- 修改生产环境或长期运行的代码前,先在桌面环境验证所有边界条件。
关于“单次最大运行时间”,代码实现很简单:
unsigned long motorStartTime = 0; const unsigned long MAX_RUN_TIME_MS = 60000; void checkMotorTime() { if (currentState != STATE_STOPPED) { if (millis() - motorStartTime > MAX_RUN_TIME_MS) { stopMotor(); Serial.println("Safety timeout triggered"); } } }7.4 从单设备到多设备的扩展
如果你家里不止一个窗户,建议从一开始就考虑多设备扩展。这时候 MQTT 主题的规范性就更重要了,比如使用这种结构:
home/shade/living_room/command home/shade/living_room/state home/shade/bedroom/command home/shade/bedroom/state在 HA 中配置每个实体时,把 command_topic 对应到不同房间即可。设备端代码可以用宏定义当前设备 ID,编译时一次性烧录多块板子,但每个板子的DEVICE_ID不同。
7.5 固件升级预留 OTA 能力
设备已经装到窗帘盒里之后,每次改代码都拆下来插 USB 线,会非常痛苦。建议在固件里预留 OTA(Over-The-Air)升级能力。
Arduino 框架下有ArduinoOTA库,使用起来非常简单:
#include <ArduinoOTA.h> void setupOTA() { ArduinoOTA.begin(); } void loopOTA() { ArduinoOTA.handle(); }把setupOTA()加在setup()末尾,loopOTA()加在主循环里,之后 Arduino IDE 就可以通过 WiFi 端口直接上传程序。需要注意的是,OTA 升级过程中如果断电,设备可能变砖。生产环境中最好配合“双分区运行”机制,但 DIY 场景下只要确保升级时供电稳定,基本足够。
8. 总结与拓展方向
自己做智能卷帘和买成品最大的区别,不只是省钱,而是能把控制逻辑完全掌握在自己手里。整套项目做下来,核心收获可以总结为三条:一是理解了直流电机的驱动与控制原理;二是熟悉了 ESP32 开发、MQTT 协议和 Home Assistant 的集成方式;三是建立了一套从硬件到软件的完整排错思路。
如果你准备照着这个方向继续深入,接下来可以尝试这几个方向:
- 把单台设备的控制逻辑统一抽象成“设备模板”,支持多台卷帘共用一套固件。
- 加入环境光传感器和温湿度传感器,让卷帘根据室内外条件自动调节。
- 研究无刷电机或管状电机,进一步降低噪音、提升扭矩。
- 把控制板做成 PCB 而不是洞洞板跳线,提升长期运行的可靠性。
- 如果对低功耗有要求,可以研究 ESP32 的 Deep Sleep 模式,在不使用时降低功耗。
说到底,智能家居的灵魂不是某一个 App,而是设备之间能够稳定、可靠地协同工作。这套以 ESP32 为核心、MQTT 为通信协议的自制智能卷帘方案,是一套非常适合动手实践的样板工程。建议你从一块开发板、一个小电机开始,先跑通最基础的控制逻辑,再一步步加入更多功能。只要把基础架构搭稳,后续的扩展就会顺畅很多。