基于Wio Terminal的物联网数据看板:从API获取到屏幕显示的完整实践

1. 项目概述:当硬件遇上实时疫情数据

最近在整理我的Wio Terminal项目库,翻到了一个挺有意思的旧项目——用这块小巧的物联网开发板来实时读取并显示全球COVID-19疫情数据。这不仅仅是一个简单的数据展示,更像是一个将物联网硬件、网络API和实时信息可视化结合的微型实践。对于很多刚接触Arduino和物联网开发的朋友来说,从点亮一个LED到成功从互联网获取数据并显示,中间隔着好几道坎。这个项目恰好能带你平滑地跨过这些坎:从配置Wi-Fi网络,到理解HTTP请求和JSON数据解析,再到在屏幕上绘制出直观的图表和文字。Wio Terminal本身集成了屏幕、按键和无线模块,让它成为这类信息显示终端的绝佳载体,你可以把它做成一个摆在桌面的疫情数据看板,或者一个更通用的网络信息显示器的基础框架。

2. 核心思路与方案选型

2.1 为什么选择Wio Terminal?

选择Wio Terminal作为这个项目的硬件核心,是基于它“开箱即用”的特性。对于物联网数据获取与显示类项目,我们通常需要几个核心模块:主控MCU、网络连接模块、显示界面以及交互输入。如果使用传统的Arduino Uno搭配各种扩展板,光是接线和库兼容性问题就够头疼一阵子。Wio Terminal则将这些全部集成在了一块板子上:

  • 主控:基于ATSAMD51的高性能ARM Cortex-M4F内核,性能远超普通的AVR芯片,能流畅处理网络通信和图形界面。
  • 网络:板载Realtek RTL8720DN芯片,同时支持2.4GHz Wi-Fi和蓝牙5.0,省去了外接ESP8266/ESP32模块的麻烦。
  • 显示:一块2.4英寸的LCD彩屏,分辨率320x240,自带ILI9341驱动器,有成熟的TFT_eSPI图形库支持,显示图表和文字效果很好。
  • 交互:三个功能按键、一个五向摇杆、甚至还有麦克风和光线传感器,为交互设计提供了可能。
  • 开发环境:完美兼容Arduino IDE,生态友好,学习成本低。

这样一来,我们的精力可以完全集中在逻辑和代码上,而不是底层硬件调试,这对于实现“快速验证想法”的目标至关重要。

2.2 数据源的选择与API分析

项目的另一个核心是数据源。疫情期间,全球多家机构和平台提供了开放的疫情数据API。我们的选择需要满足几个条件:免费、稳定、数据格式规范(最好是JSON)、访问不需要复杂的认证。当时我主要对比了几个主流来源:

  1. 约翰斯·霍普金斯大学(JHU)数据:权威性高,但提供的原始数据多为CSV文件,需要后端处理,对单片机直接获取不太友好。
  2. Worldometer:网站数据直观,但通常没有官方公开的、稳定的免费API供程序化调用,直接爬取网页结构易变,可靠性差。
  3. disease.sh - Open Disease Data API:这是一个社区维护的免费API,专门提供COVID-19及其他疾病数据。它提供了结构清晰的JSON接口,支持按国家、州查询全球数据,并且拥有不错的免费访问频率限制。这正是我们需要的。

因此,我们决定使用disease.sh的API。例如,获取全球总数据的接口是:https://disease.sh/v3/covid-19/all。返回的JSON数据包包含确诊、死亡、康复、新增等关键字段,非常规整。获取特定国家(如中国)的数据接口是:https://disease.sh/v3/covid-19/countries/CN。选择这个API,大大简化了我们单片机端的数据解析工作。

2.3 整体软件架构设计

整个项目的代码逻辑可以清晰地分为几个层次:

  • 网络连接层:负责让Wio Terminal连接到指定的Wi-Fi网络。这是所有后续操作的基础。
  • 数据获取层:使用HTTPClient库向目标API发起GET请求,并将服务器返回的原始数据接收下来。
  • 数据解析层:使用ArduinoJson库对收到的JSON格式字符串进行解析,提取出我们需要的具体数值(如总确诊数、今日新增等)。
  • 用户界面层:使用TFT_eSPI库在屏幕上绘制界面。包括:绘制静态的背景和标题;将解析出的数据以文本形式显示在指定位置;或许还可以用简单的柱状图或折线图来展示趋势。
  • 逻辑控制层:通过板载按键或摇杆,实现界面切换(如全球数据/国家数据)、手动刷新数据等功能。同时,还需要一个定时器,实现每隔一段时间(如10分钟)自动刷新数据。

这个分层结构使得代码模块化程度高,便于调试和维护。例如,如果你想更换数据源,基本上只需要修改“数据获取层”和“数据解析层”的少量代码。

3. 开发环境搭建与核心库配置

3.1 硬件准备与Arduino IDE配置

首先,确保你手头有Wio Terminal开发板、一根USB Type-C数据线(用于供电和编程)。软件方面,需要在电脑上安装Arduino IDE(1.8.x或2.0版本均可)。

为了让Arduino IDE识别Wio Terminal,我们需要添加对应的板卡支持。具体步骤如下:

  1. 打开Arduino IDE,点击文件->首选项
  2. 在“附加开发板管理器网址”一栏中,填入以下URL(如果已有其他URL,用逗号隔开):https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json
  3. 点击工具->开发板->开发板管理器...
  4. 在弹出的搜索框中,输入“Seeed SAMD”,找到“Seeed SAMD Boards”并点击安装。这个包包含了Wio Terminal的支持。
  5. 安装完成后,在工具->开发板列表中,就能选择“Seeed Wio Terminal”了。

接下来,需要安装本项目依赖的几个核心库。同样通过工具->管理库...打开库管理器:

  • TFT_eSPI:这是驱动Wio Terminal屏幕的核心图形库。安装后需要配置。找到Arduino安装目录下的libraries/TFT_eSPI/User_Setups文件夹,将其中的Setup206_Seeed_Wio_Terminal.h文件复制到libraries/TFT_eSPI根目录下,并重命名为User_Setup.h(覆盖原有的空文件或选择配置)。这个文件已经为Wio Terminal的屏幕引脚做好了预配置。
  • ArduinoJson:用于解析API返回的JSON数据。建议安装较新的版本(如v6.x或v7.x),注意其API与旧版(v5.x)有较大不同。
  • WiFiHTTPClient:这些通常是Arduino核心库的一部分,对于ESP32/ESP8266或Wio Terminal(基于SAMD)的特定版本,可能需要确认。Wio Terminal的Wi-Fi功能通过rpcWiFi库实现,在安装好“Seeed SAMD Boards”后通常会自动包含。

3.2 网络连接模块的初始化与测试

Wio Terminal的Wi-Fi功能封装在rpcWiFi库中,使用方式与常见的WiFi库类似,但需要注意一些细节。下面是一个最基本的连接测试代码:

#include <rpcWiFi.h> const char* ssid = "你的Wi-Fi名称"; const char* password = "你的Wi-Fi密码"; void setup() { Serial.begin(115200); while (!Serial); // 等待串口连接,仅用于调试 // 初始化Wi-Fi为STA模式 WiFi.mode(WIFI_STA); WiFi.disconnect(); delay(100); Serial.print("正在连接到: "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("Wi-Fi连接成功!"); Serial.print("IP地址: "); Serial.println(WiFi.localIP()); } void loop() { // 主循环为空,仅作连接测试 }

注意:Wio Terminal的rpcWiFi库在连接某些WPA2企业网络或带有特殊认证的公共网络时可能会遇到问题。对于家庭普通路由器网络,连接通常很稳定。如果连接失败,首先检查串口输出的错误信息,并确认SSID和密码是否正确,以及路由器是否设置了MAC地址过滤。

将这段代码上传到Wio Terminal,打开串口监视器(波特率115200),你应该能看到连接成功的提示。这是项目万里长征的第一步,也是最关键的一步,务必确保稳定。

3.3 图形库初始化与基础UI绘制

网络连通后,我们来点亮屏幕。TFT_eSPI库功能强大,我们先初始化并绘制一个简单的界面框架。

#include <TFT_eSPI.h> TFT_eSPI tft = TFT_eSPI(); void setup() { Serial.begin(115200); tft.init(); tft.setRotation(3); // 根据你的摆放习惯设置旋转角度,3为USB口在右侧 tft.fillScreen(TFT_BLACK); // 清屏为黑色 // 绘制标题 tft.setTextColor(TFT_WHITE, TFT_BLACK); // 白色字,黑色背景 tft.setTextSize(2); // 设置字体大小 tft.setCursor(20, 10); // 设置起始坐标(像素) tft.println("COVID-19 Data Board"); // 绘制一条分隔线 tft.drawFastHLine(10, 40, 300, TFT_CYAN); // 从(10,40)画一条长300像素的水平线 // 设置后续数据显示的字体 tft.setTextSize(2); tft.setTextColor(TFT_GREEN, TFT_BLACK); } void loop() { // 后续在这里更新数据 delay(1000); }

上传代码后,屏幕应该会显示标题和一条横线。这里有几个实操要点:

  • setRotation()函数非常重要,它决定了屏幕的坐标系原点。多尝试几个参数(0-3),找到最适合你观看和编程的方向。
  • setTextColor(前景色, 背景色)中的背景色设置,在调用println时会用背景色填充整个文本区域,从而实现“覆盖”之前旧文本的效果,这在动态更新数据时非常有用,避免文字重叠。
  • 屏幕坐标原点(0,0)默认在旋转后的左上角,X轴向右,Y轴向下。

4. 核心功能实现:数据获取、解析与显示

4.1 发起HTTP请求获取原始JSON数据

网络和屏幕都就绪后,就可以向数据API发起请求了。我们将使用HTTPClient库(对于Wio Terminal,来自rpcWiFi环境)来完成这个任务。首先,我们封装一个函数来获取全球数据。

#include <rpcWiFi.h> #include <HTTPClient.h> const char* global_api_url = "https://disease.sh/v3/covid-19/all"; String fetchGlobalData() { String payload = ""; // 用于存储返回的JSON字符串 if (WiFi.status() == WL_CONNECTED) { HTTPClient http; http.begin(global_api_url); // 指定请求地址 int httpCode = http.GET(); // 发起GET请求 if (httpCode == HTTP_CODE_OK) { // 如果返回码为200 payload = http.getString(); // 获取响应体内容 Serial.println("数据获取成功:"); Serial.println(payload); } else { Serial.printf("HTTP GET请求失败,错误码: %d\n", httpCode); payload = "Error: " + String(httpCode); } http.end(); // 释放资源 } else { Serial.println("Wi-Fi未连接"); payload = "Error: WiFi Disconnected"; } return payload; }

这个函数的核心流程是:检查网络连接 -> 初始化HTTPClient -> 开始连接指定URL -> 执行GET方法 -> 判断HTTP状态码 -> 成功则读取响应体 -> 清理资源。返回的payload变量就是一个包含所有数据的JSON字符串。

实操心得:务必在每次请求后调用http.end(),这是一个好习惯,能确保TCP连接被正确关闭,释放系统资源。特别是在需要频繁请求的循环中,不释放连接可能导致内存泄漏或网络端口耗尽。

4.2 使用ArduinoJson解析复杂数据结构

拿到{"cases":12345678,"deaths":98765,...}这样的JSON字符串后,我们需要从中提取出具体的数字。ArduinoJson库能高效地完成这个工作。这里以v7.x版本为例(v6类似)。

#include <ArduinoJson.h> // 假设payload是fetchGlobalData()返回的字符串 void parseAndDisplayGlobalData(String payload) { // 跳过错误信息 if (payload.startsWith("Error")) { tft.setTextColor(TFT_RED, TFT_BLACK); tft.setCursor(10, 60); tft.println(payload); return; } // 创建JsonDocument对象,容量需要根据JSON大小估算 const size_t capacity = JSON_OBJECT_SIZE(10) + 1024; // 估算缓冲区 DynamicJsonDocument doc(capacity); // 反序列化(解析)JSON数据 DeserializationError error = deserializeJson(doc, payload); if (error) { Serial.print(F("JSON解析失败: ")); Serial.println(error.f_str()); tft.setTextColor(TFT_RED, TFT_BLACK); tft.setCursor(10, 60); tft.println("JSON Parse Error"); return; } // 从doc对象中提取数据 long totalCases = doc["cases"]; // 总确诊 long todayCases = doc["todayCases"]; // 今日新增 long totalDeaths = doc["deaths"]; // 总死亡 long todayDeaths = doc["todayDeaths"]; // 今日新增死亡 long recovered = doc["recovered"]; // 总康复 long active = doc["active"]; // 现存病例 long affectedCountries = doc["affectedCountries"]; // 受影响国家数 // 现在,你可以使用这些变量了... Serial.printf("全球总确诊: %ld\n", totalCases); }

关键点解析

  • 缓冲区大小估算:这是使用ArduinoJson最容易出错的地方。缓冲区太小会导致解析失败或数据截断。JSON_OBJECT_SIZE(10)表示文档中有大约10个键值对,+1024是为字符串等值预留的空间。对于disease.sh的全球数据接口,这个容量是足够的。更严谨的做法是使用在线工具或库的辅助函数来计算,但在单片机开发中,通常根据经验估算并留足余量。
  • 错误处理:务必检查DeserializationError。网络传输可能不完整,或者API返回格式变化,都会导致解析失败。良好的错误处理能让你的程序更健壮,而不是直接崩溃。
  • 数据类型:JSON中的数字可能很大,所以使用long类型来接收。确保你的变量类型有足够的范围。

4.3 设计并实现动态数据刷新界面

有了解析出的数据,下一步就是在屏幕上美观地显示出来。我们需要设计一个布局,并实现数据的动态更新(避免闪烁)。

// 定义显示区域的坐标和颜色 #define DATA_START_X 20 #define DATA_START_Y 60 #define DATA_LINE_HEIGHT 25 #define LABEL_COLOR TFT_CYAN #define VALUE_COLOR TFT_GREEN #define UNIT_COLOR TFT_YELLOW // 全局变量存储上一次显示的值,用于判断是否需要更新 long lastTotalCases = -1; long lastTodayCases = -1; // ... 其他变量同理 void updateGlobalDisplay(long totalCases, long todayCases, long totalDeaths, long todayDeaths) { tft.setTextSize(2); // 1. 显示“总确诊” tft.setTextColor(LABEL_COLOR, TFT_BLACK); tft.setCursor(DATA_START_X, DATA_START_Y); tft.print("Confirmed: "); if (totalCases != lastTotalCases) { tft.setTextColor(VALUE_COLOR, TFT_BLACK); // 用背景色清空旧数值区域(近似宽度) tft.fillRect(DATA_START_X + 120, DATA_START_Y, 150, 20, TFT_BLACK); tft.setCursor(DATA_START_X + 120, DATA_START_Y); tft.print(totalCases); // 这里可以添加千位分隔符格式化函数 lastTotalCases = totalCases; } // 2. 显示“今日新增” tft.setTextColor(LABEL_COLOR, TFT_BLACK); tft.setCursor(DATA_START_X, DATA_START_Y + DATA_LINE_HEIGHT); tft.print("Today New: "); if (todayCases != lastTodayCases) { tft.setTextColor(TFT_ORANGE, TFT_BLACK); // 今日新增用橙色突出 tft.fillRect(DATA_START_X + 120, DATA_START_Y + DATA_LINE_HEIGHT, 150, 20, TFT_BLACK); tft.setCursor(DATA_START_X + 120, DATA_START_Y + DATA_LINE_HEIGHT); tft.printf("+%ld", todayCases); lastTodayCases = todayCases; } // 3. 显示“总死亡”和“今日死亡”... // ... 类似逻辑,调整Y坐标即可 // 4. 显示最后更新时间 tft.setTextColor(TFT_GRAY, TFT_BLACK); tft.setTextSize(1); tft.setCursor(10, 220); tft.print("Last Update: "); // 这里可以添加RTC时间或从JSON中解析更新时间,简单起见显示时间戳 tft.print(millis() / 1000); tft.print("s ago"); }

界面优化技巧

  • 避免闪烁:核心在于“局部更新”。只在数据真正发生变化时,用背景色重绘(fillRect)那个数字所在的区域,然后再绘制新数字。而不是每次刷新都重绘整个屏幕。
  • 格式化数字:对于很大的数字(如12345678),阅读不友好。可以写一个辅助函数将其格式化为“12.3M”或“1,234万”的形式。
  • 使用颜色编码:用不同颜色区分数据类别(如总数据用绿色,新增用橙色,死亡用红色),增强可读性。

4.4 整合主循环与定时自动刷新

最后,我们将所有模块整合到setup()loop()中,并加入定时自动刷新逻辑。

#include <TFT_eSPI.h> #include <rpcWiFi.h> #include <HTTPClient.h> #include <ArduinoJson.h> // ... 之前的变量、函数定义 ... unsigned long lastDataUpdateTime = 0; const unsigned long UPDATE_INTERVAL = 600000; // 更新间隔,10分钟(600000毫秒) void setup() { Serial.begin(115200); tft.init(); tft.setRotation(3); tft.fillScreen(TFT_BLACK); drawStaticUI(); // 绘制静态界面元素(标题、标签等) connectToWiFi(); // 连接Wi-Fi的函数,封装了之前的连接代码 // 启动后立即获取一次数据 String data = fetchGlobalData(); parseAndDisplayGlobalData(data); lastDataUpdateTime = millis(); } void loop() { unsigned long currentTime = millis(); // 检查是否到达定时更新时间 if (currentTime - lastDataUpdateTime >= UPDATE_INTERVAL) { Serial.println("定时更新数据..."); String data = fetchGlobalData(); parseAndDisplayGlobalData(data); lastDataUpdateTime = currentTime; } // 这里可以加入按键检测代码,实现手动刷新或切换国家 // checkButtonPress(); // 短暂延时,避免loop空跑消耗CPU delay(100); }

这样,一个具备自动定时刷新功能的COVID-19数据看板就完成了。它会在启动后以及每隔10分钟自动从网络获取最新数据并更新显示。

5. 功能扩展与高级优化

5.1 实现多国家数据切换与查询

只显示全球数据略显单一。我们可以利用板载的五向摇杆或按键,让用户能够切换查看不同国家的数据。首先,需要修改数据获取函数,使其能接收国家代码参数。

String fetchCountryData(const char* countryCode) { String payload = ""; char url[100]; // 构造国家数据API URL,例如 https://disease.sh/v3/covid-19/countries/CN snprintf(url, sizeof(url), "https://disease.sh/v3/covid-19/countries/%s", countryCode); HTTPClient http; http.begin(url); int httpCode = http.GET(); if (httpCode == HTTP_CODE_OK) { payload = http.getString(); } else { Serial.printf("获取国家(%s)数据失败,代码: %d\n", countryCode, httpCode); payload = "Error"; } http.end(); return payload; }

然后,需要解析国家数据。国家数据的JSON结构比全球数据更复杂,它嵌套了国家信息对象。

void parseAndDisplayCountryData(String payload, const char* countryCode) { if (payload.startsWith("Error")) { /* 错误处理 */ } const size_t capacity = JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(20) + 2048; DynamicJsonDocument doc(capacity); DeserializationError error = deserializeJson(doc, payload); if (error) { /* 错误处理 */ } // 提取国家信息 const char* countryName = doc["country"]; // 国家名称 long countryCases = doc["cases"]; long countryTodayCases = doc["todayCases"]; // ... 提取其他字段 // 更新显示,可以同时显示国家名称和国旗(如果有图片资源) tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.setTextSize(2); tft.setCursor(100, 10); // 在标题位置显示国家名 tft.fillRect(100, 10, 150, 20, TFT_BLACK); // 清空旧国名区域 tft.print(countryName); // 更新数据值... updateCountryDisplay(countryCases, countryTodayCases, ...); }

最后,在loop()中加入摇杆或按键检测逻辑,根据用户输入切换当前显示的国家代码(如“US”、“IN”、“FR”等),并调用fetchCountryDataparseAndDisplayCountryData

5.2 数据可视化:绘制简易趋势图表

纯数字不够直观,我们可以利用TFT_eSPI的绘图功能,绘制一个简单的柱状图来展示不同国家的数据对比,或者绘制折线图展示某个国家最近几天的历史趋势(这需要调用历史数据API)。

以绘制几个国家今日新增病例的对比柱状图为例:

  1. 定义一个结构体数组,存储要对比的国家代码和其对应的数据。
  2. 在获取完这些国家的数据后,找到最大值(用于归一化)。
  3. 在屏幕底部划定一个绘图区域。
  4. 遍历国家数组,计算每个国家数据相对于最大值的比例,以此确定柱子的高度。
  5. 使用tft.fillRect()函数绘制柱子,并在柱子顶部或底部标注国家缩写和数据。
void drawBarChart(CountryData data[], int numCountries) { int chartX = 20; int chartY = 150; int chartWidth = 280; int chartHeight = 60; int barWidth = chartWidth / numCountries - 5; // 留出间隙 // 1. 找出最大值 long maxValue = 0; for (int i = 0; i < numCountries; i++) { if (data[i].todayCases > maxValue) maxValue = data[i].todayCases; } // 2. 绘制坐标轴 tft.drawRect(chartX, chartY, chartWidth, chartHeight, TFT_WHITE); // 3. 绘制柱子和标签 for (int i = 0; i < numCountries; i++) { int barHeight = map(data[i].todayCases, 0, maxValue, 0, chartHeight); int x = chartX + i * (barWidth + 5) + 2; int y = chartY + chartHeight - barHeight; tft.fillRect(x, y, barWidth, barHeight, TFT_BLUE); // 绘制柱子 // 在柱子下方写国家代码 tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.setTextSize(1); tft.setCursor(x, chartY + chartHeight + 5); tft.print(data[i].code); } }

这只是一个简单的示例。更复杂的图表(如折线图)需要存储一系列历史数据点,并计算每个点在屏幕上的位置进行连线。

5.3 低功耗优化与稳定性增强

对于需要长期运行的项目,稳定性和功耗是需要考虑的问题。

  • 稳定性增强

    • Wi-Fi重连机制:在loop()中定期检查WiFi.status(),如果断开连接,则尝试重新连接。重连逻辑应包含指数退避策略,避免频繁重试。
    • API请求异常处理:网络请求可能因各种原因(服务器错误、超时)失败。HTTPClient可以设置超时时间http.setTimeout(10000)。对于失败请求,应进行有限次数的重试,并在多次失败后进入休眠或显示明确错误。
    • 看门狗定时器(WatchDog):启用硬件看门狗,防止程序跑飞。在loop()中定期喂狗。对于SAMD51芯片,可以使用wdt.h库。
  • 低功耗优化

    • 深度睡眠:如果数据更新间隔很长(如每小时一次),可以在每次更新显示后,让Wio Terminal进入深度睡眠模式,到时间后由RTC或外部中断唤醒。这能极大降低功耗。注意,深度睡眠下Wi-Fi和CPU都会关闭,唤醒后需要重新初始化连接。
    • 降低屏幕亮度TFT_eSPI库允许通过tft.setBrightness()控制背光亮度(0-255)。在夜间或无人观看时,可以调低亮度或关闭背光。
    • 优化刷新频率:仅在数据真正变化时更新屏幕局部区域,避免全屏刷新。关闭调试串口输出也能节省少量功耗。

6. 常见问题排查与调试心得

在开发过程中,你几乎一定会遇到下面这些问题。这里记录了我的排查思路和解决方法。

6.1 网络连接失败与SSL证书问题

  • 问题现象:串口打印一直显示连接中“.”,或者提示连接失败。
  • 排查步骤
    1. 检查SSID和密码:最基础也最容易出错。确保没有多余空格,密码大小写正确。
    2. 检查路由器设置:有些路由器会设置MAC地址过滤或仅允许特定设备连接。检查Wio Terminal的MAC地址(可通过WiFi.macAddress()打印)是否被加入黑名单。
    3. 检查网络频段:确保路由器2.4GHz频段已开启,Wio Terminal不支持5GHz。
    4. 简化网络环境:如果连接复杂的企业网络或公共热点失败,先尝试连接一个简单的家庭Wi-Fi网络,以排除网络环境问题。
    5. SSL证书问题:当使用HTTPS链接时(disease.sh就是),单片机需要验证服务器证书。有时会因为根证书问题或时间不同步导致连接失败。可以尝试:
      • 更新Wio Terminal的核心固件和Wi-Fi固件。
      • 在代码中跳过证书验证(仅用于调试,不安全):http.begin(url, root_ca);其中root_ca可以留空或使用一个已知的证书,但更简单粗暴的调试方法是使用http.begin(url);对于某些库版本可能默认不验证。生产环境不推荐禁用验证

6.2 JSON解析错误与内存不足

  • 问题现象deserializeJson()返回错误,如NoMemoryInvalidInputTooDeep
  • 排查与解决
    • NoMemory(内存不足):这是最常见的问题。增大DynamicJsonDocument doc(capacity);中的capacity值。估算容量很关键。可以使用 ArduinoJson Assistant 在线工具,将你的API返回的JSON样例粘贴进去,它会帮你计算所需的最小容量。给这个值再增加20%-30%的余量。
    • InvalidInput(输入无效):检查payload字符串是否完整、是否以{开头。可能是网络请求未完全接收数据。在http.getString()前,可以用http.getSize()检查数据长度是否合理。或者,使用Stream方式逐步解析。
    • TooDeep(嵌套过深):检查JSON结构是否嵌套层数超过了库的默认限制(通常为10层)。对于disease.sh的国家数据,countryInfo对象内部有一层嵌套,远未达到限制。如果遇到,可以在deserializeJson()时传入DeserializationOption::NestingLimit提高限制。

6.3 屏幕显示异常与图形库使用陷阱

  • 问题现象:花屏、颜色错乱、文字显示不全、屏幕闪烁严重。
  • 排查与解决
    1. 确认User_Setup.h文件:这是TFT_eSPI库配置的重中之重。务必确认你使用的是为Wio Terminal特制的配置文件(如Setup206_Seeed_Wio_Terminal.h),并且已正确重命名和放置。
    2. 检查setRotation():旋转设置错误会导致绘图坐标完全错乱。如果你发现图形画在了奇怪的位置,首先检查这个值。
    3. 颜色格式TFT_eSPI默认使用16位RGB565颜色。TFT_REDTFT_GREEN等是预定义的颜色。如果你自己用tft.color565(255,0,0)定义红色,结果是正确的。
    4. 闪烁问题:如果整个屏幕在刷新时闪烁,说明你在每次更新时都调用了tft.fillScreen(TFT_BLACK)全屏清空。改为前面提到的“局部更新”策略。另外,可以尝试使用双缓冲,但Wio Terminal内存有限,TFT_eSPI的双缓冲会消耗大量内存,需谨慎使用。
    5. 内存不足导致显示异常:如果同时进行网络请求、JSON解析和复杂图形绘制,可能导致堆内存不足。监控Serial.println(ESP.getFreeHeap());查看剩余内存。优化策略包括:使用局部变量而非全局变量、及时释放String对象、减少不必要的图形操作、分段处理数据。

6.4 项目移植与适配其他开发板

这个项目的核心逻辑(网络请求、JSON解析、数据显示)具有很强的通用性。如果你想移植到其他开发板,如ESP32 DevKit、ESP8266 NodeMCU,甚至STM32+以太网/Wi-Fi模块的组合,只需要替换对应的硬件抽象层。

  • ESP32/ESP8266:这是最简单的移植目标。
    • Wi-Fi库:将#include <rpcWiFi.h>替换为#include <WiFi.h>(ESP32) 或#include <ESP8266WiFi.h>
    • HTTPClient:ESP32/ESP8266的Arduino核心自带HTTPClient,用法几乎完全相同。
    • 显示库:如果使用不同的屏幕(如OLED),则需要更换对应的驱动库(如Adafruit_SSD1306)。图形绘制API需要重写,但数据流逻辑完全复用。
  • STM32等:需要选择支持Arduino生态且有网络功能的开发板(如STM32+ESP8266 AT指令,或直接使用带网络接口的STM32型号)。重点在于配置正确的网络驱动库和硬件接口。

移植的关键在于理解分层:将硬件相关的代码(网络初始化、屏幕驱动)抽象出来,保持核心的业务逻辑(数据获取、解析、显示控制)不变。这样,你的代码将拥有更好的可维护性和可移植性。