💡 划重点

  • 主控:ESP32,双核带 WiFi,适合 HTTP 拉取与屏幕刷新。
  • 显示:2.4 寸 ILI9341 SPI 屏,常亮显示最近一班公交到站分钟。
  • 交互:GPIO0 按键唤醒,短按刷新,长按切换线路。
  • 联网:HTTP API 获取实时公交数据,JSON 解析后本地显示。
  • 提醒:无源蜂鸣器在到站前 3 分钟短鸣,避免错过。
  • 成本:整套物料控制在 70 元以内,适合上班族桌面使用。

需求洞察

目标用户是每天固定线路通勤的上班族。真实痛点不是“查不到公交”,而是“查公交这件事太麻烦”:早上出门前手里拿着早餐、背包、工牌,还要掏手机、解锁、打开 App、等广告、输入线路。冬天戴手套更不方便。很多时候只想知道“下一班还有几分钟”,却要付出十几秒操作成本。

另一个痛点是手机屏幕小、信息干扰多。放在桌面上的专用小屏,可以一直显示线路和到站时间,余光一瞥就能决定是现在出门还是再等两分钟。按键唤醒则解决夜间屏幕刺眼和功耗问题:平时深度睡眠,按下才刷新。这个项目值得做,因为它把高频、低信息量的查询变成零操作,成本低,结构简单,还能按自己线路定制。

方案设计

感知层:GPIO0 轻触按键,内部上拉,按下拉低。短按唤醒并刷新,长按切换预设线路。后续可加光敏电阻自动调亮度。

控制层:ESP32 负责 WiFi 连接、HTTP 请求、JSON 解析、屏幕驱动和蜂鸣器控制。使用深度睡眠降低功耗,按键通过 EXT0 唤醒。

执行层:ILI9341 显示线路号、最近一班、第二班、当前时间、网络状态。无源蜂鸣器在预计到站小于 3 分钟时响一声,小于 1 分钟时连响两声。

日志/联网层:串口输出调试日志;HTTP 请求设置超时与重试;WiFi 断线自动重连;预留 MQTT 或 HTTP 上报接口,方便以后把到站记录存到服务器。

物料清单

物料 数量 用途 估算单价 合计
ESP32 DevKit V1 1 主控与联网 22 元 22 元
ILI9341 2.4 寸 SPI 屏 1 显示到站信息 28 元 28 元
轻触按键 1 唤醒与刷新 0.5 元 0.5 元
无源蜂鸣器 1 到站提示音 1 元 1 元
面包板 1 免焊接搭建 5 元 5 元
杜邦线 若干 连接模块 5 元 5 元
USB 电源线与适配器 1 供电 8 元 8 元
纸盒或 3D 打印外壳 1 桌面支架 0 元 0 元
合计 69.5 元

接线与结构

TFT 使用硬件 SPI:SCK=18,MOSI=23,CS=15,DC=2。RST 建议接 GPIO4,BL 接 3.3V 常亮,这样不占用额外 PWM 引脚。按键一端接 GPIO0,另一端接 GND,使用内部上拉。蜂鸣器正极接 GPIO5,负极接 GND。

注意 GPIO0 是启动模式引脚,上电时不要按住按键,否则可能进入下载模式。结构上把屏幕倾斜 15 度放入纸盒,按键放顶部,蜂鸣器开小孔。供电用 USB 适配器,放在桌面角落。

核心代码

以下为 Arduino 骨架,需在 TFT_eSPI 的 User_Setup.h 中配置 ILI9341 和上述引脚。


#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <TFT_eSPI.h>
#include <SPI.h>

#define BTN_PIN 0
#define BUZZ_PIN 5
#define TFT_RST 4

TFT_eSPI tft = TFT_eSPI();
const char* ssid = "你的WiFi";
const char* password = "你的密码";
const char* apiUrl = "http://api.example.com/bus?line=123&stop;=abc";

void showMessage(String line1, String line2, uint16_t color) {
  tft.fillScreen(TFT_BLACK);
  tft.setTextSize(2);
  tft.setCursor(10, 30);
  tft.println(line1);
  tft.setTextSize(3);
  tft.setCursor(10, 90);
  tft.println(line2);
}

bool fetchBus(int &min1;, int &min2;) {
  if (WiFi.status() != WL_CONNECTED) return false;
  HTTPClient http;
  http.begin(apiUrl);
  http.setTimeout(5000);
  int code = http.GET();
  if (code != 200) { http.end(); return false; }
  String payload = http.getString();
  StaticJsonDocument<512> doc;
  DeserializationError err = deserializeJson(doc, payload);
  if (err) { http.end(); return false; }
  min1 = doc["arrivals"][0]["minutes"] | -1;
  min2 = doc["arrivals"][1]["minutes"] | -1;
  http.end();
  return true;
}

void beep(int times) {
  for (int i = 0; i < times; i++) {
    tone(BUZZ_PIN, 2000, 120);
    delay(180);
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(BUZZ_PIN, OUTPUT);
  tft.init();
  tft.setRotation(1);
  showMessage("连接WiFi", "...", TFT_YELLOW);
  WiFi.begin(ssid, password);
  unsigned long start = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - start < 15000) delay(500);
  if (WiFi.status() == WL_CONNECTED) showMessage("已联网", "按按键刷新", TFT_GREEN);
  else showMessage("联网失败", "检查热点", TFT_RED);
}

void loop() {
  if (digitalRead(BTN_PIN) == LOW) {
    delay(50);
    if (digitalRead(BTN_PIN) == LOW) {
      showMessage("刷新中", "...", TFT_CYAN);
      int m1 = -1, m2 = -1;
      if (fetchBus(m1, m2)) {
        String a = String(m1) + " 分钟";
        String b = "下一班 " + String(m2) + " 分钟";
        showMessage(a, b, m1 <= 3 ? TFT_RED : TFT_GREEN);
        if (m1 <= 3 && m1 >= 0) beep(m1 <= 1 ? 2 : 1);
      } else {
        showMessage("获取失败", "再按重试", TFT_ORANGE);
      }
      while (digitalRead(BTN_PIN) == LOW) delay(10);
    }
  }
  delay(100);
}

调试步骤

  1. 先只接屏幕,烧录 TFT_eSPI 示例,确认引脚和旋转方向正确。
  2. 接按键,串口打印高低电平,确认按下为低,松开为高。
  3. 连接 WiFi,串口输出 IP 地址,确认热点兼容 2.4G。
  4. 用浏览器或串口调试 HTTP API,确认返回 JSON 字段名。
  5. 把真实字段填入 fetchBus,观察屏幕刷新和蜂鸣器。
  6. 加入深度睡眠与 EXT0 唤醒,测待机电流和唤醒成功率。
  7. 装入外壳,固定屏幕和按键,连续运行一天观察稳定性。

设计图

公交到站桌面播报器结构flow
1
感知输入
→
2
控制处理
→
3
执行反馈
→
4
数据上报