ESP8266 + OLED 实时天气显示教程 (Arduino + 心知天气 API)


所见即所得
原创
发布时间: 2025-12-01 09:04:38 | 阅读数 0收藏数 0评论数 0
封面
本文介绍了如何使用 Arduino IDE 配合 ESP8266 开发板,结合 OLED 屏幕和心知天气 API,搭建一个可实时显示天气信息的物联网终端。内容涵盖开发环境配置、硬件接线、API 密钥申请、自定义字符取模及数据显示等关键步骤,为初学者提供了一套完整、可操作的实践方案。
1

下载ArduinoIDE

我们要使用 Arduino IDE 编程工具,将编写好的程序烧录到单片机中。请前往 Arduino 官网首页,下载并安装该编程工具。

2

修改配置

下载完成后,打开 Arduino IDE,依次点击 文件 > 首选项,在“附加开发板管理器网址”一栏中填入以下地址:

http://arduino.esp8266.com/stable/package_esp8266com_index.json

然后点击“确定”保存设置。

3

开发准备

在完成开发板管理器地址的配置后,接下来需要安装 ESP8266 的开发板支持包。具体操作如下:

  1. 点击 Arduino IDE 顶部菜单栏中的 工具(Tools),然后选择 开发板(Board) > 开发板管理器(Boards Manager)
  2. 在弹出的“开发板管理器”窗口中,等待索引加载完成后,在搜索框中输入 ESP8266
  3. 找到由 ESP8266 Community 提供的 "esp8266 by ESP8266 Community" 条目,点击右侧的 “安装” 按钮,等待依赖包下载并自动安装完成。

安装成功后,关闭开发板管理器窗口。接着再次点击 工具(Tools) > 开发板(Board),在下拉列表中找到 ESP8266 模块(ESP8266 Modules) 分类,并从中选择 “Generic ESP8266 Module” 作为当前使用的开发板。

这一步确保了 Arduino IDE 能够正确识别和编译适用于 ESP8266 芯片的代码,为后续的程序烧录做好准备。

4

注册心知天气

请按以下步骤获取心知天气的 API 私钥,以便后续在程序中调用天气数据:

  1. 打开浏览器,在地址栏输入 “心知天气” 并访问其官方网站。
  2. 进入网站后,点击页面右上角的 “注册” 按钮,创建一个新账号;若已有账号,可直接点击 “登录”
  3. 成功登录后,在首页或导航栏中找到 “立即免费使用” 按钮并点击进入产品申请页面。
  4. “添加产品” 区域下,选择 “免费申请” 选项。
  5. 在弹出的产品版本列表中,选择 “免费版”(通常包含基础功能和有限的调用次数,适合开发测试使用)。
  6. 完成申请后,系统会为你生成一组 API 凭据,请务必 记录下其中的私钥(API Key 或 Private Key),因为后续在 Arduino 程序中请求天气数据时将需要用到该密钥。

请妥善保管此私钥,避免泄露,以免影响服务安全或产生非预期的调用费用。

5

接线图

在本项目中,我们使用了 ESP8266 开发板 与一块 I²C 接口的 OLED 显示屏 进行数据交互和可视化。该 OLED 屏幕通常采用 SSD1306 驱动芯片,具有低功耗、高对比度和清晰显示等优点,非常适合用于嵌入式系统的状态展示。

OLED 屏幕共有四个引脚,分别为:

  1. SCL(Serial Clock Line):I²C 通信的时钟信号线
  2. SDA(Serial Data Line):I²C 通信的数据信号线
  3. VCC:电源正极
  4. GND:电源地

为了正确连接 OLED 屏幕与 ESP8266 开发板,请按照以下方式接线:

  1. 将 OLED 的 SCL 引脚 连接到 ESP8266 的 D1 引脚(对应 GPIO5)
  2. 将 OLED 的 SDA 引脚 连接到 ESP8266 的 D2 引脚(对应 GPIO4)
  3. 将 OLED 的 VCC 引脚 连接到 ESP8266 开发板上的 3.3V 电源输出端口(注意:不可接 5V,以免损坏 OLED 模块)
  4. 将 OLED 的 GND 引脚 连接到 ESP8266 的 GND(接地)端口

完成硬件连接后,在 Arduino IDE 中需安装相应的 OLED 库(如 Adafruit_SSD1306Adafruit_GFX),并在代码中正确配置 I²C 地址(通常为 0x3C 或 0x3D),以便成功驱动屏幕并显示天气或其他传感器数据。

6

取字模

在使用取模软件为 OLED 屏幕生成自定义字符或图标时,请按照以下步骤操作:

  1. 打开取模软件
  2. 启动你所使用的字模提取工具(如 PCtoLCD2002),进入主界面。
  3. 切换到字符模式
  4. 在软件界面中找到模式选择区域,点击切换至 “字符模式”(Character Mode),以便对文字进行取模处理。
  5. 设置字体与尺寸
  6. 点击菜单栏中的 “设置”“选项”,在弹出的设置窗口中:
  7. 选择所需的 字体(如宋体、微软雅黑等)
  8. 设置合适的 字号(例如 12×12、16×16 等,需根据 OLED 屏幕分辨率和显示需求确定)
  9. 确保字符编码格式为 GB2312UTF-8(根据你的开发环境支持情况选择)
  10. 配置字模选项
  11. 在“字模选项”中进行如下关键设置:
  12. 输出格式:通常选择 C51 格式(即 C 语言数组格式)
  13. 扫描方式:一般选择 逐行式(横向),且 高位在前(MSB First)
  14. 取模方向:常用 阴码(背景为1,字为0)阳码(背景为0,字为1),需与 OLED 显示库的渲染逻辑一致(多数情况下使用阳码)
  15. 勾选 “包含空格”(如需对齐)或按需调整其他参数
  16. 设置完成后,点击 “确定” 保存配置。
  17. 输入字符并生成字模
  18. 在主界面的输入框中键入你想要显示的汉字、字母或符号(例如 “晴”、“25℃” 等),然后点击 “生成字模” 按钮。
RAR
字模软件.rar
702.13KB
7

代码

/*
* ESP8266 户外天气 + 日期显示(精简优化版)
* 功能说明:
* - 固定城市:北京(使用点阵字模显示“北”“京”)
* - 天气图标:仅当天气为 "Sunny"(晴)时显示“晴”字
* - 其他天气状态:直接显示英文文本(避免中文乱码)
* - 温度单位 ℃ 自动跟随数字,不重叠
* - 每10分钟自动更新一次天气
*/

#include <ESP8266WiFi.h> // Wi-Fi 连接库
#include <Wire.h> // I2C 通信库(用于 OLED)
#include <Adafruit_GFX.h> // Adafruit 图形基础库
#include <Adafruit_SSD1306.h> // SSD1306 OLED 驱动库
#include <ESP8266HTTPClient.h> // HTTP 客户端(用于请求天气 API)
#include <WiFiClientSecure.h> // 支持 HTTPS 的安全客户端
#include <ArduinoJson.h> // JSON 解析库

// ============ 用户配置区 ============
#define WIFI_SSID "vivoX200" // ← 你的 Wi-Fi 名称
#define WIFI_PASS "12345678" // ← 你的 Wi-Fi 密码
#define CITY_NAME "beijing" // 城市拼音(小写)
#define WEATHER_API_KEY "你的秘钥" // ← 你在 Seniverse 申请的 API 密钥

#define SCREEN_WIDTH 128 // OLED 屏幕宽度
#define SCREEN_HEIGHT 64 // OLED 屏幕高度
#define OLED_ADDR 0x3C // OLED 的 I2C 地址(通常为 0x3C 或 0x3D)
// ===================================

// 创建 OLED 显示对象
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// 全局变量:存储从 API 获取的数据
String weatherText = "Loading"; // 天气描述(英文)
String temperature = "--"; // 当前温度(摄氏度)
String dateStr = "----/--/--"; // 最后更新日期(YYYY-MM-DD)

// ========== 16x16 点阵中文字模(PROGMEM 存储在 Flash 中)==========
// “北” 字模
static const unsigned char PROGMEM bei_text[] = {
0x00,0x00,0x06,0x40,0x06,0x40,0x06,0x40,0x06,0x40,0x06,0x46,0x7E,0x58,0x06,0x60,
0x06,0x40,0x06,0x40,0x06,0x40,0x06,0x40,0x3E,0x42,0x66,0x42,0x06,0x7E,0x00,0x00
};

// “京” 字模
static const unsigned char PROGMEM jing_text[] = {
0x00,0x00,0x01,0x00,0x01,0x80,0x7F,0xFE,0x00,0x00,0x1F,0xF8,0x18,0x18,0x18,0x18,
0x18,0x18,0x1F,0xF8,0x08,0x80,0x0C,0xB0,0x18,0x98,0x30,0x86,0x03,0x80,0x02,0x00
};

// “晴” 字模(仅 Sunny 时显示)
static const unsigned char PROGMEM qing_text[] = {
0x00,0x20,0x00,0x20,0x7B,0xFE,0x48,0x20,0x49,0xFC,0x48,0x20,0x4B,0xFE,0x78,0x00,
0x49,0xFC,0x49,0x04,0x49,0xFC,0x49,0x04,0x79,0xFC,0x49,0x04,0x01,0x14,0x01,0x08
};

// “℃” 符号字模
static const unsigned char PROGMEM du_text[] = {
0x60,0x00,0x91,0xF4,0x96,0x0C,0x6C,0x04,0x08,0x04,0x18,0x00,0x18,0x00,0x18,0x00,
0x18,0x00,0x18,0x00,0x18,0x00,0x08,0x00,0x0C,0x04,0x06,0x08,0x01,0xF0,0x00,0x00
};
// ================================================================

void setup() {
// 初始化串口(用于调试)
Serial.begin(115200);
// 初始化 I2C 总线
Wire.begin();

// 初始化 OLED 屏幕
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
// 如果初始化失败,进入死循环(防止程序继续运行出错)
for (;;);
}

// 显示启动信息
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.println("正在连接 Wi-Fi...");
display.display();

// 连接 Wi-Fi
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}

// 首次获取天气数据
fetchWeather();
}

// 从 ISO8601 时间字符串(如 "2025-04-05T14:30:00")中提取日期部分 "2025-04-05"
String extractDate(String isoTime) {
if (isoTime.length() >= 10) {
return isoTime.substring(0, 10); // 截取前10个字符
}
return "----/--/--"; // 格式错误时返回占位符
}

// 从心知天气 API 获取当前天气数据
void fetchWeather() {
// 检查 Wi-Fi 是否已连接
if (WiFi.status() != WL_CONNECTED) {
weatherText = "无 Wi-Fi";
temperature = "--";
dateStr = "----/--/--";
return;
}

// 构造 API 请求 URL(使用英文返回,便于匹配 "Sunny")
String url = "https://api.seniverse.com/v3/weather/now.json?key=" + String(WEATHER_API_KEY)
+ "&location=" + CITY_NAME + "&language=en&unit=c";

// 创建安全的 HTTPS 客户端(跳过证书验证,仅用于开发)
WiFiClientSecure client;
client.setInsecure(); // ⚠️ 注意:生产环境应验证证书

// 发起 HTTP GET 请求
HTTPClient http;
http.begin(client, url);
int httpResponseCode = http.GET();
Serial.println(url);
if (httpResponseCode == 200) {
// 请求成功,读取响应内容
String payload = http.getString();

// 解析 JSON 数据(分配 256 字节内存,足够当前用途)
DynamicJsonDocument doc(256);
DeserializationError error = deserializeJson(doc, payload);

if (!error && doc.containsKey("results")) {
// 成功解析,提取所需字段
JsonObject now = doc["results"][0]["now"];
weatherText = now["text"].as<String>(); // 天气描述(英文)
temperature = now["temperature"].as<String>(); // 温度
String lastUpdate = doc["results"][0]["last_update"].as<String>();
dateStr = extractDate(lastUpdate); // 提取日期
} else {
// JSON 解析失败
weatherText = "解析错误";
temperature = "--";
dateStr = "----/--/--";
}
} else {
// HTTP 请求失败(如 403、404 等)
weatherText = "HTTP " + String(httpResponseCode);
temperature = "--";
dateStr = "----/--/--";
}

// 关闭 HTTP 连接
http.end();
}

void loop() {
// 每 10 分钟(600,000 毫秒)更新一次天气
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate > 600000) {
fetchWeather();
lastUpdate = millis();
}

// 清空屏幕并开始绘制新内容
display.clearDisplay();
display.setTextSize(2); // 默认使用大号字体
display.setTextColor(WHITE);

// 第一行:显示日期(例如:2025-04-05)
display.setCursor(0, 0);
display.print(dateStr);

// 第二行:显示城市名“北京”(使用点阵字模)
display.drawBitmap(48, 24, bei_text, 16, 16, 1); // “北” 显示在 (48,24)
display.drawBitmap(64, 24, jing_text, 16, 16, 1); // “京” 显示在 (64,24)

// 第三行:显示温度 + ℃ 符号
display.setCursor(0, 48);
display.print(temperature); // 先打印温度数字

// 计算温度字符串的宽度,以便将 ℃ 符号紧随其后
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(temperature, 0, 48, &x1, &y1, &w, &h);
display.drawBitmap(w + 4, 48, du_text, 16, 16, 1); // ℃ 显示在数字右侧

// 显示天气状态:
if (weatherText == "Clear") {
// 如果是晴天,显示“晴”字图标
display.drawBitmap(96, 48, qing_text, 16, 16, 1);
} else {
// 其他天气(如 Cloudy, Rain 等),显示英文文本
display.setTextSize(2); // 切换为小号字体
display.setCursor(90, 52); // 调整 Y 坐标使文字垂直居中
display.print(weatherText);
}

// 将缓冲区内容推送到 OLED 屏幕
display.display();

// 每 5 秒刷新一次屏幕(避免频繁刷新)
delay(5000);
}
阅读记录0
点赞0
收藏0
禁止 本文未经作者允许授权,禁止转载
猜你喜欢
评论/提问(已发布 0 条)
评论 评论
收藏 收藏
分享 分享
pdf下载 下载
pdf下载 举报