如果跟你说,一块手表能连续用一个月不充电,同时还支持换表盘、看天气、连蓝牙,甚至还能当指南针和番茄钟,你第一反应多半是"不可能"。但如果你愿意花点时间跟着开源社区的方案折腾一块墨水屏手表,这件事还真能落地——而且是完全免费开源、用 Arduino 编程就能搞定的那种。
我把这个项目从选型到成品完整走了一遍,整个过程涉及屏幕选型、低功耗设计、表盘定制、网络配网、断电存储等不少坑,也有不少值得记录的技巧。这篇文章就围绕"30天长续航"和"墨水屏"这两个核心点,把整个项目的来龙去脉、硬件搭建、软件架构和踩坑经验一次讲透。无论你只是想复刻一块自己专属的手表,还是想深入了解低功耗物联网设备的编程思路,这篇文章应该都能给你一些启发。
1. 为什么是墨水屏加低功耗主控:这个组合才是续航的根本
先把最核心的问题摆出来:一块手表要做到30天续航,靠大电池是走不通的,手表体积就那么大,塞不下太多毫安时。真正要解决的是"显示器件"和"主控平台"两者的能耗结构。
1.1 墨水屏的功耗模型和手机屏幕完全不一样
普通的 TFT 屏幕或者 OLED 屏幕,只要亮着就在耗电,刷新频率越高耗电越猛。墨水屏完全相反,它只有在"画面改变"的那一瞬间才消耗电流,画面静止的时候屏幕本身是零功耗的,断电之后画面依然保留。
这个特性对穿戴设备来说太关键了。一块手表绝大多数时间界面都是静止的——看时间、看日期、看天气,这些内容一分钟最多变化一次。墨水屏把功耗从"持续不断"变成了"按需刷新",光是这一点就能让续航从几天拉到几周。
我当时选的是某款 1.54 英寸三色墨水屏,黑、白、红三种颜色,分辨率 200 x 200。选它的原因有三:尺寸适合表盘、驱动资料全、三色显示让 UI 有更多设计空间。红色可以用来做高亮提醒,比如闹钟图标、低电量警告,都能做出明显的视觉层次。
1.2 主控选型:性能和功耗之间怎么权衡
墨水屏把显示功耗解决了,另一个耗电大头就是主控芯片和无线模块。这个项目要求同时支持蓝牙和 Wi-Fi,还能跑物联网协议,所以主控的选择直接决定了整体功耗的下限。
市面上的主流方案大致分两类:
| 方案 | 特点 | 适合场景 |
|---|---|---|
| 低功耗蓝牙 MCU(如 nRF 系列) | 只支持 BLE,不支持 Wi-Fi,功耗极低 | 追求极限续航,但功能受限 |
| Wi-Fi + BLE 集成 MCU(如 ESP32 系列) | 支持双模无线,生态成熟,Arduino 支持好 | 功能全面,可玩性高,需要深度睡眠配合 |
| 板级 SiP 模块 | 集成度高,体积小 | 小批量产品化,但成本偏高 |
我最终选了带 Wi-Fi 和 BLE 的低功耗双模 MCU 方案,也就是 ESP32 这一路。虽然它在跑 Wi-Fi 时功耗不算低,但通过 Arduino 框架的esp_sleep深度睡眠机制,可以把待机电流压到微安级别。配合 RTC 定时唤醒,日常使用完全能做到"只在需要时干活"。
提示:ESP32 的电池供电不建议直接接 5V 引脚。最好用锂电池供电,经过稳压到 3.3V 再给主控和屏幕,确保电压纹波小,防止屏幕刷新异常。
1.3 系统架构思路:Arduino 生态帮你省掉大量底层工作
很多做嵌入式的人可能会想,这么复杂的系统是不是得上 RTOS?其实用 Arduino 框架完全够。Arduino 的优势在于生态库——墨水屏驱动、Wi-Fi 配网、JSON 解析、低功耗 API 都有现成轮子,写的代码几乎全是"业务逻辑"层面的,不需要纠结寄存器配置。
项目整体架构是一个"主循环 + 定时唤醒"的事件驱动模型。日常处于深度睡眠,RTC 闹钟每 90 秒唤醒一次,主动拉取传感器数据刷新表盘,如果用户在操作则进入短时活跃状态,空闲后自动睡回。这套逻辑用 Arduino 写起来很直观,维护成本也低,非常适合开源项目的协作形式。
2. 硬件搭建阶段的部件选配与装配避坑清单
软件写得再好,硬件焊接不过关,一切都白搭。这个项目涉及电池、屏幕、传感器等多个模块,装配过程有几个坑特别容易踩,我一个个说。
2.1 完整部件清单:每一件都是"续命"的关键
- 主控:ESP32 开发板(选带电池充电管理的那类更省事)
- 墨水屏:1.54 英寸三色墨水屏模块(200x200)
- 电池:200mAh 锂聚合物电池(扁平封装,适合塞表壳)
- 充电管理:TP4056 充电保护一体板(带锂电池保护)
- 传感器:6 轴 IMU 模块(加速度计 + 磁力计,用于指南针)
- 实时时钟:低功耗 RTC 模块(带闹钟中断功能)
- 按键:2 个微动开关(用于翻页、确认)
- 表壳:3D 打印或手工改造的表壳
你可能注意到我加了 RTC 模块。很多人问:主控本身不是有内部 RTC 吗?但问题是深度睡眠状态下内部 RTC 的计时精度和唤醒能力都不够稳,尤其是走时间长了会漂。外挂低功耗 RTC 相当于"闹钟",把唤醒信号直接接给主控的 EXT1 唤醒引脚,精度和功耗都能兼顾。
2.2 焊接与调试顺序:先点亮屏幕再做其他
我强烈建议遵循这个顺序装配,别图快。先把主控和墨水屏焊接好,刷一个出厂画面测试程序,点亮点亮正常之后,再接传感器和 RTC。原因是墨水屏是整套系统里最脆的部件,排线非常薄,焊接手法不对容易虚焊甚至断线。如果一上来就把所有模块都焊好,出了问题很难定位。
焊接墨水屏的 FPC 排线时,注意使用助焊剂,温度控制在 320°C 以内,焊完后用放大镜检查有没有桥连。我第一块屏幕就是因为没看清桥连,刷新时出现花屏,排查了整整一天。
2.3 电池充电管理:别忽略的低电量关断问题
锂电池在过放之后会被保护板切断输出,这时如果强推开机,会出现反复重启的现象。TP4056 充电管理模块上的输出端最好加一个大一点的电容器,比如 100uF 钽电容,用来吸收电池接入瞬间的浪涌电流,避免主控在开机瞬间因电压跌落而复位。
另外,如果开发板上自带充电电路,要看它是否带升压路径管理。很多时候电池充满 4.2V,但主控和屏幕需要稳定 3.3V,中间必须加 LDO 稳压。不要直接把电池正极接到 3V3 引脚,那样会导致电压波动,屏幕刷新时尤其明显,容易出噪点。
3. 表盘 UI 与自定义机制:墨水屏的显示策略决定观感
墨水屏显示刷新慢,这是它的物理特性,也就决定了 UI 不能像手机一样随便动效。表盘设计必须把"刷新次数"和"刷新区域"这两个指标当成第一原则来考虑。
3.1 分区域刷新:别整屏刷新,全局刷一次要两秒
这块屏幕的全刷(full refresh)时间大约在 2 到 3 秒,如果每一次更新时间都整屏刷,那用户体验就是每隔几十秒闪一次大黑场,眼睛很难受,而且墨水屏全刷次数过多还会加速老化。正确做法是分区域局部刷新(partial refresh)。
我把表盘划分成几个固定区域:
- 左上区:星期和日期(每天刷新一次)
- 右上区:天气图标和温度(每 30 分钟刷新)
- 中央区:时分秒主时间(每秒或每分钟刷新)
- 下沿区:闹钟状态、蓝牙连接图标、指南针小箭头
这样每次唤醒只需要把对应区域的像素推送到屏幕控制器,局部刷新一次大约 300 到 500 毫秒,观感好很多,功耗也小。
3.2 表盘布局参数与数据结构设计
因为是开源项目,自定义表盘就意味着用户能改配置。我设计了一个简单的watchface_config结构体,把表盘的各个要素抽象出来:
typedef struct { bool show_seconds; // 是否显示秒 bool show_date; // 是否显示日期 bool show_weather; // 是否显示天气 uint8_t color_mode; // 黑白 or 三色模式 uint8_t refresh_interval; // 刷新间隔(秒) char city_code[16]; // 天气城市代码 } watchface_config;用户在配置网页里改完设置后,配置会被写进 NVS(非易失存储),下次开机自动加载。这里有个关键细节:配置写入频率不要太高,因为闪存有擦写寿命,正常使用完全够,但如果为了调试反复擦写,寿命损耗会比较明显。
3.3 UI 框架选型:LVGL 还是裸写?
用 LVGL 做表盘很炫,字体渲染、控件布局都方便。但是 LVGL 在 ESP32 上跑起来需要占用不少内存,而且墨水屏驱动和 LVGL 的刷新适配还得额外写。对于这种小尺寸、固定布局的墨水面板,我觉得裸写更划算。
裸写的方式很简单:预置一张 1-bit 位图字体,自己实现draw_char和draw_text函数。虽然字体是点阵的,但配合三色屏,反而做出了那种复古电子表和现代慢设计结合的质感。开源项目的好处是,你可以把它作为"精简内核"来维护,想上 LVGL 的用户自己往上层加。
注意:三色墨水屏的红色像素刷新比黑白像素慢,布局时尽量避免红色元素和黑色元素贴太近,否则刷新时容易串色。我实测红色区域和黑色文字保持至少 4 像素间距,观感最干净。
4. 配网与功能扩展:天气、蓝牙同步、指南针一个都不能少
手表的核心功能不只是看时间,标题里的"实时天气、番茄闹钟、指南针"也要真正落地。这三块分别对应了网络请求、本地状态机控制和传感器数据融合,各有各的门道。
4.1 Wi-Fi 配网:AP 模式还是 BLE 配网
手表没有键盘,输入 Wi-Fi 密码是个难题。我最终采用了"AP 配网"方案:首次开机时,主控开启一个临时热点,手机连上这个热点后打开 192.168.4.1 配置页,输入家中 Wi-Fi 的 SSID 和密码,同时设置天气城市,一并保存。
这个方案在 Arduino 生态里成熟稳定,ESP32 开 SoftAP 的代码量也就几十行,配合WiFiManager库,几乎算是"标配"。
配网成功后,主控自动切换为 STA 模式连接路由器。注意:连接 Wi-Fi 的瞬间电流可能冲到 200mA 以上,如果电池电压偏低,建议在 Wi-Fi 连接前先检测电池电量,低于 3.6V 就跳过这次天气更新,直接显示上次缓存的数据。否则网络请求会让电池电压骤降,导致意外关机。
4.2 实时天气与 JSON 解析:拉数据前先想好异常情况
天气数据来源用的是公开的开放接口,返回 JSON。Arduino 上解析 JSON 可以用ArduinoJson库,内存开销要注意,ESP32 跑起来没问题,但解析时尽量复用同一个 JSON 文档对象,避免频繁分配堆内存造成碎片。
我的处理逻辑是:
- 从 NVS 读取城市代码和上次缓存天气。
- 尝试连接 Wi-Fi,设置 10 秒超时。
- 拉取天气 API,解析温度、天气码。
- 如果成功,刷新天气区域并缓存数据到 NVS。
- 如果失败,继续显示旧缓存,并在下次唤醒后再试。
这里有一个容易忽略的细节:不同天气 API 返回的"天气码"含义差别很大。我在设计数据结构时,让 UI 层只认识"晴、多云、雨、雪、雾"这几种枚举,而把天气码映射放在数据接入层。这样以后换数据源,只需要改映射函数,UI 完全不动。
4.3 蓝牙同步的三种玩法,推荐从"配置同步"入手
手表支持蓝牙以后,可玩性就高了。最简单的场景是和手机 App 同步表盘配置、闹钟列表。BLE 协议栈在 ESP32 上属于成熟方案,Arduino 的BLE库封装得也比较友好。
BLE 的数据结构我用的是"服务 + 特征值"模型:一个自定义服务,里面放三个 characteristic,分别对应配置下发、时间同步、命令回执。手机端写入新的表盘配置后,手表收到数据先做校验,再落盘到 NVS,下次刷新立刻生效。
进阶玩法是推送手机通知——来电提醒、消息提醒都可以做。不过 BLE 通知推送需要手机 App 常驻后台,不同手机系统的策略差异很大,这块调试成本较高,你要有心理准备。建议先把配置同步和基础时间校准做好,通知推送作为后期可插拔模块。
4.4 指南针功能的可靠实现:磁力计校准绕不开
指南针看起来简单,把磁力计数据读出来算个角度就行,但实际做起来,没校准的磁力计误差能偏出几十度。尤其是手表戴在手上,周围有磁铁表扣、马达、金属表壳,都会干扰磁场。
我做了一个三步校准流程:
- 椭圆拟合校准:让用户在空中画"8"字,采集 3 轴磁力数据,用最小二乘法做偏移和缩放修正。
- 固定偏置补偿:因为表壳中可能有固定磁干扰,在校准完成后计算一个固定的偏置量,写进配置。
- 姿态融合:单纯用磁力计算角度,手腕一倾斜数据就乱。需要把加速度计数据也读进来,通过互补滤波稳定姿态。
至于怎么显示,我画了一个表盘下方的小型罗盘条,因为墨水屏不适合做连续动画,所以罗盘用的是"每 15 度一格"的离散刻度,指针方向变化时做局部刷新,避免整块屏幕闪来闪去。
5. 低功耗实现的完整链路:从 5 小时到 30 天的关键修改
如果一个 ESP32 开发板什么都不做跑一个空循环,电流可能在 30 到 50mA 左右,200mAh 电池最多撑 5 小时。做到 30 天是一整套功耗控制的组合拳,每一环都不能掉链子。
5.1 电量消耗拆解:每一天的预算要算清楚
先算一个账。200mAh 电池,30 天,意味着平均每天的可用电量是 6.67mAh。
各项消耗:
- 深度睡眠 + RTC 唤醒待机:约 5uA,24 小时共 0.12mAh
- 每 90 秒唤醒一次做基本时间显示:每次约 2mA 持续 4 秒,一天 960 次,约 2.13mAh
- Wi-Fi 天气获取(每天 8 次,每次 20 秒):约 1.33mAh
- 局部屏幕刷新(每天 300 次,每次 15mA 持续 0.5 秒):约 0.62mAh
- 指南针 / 传感器采样(按需):约 0.2mAh
- 无线电蓝牙广播(仅配网/同步时开启):可忽略
加起来约 4.4mAh,离 6.67mAh 的预算还有余量。所以 30 天这个数字不是吹的,是严格按照功耗模型算出来的,实测中只要把 Wi-Fi 更新频率控制在 30 分钟一次,整个月跑下来是没问题的。
5.2 外设独立供电:不用的设备就该完全断电
墨水屏模块和传感器模块带有自己的电源引脚,把它们分别接到主控的 GPIO 控制引脚上,就能实现"用的时候上电,不用的时候断电"。比如传感器在指南针功能打开时才供电,平时是断电状态,可以省掉不少漏电流。
主控本身的esp_sleep_enable_timer_wakeup和esp_sleep_enable_ext1_wakeup是两把钥匙。前一个实现周期唤醒,后一个实现按键唤醒。我在主循环里加了一个状态判断:如果是周期唤醒就只刷新时间,如果是按键唤醒就进入菜单操作流程,操作结束后再睡回去。
5.3 避开隐藏耗电:GPIO 悬空和串口输出
低功耗设计最坑的一点是 GPIO 悬空。深度睡眠状态下,如果有 GPIO 引脚处于高阻态且没有接上下拉,它会不断在高低电平之间跳变,产生额外的电流消耗。把所有未使用的 GPIO 都设置成输出低电平,或者启用内部下拉电阻,能明显降低睡眠电流。
还有一个很容易忽略的是串口输出。开发阶段大家都在跑Serial.println,但如果这个代码在低功耗模式下没关掉,串口外设本身会持续耗电。正式运行固件时,建议用宏开关把调试输出整个关掉:
#define DEBUG_ENABLE 0 #if DEBUG_ENABLE #define DEBUG_PRINT(...) Serial.print(__VA_ARGS__) #else #define DEBUG_PRINT(...) #endif这样在调试时打开开关,发布时关闭,代码不用改第二遍。
5.4 实时时钟与闪存数据保护:掉电不丢表盘设置
RTC 模块自带电池备份,断电后时间不丢,这是整个系统稳定性的基石。每次进入睡前,把当前时间写入 RTC;每次唤醒后,从 RTC 读时间。即使主控完全没电,只要 RTC 的纽扣电池还在,时间就不会乱。
配置数据存在 NVS 里,我按"配置版本号 + 校验和"的方式管理。用户从网页传配置文件时,先校验、再写入、后读回确认,三重保护避免配置写入一半导致开机后 NVS 数据错乱。如果校验失败,固件会自动恢复默认配置并提示重新配网,这个兜底逻辑很关键,至少不会变砖。
6. 常见问题排查与整体复盘
整套系统做完之后,我有几个实际体会,如果你准备复刻,这些经验能帮你节省大量时间。
6.1 墨水屏刷新异常的快速定位方法
如果是花屏、残影、条纹,先不要怀疑代码。99% 的情况是供电问题、时序问题、或者屏幕排线接触不良。我的定位顺序是:
- 测量屏幕供电电压是否稳定在 3.3V,观察刷新瞬间电压跌落幅度。
- 用示波器抓 SPI 通信时序,看时钟频率是否超过屏幕的规格上限。
- 尝试用示例代码反复刷新 100 次,看故障出现概率。
- 如果局部刷新时颜色串扰严重,把刷新函数改成整屏刷新对比一下。
6.2 重量级项目的精简建议:把不常用的功能做成"模块式扩展"
开源项目最容易犯的毛病是主干越堆越庞大。我的建议是,核心手表功能(时间、表盘、闹钟)保持精简稳定,天气、蓝牙、指南针这些功能做成独立的代码文件,通过编译宏开关来控制是否启用。
这样你拿到一个干净的基线固件,任何时候都可以快速回归测试。想加功能,再把对应模块的宏打开。我自己的仓库就一直保持这个策略,每次社区里有人提交新功能模块,我都强烈建议他们先以"扩展模块"的形式提交,合入主干之前再充分测试。
6.3 最后一点关于续航的真心话
续航这个东西,其实是一个"全系统协同"的结果。屏幕选型、主控休眠策略、外设电源管理、软件刷新逻辑,任何一环漏电都会影响最终数值。我做第一版的时候,光是一个 GPIO 悬空问题就多耗了 10 倍睡眠电流,排查了很久才发现。所以如果你复刻后测出的续航达不到 30 天,不要急着质疑器件,先拿出电流表逐项测一下睡眠电流、唤醒电流和刷新电流,定位到具体的偷电环节再针对性优化。
这条 DIY 路走下来,我最大的收获其实不是手表本身,而是对"低功耗系统设计"有了真正的直觉。以后再看到任何宣称长续航的智能硬件,我脑子里会自动把它拆成"显示、唤醒、通信、交互"四个模块来估算功耗,基本八九不离十。希望这篇分享能给你同样的启发。