为你的ESP32项目加个‘小眼睛’:用1.3寸ST7789屏做个迷你天气站(TFT_eSPI显示图文实战)
在物联网和智能硬件项目中,可视化交互往往能大幅提升用户体验。ESP32作为一款功能强大的微控制器,搭配1.3寸ST7789 SPI屏幕,可以创造出各种有趣的显示应用。本文将带你从零开始,打造一个能联网获取并显示天气信息的迷你天气站,重点解决图形化界面设计中的实际问题。
1. 项目规划与硬件准备
1.1 核心组件选型
这个天气站项目的核心在于将数据获取与显示完美结合。我们需要以下硬件:
- 主控芯片:ESP32-WROOM-32D(内置Wi-Fi功能)
- 显示屏:1.3寸ST7789驱动的240×240分辨率SPI屏幕
- 传感器:可选BME280(温湿度、气压)作为本地数据补充
硬件连接时需特别注意SPI引脚配置。ST7789屏幕通常需要6根连线:
| 屏幕引脚 | ESP32引脚 | 备注 |
|---|---|---|
| GND | GND | 接地 |
| VCC | 3.3V | 电源 |
| SCL | GPIO18 | SPI时钟 |
| SDA | GPIO23 | SPI数据 |
| RES | GPIO17 | 复位信号 |
| DC | GPIO16 | 数据/命令选择 |
| BLK | GPIO4 | 背光控制 |
1.2 开发环境搭建
推荐使用PlatformIO作为开发环境,它比Arduino IDE更适合管理依赖项。创建新项目时,需要添加以下库依赖:
lib_deps = bodmer/TFT_eSPI@^2.4.79 bblanchon/ArduinoJson@^6.19.4TFT_eSPI库的配置是关键步骤。在项目目录中找到lib/TFT_eSPI/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 -1 // 未使用片选 #define TFT_DC 16 // 数据/命令选择引脚 #define TFT_RST 17 // 复位引脚 #define LOAD_GLCD // 启用默认字体2. TFT_eSPI图形编程基础
2.1 屏幕初始化与基本绘图
屏幕初始化是第一步。创建一个全局TFT对象并进行初始化:
#include <TFT_eSPI.h> TFT_eSPI tft = TFT_eSPI(); void setup() { tft.init(); tft.setRotation(1); // 根据安装方向调整 tft.fillScreen(TFT_BLACK); }绘制基础天气元素时,可以组合使用多种图形函数:
// 绘制温度计图标 void drawThermometer(int x, int y) { tft.fillRoundRect(x, y, 20, 60, 10, TFT_RED); // 温度计主体 tft.fillCircle(x+10, y+65, 8, TFT_RED); // 底部圆形 tft.fillRect(x+8, y+5, 4, 50, TFT_WHITE); // 内部白色区域 } // 绘制动态温度条 void drawTempBar(float temp, int x, int y) { int height = map(temp, -10, 40, 0, 50); // 将温度映射为高度 tft.fillRect(x+8, y+5+(50-height), 4, height, TFT_RED); }2.2 文本显示优化技巧
小屏幕上文字显示需要特别注意可读性。TFT_eSPI支持多种字体,但默认字体较小。我们可以使用内置的大字体或加载自定义字体:
// 使用内置字体 tft.setTextSize(2); // 2倍放大默认字体 tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.drawString("25.5C", 50, 30); // 使用自定义字体(需先加载) tft.loadFont(NotoSansBold20); tft.drawString("晴天", 100, 80); tft.unloadFont(); // 释放字体内存提示:频繁加载/卸载字体会影响性能,建议在setup()中加载常用字体,程序结束时卸载。
3. 天气数据获取与处理
3.1 选择合适的天气API
市面上有多种免费天气API可供选择,如OpenWeatherMap、和风天气等。以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"; void fetchWeatherData() { HTTPClient http; String url = "http://api.openweathermap.org/data/2.5/weather?q=Beijing&units=metric&appid=" + apiKey; http.begin(url); int httpCode = http.GET(); if (httpCode == HTTP_CODE_OK) { String payload = http.getString(); parseWeatherData(payload); } http.end(); }3.2 JSON数据解析
使用ArduinoJson库解析返回的天气数据:
void parseWeatherData(String jsonStr) { DynamicJsonDocument doc(1024); deserializeJson(doc, jsonStr); float temp = doc["main"]["temp"]; int humidity = doc["main"]["humidity"]; const char* weather = doc["weather"][0]["main"]; updateDisplay(temp, humidity, weather); }4. 界面设计与动态刷新
4.1 布局规划
240×240的小屏幕需要精心设计布局。一个实用的天气站界面可以包含:
- 顶部区域:城市名称和当前时间
- 中部左侧:天气图标和温度
- 中部右侧:湿度和其他指标
- 底部区域:预报或额外信息
使用以下代码绘制基本布局框架:
void drawLayout() { // 顶部标题栏 tft.fillRect(0, 0, 240, 30, TFT_NAVY); tft.setTextColor(TFT_WHITE, TFT_NAVY); tft.drawString("北京天气", 10, 8); // 分割线 tft.drawFastHLine(0, 120, 240, TFT_WHITE); // 温度显示区域 drawThermometer(20, 40); // 湿度显示区域 tft.fillCircle(180, 80, 30, TFT_BLUE); }4.2 动态刷新策略
为了减少屏幕闪烁,可以采用局部刷新技术:
void updateTemperature(float newTemp) { static float lastTemp = -100; // 初始值 if (abs(newTemp - lastTemp) > 0.5) { // 温度变化超过0.5度才更新 // 清除旧温度显示区域 tft.fillRect(50, 50, 100, 30, TFT_BLACK); // 绘制新温度 tft.setTextColor(TFT_YELLOW, TFT_BLACK); tft.drawString(String(newTemp, 1) + "°C", 50, 50); // 更新温度条 drawTempBar(newTemp, 20, 40); lastTemp = newTemp; } }4.3 天气图标实现
可以使用两种方式显示天气图标:
- 矢量图形绘制:适合简单图标
void drawSunIcon(int x, int y) { tft.fillCircle(x+15, y+15, 10, TFT_YELLOW); // 太阳主体 // 绘制太阳光线 for (int i=0; i<360; i+=45) { float rad = i * DEG_TO_RAD; tft.drawLine(x+15, y+15, x+15+25*sin(rad), y+15+25*cos(rad), TFT_YELLOW); } }- 位图显示:适合复杂图标
// 先定义位图数据(可使用工具转换) const uint16_t rainIcon[] PROGMEM = { 0x0000, 0x0000, 0x0000, // ... // 省略具体位图数据 }; void drawWeatherIcon(const char* weather) { if (strcmp(weather, "Rain") == 0) { tft.pushImage(150, 40, 64, 64, rainIcon); } // 其他天气条件判断... }5. 高级优化技巧
5.1 内存管理
ESP32的PSRAM可以用于存储大尺寸字体或图像:
// 检查并使用PSRAM if (psramFound()) { tft.loadFont(NotoSansBold36, psram); Serial.println("Using PSRAM for fonts"); }5.2 省电策略
对于电池供电的项目,可以实施以下省电措施:
- 降低屏幕刷新频率(如每分钟更新一次)
- 根据环境光线调节背光亮度
void adjustBacklight() { int lightLevel = analogRead(LIGHT_SENSOR_PIN); int brightness = map(lightLevel, 0, 4095, 10, 255); analogWrite(TFT_BL, brightness); }- 深度睡眠模式
void deepSleep(int minutes) { esp_sleep_enable_timer_wakeup(minutes * 60 * 1000000); esp_deep_sleep_start(); }5.3 抗锯齿处理
TFT_eSPI支持有限的反走样处理,可以改善斜线和曲线的显示效果:
// 启用反走样(会降低性能) tft.setTextColor(TFT_WHITE, TFT_BLACK, true); // 第三个参数启用反走样 // 绘制抗锯齿线 tft.drawWideLine(50, 50, 100, 100, 3, TFT_WHITE, true);在实际项目中,我发现天气图标的动态变化最能提升用户体验。例如下雨时让雨滴图标有下落动画,或者晴天时让太阳图标微微闪烁,这些细节能让设备显得更加生动。实现简单动画的关键是控制好刷新频率,避免过于频繁的更新导致屏幕闪烁。