ESP32 新手入門:用NodeMCU-32S+OLED打造網路氣象站

ESP32 新手救星!用 NodeMCU-32S 打造桌面氣象站,從網路抓氣象資料,免 API Key、解決斷線痛點。程式碼複製貼上即用,輕鬆完廣高質感 IoT 專案!

ESP32 新手入門:用NodeMCU-32S+OLED打造網路氣象站

如果你剛買了一塊 NodeMCU-32S (或是其它ESP32開發板),看著它密密麻麻的腳位不知道從何下手,這篇文章就是你的最佳起點!

我們不談艱深的理論,直接帶你用最簡單的方式,接上螢幕、連上 WiFi,做出一個能即時顯示當地氣溫與濕度的「桌面氣象站」。網路氣象資料傑森選擇用Open-Meteo ,它對 ESP32 等微控制器非常友善,能避免繁瑣的 SSL 憑證問題,且回傳的 JSON 格式簡潔輕量,不易造成記憶體不足。重點是:不用申請 API Key。

範例程式傑森幫大家都準備好了,程式碼複製貼上就能用!

為什麼選擇 NodeMCU-32S?

  • 經典款:它是 ESP32 系列中最標準的型號,網路上 90% 的教學都通用。
  • 雙核心:效能強大,跑氣象站根本是小菜一碟。
  • 穩定:相較於迷你版開發板,它內建的穩壓晶片通常較好,不易當機。
  • 不過其它ESP32開發板也同樣適用哦!

第一步:準備材料

  1. NodeMCU-32S 開發板 x1
  2. 0.96 吋 OLED 顯示螢幕 (I2C 介面, SSD1306 驅動) x1
  3. 麵包板杜邦線
NodeMCU-32S 相容版本 ESP32開發板 WiFi 藍牙 可用Arduino IDE
安可信原廠貨! 全腳位引出,還保持迷你的身型,插上麵包後還能插杜邦線,真的是太棒了! 和NodeMCU V2幾乎一樣尺寸! 有5V供電輸出,非常方便! 有了ESP32開發板,真的可以忘記原來的那些Arduino板子了! 可以用Arduino IDE開發,但效能更強大,還內建WiFi 傑森實測記錄,大家可以到 F 粉 絲 團 B 看貼文哦! ESP32-D0WDQ6 內置兩個低功耗 Xtensa® 32-bit LX6 MCU。片上存儲包括: • 448 KB 的 ROM,用於程序啟動和內核功能
ESP32S擴展板 適用於Nodemcu-32s 38Pin全引出
※ 不含ESP32S開發板,需另購! ESP32S專用擴展板 適用於Nodemcu-32s,其它型號都不相容哦!請留意。 38Pin全引出,無敵方便!

第二步:硬體接線

ESP32每個開發板的腳位可能不同,請務必先行了解。標準 ESP32 的 I2C 腳位是 GPIO 21GPIO 22

這個範例只需要4條線,簡單吧!

OLED 引腳NodeMCU-32S說明
GNDGND接地
VCC3V3 (或 5V)電源
SDAGPIO 21 (D21)SDA
SCLGPIO 22 (D22)SCL
⚠️ 新手注意: 很多教學會告訴你隨便接再改程式,但為了穩定性,請務必接在 21 (SDA)22 (SCL) 這兩個硬體專用腳位。

第三步:電腦環境設定

安裝驅動程式 (這是最常卡關的地方)

NodeMCU-32S 通常使用 CP2102 或 CH340 晶片來跟電腦溝通。

  • 插上 USB 線後,如果電腦沒有發出「登登」的聲音,或者找不到 COM Port,請去下載 CP210x DriverCH340 Driver 安裝。

Arduino IDE 設定

安裝開發板:如果之前已使用過ESP32開發板,就不用重複進行了。若是第一次,請參考我們另一篇專文教學哦!

在Arduino IDE中新增ESP32的開發板-以Nodemcu-32s為例
雖然Arduino Uno方便好用,如果真的需要WiFi功能,傑森一律建議直接換塊ESP32的板子,這是最直接的方法。 但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 個地方:

  1. ssid: 你的 WiFi 名稱。
  2. password: 你的 WiFi 密碼。
  3. LATITUDE / LONGITUDE: 你的經緯度 (用 Google Maps 查)。

👇 重點解析:

  1. 建議ESP32離WIFI分享器愈近愈好,若訊號不好,建議用手機分享熱點。記得,只能用2.4G的訊號哦!
  2. 這次用的是黃藍雙色0.96吋的OLED,上方16Pixel拿來做標題很合適。
  3. 【關鍵技巧 1】降低發射功率​:ESP32-C3 Super Mini 電路板較精簡,全速運轉容易電壓不穩當機​,降低到 8.5dBm 可以大幅增加穩定性,且不影響近距離連線。
  4. 【關鍵技巧 2】關閉省電模式​:預設 WiFi 會偷懶睡覺導致漏接封包,強制它不准睡​。
  5. 關鍵技巧 3】強制指定 DNS​:指定使用 Google DNS (8.8.8.8),避免解析網址失敗
  6. Open-Meteo這類服務流量極大,有時可能無法一次就連上,所以程式內會連續試3次,每次會等5秒,失敗就會間隔2秒再試,若真的都沒連上,那就停個幾分鐘再試了。
  7. 程式預設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 + 
                        "&current=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 的世界!有問題歡迎到我們粉絲團留言討論。

NodeMCU-32S 相容版本 ESP32開發板 WiFi 藍牙 可用Arduino IDE
安可信原廠貨! 全腳位引出,還保持迷你的身型,插上麵包後還能插杜邦線,真的是太棒了! 和NodeMCU V2幾乎一樣尺寸! 有5V供電輸出,非常方便! 有了ESP32開發板,真的可以忘記原來的那些Arduino板子了! 可以用Arduino IDE開發,但效能更強大,還內建WiFi 傑森實測記錄,大家可以到 F 粉 絲 團 B 看貼文哦! ESP32-D0WDQ6 內置兩個低功耗 Xtensa® 32-bit LX6 MCU。片上存儲包括: • 448 KB 的 ROM,用於程序啟動和內核功能