基于ESP32-C3与GC9A01圆形屏的智能电子怀表DIY全攻略

发布时间:2026/7/28 5:40:31
基于ESP32-C3与GC9A01圆形屏的智能电子怀表DIY全攻略 1. 项目缘起为什么是“电子怀表”前阵子收拾屋子翻出来一块老旧的机械怀表黄铜外壳走时已经不准了。把玩的时候脑子里突然冒出一个念头这玩意儿要是能显示点别的东西比如天气、日程甚至能联网更新会不会更有意思这个想法成了我做 DigiPclock 的起点。我不想做一个简单的桌面电子钟而是想复刻怀表那种可以握在手里、随时掏出来看一眼的“把玩感”和“仪式感”同时赋予它现代电子产品的智能和可玩性。核心的硬件选择几乎没怎么纠结。主控方面ESP32-C3 是当下的性价比之王RISC-V 内核、Wi-Fi/蓝牙、低功耗该有的都有了价格还非常亲民完美契合这种需要联网和复杂显示的小玩意儿。屏幕则锁定了 GC9A01这是一块 1.28 英寸的圆形 TFT 屏分辨率 240x240像素密度足够高显示图标和文字都很细腻最关键的是它的圆形外观天生就是为了“怀表”形态准备的。外壳自然交给了 3D 打印可以自由设计结构、预留按键和充电口还能随时迭代修改。所以DigiPclock 本质上是一个基于 ESP32-C3 和圆形 GC9A01 屏幕通过 3D 打印外壳整合的具备网络对时、自定义表盘等功能的可穿戴式或桌面把玩式智能电子钟。它不像手表那样需要时刻佩戴更像一个数字时代的“小玩意儿”兼顾实用与极客的乐趣。2. 硬件选型与核心电路设计硬件是项目的骨架选型决定了项目的上限和实现的复杂度。这里我详细拆解一下每个核心部件的选择理由和电路设计中的关键点。2.1 主控芯片为什么是 ESP32-C3 而非 ESP8266 或 ESP32-S3ESP32-C3 在这个项目里几乎是唯一解。我们对比一下常见的几款 ESP 系列芯片ESP8266经典但外设和内存相对有限且是 Xtensa 架构在 Arduino 生态下的某些新库支持可能不如 RISC-V。ESP32双核性能强但功耗相对高且对于本项目来说性能过剩。ESP32-S3性能强劲有 USB OTG但成本较高。ESP32-C3单核 RISC-V主频 160MHz内置 400KB SRAM支持 Wi-Fi 4 和 Bluetooth 5 (LE)最重要的是它支持“Deep-sleep”和“Light-sleep”等低功耗模式在仅显示静态画面的怀表场景下可以大幅延长续航。其成本与 ESP8266 相当但架构更现代功耗控制更好。对于 DigiPclock我们不需要双核不需要高性能计算但需要稳定的 Wi-Fi 连接、足够的 RAM 来缓存图形数据、以及优秀的低功耗特性。ESP32-C3 恰到好处。我选用的是合宙的 ESP32-C3 核心板通常称为“小智”它集成了 CH343 串口芯片、复位和下载按键外围电路极其简洁几乎可以直接拿来用。2.2 显示核心GC9A01 圆形 TFT 屏的驱动要点GC9A01 是一颗驱动 240x240 RGB 屏幕的芯片。选择它纯粹是因为外形。市面上 1.28 英寸圆形屏的驱动芯片主要是 GC9A01 和 ST7789但 ST7789 的圆形屏通常有缺口Notch而 GC9A01 的这款可以实现真正的全圆显示视觉效果更完美。驱动 GC9A01 需要连接一组 SPI 接口。典型的接线方式如下SCLK (串行时钟)- ESP32-C3 的任意 GPIO (如 GPIO2)MOSI (主机输出)- ESP32-C3 的任意 GPIO (如 GPIO3)DC (数据/命令选择)- ESP32-C3 的任意 GPIO (如 GPIO10)。这个引脚至关重要它告诉屏幕当前发送的是命令还是图像数据。CS (片选)- ESP32-C3 的任意 GPIO (如 GPIO7)。如果只接一块屏可以接地GND使其一直有效。RST (复位)- ESP32-C3 的任意 GPIO (如 GPIO6)。或者可以直接接一个 RC 复位电路到 ESP32-C3 的 EN 引脚实现同步复位。BLK (背光控制)- ESP32-C3 的任意 GPIO (如 GPIO11)。通过 PWM 可以控制屏幕亮度。这里有一个非常重要的细节电源。GC9A01 屏幕和背光 LED 需要 3.3V 供电但背光 LED 的电流可能瞬间较大几十毫安。如果直接使用 ESP32-C3 开发板上的 3.3V 引脚可能会造成压降导致系统不稳定。稳妥的做法是使用一个独立的 3.3V LDO 稳压芯片如 AMS1117-3.3为屏幕供电输入接电池或 USB 的 5V输出接屏幕的 VCC。ESP32-C3 核心板则可以直接由 USB 的 5V 供电板载了稳压。这样实现了电源分离互不干扰。2.3 供电与续航锂电池管理电路设计既然是“怀表”移动使用是核心场景之一因此必须内置电池。我选择了一款常见的 500mAh 的 3.7V 锂聚合物电池。核心是充电管理芯片。我选用 TP4056这是一款单节锂电池线性充电管理 IC外围电路非常简单只需要几个电阻电容。它的典型应用电路如下将 USB 的 5V 接入 TP4056 的VIN引脚。将电池的正负极分别接到BAT和BAT-。CHRG和STDBY引脚可以接 LED 指示灯显示充电状态。TP4056 的BAT引脚输出就是稳定的电池电压约 3.7V-4.2V。但是TP4056 的输出BAT电压会随着电池放电从 4.2V 降到 3.0V 左右而 ESP32-C3 和屏幕需要稳定的 3.3V。因此我们需要一个升压/降压Buck-Boost稳压电路。我选择了 MT3608这是一款升压芯片或更合适的AP6320x 系列同步降压芯片但输入电压范围需覆盖电池电压范围。实际上更简单通用的方案是使用ME6211 等 LDO但 LDO 在电池电压低于 3.3V压差时无法工作。对于电池供电设备最佳选择是支持宽电压输入如 2.5V-5.5V的同步降压Buck稳压芯片如 SY8088、MP2315 等。它能将波动的电池电压高效、稳定地转换为 3.3V即使电池电压低至 3.0V也能输出稳定的 3.3V最大限度利用电池电量。最终的供电链路是USB 5V - TP4056 - 锂电池 - 同步降压稳压芯片 - 3.3V - ESP32-C3 屏幕。同时USB 5V 也应直接给 ESP32-C3 核心板的 VIN 供电以便在插入 USB 时系统由外部电源供电同时给电池充电。2.4 交互与结构按键与外壳设计怀表需要基本的交互。我设计了两个物理按键按键 A (GPIO9)短按切换显示模式时间/日期/天气等长按进入配网模式。按键 B (GPIO8)短按调整亮度或切换子页面长按开关屏幕背光实现“熄屏显示”。按键电路采用经典的上拉电阻设计。ESP32-C3 内部有可配置的上拉电阻但为了稳定性我仍然在外部接了 10K 电阻上拉到 3.3V按键另一端接地。当按键按下时GPIO 读到低电平。外壳使用 Fusion 360 进行 3D 建模。设计要点包括精确的配合公差屏幕开口与屏幕边缘的单边间隙建议留 0.1-0.2mm。ESP32-C3 核心板与外壳的固定柱采用热熔胶或螺丝固定需要留出相应的卡槽和螺丝柱。按键手感外壳上的按键孔要对准微动开关并且设计限位结构防止按键歪斜。可以在微动开关上套一小段硅胶管来改善手感。充电接口开孔为 Micro-USB 或 Type-C 接口预留足够大且位置精准的开口。散热与密封在非显示区域设计一些小透气孔避免设备发热时内部压力增大。如果考虑防水则需要设计密封圈槽。打印设置使用 PLA 材料层高 0.2mm填充率 15%-20% 即可保证强度。对于需要精细结构的按键部分可以尝试更低的层高如 0.15mm以提高精度。3. 软件框架与核心功能实现硬件搭好了软件才是灵魂。我的目标是让代码结构清晰易于扩展新的表盘或功能。3.1 开发环境搭建与库依赖我选择 Arduino IDE 进行开发主要是因为其库生态丰富对于 GC9A01 和网络功能的支持都很成熟。首先需要在 Arduino IDE 的“开发板管理器”中添加 ESP32 支持。URL 是https://espressif.github.io/arduino-esp32/package_esp32_index.json。然后安装“esp32”平台选择“ESP32C3 Dev Module”作为开发板。核心依赖的库有以下几个TFT_eSPI这是一个强大的 SPI 显示屏驱动库支持 GC9A01。需要手动配置库中的User_Setup.h文件。WiFiManager这个库太重要了。它允许设备在首次启动或配网模式下创建一个 Wi-Fi 热点用户用手机连接后可以在网页上选择要连接的 Wi-Fi 网络并输入密码。这避免了将 Wi-Fi 密码硬编码在代码里的不安全性。NTPClient用于从网络时间协议服务器获取精确的 UTC 时间。ArduinoJson用于解析从网络 API 获取的天气等信息。Timezone用于处理本地时区和夏令时转换。TFT_eSPI 的配置是关键一步。你需要找到 Arduino 库文件夹下的TFT_eSPI/User_Setup.h文件根据你的引脚连接进行修改。以下是我的配置片段注释掉其他无关的驱动// 注释掉其他所有驱动只启用 GC9A01 #define GC9A01_DRIVER // 定义屏幕尺寸 #define TFT_WIDTH 240 #define TFT_HEIGHT 240 // 定义 SPI 时钟频率GC9A01 可以支持较高频率 #define SPI_FREQUENCY 40000000 // 定义引脚根据你的实际连接修改 #define TFT_CS 7 // Chip select control pin #define TFT_DC 10 // Data Command control pin #define TFT_RST 6 // Reset pin (could connect to Arduino RESET pin) #define TFT_BL 11 // LED back-light control pin // 使用硬件 SPIMOSI 和 SCLK 引脚由 Arduino 核心定义通常不需要再定义3.2 主程序逻辑与状态机设计程序不能是简单的一条线loop()需要合理的管理状态。我采用了一个简单的“状态机”模型。// 定义全局状态变量 enum AppState { STATE_SHOW_CLOCK, // 显示时钟 STATE_SHOW_DATE, // 显示日期 STATE_SHOW_WEATHER, // 显示天气 STATE_WIFI_CONFIG // Wi-Fi 配置模式 }; AppState currentState STATE_SHOW_CLOCK; // 在 setup() 中初始化 void setup() { initHardware(); // 初始化屏幕、串口等 if (!loadWiFiConfig()) { // 尝试读取保存的Wi-Fi配置 currentState STATE_WIFI_CONFIG; } else { connectToWiFi(); initTimeClient(); // 初始化NTP } } // 主循环 void loop() { switch (currentState) { case STATE_SHOW_CLOCK: drawClockFace(); updateTimeDisplay(); checkButtonPress(); // 检查按键可能切换状态 break; case STATE_SHOW_DATE: drawDatePage(); checkButtonPress(); break; case STATE_SHOW_WEATHER: if (isTimeToUpdateWeather()) { fetchAndDrawWeather(); } checkButtonPress(); break; case STATE_WIFI_CONFIG: startWiFiManagerPortal(); // 启动配网网页 // 阻塞在这里直到配网成功 break; } handleLowPower(); // 根据状态决定是否进入睡眠 }这种结构清晰地将不同的显示模式隔离开每个状态只负责自己的绘制和更新逻辑。checkButtonPress()函数会根据按键动作改变currentState。3.3 网络对时与天气获取精准的时间是时钟的基础。使用NTPClient库非常简单。#include NTPClient.h #include WiFiUdp.h WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, pool.ntp.org, 8*3600, 60000); // 使用中国区NTP服务器东八区 void initTimeClient() { timeClient.begin(); timeClient.update(); // 首次更新 } void updateTimeDisplay() { if (millis() - lastTimeUpdate 60000) { // 每分钟同步一次 timeClient.update(); lastTimeUpdate millis(); } int hours timeClient.getHours(); int minutes timeClient.getMinutes(); // ... 绘制到屏幕 }注意pool.ntp.org是一个全球性的 NTP 服务器池在国内访问有时延迟较高或不稳定。建议替换为国内的 NTP 服务器如ntp.ntsc.ac.cn国家授时中心或cn.pool.ntp.org以获得更快的同步速度和稳定性。天气功能通过 HTTP 请求免费的天气 API 实现例如和风天气、OpenWeatherMap 等。这里以和风天气为例需要注册获取 Key。void fetchWeatherData() { if (WiFi.status() WL_CONNECTED) { HTTPClient http; String url https://devapi.qweather.com/v7/weather/now?locationYOUR_LOCATION_IDkeyYOUR_API_KEY; http.begin(url); int httpCode http.GET(); if (httpCode 200) { String payload http.getString(); DynamicJsonDocument doc(1024); deserializeJson(doc, payload); int temp doc[now][temp].asint(); String cond doc[now][text].asString(); // 更新全局天气变量 currentTemp temp; currentWeather cond; } http.end(); } }为了省电天气数据不需要像时间一样频繁更新可以每小时甚至每三小时更新一次。更新后将数据和时间戳保存到 ESP32-C3 的 Preferences非易失性存储中即使重启也能显示上一次获取的天气。3.4 低功耗策略与续航优化ESP32-C3 的低功耗模式是提升续航的关键。我们的使用场景是大部分时间显示静态或缓慢变化的画面如时钟只有按键或定时更新时才需要全速运行。屏幕背光控制这是耗电大户。通过 PWM 将背光亮度设置在可接受的最低水平比如 30%。在STATE_SHOW_CLOCK状态下检测到一段时间无操作例如 30 秒后自动关闭背光digitalWrite(TFT_BL, LOW)。按下任意按键再重新点亮。CPU 频率调节在不需要高性能时可以降低 CPU 频率。setCpuFrequencyMhz(80)可以将频率从 160MHz 降到 80MHz显著降低功耗。Wi-Fi 间歇性连接在不需要网络比如刚同步完时间、天气后可以断开 Wi-Fi 连接WiFi.disconnect(true)并关闭射频WiFi.mode(WIFI_OFF)。在下次需要同步前再重新连接。使用 Deep Sleep这是最省电的模式但在此项目中不太适用因为 Deep Sleep 下 RAM 内容会丢失唤醒后相当于重启无法保持显示状态。我们的屏幕本身没有记忆功能重启后需要重新初始化并绘制这个过程耗电且体验不连贯。Light Sleep 模式这是一个折中方案。在 Light Sleep 下CPU 暂停RAM 数据保留部分外设关闭。可以通过定时器Timer或外部引脚如按键唤醒。对于时钟我们可以让 ESP32-C3 在两次刷新显示之间比如每秒刷新一次那么在两次刷新间隔的 900ms 里进入 Light Sleep。这需要精细的中断和计时器编程。一个简单的 Light Sleep 实现思路#include driver/rtc_cntl.h void enterLightSleep(int ms) { esp_sleep_enable_timer_wakeup(ms * 1000); // 微秒为单位 esp_light_sleep_start(); } void loop() { updateDisplay(); // 处理其他任务... enterLightSleep(900); // 睡眠900毫秒 }实测下来结合背光控制和适度的 Light Sleep一个 500mAh 的电池在显示简单时钟、每小时同步一次时间、每三小时更新一次天气的场景下可以轻松实现 3-5 天的续航。如果完全关闭 Wi-Fi 仅作为离线时钟续航会更长。4. 3D 打印外壳的设计、打印与后期处理外壳是将所有电子部件整合成一个完整产品的关键也是最体现“怀表”质感的部分。4.1 建模细节与装配考量使用 Fusion 360 建模时我遵循了“自上而下”的设计方法。先创建一个代表内部可用空间的“主板”组件然后所有其他零件前壳、后盖、固定柱都参考这个主板的大小和关键定位点如螺丝孔、屏幕开口进行设计。前壳屏幕开口尺寸要比 GC9A01 的显示区域稍大但比整个屏幕模块的外径小这样才能卡住屏幕。我留了 0.15mm 的单边过盈配合利用 PLA 材料微小的弹性将屏幕“压”进去非常牢固无需胶水。按键孔孔径要比微动开关的按键柱直径大 0.5mm 左右确保不会卡住。在内部设计一圈凸台限制按键柱的行程防止按得太深。充电口开孔使用 Fusion 360 的“投影”功能将 Type-C 接口的精确轮廓投影到外壳曲面上再切割这样开孔非常精准。后盖电池仓根据选定的 500mAh 电池尺寸设计一个带卡扣的仓室。仓室底部预留走线槽让电池导线可以连接到主板。散热孔在主板 MCU 和稳压芯片对应的位置设计一些装饰性的网格状小孔帮助空气流通。磁吸或卡扣我选择了简单的螺丝固定四颗 M2 螺丝因为更可靠。如果想做得更精致可以设计隐藏式磁吸结构但需要精确计算磁铁的位置和吸力。内部固定结构屏幕固定除了前壳的过盈配合还在屏幕 PCB 的四个角对应位置设计了 1mm 高的支撑柱防止屏幕 PCB 向后弯曲。主板固定设计四个带 M2 螺丝孔的立柱位置与 ESP32-C3 核心板的安装孔对齐。立柱高度要计算好确保屏幕 FPC 排线有足够的弯曲空间不会过度弯折。按键固定设计一个小的“按键板”将两个微动开关焊接在一小块万用板上然后这个按键板通过卡槽或螺丝固定在外壳内部确保按键位置精准。4.2 切片设置与打印实战模型设计好后导出为 STL 文件用 PrusaSlicer 或 Cura 进行切片。层高为了获得较好的表面质量我使用 0.16mm 的层高。对于内部结构和不重要的面可以切换到 0.2mm 以加快打印速度。填充密度15% 的网格填充对于这种小物件强度完全足够。可以在螺丝孔、卡扣等受力部位局部增加填充或设置更多外围圈数。支撑前壳的屏幕开口是悬空结构必须生成支撑。我使用“树状支撑”因为它更容易拆除且对模型表面的损伤较小。支撑与模型的接触面距离Z距离可以设置为 0.2mm这样既好拆又不留太多痕迹。打印方向前壳应该以后盖接触打印床的方式放置。这样屏幕开口的悬空面朝上由支撑材料填充而外壳的外表面接触床的一面会非常光滑平整。后盖则相反将外侧接触床保证外观面的质量。材料我使用普通的 PLA 材料它比标准 PLA 韧性更好打印的卡扣不易断裂。打印温度设为 210°C热床 60°C。打印完成后需要小心地移除支撑。使用平口钳和镊子一点点剥离树状支撑。对于残留在屏幕开口内侧的支撑碎屑可以用小锉刀或精细的砂纸600目以上轻轻打磨。4.3 打磨、上色与表面处理原始的 3D 打印件会有层纹为了获得怀表般的质感表面处理必不可少。打磨这是最耗时但效果最显著的步骤。从低目数如 240目砂纸开始打磨掉明显的层纹和支撑残留。然后逐步提高到 400目、800目、甚至 1200目。关键技巧是“湿磨”边蘸水边打磨可以防止砂纸被 PLA 碎屑堵塞打磨面也更光滑。对于曲面和细节处可以使用海绵砂纸。补土对于仍然可见的层纹或小坑洞可以使用模型专用的水补土喷罐。薄薄地喷几层每层干透后用细砂纸1000目以上轻轻打磨。这能填平细微的瑕疵形成一个均匀的底漆层。上色底漆使用灰色或白色水补土作为底漆它能增强面漆的附着力。面漆为了模拟黄铜怀表的感觉我选择了“匠域”的金属底漆然后喷涂“酋长大陆”的哑光透明保护漆。如果想要更丰富的色彩可以使用 AV 或酋长大陆的模型漆进行笔涂或喷涂。一定要薄涂多层每层干透后再上下一层避免笔痕或流漆。旧化可选用深棕色的渍洗液如田宫 XF-1 黑色渍洗液稀释涂抹在凹槽、铆钉等细节处待半干时用棉签蘸取溶剂如 ZIP 油擦掉凸起部分的渍洗液留下凹槽的阴影增加立体感和陈旧感。最终保护喷涂一层消光或半光透明保护漆保护漆面并统一光泽度。经过这些处理一个充满质感的“电子怀表”外壳就完成了。将电路总成小心地装入拧紧螺丝整个项目便从一堆散件变成了一个可以握在手中的精致作品。