💡 划重点

  • 本项目面向上班族早晨出门前的公交查询场景,用 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 分钟,摸查稳压芯片温度,调整屏幕亮度或刷新间隔。

设计图

公交到站桌面播报器结构flow
1
感知输入:按键唤醒、定时刷新、Wi-Fi状态
→
2
控制处理:ESP32联网请求与到站解析
→
3
执行反馈:ILI9341显示、蜂鸣器短鸣
→
4
数据上报:HTTP查询、串口日志与重试