
1. 项目缘起与整体设计思路1.1 为什么要做一块墨水屏手表智能手表这个品类已经很成熟了但市面上大多数产品都有一个共同的痛点续航焦虑。一天一充甚至一天两充的使用体验让很多人最终把手表扔进了抽屉。我自己就是典型例子前前后后买过三四块智能手表没有一块能坚持超过半年还在戴的核心原因就是充电太频繁。墨水屏EPDElectrophoretic Display这个技术路线恰好能解决这个问题。它的物理特性决定了只在刷新画面时才耗电静态显示时功耗几乎为零。这就意味着如果设计得当一块墨水屏手表做到30天甚至更长的续航是完全可行的。再加上现在ESP32系列芯片价格便宜、生态成熟、自带Wi-Fi和蓝牙用Arduino框架开发门槛也不高自己动手做一块开源墨水屏手表就成了一件很有性价比的事情。这个项目的核心目标很明确做一块免费开源的、用Arduino编程的墨水屏智能手表续航做到30天级别支持自定义表盘、蓝牙和Wi-Fi连接、实时天气显示、番茄闹钟、指南针等实用功能。适合有基本Arduino开发经验的创客、嵌入式爱好者以及想学ESP32实战的开发者参考。1.2 硬件选型的底层逻辑整个项目最关键的决策就是硬件选型这直接决定了续航、功能和开发难度。我前后试过好几套方案最终确定的配置如下模块选型选型理由主控ESP32-S3 MiniN16R816MB Flash 8MB PSRAM双核240MHz自带Wi-Fi和BLE 5.0体积小显示屏1.54寸或2.13寸墨水屏SPI接口功耗极低阳光下可读刷新后断电保持画面电源管理TP4056充电 低功耗LDO充电简单可靠LDO静态电流低电池3.7V 1000mAh锂聚合物电池容量与体积的平衡点传感器MPU6050加速度陀螺仪指南针和抬腕亮屏都需要时钟PCF8563或DS3231 RTC深度睡眠时维持时间功耗微安级按键3个轻触按键功能切换、刷新、复位选ESP32-S3而不是普通ESP32主要考虑是S3的深度睡眠功耗更低而且支持更多的GPIO和PSRAM跑LVGL之类的图形库更从容。N16R8这个版本16MB Flash可以放不少字库和图片资源8MB PSRAM在刷新大尺寸墨水屏时做帧缓冲非常有用。墨水屏我建议新手从1.54寸开始分辨率200x200SPI接口驱动芯片常见的是SSD1681或DEPG0154。2.13寸的250x122分辨率更高但刷新时间更长对续航有一定影响。如果你追求极致续航1.54寸是更好的选择。注意墨水屏分局部刷新和全局刷新两种模式。局部刷新快但有残影全局刷新慢但画面干净。手表场景下建议表盘用局部刷新整点或切换页面时做一次全局刷新清残影。1.3 软件架构的分层设计软件层面我采用的是分层架构从上到下大致分为四层应用层表盘渲染、天气获取、番茄钟逻辑、指南针计算、菜单交互服务层Wi-Fi管理、蓝牙HID、时间同步NTP、电源管理驱动层墨水屏驱动、RTC驱动、MPU6050驱动、按键驱动硬件抽象层Arduino ESP32核心库、SPI/I2C总线封装这样分层的好处是每一层职责清晰换屏幕或者换传感器时只需要改驱动层应用层代码基本不用动。比如你一开始用1.54寸屏后来想换成2.13寸只要驱动层适配好分辨率上层渲染逻辑几乎不用改。开发环境我用的是Arduino IDE 2.x配合ESP32开发板包。这里有个坑要提醒ESP32的板包下载速度在国内经常很慢建议提前下载好离线包。安装方法是在Arduino IDE的“开发板管理器”里添加ESP32的板包地址然后搜索“esp32”安装。如果网络不好可以手动下载离线包放到Arduino的hardware目录下。版本建议用2.0.11或更新的稳定版太老的版本对S3支持不好。2. 核心功能模块的细节拆解2.1 墨水屏驱动与表盘渲染墨水屏的驱动是整个项目里最需要耐心调试的部分。以常见的SSD1681为例它通过SPI接口通信需要控制几个关键引脚CS片选、DC数据/命令、RST复位、BUSY忙检测。初始化流程大致是硬件复位→软复位→设置驱动输出→设置数据入口模式→设置显示更新控制→设置RAM内容→上电→刷新。表盘渲染的核心思路是维护一个帧缓冲区framebuffer在内存里把要显示的内容画好然后一次性推送到屏幕。1.54寸200x200分辨率1位色深一帧需要200*200/8 5000字节ESP32-S3的PSRAM完全放得下。渲染时用Adafruit GFX库或者自己写简单的绘图函数都可以。自定义表盘的关键在于把表盘元素抽象成可配置的结构。我定义了一个表盘描述结构包含背景图、时间位置、日期位置、电量图标位置、天气图标位置等。用户想换表盘时只需要提供一张背景图和一份坐标配置不用改代码。背景图可以用Image2Lcd之类的工具转成C数组或者存到SPIFFS/LittleFS文件系统里按需读取。// 表盘配置结构示例 struct WatchFace { const uint8_t* background; int16_t timeX, timeY; int16_t dateX, dateY; int16_t batteryX, batteryY; int16_t weatherX, weatherY; uint8_t timeFontSize; };局部刷新的实现要利用墨水屏的“局部刷新”模式只更新变化的区域。比如秒针区域、电量区域。但要注意局部刷新次数多了会有残影建议每刷新20-30次局部后做一次全局刷新。这个阈值可以根据实际观感调整。实操心得墨水屏的BUSY引脚一定要接而且要用中断或者轮询方式等待它变低。我一开始偷懒没接BUSY用固定延时结果不同批次的屏幕刷新时间不一样经常出现花屏。接了BUSY之后稳定多了。2.2 蓝牙与Wi-Fi连接实战ESP32-S3支持Wi-Fi和蓝牙双模但两者同时工作时会有资源竞争功耗也会上升。我的策略是分时复用需要同步天气和时间时开Wi-Fi同步完立刻关闭蓝牙则用于和手机App通信按需开启。Wi-Fi部分主要是连接路由器获取天气数据。我用的是心知天气或者和风天气的免费APIHTTP GET请求返回JSON然后用ArduinoJson库解析。关键代码如下#include WiFi.h #include HTTPClient.h #include ArduinoJson.h void fetchWeather() { WiFi.begin(ssid, password); while (WiFi.status() ! WL_CONNECTED) delay(100); HTTPClient http; http.begin(https://api.seniverse.com/v3/weather/now.json?keyYOUR_KEYlocationbeijinglanguagezh-Hansunitc); int code http.GET(); if (code 200) { String payload http.getString(); DynamicJsonDocument doc(1024); deserializeJson(doc, payload); // 解析温度、天气现象等 } http.end(); WiFi.disconnect(true); WiFi.mode(WIFI_OFF); }蓝牙部分我建议用BLE而不是经典蓝牙因为BLE功耗低得多。ESP32-S3支持BLE 5.0可以做BLE HID设备把手表模拟成蓝牙键盘或者媒体控制器。比如双击按键切歌、长按呼出语音助手。BLE HID的实现需要用到ESP32的BLE库定义HID描述符然后通过BLE连接发送按键报告。如果你只是想用手机App控制手表那用BLE串口Nordic UART Service更简单。手机端用nRF Connect或者自己写App发送指令给手表手表解析后执行相应操作。注意Wi-Fi和BLE同时开启时ESP32的射频部分会分时切换可能导致连接不稳定。建议在代码里做好状态机管理同一时间只开一个射频功能。2.3 实时天气与时间同步天气显示是智能手表的高频功能。我的做法是每2小时同步一次天气同步时开Wi-Fi获取数据后关闭。天气图标我预先转成位图数组存在Flash里根据API返回的天气代码映射到对应图标。时间同步用NTP协议连接Wi-Fi后从pool.ntp.org获取UTC时间然后根据时区偏移转换成当地时间写入RTC芯片。RTC芯片在深度睡眠期间继续走时唤醒后从RTC读取时间不需要每次都联网。#include time.h void syncTime() { configTime(8 * 3600, 0, pool.ntp.org, time.nist.gov); struct tm timeinfo; if (getLocalTime(timeinfo)) { // 写入RTC rtc.setTime(timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); rtc.setDate(timeinfo.tm_mday, timeinfo.tm_mon 1, timeinfo.tm_year 1900); } }这里有个细节NTP同步需要Wi-Fi连接稳定如果信号弱可能超时。建议设置重试机制最多重试3次每次间隔2秒。如果都失败就跳过本次同步用RTC的旧时间继续走。2.4 番茄闹钟与指南针实现番茄闹钟的逻辑很简单25分钟工作5分钟休息循环4次后长休息15分钟。用ESP32的定时器或者RTC闹钟实现。我倾向于用RTC闹钟因为深度睡眠时定时器会停而RTC闹钟可以把芯片从深度睡眠中唤醒。// 设置RTC闹钟25分钟后唤醒 rtc.setAlarm(25 * 60); esp_deep_sleep_start();唤醒后蜂鸣器响或者振动马达振动提示时间到。蜂鸣器用PWM驱动可以调音调。指南针用MPU6050的磁力计数据如果你用的是MPU9250或者加了HMC5883L计算航向角。MPU6050本身没有磁力计只能算俯仰和横滚要做指南针需要额外的磁力计模块。我建议直接用MPU9250或者QMC5883L省事。航向角计算公式float heading atan2(my, mx) * 180 / PI; if (heading 0) heading 360;然后做倾斜补偿和磁偏角校正。实际使用中磁力计容易受手表内部电池和电机的干扰需要做硬铁和软铁校准。校准方法是在水平面上旋转手表几圈记录最大最小值计算偏移量。实操心得指南针功能在手表上其实用得不多但做出来很有成就感。校准的时候远离电脑、手机等磁场源否则数据会飘得厉害。3. 完整实操流程与关键环节3.1 硬件焊接与组装步骤硬件组装我按以下顺序进行每一步都验证后再进行下一步电源部分先焊TP4056充电模块、电池保护板、LDO稳压。焊好后用万用表测输出电压确保3.3V稳定。主控最小系统ESP32-S3 Mini焊到转接板或者直接焊到洞洞板上测试能否正常烧录程序。墨水屏连接按SPI接线CS、DC、RST、BUSY、CLK、MOSI。注意墨水屏是3.3V逻辑不要接5V。RTC和传感器I2C总线连接SCL和SDA加上拉电阻4.7kΩ。按键和蜂鸣器按键一端接GPIO另一端接地启用内部上拉。蜂鸣器用三极管驱动避免GPIO电流不够。整体组装3D打印外壳把屏幕、主板、电池分层放置注意电池不要被挤压。焊接时用细焊锡丝和尖头烙铁温度320度左右。墨水屏的FPC排线很脆弱焊接时间不要超过3秒。3.2 开发环境搭建与烧录Arduino IDE的ESP32环境搭建步骤如下下载Arduino IDE 2.3.x安装。打开“文件→首选项”在“附加开发板管理器网址”里填入ESP32的板包地址。打开“工具→开发板→开发板管理器”搜索“esp32”安装最新稳定版。安装完成后在开发板列表里选择“ESP32S3 Dev Module”。配置参数Flash Size 16MBPSRAM OPI PSRAMPartition Scheme 16M Flash (3MB APP/9.9MB FATFS)Upload Speed 921600。烧录时如果遇到“等待上电同步”一直卡住检查以下几点USB线是否支持数据传输有些线只能充电、驱动是否安装CH340或CP2102、烧录模式是否正确有些板子需要按住BOOT再按RESET进入下载模式。# 如果用esptool命令行烧录 esptool.py --chip esp32s3 --port /dev/ttyUSB0 --baud 921600 write_flash 0x0 firmware.bin3.3 电源管理与续航优化续航是墨水屏手表的核心卖点要做到30天必须把平均电流控制在1mA以下。1000mAh电池30天720小时平均电流要小于1.39mA。各模块功耗估算模块工作电流工作占比平均电流ESP32-S3深度睡眠10μA95%9.5μAESP32-S3活跃80mA5%4mA墨水屏刷新15mA1%0.15mARTC1μA100%1μALDO静态5μA100%5μA合计约4.2mA4.2mA对应1000mAh电池只能撑238小时约10天。要延长到30天需要进一步优化减少ESP32活跃时间表盘刷新从每分钟一次改成每5分钟一次只在按键时立即刷新。降低活跃电流关闭不用的外设Wi-Fi和BLE用完即关。选用更低静态电流的LDO比如HT7333静态4μA。墨水屏刷新频率降低局部刷新代替全局刷新。优化后平均电流可以降到1.2mA左右1000mAh电池能撑约34天。实际使用中因为电池老化、温度影响30天是比较现实的预期。注意深度睡眠时GPIO状态要设置好悬空的引脚可能漏电。把不用的引脚设为输入下拉或者输出低电平。3.4 表盘自定义与资源管理表盘资源我建议存在LittleFS文件系统里而不是编译进固件。这样用户可以通过Wi-Fi或者蓝牙上传新表盘不用重新烧录固件。LittleFS的使用方法#include LittleFS.h void setup() { if (!LittleFS.begin(true)) { Serial.println(LittleFS mount failed); return; } // 读取表盘文件 File f LittleFS.open(/face1.bin, r); if (f) { f.read(framebuffer, f.size()); f.close(); } }表盘图片格式我用的是1位BMP转成的原始二进制200x200分辨率正好5000字节。转换工具可以用Python的Pillow库from PIL import Image img Image.open(face.png).convert(1) img img.resize((200, 200)) with open(face1.bin, wb) as f: f.write(img.tobytes())上传表盘可以通过BLE文件传输或者Wi-Fi HTTP服务器。ESP32可以起一个简单的Web服务器手机浏览器打开上传页面选择文件上传到LittleFS。4. 常见问题与排查技巧实录4.1 墨水屏刷新异常排查墨水屏最常见的问题就是花屏、残影、刷新不全。我整理了一个排查表现象可能原因解决方法完全白屏接线错误、初始化失败检查SPI接线确认RST时序花屏BUSY未等待、SPI速率过高接BUSY引脚降低SPI频率到10MHz残影严重局部刷新过多增加全局刷新频率刷新一半卡住电源供电不足加大电容检查LDO电流能力显示偏移分辨率配置错误核对屏幕型号和分辨率我遇到最坑的一次是屏幕偶尔花屏查了半天发现是SPI线太长导致信号完整性不好。把线缩短到5cm以内就稳定了。所以PCB设计或者连线时SPI线尽量短必要时加串联电阻。4.2 蓝牙连接不稳定处理蓝牙连接不上或者频繁断开通常有这几个原因天线问题ESP32-S3的板载天线如果被电池或者金属外壳遮挡信号会很差。外壳设计时天线区域要留空。电源噪声蓝牙对电源噪声敏感LDO输出要加滤波电容。协议栈冲突Wi-Fi和BLE同时开容易冲突分时使用。配对信息残留手机端删除配对记录手表端清除绑定信息后重试。如果手机搜不到手表先确认BLE广播是否正常开启。可以用nRF Connect扫描看能不能发现设备。如果广播正常但连不上检查服务UUID和特征值配置是否正确。4.3 续航不达标的优化思路续航不达标是最常见的问题。我的排查顺序是测实际电流用万用表或者电流表串在电池回路测深度睡眠电流。如果大于100μA说明有漏电。查GPIO状态悬空引脚、上拉电阻、外设供电都要检查。查LDO静态电流有些LDO静态电流几mA直接拖垮续航。查RTC闹钟频率如果每分钟唤醒一次累积功耗很可观。查Wi-Fi/BLE开启时间同步天气时间尽量短用完立刻关。我实测发现把表盘刷新从每分钟改成每5分钟续航从12天提升到了28天。所以刷新频率对续航影响巨大。4.4 编译与烧录常见错误Arduino IDE编译ESP32项目时经常遇到各种错误我列几个典型的“Board not found”板包没装好重新安装ESP32板包。“PSRAM not available”开发板配置里没开PSRAM选OPI PSRAM。“Partition too small”分区表选小了换成16M Flash的大分区方案。“Timed out waiting for packet header”烧录时没进入下载模式按住BOOT再点烧录。“undefined reference to xxx”库没装全用库管理器安装依赖。编译速度慢的话可以在首选项里开启“编译缓存”第二次编译会快很多。Windows下编译ESP32确实比Mac和Linux慢这是正常的。4.5 传感器数据漂移与校准MPU6050和磁力计用久了会有漂移。加速度计和陀螺仪的零偏可以通过静止时采样求平均来校准。磁力计需要做椭圆拟合校准消除硬铁和软铁干扰。校准代码思路// 静止时采集1000个样本求平均作为零偏 float ax_offset 0, ay_offset 0, az_offset 0; for (int i 0; i 1000; i) { ax_offset mpu.getAccX(); ay_offset mpu.getAccY(); az_offset mpu.getAccZ(); delay(2); } ax_offset / 1000; ay_offset / 1000; az_offset az_offset / 1000 - 1.0; // 减去重力指南针校准则是水平旋转手表记录mx和my的最大最小值计算偏移和缩放float mx_offset (mx_max mx_min) / 2; float my_offset (my_max my_min) / 2; float mx_scale (mx_max - mx_min) / 2; float my_scale (my_max - my_min) / 2;校准后航向角精度能到±5度以内日常使用足够了。4.6 外壳设计与佩戴体验外壳我用Fusion 360设计3D打印PLA或者PETG。设计要点屏幕开窗要精确留0.2mm公差。按键位置要对应PCB上的轻触开关行程要够。表带接口用标准的20mm生耳方便换表带。背面留充电口和传感器开孔。整体厚度控制在12mm以内太重了戴着不舒服。打印时层高0.15mm填充20%外壳壁厚1.2mm。打印完用砂纸打磨一下喷一层哑光漆质感会好很多。实操心得第一版外壳我设计得太紧凑电池放进去后盖不上盖。后来把内部空间加大了1mm问题解决。所以设计外壳时一定要留足余量尤其是电池和排线。5. 功能扩展与进阶玩法5.1 接入更多传感器ESP32-S3的GPIO很丰富可以接很多传感器扩展功能温湿度SHT30或者DHT22显示环境温湿度。气压计BMP280做高度计和天气趋势预测。心率血氧MAX30102做健康监测。环境光BH1750自动调节屏幕刷新策略。接传感器时注意I2C地址冲突多个设备地址不能一样。如果冲突可以用I2C多路复用器TCA9548A。5.2 低功耗蓝牙HID应用把手表做成BLE HID设备可以当蓝牙键盘或者媒体控制器。比如单击按键播放/暂停双击按键下一曲长按按键唤醒语音助手实现时需要定义HID报告描述符然后通过BLE发送按键报告。ESP32的BLE库有HID示例改一改就能用。5.3 OTA无线升级每次改代码都要插线烧录很麻烦OTAOver-The-Air升级可以解决这个问题。ESP32支持通过Wi-Fi更新固件Arduino IDE里也有OTA功能。#include ArduinoOTA.h void setup() { ArduinoOTA.begin(); } void loop() { ArduinoOTA.handle(); }配置好之后在Arduino IDE的工具菜单里选择网络端口就能无线烧录了。注意OTA时手表要连上Wi-Fi而且电量要充足。5.4 与智能家居联动手表连上Wi-Fi后可以通过MQTT协议和智能家居平台联动。比如手表显示家里温湿度按键控制客厅灯番茄钟结束时推送通知到手机MQTT用PubSubClient库连接MQTT broker订阅和发布消息。这样手表就不只是一个独立设备而是智能家居的一个终端。6. 个人实操体会与建议这个项目我从立项到最终成型大概花了三个月中间踩了不少坑也学到了很多东西。最大的体会是墨水屏手表的核心难点不在功能实现而在功耗控制和稳定性。功能代码网上都能找到参考但把功耗压到1mA以下、让设备连续跑一个月不出问题需要反复调试和优化。另一个体会是硬件和软件的配合很重要。比如墨水屏的刷新时序、电源的纹波、天线的布局这些硬件细节直接决定了软件能不能稳定运行。我建议做类似项目的朋友不要急着堆功能先把最小系统跑通把功耗测准再逐步加功能。最后分享一个小技巧调试阶段可以用USB供电但测续航一定要用电池。USB供电时LDO的静态电流和电池供电不一样测出来的数据不准。另外墨水屏在低温下刷新会变慢冬天户外使用时要注意。这个项目后续还可以扩展很多方向比如加太阳能充电板做永久续航、加NFC做门禁卡、加GPS做轨迹记录。硬件上ESP32-S3的性能还有很大余量软件上Arduino生态也很丰富想怎么玩都可以。