ESP32 新手入門:用NodeMCU-32S+OLED打造網路氣象站
ESP32 新手救星!用 NodeMCU-32S 打造桌面氣象站,從網路抓氣象資料,免 API Key、解決斷線痛點。程式碼複製貼上即用,輕鬆完廣高質感 IoT 專案!
如果你剛買了一塊 NodeMCU-32S (或是其它ESP32開發板),看著它密密麻麻的腳位不知道從何下手,這篇文章就是你的最佳起點!
我們不談艱深的理論,直接帶你用最簡單的方式,接上螢幕、連上 WiFi,做出一個能即時顯示當地氣溫與濕度的「桌面氣象站」。網路氣象資料傑森選擇用Open-Meteo ,它對 ESP32 等微控制器非常友善,能避免繁瑣的 SSL 憑證問題,且回傳的 JSON 格式簡潔輕量,不易造成記憶體不足。重點是:不用申請 API Key。
範例程式傑森幫大家都準備好了,程式碼複製貼上就能用!
為什麼選擇 NodeMCU-32S?
- 經典款:它是 ESP32 系列中最標準的型號,網路上 90% 的教學都通用。
- 雙核心:效能強大,跑氣象站根本是小菜一碟。
- 穩定:相較於迷你版開發板,它內建的穩壓晶片通常較好,不易當機。
- 不過其它ESP32開發板也同樣適用哦!
第一步:準備材料
- NodeMCU-32S 開發板 x1
- 0.96 吋 OLED 顯示螢幕 (I2C 介面, SSD1306 驅動) x1
- 麵包板 與 杜邦線


第二步:硬體接線
ESP32每個開發板的腳位可能不同,請務必先行了解。標準 ESP32 的 I2C 腳位是 GPIO 21 與 GPIO 22。
這個範例只需要4條線,簡單吧!
| OLED 引腳 | NodeMCU-32S | 說明 |
| GND | GND | 接地 |
| VCC | 3V3 (或 5V) | 電源 |
| SDA | GPIO 21 (D21) | SDA |
| SCL | GPIO 22 (D22) | SCL |
⚠️ 新手注意: 很多教學會告訴你隨便接再改程式,但為了穩定性,請務必接在 21 (SDA) 和 22 (SCL) 這兩個硬體專用腳位。
第三步:電腦環境設定
安裝驅動程式 (這是最常卡關的地方)
NodeMCU-32S 通常使用 CP2102 或 CH340 晶片來跟電腦溝通。
- 插上 USB 線後,如果電腦沒有發出「登登」的聲音,或者找不到 COM Port,請去下載 CP210x Driver 或 CH340 Driver 安裝。
Arduino IDE 設定
安裝開發板:如果之前已使用過ESP32開發板,就不用重複進行了。若是第一次,請參考我們另一篇專文教學哦!

選擇開發板:
- 進入
工具>開發板,選擇 Nodemcu-32s 。 - 或是選擇 ESP32 Dev Module 也可以。
安裝函式庫:
- 進入
工具>管理程式庫,搜尋並安裝:Adafruit SSD1306 SSD1306 驅動庫 (專門控制這顆 OLED 螢幕)Adafruit GFX Library Adafruit 圖形庫 (畫圓、畫線、寫字用的核心庫)ArduinoJson JSON 解析庫 (把網路上下載的文字變成變數)
第四步:上傳程式碼
範例程式碼已經幫你準備好了!而且有詳細的註解,只要你有一點Arduiono基礎,應該很容易看懂的哦!
程式主要有幾個部份:連上WIFI,準備好OLED,從Open-Meteo抓取氣象資料,然後顯示在OLED上。
氣象公開資料服務很多,Open-Meteo 最大的優點是完全免費且無需申請 API Key,大幅降低了程式開發的門檻。它支援 HTTP 協定(非加密),這對 ESP32 等微控制器非常友善,能避免繁瑣的 SSL 憑證問題,且回傳的 JSON 格式簡潔輕量,不易造成記憶體不足。
👇 上傳前只需要改 3 個地方:
ssid: 你的 WiFi 名稱。password: 你的 WiFi 密碼。LATITUDE/LONGITUDE: 你的經緯度 (用 Google Maps 查)。
👇 重點解析:
- 建議ESP32離WIFI分享器愈近愈好,若訊號不好,建議用手機分享熱點。記得,只能用2.4G的訊號哦!
- 這次用的是黃藍雙色0.96吋的OLED,上方16Pixel拿來做標題很合適。
- 【關鍵技巧 1】降低發射功率:ESP32-C3 Super Mini 電路板較精簡,全速運轉容易電壓不穩當機,降低到 8.5dBm 可以大幅增加穩定性,且不影響近距離連線。
- 【關鍵技巧 2】關閉省電模式:預設 WiFi 會偷懶睡覺導致漏接封包,強制它不准睡。
- 關鍵技巧 3】強制指定 DNS:指定使用 Google DNS (8.8.8.8),避免解析網址失敗
- Open-Meteo這類服務流量極大,有時可能無法一次就連上,所以程式內會連續試3次,每次會等5秒,失敗就會間隔2秒再試,若真的都沒連上,那就停個幾分鐘再試了。
- 程式預設10鐘會上Open-Meteo重抓資料,可自行修改。
/*
* NodeMCU-32S 桌面氣象站 (Open-Meteo 版)
* 適用開發板:DOIT ESP32 DEVKIT V1 / NodeMCU-32S
* 接線:SDA -> GPIO 21, SCL -> GPIO 22
*/
// ==========================================
// 1. 引入函式庫 (Libraries)
// ==========================================
#include <Wire.h> // I2C 通訊庫 (讓 ESP32 可以跟 OLED 講話)
#include <Adafruit_GFX.h> // Adafruit 圖形庫 (畫圓、畫線、寫字用的核心庫)
#include <Adafruit_SSD1306.h> // SSD1306 驅動庫 (專門控制這顆 OLED 螢幕)
#include <WiFi.h> // WiFi 連線庫 (讓 ESP32 連上網路)
#include <HTTPClient.h> // HTTP 客戶端 (像瀏覽器一樣發送請求)
#include <ArduinoJson.h> // JSON 解析庫 (把網路上下載的文字變成變數)
// ==========================================
// 2. 使用者設定區 (User Settings) - 請修改這裡
// ==========================================
const char* ssid = "WIFINAME"; // <--- 請輸入您的 WiFi 名稱
const char* password = "111222333"; // <--- 請輸入您的 WiFi 密碼
// 設定地點座標 (目前設定為台中)
// 你可以去 Google Maps 點一下你的位置查詢經緯度
String LATITUDE = "24.15"; // 緯度
String LONGITUDE = "120.67"; // 經度
// ==========================================
// 3. 硬體腳位與參數設定
// ==========================================
#define SCREEN_WIDTH 128 // OLED 螢幕寬度 (像素)
#define SCREEN_HEIGHT 64 // OLED 螢幕高度 (像素)
// ESP32-C3 Super Mini 的 I2C 腳位定義
// 如果你用的是其他板子,這兩行可能要改
#define SDA_PIN 21 // SDA
#define SCL_PIN 22 // SCL
// 宣告 OLED 物件
// &Wire: 使用預設的 I2C 通道
// -1: 代表沒有重置腳位 (ESP32 不需要手動重置螢幕)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// ==========================================
// 4. 全域變數 (Global Variables)
// ==========================================
float temperature = 0; // 儲存溫度的變數 (有小數點)
int humidity = 0; // 儲存濕度的變數 (整數)
bool isDataLoaded = false; // 標記:是否已經成功抓到過資料?(用來控制螢幕顯示內容)
// 計時器相關變數 (用來取代 delay,讓程式不會卡住)
unsigned long lastTime = 0; // 記錄上一次更新的時間
unsigned long timerDelay = 600000; // 更新間隔:10分鐘 (600,000 毫秒)
// ==========================================
// 5. 初始化設定 (Setup) - 開機只跑一次
// ==========================================
void setup() {
// 開啟 Serial 通訊,讓我們可以在電腦上看到 ESP32 的狀態
Serial.begin(115200);
delay(2000); // 稍微等 2 秒,避免開機訊息漏看
// 印出歡迎訊息
Serial.println(F("\n\n================================="));
Serial.println(F(" ESP32-C3 氣象站系統啟動中... "));
Serial.println(F("=================================\n"));
// --- 步驟 A: 啟動 OLED 螢幕 ---
Serial.print(F("[初始化] 啟動 OLED 螢幕... "));
// 指定 I2C 腳位 (SDA, SCL)
Wire.begin(SDA_PIN, SCL_PIN);
// 嘗試啟動螢幕。SSD1306_SWITCHCAPVCC 代表使用內部升壓電路
// 0x3C 是這顆螢幕的 I2C 位址 (有些是 0x3D,如果黑屏可以改改看)
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("失敗! (請檢查接線)"));
for(;;); // 如果螢幕壞了,程式就卡死在這裡,不再往下跑
}
Serial.println(F("成功"));
// 顯示開機畫面
display.clearDisplay(); // 清除畫面
display.setTextColor(SSD1306_WHITE);// 設定文字為白色
display.setTextSize(1); // 設定字體大小為 1 (最小)
display.setCursor(0, 10); // 設定文字起始位置 (x=0, y=10)
display.println(F("System Booting..."));
display.println(F("Connecting Network.."));
display.display(); // 真正的把畫面畫出來
// --- 步驟 B: 設定 WiFi (包含防當機設定) ---
Serial.println(F("[初始化] 設定 WiFi 參數..."));
WiFi.mode(WIFI_STA); // 設定為 Station 模式 (像手機一樣連 WiFi)
// 【關鍵技巧 1】降低發射功率
// ESP32-C3 Super Mini 電路板較精簡,全速運轉容易電壓不穩當機
// 降低到 8.5dBm 可以大幅增加穩定性,且不影響近距離連線
Serial.print(F(" - 降低發射功率至 8.5dBm (防當機)... "));
WiFi.setTxPower(WIFI_POWER_8_5dBm);
Serial.println(F("OK"));
// 【關鍵技巧 2】關閉省電模式
// 預設 WiFi 會偷懶睡覺導致漏接封包,強制它不准睡
Serial.print(F(" - 關閉 WiFi 省電模式 (增強訊號)... "));
WiFi.setSleep(false);
Serial.println(F("OK"));
// 【關鍵技巧 3】強制指定 DNS
// 指定使用 Google DNS (8.8.8.8),避免解析網址失敗
Serial.print(F(" - 指定 DNS 為 8.8.8.8... "));
IPAddress dns(8, 8, 8, 8);
WiFi.config(INADDR_NONE, INADDR_NONE, dns);
Serial.println(F("OK"));
// 開始連線
Serial.print(F("[WiFi] 正在連線至: "));
Serial.print(ssid);
WiFi.begin(ssid, password);
// 等待連線,如果超過 30 秒連不上就重開機
int limit = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
limit++;
if (limit > 60) {
Serial.println(F("\n[錯誤] WiFi 連線超時! 準備重開機..."));
ESP.restart(); // 軟體重開機指令
}
}
// 連線成功,印出 IP 位址
Serial.println(F("\n[WiFi] 連線成功!"));
Serial.print(F(" - IP 位址: "));
Serial.println(WiFi.localIP());
Serial.print(F(" - 訊號強度: "));
Serial.println(WiFi.RSSI());
// --- 步驟 C: 第一次抓取天氣 ---
getWeather();
}
// ==========================================
// 6. 主迴圈 (Loop) - 不斷重複執行
// ==========================================
void loop() {
// 這裡使用「非阻塞」的時間檢查法
// millis() 會回傳開機到現在經過的毫秒數
// 如果 (現在時間 - 上次更新時間) > 設定的間隔 (10分鐘)
if ((millis() - lastTime) > timerDelay) {
Serial.println(F("\n[計時器] 更新時間到了!"));
// 檢查 WiFi 是否還連著
if(WiFi.status() == WL_CONNECTED){
getWeather(); // 執行抓取天氣函式
} else {
Serial.println(F("[錯誤] WiFi 斷線! 嘗試重新連線..."));
WiFi.reconnect();
}
// 更新「上次更新時間」為現在
lastTime = millis();
}
}
// ==========================================
// 7. 更新螢幕畫面函式 (updateDisplay)
// ==========================================
void updateDisplay() {
Serial.println(F("[顯示] 正在更新 OLED 畫面..."));
display.clearDisplay(); // 清除舊畫面
// --- 繪製標題列 (上方白色區塊) ---
// fillRect(x, y, 寬, 高, 顏色) -> 畫一個實心矩形
display.fillRect(0, 0, 128, 16, SSD1306_WHITE);
// 因為背景是白色,所以字要設成黑色 (BLACK, WHITE 代表黑字白底)
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(4, 4); // 微調位置讓字置中
display.print(F("TAICHUNG CITY"));
// 右上角的小字
display.setCursor(100, 4);
display.print(F("WiFi"));
// --- 繪製數據區 (下方黑色區塊) ---
display.setTextColor(SSD1306_WHITE); // 改回白字
if (isDataLoaded) { // 如果已經成功抓到資料
// 1. 顯示溫度 (左邊大字)
display.setTextSize(3); // 字體放大 3 倍
display.setCursor(5, 30);
display.print((int)temperature); // 強制轉成整數顯示 (例如 13.5 變 13)
// 畫度數的小圈圈 (根據數字位數調整位置)
int circleX = (temperature >= 10) ? 45 : 25;
display.drawCircle(circleX, 30, 3, SSD1306_WHITE); // 外圈
display.drawCircle(circleX, 30, 2, SSD1306_WHITE); // 內圈(加粗效果)
// 顯示單位 "C"
display.setTextSize(2);
display.setCursor(circleX + 8, 30);
display.print(F("C"));
// 2. 畫中間的分隔線
display.drawLine(78, 20, 78, 60, SSD1306_WHITE);
// 3. 顯示濕度 (右邊小字)
display.setTextSize(1);
display.setCursor(85, 25);
display.print(F("HUMID")); // 濕度標籤
display.setTextSize(2);
display.setCursor(85, 40);
display.print(humidity); // 濕度數值
display.setTextSize(1);
display.print(F("%")); // 百分比符號
} else {
// 萬一還沒抓到資料,顯示等待中
display.setCursor(20, 35);
display.setTextSize(1);
display.print(F("Syncing Data..."));
}
display.display(); // 重要!這行指令才會真正把圖推送到螢幕上
Serial.println(F("[顯示] 畫面更新完成"));
}
// ==========================================
// 8. 抓取天氣函式
// ==========================================
void getWeather() {
Serial.println(F("\n-----------------------------"));
Serial.println(F("[HTTP] 準備抓取天氣資料..."));
// 設定最多重試 3 次
int maxRetries = 3;
bool success = false; // 標記是否成功
for (int attempt = 1; attempt <= maxRetries; attempt++) {
Serial.print(F("[嘗試] 第 "));
Serial.print(attempt);
Serial.print(F(" 次連線... "));
WiFiClient client;
HTTPClient http;
// 組合網址
String serverPath = "http://api.open-meteo.com/v1/forecast?latitude=" + LATITUDE +
"&longitude=" + LONGITUDE +
"¤t=temperature_2m,relative_humidity_2m&timezone=auto";
// 開始連線
if (http.begin(client, serverPath)) {
http.setTimeout(5000); // 設定 5 秒超時 (失敗就趕快重試,不用等太久)
int httpCode = http.GET();
if (httpCode == 200) {
// --- 成功抓到資料 ---
Serial.println(F("成功!"));
String payload = http.getString();
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (!error) {
temperature = doc["current"]["temperature_2m"];
humidity = doc["current"]["relative_humidity_2m"];
Serial.print(F(" >>> 溫度: ")); Serial.println(temperature);
Serial.print(F(" >>> 濕度: ")); Serial.println(humidity);
isDataLoaded = true;
updateDisplay(); // 更新螢幕
success = true; // 標記為成功
http.end();
break; // 【關鍵】成功了就跳出迴圈,不用再重試
} else {
Serial.print(F("JSON 解析失敗: "));
Serial.println(error.c_str());
}
} else {
// --- HTTP 錯誤 ---
Serial.print(F("伺服器回應錯誤: "));
Serial.println(httpCode);
// 如果是 refused (-1) 或 timeout (-11),通常重試有效
}
http.end(); // 記得關閉連線
} else {
Serial.println(F("無法建立連線 (Connect Failed)"));
}
// 如果還沒成功,且還有剩餘次數,就等待一下再試
if (!success && attempt < maxRetries) {
Serial.println(F(" -> 等待 2 秒後重試..."));
delay(2000); // 休息 2 秒讓網路穩定
}
}
// 如果試了 3 次還是失敗
if (!success) {
Serial.println(F("[遺憾] 3 次嘗試都失敗,請檢查網路狀況。"));
// 可以在螢幕上顯示一個小小的錯誤提示 (選擇性)
display.fillRect(0, 16, 128, 48, SSD1306_BLACK); // 清空下方
display.setCursor(20, 35);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.print(F("Net Error..."));
display.display();
}
Serial.println(F("-----------------------------\n"));
}

希望這篇教學能讓你成功踏入 ESP32 的世界!有問題歡迎到我們粉絲團留言討論。




