基于ESP32的WiFi智能时钟 - 从硬件连接到全栈UI渲染的实践
一、背景与动机
前段时间在整理桌面时,发现缺一个既能看时间又能看天气的小屏幕。市面上的桌面时钟要么功能简单、要么价格不菲,而且都不太"个人化"——我想要的是能显示农历日期、能看到夜空背景、还能根据网络自动校时的设备。
手上正好有一块吃灰已久的 ESP32 开发板和一个 1.8 寸 ST7735S TFT 屏幕,于是决定自己动手做一个 WiFi 智能时钟。其实类似的教程网上也不少,但大部分只做到"显示时间"就停了。我想把这个项目做得更完整一些:网络校时、天气信息、农历日期、甚至是全屏程序化渲染的夜空场景。这篇文章把整个实践过程记录下来,希望对也想做个桌面时钟的朋友有用。
二、核心设计思路
1. 硬件选型与连接
硬件组合很简单:ESP32 DOIT DevKit V1 做主控,搭配 1.8 寸 ST7735S TFT 屏幕(128×160 像素,SPI 接口,RGB565 色彩)。ST7735S 是这类小尺寸屏幕里非常常见的驱动芯片,价格便宜、文档齐全。
SPI 接线方式如下,我这里用了几根杜邦线直连:
| 信号 | GPIO | 说明 |
|---|---|---|
| MOSI | 23 | SPI 主出从入 |
| CLK | 18 | SPI 时钟 |
| CS | 5 | 片选 |
| DC | 21 | 数据/命令选择 |
| RST | 2 | 复位 |
| BCKL | 4 | 背光控制 |
2. 软件架构
整体采用 ESP-IDF 框架,通过 PlatformIO 管理。图形引擎选择了 U8g2 v2.35.19——这是一款非常成熟单色图形库,支持丰富的字体。虽然 ST7735S 是彩色屏,但我采用了一个巧妙的方案:U8g2 按位图模式驱动,然后逐行混合到程序化生成的夜空背景之上,实现白色 UI 叠加的效果。
项目按功能拆分为以下模块:
lcd_st7735— 屏幕底层驱动:SPI 通信、初始化序列、场景渲染(渐变天空 + 星星 + 月亮 + 山脉)wifi_service— WiFi 连接管理 + SNTP 网络授时weather_client— 天气 HTTP 客户端(心知天气 API)main.c— 主循环:UI 布局绘制、农历转换(查表法覆盖 1900-2100 年)
三、实战步骤
1. 搭建开发环境
项目使用 PlatformIO,在 VS Code 中安装 PlatformIO 插件后即可。platformio.ini 配置如下:
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = espidf
monitor_speed = 115200添加 U8g2 库依赖:
lib_deps =
olikraus/U8g2@^2.35.192. WiFi 连接与网络授时
时钟的核心需求之一是时间准确。我用了 SNTP(Simple Network Time Protocol)从 pool.ntp.org 获取 UTC 时间,再转换为北京时间(CST-8)。
WiFi 连接这块我做了一些工程化的处理:支持自动重连(最多 10 次)、断开原因诊断(通过 wifi_err_reason 判断是密码错误还是信号丢失),未联网时屏幕底部显示连接状态提示,方便调试。
配置集中在 wifi_config.h 中:
#define APP_WIFI_ENABLE 1
#define APP_WIFI_SSID "your_ssid"
#define APP_WIFI_PASSWORD "your_password"
#define APP_WIFI_MAXIMUM_RETRY 10
#define APP_SNTP_SERVER "pool.ntp.org"
#define APP_TIMEZONE "CST-8"3. 天气数据获取
天气信息来自心知天气 API。这块的设计要点是容错:成功获取后 10 分钟轮询一次,如果请求失败则 30 秒快速重试,避免长时间无天气显示。
#define APP_WEATHER_API_KEY "your_api_key"
#define APP_WEATHER_LANGUAGE "zh-Hans"
#define APP_WEATHER_LOCATION "shenzhen"
#define APP_WEATHER_UNIT "c"免费 API Key 在心知天气官网注册即可获取,对个人项目来说完全够用。
4. 农历日期转换
这个功能是我个人比较坚持的——作为中国人,看农历日期比纯粹的公历更有生活感。农历转换采用查表法,一张预计算的农历数据表覆盖了 1900 到 2100 年,支持干支纪年、闰月识别。
5. 程序化夜空场景渲染
这是最有意思的一部分。我没有用外部图片,而是用代码在屏幕上一笔一笔绘制夜空背景:
- 三段渐变天空:从顶部藏青色过渡到暗紫色、再到底部的暖褐色
- 星星点:用确定性哈希算法散布,确保每次开机星星位置固定
- 暖色月亮:绘制在右上角,给画面一个视觉焦点
- 山脉剪影:底部三重山峰,白色文字和图标叠加在场景之上
U8g2 本身是单色库,但通过像素级位图混合到 RGB 背景上,实现了"白色 UI + 彩色背景"的效果。这套渲染逻辑都封装在 lcd_st7735.c 中,屏幕布局大致如下:
┌──────────────────────────────────┐
│ ⭐ 月 │ ← 夜空渐变背景
│ ShenZhen │
│ [🌡] 26°C [☁️] │ ← 天气面板(白框透明底)
│ ───────────────────────────── │
│ 正月十五 │ ← 农历日期
│ 2025-06-14 Sun │ ← 公历日期+星期
│ ───────────────────────────── │
│ 12:34:56 │ ← 时间(粗体大字)
│ ⛰️ ⛰️ ⛰️ │ ← 山脉剪影
└──────────────────────────────────┘6. 编译与烧录
一切就绪后,一条命令即可编译烧录:
# 编译
pio run
# 烧录到开发板
pio run --target upload
# 查看串口日志
pio device monitor首次上电后,ESP32 会自动连接 WiFi,同步 NTP 时间,获取天气数据,然后进入时钟主循环。
四、踩坑记录
这个项目虽然看起来不复杂,但实际调试过程中还是踩了几个坑:
1. ST7735S 初始化序列:市面上 ST7735S 的初始化命令序列五花八门,不同厂商的屏幕参数有细微差异。我试了好几个版本的初始化序列才找到当前这块屏幕能正确显示色彩的配置。建议到手后先跑一个纯色填充测试,确认 RGB 顺序和偏移参数。
2. U8g2 与彩色背景的混合:U8g2 本质是单色库,要叠加到渐变天空背景上需要逐像素处理。最初我直接在背景上画白色文字,发现部分像素被背景色覆盖导致文字不清晰。后来改用先截取 U8g2 缓冲区,再逐行混合到场景缓冲区的方案,文字边缘才干净起来。
3. 农历数据表精度:农历查表法虽然速度快,但闰月判断的逻辑需要仔细核对。我对照了几年的在线农历数据人工验证,发现有一条闰月标志位的判断写反了,修正后才通过验证。
4. 心知天气 API 免费额度:免费版 API 有调用次数限制,如果刷新太频繁会超出配额。最终采用成功 10 分钟刷新、失败 30 秒重试的策略,既保证了数据及时性又不会浪费配额。
五、总结
这个项目从构思到跑通,前前后后大概花了两个周末的时间。最大的收获是重新熟悉了 ESP-IDF 的底层开发流程——从 SPI 驱动到 WiFi 管理,从 HTTP 客户端到 SNTP 校时,几乎是嵌入式开发的全栈体验。
程序化生成夜空背景是我最满意的一环。虽然用代码画出来的场景不如真实照片或外部图片精细,但这种"零依赖"的方式让整个项目干净利落——不需要 SD 卡、不需要文件系统,编译烧录就能跑。如果你也想做一个桌面时钟,又不想局限于"数字+纯色背景"的 boring 方案,可以试试这种思路。
效果在这里展示:
