💡 划重点
- 本项目面向上班族早晨出门前的公交查询场景,用 ESP32 主控搭配 ILI9341 显示屏,做一个放在玄关或桌面的公交到站播报器。设备平时低亮显示最近一班公交的预计到站时间,按键唤醒后立即刷新;联网层通过 HTTP API 获取线路到站数据,失败自动重试并记录串口日志。整机目标成本控制在 75 元内,接线简单,适合作为入门级联网硬件项目。
需求洞察
目标用户是每天固定线路通勤的上班族,尤其是住处离公交站 3 到 8 分钟步行距离的人群。真实痛点不是“查不到公交”,而是“临出门时不想掏手机、解锁、打开 App、输入线路、看刷新”。手机查询步骤多,早晨手上可能拿着早餐、钥匙和包,容易错过一班车;一直盯着手机又浪费时间。桌面播报器把关键信息固定在眼前:还有几分钟、是否该立刻出门。它值得做,因为需求高频、信息单一、硬件成本低,且 ESP32 联网和屏幕显示技术成熟,失败时也能降级为普通时钟或提示器。
方案设计
感知层由按键和定时器组成。按键接 GPIO0,内部上拉,按下拉低,用于唤醒刷新;定时器每 60 秒自动刷新一次,避免屏幕长期显示过期数据。控制层使用 ESP32 完成 Wi-Fi 连接、HTTP 请求、字符串解析和显示刷新。执行层为 ILI9341 屏幕,显示线路号、最近一班到站分钟数、刷新状态;可选有源蜂鸣器在到站小于 3 分钟时短鸣提醒。日志与联网层负责请求公交 API、处理超时和异常、通过串口输出时间戳和错误码,便于调试。供电采用 USB 5V,整机可放在桌面或粘在玄关墙面。
物料清单
| 物料 | 数量 | 用途 | 估算单价 | 合计 |
|---|---|---|---|---|
| ESP32 开发板 | 1 | 主控、Wi-Fi、HTTP 请求 | 18 元 | 18 元 |
| ILI9341 2.8 寸 SPI 屏 | 1 | 显示到站信息 | 28 元 | 28 元 |
| 轻触按键模块 | 1 | 按键唤醒与手动刷新 | 1 元 | 1 元 |
| 有源蜂鸣器 | 1 | 临近到站提示 | 2 元 | 2 元 |
| 杜邦线加面包板 | 1 套 | 接线与原型搭建 | 6 元 | 6 元 |
| USB 供电线 | 1 | 5V 供电 | 5 元 | 5 元 |
| 外壳或亚克力板 | 1 | 桌面支架与保护 | 8 元 | 8 元 |
| 10k 电阻 | 2 | 按键上拉备用、蜂鸣器限流 | 1 元 | 2 元 |
| 合计 | 70 元 |
接线与结构
屏幕采用 SPI 接线:SCK 接 GPIO18,MOSI 接 GPIO23,CS 接 GPIO15,DC 接 GPIO2,RST 接 GPIO4,LED 和 VCC 接 3.3V,GND 共地。给定接线中没有复位脚,这里微调增加 RST=GPIO4,理由是让程序可以主动复位屏幕,减少上电花屏;若屏幕模块复位脚接 3.3V 也可不改代码。按键一端接 GPIO0,另一端接 GND,使用内部上拉。GPIO0 参与启动模式,烧录和上电时应松开按键,启动完成后再按。蜂鸣器正极接 GPIO25,负极经 1k 电阻接地,避免电流过大。结构上把屏幕装入简易亚克力支架,ESP32 固定在背板,按键放在顶部,USB 线从后方引出。
核心代码
#include <WiFi.h>
#include <HTTPClient.h>
#include <TFT_eSPI.h>
TFT_eSPI tft;
const char* SSID = "你的WiFi";
const char* PASS = "你的密码";
const char* API = "http://api.example.com/bus?line=88";
unsigned long lastRefresh = 0;
void showMsg(String line1, String line2, uint16_t color) {
tft.fillScreen(TFT_BLACK);
tft.setTextSize(3);
tft.setCursor(10, 30);
tft.println(line1);
tft.setTextSize(2);
tft.setCursor(10, 100);
tft.println(line2);
}
void refreshBus() {
if (WiFi.status() != WL_CONNECTED) {
return;
}
HTTPClient http;
http.begin(API);
http.setTimeout(5000);
int code = http.GET();
Serial.printf("HTTP %d\n", code);
if (code == 200) {
String payload = http.getString();
int idx = payload.indexOf("\"next_min\":");
if (idx >= 0) {
int start = idx + 11;
int end = payload.indexOf(',', start);
if (end < 0) end = payload.indexOf('}', start);
String minStr = payload.substring(start, end);
minStr.trim();
showMsg("LINE 88", minStr + " MIN", TFT_GREEN);
if (minStr.toInt() <= 3) {
digitalWrite(25, HIGH);
delay(200);
digitalWrite(25, LOW);
}
} else {
}
} else {
}
http.end();
}
void setup() {
Serial.begin(115200);
pinMode(0, INPUT_PULLUP);
pinMode(25, OUTPUT);
digitalWrite(25, LOW);
tft.init();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
WiFi.begin(SSID, PASS);
unsigned long t0 = millis();
while (WiFi.status() != WL_CONNECTED && millis() - t0 < 15000) {
delay(300);
}
refreshBus();
lastRefresh = millis();
}
void loop() {
if (digitalRead(0) == LOW) {
delay(30);
if (digitalRead(0) == LOW) {
Serial.println("Key wake");
refreshBus();
lastRefresh = millis();
while (digitalRead(0) == LOW) delay(10);
}
}
if (millis() - lastRefresh > 60000UL) {
refreshBus();
lastRefresh = millis();
}
delay(100);
}
调试步骤
第一步只接屏幕,烧录点亮测试,确认背光、方向和颜色正常;若白屏,检查 CS、DC、RST 和 SPI 引脚配置。第二步接按键,串口打印按下事件,确认 GPIO0 上电时不被按住。第三步连接 Wi-Fi,观察串口是否获得 IP;失败时检查热点频段和密码。第四步用浏览器或串口请求 API,确认返回字段与代码中的 next_min 一致。第五步整机联调,按按键刷新,拔掉路由器模拟断网,确认屏幕显示错误状态且程序不卡死。最后连续运行 30 分钟,摸查稳压芯片温度,调整屏幕亮度或刷新间隔。