基于ESP32与TFT屏幕的桌面气象站:从硬件选型到软件实现全解析

基于ESP32与TFT屏幕的桌面气象站:从硬件选型到软件实现全解析
1. 项目概述一个桌面气象站的诞生几年前我偶然间在开源硬件社区看到了一个基于ESP32和TFT屏幕的天气显示项目当时就被它简洁的界面和实时更新的数据吸引了。作为一个喜欢鼓捣嵌入式设备和关注天气的人我立刻意识到这不仅仅是一个“玩具”而是一个能真正融入日常生活的实用工具。于是我决定自己动手基于TTGO TFT屏幕模块打造一个能够解析DarkSky气象数据并显示时钟的桌面气象站。这个项目的核心是将一块小巧的TTGO T-Display模块集成了ESP32和1.14英寸彩色TFT屏变成一个信息中心。它需要完成几件事首先通过Wi-Fi连接到互联网其次定时从DarkSky或其替代服务如OpenWeatherMap的API获取你所在位置的实时天气、未来几小时的预报以及温度、湿度、气压等详细数据最后将这些数据与一个清晰易读的时钟一同美观地呈现在那块小小的屏幕上。整个过程涉及嵌入式开发、网络通信、JSON数据解析和TFT屏幕图形编程是一个综合性很强的练手项目。最终你将得到一个完全自主可控的硬件设备。它不像商业产品那样有复杂的菜单和广告只显示你最关心的信息现在几点、外面天气如何、今天会下雨吗、温度变化趋势怎样。你可以把它放在书桌、床头或厨房一眼就能获取环境信息既实用又有成就感。这个项目非常适合有一定Arduino或MicroPython基础的开发者也适合想从纯软件转向“软硬结合”的爱好者。接下来我将详细拆解从硬件选型到代码实现的每一个环节并分享我踩过的那些坑和总结出的优化技巧。2. 核心硬件与平台选型解析2.1 为什么是TTGO T-Display市面上能跑Micropython或Arduino的ESP32开发板很多搭配屏幕的方案也不少但我最终锁定TTGO T-Display主要是基于以下几个核心考量集成度与尺寸的完美平衡TTGO T-Display最大的优势在于其“All-in-One”的设计。它将ESP32-WROVER模组、1.14英寸IPS TFT屏幕分辨率135x240、锂电池充电管理电路以及几个物理按键全部集成在一块比银行卡还小的PCB上。这意味着你不需要任何飞线、电平转换芯片或复杂的排线连接开箱即用极大降低了硬件搭建的复杂度和出错概率。对于桌面摆件这类对体积有要求的项目这种高集成度是首选。充足的硬件资源它搭载的ESP32模组通常拥有4MB或16MB的Flash以及520KB的SRAM。这对于我们既要运行Wi-Fi连接、HTTP客户端又要处理图形界面和JSON解析的任务来说资源是绰绰有余的。其双核处理器也能让网络请求和屏幕刷新在一定程度上并行处理提升响应速度。成熟的软件生态支持TTGO T-Display在Arduino IDE和MicroPython社区都有非常完善的驱动库支持。例如在Arduino环境下有TFT_eSPI这个功能强大、性能优异的图形库专门为其优化在MicroPython下也有对应的st7789驱动。这省去了我们自己从头编写底层屏幕驱动的麻烦可以专注于应用逻辑。注意TTGO T-Display有几个版本如V1.0 V1.1主要区别在于屏幕驱动芯片ST7789或ST7735和按键引脚定义。在开始编程前务必确认你手中的版本并选择对应的库和引脚初始化代码这是第一个容易踩坑的地方。2.2 气象数据源从DarkSky到OpenWeatherMap的迁移项目最初设计是使用DarkSky API因为它提供的天气数据非常详细特别是“分钟级降水预报”功能很有特色。然而DarkSky已于2023年停止向新用户开放API并于2024年彻底关闭服务。因此我们必须转向其他替代方案。OpenWeatherMap成为首选目前最主流、功能最全面的免费气象API之一就是OpenWeatherMap。它提供多种API接口对于本项目我们主要关注两个Current Weather Data API获取实时天气数据包括温度、体感温度、湿度、气压、天气状况晴、雨、雪等图标代码、风速风向等。One Call API 3.0这是更强大的接口一次调用即可获取当前天气、分钟级预报未来1小时、小时级预报未来48小时和每日预报未来8天。虽然免费套餐对调用次数有限制每分钟60次每日100万次但对于个人项目每分钟或每10分钟更新一次来说完全够用。API Key的申请与安全使用OpenWeatherMap需要在其官网免费注册账号并获取一个API Key。这个Key是访问数据的凭证务必不要在代码中明文提交到GitHub等公开仓库。正确的做法是将其存储在单独的配置文件如config.h或secrets.h中并将该文件加入.gitignore。或者更安全的方式是利用ESP32的NVS非易失性存储功能在首次配网时通过Web界面输入并保存。数据格式与解析OpenWeatherMap API返回的数据是标准的JSON格式。这意味着我们的代码中必须包含一个JSON解析库。在Arduino环境下可以使用内置的ArduinoJson库在MicroPython下则使用ujson或json模块。解析JSON是整个项目的关键步骤之一需要精准地定位到所需数据的路径。2.3 开发环境搭建Arduino IDE vs. MicroPython这是一个见仁见智的选择两者各有优劣取决于你的熟悉程度和项目需求。Arduino IDEC方案优势性能极致编译后的二进制代码运行效率高对硬件底层控制能力强。库生态极其丰富且成熟特别是TFT_eSPI库其图形渲染速度、字体支持和功能完整性在ESP32平台上几乎是最好的。适合对性能有要求、项目复杂度较高或熟悉C/C的开发者。劣势开发流程相对繁琐编译、上传调试不如MicroPython直观。内存管理需要更小心指针和内存泄漏是潜在风险。核心库准备TFT_eSPI用于驱动屏幕绘制图形和文字。ArduinoJson用于解析从API获取的JSON数据。WiFi/WiFiClientSecure用于连接Wi-Fi和发起HTTPS请求。NTPClient用于从网络时间服务器同步精确时间。MicroPython方案优势开发体验类似Python交互式REPL读取-求值-打印循环允许你逐行执行代码调试非常方便。代码编写更快速无需漫长的编译过程。适合快速原型验证、初学者或偏好Python语法的开发者。劣势运行效率低于编译型语言对于复杂动画或高频刷新可能会感到吃力。内存资源相对紧张需要更精细地管理。部分高级库可能没有Arduino平台那么丰富。核心库准备st7789/tft_config驱动TTGO屏幕的MicroPython库。network/urequests处理Wi-Fi连接和HTTP请求。ujson解析JSON。ntptime同步网络时间。我的选择与建议对于这个气象站项目两种方案都能很好地完成。如果你追求极致的显示流畅度和项目长期运行的稳定性我推荐Arduino IDE TFT_eSPI的组合。如果你希望快速看到效果享受交互式编程的乐趣MicroPython是更好的起点。下文我将以Arduino环境为例进行详细讲解因为其生态更稳定且性能表现更优。3. 软件架构与核心逻辑设计3.1 整体程序状态机设计一个健壮的嵌入式程序不应该只是简单地在loop()里顺序执行。为了处理网络连接、数据获取、屏幕刷新等可能失败或耗时的任务并保持系统的响应性采用状态机State Machine模型是最佳实践。我们的气象站可以划分为以下几个核心状态初始化状态STATE_INIT上电后首先进入此状态。初始化串口、屏幕、连接Wi-Fi。成功后立即尝试同步一次网络时间NTP然后跳转到时间更新状态。如果Wi-Fi连接失败则进入错误状态在屏幕上显示错误信息。时间更新状态STATE_UPDATE_TIME这个状态负责定期如每30秒或每分钟从NTP服务器获取精确时间。它本身执行很快主要作用是作为整个系统的时间基准。更新成功后程序大部分时间会停留在空闲显示状态。空闲显示状态STATE_IDLE_DISPLAY这是主状态。在此状态下屏幕以一定的频率如每秒一次刷新时钟。同时这里会检查两个重要的定时器天气数据更新定时器例如每10分钟标志置位一次。屏幕刷新定时器控制时钟秒针跳动或信息轮播。 当天气更新定时器到期则切换到天气数据获取状态。天气数据获取状态STATE_FETCH_WEATHER这是一个关键且易阻塞的状态。在这里程序会创建Wi-Fi客户端。构造带有API Key和位置参数的HTTP请求URL。向OpenWeatherMap服务器发送HTTPS请求并等待响应。接收数据流并将其整合成完整的JSON字符串。调用JSON解析函数处理数据。 这个过程中任何一步失败网络超时、服务器错误、JSON解析错误都应该有相应的错误处理和重试机制例如三次重试后放弃等待下一个周期。成功后将解析出的温度、天气图标代码、湿度等数据存入全局变量并跳转回空闲显示状态进行显示。错误处理状态STATE_ERROR专门用于处理各类错误如Wi-Fi断开、API请求失败等。在此状态屏幕应显示明确的错误代码或信息如“Wi-Fi Fail”或“API Err”并尝试自动恢复如等待一段时间后重连。采用状态机后loop()函数变得非常清晰void loop() { switch (currentState) { case STATE_INIT: stateInit(); break; case STATE_UPDATE_TIME: stateUpdateTime(); break; case STATE_IDLE_DISPLAY: stateIdleDisplay(); break; case STATE_FETCH_WEATHER: stateFetchWeather(); break; case STATE_ERROR: stateError(); break; } // 可以在这里执行一些任何状态都需要做的后台任务比如喂看门狗 }3.2 数据结构与全局变量规划良好的数据结构是代码清晰的基石。我们需要规划一些全局变量来存储关键信息// 时间结构体 struct { int year; int month; int day; int hour; int minute; int second; String weekday; // 星期几 } currentTime; // 天气结构体 struct { float tempCurrent; // 当前温度 float tempFeelsLike; // 体感温度 int humidity; // 湿度百分比 int pressure; // 气压 (hPa) int weatherId; // 天气状况ID (用于匹配图标) String description; // 天气描述 (晴, 小雨等) float windSpeed; // 风速 int windDeg; // 风向角度 float rainLastHour; // 过去一小时降雨量 (mm) } currentWeather; // 未来几小时预报简化存储接下来3小时 struct { int hourOffset; // 相对于当前时间的小时偏移量 (1, 2, 3) int weatherId; float temp; } hourlyForecast[3]; // 系统状态与标志位 enum AppState { STATE_INIT, STATE_UPDATE_TIME, STATE_IDLE_DISPLAY, STATE_FETCH_WEATHER, STATE_ERROR }; AppState currentState STATE_INIT; unsigned long lastTimeUpdate 0; unsigned long lastWeatherUpdate 0; const long timeUpdateInterval 30000; // 30秒更新一次时间NTP const long weatherUpdateInterval 600000; // 10分钟更新一次天气使用结构体将相关数据封装在一起提高了代码的可读性和可维护性。全局变量虽然要谨慎使用但在这种单任务的小型嵌入式项目中合理规划是最高效的方式。3.3 网络请求与JSON解析的关键实现这是项目的数据管道也是最容易出问题的环节。构造HTTPS请求OpenWeatherMap的API需要使用HTTPS。在Arduino中我们使用WiFiClientSecure客户端。需要注意的是ESP32需要验证服务器证书。我们可以选择忽略证书验证不推荐用于生产环境或设置正确的根证书。#include WiFiClientSecure.h WiFiClientSecure client; // 方法一忽略证书验证仅用于测试 client.setInsecure(); // 方法二设置根证书更安全 // 你需要将OpenWeatherMap的根证书内容放入一个变量中 // const char* root_ca -----BEGIN CERTIFICATE-----\n...; // client.setCACert(root_ca);发送请求与接收数据bool fetchWeatherData() { if (!client.connect(api.openweathermap.org, 443)) { Serial.println(Connection to server failed!); return false; } // 构造请求URL其中API_KEY和CITY_ID需要替换成你的 String url /data/3.0/onecall?latYOUR_LATlonYOUR_LONexcludeminutely,dailyunitsmetriclangzh_cnappid; url API_KEY; client.print(String(GET ) url HTTP/1.1\r\n Host: api.openweathermap.org\r\n Connection: close\r\n\r\n); // 等待响应头 unsigned long timeout millis(); while (client.available() 0) { if (millis() - timeout 5000) { Serial.println(Client Timeout!); client.stop(); return false; } } // 跳过HTTP响应头直到遇到空行 while (client.connected()) { String line client.readStringUntil(\n); if (line \r) { // 空行标识头部结束 break; } } // 读取JSON响应体 String payload ; while (client.available()) { payload client.readString(); } client.stop(); // 调用解析函数 return parseWeatherJson(payload); }JSON解析的实战技巧使用ArduinoJson库时务必根据API返回的示例数据在 ArduinoJson Assistant 工具中计算所需的内存。分配不足会导致解析失败分配过多会浪费宝贵的内存。#include ArduinoJson.h bool parseWeatherJson(String jsonString) { const size_t capacity JSON_OBJECT_SIZE(5) JSON_ARRAY_SIZE(2) 200; // 根据实际计算 DynamicJsonDocument doc(capacity); DeserializationError error deserializeJson(doc, jsonString); if (error) { Serial.print(F(deserializeJson() failed: )); Serial.println(error.f_str()); return false; } // 提取数据注意根据One Call API 3.0的实际结构访问 currentWeather.tempCurrent doc[current][temp]; currentWeather.weatherId doc[current][weather][0][id]; currentWeather.description doc[current][weather][0][description].asString(); // 解析小时预报 JsonArray hourly doc[hourly]; for (int i 0; i 3 i hourly.size(); i) { hourlyForecast[i].hourOffset i 1; hourlyForecast[i].weatherId hourly[i][weather][0][id]; hourlyForecast[i].temp hourly[i][temp]; } return true; }实操心得网络请求是最不稳定的环节。一定要加入超时判断和重试逻辑。我通常会设置一个3次的重试计数器每次失败后延迟一段时间再试。同时在stateFetchWeather中如果最终失败不要卡死程序而是记录错误并优雅地退回到STATE_IDLE_DISPLAY显示上一次的有效数据或一个“数据过期”的提示。4. TFT屏幕界面设计与图形编程4.1 TFT_eSPI库基础与初始化TFT_eSPI是驱动本项目屏幕的灵魂。首先你需要根据你的TTGO T-Display具体版本修改库中的用户配置文件。通常路径是Arduino/libraries/TFT_eSPI/User_Setup.h。你需要注释掉其他屏幕的驱动并启用正确的驱动和引脚定义。TTGO T-Display的配置通常已经包含在库的示例中找到对应的User_Setup_Select.h文件取消注释#include User_Setups/Setup25_TTGO_T_Display.h这一行即可。初始化屏幕非常简单#include TFT_eSPI.h TFT_eSPI tft TFT_eSPI(); void setup() { tft.init(); tft.setRotation(1); // 根据你的摆放方向设置旋转角度0-3 tft.fillScreen(TFT_BLACK); // 清屏为黑色 }setRotation()函数非常重要它决定了屏幕的坐标系原点0,0在哪个角落直接影响你所有绘图坐标的计算。4.2 界面布局规划与绘制策略在135x240的分辨率下空间非常宝贵。一个清晰的布局至关重要。我采用的经典布局如下顶部区域约20像素高显示当前日期如“2024-05-27 周二”和城市名称。使用小号字体。中部左侧主区域约100x100像素显示大型数字时钟时、分这是最显眼的信息。字体可以选用TFT_eSPI内置的FreeSansBoldOblique24pt或SevenSegNumFont等数码管字体极具视觉冲击力。中部右侧区域显示当前天气图标和一个中等大小的温度值如“23.5°C”。天气图标需要根据weatherId进行映射自己绘制或使用现成的图标字体。底部区域约40像素高分为两行。第一行显示天气描述如“晴朗”、体感温度和湿度如“体感24°C 湿度65%”。第二行显示未来3小时的简易预报可以用小型图标和温度表示如“☀️22° ☁️21° ️20°”。最底部状态栏约10像素高显示Wi-Fi信号强度图标或简单的系统状态如“●”表示正常“!”表示错误。绘制策略为了减少屏幕闪烁和提升效率切忌在loop()中频繁调用fillScreen()清屏再重绘所有元素。应该采用“局部更新”策略时钟每秒只重绘变化的秒数字如果需要秒每分钟重绘一次时分。天气数据只有在数据更新后才重绘相关区域。静态元素如边框、标签文字“湿度”只在初始化时绘制一次。TFT_eSPI提供了setTextColor(前景色, 背景色)函数在更新文本时如果指定背景色它会先绘制一个背景色方块再写文字完美覆盖旧内容实现无闪烁更新。4.3 自定义天气图标与字体优化系统自带的字体可能不符合你的审美。TFT_eSPI支持从.vlw格式的字体文件创建自定义字体。你可以使用其配套的“Processing”脚本将电脑上的任何TrueType字体转换成.vlw文件然后放入项目的data目录下通过tft.loadFont()加载。这让你可以自由选择更美观的数码字体或手写体。对于天气图标有两种主流方案图标字体将一套天气图标晴、阴、雨、雪等制作成一个字体文件。这样每个图标就对应一个字符码可以用tft.drawChar()或tft.print()直接打印颜色和大小可调非常方便。这是我最推荐的方式。位图BMP将图标保存为小尺寸的BMP文件利用tft.drawBitmap()函数绘制。这种方式更灵活可以绘制彩色图标但需要额外的存储空间并且绘制速度稍慢。图标与weatherId的映射关系需要根据OpenWeatherMap的 天气条件代码 来建立。例如ID为800晴天映射到太阳图标ID为500-531雨天映射到雨伞或云下雨滴图标。// 示例根据weatherId获取图标字符假设已加载图标字体 String getWeatherIcon(int id) { if (id 200 id 300) return T; // 雷暴图标 else if (id 300 id 600) return R; // 雨图标 else if (id 600 id 700) return S; // 雪图标 else if (id 800) return B; // 晴天图标 else if (id 800) return C; // 多云图标 else return ?; // 未知 }5. 系统优化与深度调试技巧5.1 低功耗设计与续航考量虽然TTGO T-Display可以连接锂电池但ESP32和TFT屏幕都是耗电大户。如果希望它作为便携或长期离电设备使用必须进行低功耗优化。深度睡眠Deep Sleep对于不需要实时显示的场景可以让ESP32在采集完数据、更新屏幕后进入深度睡眠模式。例如每10分钟唤醒一次获取天气、更新屏幕然后继续睡眠。这能极大降低功耗。但TTGO T-Display的屏幕在深度睡眠时无法保持显示唤醒后需要重新初始化并全屏绘制。屏幕背光控制屏幕背光是主要的耗电源。可以通过PWM控制其亮度。在环境光较暗的夜晚可以自动或手动调低背光。tft.setBrightness(pwm_value)可以实现这一点其中pwm_value通常在0-255之间。Wi-Fi与CPU频率在不需要连接网络时如深度睡眠间隔Wi-Fi模块是关闭的。在代码中完成网络请求后可以调用WiFi.disconnect()和WiFi.mode(WIFI_OFF)来进一步省电。此外可以通过setCpuFrequencyMhz()降低CPU主频如从240MHz降到80MHz在只进行屏幕刷新等简单任务时能有效省电。注意事项深度睡眠与定时唤醒需要连接GPIO引脚到RTC控制器并且会丢失RAM中的所有数据。你需要将需要保持的数据如上次的天气数据保存到RTC内存或PreferencesNVS中并在唤醒后读取。这增加了程序的复杂性请根据实际需求权衡。5.2 网络稳定性与错误恢复机制嵌入式设备在网络不稳定的环境中运行是常态。必须构建鲁棒的错误处理机制。Wi-Fi自动重连不要在setup()中只连接一次Wi-Fi。在loop()或一个独立任务中持续检查WiFi.status()。如果断开则尝试重连。可以加入指数退避算法避免频繁重连刷日志。void checkWiFi() { if (WiFi.status() ! WL_CONNECTED) { Serial.println(WiFi disconnected. Reconnecting...); WiFi.disconnect(); delay(1000); WiFi.begin(ssid, password); unsigned long startAttemptTime millis(); while (WiFi.status() ! WL_CONNECTED millis() - startAttemptTime 10000) { delay(500); Serial.print(.); } if (WiFi.status() WL_CONNECTED) { Serial.println(\nReconnected!); } else { Serial.println(\nFailed to reconnect!); } } }API请求超时与重试如前所述为client.connect()和client.available()设置明确的超时。将整个数据获取过程包装在一个带重试次数的函数里。数据有效性校验与降级显示解析JSON后检查关键数据如温度是否在合理范围内如-50°C到60°C。如果数据无效或获取失败不要更新显示变量继续显示旧数据并在状态栏给出提示如“数据延迟”。可以设置一个数据“保鲜期”超过一定时间如30分钟未更新则显示“数据已过期”。5.3 性能监控与调试输出在开发阶段充分的调试信息能帮你快速定位问题。串口日志分级定义不同的日志级别如LOG_ERROR,LOG_INFO,LOG_DEBUG。通过宏定义控制编译时输出哪些级别的日志。#define DEBUG_LEVEL 1 // 0无1错误2信息3调试 #define LOG_E(...) if (DEBUG_LEVEL1) Serial.printf(__VA_ARGS__) #define LOG_I(...) if (DEBUG_LEVEL2) Serial.printf(__VA_ARGS__) #define LOG_D(...) if (DEBUG_LEVEL3) Serial.printf(__VA_ARGS__)内存监控ESP32的堆内存是有限的。定期打印剩余内存有助于发现内存泄漏。LOG_D(Free heap: %d bytes\n, ESP.getFreeHeap());任务看门狗如果使用了FreeRTOS任务或者你的loop()执行时间可能很长考虑启用硬件看门狗esp_task_wdt_init()来防止程序跑飞。5.4 常见问题排查速查表下表总结了我开发过程中遇到的一些典型问题及解决方法问题现象可能原因排查步骤与解决方案屏幕白屏或花屏1. 电源供电不足。2.User_Setup.h配置错误。3. 初始化顺序或引脚冲突。1. 使用可靠的5V/1A以上电源或确保电池电量充足。2. 仔细核对TFT_eSPI库中对应你型号的配置文件是否启用正确。3. 确保tft.init()和tft.setRotation()在setup()中正确调用。Wi-Fi无法连接1. SSID/密码错误。2. 路由器设置了MAC过滤或隐藏SSID。3. ESP32的Wi-Fi天线PCB天线附近有金属屏蔽。1. 用串口打印确认SSID和密码。2. 检查路由器设置尝试用手机热点测试。3. 调整设备摆放位置远离大型金属物体。无法获取天气数据HTTP失败1. API Key无效或过期。2. 免费API调用次数超限。3. 网络无法访问国外服务器特定网络环境问题。4. 系统时间未同步导致HTTPS证书验证失败。1. 登录OpenWeatherMap检查API Key状态。2. 检查账户调用统计免费套餐有每分钟/每日限制。3. 尝试在代码中先client.setInsecure()跳过证书验证以区分是网络问题还是证书问题。4. 确保NTP时间同步成功后再发起首次HTTPS请求。JSON解析失败1. 分配的DynamicJsonDocument容量不足。2. 网络返回的数据不完整或非JSON格式。3. JSON键名路径写错。1. 使用ArduinoJson Assistant重新计算所需容量并适当增加余量。2. 将payload字符串通过串口打印出来复制到在线JSON校验工具中检查格式。3. 仔细对照API文档确认键名的大小写和层级关系。程序运行一段时间后重启1. 内存泄漏导致堆耗尽。2. 看门狗超时某个任务阻塞太久。3. 电源电压不稳定。1. 在关键函数前后打印内存检查是否有未释放的String对象或动态内存分配。2. 检查loop()或任务中是否有delay()过长或网络请求没有超时保护。用yield()或delay()拆分长任务。3. 监测电源电压尤其是在电池供电时。屏幕刷新闪烁严重在loop()中频繁调用tft.fillScreen()。改为局部更新策略。只更新需要变化的文字或图形区域并使用带背景色的setTextColor。对于图形可以先画一个背景色的填充矩形再画新图。时间不同步或走时不准1. NTP服务器连接失败。2. 时区设置错误。3. 依赖millis()软件计时存在累积误差。1. 检查NTP服务器地址如pool.ntp.org和端口123是否可达。2. 在同步时间后手动加上或减去时区偏移的小时数如东八区8。3. 定期如每天重新同步一次NTP时间校正误差。6. 功能扩展与进阶玩法基础功能实现后这个平台还有巨大的扩展潜力。1. 多数据源融合显示 除了天气你可以让这个小屏幕显示更多信息。例如室内环境连接一个BME280传感器I2C接口同时显示室内的温湿度、气压。日历与日程通过连接第三方日历API如Google Calendar显示今天的下一个日程。系统信息显示ESP32的IP地址、内存使用率、运行时长等。2. 交互功能升级 利用TTGO T-Display板载的两个物理按键通常标记为BUTTON_A,BUTTON_B或增加触摸屏某些版本支持可以实现交互。页面切换按A键切换显示页面如“主页面/天气详情页/室内环境页”。设置菜单长按B键进入设置菜单可以配置Wi-Fi、选择城市、调整屏幕亮度等。这需要实现一个简单的菜单系统。3. 无线配置Web配网 摆脱硬编码Wi-Fi密码。可以使用WiFiManager库Arduino或microdotMicroPython等让设备启动后进入AP模式手机连接后弹出网页用于配置Wi-Fi SSID/密码和API Key并保存到NVS中。4. 外观设计与封装 为你的气象站设计一个3D打印的外壳。可以在开源模型网站如Thingiverse上搜索“TTGO T-Display case”有很多现成的设计。一个好的外壳不仅能保护电路还能让其更好地融入家居环境。完成这个项目后你收获的不仅仅是一个桌面摆件更是一套完整的嵌入式开发经验从硬件驱动、网络通信、数据解析到UI设计和系统稳定性优化。它就像一个微缩的物联网设备原型其中的设计思路和解决方法可以迁移到更多、更复杂的项目中去。我最享受的时刻就是在某个安静的下午看着这个小屏幕上的数字平稳跳动天气图标随着窗外风云变幻而切换那一刻代码与物理世界产生了真切而美妙的连接。