1. 项目缘起:从游戏到现实的“哔哔小子”之梦
如果你和我一样,是《辐射》系列游戏的忠实粉丝,那么对“哔哔小子”这个装备一定不会陌生。它不仅是游戏主角手腕上的多功能个人终端,更是废土生存的象征,集成了状态监控、地图导航、物品管理、电台广播等一系列功能,其复古的绿色CRT显示风格和标志性的“哔哔”音效,早已成为游戏文化的一部分。每次在游戏中打开哔哔小子界面,那种沉浸感都让人着迷。
于是,一个念头自然而生:能不能把这份来自虚拟废土的浪漫,带到现实世界,戴在自己的手腕上?这就是“PiP Watch”(哔哔小子手表)项目的初衷。它不是一个简单的游戏周边,而是一个融合了硬件DIY、嵌入式开发、UI设计和情怀的综合性创客项目。我们想复刻的,不仅仅是那个绿色的界面,更是那种将复杂信息集成于方寸之间的极客精神。
市面上虽然有一些基于树莓派Zero或更高性能SBC(单板计算机)的哔哔小子手表方案,但它们往往体积较大、功耗较高,续航是硬伤。而我们的目标,是打造一款更贴近“手表”形态,能够日常佩戴、拥有不错续航的轻量化版本。经过一番选型,DFRobot的Firebeetle ESP32-E进入了我的视野。这款板子集成了ESP32主控、锂电池充电管理、丰富的GPIO,更重要的是,它极其紧凑的尺寸和低功耗特性,完美契合了可穿戴设备的需求。用ESP32来驱动一块小屏幕,实现动态的哔哔小子界面,并整合一些基础传感器,这个想法让我跃跃欲试。
2. 核心硬件选型与设计思路解析
一个可穿戴项目,硬件是骨架。选型不仅决定了功能上限,更直接关系到成品的外观、佩戴舒适度和续航能力。我的核心思路是:在有限的体积和功耗预算内,最大化“哔哔小子”的感官体验。
2.1 主控板:为什么是Firebeetle ESP32-E?
在众多ESP32开发板中,选择Firebeetle ESP32-E(以下简称ESP32-E)是基于以下几个关键考量:
- 极致的尺寸与集成度:ESP32-E的PCB尺寸仅为25.4mm x 38.1mm,比一张SD卡还小。它板载了CP2102 USB转串口芯片、锂电池充电管理电路(最大充电电流500mA)和3.3V稳压器。这意味着你不需要额外焊接任何模块,只需接上电池和屏幕,一个最小系统就搭建完成了,极大简化了手表内部的堆叠设计。
- 专为低功耗优化:ESP32本身支持多种低功耗模式(Deep Sleep, Light Sleep等)。ESP32-E在设计上进一步考虑了功耗,例如其AMS1117稳压器的静态电流较低。对于手表这类需要长时间待机的设备,每一微安的电流都至关重要。实测中,在深度睡眠模式下,整机电流可以控制在10μA左右,这意味着一块400mAh的电池,纯待机时间可以长达数年。
- 足够的性能与接口:ESP32双核240MHz的主频,驱动一块分辨率不高的TFT屏幕进行UI动画渲染绰绰有余。它拥有34个可用的GPIO,虽然部分被板载功能占用(如LED、按键),但剩余引脚足够连接屏幕、传感器和震动马达。内置的Wi-Fi和蓝牙虽然在本项目中可能不是核心功能(为了省电通常会关闭),但为未来扩展(如无线同步数据、接收通知)留下了可能。
- 良好的生态与社区支持:DFRobot提供了完善的Arduino库支持,并且其引脚布局清晰,相关资料易于查找。这对于快速原型开发至关重要。
注意:ESP32-E有两个版本,区别在于引脚引出方式。推荐使用“Firebeetle ESP32-E IoT Microcontroller(V1.0)”版本,它的所有GPIO都以排针形式引出,更便于连接外设。
2.2 显示核心:屏幕的抉择
哔哔小子的灵魂在于那块绿色的屏幕。为了还原神韵,屏幕选择需要满足:
- 尺寸适中:1.3英寸到1.54英寸是手表比较合适的尺寸。
- 圆形或方形:更符合手表外观,方形屏显示信息面积更大。
- 驱动方式:SPI接口屏幕是首选,因为它占用引脚少(通常只需4-6个),驱动效率高,对ESP32的GPIO压力小。
- 色彩与风格:虽然哔哔小子是单色绿,但使用一块全彩屏(如ST7789驱动)可以通过软件模拟绿色调,未来还能切换其他主题(如琥珀色),可玩性更高。
我最终选择了一块1.54英寸的IPS TFT液晶屏,驱动芯片为ST7789,分辨率为240x240。理由如下:
- 240x240分辨率:在这个尺寸下像素密度足够,能显示比较细腻的字体和图标。
- IPS材质:可视角度极佳,无论从哪个方向看手表,内容都清晰可见。
- SPI接口:仅需占用ESP32的5个引脚(SCK, MOSI, DC, CS, RST),背光控制可以单独接一个PWM引脚实现亮度调节。
- 有现成的Arduino库:如
TFT_eSPI库,对ST7789支持非常好,性能优化到位。
2.3 能源与交互:电池、充电与外壳
- 电池:选择一款3.7V、300-500mAh的锂聚合物电池。容量太小续航不足,太大则增加体积和重量。电池需要配备标准的2.0mm间距PH2.0连接器,以便与ESP32-E的电池接口对接。厚度最好在3-4mm左右,便于放入表壳。
- 充电:ESP32-E板载了Micro USB接口用于编程和充电,非常方便。你可以直接通过这个口为手表充电,充电时板载的红色LED会亮起,充满熄灭。
- 交互与反馈:
- 按键:除了ESP32-E板载的一个用户按键(GPIO0),我额外添加了两个轻触开关,作为“选择”和“返回”键。这三个按键构成了最基础的人机交互。
- 震动马达:一个小型的扁平震动马达(约3mm厚)是必不可少的。它用于提供通知、闹钟和界面交互的触觉反馈。通过一个NPN三极管或MOSFET(如2N2222或SI2302)由ESP32的GPIO控制其开关。
- 外壳:这是让项目从“开发板堆叠”变成“手表”的关键。我使用Fusion 360进行3D建模设计。设计要点包括:
- 紧凑堆叠:将ESP32-E主板、屏幕、电池、震动马达在三维空间内合理布局,计算好所有连接线和排线的走线空间。
- 人体工学:表壳背面需要有一定弧度以贴合手腕,表耳用于安装表带(20mm通用生耳)。
- 按键开孔:为三个按键预留孔位,并设计合适的按键帽,保证手感。
- 屏幕保护:设计一个窗口,并考虑是否增加一层亚克力面板或钢化玻璃膜来保护屏幕。
- 充电口开孔:为Micro USB口开孔,方便充电而不必打开后盖。
我将设计好的模型用光固化3D打印机(如Anycubic Photon)打印出来,以获得更精细的表面纹理和更准确的孔位。后期经过打磨、喷漆(哑光黑或军绿色),质感就上来了。
3. 软件架构与核心功能实现
硬件搭好了,接下来就是注入灵魂的软件部分。整个软件系统围绕“低功耗”和“流畅的复古UI”两个核心目标构建。
3.1 开发环境与核心库搭建
我使用Arduino IDE进行开发,因为它对ESP32和各类显示库的支持非常成熟。
必须安装和配置的库:
- ESP32 Board Support:在Arduino IDE的 boards manager 中添加ESP32支持。
- TFT_eSPI:这是驱动屏幕的核心库。需要手动配置其用户设置文件(
User_Setup.h)。- 打开
TFT_eSPI库文件夹下的User_Setup.h。 - 根据你的屏幕型号,注释掉其他驱动,取消注释
ST7789_DRIVER。 - 正确设置
TFT_WIDTH,TFT_HEIGHT, 以及你连接ESP32的引脚定义,例如:#define TFT_CS 15 // Chip select control pin #define TFT_DC 2 // Data Command control pin #define TFT_RST 4 // Reset pin (could connect to ESP32 RST) #define TFT_MOSI 23 // Data out #define TFT_SCLK 18 // Clock out - 设置
SPI_FREQUENCY为40000000(40MHz)以获得更快的刷新速度。
- 打开
- Button2:一个优秀的按键库,支持单击、双击、长按等事件检测,能简化我们的按键逻辑。
- WiFi & NTPClient:用于时间同步(可选,开启时会增加功耗)。
- Preferences:ESP32的非易失性存储库,用于保存手表设置(如亮度、主题色等)。
3.2 低功耗策略设计
对于手表,不能让ESP32一直全速运行。我的策略是:
状态机设计:将手表运行状态分为几个等级:
- 活跃状态:屏幕亮起,处理器全速运行,响应所有操作。一段时间无操作后(如30秒),进入休眠状态。
- 休眠状态:关闭屏幕背光,CPU降频或进入Light Sleep模式,但保持对按键中断的监听。此时功耗在几毫安级别。
- 深度睡眠状态:在休眠状态持续一段时间后,或由用户主动触发,进入Deep Sleep。此时仅RTC模块和少数GPIO(配置为EXT0唤醒)保持极低功耗运行,电流降至10μA级。通过按键(连接至EN引脚或配置为EXT0唤醒的GPIO)可以唤醒系统。
中断唤醒:将三个按键都连接到支持外部唤醒的GPIO(如GPIO0, GPIO33等)。在深度睡眠前,配置其中某个按键(如主页键)为唤醒源。当按下该键时,ESP32会从深度睡眠中复位重启,程序从头开始执行,并在初始化后立即显示主界面,体验上就像“点亮屏幕”。
外设电源管理:在代码中,进入休眠前,主动将屏幕的背光引脚拉低,并将屏幕置于睡眠模式。对于震动马达,确保其控制引脚处于确定的高阻态或低电平,防止误触发耗电。
3.3 哔哔小子UI绘制与动画
这是最有趣也最具挑战的部分。我们需要用代码“画”出那个熟悉的界面。
字体与图形:
- 哔哔小子使用的是等宽字体。我找到了一个名为“Fixedsys”风格的像素字体,将其转换为
.vlw格式(Arduino位图字体),并使用TFT_eSPI的setFreeFont函数加载。对于小字号,也可以使用库内置的等宽字体。 - 界面上的图标、边框、辐射标志等,可以先用电脑上的绘图工具(如Aseprite)绘制成单色或有限色的位图,然后使用
Img2Code之类的工具转换为C语言数组,直接嵌入代码中通过drawBitmap函数绘制。
- 哔哔小子使用的是等宽字体。我找到了一个名为“Fixedsys”风格的像素字体,将其转换为
界面框架:
- 设计一个简单的UI框架,管理不同的“页面”(App),如主时钟页面、健康数据页面、设置页面等。
- 每个页面是一个函数或一个类,负责绘制自己的静态元素和更新动态数据。
- 使用一个状态变量记录当前页面索引,按键操作用于切换页面。
动态效果与“绿噪点”:
- 页面切换动画:可以模拟CRT的扫描线效果或简单的滑入滑出。在
TFT_eSPI中,可以通过分步重绘矩形区域来实现。 - 绿噪点滤镜:为了更逼真,可以在绘制完一帧后,在整个屏幕随机绘制一些半透明的绿色或黑色像素点,模拟CRT的噪点。但要注意控制密度和频率,避免影响主要信息阅读。
- 滚动文字:在信息栏模拟缓慢的横向滚动文字,这是哔哔小子的经典元素。可以通过计算文字位置并定期重绘来实现。
- 页面切换动画:可以模拟CRT的扫描线效果或简单的滑入滑出。在
代码结构示例(主循环):
#include <TFT_eSPI.h> #include <Button2.h> TFT_eSPI tft = TFT_eSPI(); Button2 btnHome(0); // GPIO0 enum AppState { CLOCK, STATUS, SETTINGS }; AppState currentApp = CLOCK; unsigned long lastActiveTime = 0; const unsigned long SLEEP_TIMEOUT = 30000; // 30秒无操作休眠 void drawClockApp() { // 绘制时钟界面:绿色背景,复古边框,大号时间字体,小号日期 tft.fillScreen(TFT_GREEN); // ... 绘制边框和装饰 ... // ... 获取并显示时间 ... } void drawStatusApp() { // 绘制状态界面:模拟健康数据(可连接心率传感器) tft.fillScreen(TFT_BLACK); // ... 绘制状态栏和虚拟数据 ... } void setup() { Serial.begin(115200); tft.init(); tft.setRotation(1); // 根据手表佩戴方向调整 tft.fillScreen(TFT_BLACK); btnHome.setPressedHandler([](Button2 & b) { lastActiveTime = millis(); // 重置活动计时器 wakeFromSleep(); // 唤醒函数(点亮背光等) // 短按切换App,长按进入深度睡眠 }); // 初始化其他外设... lastActiveTime = millis(); } void loop() { btnHome.loop(); // 必须持续调用以检测按键 // 根据当前状态更新对应App的UI switch (currentApp) { case CLOCK: updateClock(); break; case STATUS: updateStatus(); break; // ... } // 检查休眠超时 if (millis() - lastActiveTime > SLEEP_TIMEOUT) { enterLightSleep(); } // 添加少量延时,控制刷新率,节省功耗 delay(50); }
4. 功能扩展与传感器集成
基础的时间显示和UI复刻完成后,可以为其添加一些“实用”功能,让它更像一个真正的个人终端。
4.1 环境传感器:感知废土(现实世界)
- 温湿度计:使用一颗DHT11或更精确的SHT30传感器,可以测量环境温湿度。在状态页面上显示这些信息,甚至可以根据温度给出“辐射区高温警告”之类的趣味提示。
- 气压计/高度计:BMP280或BME280传感器可以测量气压和估算海拔。虽然精度有限,但为手表增加了一层环境感知维度。
- 连接方式:这些传感器大多支持I2C接口。ESP32-E有多个GPIO支持I2C,你可以将它们连接到同一组I2C总线(GPIO21-SDA, GPIO22-SCL)上,通过不同的设备地址来访问。
4.2 生物传感器:监测“幸存者”状态
- 心率血氧:MAX30102是一款集成了心率与血氧检测的传感器模块。虽然其算法相对复杂,且对佩戴位置要求较高,但集成后可以为“健康”页面提供真实的生物数据,极大提升项目的科技感和可玩性。你需要处理其产生的原始光电容积脉搏波(PPG)数据,或使用现成的算法库来估算心率和血氧饱和度。
- 计步器:ESP32本身内置了加速度计?不,它没有。你需要外接一个MPU6050或更便宜的LIS3DH三轴加速度计。通过分析加速度数据,可以实现基本的步数统计功能。这需要编写或移植一些计步算法(如峰值检测)。
4.3 无线功能与数据同步
- 蓝牙通知:让手表通过蓝牙低功耗(BLE)连接到你的手机,接收来电、短信或App通知。这需要编写一个BLE客户端角色(Peripheral)的程序,并开发一个手机端App(或利用现成的开源项目如
BLE Notify)来转发通知。当收到通知时,手表屏幕点亮、震动,并显示通知摘要。 - Wi-Fi时间同步:在手表连接充电器或手动触发时,可以开启Wi-Fi,连接到预设网络,使用NTP协议从互联网同步精确时间。同步完成后立即关闭Wi-Fi以省电。
实操心得:功能扩展要循序渐进。建议先完成核心的显示和基础交互,稳定后再逐一添加传感器。每添加一个外设,都要重新评估功耗和代码复杂度。I2C总线上的设备多了,要注意上拉电阻的阻值(通常4.7kΩ),并处理好地址冲突。生物传感器的数据滤波和算法是难点,初期可以先用厂家提供的示例代码获取数据,显示出来就算成功,后续再优化准确性。
5. 电源管理、续航测试与优化实战
“能不能用一整天?”这是可穿戴设备的终极拷问。我们需要对整机功耗进行量化分析和优化。
5.1 各状态功耗测量
你需要一个万用表(电流档)或更专业的功耗分析仪。将设备串联在电池供电回路中,测量不同工作状态下的平均电流。
| 工作状态 | 屏幕背光 | ESP32模式 | 外设状态 | 预估平均电流 | 续航估算 (400mAh) |
|---|---|---|---|---|---|
| 深度睡眠 | 关闭 | Deep Sleep | 仅唤醒电路 | ~10 μA | 约4.5年(理论值) |
| 休眠 (屏熄) | 关闭 | Light Sleep / CPU降频 | 按键监听开启 | ~2 mA | 约8天 |
| 活跃 (低亮度) | 50% PWM | 全速 (80MHz) | 传感器采样,UI刷新 | ~60 mA | 约6.5小时 |
| 活跃 (高亮度) | 100% | 全速 (240MHz) | BLE广播,频繁刷新 | ~120 mA+ | 约3小时 |
表1:基于典型配置的功耗估算(实际值因具体元件和代码效率而异)
5.2 关键优化措施
根据上表,提升续航的核心在于尽可能让设备处于深度睡眠或低功耗休眠状态。
屏幕背光是耗电大户:这是最有效的优化点。
- 将背光控制引脚连接到ESP32的PWM输出引脚(如GPIO12)。
- 在代码中设置一个较低的默认亮度(例如30%),并提供设置菜单让用户调整。
- 实现自动亮度调节:虽然成本略高,但可以增加一个环境光传感器(如BH1750),根据环境光线自动调整背光PWM值,在暗处大幅降低亮度。
优化CPU工作模式:
- 在活跃状态下,如果不进行复杂计算(如动画渲染),可以将CPU频率从240MHz降至80MHz(使用
setCpuFrequencyMhz(80)),能显著降低功耗。 - 在
loop()循环中,如果没有紧急任务,使用delay()或vTaskDelay()让CPU有机会进入IDLE状态,而不是忙等待。
- 在活跃状态下,如果不进行复杂计算(如动画渲染),可以将CPU频率从240MHz降至80MHz(使用
外设精细化管理:
- 传感器:不要持续采样。例如,温度每分钟读取一次即可。在两次读取之间,通过GPIO控制其电源或将其置于睡眠模式。
- 震动马达:震动时间要短(通常100-200ms足矣),驱动后立即关闭控制引脚。
- 无线模块:Wi-Fi和蓝牙在不需要时彻底关闭。连接成功后立即断开并关闭射频。避免让设备处于持续的广播或扫描状态。
软件逻辑优化:
- 减少屏幕刷新:只刷新需要变化的区域(局部刷新),而不是整个屏幕(全屏刷新)。
TFT_eSPI库的setAddrWindow和pushColor函数支持局部更新。 - 使用更高效的图形绘制:预计算并存储常用的界面元素,避免在循环中重复计算坐标和颜色。
- 减少屏幕刷新:只刷新需要变化的区域(局部刷新),而不是整个屏幕(全屏刷新)。
5.3 续航实测与调整
搭建一个初步的原型后,进行24小时续航测试。记录设备在不同使用强度下的实际耗电量。根据测试结果,回头调整你的休眠超时时间、默认亮度、传感器采样频率等参数。一个理想的目标是:在每天查看时间、通知数十次的中度使用下,能够坚持2-3天以上的续航,这会让佩戴体验好很多。
6. 外壳制作、组装与最终调试
当所有代码功能稳定,功耗也达到满意水平后,就可以进行最终组装了。
6.1 3D打印与后处理
- 打印参数:使用光固化树脂打印,层高选择0.05mm或0.03mm以获得光滑表面。表壳通常需要拆分打印(前盖、后盖),注意设计好卡扣或螺丝柱。
- 清洗与固化:打印完成后,用酒精彻底清洗模型,去除残留树脂,然后放入UV固化箱进行二次固化,使其达到最大强度。
- 打磨与上色:
- 用细砂纸(如800目、1000目)打磨掉支撑残留和层纹。
- 喷涂水补土,检查并填补可能存在的瑕疵。
- 选择哑光黑、军绿色或做旧的银色进行喷涂。可以尝试渍洗或干扫等模型技法,做出废土风格的磨损效果。
- 最后喷涂一层哑光保护漆,增加耐用性。
6.2 内部组装“排雷”
这是最容易出问题的环节,需要耐心和细心。
焊接与连线:
- 使用细的硅胶线或FFC排线进行内部连接,它们更柔软,便于在狭小空间内弯曲。
- 为所有连接点点上热熔胶或使用Kapton胶带固定,防止在佩戴过程中因晃动导致脱焊或短路。
- 特别注意:电池的正负极绝对不能短路!在焊接电池线时,最后连接正极,或者使用带保护板的电池。
堆叠顺序:
- 通常的顺序是:后盖 -> 电池(粘贴在后盖内) -> 主板(用尼龙柱垫高) -> 屏幕(通过排线连接) -> 前盖。
- 在主板和电池、屏幕之间,可以贴一层绝缘胶带或麦拉片,防止刮擦短路。
- 确保所有排线都有足够的弯曲空间,不会被外壳挤压。
按键与密封:
- 轻触开关的按键帽需要精心调整长度,确保能从外壳内部正好顶到外壳的按键孔,手感清脆。
- 如果希望有一定的防水防尘能力,可以在屏幕与前盖、前后盖接合处使用薄泡棉胶条或橡胶密封圈。充电口可以使用硅胶防尘塞。
6.3 最终上电与综合测试
组装完成后,不要急于拧紧所有螺丝。先连接电池,进行上电测试:
- 基本功能测试:按键能否正常唤醒、操作?屏幕显示是否正常?背光能否调节?
- 充电测试:连接USB线,检查充电指示灯是否正常,测量电池电压是否在上升。
- 功耗复测:再次测量组装后的整机在睡眠和活跃状态下的电流,与之前裸板测试对比,看是否有异常漏电。
- 压力测试:连续运行几个小时,频繁操作,观察是否有死机、重启、发热异常等情况。
- 佩戴体验:装上表带,实际佩戴,感受重量、厚度是否合适,表冠是否硌手。
一切正常后,就可以拧紧螺丝,你的专属废土风格哔哔小子手表就正式诞生了。这个项目从构思到实现,充满了硬件选型的权衡、代码调试的枯燥和最终点亮屏幕那一刻的喜悦。它不仅仅是一个手表,更是一个承载着游戏情怀和个人技术探索的实体作品。当你抬起手腕,看到那抹熟悉的绿色荧光亮起,耳边仿佛响起了避难所熟悉的广播声,那一刻,所有的努力都值了。