1. 项目缘起与整体设计思路
1.1 为什么要做一块“伪”智能手表
手上有一块闲置的卡西欧F-91W,表带断了,表壳也花得不成样子,但那个经典的方形表盘和树脂外壳的造型一直让我觉得可惜。与此同时,抽屉里躺着几块ESP32开发板,平时用来做温湿度采集、蓝牙控制小车之类的项目,一直没找到一个能长期戴在身上、又有实用价值的载体。某天刷到有人把ESP32塞进电子表外壳的帖子,突然觉得这事儿可以干——用ESP32做核心,保留卡西欧的外观风格,实现时间显示、温湿度监测、蓝牙通知提醒这些基础功能,成本控制在百元以内,还能随时改代码扩展功能。
这个项目的核心定位很明确:不是要替代手机或真正的智能手表,而是做一个“带单片机的电子表”。它解决的需求也很具体——我想要一块续航够用、能看时间、能显示环境温湿度、能通过蓝牙接收手机通知提醒的手表,同时外观要有复古数码表的味道。适合有基础焊接能力、玩过Arduino或ESP32的爱好者参考,纯小白也能跟着做,但需要一定的动手能力。
1.2 硬件选型的取舍逻辑
选ESP32而不是STM32或51单片机,理由很直接:内置WiFi和蓝牙。蓝牙用来做通知推送和配置,WiFi留着以后扩展NTP对时或者OTA升级。51单片机虽然便宜,但加蓝牙模块和屏幕驱动之后,整体复杂度和成本反而上去了。STM32性能强,但蓝牙方案需要外挂模块,开发环境对新手也不够友好。
屏幕方面,我试过0.96寸OLED和1.14寸TFT两种方案。OLED功耗低、对比度高,但尺寸太小,塞进卡西欧表壳后可视面积不够。最终选了1.14寸ST7789 TFT屏,分辨率135x240,IPS全视角,驱动简单,SPI接口占用引脚少,亮度也够户外看。电池用3.7V 502030规格的锂聚合物电池,容量约250mAh,实测中等亮度下能撑6到8小时,日常通勤够用,晚上充电就行。
主控板我选的是ESP32-C3 SuperMini,体积小、价格低、支持蓝牙5.0,而且引脚布局紧凑,刚好能塞进表壳内部。充电管理用TP4056模块,带过充过放保护,输出接一个ME6211 LDO稳压到3.3V给ESP32和屏幕供电。整个硬件方案的核心思路就是:小体积、低功耗、模块化,每个部分都能单独替换或升级。
1.3 外观方案与结构处理
卡西欧F-91W的原装表壳内部空间非常有限,直接塞ESP32肯定放不下。我的做法是:保留表壳正面和表带接口,把后盖掏空,用3D打印一个加厚的后盖来容纳电路。表盘部分把原装的液晶屏拆掉,换成TFT屏,用黑色亚克力板切割出合适的窗口,边缘用热熔胶固定。按键方面,原装的四个侧键保留两个,分别接ESP32的GPIO9和GPIO10,用作功能切换和屏幕开关。
3D打印后盖的模型我用Fusion 360画的,壁厚1.2mm,内部留出电池和主板的卡槽,充电口开在底部,用Type-C母座引出。打印材料用PETG,耐温性和韧性比PLA好,戴在手上不容易因为出汗变形。整个装配过程不需要螺丝,靠卡扣和少量热熔胶固定,方便后期拆修。
注意:拆原装表壳的时候,表镜和表壳之间有一圈防水胶圈,拆完基本就废了,所以这个项目做出来不防水,洗手时最好摘下来。
2. 核心硬件连接与电路细节
2.1 ESP32-C3与ST7789屏幕的SPI接线
ST7789用SPI接口驱动,ESP32-C3的SPI引脚可以灵活映射,我选了一组不冲突的引脚。具体接线如下:
| ST7789引脚 | ESP32-C3引脚 | 功能说明 |
|---|---|---|
| VCC | 3.3V | 逻辑供电 |
| GND | GND | 共地 |
| SCL | GPIO4 | SPI时钟 |
| SDA | GPIO6 | SPI数据 |
| RES | GPIO7 | 复位 |
| DC | GPIO5 | 数据/命令选择 |
| CS | GPIO10 | 片选 |
| BLK | GPIO3 | 背光控制 |
背光控制引脚接GPIO3,通过PWM调节亮度。实测背光电流约20mA,直接由GPIO驱动会超过ESP32单脚推荐电流,所以我在背光引脚和屏幕BLK之间加了一个2N7002 NMOS做开关,GPIO3控制栅极,源极接地,漏极接屏幕BLK,屏幕BLK再通过一个100Ω电阻接3.3V。这样GPIO只需要提供逻辑电平,背光电流走MOS管,安全很多。
SPI时钟频率我设的是40MHz,再高会出现花屏,40MHz下刷一整屏大概15ms,肉眼看不到闪烁。如果你用的排线比较长,建议降到20MHz,稳定性更好。
2.2 电源管理与充电电路
电池正极接TP4056的B+,负极接B-,TP4056的OUT+和OUT-分别接ME6211的VIN和GND,ME6211的VOUT输出3.3V给ESP32和屏幕。TP4056的充电电流我设的是500mA,通过更换RPROG电阻实现,公式是:
Icharge = 1200 / RPROG (kΩ)
要得到500mA,RPROG取2.4kΩ。实际焊接时我用的是2.2kΩ,充电电流约545mA,稍微大一点没关系,电池支持1C充电,250mAh的电池用500mA充也就半小时左右。
ME6211的压差在200mV左右,电池电压降到3.5V时输出还能稳在3.3V,再低就掉压了。ESP32-C3的工作电压范围是3.0V到3.6V,所以电池从4.2V放到3.4V这段时间系统都能正常工作。实测从满电到自动关机大约7小时,屏幕亮度50%、蓝牙广播间隔1秒的情况下。
2.3 按键与外部中断的实战配置
手表上保留了两个物理按键,接在GPIO9和GPIO10上,另一端接地。按键按下时引脚被拉低,所以配置为内部上拉输入。这里我用的是外部中断而不是轮询,原因是轮询会占用CPU时间,而且按键响应有延迟。ESP32的外部中断配置在Arduino IDE里很简单:
pinMode(BUTTON_PIN, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), handleButton, FALLING);FALLING表示下降沿触发,也就是按键按下的瞬间。中断服务函数里我只做一件事:设置一个标志位,主循环检测到标志位再执行具体操作。千万不要在中断里做耗时操作,比如刷屏、读传感器、串口打印,否则会触发看门狗复位。我一开始在中断里直接调了屏幕刷新函数,结果手表每隔几秒就重启,排查了半天才发现是中断里执行时间太长。
按键消抖我用的是软件延时消抖,在中断触发后记录时间戳,如果两次中断间隔小于200ms就忽略。这个方法比在中断里delay靠谱,不会阻塞其他中断。
3. 软件框架与核心功能实现
3.1 Arduino IDE环境搭建与离线包处理
开发环境我用的是Arduino IDE 2.3.2,ESP32支持包版本2.0.14。国内下载支持包经常失败,我的做法是手动下载离线包:在Arduino IDE的“首选项”里找到“附加开发板管理器网址”,填入ESP32的官方JSON地址,然后在开发板管理器里搜索“esp32”,选择2.0.14版本安装。如果网络不稳定,可以直接去GitHub下载esp32-2.0.14.zip,解压到Arduino15/packages/esp32/hardware/esp32/2.0.14目录下,重启IDE就能识别。
库方面需要装三个:TFT_eSPI用于屏幕驱动,ESP32BLEKeyboard用于蓝牙HID,Adafruit_SHT31用于温湿度传感器。TFT_eSPI的配置比较特殊,需要修改User_Setup.h文件,把屏幕驱动型号、引脚定义、SPI频率都改对。我直接把配置文件里的ST7789驱动打开,注释掉其他驱动,然后填入上面表格里的引脚号。这一步如果搞错,屏幕要么不亮,要么颜色反色,要么偏移。
实操心得:TFT_eSPI的
User_Setup.h修改后,最好把整个库文件夹复制一份备份,下次换项目直接替换,省得重新配。
3.2 时间显示与NTP对时逻辑
时间显示是手表的基础功能。ESP32本身有RTC,但精度一般,一天误差几秒到几十秒。我的方案是:开机时连WiFi用NTP对时,然后断开WiFi,靠RTC走时。NTP服务器用pool.ntp.org,时区设成东八区,代码里用configTime(8 * 3600, 0, "pool.ntp.org")。对时成功后把时间写入RTC,之后每24小时重新对一次,或者手动按键触发对时。
屏幕刷新策略上,我只刷新变化的部分,而不是整屏重绘。比如秒数变化时,只更新秒数所在的区域,背景不重画。这样刷屏时间从15ms降到2ms左右,功耗也低。TFT_eSPI提供了sprite功能,可以创建一个内存画布,先在画布上画好,再一次性推送到屏幕,避免闪烁。我用了一个240x135的sprite,占用约64KB内存,ESP32-C3的400KB RAM完全够用。
时间显示的字体我用的是TFT_eSPI自带的FreeSansBold24pt,数字够大,一米外能看清。日期和星期用小一号的字体放在下方。整个表盘布局模仿卡西欧的经典风格:上方显示时间,中间显示温湿度,下方显示蓝牙连接状态和电池电量。
3.3 蓝牙通知提醒与HID实现
蓝牙部分我用了BLE HID方案,把ESP32模拟成一个蓝牙键盘,手机连接后,收到通知时ESP32会收到按键事件,然后触发屏幕显示提醒。具体实现是:ESP32作为BLE HID设备广播,手机配对后,在手机端设置里开启“通知访问权限”,把通知转发到ESP32。ESP32收到通知后,在屏幕上弹出一个提示框,显示“新消息”并振动一下(振动马达接在GPIO1上,通过MOS管驱动)。
这里有个坑:ESP32-C3的BLE和WiFi不能同时开,只能二选一。所以我的逻辑是:开机先连WiFi对时,对时完成后关闭WiFi,再启动BLE。这样时间准了,蓝牙通知也能用。如果你需要同时用WiFi和蓝牙,得换ESP32-S3,它支持双模共存。
振动马达我用的是1027扁平马达,额定电压3V,电流约80mA。GPIO1通过2N7002驱动,栅极串一个100Ω电阻,源极接地,漏极接马达负极,马达正极接3.3V,马达两端反并联一个1N4148二极管做续流,防止关断时反向电动势打坏MOS管。这个电路我实测了上百次开关,没出过问题。
3.4 温湿度采集与屏幕布局
温湿度传感器我用的是SHT31,I2C接口,精度±2%RH和±0.3°C,比DHT11靠谱得多。接线很简单:VCC接3.3V,GND接地,SDA接GPIO8,SCL接GPIO9。注意GPIO9同时被按键占用,所以我把按键改到了GPIO10和GPIO11,避免冲突。I2C地址是0x44,代码里用Adafruit_SHT31 sht31 = Adafruit_SHT31();初始化,每5秒读一次,读到的温度和湿度显示在屏幕中间区域。
屏幕布局我调整了好几版,最终定下来的是:
- 顶部区域:时间(大字体),居中显示
- 中部左侧:温度,带°C符号
- 中部右侧:湿度,带%符号
- 底部左侧:蓝牙图标,连接时显示实心,未连接显示空心
- 底部右侧:电池图标,根据电压显示电量格数
电池电压采集用ESP32-C3的ADC,接一个100kΩ和100kΩ的分压电阻,把电池电压降到一半再进ADC。ADC参考电压是3.3V,12位分辨率,所以电压计算公式是:
Vbat = (ADC值 / 4095) * 3.3 * 2
实测ADC读数有±50mV的波动,我在代码里做了滑动平均滤波,取最近10次读数的平均值,显示就稳定了。
4. 常见问题与排查技巧实录
4.1 屏幕不亮或花屏的排查思路
屏幕问题是最常见的,我遇到过好几次。排查顺序如下:
- 先查背光:用万用表测BLK引脚电压,如果是0V,说明背光控制没生效。检查GPIO3是否配置为输出,MOS管是否焊反。
- 再查SPI通信:用逻辑分析仪抓SCL和SDA波形,如果没有波形,说明SPI初始化失败。检查
User_Setup.h里的引脚定义是否和实际接线一致。 - 最后查复位时序:ST7789的RES引脚需要先拉低再拉高,延时至少10ms。如果复位不对,屏幕会一直白屏或花屏。我在代码里加了
digitalWrite(RES, LOW); delay(10); digitalWrite(RES, HIGH); delay(120);,问题解决。
花屏还有一种可能是SPI频率太高,降到20MHz试试。如果颜色反了,在User_Setup.h里把TFT_RGB_ORDER改成TFT_BGR。
4.2 蓝牙连接不稳定或频繁断开的处理
BLE连接不稳定通常有三个原因:天线布局、电源噪声、广播间隔。ESP32-C3的板载天线对周围金属很敏感,我把主板放在电池正上方时,蓝牙信号明显变差,后来把主板移到表壳顶部,天线朝外,信号就稳了。电源噪声方面,我在ME6211输出端并了一个100μF钽电容和一个0.1μF陶瓷电容,滤掉高频纹波,蓝牙断连次数从每天十几次降到几乎为零。
广播间隔我设的是500ms,太快费电,太慢手机发现不了。连接参数里把slave latency设成0,connection interval设成30ms,响应速度够快,功耗也能接受。
4.3 电池续航短与充电异常的解决
续航短一般是屏幕背光太亮或蓝牙一直广播。我的做法是:屏幕默认30秒无操作自动熄屏,按键唤醒;蓝牙在无连接时进入低功耗广播模式,连接后正常。实测这样设置后,续航从4小时提升到7小时。
充电异常最常见的是TP4056发热严重。如果充电电流设得太大,TP4056会烫手,甚至触发过热保护。我把RPROG从1.2kΩ换成2.2kΩ后,充电电流降到545mA,温度控制在50°C以内。另外,TP4056的B+和B-到电池的线要尽量短,线太长会导致充电电压跌落,充不满。
4.4 常见问题速查表
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 屏幕白屏 | 复位时序不对 | 检查RES引脚延时 |
| 屏幕花屏 | SPI频率过高 | 降到20MHz |
| 颜色反色 | RGB顺序错误 | 改TFT_RGB_ORDER |
| 蓝牙频繁断连 | 天线被遮挡 | 调整主板位置 |
| 续航短 | 背光常亮 | 加自动熄屏逻辑 |
| 充电发热 | 充电电流过大 | 换大RPROG电阻 |
| 按键无响应 | 中断冲突 | 检查引脚占用 |
| 时间不准 | RTC漂移 | 每天NTP对时一次 |
独家避坑:ESP32-C3的GPIO9是BOOT引脚,上电时如果被拉低会进入下载模式。我把按键接在GPIO9上时,每次按着按键开机,手表就进不了正常程序。后来把按键改到GPIO10和GPIO11,问题消失。如果你非要用GPIO9,记得在按键和引脚之间串一个10kΩ电阻,并且不要在上电时按住。
5. 外壳装配与佩戴体验优化
5.1 3D打印后盖的模型调整
后盖模型我改了五版才定型。第一版太厚,戴在手上像块砖;第二版太薄,电池鼓包后盖会裂;第三版卡扣太紧,拆一次断一次。最终版的壁厚是1.2mm,内部留了0.5mm的余量给电池和主板,卡扣用悬臂梁结构,拆装十几次都没断。打印参数:层高0.16mm,填充20%,打印温度240°C,热床80°C,速度50mm/s。PETG打印出来表面有点拉丝,用打火机快速燎一下就去掉了。
表带接口我保留了原装的弹簧杆,但原装表带太硬,戴久了手腕不舒服。我换了一条22mm宽的硅胶表带,柔软透气,颜色选了黑色,和表壳搭配。表带连接处用3D打印的转接件,把弹簧杆固定在打印件上,再用M2螺丝锁紧。
5.2 佩戴舒适度与散热处理
ESP32和屏幕工作时会发热,尤其是蓝牙广播时,主板背面温度能到45°C左右。直接贴着手腕会不舒服,我在主板和表壳之间垫了一层0.5mm厚的导热硅胶垫,把热量导到表壳背面,再通过表壳散出去。实测佩戴时表壳背面温度在38°C左右,可以接受。
重量方面,整块手表(含电池和表带)约52克,比原装卡西欧重了20克,但戴习惯了没什么感觉。如果你追求轻量化,可以把电池换成200mAh的,能减重5克左右。
5.3 日常使用中的小技巧
- 充电:用5V 1A的充电头,不要用快充头,TP4056不支持快充协议,快充头可能不输出。
- 防水:不防水,洗手、下雨天最好摘下来。
- 清洁:屏幕用眼镜布擦,表壳用酒精棉片擦,不要用丙酮,会腐蚀PETG。
- 固件升级:我把OTA功能留着了,连上WiFi后可以通过Arduino IDE的OTA功能无线烧录,不用拆壳。
这个项目从画图到装配花了大概三个周末,中间踩了不少坑,但做出来的效果挺满意。它不是什么高科技产品,就是一块带蓝牙通知和温湿度显示的电子表,但每次抬手看时间的时候,都会觉得这块表比买来的有意思。后续我打算加一个计步功能,用MPU6050做姿态解算,再把屏幕换成触摸的,这样交互会更方便。如果你也想做一块,建议先从点亮屏幕开始,一步步来,别想着一次把所有功能都塞进去。