都会明武电子 LOGO 都会明武电子电子模块 · 技术支持

ESP32 2.8黄屏源码解说

这不是只讲效果的页面,而是按源码顺序讲:为什么要写这些代码,每一段负责什么,烧录进去以后 ESP32 怎么变成一个联网触摸时钟。

main.ino 屏幕 UI 触摸 WiFi 配网 天气空气 番茄计时 BMI160

先说结论

这个程序的意义是:把 ESP32 从一块开发板,做成一个可以触摸、可以联网、可以网页配置、可以读 BMI160 姿态的小屏产品。

真正烧录的源码文件是:

资料包内:AI开发记录 / src / main.ino

1. 引用库:先把工具准备好

这段代码的意思

Wire 用来读 BMI160 这种 I2C 传感器。

WiFiWebServer 负责联网和手机网页设置。

HTTPClient 负责去网上拿天气、空气数据。

TFT_eSPILVGL 负责屏幕绘图和页面 UI。

#include <Wire.h>
#include <WiFi.h>
#include <WebServer.h>
#include <HTTPClient.h>
#include <Preferences.h>
#include <TFT_eSPI.h>
#include <lvgl.h>
#include <time.h>

2. 硬件引脚:告诉程序线接在哪里

这段代码的意思

BMI160 用 I2C,当前接线是 SCL IO22SDA IO27

触摸芯片单独走一组 SPI 引脚,程序要知道触摸的 CLK、CS、DIN、DO、IRQ 分别在哪。

屏幕尺寸固定为 240×320,后面横竖屏切换就是围绕这个尺寸转换。

#define I2C_SCL_PIN 22
#define I2C_SDA_PIN 27
#define I2C_CLOCK_HZ 100000

#define BMI160_ADDR_PRIMARY 0x68
#define BMI160_ADDR_ALT     0x69
#define BMI160_CHIP_ID      0xD1

#define TOUCH_CLK  25
#define TOUCH_CS   33
#define TOUCH_DIN  32
#define TOUCH_DO   39
#define TOUCH_IRQ  36

#define LCD_W 240
#define LCD_H 320

3. 页面和计时状态:先把功能分区

这段代码的意思

Page 是页面列表。比如主页、菜单、计时、待办、天气、黄历、BMI、I2C。

TimerState 是计时器状态。计时器不是只有一个数字,还要知道当前是未开始、运行中、暂停还是完成。

selectedMinutes 是当前选中的分钟数,默认 30 分钟。

enum Page {
  PAGE_CLOCK,
  PAGE_MENU,
  PAGE_POMODORO,
  PAGE_TODO,
  PAGE_WEATHER,
  PAGE_HUANGLI,
  PAGE_BMI,
  PAGE_I2C,
  PAGE_COUNT
};

enum TimerState {
  STATE_SELECT,
  STATE_RUNNING,
  STATE_PAUSED,
  STATE_DONE
};

static Page currentPage = PAGE_CLOCK;
static TimerState timerState = STATE_SELECT;
static uint16_t selectedMinutes = 30;

4. BMI160:先识别,再读数

这段代码的意思

程序先找 BMI160 的地址,常见是 0x680x69

找到以后给 BMI160 发命令:复位、打开加速度计、设置量程。

读取时从寄存器 0x12 开始读 6 个字节,换算成 X、Y、Z 三个方向的数据。

static bool initBMI160() {
  if (!detectBMI160Address(&bmi160Addr)) return false;
  i2cWriteByte(bmi160Addr, 0x7E, 0xB6);
  delay(100);
  if (!i2cWriteByte(bmi160Addr, 0x7E, 0x11)) return false;
  delay(80);
  i2cWriteByte(bmi160Addr, 0x40, 0x28);
  i2cWriteByte(bmi160Addr, 0x41, 0x03);
  return true;
}

static bool readBMI160() {
  uint8_t raw[6];
  if (!i2cReadBytes(bmi160Addr, 0x12, raw, 6)) return false;
  int16_t ax = (int16_t)((raw[1] << 8) | raw[0]);
  int16_t ay = (int16_t)((raw[3] << 8) | raw[2]);
  int16_t az = (int16_t)((raw[5] << 8) | raw[4]);
  axG = ax / 16384.0f;
  ayG = ay / 16384.0f;
  azG = az / 16384.0f;
  return true;
}

5. WiFi 和网页:让客户自己配置

这段代码的意思

ESP32 会开一个热点,名字是 ESP32-CLOCK。手机连接后打开 192.168.4.1

网页里可以填本地 WiFi、城市、经纬度、计时分钟、待办事项。

保存后,ESP32 用这些资料连接客户家里的 WiFi。

static void handleRoot() {
  refreshI2CStatus();
  if (WiFi.status() == WL_CONNECTED) {
    if (!weatherOk) fetchWeather();
    if (!airOk) fetchAirQuality();
  }

  html += "<h1>ESP32效率时钟</h1>";
  html += "<p>WiFi:<b>ESP32-CLOCK</b><br>";
  html += "密码:<b>12345678</b><br>";
  html += "地址:<b>192.168.4.1</b></p>";
  html += "<label>本地WiFi名称</label>";
  html += "<input name='ssid' type='text'>";
  html += "<label>本地WiFi密码</label>";
  html += "<input name='pass' type='text'>";
}

6. 天气和空气:联网后去网上取

这段代码的意思

如果用户选择“网上数据”,并且 WiFi 已连接,ESP32 就访问天气接口。

接口返回 JSON,程序从里面找温度、湿度、天气代码,再显示到屏幕上。

如果 WiFi 没连上,就显示等待或未获取。

static void fetchWeather() {
  if (!useNetEnv()) {
    weatherOk = false;
    return;
  }
  if (WiFi.status() != WL_CONNECTED) {
    weatherOk = false;
    return;
  }
  HTTPClient http;
  String url = "http://api.open-meteo.com/v1/forecast?latitude=";
  url += String(cityLat, 4);
  url += "&longitude=";
  url += String(cityLon, 4);
  url += "¤t=temperature_2m,relative_humidity_2m,weather_code&timezone=auto";
  http.begin(url);
  int code = http.GET();
}

7. 番茄计时:点按钮改变状态

这段代码的意思

点 10、25、30 时,程序调用 setDuration 改倒计时时长。

点开始后状态变成运行中,loop 里每次都会扣时间。

点暂停就停住,点停止就把剩余时间清零并结束。

static bool handlePomodoroTap(int16_t x, int16_t y) {
  int16_t sx = touchScreenX(x, y);
  int16_t sy = touchScreenY(x, y);

  if (timerState != STATE_RUNNING && sy >= 166 && sy <= 226) {
    if (sx >= 12 && sx < 82) setDuration(10);
    else if (sx >= 84 && sx < 156) setDuration(25);
    else if (sx >= 158 && sx <= 232) setDuration(30);
    timerState = STATE_SELECT;
    renderPage();
    return true;
  }

  if (sy >= 230 && sy <= 292) {
    if (sx >= 12 && sx < 82) timerState = STATE_RUNNING;
    else if (sx >= 84 && sx < 156) timerState = STATE_PAUSED;
    else if (sx >= 158 && sx <= 232) timerState = STATE_DONE;
    renderPage();
    return true;
  }
}

8. setup 和 loop:ESP32 每天都在跑这两个

这段代码的意思

setup 只在开机时跑一次:初始化串口、屏幕、触摸、LVGL、配置、I2C、WiFi。

loop 会一直循环:处理网页请求、处理触摸、更新时间、更新天气、读取 BMI160、刷新计时器和屏幕。

所以这个程序不是一次画完就结束,而是一直在后台检查状态。

void setup() {
  Serial.begin(115200);
  tft.begin();
  tft.setRotation(2);
  touchInit();
  lv_init();
  loadConfig();
  refreshI2CStatus();
  wifiInit();
  renderPage();
}

void loop() {
  webServer.handleClient();
  handleTouch();
  updateBeijingTimeConfig();
  autoLocateByIp();
  fetchWeather();
  fetchAirQuality();
  readBMI160();
  updateTimer();
  lv_timer_handler();
}

9. B站口播稿:源码解说版

这一版 ESP32 二点八寸黄屏,不只是显示时间。我在代码里先把屏幕、触摸、WiFi、网页、天气、空气、待办、番茄计时和 BMI160 都拆成不同模块。

开机以后,setup 负责初始化硬件:打开屏幕、启动触摸、启动 LVGL、读取保存过的 WiFi 和城市配置,再扫描 I2C 看 BMI160 有没有接好。

进入 loop 后,ESP32 会一直工作:有人打开网页就处理网页,有人点屏幕就处理触摸,WiFi 连上以后就更新时间、天气和空气数据,BMI160 每隔一小段时间读取一次 X、Y、Z 姿态。

所以这个项目的核心,不是一张漂亮图片,而是把一个小屏做成真正能用的小产品。后面继续优化中文字体、横竖屏触摸坐标和计时页面,就可以越来越接近成品。

10. 实拍视频

下载网页压缩版视频返回显示资料分类

下面是用原始 1080p 视频做的实拍版。下一版我会把上面的源码解说配到视频里。