💡 划重点
- 用 ESP32 驱动 ILI9341 彩屏,通过 HTTP API 拉取公交实时到站信息。
- 按键唤醒 + 定时唤醒,平时深度睡眠,出门前按一下就能看最近两班车。
- 目标成本 75 元内,适合上班族放在玄关、办公桌或床头。
- 显示线路、站名、到站分钟、下一班和更新时间,首班车 3 分钟内蜂鸣提醒。
- 结构简单:屏幕 + 主控 + 按键 + 蜂鸣器,接线少,调试快。
需求洞察
目标用户是依赖公交通勤的上班族和学生。真实痛点很具体:早上出门前不知道车还有几分钟,频繁解锁手机查 App,容易分心;有时刚走到站台车刚走,有时等十几分钟不来。手机通知不可靠,智能音箱又太贵、太占地方。为什么值得做?因为公交实时数据已经公开,ESP32 联网成本很低,一块小屏就能把“查手机”变成“看一眼”。桌面播报器放在玄关,出门前按一下,屏幕直接显示最近两班车;放在办公桌,下班前按一下就知道要不要立刻走。它不替代手机,而是把关键信息从手机里“抠”出来,变成零操作、低干扰的桌面设备。
方案设计
感知层:GPIO0 按键唤醒,按下后从深度睡眠启动;同时支持 RTC 定时唤醒,每 60 秒自动刷新一次。Wi-Fi 联网后请求公交 HTTP API,获取线路、站名、到站分钟。
控制层:ESP32 解析 JSON,计算最近两班车,判断是否小于 3 分钟,决定是否蜂鸣。状态机管理“连接 Wi-Fi、请求 API、刷新屏幕、进入睡眠”四个状态,异常时显示错误码。
执行层:ILI9341 显示线路、站名、到站倒计时、下一班和更新时间;无源蜂鸣器在首班车小于 3 分钟时短鸣两声。深度睡眠时关闭背光,降低功耗。
日志/联网层:串口输出 Wi-Fi 状态、HTTP 状态码、JSON 解析结果;可选把刷新记录上报到 MQTT 或私有服务器,方便后续统计。整体用 3.3V 供电,USB 线接 5V 电源即可。
物料清单
| 物料 | 数量 | 用途 | 估算单价 |
|---|---|---|---|
| ESP32 开发板 | 1 | 主控、Wi-Fi、深度睡眠 | 22 元 |
| ILI9341 2.4 寸 SPI 屏 | 1 | 显示到站信息 | 28 元 |
| 轻触按键 | 1 | GPIO0 唤醒 | 0.5 元 |
| 无源蜂鸣器 | 1 | 到站提醒 | 1.5 元 |
| 杜邦线 | 20 根 | 接线 | 5 元 |
| 面包板 | 1 | 原型搭建 | 8 元 |
| USB 数据线 | 1 | 供电与烧录 | 5 元 |
| 外壳/亚克力 | 1 | 桌面摆放 | 5 元 |
| 合计 | 75 元 |
接线与结构
| 模块 | 引脚 | ESP32 GPIO | 说明 |
|---|---|---|---|
| ILI9341 | VCC | 3.3V | 供电 |
| ILI9341 | GND | GND | 共地 |
| ILI9341 | CS | 15 | 片选 |
| ILI9341 | RESET | 4 | 复位,微调新增 |
| ILI9341 | DC | 2 | 数据/命令 |
| ILI9341 | MOSI | 23 | SPI 数据 |
| ILI9341 | SCK | 18 | SPI 时钟 |
| ILI9341 | LED | 3.3V | 背光常亮 |
| 按键 | 一端 | GPIO0 | 另一端接 GND |
| 蜂鸣器 | 正极 | GPIO25 | 负极接 GND |
GPIO0 是 RTC GPIO,可用于深度睡眠唤醒,但上电时不要按住,否则会进入下载模式。RST 微调到 GPIO4,是因为原建议未给复位脚,单独占用一个普通 IO 更稳定。
核心代码
以下为 Arduino 骨架,需安装 TFT_eSPI、ArduinoJson、HTTPClient。API 字段请按实际返回修改。
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <TFT_eSPI.h>
#include <esp_sleep.h>
TFT_eSPI tft = TFT_eSPI();
const char* ssid = "YOUR_WIFI";
const char* password = "YOUR_PASS";
const char* apiUrl = "http://api.example.com/bus?station=xxx&line;=xxx";
#define BTN_PIN 0
#define BUZZER_PIN 25
void drawUI(String line, String station, int min1, int min2) {
tft.fillScreen(TFT_BLACK);
tft.setTextSize(2);
tft.setTextSize(1);
tft.setTextSize(3);
tft.setTextSize(2);
}
void fetchBus() {
if (WiFi.status() != WL_CONNECTED) return;
HTTPClient http;
http.begin(apiUrl);
int code = http.GET();
if (code == 200) {
String payload = http.getString();
StaticJsonDocument<1024> doc;
deserializeJson(doc, payload);
int min1 = doc["data"]["arrivals"][0]["minutes"];
int min2 = doc["data"]["arrivals"][1]["minutes"];
String line = doc["data"]["line"].as<String>();
String station = doc["data"]["station"].as<String>();
if (min1 <= 3) {
tone(BUZZER_PIN, 2500, 200);
}
}
http.end();
}
void setup() {
Serial.begin(115200);
pinMode(BUZZER_PIN, OUTPUT);
tft.init();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE);
tft.setTextSize(2);
WiFi.begin(ssid, password);
unsigned long t0 = millis();
while (WiFi.status() != WL_CONNECTED && millis() - t0 < 15000) delay(500);
if (WiFi.status() == WL_CONNECTED) fetchBus();
delay(30000);
esp_sleep_enable_timer_wakeup(60ULL * 1000000ULL);
esp_deep_sleep_start();
}
void loop() {}
调试步骤
- 屏幕测试:只接 ILI9341,运行 TFT_eSPI 示例,确认引脚、旋转方向和背光。
- Wi-Fi 测试:串口打印 IP,确认能连上热点。
- API 测试:浏览器打开接口,核对 JSON 字段,替换代码中的路径。
- 按键唤醒:测 GPIO0 按下拉低,深度睡眠后按一下能重新启动并刷新。
- 蜂鸣器:在首班车 3 分钟内触发,确认声音和时长合适。
- 整机联调:设置 60 秒定时唤醒,观察刷新是否稳定、屏幕是否闪屏。
- 封装:放入外壳,开屏幕窗和按键孔,背面留 USB 供电口。