ESP32小屏幕玩出花:用TFT_eSPI库在1.3寸ST7789上显示动态天气和传感器数据
ESP32小屏幕玩出花:用TFT_eSPI库在1.3寸ST7789上显示动态天气和传感器数据
当一块1.3寸的ST7789屏幕遇上ESP32,能碰撞出怎样的火花?这不仅仅是点亮几个像素点那么简单。想象一下,你的桌面上摆放着一个精致的小设备,实时显示着室内温湿度、室外天气状况,甚至还有动态的温度计效果——这就是我们今天要实现的创意项目。
对于已经熟悉ESP32基础开发的创客来说,如何将硬件能力转化为实际应用才是真正的挑战。本文将带你从零开始,构建一个完整的动态信息仪表盘系统。这个系统不仅会读取本地传感器数据,还能通过WiFi获取网络天气信息,最终在一块240×240像素的小屏幕上呈现专业级的可视化效果。
1. 硬件准备与环境搭建
1.1 所需硬件清单
要完成这个项目,你需要准备以下硬件组件:
- ESP32开发板 :推荐使用ESP32-WROOM-32,它具备足够的处理能力和内存
- 1.3寸ST7789 SPI屏幕 :240×240分辨率,带IPS面板的版本显示效果更佳
- 温湿度传感器 :DHT22(AM2302)精度更高,DHT11更经济实惠
- 面包板与连接线 :用于快速原型搭建
- Micro USB数据线 :用于供电和程序烧录
硬件连接时,特别注意SPI接口的引脚分配。以下是推荐接线方式:
| 屏幕引脚 | ESP32引脚 | 备注 |
|---|---|---|
| VCC | 3.3V | 电源正极 |
| GND | GND | 电源地 |
| SCL | GPIO18 | SPI时钟线 |
| SDA | GPIO23 | SPI数据线 |
| RES | GPIO17 | 复位引脚(低电平有效) |
| DC | GPIO16 | 数据/命令选择引脚 |
| CS | GPIO5 | 片选引脚 |
1.2 软件环境配置
开发环境我们选择PlatformIO + VSCode组合,它比传统的Arduino IDE更适合管理复杂项目。
首先安装必要的库:
# 在PlatformIO项目的lib_deps中添加以下库
lib_deps =
bodmer/TFT_eSPI@^2.4.79
adafruit/DHT sensor library@^1.4.4
bblanchon/ArduinoJson@^6.19.4
TFT_eSPI库需要正确配置才能驱动ST7789屏幕。找到库目录下的 User_Setup.h 文件,进行如下修改:
#define ST7789_DRIVER // 指定驱动芯片类型
#define TFT_WIDTH 240 // 屏幕宽度
#define TFT_HEIGHT 240 // 屏幕高度
#define TFT_MOSI 23 // SPI数据引脚
#define TFT_SCLK 18 // SPI时钟引脚
#define TFT_CS 5 // 片选引脚
#define TFT_DC 16 // 数据/命令选择引脚
#define TFT_RST 17 // 复位引脚
#define LOAD_GLCD // 启用默认字体
#define SPI_FREQUENCY 40000000 // SPI通信频率
提示:如果屏幕显示颜色异常,尝试调整
TFT_RGB_ORDER定义,ST7789通常需要设置为TFT_RGB_ORDER TFT_RGB
2. 传感器数据采集与网络连接
2.1 读取DHT传感器数据
DHT系列传感器虽然使用简单,但在实际应用中需要注意读取时机和错误处理。以下是经过优化的读取代码:
#include <DHT.h>
#define DHTPIN 4 // 传感器数据引脚
#define DHTTYPE DHT22 // DHT 22 (AM2302)
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(115200);
dht.begin();
}
float readTemperature() {
float t = dht.readTemperature();
if (isnan(t)) {
Serial.println("Failed to read temperature!");
return -999.0; // 特殊值表示读取失败
}
return t;
}
float readHumidity() {
float h = dht.readHumidity();
if (isnan(h)) {
Serial.println("Failed to read humidity!");
return -999.0;
}
return h;
}
2.2 连接WiFi并获取天气数据
要让ESP32接入网络获取天气信息,我们需要完成WiFi连接和HTTP请求处理。这里使用免费的OpenWeatherMap API:
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
const char* ssid = "your_SSID";
const char* password = "your_PASSWORD";
const String apiKey = "your_API_KEY";
const String city = "Beijing";
void connectWiFi() {
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nConnected!");
} else {
Serial.println("\nConnection failed!");
}
}
String getWeatherData() {
if (WiFi.status() != WL_CONNECTED) return "";
HTTPClient http;
String url = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=" + apiKey + "&units=metric";
http.begin(url);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
http.end();
return payload;
} else {
http.end();
return "";
}
}
void parseWeatherData(String jsonStr, float &temp, float &humidity, String &conditions) {
DynamicJsonDocument doc(1024);
deserializeJson(doc, jsonStr);
temp = doc["main"]["temp"];
humidity = doc["main"]["humidity"];
conditions = doc["weather"][0]["main"].as<String>();
}
注意:实际项目中应该将API密钥等敏感信息存储在单独的配置文件中,不要硬编码在代码里
3. 屏幕UI设计与动态效果实现
3.1 基础UI框架搭建
在240×240的小屏幕上设计UI需要精打细算每个像素。我们先规划几个显示区域:
- 顶部区域 (高度40px):显示城市名称和当前天气状况
- 中间左侧 (120×160):室外温度显示与趋势图
- 中间右侧 (120×160):室内传感器数据显示
- 底部区域 (高度40px):状态栏(WiFi信号、更新时间)
创建基础绘制函数:
#include <TFT_eSPI.h>
TFT_eSPI tft;
void initDisplay() {
tft.init();
tft.setRotation(1); // 根据屏幕实际方向调整
tft.fillScreen(TFT_BLACK);
}
void drawHeader(String city, String weather) {
tft.fillRect(0, 0, 240, 40, TFT_NAVY);
tft.setTextColor(TFT_WHITE, TFT_NAVY);
tft.setTextSize(2);
tft.drawString(city, 10, 10);
tft.drawString(weather, 150, 10);
}
void drawFooter(bool wifiStatus) {
tft.fillRect(0, 200, 240, 40, TFT_DARKGREY);
// 绘制WiFi状态图标
if (wifiStatus) {
tft.fillCircle(220, 220, 8, TFT_GREEN);
} else {
tft.fillCircle(220, 220, 8, TFT_RED);
}
}
3.2 温度计动态效果
静态显示温度值不够直观,我们实现一个动态的温度计柱状图:
void drawThermometer(float temp, float minTemp, float maxTemp, int x, int y) {
// 温度计边框
tft.drawRoundRect(x, y, 40, 120, 10, TFT_WHITE);
tft.fillRoundRect(x+15, y+120, 10, 20, 5, TFT_WHITE);
// 计算温度柱高度 (0-100范围)
int tempHeight = map(constrain(temp, minTemp, maxTemp), minTemp, maxTemp, 0, 100);
// 使用渐变色 - 低温蓝色到高温红色
uint16_t color;
if (temp < 0) {
color = TFT_BLUE;
} else if (temp < 15) {
color = tft.color565(0, 0, 255 - (temp * 17));
} else if (temp < 30) {
color = tft.color565((temp-15)*17, 0, 255 - (temp-15)*17);
} else {
color = tft.color565(255, (temp-30)*8, 0);
}
// 绘制温度柱
tft.fillRoundRect(x+5, y+110-tempHeight, 30, tempHeight, 5, color);
// 显示温度值
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextSize(1);
tft.drawNumber((int)temp, x+50, y+50);
tft.drawString("C", x+70, y+50);
}
3.3 数据刷新策略
频繁刷新整个屏幕会导致闪烁,我们需要智能更新策略:
unsigned long lastUpdate = 0;
float lastOutTemp = -100;
float lastInTemp = -100;
void updateDisplay(float outTemp, float inTemp, float humidity, String conditions, bool wifiStatus) {
unsigned long now = millis();
// 每5秒全量刷新一次
if (now - lastUpdate > 5000) {
tft.fillScreen(TFT_BLACK);
drawHeader("Beijing", conditions);
drawFooter(wifiStatus);
lastUpdate = now;
}
// 温度变化超过0.5度才更新
if (abs(outTemp - lastOutTemp) > 0.5) {
drawThermometer(outTemp, -10, 40, 30, 50);
lastOutTemp = outTemp;
}
if (abs(inTemp - lastInTemp) > 0.5) {
drawThermometer(inTemp, 10, 40, 150, 50);
lastInTemp = inTemp;
}
// 湿度显示
static int lastHumidity = -1;
if ((int)humidity != lastHumidity) {
tft.fillRect(150, 180, 80, 20, TFT_BLACK);
tft.setTextColor(TFT_CYAN, TFT_BLACK);
tft.setTextSize(2);
tft.drawString("Hum: " + String((int)humidity) + "%", 150, 180);
lastHumidity = (int)humidity;
}
}
4. 系统整合与优化
4.1 主程序逻辑
将所有功能模块整合到一个协调的工作流程中:
void setup() {
Serial.begin(115200);
initDisplay();
dht.begin();
connectWiFi();
// 初始显示
tft.fillScreen(TFT_BLACK);
drawHeader("Loading...", "");
drawFooter(false);
}
void loop() {
// 读取传感器数据
float inTemp = readTemperature();
float inHumidity = readHumidity();
// 获取天气数据
String weatherJson = getWeatherData();
float outTemp = 0, outHumidity = 0;
String conditions = "";
if (weatherJson != "") {
parseWeatherData(weatherJson, outTemp, outHumidity, conditions);
}
// 更新显示
updateDisplay(outTemp, inTemp, inHumidity, conditions, WiFi.status() == WL_CONNECTED);
// 节能延迟
delay(2000);
}
4.2 性能优化技巧
经过实际测试,我们发现以下几个优化点能显著提升用户体验:
- SPI优化 :将SPI频率提升到最大稳定值(ST7789通常支持40MHz)
- 局部刷新 :只更新变化的部分区域,减少闪烁
- 内存管理 :
- 使用
PROGMEM存储静态资源 - 预渲染复杂图形到内存缓冲区
- 使用
- 电源管理 :
- 在不活跃时降低屏幕亮度
- 使用ESP32的深度睡眠模式
// 示例:使用双缓冲减少闪烁
TFT_eSprite spr = TFT_eSprite(&tft);
void setup() {
// ...其他初始化...
spr.createSprite(240, 240); // 创建与屏幕同尺寸的缓冲区
}
void drawToBuffer() {
spr.fillScreen(TFT_BLACK);
// 所有绘制操作改为对spr进行...
spr.pushSprite(0, 0); // 一次性输出到屏幕
}
4.3 扩展思路
这个基础项目可以进一步扩展为:
- 多数据源整合 :加入空气质量指数、天气预报等
- 交互功能 :通过按钮切换显示模式
- 云端同步 :将传感器数据上传到物联网平台
- 低功耗设计 :使用电池供电,优化续航
在实现这些扩展功能时,建议采用模块化设计,每个功能独立成单独的代码文件,通过清晰的接口进行交互。例如,可以创建 WeatherService 类处理所有天气相关逻辑, DisplayManager 类管理屏幕输出等。
更多推荐

所有评论(0)