这个标题我盯着看了很久,说实话挺感慨的。做智能穿戴这些年,开源方案不少,但大多数作品都在做加法——功能堆得满满当当,续航却一路缩水到以“天”为单位。而“30天长续航”这五个字,恰恰掐中了墨水屏智能手表最核心的命脉:它的价值不在功能,而在“你根本想不起要充电”的那种踏实感。
这个项目本质上是用 Arduino 生态 + 墨水屏 + 一颗低功耗主控,把市面上几百上千块智能手表的核心体验给拆解重构了一遍。它适合谁?适合想深度玩转硬件、又不满足于跑个点灯例程的开发者;适合被手环续航折磨、想自己动手定制体验的极客;也适合想入坑低功耗嵌入式、但不想一上来就啃复杂 RTOS 的初学者。这篇文章我会从整体架构、硬件选型、软件设计、低功耗实现到踩坑实录,把整个项目的关键环节掰开揉碎,给你一份可以直接复现的参考方案。
1. 内容整体设计与思路拆解
1.1 为什么是墨水屏 + Arduino,而不是 Android 或 RTOS
先聊一个被问过很多次的选型问题:既然要做功能这么全的手表,为什么不用现成的智能手表方案,或者干脆跑一个轻量级 Linux?答案在标题里就写明了——免费开源、30天长续航。
墨水屏的底层逻辑是双稳态显示,只在刷新瞬间消耗电能,显示静态内容时完全不耗电。这一特性决定了它天然适合“信息密度高、刷新频率低”的穿戴设备。一块 1.54 英寸或 1.9 英寸的墨水屏,静态功耗几乎为零,整机常显待机电流可以做到微安级别,这是任何 TFT 屏都做不到的。
而 Arduino 生态的价值,在于它把“复杂的事情拆成了简单的小步子”。你不需要从零搭一套嵌入式框架,不需要面对 RTOS 的任务调度和内存管理,只需要像写普通程序一样调库、画界面、读传感器。对于自定义表盘、番茄钟这种偏应用层的需求,Arduino 的抽象程度刚好够用——既能接触到寄存器级别的控制,又不至于陷入晦涩的平台底层。
我见过不少开发者纠结“要不要直接上专用 SoC,比如 Nordic 方案”。坦白说,如果是做量产产品,专用 SoC 肯定是对的,功耗、蓝牙稳定性、封装尺寸都更优。但作为开源项目和玩具级产品,ESP32 系主控提供了 Wi-Fi + 蓝牙双栈、丰富的外设接口、充沛的社区文档,而且价格非常友好。做出来可能不是最优解,但一定是最“把玩友好”的解。
1.2 功能拆解:从“能做什么”到“该做什么”
项目标题里提到了六件事:自定义表盘、蓝牙 Wi-Fi 连接、实时天气、番茄闹钟、指南针。这六个功能指向的是三类用户诉求:
第一类是个性化表达,对应自定义表盘。做穿戴设备久了会发现,用户对表盘的需求是个无底洞,有人喜欢极简数字,有人偏爱情怀表针,有人要看心率曲线。与其内置几百个模板,不如提供一个简单的图形化编辑思路或代码级配置入口,把设计的自由度还给用户。Arduino 方案很适合这种玩法:表盘本质上就是一组绘制函数,改几个坐标、换几张位图,就能生成一个全新的表盘。
第二类是场景效率,对应实时天气、蓝牙通知和番茄闹钟。这部分考验的是低功耗策略与交互逻辑的平衡:Wi-Fi 是功耗杀手,不能一直开着;蓝牙要维持连接但不能频繁唤醒主控;番茄钟需要精准的时间基点,但又不能依赖外部网络时间。设计核心是“什么时候该醒,什么时候该睡”。
第三类是工具属性,对应指南针。这个功能看似简单,实则涉及磁力计的校准、倾斜补偿、与表盘方向的联动。如果不做校准和滤波,静止状态下指针会漂得让你怀疑人生。
1.3 方案选型背后的关键取舍
把功能都清点一遍后,方案设计就会自动收敛到一条明确的路径:一颗支持深度睡眠的主控 + 低功耗显示器 + 按需唤醒的外设管理 + 轻量级无线通信策略。
与我之前做过的一些项目相比,这个项目最大的区别在于:它不是“功能驱动”的,而是“功耗预算驱动”的。每个功能模块在添加前都要问一个问题:它会把平均功耗拉高多少?如果答案是“让 30 天变成 20 天”,那这个功能就要重新设计——比如 Wi-Fi 连接只在获取天气时启用 5 秒,蓝牙只在收到通知时唤醒 MCU。这个底层逻辑我也融入到了后续的硬件选型里。
2. 硬件选型与核心参数解析
2.1 主控选择:ESP32-PICO 还是 ESP32-C3
市面上支持 Arduino 的低功耗主控不少,但结合 Wi-Fi + 蓝牙双连接需求,主流答案就是乐鑫系的 ESP32 或 ESP32-C3。
| 型号 | 内核 | Wi-Fi | 蓝牙 | 深度睡眠电流 | 适用场景 |
|---|---|---|---|---|---|
| ESP32 经典款 | 双核 Xtensa LX6 | Wi-Fi 4 | 经典 BT + BLE 4.2 | 约 10-20 μA | 功能全、可折腾,但尺寸偏大 |
| ESP32-C3 | 单核 RISC-V | Wi-Fi 4 | BLE 5.0 | 约 5-10 μA | 功耗更优、封装小、新手友好 |
| ESP32-S3 | 双核 Xtensa LX7 | Wi-Fi 4 | BLE 5.0 | 约 7-15 μA | AI 加速与更大 RAM,适合复杂 UI |
我实际选的是 ESP32-C3 模组,理由有三个:一是 RISC-V 单核已经足够支撑表盘渲染与传感器读取,没必要双核;二是 BLE 5.0 的有效吞吐和广播扩展对通知推送更友好;三是封装尺寸小,方便排布在圆形或方形表壳里。
但这里要提醒一句:ESP32-C3 的 GPIO 数量有限,如果表盘还打算接按钮、马达、光传感器,引脚规划就要在画板阶段提前做好,否则后期飞线会非常痛苦。
2.2 墨水屏的选择:尺寸、分辨率和驱动波形
墨水屏主流来源是元太(E Ink)屏,市面上买到的模组大多由微晶或大连佳显等厂家整合驱动板。对智能手表而言,1.54 英寸(200x200)是最稳妥的起步尺寸;如果想要更现代一点的比例,可以上 1.9 英寸的圆形屏,但圆形屏的驱动和 UI 适配复杂度会高一个量级,新手建议先从方形屏开始。
关键参数要看三处:
- 驱动 IC:常见的有 SSD1681、UC8253、JD79653 等,Arduino 生态里 U8g2 库覆盖面较广,但部分国产驱动 IC 需要自己写底层时序。建议选 SSD1681 或同类“gxEPD2 库直接支持”的型号,能省掉大半个月的调试时间。
- 刷新方式:墨水屏支持全刷(刷新过程会闪屏)和局刷(只有局部区域变化)。表盘的秒级更新必须用局刷,但局刷次数多了会有残影,需要在代码层面定期全刷一次。
- 温度补偿:墨水屏在低温下刷新会很慢甚至失败,驱动板如果带温度传感器或支持手动温度补偿,体验会好很多。
2.3 指南针与传感器选型
指南针方案我选了 QMC6310 磁力计,它比 HMC5883 便宜,精度和稳定性在消费级里够用。但注意:磁力计对 PCB 布局非常敏感,一定要避开扬声器、马达、大电流走线,否则读数会带着严重的固定偏差。
另外我还加了一颗 LIS3DH 三轴加速度计,用于抬腕亮屏和自动旋转表盘方向。磁力计和加速度计的组合还能实现简单的倾斜补偿,指南针在手臂倾斜时依然能保持方向准确。
2.4 电池与电源管理
30 天续航的物理基础是电池容量与整机平均功耗的比值。我用的是 400mAh 锂聚合物电池,如果整机平均功耗控制在 0.4mA 左右,理论续航就是 1000 小时,也就是 41 天,留出余量后 30 天完全可实现。
电源管理分三路:
- 主路 3.3V:给 MCU、传感器、墨水屏逻辑供电,用低静态功耗 LDO,比如 TPS7A02 或 ME6211;
- 墨水屏驱动电压:部分模组需要额外提供 3.3V 或 5V 的驱动电压,一般由模组自带的 DC-DC 升压搞定,外部只要保证输入稳定;
- 电池电量监测:用电阻分压 + ADC 读取,也可以通过 GPIO 控制分压电阻的通断,避免分压电阻常开漏电。
3. 软件架构与表盘渲染方案
3.1 用“帧”的思路设计表盘系统
表盘的本质是一层静态画面叠加一组动态元素。我在代码里把表盘拆成三层:
- 背景层:包含位图、指针表盘刻度或几何色块,只在切换表盘时全刷;
- 信息层:日期、电量、天气图标、蓝牙状态等低频变化元素,每分钟或每次事件触发时局刷;
- 实时层:秒针、倒计时、Tomato 状态等秒级变化元素,单独占一个局刷区域。
这样做的好处是刷屏次数大幅减少:安静时屏幕 1 分钟才局刷一次,人眼几乎感知不到刷新过程。如果用简单粗暴的全刷每秒刷新一次,功耗、残影、刷新闪烁都会失控。
3.2 自定义表盘的“代码模板”思路
自定义表盘不一定非要做一个可视化编辑器,我采用的是 Arduino 代码模板模式:在watchface.h里定义几个函数指针,用户只要按模板实现drawBackground()、drawTime()、drawBattery()等函数,就能生成自己的表盘。
// watchface.h 简化示例 typedef struct { void (*init)(); void (*drawBackground)(GxEPD2_GFX &display); void (*drawTime)(GxEPD2_GFX &display, time_t now, int hour, int minute, int second); void (*drawStatus)(GxEPD2_GFX &display, int battery, bool btConnected); uint32_t refreshIntervalMs; } WatchFace;例如实现一个极简指针表盘,主循环只需要在秒级定时器触发时,清除实时层,画上新的指针角度即可。所有表盘共用一套消息循环和定时框架,切换只是重新挂载一个结构体实例,这样既保持了代码整洁,又保留了极强的 DIY 自由度。
3.3 墨水屏渲染的几个技巧
用 U8g2 或 GxEPD2 库驱动墨水屏时,有几个关键点:
- 尽量用局部刷新区域而不是全屏刷新。局刷虽然省电,但连续数十次后残影会明显,我的策略是每 30 次局刷后自动触发一次全刷,并在全刷前显示一张纯黑/纯白反转图,既清残影又不那么突兀。
- 字体用内置字体而非加载外部字库。墨水屏分辨率低,自定义字体还需要额外管理字形索引,除非有多语言需求,否则建议直接挑选 U8g2 内置的几套字体(例如
u8g2_font_ncenB14_tr),既省 Flash 又免折腾。 - 位图素材压缩。表盘背景如果是位图,建议用 RLE 压缩后在代码里解码,200x200 黑白位图理论上 5KB 即可搞定,不压缩则要 5KB+,Flash 虽然不小但也没必要浪费。
3.4 数据流与定时框架
我参照常见的 RTOS 思路,但用 Arduino 的millis()事件调度实现了简单任务表,按周期轮询各模块:
// 任务调度伪码 task_t tasks[] = { { 1000, task_updateSecond }, // 每秒更新实时层 { 60000, task_updateMinute }, // 每分钟更新信息层 { 300000, task_updateWeather }, // 每5分钟检查天气(非阻塞) { 10000, task_updateMagnet }, // 每10秒更新指南针 { 200, task_checkButton }, // 按键扫描 };事件循环统一处理按键、蓝牙回调、传感器中断,避免在主循环里做阻塞读取。比如磁力计 I2C 读取如果阻塞太久,就会影响墨水屏刷新的时序,导致刷到一半被中断,出现奇怪的条纹。
4. 低功耗的硬核实现路径
4.1 状态机:跑、停、睡
墨水屏手表想要 30 天续航,核心不是“某一项外设优化到极致”,而是每个模块按状态机管理好自己什么时候耗电。我把整机分成四个状态:
| 状态 | 主控 | 墨水屏 | 蓝牙 | Wi-Fi | 传感器 | 典型场景 |
|---|---|---|---|---|---|---|
| 运行态 | 全速 | 可刷新 | 连接 | 关 | 开启 | 用户正在操作 |
| 待机态 | 浅睡眠 | 保持图像 | 连接 | 关 | 间歇 | 不操作但需显示时间 |
| 深睡态 | 深睡眠 | 保持图像 | 广播唤醒 | 关 | 低功耗唤醒 | 夜间或长期佩戴 |
| 飞行态 | 深睡眠 | 保持图像 | 关闭 | 关 | 关闭 | 省电极致模式 |
在深睡态下,ESP32-C3 主控进入深度睡眠,只保留 RTC 外设,电流能压到 10μA 以下。闹钟就是 RTC 定时唤醒;蓝牙则通过“保持广播但不保持连接”的方式,手机端推送触发时才会唤醒主控。这一步是整个项目功耗设计的灵魂。
4.2 无线连接策略:能不连就不连
很多人做智能手表会把蓝牙一直保持连接,这是续航的头号杀手。我的策略是分层连接:
- 普通状态:蓝牙广播开启但不建立连接,功耗约几十μA;
- 需要推送时:手机 App 通过 BLE 发起连接请求,手表收到广播后短暂唤醒建立连接,接收数据后 5 秒内断开;
- Wi-Fi 只在获取天气时开启:默认用热点名称 + 密码预先配置,天气刷新后立即关闭 Wi-Fi,功耗从动辄上百 mA 掉落回微安级。
具体到天气获取,我用的是免费天气 API(如 Open-Meteo),无需申请 Key,直接 HTTP GET 返回 JSON。对 ESP32-C3 来说,解析 JSON 用 ArduinoJson 库很顺手,但要注意内存——ESP32-C3 的 RAM 不大,缓冲区要按需裁剪,别把整个 JSON 都载入。
4.3 实测功耗数据与优化过程
用真实的电流表实测,我的整机功耗大致如下:
| 模块 | 状态 | 电流 |
|---|---|---|
| 主控 ESP32-C3 | 深度睡眠(RTC 运行) | 约 8μA |
| 墨水屏 | 静态显示 | 0μA |
| 磁力计 QMC6310 | 空闲 | 约 2μA |
| 加速度计 LIS3DH | 低功耗模式 | 约 5μA |
| 蓝牙广播 | 常开广播 | 约 30-60μA |
| 整机平均 | 混合场景 | 约 0.3-0.5mA |
也就是说,400mAh 电池实际能用 33-55 天,与标题里的 30 天相比还留有余量。优化过程中我发现两个容易被忽视的漏电源头:一个是 LDO 静态功耗,另一个是 GPIO 悬空漏电。正确的做法是所有不用的 GPIO 强制设为输出低或输入上拉,并避免对传感器直接供电导致其内部漏电过大。
4.4 睡眠唤醒与外设联动
深睡唤醒后,主控要快速恢复到用户可交互状态。我的启动流程是:先恢复 RTC 时间和日期,再读取加速度计判断是否有抬腕动作,如果是则启动表盘刷新;否则立即回到深睡。整个过程从唤醒到屏幕显示首帧控制在 300ms 以内,体感上没有明显的延迟。
这里建议大家用 ESP32 的esp_sleep_source_t来区分唤醒源:按键唤醒、RTC 闹钟唤醒、GPIO 外设唤醒,分别走不同的恢复逻辑,不仅清爽,还便于排查异常唤醒。
5. 关键外设的详细实现:天气、番茄钟、指南针
5.1 天气数据与 Wi-Fi 协作的完整流程
天气更新是 Wi-Fi 功耗最重的场景,所以必须做三件事:触发条件、数据缓存、显示降级。
触发条件我只保留了三种:开机时、用户主动下拉刷新时、每 30 分钟一次的周期检查(如果上次失败则缩短到 5 分钟)。这样 Wi-Fi 平均每天只打开几十秒,功耗占比可以忽略。
数据缓存方面,我会把天气的 JSON 中关键字段(温度、天气码、风速)解析后存到 RTC 内存或 NVS 中,这样即使深睡重启也不用重新请求。显示降级是指:如果 Wi-Fi 连接失败,表盘上显示“--”而不是阻塞等待,保持用户体验。
// Open-Meteo 请求示例 GET https://api.open-meteo.com/v1/forecast?latitude=39.9&longitude=116.4¤t_weather=true&timezone=Asia/Shanghai解析后的数据结构可以这样定义:
struct WeatherData { int temperature; // 温度(四舍五入) int weatherCode; // 天气码(用于映射图标) int windSpeed; // 风速 km/h int humidity; // 湿度百分比 unsigned long updatedAt; // 更新时间戳 };5.2 番茄闹钟的状态机设计
番茄钟看起来只是倒计时,但放在低功耗手表上就得精心设计。我把状态机拆成五态:
| 状态 | 动作 | 时长 | 提醒方式 |
|---|---|---|---|
| 空闲 | 显示按钮提示 | 不限 | 无 |
| 专注中 | 显示倒计时 | 25 分钟 | 结束时震动/提示 |
| 短休息 | 显示倒计时 | 5 分钟 | 结束时震动/提示 |
| 长休息 | 显示倒计时 | 15 分钟 | 结束后自动回到空闲 |
| 已暂停 | 暂停当前存量 | 不限 | 无 |
关键点在于:倒计时基时用 RTC 的 epoch 时间戳偏移,而不是简单的millis()计数,这样深睡醒来后依然能正确恢复。唤醒由 RTC 闹钟触发,主控深睡,直到倒计时结束那一刻才醒来。所以番茄钟本身几乎不增加额外功耗。
5.3 指南针的校准与显示融合
指南针要做到“能用”而不是“能转”,必须处理三件事:硬磁校准、倾斜补偿、磁偏角修正。
硬磁校准时,让手表在水平面上绕 Z 轴转两圈,采集磁力计 XYZ 的最大最小值,算出偏移量存到 NVS。这个过程虽然手工感强,但比算法自动校准要稳定得多。倾斜补偿则利用加速度计计算俯仰角和翻滚角,对磁力计读数做正交化处理。最后根据所在地区的磁偏角,把航向角修正到正北方向。
显示上,我选择了“表盘中央画罗盘 + 顶部指针箭头”的方式,而不是把航向数字单调打在屏上。这样在户外看方向时,视觉直观性会好很多。但注意磁力计刷新频率不宜过高,10 秒一次足够,既省电又避免数字跳动太频繁。
5.4 表盘、天气、指南针的界面整合
多页界面切换我采用“滑动 + 自动回退”设计:用户短按按键切换表盘页、天气页、指南针页,60 秒无操作自动回到表盘页。页面切换时,用局刷把主内容区清空再绘制,表盘页的背景层可以复用缓存位图,减少双重刷新。
6. 常见问题与排查技巧实录
6.1 “屏幕为什么越刷越花”
这是新手最容易遇到且最容易被误导的问题。墨水屏的局刷本质是通过特定波形把粒子推到某个状态,连续多次局刷后,残留电荷会积累,导致局部位置变灰或出现“鬼影”。如果你发现屏幕上有上一帧的残影,先检查是不是全刷间隔太长;如果全刷后依然有残影,则要考虑是不是温度太低导致刷新波形不完整。
我的解决策略是:局刷 30 次后强制全刷,全刷前显示一帧纯白图。实测残影可以控制在肉眼几乎不可见的程度。
6.2 电池耗电异常
如果实测续航远低于预期,先别急着怀疑主控深度睡眠配置。最优先检查的是 LDO 静态电流和传感器供电策略。很多 LDO 在空载时静态电流就有几十 μA,如果这里没控制好,整机就凭空多了几天的功耗。其次,断掉所有外设供电,只保留 MCU,测基础电流,再逐个加回外设,才能定位异常模块。
6.3 蓝牙连接不稳定
ESP32-C3 的 BLE 在广播模式下,手机端扫描需要一定周期。如果手表端广播间隔设得太长,省电但搜不到;设得太短,功耗上来了。我的经验是广播间隔取 300ms 左右,同时用“快速广播 + 慢速广播”的双模式:开机 30 秒内快速广播方便配对,配对完成后进入慢速广播。
6.4 天气刷新失败
天气刷新失败最常见的是 HTTP 请求超时。ESP32-C3 的 Wi-Fi 驱动占用的堆内存比较大,如果 ArduinoJson 的 JSON 解析缓冲区分配过大,可能导致连接断开或崩溃。建议把接收缓冲区控制在 2KB 以内,同时将 HTTP 超时时间设为 10 秒,避免因为网络慢阻塞主循环。
6.5 指南针漂移
指南针漂移,先检查硬磁校准是否完整,再看有没有把磁力计放在 PCB 的电源层上方,或者靠近电池。锂聚合物电池本身的磁场变化很小,但大电流走线和磁扣表带会产生明显干扰。如果条件允许,给磁力计单独隔一块地,并把 PCB 铺铜的开口方向做严格规划。
6.6 常见问题速查表
| 问题现象 | 排查方向 | 解决建议 |
|---|---|---|
| 屏幕残影严重 | 全刷周期、温度 | 每 30 次局刷全刷一次,检查环境温度 |
| 续航远低于 20 天 | LDO、传感器供电、GPIO 漏电 | 逐个模块电流排查 |
| 蓝牙搜不到 | 广播间隔、MAC 地址 | 双模式广播,间隔 300ms |
| Wi-Fi 连不上 | 天线净空、电源稳定性 | 预留天线净空,加 10μF 退耦电容 |
| 时间走得不准 | RTC 晶振、NTP 同步 | 启用 NTP 更新,但每天最多一次 |
| 指南针反转 | 磁偏角、安装方向 | 校准磁偏角,检查传感器坐标轴方向 |
7. 经验总结与后续扩展思路
做到这个程度,项目已经是一个完整可佩戴的设备了。但从“能玩”到“好用”,中间还差几个值得打磨的点:
第一是震动马达与触觉反馈。番茄钟结束时若没有物理提醒,体验会大打折扣。我选的是扁平震动马达,驱动管用一颗 MOSFET 即可,但要注意马达启动瞬间的电流冲击,需要并联一个大电容并在软件里做软启动。
第二是表带与结构开模。开源项目多数使用 3D 打印外壳,但 3D 打印的耐用性和防水性能都很有限。如果想长期佩戴,建议把外壳改为亚克力或 CNC 铝合金,表耳处预留标准 22mm 表带接口。
第三是手机 App 的完善。目前我用的调试工具是现成 BLE 调试助手,但要做时间同步、表盘推送和天气地名配置,一个简易的配套 App 能大幅提升体验。
因为我个人更习惯在电路和固件里折腾,App 这块目前只做成了配置工具,没有做复杂的消息推送后台。其实想想,如果没有 App 的推送需求,蓝牙连接完全可以只在同步时短暂开启,这能进一步把整机电流压低到 0.2mA 量级,向 60 天续航迈进。
最后再分享一个小技巧:如果你第一次焊 ESP32-C3 模组,建议先不要直接焊到主板上,先用杜邦线接一个最小系统验证固件能跑通,再集成进手表主板。调试过墨水屏驱动、传感器读取这些环节后,再动手画正式 PCB,能省去很多来回打样的时间。这个项目的核心乐趣本来就是“折腾”二字,留点余量,你才有空间把它变成属于你自己的作品。