1. 项目缘起与整体设计思路
1.1 为什么要做一块“伪”智能手表
手上戴着一块卡西欧 F-91W,戴了快六年,电池换过两次,外壳磨得发亮,但它依然准时。这种经典数显表的设计语言非常耐看——方正的表壳、简单的液晶、四个按键,没有多余的东西。与此同时,抽屉里躺着几块吃灰的 ESP32 开发板,有 ESP32-WROOM-32 的,也有 ESP32-C3 的,一直想找个能天天用得上的项目把它们消化掉。
于是就有了这个想法:把 ESP32 塞进卡西欧风格的表壳里,做一块“看起来像卡西欧、实际上是 ESP32”的伪智能手表。它不需要能装 App、不需要能刷视频,核心诉求就三条:能看时间、能连手机同步、能显示一些自定义信息(比如温湿度、消息提醒、电量)。说白了,就是一块“带联网能力的电子表”。
这个定位很关键。很多人做 DIY 手表一上来就想对标 Apple Watch,结果项目复杂度爆炸,最后烂尾。我的思路是反过来的:先把它当成一块电子表做扎实,智能功能是加分项,不是必需品。这样即使联网部分暂时没调通,它依然是一块能用的表。
1.2 硬件选型的取舍逻辑
选 ESP32 而不是 STM32 或者 51 单片机,理由很直接。第一,ESP32 自带 Wi-Fi 和蓝牙,省掉外挂模块的麻烦和体积;第二,Arduino 生态成熟,库多,开发快;第三,ESP32 支持深度睡眠,功耗虽然比不过专用 RTC 芯片,但配合合理的唤醒策略,撑个几天没问题;第四,价格便宜,一块 ESP32-C3 模组十几块钱,做坏了不心疼。
具体到型号,我最终选了ESP32-C3-MINI-1。原因有三:它是 RISC-V 架构,单核,够用;体积小,只有 13.2×16.6mm,塞进表壳压力小;支持 BLE 5.0,和手机通信够用。相比之下 ESP32-WROOM-32 体积偏大,双核在这个场景下纯属浪费。
屏幕方面,我用了1.14 寸 ST7789 SPI 屏,135×240 分辨率。有人会问为什么不用更省电的段码 LCD 或者墨水屏。段码 LCD 显示内容太死板,做不出“智能”的感觉;墨水屏刷新太慢,看时间体验差。TFT 虽然费电,但刷新快、颜色丰富、驱动库成熟,权衡下来是最优解。
电源部分是这个项目的难点。ESP32 峰值电流能到 500mA,Wi-Fi 工作时平均也有 100mA 以上。我用了3.7V 500mAh 锂聚合物电池,配合TP4056 充电模块和ME6211 LDO 稳压到 3.3V。这里没用 DC-DC 是因为 LDO 纹波小,对屏幕和射频干扰小,代价是效率低一些,但 500mAh 的容量能接受。
1.3 整体架构与功能规划
整块表的功能分三层。底层是时间基准,用 ESP32 内部的 RTC 配合 NTP 校时;中间层是显示和交互,ST7789 负责画面,一个物理按键负责切换页面;上层是联网功能,通过 BLE 和手机通信,接收通知、同步时间、上传传感器数据。
表壳我用 3D 打印机打的,材料是 PLA。模型是自己用 Fusion 360 画的,参考了 F-91W 的尺寸——表壳 38.2×35.2×8.5mm,表带接口 18mm。打印精度 0.2mm 层高,打完用砂纸打磨,喷一层哑光黑漆,远看还真有几分卡西欧的味道。
提示:3D 打印表壳时,屏幕开窗要留 0.3mm 的余量,否则屏幕装不进去。按键孔建议打成 3.2mm,配 3mm 的轻触按键刚好。
2. 核心硬件细节与电路设计要点
2.1 ESP32-C3 最小系统搭建
ESP32-C3 的最小系统比想象中简单,但有几个坑必须提前说清楚。首先是EN 引脚,必须接一个 10kΩ 上拉电阻到 3.3V,再并一个 0.1μF 电容到地,否则芯片上电复位不稳定,会出现时好时坏的现象。我第一次打板就是漏了这个电容,结果十次上电有三次不启动,排查了半天。
其次是GPIO9 启动模式引脚。ESP32-C3 默认从内部 Flash 启动,GPIO9 需要保持高电平。如果你把 GPIO9 用作普通 IO,一定要确保上电瞬间它是高电平,否则芯片会进入下载模式,程序不运行。我的做法是 GPIO9 接一个 10kΩ 上拉到 3.3V,同时接一个按键到地,这样既能正常启动,又能手动进入下载模式。
USB 下载电路我用了CH343P,比 CH340 体积小,支持自动下载。自动下载电路的核心是两个三极管,分别控制 EN 和 GPIO9。具体接法是:DTR 通过一个 NPN 三极管控制 EN,RTS 通过另一个 NPN 控制 GPIO9,同时 EN 和 GPIO9 之间要交叉耦合。这个电路网上有标准参考,照着抄就行,但要注意三极管的基极电阻用 10kΩ,集电极电阻用 10kΩ,别用大了。
电源部分,ME6211 的输入输出都要加电容。输入端 10μF,输出端 10μF 加 0.1μF,这是 LDO 稳定的基本要求。我实测过,如果输出端只放 1μF,Wi-Fi 一开就重启,因为瞬态响应跟不上。
2.2 ST7789 屏幕的接线与驱动
ST7789 用 SPI 驱动,接线不复杂,但引脚分配有讲究。ESP32-C3 的 SPI 引脚是固定的:GPIO6 是 SCK,GPIO7 是 MOSI,这两个不能改。CS、DC、RST、BLK 可以自由分配,我用了 GPIO10、GPIO4、GPIO5、GPIO3。
屏幕的背光控制很关键。BLK 引脚直接接 3.3V 的话,背光常亮,功耗大概 20mA。我把它接到 GPIO3,用 PWM 控制亮度,白天 100%,晚上 30%,既省电又护眼。PWM 频率用 5kHz,占空比调节,实测没有频闪。
驱动库我用的是TFT_eSPI,比 Adafruit 的快不少,而且支持 DMA。配置的时候要改User_Setup.h,把驱动改成 ST7789,分辨率改成 135×240,引脚按实际接线填。这里有个坑:ST7789 的 135×240 屏,实际显存是 240×320,需要设置偏移量。我的屏偏移是_xstart=52, _ystart=40,不同批次可能不一样,要自己试。
注意:TFT_eSPI 的偏移量如果设错,画面会偏到一边或者出现花屏。调试时先显示一个全屏红色,看边缘有没有黑边,再微调偏移值。
2.3 电源管理与充电电路
电池管理是 DIY 手表最容易翻车的地方。我用的是 TP4056 充电模块,但它有个问题:充电时如果同时给负载供电,充电电流会被负载分走,导致充电时间变长,甚至充不满。解决办法是在电池和负载之间加一个P 沟道 MOSFET,充电时断开负载,放电时导通。我用的是 AO3401,导通电阻 50mΩ,压降可以忽略。
电量检测用 ESP32-C3 的 ADC,通过一个分压电路读电池电压。分压电阻用 100kΩ 和 100kΩ,这样静态电流只有 18μA,对电池影响很小。ADC 读数要校准,ESP32-C3 的 ADC 线性度一般,我用了多点校准,在 3.3V、3.7V、4.2V 三个点测出实际值,拟合一条曲线,误差能控制在 2% 以内。
低功耗策略上,我做了几件事。第一,屏幕背光自动调暗;第二,Wi-Fi 只在需要同步时开启,平时关闭;第三,用 ESP32-C3 的light sleep模式,配合 GPIO 唤醒。实测下来,正常使用(每分钟刷新一次时间,每小时同步一次)能撑 3 天左右。如果关掉 Wi-Fi,只当电子表用,能撑 7 天。
3. 软件架构与关键代码实现
3.1 开发环境搭建与离线包配置
Arduino IDE 对 ESP32-C3 的支持已经比较成熟,但国内下载开发板包经常失败。我的做法是手动装离线包。先去 GitHub 下载esp32-3.0.0.zip,然后解压到~/Arduino/hardware/espressif/esp32目录下,再运行tools/get.exe安装工具链。这样比在线下载稳定得多。
如果你用 PlatformIO,配置更简单。在platformio.ini里写:
[env:esp32-c3-devkitm-1] platform = espressif32 board = esp32-c3-devkitm-1 framework = arduino monitor_speed = 115200 board_build.flash_mode = dioPlatformIO 的好处是库管理方便,编译速度也比 Arduino IDE 快。我实测同一个项目,PlatformIO 编译 25 秒,Arduino IDE 要 50 秒。而且 PlatformIO 支持离线包,把.platformio目录拷到另一台电脑就能直接用。
提示:Windows 下编译 ESP32 慢,很大一部分原因是杀毒软件实时扫描。把项目目录和
.platformio目录加入杀毒软件白名单,编译速度能快一倍。
3.2 时间管理与 NTP 校时
时间基准用 ESP32 内部的 RTC,但内部 RTC 精度一般,一天能漂几秒。所以需要定期 NTP 校时。我的策略是:开机连 Wi-Fi 校时一次,之后每 6 小时校时一次,如果连不上 Wi-Fi,就用内部 RTC 继续走,误差累积到一定程度再提醒用户。
NTP 校时代码很简单:
#include <WiFi.h> #include <time.h> void syncTime() { configTime(8 * 3600, 0, "ntp.aliyun.com", "pool.ntp.org"); struct tm timeinfo; if (getLocalTime(&timeinfo)) { Serial.println("Time synced"); } }这里时区设的是东八区,偏移 8×3600 秒。configTime会自动处理夏令时,但国内不用夏令时,所以第二个参数填 0。
显示时间的时候,我用getLocalTime每秒读一次,然后格式化输出。注意getLocalTime有阻塞风险,如果 NTP 没同步上,它会等 5 秒才返回。解决办法是先用getLocalTime(&timeinfo, 0)非阻塞读,如果返回 false 就跳过这次刷新。
3.3 BLE 通信与手机端交互
BLE 是这个项目的“智能”核心。我用 ESP32-C3 做 BLE 外设,手机做中心设备。服务 UUID 自定义,特征值分三个:一个用来接收手机发来的通知文本,一个用来发送传感器数据,一个用来同步时间。
BLE 初始化代码:
#include <BLEDevice.h> #include <BLEServer.h> #include <BLEUtils.h> BLEServer *pServer; BLECharacteristic *pNotifyChar; void setupBLE() { BLEDevice::init("CASIO-ESP32"); pServer = BLEDevice::createServer(); BLEService *pService = pServer->createService(SERVICE_UUID); pNotifyChar = pService->createCharacteristic( NOTIFY_CHAR_UUID, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY ); pService->start(); BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->start(); }手机端我用的是nRF Connect调试,正式用的话可以自己写个 App,或者用MIT App Inventor快速搭一个。实测 BLE 连接距离在空旷环境 10 米左右,隔一堵墙 5 米,够用了。
功耗方面,BLE 广播时平均电流 15mA,连接后 8mA,比 Wi-Fi 省很多。所以我的策略是:平时只开 BLE,Wi-Fi 只在需要大流量传输时开。
3.4 显示界面与交互逻辑
界面设计我做了三个页面:主页面显示时间、日期、电量;第二页显示温湿度(外接 SHT30 传感器);第三页显示手机通知。用一个物理按键切换,短按翻页,长按 2 秒进入配网模式。
时间显示用大字体,我用了 TFT_eSPI 的drawString配合自定义字体。默认字体太小,我加载了一个 48 像素高的数字字体,显示效果接近卡西欧的液晶。日期和电量用小字体放在底部。
刷新策略上,不能每秒全屏重绘,那样会闪。我的做法是只重绘变化的区域。比如时间,只重绘时分秒那部分,背景不动。这样刷新率能到 30fps,肉眼看不到闪烁。
按键处理用外部中断,而不是轮询。ESP32-C3 的 GPIO 都支持中断,我把按键接在 GPIO2,配置成下降沿触发。中断里只置一个标志位,主循环里处理,避免在中断里做耗时操作。
volatile bool buttonPressed = false; void IRAM_ATTR buttonISR() { buttonPressed = true; } void setup() { pinMode(2, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(2), buttonISR, FALLING); } void loop() { if (buttonPressed) { buttonPressed = false; // 处理按键 } }注意:中断服务函数要加
IRAM_ATTR,否则可能因为 Flash 访问导致崩溃。另外中断里不要用delay和Serial.print。
4. 组装调试与常见问题排查
4.1 硬件组装顺序与注意事项
组装顺序很重要,搞错了要拆重来。我的顺序是:先焊 ESP32-C3 模组和外围电路,测试能下载程序;再焊屏幕,测试显示正常;然后焊电源部分,测试充电和放电;最后装进表壳。
焊接 ESP32-C3 模组时,风枪温度设 280℃,风速调最低。模组底部有散热焊盘,要先在 PCB 上涂锡膏,放上模组后用风枪从底部加热,看到锡膏融化后轻轻按压,确保接触良好。焊完用放大镜检查引脚有没有连锡,特别是 GPIO 密集的区域。
屏幕的 FPC 排线很脆弱,焊接时温度不能超过 300℃,时间不超过 3 秒。我建议用烙铁头拖焊,不要用风枪,风枪容易把排线吹变形。
电池焊接要注意极性,红线正极,黑线负极。TP4056 模块的 B+ 和 B- 接电池,OUT+ 和 OUT- 接负载。接反了会烧模块,我烧过两个,都是因为晚上焊的时候没看清。
4.2 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决办法 |
|---|---|---|---|
| 上电不启动 | EN 引脚电容缺失 | 测 EN 电压 | 加 0.1μF 电容 |
| 下载失败 | GPIO9 电平不对 | 测 GPIO9 上电电平 | 加 10kΩ 上拉 |
| 屏幕花屏 | 偏移量设错 | 显示全屏红色 | 调整 _xstart/_ystart |
| Wi-Fi 一开就重启 | 电源瞬态响应差 | 测 3.3V 纹波 | 输出端加 10μF 电容 |
| 时间走不准 | 内部 RTC 漂移 | 对比 NTP 时间 | 缩短校时间隔 |
| BLE 连不上 | 广播间隔太长 | 用 nRF Connect 扫描 | 改广播间隔为 100ms |
| 充电充不满 | 负载分流 | 测充电电流 | 加 P 沟道 MOSFET |
| 按键不灵敏 | 中断配置错误 | 测 GPIO 电平 | 改下降沿触发 |
4.3 调试过程中的踩坑记录
第一个坑是ADC 读数不准。ESP32-C3 的 ADC 在 Wi-Fi 开启时噪声很大,读数能跳 0.2V。解决办法是读 10 次取平均,而且要在 Wi-Fi 关闭时读。我后来改成每 10 秒读一次,读之前先关 Wi-Fi,读完再开,虽然麻烦但准确。
第二个坑是屏幕刷新撕裂。TFT_eSPI 默认不用 DMA,刷新大区域时能看到明显的撕裂。开启 DMA 后好很多,但要注意 DMA 缓冲区不能太小,我设的是 1/4 屏幕大小,也就是 135×60×2 字节,约 16KB。ESP32-C3 的 RAM 有 400KB,够用。
第三个坑是BLE 和 Wi-Fi 共存。ESP32-C3 是单核,BLE 和 Wi-Fi 同时开的时候,射频会冲突,导致 BLE 断连。我的解决办法是分时复用:需要 Wi-Fi 时先关 BLE,传完再开 BLE。虽然切换有 1 秒左右的延迟,但稳定性好很多。
第四个坑是低功耗模式唤醒后屏幕不亮。light sleep 唤醒后,SPI 外设需要重新初始化,否则屏幕没反应。我在唤醒后加了一段重新初始化的代码,问题解决。
4.4 实测功耗与续航优化
我用INA219电流传感器测了整机功耗,数据如下:
| 工作状态 | 电流 | 占比 |
|---|---|---|
| 深度睡眠 | 0.8mA | 60% |
| 亮屏显示 | 25mA | 30% |
| Wi-Fi 传输 | 120mA | 5% |
| BLE 连接 | 8mA | 5% |
按 500mAh 电池算,平均电流约 12mA,理论续航 41 小时。实际因为电池老化、温度影响,能撑 30 小时左右。如果关掉 Wi-Fi,只开 BLE,平均电流降到 6mA,续航能到 80 小时。
进一步优化的话,可以把屏幕换成夏普 Memory LCD,静态显示时几乎不耗电,只有刷新时才耗电。但 Memory LCD 贵,一块 1.3 寸的要 100 多,而且驱动复杂。我暂时没换,等哪天想折腾了再说。
提示:测功耗时,万用表要串在电池和负载之间,用 mA 档。注意万用表本身有内阻,会引入压降,测出来的电流偏小。更准的方法是用 INA219 或者 INA226 这类专用芯片。
5. 外壳制作与佩戴体验
5.1 3D 打印表壳的设计细节
表壳模型我改了七版才定稿。第一版太厚,戴手上像块砖;第二版太薄,屏幕装不进去;第三版按键位置不对,按着别扭。最终版的尺寸是 38.2×35.2×10.5mm,比 F-91W 厚 2mm,因为要塞电池和 PCB。
屏幕开窗是 1.14 寸屏的尺寸,26×15mm,边缘留 0.5mm 边框。开窗用倒角刀走一圈,做出 45 度倒角,看起来更精致。按键孔三个,直径 3.2mm,间距 8mm,对应三个功能键。
表带接口是 18mm 标准接口,可以直接用 F-91W 的表带。我买了一条硅胶表带,比原装树脂带舒服,而且防水。
打印参数:层高 0.2mm,壁厚 1.2mm,填充 20%,支撑用树形支撑。打完用 400 目砂纸打磨,再喷哑光黑漆,最后贴一张卡西欧的 Logo 贴纸,远看能以假乱真。
5.2 佩戴体验与改进方向
戴了半个月,整体体验还行,但有几个地方要改进。第一,厚度还是偏厚,穿长袖的时候会卡袖口。下一版打算把电池换成 300mAh 的薄电池,厚度能减 2mm。第二,按键手感偏硬,因为用的是 3mm 轻触按键,行程短。换成 4mm 的锅仔片会好一些。第三,屏幕在强光下看不清,TFT 的通病。下一版考虑加一层防眩光膜,或者换半反半透屏。
续航方面,我现在的用法是白天正常显示,晚上 11 点自动进入深度睡眠,早上 7 点唤醒。这样一天耗电约 40%,两天一充。充电用磁吸触点,不用插线,方便。
这个项目后续还可以扩展。比如加一个MAX30102心率传感器,做成心率监测;加一个BME280气压计,做海拔显示;或者加一个SD 卡槽,记录运动数据。不过这些都是后话了,先把基础功能做扎实再说。
5.3 成本核算与物料清单
整个项目做下来,成本比想象中低。物料清单如下:
| 物料 | 型号 | 数量 | 单价 |
|---|---|---|---|
| 主控 | ESP32-C3-MINI-1 | 1 | 12元 |
| 屏幕 | ST7789 1.14寸 | 1 | 25元 |
| 电池 | 3.7V 500mAh | 1 | 15元 |
| 充电模块 | TP4056 | 1 | 3元 |
| 稳压 | ME6211 | 1 | 1元 |
| 传感器 | SHT30 | 1 | 8元 |
| PCB | 嘉立创打样 | 5片 | 10元 |
| 外壳 | 3D打印 | 1 | 5元 |
| 表带 | 18mm硅胶 | 1 | 15元 |
| 其他 | 电阻电容按键 | - | 10元 |
| 合计 | 约104元 |
一百块出头,比买一块卡西欧还便宜。当然,时间成本没算进去,前后折腾了大概三周,每天晚上搞两小时。但这个过程学到的东西,比直接买一块表值多了。
最后分享一个小技巧:PCB 打样的时候,把屏幕的 FPC 座和电池座都放在背面,正面只留屏幕和按键,这样组装的时候正面干净,看起来更像成品。另外,PCB 颜色选黑色,和表壳更搭。