☰
基于ESP32的墨水屏智能手表:开源低功耗设计与30天续航实践
2026/10/13 5:43:24 网站建设 项目流程

我前前后后折腾了大概两个月,最终做出来一款墨水屏智能手表。主控是 ESP32,屏幕是 1.54 英寸黑白墨水屏,板载 DS3231 高精度 RTC、QMC5883L 磁力计,再加上一块 500mAh 锂电池。在开启蓝牙通知、每天自动更新三四次天气、番茄钟正常使用的前提下,实测满电续航能到 35 天左右。整机固件用 Arduino 平台写,整个项目免费开源,从硬件原理图到表盘代码都可以随意修改。这篇文章不谈空话,把我踩过的坑、算过的功耗账、表盘自定义的实现套路全部写出来,给准备做同类东西的朋友一个参考。

这个项目的价值在于:它把“超长续航”“常用功能”“可二次开发”三个点平衡到了一起。墨水屏保证了静态显示零功耗,ESP32 提供了蓝牙和 Wi-Fi 能力,开源固件则把表盘、天气刷新、闹钟等功能的修改权完全交到了使用者手上。适合想入门低功耗硬件开发的创客,也适合给嵌入式爱好者当练手项目,甚至可以直接作为礼物送人。只要你有点 Arduino 基础、焊得动几根线,就能复刻出来。

1. 项目概述与整体思路拆解

1.1 标题里的五个关键词,其实对应五条产品需求

把这个项目标题拆开看,每一个特性都不是拍脑袋加的,背后都对应一个真实使用场景。

“30 天长续航”是我最先定的硬指标。市面上大多数智能手表一天一充,主要是彩色屏幕和实时操作系统在持续耗电。墨水屏天生适合静态信息展示,因为它刷完一屏后就不需要再供电,画面能保持几个月。再加上 ESP32 的深睡模式,整机大部分时间处于“断电”状态,续航自然拉长。

“可自定义表盘”解决的是个性化需求。固件把表盘抽象成独立的绘制函数,你要换风格,不用改主程序逻辑,只改绘制代码。甚至可以把表盘图片通过取模工具转成字节数组,存进文件系统或直接编进固件,运行时按需加载。我最终做的内置表盘有好几套,指针款、数字款、极简信息款,切换表盘按键按两下就行。

“蓝牙 Wi-Fi 连接”是功能丰富度的保证。Wi-Fi 用来做 NTP 时间同步和天气数据获取,蓝牙则用来接收手机通知。这两个无线模块同时存在,基本覆盖了智能手表的核心通信需求。实际使用中,我大部分时间把 Wi-Fi 关掉,只在设定的时间点打开,这样功耗可控。

“实时天气”和“番茄闹钟”属于实用性功能。天气数据通过 Wi-Fi 定时拉取,展示在表盘上;番茄钟则用 RTC 闹钟实现,时间到了触发中断唤醒主控,震动马达提示。这两个功能合在一起,让我日常出门不用掏手机也能看到关键信息。

“指南针”则是传感器应用的一个典型代表。磁力计读取地磁场方向,经过校准和坐标换算就能显示方位角。这个功能看着简单,但涉及传感器校准、角度补偿、磁偏角修正,对新手来说是一个很好的进阶练习。

1.2 为什么是墨水屏 + ESP32,而不是常见的手表方案

很多人会问:为什么不用常见的彩屏蓝牙手表方案?因为目标完全不同。我的需求里,“续航三十天”优先级最高,在这个前提下,屏幕、主控、通信方案都被限制死了。

普通 TFT 彩屏在刷新过程中需要背光,即使显示静态画面也要持续扫描刷新,待机功耗最少也得几十毫安时每小时。墨水屏完全不同,它属于双稳态显示技术,画面一旦刷出来,撤掉电源画面也不消失。对于手表这种显示内容变化频率低的设备,墨水屏几乎是量身定做。刷新一次屏幕大约消耗几十毫安秒的电量,如果每天只刷新 20 次,折合下来还不到 1mAh,基本可以忽略不计。

主控选 ESP32 有两个理由:一是 Arduino 生态支持极好,开发门槛低,遇到问题搜一下就有方案;二是一颗芯片同时带 Wi-Fi 和蓝牙,不用额外加通信模块,省空间也省电。ESP32 深睡模式下,RTC 定时器可以唤醒芯片,功耗可以压到 10uA 级别,配合外部 RTC 芯片做闹钟唤醒基座,非常合适。

当然,ESP32 的深睡电流并不是所有开发板都能做到。很多市售开发板带着 USB 转串口芯片,这个芯片在板子断电后还在耗电,导致整个系统的待机电流高达几毫安。这个我后面在排查章节会细说,也是新手最容易忽视的坑。

2. 硬件选型与核心部件解析

2.1 主控、屏幕、传感器选型表

整个项目的硬件选型花了我很长时间,核心原则是“功能满足前提下,选资料多、便宜、功耗可控的芯片”。我最终定的配置如下:

部件型号选型理由
主控ESP32 标准版双核、Wi-Fi + BLE、Arduino 生态完善,深睡功耗可到 10uA 级别
墨水屏1.54 英寸 200x200 SSD1680 驱动尺寸接近手表表盘,分辨率够用,支持局部刷新
实时时钟DS3231带温度补偿,年误差几分钟,闹钟中断输出可直接唤醒主控
磁力计QMC5883L三轴地磁传感器,I2C 接口,附带校准算法后可当指南针用
电池500mAh 聚合物锂电池容量和体积平衡,实测满足三十天目标
电源管理XC6206 低压差线性稳压器静态电流极低,约 1uA,比普通 LDO 省电得多
震动马达手机扁平马达番茄钟和闹钟的提醒方式,比蜂鸣器安静,且不影响功耗

选择 SSD1680 驱动芯片的墨水屏,是因为它支持 1.54 英寸 200x200 分辨率,局部刷新速度相对老款提升不少。虽然墨水屏整体刷新速度还是远慢于 TFT,但做手表这种低频刷新场景足够。

DS3231 是整机低功耗方案里最重要的一颗芯片。ESP32 深睡后内部 RTC 虽然还能跟踪时间,但误差偏大,而且一旦主控完全断电时间就停。DS3231 自带电池备份,温度补偿晶振可以让走时误差降到一年几分钟级别,它还有两路闹钟中断输出,可以配置在指定时间拉低引脚电平。我把这个中断脚接到 ESP32 的 RTC GPIO 上,闹钟时间一到就会把主控从深睡中唤醒。

2.2 电源与低功耗设计是 30 天续航的关键

一块 500mAh 电池要撑一个月,日均可用电量也就 16mAh 左右。所以每一路电都要算得很仔细。我先列一个功耗估算表,然后逐项拆解。

整机的耗电主要分四部分:深睡静态、主板工作、墨水屏刷新、无线通信。深睡时整机从电池取的电流包含 LDO 静态电流、RTC 芯片工作电流和表盘上的固定电阻漏电流。XC6206 的静态电流在微安级别,DS3231 工作电流大约 2uA,加上其他漏电,实测整机深睡电流大约 20uA。一天下来就是 0.48mAh,三十天也就 14.4mAh,占总电量的不到 3%。

工作状态主要看主控唤醒后的运行时长。我平时设置每 15 分钟唤醒一次,刷新时间显示,每次从深睡恢复到执行完绘制大约花 3 秒,期间平均电流约 40mA,折合每次 0.033mAh,一天 96 次约 3.2mAh。

墨水屏刷新耗电与画面内容有关,全屏刷新一次大约消耗 30mAs,也就是 0.0083mAh。一天刷新 20 次全屏,耗电小于 0.2mAh。局部刷新一次更省,大约只有全刷的三分之一。

Wi-Fi 是最耗电的部分。ESP32 建立连接并抓取天气,高峰期电流能到 200mA,整个过程持续 8 到 10 秒。我每天设置 4 次天气自动更新,每次按 8 秒、平均 150mA 计算,一天耗电大约 1.33mAh。蓝牙通知则按每天收到 30 条消息估算,每条连接保持约 1 秒,一天约 0.15mAh。

各项加起来,一天的耗电大约在 5mAh 上下。500mAh 电池理论上能跑一百天,实际因为温度、电池自放电、机械结构接触电阻、偶然的屏幕频繁刷新等因素,能实现 35 天左右,已经达到目标。

这里有一个非常关键的设计决定:主控平时绝对不做任何网络连接,所有定时任务都在醒着的几秒内集中处理,干完立刻睡。Wi-Fi 是耗电大户,连接和传输时必须让电流稳定在较高水平,不能频繁断开重连,否则累计功耗会迅速上升。

2.3 外壳与装配中容易被忽略的细节

硬件设计时光盯着电路,外壳就能给你上一课。我第一版用胶水加洞洞板搭的样机,戴在手上走两步,屏幕排线的接触不良导致画面随机花屏。后来换成 3D 打印外壳加固定结构才算稳定。

外壳设计需要注意三点。第一,3D 打印壳内部要留出电池仓和安全间距,锂聚合物电池受挤压容易鼓包,不能和 PCB 贴得过紧。第二,天线位置不能遮挡,ESP32 的 Wi-Fi 天线区域最好朝表冠方向外露,否则金属外壳或表带扣会吸收无线信号,导致连接时间拉长甚至失败。第三,按键位置要方便按压,我是用两根铜针做“侧边按键”,把表壳侧面的按钮柱直接顶到 PCB 的轻触开关上,按钮手感还行。

装配顺序也有讲究:先焊好屏幕排线和电池插头,再把板子装进壳,最后合盖。不要先把外壳装好再去焊线,那样屏排线很容易因为弯折撕裂。电池的 JST 插头在壳内要预留一点余量,很多电池仓空间紧张,插头硬顶着盖板,时间长了会被压断焊点。

3. 软件架构与表盘自定义

3.1 从深睡到唤醒:整体任务调度

软件架构的主线是“事件驱动 + 极短运行时间”。主控大部分时间关闭所有外设,用 DS3231 的闹钟中断做定时唤醒。唤醒后根据标志位判断要执行哪一类任务,执行完再睡。

任务调度状态可以抽象成下面几类:按键唤醒、RTC 闹钟唤醒、定时器唤醒。按键唤醒用于切换表盘、手动刷新天气、启动或停止番茄钟、触发指南针显示。RTC 闹钟唤醒用于每分钟的时间刷新、闹钟到点触发、番茄钟结束触发。定时器唤醒用于每天固定时段更新天气。

这里的关键点在于:每次唤醒后,主控优先检查电池电压和上一次任务的完成情况。如果上一次网络请求失败,下一次唤醒时就不会再次重试,要等到下一个周期再试。这样避免连续失败的请求耗尽电量。天气更新失败对用户来说,只是表盘上天气数据旧几个小时,完全可以接受,不影响手表的核心走时功能。

3.2 表盘定义与渲染流程

自定义表盘其实就是一个绘制函数,入参是当前时间和传感器数据,输出是屏幕像素状态。我写了一个基类,所有表盘继承实现自己的 draw 方法。主循环只负责获取当前时间、读传感器,然后调用当前选中的表盘绘制函数。

渲染流程里要特别注意墨水屏的刷新方式。墨水屏不适合频繁局部刷新,局部刷新多了会积累残影。我的策略是:整点强制全刷一次清除残影,其余时间用局部刷新显示分钟变化。局部刷新区域只更新时间文本,其他区域的像素保持不变,这样既减少了刷新时间,也避免了大面积反复写入带来的功耗上升。

表盘图片的存储有两种方案。最简单的方案是把所有图标转成 C 语言数组,直接编进固件。缺点是你每换一次表盘图片就要重新编译烧录。另一种方案是把图片转成 XBM 格式文件,放进 SPIFFS 文件系统,运行时读取。第二种灵活度高但代码量会多不少,我最后选择了编进固件,给内置的天气图标和功能图标用的就是这个办法。

取模流程是:先在电脑上把图片调整为目标尺寸,用图像取模工具把图片转成 1 位黑白位图,再导出字节数组。一个 32x32 的天气图标,转成字节数组只有 128 字节,非常省空间。整套流程走下来,换表盘的新手也能在半小时内搞明白。

3.3 天气、时间与蓝牙的数据协议

时间同步用 NTP 协议。主控连接 Wi-Fi 后,向 NTP 服务器发送时间请求,解析返回的秒数,换算成当前年月日后写入 DS3231。这个过程只需要几 KB 的代码,Arduino 生态有现成库可以调用。要注意的是时区设置,如果不加上本地时区偏移,表上显示的时间会和你手机相差八小时。

天气更新用的是 HTTP 请求。我选用了一个免费的天气 API 服务商,注册后得到 API Key,按固定 URL 格式请求当前天气数据,返回 JSON 字符串。解析 JSON 用 ArduinoJson 库,要预留足够的内存缓冲。解析完把温度、天气代码、湿度直接存入全局结构体,表盘绘制时直接读取。

蓝牙部分主要是通知接收,用到的是 BLE 标准里的 Notify 特性。ESP32 作为 BLE 外设,手机端作为中心设备连接后,手机 应用可以把通知内容以 UTF-8 字符串的形式推到手表。手表收到后存入缓冲区,并在表盘上滚动显示。BLE 广播会持续耗电,所以我只在进入“同步模式”时才开启广播,平时广播是关闭状态。手表通过长按按键进入同步模式,手机配对完写完数据再退出。

4. 从零到一:固件开发的实操过程

4.1 开发环境配置与库选择

开发环境用的是 Arduino IDE。先安装 ESP32 的开发板支持包,打开开发板管理器搜索 esp32 安装即可。我写代码时用到的库有这几个:

  • GxEPD2:墨水屏驱动库,支持 SSD1680 等多款常用驱动芯片
  • DS3231:RTC 驱动,用于读写时间和配置闹钟
  • QMC5883L 库:读取磁力计三轴数据
  • ArduinoJson:天气 JSON 解析
  • HTTPClient:配合 Wi-Fi 连接做网络请求
  • BLEDevice:BLE 外设模式的基础功能库

库的版本兼容性是一个容易被忽略的坑。ArduinoJson 的 v6 和 v5 接口差别很大,网上很多老教程用的是 v5 的 API,直接抄过来在 v6 下会报编译错误。我建议固定使用 v6,JSON 解析时用动态文档对象,解析前先调用测量函数确认缓冲区够用。

墨水屏库的使用上,建议先跑一遍示例程序,确认识别到屏幕型号。不同厂商的墨水屏模块虽然驱动芯片相同,但面板的时序参数可能有细微差异,初始化参数不对时画面的对比度会很差。

4.2 指南针校准与显示实现

指南针功能看着简单,做起来最容易翻车。磁力计输出的是三轴磁场分量,要得到方位角,需要把水平面的 X、Y 轴磁场分量带入反正切函数。理论公式就是 angle = atan2(Y, X),然后换算成 0 到 360 度的角度值。

但实际读出来的数值存在零偏、增益不一致、硬磁干扰等问题。如果不校准,显示出来的方向可能偏几十度。最简单的校准方法是“椭圆校准”:让手表在水平面上画圈转动,采集一圈数据,记录 X、Y 轴的最大最小值和中心偏置,然后在程序中减去偏置并做归一化缩放。

我再加了一个一阶低通滤波,角度值不会因为手抖而跳来跳去。滤波系数取 0.8,新的角度等于旧角度乘 0.8 加新采样乘 0.2。这个系数可以在现场环境实测调整,阻尼太大转动手表时方向变化太迟钝,阻尼太小则指针晃动严重。

显示指南针时,我画了一个表盘圆环和一根指针。指针用三角函数计算两个端点的坐标,用 GxEPD2 库的画线方法绘制。墨水屏刷新一次指南针界面大约要两秒,所以这个功能不是常开的,按一下按键才显示,几秒后自动回表盘。

4.3 番茄钟与闹钟的状态机

番茄钟的核心是“到点提醒,不多耗电”。我的实现思路是用 DS3231 的闹钟功能做定时中断。设置番茄钟时,计算目标时间并写入 DS3231 的闹钟寄存器,然后打开闹钟中断,主控立刻进入深睡。时间一到,DS3231 拉低中断引脚,ESP32 被唤醒,程序里判断中断源是闹钟后,启动震动提示 2 秒,再更新表盘显示。

这里涉及一个状态机设计:空闲、运行、暂停、结束。空闲时按启动键进入运行,运行中按暂停键中断计时但保存剩余时间,按复位键回到空闲。每一步都对应一次屏幕刷新和一次 RTC 闹钟配置。

番茄钟用 RTC 闹钟而不是 ESP32 内部定时器的原因很简单:DS3231 走得准,并且主控能完全深睡而不影响计时。如果用 ESP32 定时器,系统必须保证主控的 RTC 仍在运行,深睡模式下虽然能做到,但时间基准不如外部 RTC 可靠。

闹钟功能也复用这套机制。我在设置菜单里配置每天固定的闹钟时刻,DS3231 的闹钟模块支持“每天同一时间触发”模式,设置一次就能长期生效。触发后同样是唤醒主控、震动提醒、屏幕显示“闹钟”图标。

4.4 从裸板到样机:组装与测试过程

硬件连接我按功能分成两组:SPI 总线接墨水屏,I2C 总线接 RTC 和磁力计。ESP32 的 SPI 引脚接到墨水屏模块的 SCK、MOSI、DC、CS、RST、BUSY 引脚,其中 DC 和 RST 可以用任意 GPIO 控制,但 SCK 和 MOSI 必须用硬件 SPI 支持的引脚。I2C 方面,DS3231 和 QMC5883L 都挂在同一条 I2C 总线上,两个芯片的地址不同,可以直接并联使用。

我踩过的一个大坑是 ESP32 的某些 GPIO 在启动时有特殊功能,不能随便接传感器。比如 GPIO 0 是启动模式选择引脚,接电容或外部设备可能导致芯片进入下载模式;GPIO 12 是 MTDI 引脚,会影响 Flash 电压。我最终把墨水屏的 CS 接 GPIO 5,DC 接 GPIO 4,RST 接 GPIO 16,BUSY 接 GPIO 17,I2C 用 GPIO 21 和 22,按键用 GPIO 13、25、26、27。这个分配避开了大部分特殊引脚,实测稳定。

焊接调试时,我习惯先用杜邦线连接并在面包板上验证软件逻辑,确认没问题后再焊洞洞板。整个调试过程大概花了两周,真正踩得最惨的坑反而是电池供电后屏幕刷新不正常,一查是没共地。ESP32、墨水屏、电池三者之间地线没有可靠连接,导致电压参考点不一致,屏幕驱动芯片的逻辑电平混乱。

5. 常见问题与排查技巧实录

5.1 墨水屏刷新异常与残影

墨水屏刷新时卡住不动,是最容易遇到的问题。SSD1680 驱动芯片在刷新前会检查 BUSY 引脚的电平状态,如果面板正处于忙状态,此时不要发送新指令。很多时候是自己写的代码没有等待 BUSY 释放就开始下一帧刷新,导致画面花屏。

还有一个要注意的点是电压。墨水屏的驱动电压由模块内部的升压电路生成,升压需要电池或 USB 供电的电压稳定在 3.3V 附近。电池电压低于 3.0V 时,升压电路的带载能力下降,刷新到一半会出现灰度不匀或者整屏白屏。解决办法是让系统在低电量时自动停止天气更新等高耗电操作,只保留时间显示和闹钟功能,把电量省给核心功能。

残影问题的根源是墨水屏的双稳态特性:长时间不刷新,上一帧的画面会残留在面板里。我加了“整点全刷”和“每周强制全刷一次”的策略,同时局部刷新区域尽量固定在小范围内,避免整屏频繁局部刷新导致残影叠加。实测采用这个方法后,用一个月残影也只是很轻微。

5.2 深睡电流居高不下的原因

这是所有低功耗项目都会遇到的经典问题。我第一版硬件做出来,深睡电流高达 2.8mA,完全不是预想中的 20uA。逐一排查后发现问题出在四个地方。

第一,USB 转串口芯片。我用的 ESP32 开发板自带的 USB 转串口芯片即使在主控深睡时也一直通电,耗电约 1mA 以上。解决办法是设计底板时把下载口的电源单独引出,日常运行时断开这块供电。第二,LDO 选择错误。普通 AMS1117 静态电流有 5mA 左右,换 XC6206 之后降到微安级别。第三,I2C 上拉电阻。我最初用了 4.7kΩ 上拉电阻接 3.3V,DS3231 和磁力计的总线上拉电流能到 1.4mA,后来换成 100kΩ 上拉,并把磁力计设计成按需供电,深睡时直接断开它的电源引脚。第四,GPIO 浮空漏电。所有接按键和传感器的 GPIO 都要在深睡前设置成输入上拉或输出低,不能处于高阻态。

排完这四个问题,整机深睡电流从 2.8mA 降到了 22uA,这个数字已经非常理想。调试过程耗时最长的一步反而不是代码,而是用万用表一个一个测支路电流,我最后把万用表串接在电池和电路板之间,用“烧录后拔掉下载线”的方式反复测量才锁定了问题。

5.3 指南针偏移、天气更新失败等故障速查

现象可能原因排查与解决办法
指南针角度和手机误差大磁偏角没补偿;附近有磁铁干扰查询当地磁偏角并在代码中修正;移除表带磁扣,校准后再测
指南针指针抖动滤波不够增加低通滤波系数,降低采样频率
天气偶尔更新失败网络信号差;API 返回格式变化关闭自动重试,等待下个周期再更新;检查 JSON 字段是否匹配新版本
蓝牙收不到通知手机和手表没有配对;广播未开启长按按键进入同步模式,手机端重新配对
时间逐渐偏差DS3231 备用电池没装或没电换新电池;检查 DS3231 的 VBAT 脚电压
表盘切换卡顿全屏刷新需等待切换到低刷新频率的全刷模式,或改用局部切换
电池续航明显缩短可能有 GPIO 浮空或者天线没有外露按 4.1 节排查深睡电流;检查天线区域遮挡

天气更新失败是最需要心态稳的一个问题。免费 API 服务偶尔会有响应超时或者限流,我一开始做了失败重试,结果信号差的时候连续重试能把电池电量耗掉一大截。后来改成“失败则跳过本周期,不重试”,整体体验反而很好,因为手表的核心功能是看时间和提醒,天气数据晚一两个小时更新完全不影响使用。

指南针校准一定要在接近最终使用环境的位置做校准。我在工作台上校完后,戴上手表一测偏了十几度,后来发现是桌角位置有扬声器的磁铁干扰。手表戴在手腕上的姿态和放在桌面上也不一样,水平旋转校准时尽量把手腕姿态和戴表时保持一致,误差会小很多。

最后分享一个我认为最值得的小技巧:在电池到主板的电源线中间串联一个物理拨动开关,日常佩戴时开关打开,调试烧录时开关关闭。这样能保证 USB 连接电脑时不会出现“电池供电 + USB 供电”同时存在的情况,既保护电池,也避免下载线通电时被电池反向充电,整套调试过程会顺畅很多。做完这个项目后,我对低功耗设计的理解提高了一个层级:奢侈的不是功能丰富,而是每一毫安时都花在了正确的地方。

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

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

立即咨询