ESP32 2.8黄屏源码解说
这不是只讲效果的页面,而是按源码顺序讲:为什么要写这些代码,每一段负责什么,烧录进去以后 ESP32 怎么变成一个联网触摸时钟。
这不是只讲效果的页面,而是按源码顺序讲:为什么要写这些代码,每一段负责什么,烧录进去以后 ESP32 怎么变成一个联网触摸时钟。
这个程序的意义是:把 ESP32 从一块开发板,做成一个可以触摸、可以联网、可以网页配置、可以读 BMI160 姿态的小屏产品。
真正烧录的源码文件是:
Wire 用来读 BMI160 这种 I2C 传感器。
WiFi 和 WebServer 负责联网和手机网页设置。
HTTPClient 负责去网上拿天气、空气数据。
TFT_eSPI 和 LVGL 负责屏幕绘图和页面 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>
BMI160 用 I2C,当前接线是 SCL IO22、SDA 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
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;
程序先找 BMI160 的地址,常见是 0x68 或 0x69。
找到以后给 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;
}
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'>";
}
如果用户选择“网上数据”,并且 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();
}
点 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;
}
}
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();
}
这一版 ESP32 二点八寸黄屏,不只是显示时间。我在代码里先把屏幕、触摸、WiFi、网页、天气、空气、待办、番茄计时和 BMI160 都拆成不同模块。
开机以后,setup 负责初始化硬件:打开屏幕、启动触摸、启动 LVGL、读取保存过的 WiFi 和城市配置,再扫描 I2C 看 BMI160 有没有接好。
进入 loop 后,ESP32 会一直工作:有人打开网页就处理网页,有人点屏幕就处理触摸,WiFi 连上以后就更新时间、天气和空气数据,BMI160 每隔一小段时间读取一次 X、Y、Z 姿态。
所以这个项目的核心,不是一张漂亮图片,而是把一个小屏做成真正能用的小产品。后面继续优化中文字体、横竖屏触摸坐标和计时页面,就可以越来越接近成品。
下面是用原始 1080p 视频做的实拍版。下一版我会把上面的源码解说配到视频里。