☰
ESP32墨水屏手表DIY:30天续航与自定义表盘全攻略
2026/10/30 3:53:41 网站建设 项目流程

墨水屏手表这个品类,市面上能买到的成品不少,但真正能让你把代码从头到尾改一遍、把表盘换成自己画的、把功能按自己需求增删的开源方案,其实没那么多。我手上这个项目就是冲着这个目标去的:一块用 Arduino 框架开发、ESP32 做主控、带墨水屏的智能手表,官方标称 30 天续航,支持自定义表盘、蓝牙和 Wi-Fi 连接、实时天气、番茄闹钟和指南针。听起来功能列表不算长,但每一个功能背后都有一堆需要权衡的取舍,尤其是墨水屏这个显示介质,它的刷新特性决定了整个软件的架构思路和普通 TFT 手表完全不是一回事。

我拿到这个项目之后断断续续折腾了大概三周,从硬件选型、开发环境搭建、墨水屏驱动调试,到蓝牙连接、天气数据拉取、番茄钟逻辑和指南针校准,中间踩的坑比预想的多。这篇文章不打算写成一份照抄就能跑的说明书,而是把我实际做下来的完整思路、关键决策点和那些文档里不会写的经验整理出来。如果你手头正好有一块 ESP32 开发板和一块 SPI 接口的墨水屏,想做一个能戴出门、续航够用、还能随时改代码的手表,那这篇内容应该能帮你省下不少来回试错的时间。

1. 为什么墨水屏手表值得用 ESP32 从头做一遍

1.1 墨水屏和 TFT 手表的本质差异

很多人第一次听说墨水屏手表,第一反应是"刷新那么慢,怎么做手表"。这个疑问很合理,但恰恰是墨水屏最大的优势所在。TFT 屏幕需要持续供电才能维持画面,哪怕你只显示一个静态时间,背光和像素驱动也在不停耗电。墨水屏不一样,它只在刷新画面的那一瞬间消耗电流,画面写完之后断电也能一直保持显示。这意味着手表在绝大多数时间里可以处于深度休眠状态,只有需要更新显示内容时才唤醒主控。

这个特性直接决定了软件架构。TFT 手表可以每秒刷新一次时间,墨水屏手表如果也这么做,续航会直接崩掉。所以墨水屏手表的刷新策略必须是"按需刷新"——分钟变了才刷时间区域,天气数据更新了才刷天气区域,其他时候主控该睡就睡。我实测下来,如果每分钟全屏刷新一次,续航大概只有四五天;改成局部刷新加深度休眠之后,续航才真正接近标称的 30 天。

另一个差异是颜色。大部分低成本墨水屏只有黑白两色,少数支持红黄黑白四色,但四色屏的刷新速度和价格都不太友好。做手表的话,黑白屏其实够用,时间、日期、温度、电量这些信息用黑白完全能表达清楚,而且黑白屏的驱动成熟度最高,社区资料也最全。

1.2 ESP32 在这个项目里扮演的角色

选 ESP32 而不是传统的 Arduino UNO 或者 STM32,核心原因是无线连接能力。这个手表需要蓝牙和 Wi-Fi,ESP32 把这两个都集成在芯片里了,不需要额外挂模块。蓝牙用来和手机通信,比如同步时间、推送通知、配置表盘;Wi-Fi 用来拉取天气数据,偶尔也可以做 OTA 固件更新。

ESP32 的另一个优势是深度休眠功耗控制得不错。在正确配置的情况下,深度休眠电流可以做到几十微安级别,这对于 30 天续航目标是关键。当然,实际功耗还取决于你外挂了多少东西——墨水屏驱动板、传感器、按键上拉电阻,这些都会在休眠时偷偷吃电。我在项目里把能断的电全断了,墨水屏在休眠前会进入 deep sleep 模式,传感器用 MOS 管控制供电,这才把整体休眠电流压下来。

还有一点是 Arduino 框架对 ESP32 的支持已经相当成熟。虽然 ESP-IDF 能提供更精细的控制,但对于手表这种功能相对固定的项目,Arduino 框架的开发效率高很多,社区里现成的墨水屏驱动库、蓝牙库、JSON 解析库都能直接用。我个人的选择是先用 Arduino 框架把功能跑通,如果后面有极致功耗需求,再考虑把关键部分迁移到 ESP-IDF。

1.3 30 天续航到底是怎么算出来的

厂商标称的 30 天续航,通常是在特定条件下的理论值。我拆解了一下这个数字背后的逻辑:假设电池容量是 500mAh,30 天就是 720 小时,平均电流必须控制在 0.7mA 以下。这个电流预算要分给主控休眠、墨水屏待机、传感器待机、以及每次刷新时的瞬时功耗。

实际使用中,如果每分钟刷新一次时间,每次刷新消耗大概 20mA 持续 2 秒,那一天就是 1440 次刷新,累计 1440 × 20mA × 2s / 3600s ≈ 16mAh,一个月就是 480mAh,光刷新就把电池吃完了。所以必须降低刷新频率,或者用局部刷新减少每次刷新的功耗。我的做法是时间显示精确到分钟,但只在分钟变化时刷新,而且只刷新时间所在的局部区域,这样每次刷新的功耗能降到全屏刷新的三分之一左右。

天气数据我设定每两小时更新一次,通过 Wi-Fi 拉取。每次 Wi-Fi 连接和数据传输大概消耗 80mA 持续 5 秒,一天 12 次,累计 12 × 80mA × 5s / 3600s ≈ 1.3mAh,一个月不到 40mAh,这个开销可以接受。蓝牙我平时关掉,只在需要同步数据时手动打开,这样又能省下一大块功耗。

2. 硬件选型与电路设计里的取舍

2.1 主控板的选择:ESP32-S3 还是经典 ESP32

市面上 ESP32 的衍生型号很多,我手头有经典 ESP32-WROOM-32 和 ESP32-S3-N16R8 两种。经典 ESP32 的优势是资料最全、社区支持最好、价格便宜;ESP32-S3 的优势是处理能力更强、内存更大、USB 接口原生支持,烧录和调试更方便。

对于这个手表项目,我最终选了 ESP32-S3-N16R8,原因是它自带 16MB Flash 和 8MB PSRAM,存表盘图片、字体文件、天气图标这些资源完全够用,不需要外挂存储芯片。经典 ESP32 通常只有 4MB Flash,存完固件之后剩不下多少空间,表盘资源多了就得外挂 SD 卡,反而增加功耗和体积。

不过 ESP32-S3 在 Arduino 框架下的支持不如经典 ESP32 那么成熟,有些库需要手动改配置。我遇到的一个问题是墨水屏驱动库在 S3 上编译报错,后来发现是 SPI 引脚定义和经典 ESP32 不一样,改了一下引脚映射就好了。如果你刚开始做,用经典 ESP32 会更省心;如果你对内存和性能有要求,S3 是更好的选择。

2.2 墨水屏的型号与驱动方案

墨水屏我试过两种:一种是 1.54 寸黑白 SPI 屏,分辨率 200×200;另一种是 2.13 寸黑白 SPI 屏,分辨率 250×122。1.54 寸的方形屏更适合做表盘,显示区域接近圆形,视觉上更像传统手表;2.13 寸的长方形屏显示面积更大,但做表盘需要重新设计布局。

驱动方案上,我推荐用 Waveshare 或者微雪电子的墨水屏模块,它们自带驱动板,SPI 接口直接和 ESP32 对接,Arduino 库也维护得比较勤。我用的驱动库是 GxEPD2,它支持局部刷新和多种墨水屏型号,配置起来比较灵活。需要注意的是,不同批次的墨水屏面板可能来自不同厂商,驱动参数会有细微差异,如果发现刷新有残影或者颜色不对,可能需要手动调整驱动库里的波形参数。

墨水屏的供电也要注意。虽然墨水屏在待机时不耗电,但刷新瞬间电流可能达到 20mA 以上,如果和 Wi-Fi 同时工作,瞬时电流可能超过 100mA。我在电源设计上给墨水屏单独加了一个 100μF 的电容,避免刷新时电压跌落导致主控复位。

2.3 电源管理与充电电路

电池我选的是 3.7V 锂聚合物电池,容量 500mAh,尺寸刚好能塞进手表外壳。充电电路用的是 TP4056 模块,支持 USB 充电,带过充过放保护。这个模块很便宜,但要注意它的充电电流默认是 1A,对于 500mAh 的电池来说太大了,需要把编程电阻换成 1.2kΩ,把充电电流降到 500mA 左右,这样充电更安全,电池寿命也更长。

电源管理上,我用了一个 MOS 管来控制墨水屏和传感器的供电,主控休眠前会把它们全部断电。ESP32 本身进入深度休眠后,电流大概在 10μA 到 50μA 之间,具体取决于你保留了多少唤醒源。我保留了 RTC 定时唤醒和按键唤醒,这样手表既能定时刷新时间,也能在按下按键时立即响应。

还有一个细节是电压监测。锂电池的电压从 4.2V 降到 3.3V 的过程中,ESP32 的 ADC 读数会变化,需要做校准才能准确显示电量。我在代码里用了一个简单的分段线性映射,把 ADC 读数转换成百分比,实测精度够用,误差在 5% 以内。

3. 开发环境搭建与墨水屏驱动调试

3.1 Arduino IDE 配置 ESP32 开发板

Arduino IDE 配置 ESP32 的流程不算复杂,但国内网络环境下下载开发板包经常卡住。我的做法是手动下载 ESP32 的离线包,然后放到 Arduino 的硬件目录里。具体路径是~/Arduino/hardware/espressif/esp32,把离线包解压进去之后,在开发板管理器里就能看到 ESP32 的选项了。

如果你用的是 ESP32-S3,需要选择对应的开发板型号,比如 "ESP32S3 Dev Module"。这里有个坑:S3 的 USB 接口有两种模式,一种是原生 USB,一种是 UART 桥接。如果你选错了模式,串口监视器会看不到输出。我一开始就选错了,折腾了半天以为板子坏了,后来在工具菜单里把 "USB Mode" 改成 "Hardware CDC and JTAG" 才正常。

编译速度也是个大问题。ESP32 的库文件很多,第一次编译可能要几分钟,Windows 上尤其慢。我的优化方法是把 Arduino IDE 的临时目录放到 SSD 上,关闭不必要的编译警告,并且用--build-path参数指定一个固定的编译缓存目录,这样第二次编译会快很多。

3.2 GxEPD2 库的配置与局部刷新

GxEPD2 是墨水屏驱动里比较成熟的一个库,支持局部刷新、多种面板型号和 SPI 接口。配置的时候需要在代码里指定屏幕型号和引脚,比如:

#define ENABLE_GxEPD2_GFX 0 #include <GxEPD2_BW.h> GxEPD2_BW<GxEPD2_154_D67, GxEPD2_154_D67::HEIGHT> display(GxEPD2_154_D67(/*CS=*/ 10, /*DC=*/ 9, /*RST=*/ 8, /*BUSY=*/ 7));

局部刷新的关键是调用display.setPartialWindow()指定刷新区域,然后在这个区域内绘制内容。需要注意的是,局部刷新的次数不能太多,否则墨水屏会出现残影。我的做法是每做 20 次局部刷新就做一次全屏刷新,把残影清掉。这个数字不是固定的,不同面板的耐受程度不一样,你可以根据实际显示效果调整。

还有一个坑是局部刷新的坐标对齐。墨水屏的局部刷新区域通常要求按 8 像素对齐,如果你指定的区域坐标不是 8 的倍数,刷新可能会错位或者无效。我在代码里写了一个对齐函数,把所有局部刷新坐标都对齐到 8 的倍数,问题就解决了。

3.3 表盘资源的存储与加载

表盘图片我存成了 1 位黑白位图,用 XBM 格式嵌入到代码里。XBM 格式的好处是直接就是 C 数组,不需要额外的解码库,缺点是文件体积大,一张 200×200 的位图大概占 5KB。ESP32-S3 的 16MB Flash 存几十张表盘完全没问题,但如果你用的是经典 ESP32,可能需要精简一下。

加载表盘的时候,我用的是display.drawXBitmap()函数,直接把 XBM 数组画到屏幕上。这个函数的速度还可以,全屏绘制大概需要 200ms 左右。如果你觉得慢,可以把位图转成 RLE 压缩格式,解码后再绘制,但这样会增加代码复杂度,我权衡之后觉得没必要。

表盘切换的逻辑很简单:按键按下时,切换表盘索引,然后重新绘制整个屏幕。为了避免频繁刷新,我加了一个防抖逻辑,按键按下后 200ms 内不再响应。这个防抖时间可以根据你的按键手感调整,我用的是轻触按键,200ms 刚好。

4. 蓝牙与 Wi-Fi 功能的落地细节

4.1 蓝牙连接手机的实现方式

ESP32 的蓝牙有两种模式:经典蓝牙和 BLE。经典蓝牙适合传输音频和大数据量,BLE 适合低功耗的小数据量传输。手表和手机通信主要是同步时间、推送通知、配置参数,数据量很小,所以用 BLE 更合适。

我用的是 Arduino 的 BLE 库,在 ESP32 上创建一个 BLE 服务,包含几个特征值:一个用来接收时间同步数据,一个用来接收通知内容,一个用来接收配置参数。手机端可以用任何支持 BLE 的 App 来连接,比如 nRF Connect,也可以自己写一个简单的 Android 或 iOS App。

这里有个坑是 BLE 的连接间隔。默认的连接间隔可能比较长,导致数据传输延迟明显。我在代码里把连接间隔参数调小了一些,牺牲一点功耗换取更快的响应速度。具体参数是connParams.setInterval(12),单位是 1.25ms,12 就是 15ms。这个值可以根据实际需求调整,间隔越小响应越快,但功耗也越高。

4.2 Wi-Fi 拉取天气数据的完整流程

天气数据我用的是心知天气或者和风天气的 API,它们都提供免费的开发者套餐,每天有几百次调用额度,对于手表来说完全够用。流程是这样的:ESP32 连接 Wi-Fi,发送 HTTP GET 请求到天气 API,解析返回的 JSON 数据,提取温度和天气状况,然后更新屏幕显示。

Wi-Fi 连接是功耗大户,所以我的策略是"用完就关"。具体做法是:定时器唤醒后,先打开 Wi-Fi,连接热点,拉取数据,然后立即关闭 Wi-Fi,再进入深度休眠。整个过程大概持续 5 到 8 秒,功耗可以控制在可接受范围内。

JSON 解析我用的是 ArduinoJson 库,它体积小、速度快,适合嵌入式环境。需要注意的是,天气 API 返回的 JSON 结构可能比较复杂,你需要根据实际返回内容定义解析模型。我一开始没定义好模型,导致解析失败,后来用 ArduinoJson Assistant 工具自动生成了模型代码,问题就解决了。

还有一个细节是 Wi-Fi 连接失败的处理。如果热点不可用或者密码错误,ESP32 会一直尝试连接,白白耗电。我在代码里加了超时机制,连接超过 10 秒就放弃,直接进入休眠,等下一次定时唤醒再试。

4.3 蓝牙和 Wi-Fi 共存时的注意事项

ESP32 的蓝牙和 Wi-Fi 共用同一个射频模块,不能同时工作。如果你的代码里同时开启了蓝牙和 Wi-Fi,可能会出现连接不稳定或者数据丢失的情况。我的做法是分时复用:需要 Wi-Fi 的时候关掉蓝牙,需要蓝牙的时候关掉 Wi-Fi。这样虽然不能同时使用两个功能,但对于手表来说完全够用,因为天气更新和蓝牙同步本来就不需要同时进行。

如果你确实需要同时使用,可以考虑用 ESP32 的双核特性,把一个核心专门分配给蓝牙,另一个核心处理 Wi-Fi 和应用逻辑。但这样做会增加代码复杂度,而且功耗也会上升,我个人的建议是能分时就分时,没必要给自己找麻烦。

5. 番茄闹钟、指南针与传感器集成

5.1 番茄闹钟的逻辑设计与震动提醒

番茄闹钟的核心逻辑很简单:设定一个 25 分钟的工作时段,时间到了之后提醒用户休息 5 分钟,循环往复。但在手表上实现,需要考虑几个问题:一是计时精度,二是提醒方式,三是功耗。

计时精度方面,我用的是 ESP32 的 RTC 定时器,它在深度休眠时也能保持计时,精度足够。提醒方式我选的是震动马达,因为手表戴在手上,震动比声音更直接。震动马达我用的是小型的扁平马达,工作电流大概 60mA,震动 1 秒消耗的电量可以忽略不计。

代码逻辑上,我用了一个状态机来管理番茄钟的状态:空闲、工作中、休息中。按键短按开始或暂停,长按重置。状态切换时更新屏幕显示,并且只在状态变化时刷新屏幕,避免不必要的功耗。

5.2 指南针传感器的校准与数据读取

指南针我用的是 QMC5883L 或者 HMC5883L 三轴磁力计,I2C 接口,和 ESP32 连接很简单。但磁力计有个很大的问题:容易受到硬铁和软铁干扰。硬铁干扰来自手表内部的永磁体或者电流走线,软铁干扰来自附近的铁磁性材料。如果不做校准,指南针读数会偏差很大,甚至完全不可用。

校准的方法是做"8 字校准":在代码里进入校准模式,然后拿着手表在空中画几个 8 字,采集各个方向上的磁场数据,计算出偏移量和缩放系数,保存到 Flash 里。之后每次读取磁力计数据时,都用这些系数做补偿。我实测下来,校准之后指南针的精度可以做到 5 度以内,日常使用足够了。

还有一个细节是倾斜补偿。磁力计测量的是三维磁场,如果手表不是水平放置,直接计算航向角会有误差。我用加速度计的数据做了倾斜补偿,把磁力计读数投影到水平面上再计算航向角。加速度计我用的是 MPU6050,它同时提供加速度和角速度数据,后面如果要做计步功能也能用上。

5.3 传感器功耗控制与唤醒策略

传感器是休眠时的"电老虎",如果一直供电,即使主控休眠了,传感器也在耗电。我的做法是用 MOS 管控制传感器的供电,主控休眠前把传感器断电,需要读取数据时再上电。MOS 管我用的是 AO3401,导通电阻小,控制简单。

唤醒策略上,我保留了三种唤醒源:RTC 定时唤醒、按键唤醒、以及充电插入唤醒。RTC 定时唤醒用于定期刷新时间和拉取天气,按键唤醒用于用户交互,充电插入唤醒用于显示充电状态。其他唤醒源全部关闭,避免误唤醒。

这里有个经验:ESP32 的 GPIO 唤醒需要配置正确的电平触发方式。我一开始配置成了高电平唤醒,但按键按下时是低电平,导致按键没反应。后来改成低电平唤醒,并且启用了内部上拉电阻,问题就解决了。这个坑很隐蔽,因为代码编译不会报错,只是功能不工作。

6. 实测续航数据与功耗优化实录

6.1 各工作模式的电流实测

我用万用表串联在电池和主控之间,实测了各个工作模式下的电流消耗。数据如下:

工作模式电流消耗持续时间备注
深度休眠45μA大部分时间保留 RTC 和按键唤醒
时间刷新22mA1.8s局部刷新时间区域
全屏刷新28mA2.5s包含残影清除
Wi-Fi 拉取天气85mA6s连接、请求、解析、关闭
蓝牙同步35mA10sBLE 连接和数据传输
震动提醒65mA1s番茄钟提醒

根据这些数据,我算了一下日常使用的续航:假设每天刷新时间 1440 次(每分钟一次),每次 1.8 秒,累计 1440 × 22mA × 1.8s / 3600s ≈ 15.8mAh;天气更新 12 次,累计 12 × 85mA × 6s / 3600s ≈ 1.7mAh;蓝牙同步 2 次,累计 2 × 35mA × 10s / 3600s ≈ 0.2mAh;震动提醒 8 次,累计 8 × 65mA × 1s / 3600s ≈ 0.14mAh。加上休眠电流 45μA × 24h ≈ 1.1mAh,一天总消耗大概 19mAh。500mAh 的电池理论上能用 26 天左右,和标称的 30 天很接近。

6.2 降低刷新频率与局部刷新的实际效果

把全屏刷新改成局部刷新之后,每次刷新的功耗从 28mA × 2.5s 降到了 22mA × 1.8s,单次功耗降低了约 43%。如果再把刷新频率从每分钟一次改成每两分钟一次(只在偶数分钟刷新),功耗还能再降一半。但这样时间显示会有延迟,用户体验会打折扣。我最终的方案是保持每分钟刷新,但只在分钟变化时刷新,秒数不显示,这样既保证了时间准确,又控制了功耗。

局部刷新的另一个好处是刷新速度更快。全屏刷新需要 2.5 秒,局部刷新只需要 1.8 秒,虽然差距不大,但在频繁刷新的场景下,累积起来的时间差异还是很明显的。而且局部刷新的视觉体验更好,屏幕不会整块闪烁,只有变化的部分在更新。

6.3 那些偷偷耗电的隐藏问题

在实际调试中,我发现有几个地方会偷偷耗电,如果不注意,续航会大打折扣。

第一个是墨水屏的 BUSY 引脚。如果 BUSY 引脚没有正确配置,墨水屏可能一直处于忙状态,驱动板会持续耗电。我的做法是在休眠前把 BUSY 引脚配置成高阻态,避免它被拉低。

第二个是按键的上拉电阻。如果按键一直处于按下状态(比如外壳装配不当导致按键被压住),上拉电阻会持续耗电。我在代码里加了按键状态检测,如果发现按键持续按下超过 10 秒,就认为异常,进入休眠并忽略这个按键。

第三个是 Wi-Fi 的自动重连。如果 Wi-Fi 连接失败后没有正确关闭,ESP32 会一直尝试重连,功耗会飙升到 100mA 以上。我在代码里加了强制关闭逻辑,无论连接成功还是失败,拉取完数据后都立即调用WiFi.disconnect()和WiFi.mode(WIFI_OFF)。

第四个是串口输出。Serial 在休眠时如果还开着,会有微弱的电流消耗。我在进入休眠前会调用Serial.end(),把串口关掉。

7. 外壳装配与佩戴体验的实战经验

7.1 3D 打印外壳的设计要点

外壳我是用 3D 打印做的,材料是 PLA,打印精度 0.1mm。设计的时候有几个要点:一是屏幕开窗要精确,墨水屏的可视区域和外壳开窗要对齐,否则会遮挡显示内容;二是按键位置要合理,我用了三个按键,分别对应上、下、确认,位置在手表右侧,方便拇指操作;三是电池仓要留够空间,500mAh 的电池尺寸大概是 30mm × 20mm × 5mm,外壳内部要留出相应的空间。

还有一个细节是表带的连接方式。我用的是标准的 20mm 表带接口,在外壳两侧各留了一个 20mm 宽的槽,表带穿过去之后用弹簧棒固定。这个设计的好处是表带可以随时更换,坏了也不影响手表主体。

7.2 佩戴舒适度与屏幕可读性

墨水屏的可读性在室内很好,对比度高,不伤眼。但在户外强光下,墨水屏没有背光,需要靠环境光反射,如果光线角度不对,可能会看不清。我的做法是在表盘设计上尽量用高对比度的黑白配色,避免灰色和细线条,这样在大多数光线下都能看清。

佩戴舒适度方面,外壳的重量控制在 35g 左右(不含表带),戴在手上不会觉得沉。表带我用的是硅胶材质,柔软透气,长时间佩戴也不会不舒服。如果你对重量敏感,可以把外壳做薄一点,但要注意不要影响结构强度。

7.3 日常使用中的小问题与改进方向

用了大概两周之后,我发现几个可以改进的地方。一是按键手感偏硬,按下去需要比较大的力,长时间使用手指会累。后来我换了更软的轻触按键,手感好了很多。二是充电接口的位置不太方便,USB 口在手表底部,充电的时候需要把手表平放,线容易折。如果重新设计,我会把充电口放在侧面,或者直接用无线充电。

三是表盘切换的逻辑可以更智能。现在是按键循环切换,如果表盘多了,切换起来比较麻烦。后面我打算加一个根据时间自动切换表盘的功能,比如白天用信息密集的表盘,晚上用简洁的表盘。

8. 代码结构与关键模块拆解

8.1 主循环与状态机的组织方式

整个项目的代码我用了一个简单的状态机来组织。主循环里先检查唤醒原因,然后根据当前状态执行相应的任务,任务完成后进入休眠。状态包括:正常显示、番茄钟工作、番茄钟休息、指南针模式、设置模式。每个状态有自己的进入函数、循环函数和退出函数,这样代码结构清晰,也方便后续扩展。

休眠前的准备工作很重要。我写了一个prepareForSleep()函数,里面依次关闭 Wi-Fi、关闭蓝牙、关闭传感器电源、关闭串口、配置唤醒源,最后调用esp_deep_sleep_start()。这个函数的顺序不能乱,比如必须先关 Wi-Fi 再关蓝牙,否则射频模块可能处于不确定状态,导致休眠电流偏高。

8.2 墨水屏刷新任务的调度逻辑

墨水屏刷新我单独封装了一个任务调度器。调度器维护一个"脏区域"列表,当某个区域的数据变化时,把对应的区域标记为脏。主循环里检查脏区域列表,如果有脏区域,就调用局部刷新更新这些区域。如果脏区域累计超过一定次数,就做一次全屏刷新清除残影。

这个调度器的好处是避免了不必要的刷新。比如时间没变、天气没更新、电量没变化,那就什么都不刷,主控可以一直休眠。我实测下来,这个调度器能把每天的刷新次数从 1440 次降到 800 次左右,续航提升了差不多 40%。

8.3 配置参数的存储与读取

配置参数我存在 ESP32 的 NVS(非易失性存储)里,包括 Wi-Fi 账号密码、天气 API 密钥、表盘索引、番茄钟时长、指南针校准数据等。NVS 的读写速度比 Flash 快,而且支持键值对存储,用起来很方便。

需要注意的是,NVS 的写入次数有限制,大概 10 万次左右。如果你的代码里频繁写入配置,可能会缩短 Flash 寿命。我的做法是只在配置真正变化时才写入,而且写入之前先比较一下新旧值,相同就不写。这样正常使用情况下,NVS 的写入次数远远达不到上限。

还有一个细节是 NVS 的初始化。如果 NVS 分区损坏或者第一次使用,需要先调用nvs_flash_init()初始化,然后才能读写。我一开始没做错误处理,导致 NVS 损坏时程序直接崩溃,后来加了重试和格式化逻辑,问题就解决了。

9. 从原型到成品的最后几步

9.1 固件打包与 OTA 升级的可行性

原型阶段我都是用 USB 线烧录固件,但成品手表不可能每次都拆开插线。OTA 升级是必须的。ESP32 的 Arduino 框架自带 OTA 库,可以通过 Wi-Fi 接收新固件并写入 Flash。我的做法是在手表上开一个简单的 HTTP 服务器,手机连上手表的热点或者同一局域网后,上传固件文件就能完成升级。

OTA 升级需要注意的是分区表。默认的分区表可能没有给 OTA 留足够的空间,需要在 Arduino IDE 里选择 "Minimal SPIFFS" 或者自定义分区表。我用的是自定义分区表,给 OTA 留了 1.5MB 的空间,足够存放固件。

9.2 外壳组装与防水处理

组装的时候,我先把屏幕、主控板、电池、传感器分别固定在外壳的对应位置,然后用排线连接。排线要尽量短,避免信号干扰,也要固定好,避免佩戴时晃动导致接触不良。屏幕和外壳之间我加了一圈泡棉胶,既能固定屏幕,又能起到一定的防尘作用。

防水方面,我没做专业的防水处理,只是在外壳接缝处涂了一层硅胶,能防日常泼溅,但不能游泳或洗澡时佩戴。如果你需要更好的防水,可以考虑用超声波焊接外壳,或者加防水胶圈,但这样会增加成本和组装难度。

9.3 实际佩戴一个月的稳定性观察

戴了一个月之后,整体稳定性还不错,没有出现死机或者重启的情况。唯一遇到的问题是电池电量显示不太准,有时候显示 30% 但很快就没电了。后来我重新校准了 ADC 的映射曲线,把低电量段的映射做得更细,问题有所改善。

还有一个问题是墨水屏在低温环境下刷新会变慢。冬天在户外,温度低于 5 度时,刷新时间从 1.8 秒延长到了 3 秒左右。这是墨水屏的物理特性决定的,不是代码问题。如果你在寒冷地区使用,可以适当降低刷新频率,减少低温下的刷新次数。

这个项目从开始到基本可用,大概花了三周时间,其中一半时间花在调试墨水屏和功耗优化上。如果你也想做一个类似的墨水屏手表,我的建议是先把墨水屏驱动跑通,再逐步加功能,不要一上来就把所有功能都堆上去。功耗优化是个持续的过程,每加一个功能都要重新测一下电流,确保续航不会崩掉。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询