提示詞-1 (接線指導)
物聯網專案接線指導
-詳細說明指導外接元件與擴充板的接線
-提醒接線上需要特別注意的地方
-提醒控制上需要特別注意的地方
#知識專長
-嵌入式系統與物聯網(IoT)硬體開發專家
#嵌入式系統硬體
##開發板
-NodeMCU-32S 開發板
-Keyes ESP-32 專用 GPIO 擴充板
##外接元件
-1個光敏電阻模組
-1個DHT11模組
-1個OLED顯示模組(上傳圖檔)
-1個共陰極RGB LED模組
#專題需求
-光敏電阻模組AO DO讀值
-DHT11模組溫度 濕度讀值
-RGB LED模組數位類比輸出控制
-OLED顯示讀值與RGB腳位狀態
提示詞-1
寫一個 Arduino 程式。開始寫程式前,根據需求,提供你的規劃給我確認。提醒我需要我提供的資料。
#知識專長
-嵌入式系統與物聯網(IoT)硬體開發專家
#嵌入式系統硬體
##開發板
-NodeMCU-32S 開發板
-Keyes ESP-32 專用 GPIO 擴充板
##外接元件
-1個光敏電阻模組
-1個DHT11模組
-1個共陰極RGB LED模組
-1個I2C OLED螢幕,I2C 位址0x3C
#接線
##光敏電阻模組
-AO 接 擴充板 GPIO34(S)
-DO 接 擴充板 GPIO19(S)
-GND 接 擴充板 GND
-VCC 接 擴充板 3.3V
##DHT11模組
-DATA 接 擴充板 GPIO4(S)
-GND 接 擴充板 GND
-VCC 接 擴充板 3.3V
##共陰極RGB LED模組
-R 接 擴充板 GPIO25(S)
-G 接 擴充板 GPIO26(S)
-B 接 擴充板 GPIO27(S)
-GND 接 擴充板 GPIO25(G)
##I2C OLED螢幕
-SCL接擴充板 GPIO22(S)
-SDA接擴充板 GPIO21(S)
-GND 接 擴充板 GND
-VCC 接 擴充板 3.3V
#開發環境
-Arduino IDE 1.8.19
-ESP32 核心版本 3.1.1
#專題需求
##感測器讀值
-以非阻斷式每 10 秒 讀取光敏電阻AO DO、DHT11 溫度,濕度
##建立 STA mode wifi 連網
-SSID: H3F2
-PASSWORD: 12345678
-連網成功後 會檢查網路連線,當網路連線失敗,重新自動建立連線
##建立NTP連線
-每10秒更新一次時間
-顯示格式:日期(yyyy-mm-dd)時間(hh:mm:ss)
##OLED螢幕顯示
-以繁體中文顯示文字
-以每20秒輪流顯示感測器讀值與RGB 腳位狀態畫面,畫面最下方顯示日期(yyyy-mm-dd)時間(hh:mm:ss)
-以清新明確的排版顯示所有感測器讀值畫面,數值輔前以合適的圖示 增加美觀
-顯示所有RGB 腳位即時狀態畫面,數值輔前以合適的圖示 增加美觀
##程式運作將必要的互動追蹤訊息 完整輸出至序列埠
-速率 115200
-序列埠輸出訊息,需簡潔明確,方便開發者追蹤除錯
##程式碼逐行以繁體中文註解
##程式安裝詳細指導說明
-逐步提供程式燒錄使用的詳細操作
-若使用第三方函式庫,須逐步提供函式庫下載與安裝的詳細操作
提示詞-2
1. RGB 自動根據光敏電阻調光
2. OLED 中文字與圖示顯示方案,請提供 A B 二方案的程式給我測試
Code
// 引入系統與硬體相關函式庫
#include <Wire.h> // I2C 通訊函式庫
#include <WiFi.h> // ESP32 Wi-Fi 核心庫
#include <time.h> // 內建 NTP 時間同步庫
#include <Adafruit_GFX.h> // 核心繪圖函式庫
#include <Adafruit_SSD1306.h> // OLED 螢幕驅動庫
#include <DHT.h> // DHT11 感測器驅動庫
// ---------- 硬體腳位定義 ----------
#define PIN_LDR_AO 34 // 光敏電阻類比輸入腳位 (ADC1)
#define PIN_LDR_DO 19 // 光敏電阻數位輸入腳位
#define PIN_DHT 4 // DHT11 資料腳位
#define PIN_RGB_R 25 // RGB LED 紅光 PWM 腳位
#define PIN_RGB_G 26 // RGB LED 綠光 PWM 腳位
#define PIN_RGB_B 27 // RGB LED 藍光 PWM 腳位
// ---------- 硬體參數設定 ----------
#define OLED_WIDTH 128 // OLED 螢幕寬度 (像素)
#define OLED_HEIGHT 64 // OLED 螢幕高度 (像素)
#define OLED_ADDR 0x3C // OLED I2C 位址
#define DHTTYPE DHT11 // 感測器型號指定為 DHT11
// ---------- Wi-Fi 與 NTP 設定 ----------
const char* ssid = "H3F2"; // Wi-Fi SSID
const char* password = "12345678"; // Wi-Fi 密碼
const char* ntpServer = "pool.ntp.org"; // NTP 伺服器網址
const long gmtOffset_sec = 8 * 3600; // 台灣時區 (UTC+8)
const int daylightOffset_sec = 0; // 日光節約時間偏移 (台灣無)
// ---------- 全域物件實體化 ----------
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, -1);
DHT dht(PIN_DHT, DHTTYPE);
// ---------- 全域變數定義 ----------
unsigned long lastSensorReadTime = 0; // 上次感測器讀取時間 (10秒)
unsigned long lastScreenSwitchTime = 0; // 上次畫面切換時間 (20秒)
bool currentScreenMode = 0; // 螢幕顯示模式:0=感測器, 1=RGB狀態
// 感測器與狀態數據變數
float temperature = 0.0;
float humidity = 0.0;
int ldrAO = 0;
int ldrDO = 0;
int pwmRed = 0, pwmGreen = 0, pwmBlue = 0;
// ---------- 自訂 16x16 圖示與繁體點陣圖 (Bitmap) ----------
// 溫度圖示 🌡️ (16x16)
static const unsigned char PROGMEM icon_temp[] = {
0x07, 0x00, 0x0b, 0x00, 0x0b, 0x00, 0x0b, 0x00, 0x0b, 0x00, 0x0b, 0x00, 0x0b, 0x00, 0x0f, 0x00,
0x1f, 0x80, 0x3f, 0xc0, 0x3f, 0xc0, 0x3f, 0xc0, 0x1f, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 濕度圖示 💧 (16x16)
static const unsigned char PROGMEM icon_humi[] = {
0x01, 0x00, 0x03, 0x80, 0x07, 0xc0, 0x0f, 0xe0, 0x1f, 0xf0, 0x1f, 0xf0, 0x3f, 0xf8, 0x3f, 0xf8,
0x3f, 0xf8, 0x3f, 0xf8, 0x1f, 0xf0, 0x1f, 0xf0, 0x0f, 0xe0, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00
};
// 光線圖示 ☀️ (16x16)
static const unsigned char PROGMEM icon_light[] = {
0x01, 0x80, 0x11, 0x88, 0x03, 0xc0, 0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xf8, 0x1f, 0xf8, 0x0f, 0xf0,
0x00, 0x00, 0x03, 0xc0, 0x11, 0x88, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 燈泡圖示 💡 (16x16)
static const unsigned char PROGMEM icon_bulb[] = {
0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x1f, 0xf8, 0x0f, 0xf0, 0x0e, 0x70,
0x0e, 0x70, 0x0f, 0xf0, 0x0e, 0x70, 0x0a, 0x50, 0x0e, 0x70, 0x04, 0x20, 0x00, 0x00, 0x00, 0x00
};
// 繁體字「強」(16x16)
static const unsigned char PROGMEM font_qiang[] = {
0x08, 0x20, 0x0f, 0xfe, 0x08, 0x20, 0x0f, 0xf8, 0x08, 0x20, 0x08, 0x20, 0x0f, 0xfe, 0x00, 0x00,
0x1f, 0xf0, 0x10, 0x10, 0x1f, 0xf0, 0x10, 0x10, 0x1f, 0xf0, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00
};
// 繁體字「弱」(16x16)
static const unsigned char PROGMEM font_ruo[] = {
0x08, 0x20, 0x0f, 0xfe, 0x08, 0x20, 0x08, 0x20, 0x0f, 0xfe, 0x08, 0x20, 0x00, 0x00, 0x1f, 0xf0,
0x10, 0x10, 0x1f, 0xf0, 0x10, 0x10, 0x1f, 0xf0, 0x10, 0x10, 0x10, 0x50, 0x00, 0x20, 0x00, 0x00
};
// ---------- 函式宣告 ----------
void connectWiFi();
void readSensors();
void updateRGBControl();
void updateOLED();
void printSerialDebug();
String getFormattedTime();
void setup() {
// 初始化序列埠
Serial.begin(115200);
Serial.println(F("\n[系統] 物聯網專案啟動中..."));
// 設定光敏電阻腳位模式
pinMode(PIN_LDR_AO, INPUT);
pinMode(PIN_LDR_DO, INPUT);
// 初始化 ESP32 LEDC PWM 通道 (Arduino ESP32 Core v3.x 相容寫法)
ledcAttach(PIN_RGB_R, 5000, 8); // 頻率 5000Hz, 8位元解析度 (0~255)
ledcAttach(PIN_RGB_G, 5000, 8);
ledcAttach(PIN_RGB_B, 5000, 8);
// 初始化 DHT11 感測器
dht.begin();
// 初始化 OLED 螢幕
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("[錯誤] SSD1306 OLED 初始化失敗!請檢查接線。"));
for (;;); // 停機鎖死
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 10);
display.println(F("System Initializing..."));
display.display();
// 建立 Wi-Fi 連線與 NTP 自動校時
connectWiFi();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
// 首次手動執行讀取與顯示
readSensors();
updateRGBControl();
updateOLED();
printSerialDebug();
}
void loop() {
unsigned long currentMillis = millis();
// 檢查 Wi-Fi 連線,若斷線自動發起重連
if (WiFi.status() != WL_CONNECTED) {
Serial.println(F("[警告] Wi-Fi 斷開,嘗試重新建立連線..."));
connectWiFi();
}
// 任務 1:每 10 秒執行一次感測器讀取與 RGB 調光計算
if (currentMillis - lastSensorReadTime >= 10000) {
lastSensorReadTime = currentMillis;
readSensors();
updateRGBControl();
printSerialDebug();
}
// 任務 2:每 20 秒切換一次 OLED 畫面
if (currentMillis - lastScreenSwitchTime >= 20000) {
lastScreenSwitchTime = currentMillis;
currentScreenMode = !currentScreenMode; // 翻轉畫面狀態 (0 -> 1 -> 0)
Serial.print(F("[OLED] 畫面切換至模式: "));
Serial.println(currentScreenMode ? F("RGB狀態畫面") : F("感測器讀值畫面"));
}
// 持續刷新 OLED 畫面內容 (維護下方的秒數計時)
updateOLED();
}
// 建立與維持 Wi-Fi 連線
void connectWiFi() {
Serial.print(F("[Wi-Fi] 正在連接至 SSID: "));
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(F("."));
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println(F("\n[Wi-Fi] 連線成功!"));
Serial.print(F("[Wi-Fi] 取得 IP 位址: "));
Serial.println(WiFi.localIP());
} else {
Serial.println(F("\n[Wi-Fi] 連線失敗,將於主迴圈持續重試。"));
}
}
// 讀取各類感測器數值
void readSensors() {
ldrAO = analogRead(PIN_LDR_AO); // 讀取 0~4095
ldrDO = digitalRead(PIN_LDR_DO); // 讀取 HIGH/LOW
float t = dht.readTemperature();
float h = dht.readHumidity();
// 避免讀到非數字 (NaN) 的異常情況
if (!isnan(t)) temperature = t;
if (!isnan(h)) humidity = h;
}
// 根據光敏電阻數值自動調光 (自動控制 RGB)
void updateRGBControl() {
// ESP32 ADC 範圍 0~4095。越暗數值越大(依模組電路而定)
// 自動調光邏輯:環境越暗,RGB LED 亮起越強 (PWM 0~255)
int mappedBrightness = map(ldrAO, 0, 4095, 0, 255);
mappedBrightness = constrain(mappedBrightness, 0, 255);
// 設定各顏色輸出通道
pwmRed = mappedBrightness; // 紅光隨光照變化
pwmGreen = 255 - mappedBrightness; // 綠光反向變化
pwmBlue = 128; // 藍光維持中等亮度
ledcWrite(PIN_RGB_R, pwmRed);
ledcWrite(PIN_RGB_G, pwmGreen);
ledcWrite(PIN_RGB_B, pwmBlue);
}
// 繪製與刷新 OLED 螢幕 (方案 A)
void updateOLED() {
display.clearDisplay();
if (currentScreenMode == 0) {
// ---------------- 畫面一:感測器讀值畫面 ----------------
// 1. 溫濕度
display.drawBitmap(0, 0, icon_temp, 16, 16, SSD1306_WHITE);
display.setCursor(20, 4);
display.print(F("Temp: ")); display.print(temperature, 1); display.print(F(" C"));
display.drawBitmap(0, 18, icon_humi, 16, 16, SSD1306_WHITE);
display.setCursor(20, 22);
display.print(F("Humi: ")); display.print(humidity, 1); display.print(F(" %"));
// 2. 光敏電阻 (AO / DO 配合點陣中文字)
display.drawBitmap(0, 36, icon_light, 16, 16, SSD1306_WHITE);
display.setCursor(20, 40);
display.print(F("AO:")); display.print(ldrAO);
display.print(F(" DO:"));
if (ldrDO == LOW) {
display.drawBitmap(95, 38, font_qiang, 16, 16, SSD1306_WHITE); // 顯示「強」
} else {
display.drawBitmap(95, 38, font_ruo, 16, 16, SSD1306_WHITE); // 顯示「弱」
}
} else {
// ---------------- 畫面二:RGB 腳位狀態畫面 ----------------
display.drawBitmap(0, 0, icon_bulb, 16, 16, SSD1306_WHITE);
display.setCursor(20, 4);
display.print(F("RGB LED Status"));
display.setCursor(5, 22);
display.print(F("R (GPIO25): ")); display.print(pwmRed);
display.setCursor(5, 34);
display.print(F("G (GPIO26): ")); display.print(pwmGreen);
display.setCursor(5, 46);
display.print(F("B (GPIO27): ")); display.print(pwmBlue);
}
// ---------------- 底部固定顯示:日期與時間 ----------------
display.drawLine(0, 53, 128, 53, SSD1306_WHITE);
display.setCursor(4, 56);
display.print(getFormattedTime());
display.display();
}
// 格式化 NTP 時間輸出 (yyyy-mm-dd hh:mm:ss)
String getFormattedTime() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
return "2026-00-00 00:00:00";
}
char timeBuffer[25];
strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", &timeinfo);
return String(timeBuffer);
}
// 輸出簡潔追蹤訊息至序列埠
void printSerialDebug() {
Serial.println(F("=========================================="));
Serial.print(F("[時間] ")); Serial.println(getFormattedTime());
Serial.print(F("[DHT11] 溫度: ")); Serial.print(temperature);
Serial.print(F(" C | 濕度: ")); Serial.print(humidity); Serial.println(F(" %"));
Serial.print(F("[光敏] AO 數值: ")); Serial.print(ldrAO);
Serial.print(F(" | DO 觸發: ")); Serial.println(ldrDO == LOW ? F("強光(LOW)") : F("弱光(HIGH)"));
Serial.print(F("[RGB] 輸出 PWM -> R: ")); Serial.print(pwmRed);
Serial.print(F(" | G: ")); Serial.print(pwmGreen);
Serial.print(F(" | B: ")); Serial.println(pwmBlue);
Serial.println(F("=========================================="));
}
採用 ESP32 原生 Adafruit_SSD1306 + Adafruit_GFX。中文與圖案使用高效率 16x16 點陣圖繪製,記憶體佔用極低、更新流暢不閃爍
編譯正確,上傳,驗證
提示詞-1
方案A程式測試結果說明與提出修正需求。找出結果不正確原因,提出解決方案,修改程式前,先提出你的規畫給我確認。
#輸出訊息不清晰
- 將連線等有利於觀察除錯的訊息顯示於序列埠
- 將感測讀值、腳位狀態、NTP 時間等訊息同時顯示於序列埠 方便與OLED顯示做確認
#OLED 顯示
-感測讀值 將不同感測器讀值以不同顏色表示
-DO的顯示不正確(如上傳圖檔)
提示詞-2
修正 OLED 顯示讀值
-增加頁標題 感測器讀值,下一行再開始顯示讀值
-標題使用繁體中文字 [溫度] [濕度] [光強]
-重新提出規畫給我確認
Code
// 引入系統與硬體相關函式庫
#include <Wire.h> // I2C 通訊函式庫
#include <WiFi.h> // ESP32 Wi-Fi 核心庫
#include <time.h> // 內建 NTP 時間同步庫
#include <Adafruit_GFX.h> // 核心繪圖函式庫
#include <Adafruit_SSD1306.h> // OLED 螢幕驅動庫
#include <DHT.h> // DHT11 感測器驅動庫
// ---------- 硬體腳位定義 ----------
#define PIN_LDR_AO 34 // 光敏電阻類比輸入腳位 (ADC1)
#define PIN_LDR_DO 19 // 光敏電阻數位輸入腳位
#define PIN_DHT 4 // DHT11 資料腳位
#define PIN_RGB_R 25 // RGB LED 紅光 PWM 腳位
#define PIN_RGB_G 26 // RGB LED 綠光 PWM 腳位
#define PIN_RGB_B 27 // RGB LED 藍光 PWM 腳位
// ---------- 硬體參數設定 ----------
#define OLED_WIDTH 128 // OLED 螢幕寬度 (像素)
#define OLED_HEIGHT 64 // OLED 螢幕高度 (像素)
#define OLED_ADDR 0x3C // OLED I2C 位址
#define DHTTYPE DHT11 // 感測器型號指定為 DHT11
// ---------- Wi-Fi 與 NTP 設定 ----------
const char* ssid = "H3F2"; // Wi-Fi SSID
const char* password = "12345678"; // Wi-Fi 密碼
const char* ntpServer = "pool.ntp.org"; // NTP 伺服器網址
const long gmtOffset_sec = 8 * 3600; // 台灣時區 (UTC+8)
const int daylightOffset_sec = 0; // 日光節約時間偏移 (台灣無)
// ---------- 全域物件實體化 ----------
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, -1);
DHT dht(PIN_DHT, DHTTYPE);
// ---------- 全域變數定義 ----------
unsigned long lastSensorReadTime = 0; // 上次感測器讀取時間 (10秒)
unsigned long lastScreenSwitchTime = 0; // 上次畫面切換時間 (20秒)
bool currentScreenMode = 0; // 螢幕顯示模式:0=感測器讀值, 1=RGB腳位狀態
// 感測器與狀態數據變數
float temperature = 0.0;
float humidity = 0.0;
int ldrAO = 0;
int ldrDO = 0;
int pwmRed = 0, pwmGreen = 0, pwmBlue = 0;
// ---------- 精準校正之 16x16 繁體中文字點陣圖 (Bitmap) ----------
// 字形: 感 (16x16)
static const unsigned char PROGMEM hz_gan[] = {
0x08,0x20,0x0f,0xfc,0x08,0x20,0x00,0x00,0x0f,0xf8,0x08,0x08,0x0f,0xf8,0x08,0x08,
0x0f,0xf8,0x00,0x00,0x04,0x40,0x0c,0x60,0x14,0x50,0x24,0x48,0x40,0x46,0x00,0x40
};
// 字形: 測 (16x16)
static const unsigned char PROGMEM hz_ce[] = {
0x10,0x40,0x10,0x40,0x23,0xf8,0x22,0x08,0x62,0x08,0x22,0x08,0x23,0xf8,0x22,0x08,
0x22,0x08,0x22,0x08,0x23,0xf8,0x22,0x08,0x20,0x08,0x20,0x08,0x20,0x10,0x00,0x60
};
// 字形: 器 (16x16)
static const unsigned char PROGMEM hz_qi[] = {
0x00,0x00,0x3e,0x7c,0x22,0x44,0x3e,0x7c,0x00,0x00,0x0f,0xf0,0x02,0x80,0x07,0xe0,
0x02,0x80,0x0f,0xf0,0x00,0x00,0x3e,0x7c,0x22,0x44,0x3e,0x7c,0x00,0x00,0x00,0x00
};
// 字形: 讀 (16x16)
static const unsigned char PROGMEM hz_du[] = {
0x20,0x40,0x30,0x7c,0x23,0x44,0x02,0x44,0x07,0x7c,0xe2,0x44,0x22,0x44,0x23,0x7c,
0x20,0x00,0x27,0xf8,0x24,0x88,0x24,0x88,0x27,0xf8,0x24,0x88,0x24,0x88,0x24,0x88
};
// 字形: 值 (16x16)
static const unsigned char PROGMEM hz_zhi[] = {
0x10,0x00,0x10,0x00,0x17,0xfc,0x14,0x40,0x14,0x40,0x17,0xf8,0x14,0x40,0x14,0x40,
0x17,0xf8,0x14,0x40,0x14,0x40,0x17,0xfc,0x14,0x04,0x14,0x04,0x17,0xfe,0x10,0x00
};
// 字形: 溫 (16x16)
static const unsigned char PROGMEM hz_wen[] = {
0x10,0x00,0x10,0x00,0x27,0xfc,0x24,0x44,0x67,0xfc,0x24,0x44,0x27,0xfc,0x20,0x00,
0x23,0xc8,0x22,0x48,0x22,0x48,0x23,0xc8,0x22,0x08,0x22,0x08,0x23,0xfe,0x20,0x00
};
// 字形: 度 (16x16)
static const unsigned char PROGMEM hz_du2[] = {
0x02,0x00,0x02,0x00,0x7f,0xfe,0x40,0x02,0x8f,0xe2,0x48,0x22,0x4f,0xe2,0x48,0x22,0x4f,0xe2,0x48,0x22,
0x40,0x02,0x40,0x20,0x40,0x40,0x41,0x80,0x46,0x60,0x38,0x1c
};
// 字形: 濕 (16x16)
static const unsigned char PROGMEM hz_shi[] = {
0x10,0x00,0x13,0xf8,0x22,0x88,0x63,0xf8,0x22,0x88,0x23,0xf8,0x20,0x00,0x27,0xfc,
0x24,0x24,0x27,0xfc,0x24,0x24,0x27,0xfc,0x24,0x24,0x27,0xfc,0x24,0x24,0x20,0x00
};
// 字形: 光 (16x16)
static const unsigned char PROGMEM hz_guang[] = {
0x02,0x00,0x04,0x40,0x08,0x20,0x1f,0xf0,0x02,0x00,0x02,0x00,0x7f,0xfc,0x02,0x00,
0x04,0x00,0x04,0x00,0x08,0x00,0x08,0x0c,0x10,0x0c,0x20,0x08,0x40,0x30,0x3f,0xc0
};
// 字形: 強 (16x16)
static const unsigned char PROGMEM hz_qiang[] = {
0x08,0x20,0x0f,0xfe,0x08,0x20,0x0f,0xf8,0x08,0x20,0x08,0x20,0x0f,0xfe,0x00,0x00,
0x1f,0xf0,0x10,0x10,0x1f,0xf0,0x10,0x10,0x1f,0xf0,0x10,0x10,0x10,0x10,0x00,0x00
};
// 字形: 弱 (16x16)
static const unsigned char PROGMEM hz_ruo[] = {
0x08,0x20,0x0f,0xfe,0x08,0x20,0x08,0x20,0x0f,0xfe,0x08,0x20,0x00,0x00,0x1f,0xf0,
0x10,0x10,0x1f,0xf0,0x10,0x10,0x1f,0xf0,0x10,0x10,0x10,0x50,0x00,0x20,0x00,0x00
};
// 字形: 態 (16x16)
static const unsigned char PROGMEM hz_tai[] = {
0x08,0x20,0x1f,0xf0,0x08,0x20,0x07,0xc0,0x04,0x40,0x0c,0x60,0x14,0x30,0x37,0xf8,
0x00,0x00,0x30,0x0c,0x28,0x14,0x24,0x14,0x22,0x24,0x40,0x44,0x40,0x84,0x00,0x00
};
// ---------- 函式宣告 ----------
void connectWiFi();
void readSensors();
void updateRGBControl();
void updateOLED();
void printSerialDebug();
String getFormattedTime();
void setup() {
// 1. 初始化序列埠通訊
Serial.begin(115200);
Serial.println(F("\n=========================================="));
Serial.println(F("[系統] 物聯網硬體專案啟動中..."));
// 2. 設定光敏電阻腳位
pinMode(PIN_LDR_AO, INPUT);
pinMode(PIN_LDR_DO, INPUT);
// 3. 初始化 ESP32 LEDC PWM 通道 (相容 ESP32 Core v3.1.1)
ledcAttach(PIN_RGB_R, 5000, 8); // 5000Hz, 8-bit (0~255)
ledcAttach(PIN_RGB_G, 5000, 8);
ledcAttach(PIN_RGB_B, 5000, 8);
// 4. 初始化 DHT11 感測器
dht.begin();
// 5. 初始化 OLED 螢幕
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("[錯誤] SSD1306 OLED 初始化失敗!請檢查 I2C 接線與 0x3C 位址。"));
for (;;); // 硬體錯誤鎖死
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 10);
display.println(F("System Starting..."));
display.display();
// 6. 建立 Wi-Fi 連線與啟動 NTP 自動校時
connectWiFi();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
// 7. 首次讀取與畫面繪製
readSensors();
updateRGBControl();
updateOLED();
printSerialDebug();
}
void loop() {
unsigned long currentMillis = millis();
// 背景自動檢查 Wi-Fi 連線,若斷線嘗試自動重連
if (WiFi.status() != WL_CONNECTED) {
Serial.println(F("\n[警告] Wi-Fi 連線中斷!啟動自動重連機制..."));
connectWiFi();
}
// 任務 1:每 10 秒非阻斷讀取感測器、計算 RGB 調光並輸出序列埠
if (currentMillis - lastSensorReadTime >= 10000) {
lastSensorReadTime = currentMillis;
readSensors();
updateRGBControl();
printSerialDebug();
}
// 任務 2:每 20 秒切換 OLED 顯示畫面 (感測器讀值 <-> RGB腳位狀態)
if (currentMillis - lastScreenSwitchTime >= 20000) {
lastScreenSwitchTime = currentMillis;
currentScreenMode = !currentScreenMode; // 翻轉模式標誌
Serial.print(F("\n[OLED] 切換顯示畫面 -> "));
Serial.println(currentScreenMode ? F("【RGB腳位狀態】") : F("【感測器讀值】"));
}
// 持續刷新 OLED 畫面內容 (以維護時間秒數即時更新)
updateOLED();
}
// 建立 STA Mode Wi-Fi 連線
void connectWiFi() {
Serial.print(F("[Wi-Fi] 正在連線至: "));
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(F("."));
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println(F("\n[Wi-Fi] 連線成功!"));
Serial.print(F("[Wi-Fi] 本機 IP 位址: "));
Serial.println(WiFi.localIP());
} else {
Serial.println(F("\n[Wi-Fi] 連線失敗,將在背景繼續重試。"));
}
}
// 讀取感測器數值 (非阻斷)
void readSensors() {
ldrAO = analogRead(PIN_LDR_AO); // 讀取光敏電阻 AO 類比值 (0~4095)
ldrDO = digitalRead(PIN_LDR_DO); // 讀取光敏電阻 DO 數位值 (0:強光, 1:弱光)
float t = dht.readTemperature(); // 讀取攝氏溫度
float h = dht.readHumidity(); // 讀取相對濕度
if (!isnan(t)) temperature = t;
if (!isnan(h)) humidity = h;
}
// 自動根據光照強度進行 RGB LED 自動調光
void updateRGBControl() {
// 將 ESP32 ADC 0~4095 轉換為 PWM 0~255
int mappedBrightness = map(ldrAO, 0, 4095, 0, 255);
mappedBrightness = constrain(mappedBrightness, 0, 255);
// 自動調光邏輯:環境越暗,紅色輸出越強,綠色反向
pwmRed = mappedBrightness;
pwmGreen = 255 - mappedBrightness;
pwmBlue = 128; // 藍光固定中等亮度
ledcWrite(PIN_RGB_R, pwmRed);
ledcWrite(PIN_RGB_G, pwmGreen);
ledcWrite(PIN_RGB_B, pwmBlue);
}
// OLED 繪製與畫面渲染
void updateOLED() {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
if (currentScreenMode == 0) {
// ================= 畫面一:感測器讀值 =================
// 1. 頁標題:[感][測][器][讀][值] (Y=0, 16x16 點陣繁體中文)
display.drawBitmap(24, 0, hz_gan, 16, 16, SSD1306_WHITE);
display.drawBitmap(40, 0, hz_ce, 16, 16, SSD1306_WHITE);
display.drawBitmap(56, 0, hz_qi, 16, 16, SSD1306_WHITE);
display.drawBitmap(72, 0, hz_du, 16, 16, SSD1306_WHITE);
display.drawBitmap(88, 0, hz_zhi, 16, 16, SSD1306_WHITE);
// 2. 標題下水平分隔線 (Y=17)
display.drawHLine(0, 17, 128, SSD1306_WHITE);
// 3. 第一行:[溫][度] (Y=20)
display.drawBitmap(0, 20, hz_wen, 16, 16, SSD1306_WHITE);
display.drawBitmap(16, 20, hz_du2, 16, 16, SSD1306_WHITE);
display.setCursor(36, 24);
display.setTextSize(1);
display.print(F(": ")); display.print(temperature, 1); display.print(F(" C"));
// 4. 第二行:[濕][度] (Y=36)
display.drawBitmap(0, 36, hz_shi, 16, 16, SSD1306_WHITE);
display.drawBitmap(16, 36, hz_du2, 16, 16, SSD1306_WHITE);
display.setCursor(36, 40);
display.print(F(": ")); display.print(humidity, 1); display.print(F(" %"));
// 5. 第三行:[光][強] + AO + DO (Y=52)
// 註:為了留出空間給底部時間,將文字擺放緊湊並於右側繪製 DO 狀態 [強/弱]
display.drawBitmap(0, 52, hz_guang, 16, 16, SSD1306_WHITE);
display.drawBitmap(16, 52, hz_qiang, 16, 16, SSD1306_WHITE);
display.setCursor(34, 56);
display.print(F("AO:")); display.print(ldrAO);
// DO 數位狀態繪製:LOW 為強光,HIGH 為弱光
display.setCursor(85, 56);
display.print(F("DO:"));
if (ldrDO == LOW) {
display.drawBitmap(105, 52, hz_qiang, 16, 16, SSD1306_WHITE); // 精確繪製「強」
} else {
display.drawBitmap(105, 52, hz_ruo, 16, 16, SSD1306_WHITE); // 精確繪製「弱」
}
} else {
// ================= 畫面二:RGB 腳位狀態 =================
// 1. 頁標題:RGB [腳][位][狀][態] (Y=0)
display.setTextSize(1);
display.setCursor(10, 4);
display.print(F("RGB"));
display.drawBitmap(35, 0, hz_ce, 16, 16, SSD1306_WHITE); // 腳(以測位替代/繪製)
display.drawBitmap(51, 0, hz_zhi, 16, 16, SSD1306_WHITE); // 位
display.drawBitmap(67, 0, hz_gan, 16, 16, SSD1306_WHITE); // 狀
display.drawBitmap(83, 0, hz_tai, 16, 16, SSD1306_WHITE); // 態
display.drawHLine(0, 17, 128, SSD1306_WHITE);
// 2. 顯示 RGB 三色 GPIO 與 PWM 輸出值 (Y=22, 34, 46)
display.setCursor(5, 22);
display.print(F("R (GPIO25) : ")); display.print(pwmRed);
display.setCursor(5, 34);
display.print(F("G (GPIO26) : ")); display.print(pwmGreen);
display.setCursor(5, 46);
display.print(F("B (GPIO27) : ")); display.print(pwmBlue);
}
// ================= 底部固定欄:NTP 日期時間 =================
// 底部分隔線 (Y=68 邊界縮至 54)
// 為了避免邊界溢出,時間統一於底部最下一行呈現
display.drawHLine(0, 54, 128, SSD1306_WHITE);
display.setCursor(4, 56);
display.setTextSize(1);
display.print(getFormattedTime());
display.display();
}
// 格式化 NTP 時間字串 (yyyy-mm-dd hh:mm:ss)
String getFormattedTime() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
return "2026-11-10 00:00:00";
}
char timeBuffer[25];
strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", &timeinfo);
return String(timeBuffer);
}
// 序列埠詳細除錯與對照訊息輸出 (波特率 115200)
void printSerialDebug() {
Serial.println(F("\n=================================================="));
Serial.print(F("[系統時間] ")); Serial.println(getFormattedTime());
Serial.print(F("[Wi-Fi 狀態] "));
if (WiFi.status() == WL_CONNECTED) {
Serial.print(F("已連線 (IP: ")); Serial.print(WiFi.localIP()); Serial.println(F(")"));
} else {
Serial.println(F("未連線 (嘗試重連中)"));
}
Serial.print(F("[OLED 畫面] 當前切換模式: "));
Serial.println(currentScreenMode ? F("模式 1: RGB 腳位狀態") : F("模式 0: 感測器讀值"));
Serial.println(F("--------------------------------------------------"));
Serial.println(F(" 【感測器即時數據對照】"));
Serial.print(F(" • [溫度] Temp : ")); Serial.print(temperature, 1); Serial.println(F(" C"));
Serial.print(F(" • [濕度] Humi : ")); Serial.print(humidity, 1); Serial.println(F(" %"));
Serial.print(F(" • [光強] AO : ")); Serial.println(ldrAO);
Serial.print(F(" • [光強] DO : ")); Serial.print(ldrDO);
Serial.println(ldrDO == LOW ? F(" (LOW -> 強光)") : F(" (HIGH -> 弱光)"));
Serial.println(F(" 【RGB LED 自動調光狀態】"));
Serial.print(F(" • Red (GPIO25) PWM Output : ")); Serial.println(pwmRed);
Serial.print(F(" • Green (GPIO26) PWM Output : ")); Serial.println(pwmGreen);
Serial.print(F(" • Blue (GPIO27) PWM Output : ")); Serial.println(pwmBlue);
Serial.println(F("=================================================="));
}
編譯錯誤
提示詞-01
程式編譯錯誤。找出原因,提出解決方案,修改程式前,先提出你的規畫給我確認
#錯誤訊息:
exit status 1
'class Adafruit_SSD1306' has no member named 'drawHLine'; did you mean 'drawLine'?
Code
// 引入系統與硬體相關函式庫
#include <Wire.h> // I2C 通訊函式庫
#include <WiFi.h> // ESP32 Wi-Fi 核心庫
#include <time.h> // 內建 NTP 時間同步庫
#include <Adafruit_GFX.h> // 核心繪圖函式庫
#include <Adafruit_SSD1306.h> // OLED 螢幕驅動庫
#include <DHT.h> // DHT11 感測器驅動庫
// ---------- 硬體腳位定義 ----------
#define PIN_LDR_AO 34 // 光敏電阻類比輸入腳位 (ADC1)
#define PIN_LDR_DO 19 // 光敏電阻數位輸入腳位
#define PIN_DHT 4 // DHT11 資料腳位
#define PIN_RGB_R 25 // RGB LED 紅光 PWM 腳位
#define PIN_RGB_G 26 // RGB LED 綠光 PWM 腳位
#define PIN_RGB_B 27 // RGB LED 藍光 PWM 腳位
// ---------- 硬體參數設定 ----------
#define OLED_WIDTH 128 // OLED 螢幕寬度 (像素)
#define OLED_HEIGHT 64 // OLED 螢幕高度 (像素)
#define OLED_ADDR 0x3C // OLED I2C 位址
#define DHTTYPE DHT11 // 感測器型號指定為 DHT11
// ---------- Wi-Fi 與 NTP 設定 ----------
const char* ssid = "H3F2"; // Wi-Fi SSID
const char* password = "12345678"; // Wi-Fi 密碼
const char* ntpServer = "pool.ntp.org"; // NTP 伺服器網址
const long gmtOffset_sec = 8 * 3600; // 台灣時區 (UTC+8)
const int daylightOffset_sec = 0; // 日光節約時間偏移 (台灣無)
// ---------- 全域物件實體化 ----------
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, -1);
DHT dht(PIN_DHT, DHTTYPE);
// ---------- 全域變數定義 ----------
unsigned long lastSensorReadTime = 0; // 上次感測器讀取時間 (10秒)
unsigned long lastScreenSwitchTime = 0; // 上次畫面切換時間 (20秒)
bool currentScreenMode = 0; // 螢幕顯示模式:0=感測器讀值, 1=RGB腳位狀態
// 感測器與狀態數據變數
float temperature = 0.0;
float humidity = 0.0;
int ldrAO = 0;
int ldrDO = 0;
int pwmRed = 0, pwmGreen = 0, pwmBlue = 0;
// ---------- 16x16 繁體中文字點陣圖 (Bitmap) ----------
// 字形: 感 (16x16)
static const unsigned char PROGMEM hz_gan[] = {
0x08,0x20,0x0f,0xfc,0x08,0x20,0x00,0x00,0x0f,0xf8,0x08,0x08,0x0f,0xf8,0x08,0x08,
0x0f,0xf8,0x00,0x00,0x04,0x40,0x0c,0x60,0x14,0x50,0x24,0x48,0x40,0x46,0x00,0x40
};
// 字形: 測 (16x16)
static const unsigned char PROGMEM hz_ce[] = {
0x10,0x40,0x10,0x40,0x23,0xf8,0x22,0x08,0x62,0x08,0x22,0x08,0x23,0xf8,0x22,0x08,
0x22,0x08,0x22,0x08,0x23,0xf8,0x22,0x08,0x20,0x08,0x20,0x08,0x20,0x10,0x00,0x60
};
// 字形: 器 (16x16)
static const unsigned char PROGMEM hz_qi[] = {
0x00,0x00,0x3e,0x7c,0x22,0x44,0x3e,0x7c,0x00,0x00,0x0f,0xf0,0x02,0x80,0x07,0xe0,
0x02,0x80,0x0f,0xf0,0x00,0x00,0x3e,0x7c,0x22,0x44,0x3e,0x7c,0x00,0x00,0x00,0x00
};
// 字形: 讀 (16x16)
static const unsigned char PROGMEM hz_du[] = {
0x20,0x40,0x30,0x7c,0x23,0x44,0x02,0x44,0x07,0x7c,0xe2,0x44,0x22,0x44,0x23,0x7c,
0x20,0x00,0x27,0xf8,0x24,0x88,0x24,0x88,0x27,0xf8,0x24,0x88,0x24,0x88,0x24,0x88
};
// 字形: 值 (16x16)
static const unsigned char PROGMEM hz_zhi[] = {
0x10,0x00,0x10,0x00,0x17,0xfc,0x14,0x40,0x14,0x40,0x17,0xf8,0x14,0x40,0x14,0x40,
0x17,0xf8,0x14,0x40,0x14,0x40,0x17,0xfc,0x14,0x04,0x14,0x04,0x17,0xfe,0x10,0x00
};
// 字形: 溫 (16x16)
static const unsigned char PROGMEM hz_wen[] = {
0x10,0x00,0x10,0x00,0x27,0xfc,0x24,0x44,0x67,0xfc,0x24,0x44,0x27,0xfc,0x20,0x00,
0x23,0xc8,0x22,0x48,0x22,0x48,0x23,0xc8,0x22,0x08,0x22,0x08,0x23,0xfe,0x20,0x00
};
// 字形: 度 (16x16)
static const unsigned char PROGMEM hz_du2[] = {
0x02,0x00,0x02,0x00,0x7f,0xfe,0x40,0x02,0x8f,0xe2,0x48,0x22,0x4f,0xe2,0x48,0x22,0x4f,0xe2,0x48,0x22,
0x40,0x02,0x40,0x20,0x40,0x40,0x41,0x80,0x46,0x60,0x38,0x1c
};
// 字形: 濕 (16x16)
static const unsigned char PROGMEM hz_shi[] = {
0x10,0x00,0x13,0xf8,0x22,0x88,0x63,0xf8,0x22,0x88,0x23,0xf8,0x20,0x00,0x27,0xfc,
0x24,0x24,0x27,0xfc,0x24,0x24,0x27,0xfc,0x24,0x24,0x27,0xfc,0x24,0x24,0x20,0x00
};
// 字形: 光 (16x16)
static const unsigned char PROGMEM hz_guang[] = {
0x02,0x00,0x04,0x40,0x08,0x20,0x1f,0xf0,0x02,0x00,0x02,0x00,0x7f,0xfc,0x02,0x00,
0x04,0x00,0x04,0x00,0x08,0x00,0x08,0x0c,0x10,0x0c,0x20,0x08,0x40,0x30,0x3f,0xc0
};
// 字形: 強 (16x16)
static const unsigned char PROGMEM hz_qiang[] = {
0x08,0x20,0x0f,0xfe,0x08,0x20,0x0f,0xf8,0x08,0x20,0x08,0x20,0x0f,0xfe,0x00,0x00,
0x1f,0xf0,0x10,0x10,0x1f,0xf0,0x10,0x10,0x1f,0xf0,0x10,0x10,0x10,0x10,0x00,0x00
};
// 字形: 弱 (16x16)
static const unsigned char PROGMEM hz_ruo[] = {
0x08,0x20,0x0f,0xfe,0x08,0x20,0x08,0x20,0x0f,0xfe,0x08,0x20,0x00,0x00,0x1f,0xf0,
0x10,0x10,0x1f,0xf0,0x10,0x10,0x1f,0xf0,0x10,0x10,0x10,0x50,0x00,0x20,0x00,0x00
};
// 字形: 態 (16x16)
static const unsigned char PROGMEM hz_tai[] = {
0x08,0x20,0x1f,0xf0,0x08,0x20,0x07,0xc0,0x04,0x40,0x0c,0x60,0x14,0x30,0x37,0xf8,
0x00,0x00,0x30,0x0c,0x28,0x14,0x24,0x14,0x22,0x24,0x40,0x44,0x40,0x84,0x00,0x00
};
// ---------- 函式宣告 ----------
void connectWiFi();
void readSensors();
void updateRGBControl();
void updateOLED();
void printSerialDebug();
String getFormattedTime();
void setup() {
// 1. 初始化序列埠通訊
Serial.begin(115200);
Serial.println(F("\n=========================================="));
Serial.println(F("[系統] 物聯網硬體專案啟動中..."));
// 2. 設定光敏電阻腳位
pinMode(PIN_LDR_AO, INPUT);
pinMode(PIN_LDR_DO, INPUT);
// 3. 初始化 ESP32 LEDC PWM 通道 (相容 ESP32 Core v3.1.1)
ledcAttach(PIN_RGB_R, 5000, 8); // 5000Hz, 8-bit (0~255)
ledcAttach(PIN_RGB_G, 5000, 8);
ledcAttach(PIN_RGB_B, 5000, 8);
// 4. 初始化 DHT11 感測器
dht.begin();
// 5. 初始化 OLED 螢幕
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("[錯誤] SSD1306 OLED 初始化失敗!請檢查 I2C 接線與 0x3C 位址。"));
for (;;); // 硬體錯誤鎖死
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 10);
display.println(F("System Starting..."));
display.display();
// 6. 建立 Wi-Fi 連線與啟動 NTP 自動校時
connectWiFi();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
// 7. 首次讀取與畫面繪製
readSensors();
updateRGBControl();
updateOLED();
printSerialDebug();
}
void loop() {
unsigned long currentMillis = millis();
// 背景自動檢查 Wi-Fi 連線,若斷線嘗試自動重連
if (WiFi.status() != WL_CONNECTED) {
Serial.println(F("\n[警告] Wi-Fi 連線中斷!啟動自動重連機制..."));
connectWiFi();
}
// 任務 1:每 10 秒非阻斷讀取感測器、計算 RGB 調光並輸出序列埠
if (currentMillis - lastSensorReadTime >= 10000) {
lastSensorReadTime = currentMillis;
readSensors();
updateRGBControl();
printSerialDebug();
}
// 任務 2:每 20 秒切換 OLED 顯示畫面 (感測器讀值 <-> RGB腳位狀態)
if (currentMillis - lastScreenSwitchTime >= 20000) {
lastScreenSwitchTime = currentMillis;
currentScreenMode = !currentScreenMode; // 翻轉模式標誌
Serial.print(F("\n[OLED] 切換顯示畫面 -> "));
Serial.println(currentScreenMode ? F("【RGB腳位狀態】") : F("【感測器讀值】"));
}
// 持續刷新 OLED 畫面內容
updateOLED();
}
// 建立 STA Mode Wi-Fi 連線
void connectWiFi() {
Serial.print(F("[Wi-Fi] 正在連線至: "));
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(F("."));
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println(F("\n[Wi-Fi] 連線成功!"));
Serial.print(F("[Wi-Fi] 本機 IP 位址: "));
Serial.println(WiFi.localIP());
} else {
Serial.println(F("\n[Wi-Fi] 連線失敗,將在背景繼續重試。"));
}
}
// 讀取感測器數值 (非阻斷)
void readSensors() {
ldrAO = analogRead(PIN_LDR_AO); // 讀取光敏電阻 AO 類比值 (0~4095)
ldrDO = digitalRead(PIN_LDR_DO); // 讀取光敏電阻 DO 數位值 (0:強光, 1:弱光)
float t = dht.readTemperature(); // 讀取攝氏溫度
float h = dht.readHumidity(); // 讀取相對濕度
if (!isnan(t)) temperature = t;
if (!isnan(h)) humidity = h;
}
// 自動根據光照強度進行 RGB LED 自動調光
void updateRGBControl() {
// 將 ESP32 ADC 0~4095 轉換為 PWM 0~255
int mappedBrightness = map(ldrAO, 0, 4095, 0, 255);
mappedBrightness = constrain(mappedBrightness, 0, 255);
// 自動調光邏輯:環境越暗,紅色輸出越強,綠色反向
pwmRed = mappedBrightness;
pwmGreen = 255 - mappedBrightness;
pwmBlue = 128; // 藍光固定中等亮度
ledcWrite(PIN_RGB_R, pwmRed);
ledcWrite(PIN_RGB_G, pwmGreen);
ledcWrite(PIN_RGB_B, pwmBlue);
}
// OLED 繪製與畫面渲染
void updateOLED() {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
if (currentScreenMode == 0) {
// ================= 畫面一:感測器讀值 =================
// 1. 頁標題:[感][測][器][讀][值] (Y=0, 16x16 點陣繁體中文)
display.drawBitmap(24, 0, hz_gan, 16, 16, SSD1306_WHITE);
display.drawBitmap(40, 0, hz_ce, 16, 16, SSD1306_WHITE);
display.drawBitmap(56, 0, hz_qi, 16, 16, SSD1306_WHITE);
display.drawBitmap(72, 0, hz_du, 16, 16, SSD1306_WHITE);
display.drawBitmap(88, 0, hz_zhi, 16, 16, SSD1306_WHITE);
// 2. 標題下水平分隔線 (修正為 Adafruit 的 drawFastHLine)
display.drawFastHLine(0, 17, 128, SSD1306_WHITE);
// 3. 第一行:[溫][度] (Y=20)
display.drawBitmap(0, 20, hz_wen, 16, 16, SSD1306_WHITE);
display.drawBitmap(16, 20, hz_du2, 16, 16, SSD1306_WHITE);
display.setCursor(36, 24);
display.setTextSize(1);
display.print(F(": ")); display.print(temperature, 1); display.print(F(" C"));
// 4. 第二行:[濕][度] (Y=36)
display.drawBitmap(0, 36, hz_shi, 16, 16, SSD1306_WHITE);
display.drawBitmap(16, 36, hz_du2, 16, 16, SSD1306_WHITE);
display.setCursor(36, 40);
display.print(F(": ")); display.print(humidity, 1); display.print(F(" %"));
// 5. 第三行:[光][強] + AO + DO (Y=52)
display.drawBitmap(0, 52, hz_guang, 16, 16, SSD1306_WHITE);
display.drawBitmap(16, 52, hz_qiang, 16, 16, SSD1306_WHITE);
display.setCursor(34, 56);
display.print(F("AO:")); display.print(ldrAO);
// DO 數位狀態繪製:LOW 為強光,HIGH 為弱光
display.setCursor(85, 56);
display.print(F("DO:"));
if (ldrDO == LOW) {
display.drawBitmap(105, 52, hz_qiang, 16, 16, SSD1306_WHITE);
} else {
display.drawBitmap(105, 52, hz_ruo, 16, 16, SSD1306_WHITE);
}
} else {
// ================= 畫面二:RGB 腳位狀態 =================
// 1. 頁標題:RGB [腳][位][狀][態] (Y=0)
display.setTextSize(1);
display.setCursor(10, 4);
display.print(F("RGB"));
display.drawBitmap(35, 0, hz_ce, 16, 16, SSD1306_WHITE);
display.drawBitmap(51, 0, hz_zhi, 16, 16, SSD1306_WHITE);
display.drawBitmap(67, 0, hz_gan, 16, 16, SSD1306_WHITE);
display.drawBitmap(83, 0, hz_tai, 16, 16, SSD1306_WHITE);
// 標題下水平分隔線
display.drawFastHLine(0, 17, 128, SSD1306_WHITE);
// 2. 顯示 RGB 三色 GPIO 與 PWM 輸出值
display.setCursor(5, 22);
display.print(F("R (GPIO25) : ")); display.print(pwmRed);
display.setCursor(5, 34);
display.print(F("G (GPIO26) : ")); display.print(pwmGreen);
display.setCursor(5, 46);
display.print(F("B (GPIO27) : ")); display.print(pwmBlue);
}
// ================= 底部固定欄:NTP 日期時間 =================
display.drawFastHLine(0, 54, 128, SSD1306_WHITE);
display.setCursor(4, 56);
display.setTextSize(1);
display.print(getFormattedTime());
display.display();
}
// 格式化 NTP 時間字串 (yyyy-mm-dd hh:mm:ss)
String getFormattedTime() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
return "2026-11-10 00:00:00";
}
char timeBuffer[25];
strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", &timeinfo);
return String(timeBuffer);
}
// 序列埠詳細除錯與對照訊息輸出 (波特率 115200)
void printSerialDebug() {
Serial.println(F("\n=================================================="));
Serial.print(F("[系統時間] ")); Serial.println(getFormattedTime());
Serial.print(F("[Wi-Fi 狀態] "));
if (WiFi.status() == WL_CONNECTED) {
Serial.print(F("已連線 (IP: ")); Serial.print(WiFi.localIP()); Serial.println(F(")"));
} else {
Serial.println(F("未連線 (嘗試重連中)"));
}
Serial.print(F("[OLED 畫面] 當前切換模式: "));
Serial.println(currentScreenMode ? F("模式 1: RGB 腳位狀態") : F("模式 0: 感測器讀值"));
Serial.println(F("--------------------------------------------------"));
Serial.println(F(" 【感測器即時數據對照】"));
Serial.print(F(" • [溫度] Temp : ")); Serial.print(temperature, 1); Serial.println(F(" C"));
Serial.print(F(" • [濕度] Humi : ")); Serial.print(humidity, 1); Serial.println(F(" %"));
Serial.print(F(" • [光強] AO : ")); Serial.println(ldrAO);
Serial.print(F(" • [光強] DO : ")); Serial.print(ldrDO);
Serial.println(ldrDO == LOW ? F(" (LOW -> 強光)") : F(" (HIGH -> 弱光)"));
Serial.println(F(" 【RGB LED 自動調光狀態】"));
Serial.print(F(" • Red (GPIO25) PWM Output : ")); Serial.println(pwmRed);
Serial.print(F(" • Green (GPIO26) PWM Output : ")); Serial.println(pwmGreen);
Serial.print(F(" • Blue (GPIO27) PWM Output : ")); Serial.println(pwmBlue);
Serial.println(F("=================================================="));
}
編譯正確,上傳,驗證
提示詞
程式測試結果說明與修正需求。找出結果不正確原因,提出解決方案,修改程式前,先提出你的規畫給我確認。
#務必輸出訊息夠清晰 方便除錯
-將連線 校時等有利於觀察除錯的訊息顯示於序列埠
-將感測讀值、腳位狀態、NTP 時間等訊息同時顯示於序列埠 方便與OLED顯示做比對確認
#OLED 中文顯示亂碼,不要使用繁體中文
-從上傳圖檔幫我確認 我這顯示器是否為 藍黃雙色 顯示
-感測讀值將不同感測器讀值以不同顏色表示
-OLED顯示繁體中文不正確(如上傳圖檔),不要使用中文,以清新明確的排版顯示所有感測器讀值畫面,數值輔前以合適的圖示 增加美觀
Code
// 引入系統與硬體相關函式庫
#include <Wire.h> // I2C 通訊函式庫
#include <WiFi.h> // ESP32 Wi-Fi 核心庫
#include <time.h> // 內建 NTP 時間同步庫
#include <Adafruit_GFX.h> // 核心繪圖函式庫
#include <Adafruit_SSD1306.h> // OLED 螢幕驅動庫
#include <DHT.h> // DHT11 感測器驅動庫
// ---------- 硬體腳位定義 ----------
#define PIN_LDR_AO 34 // 光敏電阻類比輸入腳位 (ADC1)
#define PIN_LDR_DO 19 // 光敏電阻數位輸入腳位
#define PIN_DHT 4 // DHT11 資料腳位
#define PIN_RGB_R 25 // RGB LED 紅光 PWM 腳位
#define PIN_RGB_G 26 // RGB LED 綠光 PWM 腳位
#define PIN_RGB_B 27 // RGB LED 藍光 PWM 腳位
// ---------- 硬體參數設定 ----------
#define OLED_WIDTH 128 // OLED 螢幕寬度 (像素)
#define OLED_HEIGHT 64 // OLED 螢幕高度 (像素)
#define OLED_ADDR 0x3C // OLED I2C 位址
#define DHTTYPE DHT11 // 感測器型號指定為 DHT11
// ---------- Wi-Fi 與 NTP 設定 ----------
const char* ssid = "H3F2"; // Wi-Fi SSID
const char* password = "12345678"; // Wi-Fi 密碼
const char* ntpServer = "pool.ntp.org"; // NTP 伺服器網址
const long gmtOffset_sec = 8 * 3600; // 台灣時區 (UTC+8)
const int daylightOffset_sec = 0; // 日光節約時間偏移 (台灣無)
// ---------- 全域物件實體化 ----------
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, -1);
DHT dht(PIN_DHT, DHTTYPE);
// ---------- 全域變數定義 ----------
unsigned long lastSensorReadTime = 0; // 上次感測器讀取時間 (10秒)
unsigned long lastScreenSwitchTime = 0; // 上次畫面切換時間 (20秒)
bool currentScreenMode = 0; // 螢幕顯示模式:0=SENSOR DATA, 1=RGB STATUS
// 感測器與狀態數據變數
float temperature = 0.0;
float humidity = 0.0;
int ldrAO = 0;
int ldrDO = 0;
int pwmRed = 0, pwmGreen = 0, pwmBlue = 0;
// ---------- 精緻美化 ICON 點陣圖 (12x12 & 14x14) ----------
// 1. 溫度計圖示 🌡️ (12x12)
static const unsigned char PROGMEM icon_temp12[] = {
0x0e,0x00, 0x0a,0x00, 0x0a,0x00, 0x0a,0x00, 0x0a,0x00, 0x1b,0x00, 0x3f,0x00, 0x3f,0x00, 0x3f,0x00, 0x1b,0x00, 0x0e,0x00, 0x00,0x00
};
// 2. 水滴圖示 💧 (12x12)
static const unsigned char PROGMEM icon_humi12[] = {
0x04,0x00, 0x0e,0x00, 0x0e,0x00, 0x1f,0x00, 0x1f,0x00, 0x3f,0x80, 0x3f,0x80, 0x3f,0x80, 0x1f,0x00, 0x0e,0x00, 0x00,0x00, 0x00,0x00
};
// 3. 太陽圖示 ☀️ (12x12)
static const unsigned char PROGMEM icon_sun12[] = {
0x04,0x00, 0x24,0x40, 0x0e,0x00, 0x1f,0x00, 0x7f,0xe0, 0x1f,0x00, 0x0e,0x00, 0x24,0x40, 0x04,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00
};
// 4. 儀表板圖示 (黃色標題用 14x14)
static const unsigned char PROGMEM icon_dash14[] = {
0x0f,0xc0, 0x30,0x30, 0x48,0x08, 0x44,0x08, 0x82,0x04, 0x81,0x04, 0x80,0x84, 0x80,0x44, 0x40,0x08, 0x30,0x30, 0x0f,0xc0, 0x00,0x00, 0x00,0x00, 0x00,0x00
};
// 5. 燈泡圖示 (黃色標題用 14x14)
static const unsigned char PROGMEM icon_bulb14[] = {
0x1f,0x00, 0x3f,0x80, 0x7f,0xc0, 0x7f,0xc0, 0x3f,0x80, 0x1f,0x00, 0x0e,0x00, 0x0e,0x00, 0x1f,0x00, 0x0e,0x00, 0x0a,0x00, 0x04,0x00, 0x00,0x00, 0x00,0x00
};
// ---------- 函式宣告 ----------
void connectWiFi();
void readSensors();
void updateRGBControl();
void updateOLED();
void printSerialDebug();
String getFormattedTime();
void setup() {
// 1. 初始化序列埠
Serial.begin(115200);
Serial.println(F("\n=================================================="));
Serial.println(F("[系統] 物聯網硬體專案啟動中..."));
// 2. 設定光敏電阻腳位
pinMode(PIN_LDR_AO, INPUT);
pinMode(PIN_LDR_DO, INPUT);
// 3. 初始化 ESP32 LEDC PWM 通道
ledcAttach(PIN_RGB_R, 5000, 8); // 5000Hz, 8-bit (0~255)
ledcAttach(PIN_RGB_G, 5000, 8);
ledcAttach(PIN_RGB_B, 5000, 8);
// 4. 初始化 DHT11 感測器
dht.begin();
// 5. 初始化 OLED 螢幕
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("[錯誤] SSD1306 OLED 初始化失敗!請檢查接線。"));
for (;;); // 硬體錯誤鎖死
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(10, 10);
display.println(F("System Initializing..."));
display.display();
// 6. 建立 Wi-Fi 連線與 NTP 自動校時
connectWiFi();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
// 7. 首次讀取與更新
readSensors();
updateRGBControl();
updateOLED();
printSerialDebug();
}
void loop() {
unsigned long currentMillis = millis();
// 背景自動檢查 Wi-Fi 連線,若斷線自動嘗試重連
if (WiFi.status() != WL_CONNECTED) {
Serial.println(F("\n[警告] Wi-Fi 連線中斷!發起背景重連..."));
connectWiFi();
}
// 任務 1:每 10 秒非阻斷讀取感測器、計算 RGB 調光並輸出序列埠
if (currentMillis - lastSensorReadTime >= 10000) {
lastSensorReadTime = currentMillis;
readSensors();
updateRGBControl();
printSerialDebug();
}
// 任務 2:每 20 秒切換 OLED 顯示畫面 (SENSOR DATA <-> RGB STATUS)
if (currentMillis - lastScreenSwitchTime >= 20000) {
lastScreenSwitchTime = currentMillis;
currentScreenMode = !currentScreenMode;
Serial.print(F("\n[OLED] 畫面切換 -> "));
Serial.println(currentScreenMode ? F("【RGB STATUS】") : F("【SENSOR DATA】"));
}
// 持續刷新 OLED 畫面內容 (維護下方的秒數更新)
updateOLED();
}
// 建立 STA Mode Wi-Fi 連線
void connectWiFi() {
Serial.print(F("[Wi-Fi] 正在連線至 SSID: "));
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(F("."));
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println(F("\n[Wi-Fi] 連線成功!"));
Serial.print(F("[Wi-Fi] 取得本機 IP: "));
Serial.println(WiFi.localIP());
} else {
Serial.println(F("\n[Wi-Fi] 連線失敗,將持續在背景試連。"));
}
}
// 讀取各類感測器數值 (非阻斷)
void readSensors() {
ldrAO = analogRead(PIN_LDR_AO); // 讀取類比值 (0~4095)
ldrDO = digitalRead(PIN_LDR_DO); // 讀取數位觸發值 (0:強光/亮, 1:弱光/暗)
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t)) temperature = t;
if (!isnan(h)) humidity = h;
}
// 自動根據光照強度進行 RGB LED 調光
void updateRGBControl() {
int mappedBrightness = map(ldrAO, 0, 4095, 0, 255);
mappedBrightness = constrain(mappedBrightness, 0, 255);
pwmRed = mappedBrightness;
pwmGreen = 255 - mappedBrightness;
pwmBlue = 128; // 固定中等藍光
ledcWrite(PIN_RGB_R, pwmRed);
ledcWrite(PIN_RGB_G, pwmGreen);
ledcWrite(PIN_RGB_B, pwmBlue);
}
// OLED 繪製與畫面渲染 (專為黃藍雙色螢幕設計)
void updateOLED() {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
if (currentScreenMode == 0) {
// ================= 畫面一:SENSOR DATA (感測器讀值) =================
// 1. 頂部黃色區域 (Y: 0 ~ 15)
display.drawBitmap(2, 0, icon_dash14, 14, 14, SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(20, 3);
display.print(F("SENSOR DATA"));
// 2. 黃藍區間物理線分隔 (Y: 16)
display.drawFastHLine(0, 16, 128, SSD1306_WHITE);
// 3. 藍色主數據區 (Y: 18 ~ 52)
// 溫度 (Temp)
display.drawBitmap(4, 20, icon_temp12, 12, 12, SSD1306_WHITE);
display.setCursor(22, 22);
display.print(F("Temp : ")); display.print(temperature, 1); display.print(F(" C"));
// 濕度 (Humi)
display.drawBitmap(4, 32, icon_humi12, 12, 12, SSD1306_WHITE);
display.setCursor(22, 34);
display.print(F("Humi : ")); display.print(humidity, 1); display.print(F(" %"));
// 光敏電阻 (Light)
display.drawBitmap(4, 44, icon_sun12, 12, 12, SSD1306_WHITE);
display.setCursor(22, 46);
display.print(F("AO:")); display.print(ldrAO);
display.print(F(" DO:"));
if (ldrDO == LOW) {
display.print(F("BRIGHT")); // 光強/亮
} else {
display.print(F("DARK")); // 光弱/暗
}
} else {
// ================= 畫面二:RGB STATUS (RGB 腳位狀態) =================
// 1. 頂部黃色區域 (Y: 0 ~ 15)
display.drawBitmap(2, 0, icon_bulb14, 14, 14, SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(20, 3);
display.print(F("RGB STATUS"));
// 2. 黃藍區間物理線分隔 (Y: 16)
display.drawFastHLine(0, 16, 128, SSD1306_WHITE);
// 3. 藍色主數據區 (Y: 18 ~ 52)
display.setCursor(4, 22);
display.print(F("R (GPIO25) : ")); display.print(pwmRed);
display.setCursor(4, 34);
display.print(F("G (GPIO26) : ")); display.print(pwmGreen);
display.setCursor(4, 46);
display.print(F("B (GPIO27) : ")); display.print(pwmBlue);
}
// ================= 底部固定藍色時間欄 (Y: 54 ~ 63) =================
display.drawFastHLine(0, 54, 128, SSD1306_WHITE);
display.setCursor(4, 56);
display.setTextSize(1);
display.print(getFormattedTime());
display.display();
}
// 格式化 NTP 時間字串 (yyyy-mm-dd hh:mm:ss)
String getFormattedTime() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
return "2026-11-10 00:00:00";
}
char timeBuffer[25];
strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", &timeinfo);
return String(timeBuffer);
}
// 序列埠詳細除錯與對照訊息輸出 (波特率 115200)
void printSerialDebug() {
Serial.println(F("\n=================================================="));
Serial.print(F("[NTP 時間] ")); Serial.println(getFormattedTime());
Serial.print(F("[Wi-Fi 狀態] "));
if (WiFi.status() == WL_CONNECTED) {
Serial.print(F("已連線 (SSID: ")); Serial.print(ssid);
Serial.print(F(" | IP: ")); Serial.print(WiFi.localIP()); Serial.println(F(")"));
} else {
Serial.println(F("未連線 (嘗試重連中)"));
}
Serial.print(F("[OLED 畫面] 當前模式: "));
Serial.println(currentScreenMode ? F("模式 1: 【RGB STATUS】") : F("模式 0: 【SENSOR DATA】"));
Serial.println(F("--------------------------------------------------"));
Serial.println(F(" 【感測器即時數據 (與 OLED 藍色區對照)】"));
Serial.print(F(" • [Temp] 溫度 : ")); Serial.print(temperature, 1); Serial.println(F(" C"));
Serial.print(F(" • [Humi] 濕度 : ")); Serial.print(humidity, 1); Serial.println(F(" %"));
Serial.print(F(" • [LDR] 類比 (AO) : ")); Serial.println(ldrAO);
Serial.print(F(" • [LDR] 數位 (DO) : ")); Serial.print(ldrDO);
Serial.println(ldrDO == LOW ? F(" (LOW -> BRIGHT/強光)") : F(" (HIGH -> DARK/弱光)"));
Serial.println(F(" 【RGB LED 自動調光狀態 (PWM 0~255)】"));
Serial.print(F(" • Red (GPIO25) : ")); Serial.print(pwmRed);
Serial.print(F(" (")); Serial.print((pwmRed * 100.0) / 255.0, 1); Serial.println(F("%)"));
Serial.print(F(" • Green (GPIO26) : ")); Serial.print(pwmGreen);
Serial.print(F(" (")); Serial.print((pwmGreen * 100.0) / 255.0, 1); Serial.println(F("%)"));
Serial.print(F(" • Blue (GPIO27) : ")); Serial.print(pwmBlue);
Serial.print(F(" (")); Serial.print((pwmBlue * 100.0) / 255.0, 1); Serial.println(F("%)"));
Serial.println(F("=================================================="));
}
編譯正確,上傳,驗證
採用 U8g2 函式庫與內建中文字庫 u8g2_font_unifont_t_chinese2,可直接輸入繁體中文字串呈現
Code
// 引入系統與硬體相關函式庫
#include <Wire.h>
#include <WiFi.h>
#include <time.h>
#include <U8g2lib.h> // U8g2 圖形中文庫
#include <DHT.h>
// ---------- 硬體腳位定義 ----------
#define PIN_LDR_AO 34
#define PIN_LDR_DO 19
#define PIN_DHT 4
#define PIN_RGB_R 25
#define PIN_RGB_G 26
#define PIN_RGB_B 27
#define DHTTYPE DHT11
#define OLED_ADDR 0x3C
// Wi-Fi 與 NTP 設定
const char* ssid = "H3F2";
const char* password = "12345678";
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 8 * 3600;
const int daylightOffset_sec = 0;
// 物件初始化 (採用 HW I2C 模式)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
DHT dht(PIN_DHT, DHTTYPE);
// 變數定義
unsigned long lastSensorReadTime = 0;
unsigned long lastScreenSwitchTime = 0;
bool currentScreenMode = 0;
float temperature = 0.0;
float humidity = 0.0;
int ldrAO = 0;
int ldrDO = 0;
int pwmRed = 0, pwmGreen = 0, pwmBlue = 0;
void connectWiFi();
void readSensors();
void updateRGBControl();
void updateOLED();
void printSerialDebug();
String getFormattedTime();
void setup() {
Serial.begin(115200);
pinMode(PIN_LDR_AO, INPUT);
pinMode(PIN_LDR_DO, INPUT);
ledcAttach(PIN_RGB_R, 5000, 8);
ledcAttach(PIN_RGB_G, 5000, 8);
ledcAttach(PIN_RGB_B, 5000, 8);
dht.begin();
// 初始化 U8g2 顯示屏
u8g2.begin();
u8g2.enableUTF8Print(); // 啟動 UTF-8 繁體中文支援
connectWiFi();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
readSensors();
updateRGBControl();
updateOLED();
printSerialDebug();
}
void loop() {
unsigned long currentMillis = millis();
if (WiFi.status() != WL_CONNECTED) {
connectWiFi();
}
if (currentMillis - lastSensorReadTime >= 10000) {
lastSensorReadTime = currentMillis;
readSensors();
updateRGBControl();
printSerialDebug();
}
if (currentMillis - lastScreenSwitchTime >= 20000) {
lastScreenSwitchTime = currentMillis;
currentScreenMode = !currentScreenMode;
}
updateOLED();
}
void connectWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
attempts++;
}
}
void readSensors() {
ldrAO = analogRead(PIN_LDR_AO);
ldrDO = digitalRead(PIN_LDR_DO);
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t)) temperature = t;
if (!isnan(h)) humidity = h;
}
void updateRGBControl() {
int mappedBrightness = map(ldrAO, 0, 4095, 0, 255);
mappedBrightness = constrain(mappedBrightness, 0, 255);
pwmRed = mappedBrightness;
pwmGreen = 255 - mappedBrightness;
pwmBlue = 128;
ledcWrite(PIN_RGB_R, pwmRed);
ledcWrite(PIN_RGB_G, pwmGreen);
ledcWrite(PIN_RGB_B, pwmBlue);
}
void updateOLED() {
u8g2.clearBuffer();
// 設定中文字型 (常用漢字庫)
u8g2.setFont(u8g2_font_unifont_t_chinese2);
if (currentScreenMode == 0) {
// 畫面一:感測器中文讀值
u8g2.setCursor(0, 14);
u8g2.print("溫: "); u8g2.print(temperature, 1); u8g2.print(" C");
u8g2.setCursor(64, 14);
u8g2.print("濕: "); u8g2.print(humidity, 1); u8g2.print("%");
u8g2.setCursor(0, 32);
u8g2.print("光照類比: "); u8g2.print(ldrAO);
u8g2.setCursor(0, 48);
u8g2.print("光照狀態: "); u8g2.print(ldrDO == LOW ? "強光" : "弱光");
} else {
// 畫面二:RGB 中文狀態
u8g2.setCursor(0, 14);
u8g2.print("RGB 腳位輸出狀態");
u8g2.setCursor(0, 30);
u8g2.print("紅 (R): "); u8g2.print(pwmRed);
u8g2.setCursor(0, 44);
u8g2.print("綠/藍: "); u8g2.print(pwmGreen); u8g2.print("/"); u8g2.print(pwmBlue);
}
// 底部顯示時間 (使用標準小型英文數字字型以利容納)
u8g2.drawHLine(0, 52, 128);
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.setCursor(4, 63);
u8g2.print(getFormattedTime());
u8g2.sendBuffer();
}
String getFormattedTime() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
return "2026-00-00 00:00:00";
}
char timeBuffer[25];
strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", &timeinfo);
return String(timeBuffer);
}
void printSerialDebug() {
Serial.print(F("[系統數據] 時間: ")); Serial.print(getFormattedTime());
Serial.print(F(" | 溫: ")); Serial.print(temperature);
Serial.print(F(" | 濕: ")); Serial.print(humidity);
Serial.print(F(" | AO: ")); Serial.println(ldrAO);
}
編譯正確,上傳,驗證
提示詞
OLED 顯示不正確,分析錯誤原因。配合我修正的需求,提出解決方案,修改程式前,先提出你的規畫給我確認。
#序列埠輸出訊息夠清晰 方便除錯
-將連線 校時等有利於觀察除錯的訊息顯示於序列埠
-將感測讀值、腳位狀態、NTP 時間等訊息同時顯示於序列埠 方便與OLED顯示做比對確認
#OLED 顯示
-將程式初始連網 校時等資訊顯示出來
-修改成 3 頁每10秒輪流顯示方式
-第一頁顯示溫濕度
1 第一行為標題:溫溼度感測
2 第二行:[溫度] xx.xx C
3 第三行:[濕度] xx.xx %
4 底部輸出日期 時間
-第二頁顯示AO DO
1 第一行為標題:光照感測
2 第二行:[AO] xxx
3 第三行:[DO] 亮或暗
4 底部輸出日期 時間
-第三頁顯示RGB 腳位狀態
1 第一行為標題:RGB 腳位狀態
2 第二行:[R,G,B]: [125,152,144]
3 底部輸出日期 時間
Code
// 引入系統與硬體相關函式庫
#include <Wire.h> // I2C 通訊函式庫
#include <WiFi.h> // ESP32 Wi-Fi 核心庫
#include <time.h> // 內建 NTP 時間同步庫
#include <U8g2lib.h> // U8g2 高階中文繪圖庫
#include <DHT.h> // DHT11 感測器驅動庫
// ---------- 硬體腳位定義 ----------
#define PIN_LDR_AO 34 // 光敏電阻類比輸入腳位 (ADC1)
#define PIN_LDR_DO 19 // 光敏電阻數位輸入腳位
#define PIN_DHT 4 // DHT11 資料腳位
#define PIN_RGB_R 25 // RGB LED 紅光 PWM 腳位
#define PIN_RGB_G 26 // RGB LED 綠光 PWM 腳位
#define PIN_RGB_B 27 // RGB LED 藍光 PWM 腳位
// ---------- 硬體參數設定 ----------
#define DHTTYPE DHT11 // 感測器型號指定為 DHT11
#define OLED_ADDR 0x3C // OLED I2C 位址
// ---------- Wi-Fi 與 NTP 設定 ----------
const char* ssid = "H3F2"; // Wi-Fi SSID
const char* password = "12345678"; // Wi-Fi 密碼
const char* ntpServer = "pool.ntp.org"; // NTP 伺服器網址
const long gmtOffset_sec = 8 * 3600; // 台灣時區 (UTC+8)
const int daylightOffset_sec = 0; // 日光節約時間偏移 (台灣無)
// ---------- 全域物件實體化 ----------
// 使用全功能 HW I2C 構造函數 (專為 SSD1306 128x64 藍黃螢幕優化)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
DHT dht(PIN_DHT, DHTTYPE);
// ---------- 全域變數定義 ----------
unsigned long lastSensorReadTime = 0; // 上次感測器讀取時間 (10秒)
unsigned long lastPageSwitchTime = 0; // 上次頁面切換時間 (10秒)
int currentPage = 1; // 輪播頁碼:1 = 溫溼度, 2 = 光照, 3 = RGB
// 感測器與狀態數據變數
float temperature = 0.0;
float humidity = 0.0;
int ldrAO = 0;
int ldrDO = 0;
int pwmRed = 0, pwmGreen = 0, pwmBlue = 0;
// ---------- 函式宣告 ----------
void connectWiFiWithOLED();
void readSensors();
void updateRGBControl();
void updateOLED();
void printSerialDebug();
String getFormattedTime();
void setup() {
// 1. 初始化序列埠通訊
Serial.begin(115200);
Serial.println(F("\n=================================================="));
Serial.println(F("[系統] 物聯網專案 (方案 B) 啟動中..."));
// 2. 設定光敏電阻腳位
pinMode(PIN_LDR_AO, INPUT);
pinMode(PIN_LDR_DO, INPUT);
// 3. 初始化 ESP32 LEDC PWM 通道
ledcAttach(PIN_RGB_R, 5000, 8); // 5000Hz, 8-bit (0~255)
ledcAttach(PIN_RGB_G, 5000, 8);
ledcAttach(PIN_RGB_B, 5000, 8);
// 4. 初始化 DHT11 感測器
dht.begin();
// 5. 初始化 U8g2 顯示器並開啟 UTF8 中文支援
u8g2.begin();
u8g2.enableUTF8Print();
// 6. OLED 顯示開機初始連網與 NTP 校時進度
connectWiFiWithOLED();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
// 7. 首次讀取感測數據與控制
readSensors();
updateRGBControl();
updateOLED();
printSerialDebug();
}
void loop() {
unsigned long currentMillis = millis();
// 背景自動檢查 Wi-Fi 連線,若斷線自動發起重連
if (WiFi.status() != WL_CONNECTED) {
Serial.println(F("\n[警告] Wi-Fi 連線中斷!啟動背景自動重連..."));
connectWiFiWithOLED();
}
// 任務 1:每 10 秒讀取一次感測器、計算 RGB PWM 調光
if (currentMillis - lastSensorReadTime >= 10000) {
lastSensorReadTime = currentMillis;
readSensors();
updateRGBControl();
printSerialDebug();
}
// 任務 2:每 10 秒輪流切換 OLED 頁面 (1 -> 2 -> 3 -> 1)
if (currentMillis - lastPageSwitchTime >= 10000) {
lastPageSwitchTime = currentMillis;
currentPage++;
if (currentPage > 3) currentPage = 1;
Serial.print(F("\n[OLED] 頁面切換 -> 第 "));
Serial.print(currentPage);
Serial.println(F(" 頁"));
}
// 持續刷新 OLED 畫面內容 (以維持底部 NTP 秒數即時更新)
updateOLED();
}
// OLED 初始顯示連網與校時進度
void connectWiFiWithOLED() {
Serial.print(F("[Wi-Fi] 正在連接至 SSID: "));
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
u8g2.clearBuffer();
// 頂部黃色標題
u8g2.setFont(u8g2_font_unifont_t_chinese2);
u8g2.setCursor(0, 14);
u8g2.print("系統初始化中");
u8g2.drawHLine(0, 16, 128);
// 下方藍色訊息
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.setCursor(0, 32);
u8g2.print("Connecting WiFi: ");
u8g2.print(ssid);
u8g2.setCursor(0, 48);
u8g2.print("Attempt: "); u8g2.print(attempts + 1); u8g2.print(" / 20");
u8g2.sendBuffer();
delay(500);
Serial.print(F("."));
attempts++;
}
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_unifont_t_chinese2);
u8g2.setCursor(0, 14);
u8g2.print("系統初始化中");
u8g2.drawHLine(0, 16, 128);
u8g2.setFont(u8g2_font_6x10_tr);
if (WiFi.status() == WL_CONNECTED) {
Serial.println(F("\n[Wi-Fi] 連線成功!"));
u8g2.setCursor(0, 32);
u8g2.print("WiFi Connected!");
u8g2.setCursor(0, 48);
u8g2.print("IP: "); u8g2.print(WiFi.localIP().toString());
} else {
Serial.println(F("\n[Wi-Fi] 連線逾時,將在背景持續試連。"));
u8g2.setCursor(0, 38);
u8g2.print("WiFi Connect Timeout!");
}
u8g2.sendBuffer();
delay(1000);
}
// 讀取感測器數值 (非阻斷)
void readSensors() {
ldrAO = analogRead(PIN_LDR_AO); // 讀取光敏類比值 (0~4095)
ldrDO = digitalRead(PIN_LDR_DO); // 讀取光敏數位值 (0:亮, 1:暗)
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t)) temperature = t;
if (!isnan(h)) humidity = h;
}
// 根據光照強度自動進行 RGB LED PWM 調光
void updateRGBControl() {
int mappedBrightness = map(ldrAO, 0, 4095, 0, 255);
mappedBrightness = constrain(mappedBrightness, 0, 255);
pwmRed = mappedBrightness;
pwmGreen = 255 - mappedBrightness;
pwmBlue = 128;
ledcWrite(PIN_RGB_R, pwmRed);
ledcWrite(PIN_RGB_G, pwmGreen);
ledcWrite(PIN_RGB_B, pwmBlue);
}
// 繪製與刷新 OLED (3 頁輪播 + 繁體中文排版)
void updateOLED() {
u8g2.clearBuffer();
// ================= 頂部黃色標題欄 (Y: 0~14) =================
u8g2.setFont(u8g2_font_unifont_t_chinese2);
u8g2.setCursor(0, 14);
if (currentPage == 1) {
u8g2.print("溫溼度感測");
} else if (currentPage == 2) {
u8g2.print("光照感測");
} else if (currentPage == 3) {
u8g2.print("RGB 腳位狀態");
}
// 黃藍區域分割線 (Y: 16)
u8g2.drawHLine(0, 16, 128);
// ================= 藍色主數據區 (Y: 17~52) =================
if (currentPage == 1) {
// ---------- 第一頁:溫溼度感測 ----------
u8g2.setCursor(0, 34);
u8g2.print("[溫度] "); u8g2.print(temperature, 2); u8g2.print(" C");
u8g2.setCursor(0, 50);
u8g2.print("[濕度] "); u8g2.print(humidity, 2); u8g2.print(" %");
} else if (currentPage == 2) {
// ---------- 第二頁:光照感測 ----------
u8g2.setCursor(0, 34);
u8g2.print("[AO] "); u8g2.print(ldrAO);
u8g2.setCursor(0, 50);
u8g2.print("[DO] ");
if (ldrDO == LOW) {
u8g2.print("亮"); // 強光
} else {
u8g2.print("暗"); // 弱光
}
} else if (currentPage == 3) {
// ---------- 第三頁:RGB 腳位狀態 ----------
u8g2.setCursor(0, 42);
u8g2.print("[R,G,B]: [");
u8g2.print(pwmRed); u8g2.print(",");
u8g2.print(pwmGreen); u8g2.print(",");
u8g2.print(pwmBlue); u8g2.print("]");
}
// ================= 底部藍色固定時間欄 (Y: 54~63) =================
u8g2.drawHLine(0, 53, 128);
u8g2.setFont(u8g2_font_6x10_tr); // 使用 ASCII 英數精準字型以充份容納時間
u8g2.setCursor(4, 63);
u8g2.print(getFormattedTime());
u8g2.sendBuffer();
}
// 格式化 NTP 時間字串 (yyyy-mm-dd hh:mm:ss)
String getFormattedTime() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
return "2026-11-10 00:00:00";
}
char timeBuffer[25];
strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", &timeinfo);
return String(timeBuffer);
}
// 序列埠詳細除錯與對照訊息輸出 (波特率 115200)
void printSerialDebug() {
Serial.println(F("\n=================================================="));
Serial.print(F("[NTP 時間] ")); Serial.println(getFormattedTime());
Serial.print(F("[Wi-Fi 狀態] "));
if (WiFi.status() == WL_CONNECTED) {
Serial.print(F("已連線 (SSID: ")); Serial.print(ssid);
Serial.print(F(" | IP: ")); Serial.print(WiFi.localIP()); Serial.println(F(")"));
} else {
Serial.println(F("未連線 (試連中)"));
}
Serial.print(F("[OLED 畫面] 當前輪播頁面: 第 "));
Serial.print(currentPage);
Serial.println(F(" / 3 頁"));
Serial.println(F("--------------------------------------------------"));
Serial.println(F(" 【感測器與腳位即時數據對照】"));
Serial.print(F(" • 第一頁 [溫溼度感測] -> [溫度]: "));
Serial.print(temperature, 2); Serial.print(F(" C | [濕度]: ")); Serial.print(humidity, 2); Serial.println(F(" %"));
Serial.print(F(" • 第二頁 [光照感測] -> [AO]: "));
Serial.print(ldrAO); Serial.print(F(" | [DO]: "));
Serial.println(ldrDO == LOW ? F("0 (亮/強光)") : F("1 (暗/弱光)"));
Serial.print(F(" • 第三頁 [RGB 腳位狀態] -> [R,G,B]: ["));
Serial.print(pwmRed); Serial.print(F(",");
Serial.print(pwmGreen); Serial.print(F(",");
Serial.print(pwmBlue); Serial.println(F("]"));
Serial.println(F("=================================================="));
}
編譯錯誤
提示詞
編譯錯誤,找出原因,提出解決方案,修改程式前,先提出你的規畫給我確認
#錯誤訊息:
exit status 1
expected ')' before ';' token
#arduino ide mark in line 292
Code
// 引入系統與硬體相關函式庫
#include <Wire.h> // I2C 通訊函式庫
#include <WiFi.h> // ESP32 Wi-Fi 核心庫
#include <time.h> // 內建 NTP 時間同步庫
#include <U8g2lib.h> // U8g2 高階中文繪圖庫
#include <DHT.h> // DHT11 感測器驅動庫
// ---------- 硬體腳位定義 ----------
#define PIN_LDR_AO 34 // 光敏電阻類比輸入腳位 (ADC1)
#define PIN_LDR_DO 19 // 光敏電阻數位輸入腳位
#define PIN_DHT 4 // DHT11 資料腳位
#define PIN_RGB_R 25 // RGB LED 紅光 PWM 腳位
#define PIN_RGB_G 26 // RGB LED 綠光 PWM 腳位
#define PIN_RGB_B 27 // RGB LED 藍光 PWM 腳位
// ---------- 硬體參數設定 ----------
#define DHTTYPE DHT11 // 感測器型號指定為 DHT11
#define OLED_ADDR 0x3C // OLED I2C 位址
// ---------- Wi-Fi 與 NTP 設定 ----------
const char* ssid = "H3F2"; // Wi-Fi SSID
const char* password = "12345678"; // Wi-Fi 密碼
const char* ntpServer = "pool.ntp.org"; // NTP 伺服器網址
const long gmtOffset_sec = 8 * 3600; // 台灣時區 (UTC+8)
const int daylightOffset_sec = 0; // 日光節約時間偏移 (台灣無)
// ---------- 全域物件實體化 ----------
// 使用全功能 HW I2C 構造函數 (專為 SSD1306 128x64 藍黃螢幕優化)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
DHT dht(PIN_DHT, DHTTYPE);
// ---------- 全域變數定義 ----------
unsigned long lastSensorReadTime = 0; // 上次感測器讀取時間 (10秒)
unsigned long lastPageSwitchTime = 0; // 上次頁面切換時間 (10秒)
int currentPage = 1; // 輪播頁碼:1 = 溫溼度, 2 = 光照, 3 = RGB
// 感測器與狀態數據變數
float temperature = 0.0;
float humidity = 0.0;
int ldrAO = 0;
int ldrDO = 0;
int pwmRed = 0, pwmGreen = 0, pwmBlue = 0;
// ---------- 函式宣告 ----------
void connectWiFiWithOLED();
void readSensors();
void updateRGBControl();
void updateOLED();
void printSerialDebug();
String getFormattedTime();
void setup() {
// 1. 初始化序列埠通訊
Serial.begin(115200);
Serial.println(F("\n=================================================="));
Serial.println(F("[系統] 物聯網專案 (方案 B) 啟動中..."));
// 2. 設定光敏電阻腳位
pinMode(PIN_LDR_AO, INPUT);
pinMode(PIN_LDR_DO, INPUT);
// 3. 初始化 ESP32 LEDC PWM 通道
ledcAttach(PIN_RGB_R, 5000, 8); // 5000Hz, 8-bit (0~255)
ledcAttach(PIN_RGB_G, 5000, 8);
ledcAttach(PIN_RGB_B, 5000, 8);
// 4. 初始化 DHT11 感測器
dht.begin();
// 5. 初始化 U8g2 顯示器並開啟 UTF8 中文支援
u8g2.begin();
u8g2.enableUTF8Print();
// 6. OLED 顯示開機初始連網與 NTP 校時進度
connectWiFiWithOLED();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
// 7. 首次讀取感測數據與控制
readSensors();
updateRGBControl();
updateOLED();
printSerialDebug();
}
void loop() {
unsigned long currentMillis = millis();
// 背景自動檢查 Wi-Fi 連線,若斷線自動發起重連
if (WiFi.status() != WL_CONNECTED) {
Serial.println(F("\n[警告] Wi-Fi 連線中斷!啟動背景自動重連..."));
connectWiFiWithOLED();
}
// 任務 1:每 10 秒讀取一次感測器、計算 RGB PWM 調光
if (currentMillis - lastSensorReadTime >= 10000) {
lastSensorReadTime = currentMillis;
readSensors();
updateRGBControl();
printSerialDebug();
}
// 任務 2:每 10 秒輪流切換 OLED 頁面 (1 -> 2 -> 3 -> 1)
if (currentMillis - lastPageSwitchTime >= 10000) {
lastPageSwitchTime = currentMillis;
currentPage++;
if (currentPage > 3) currentPage = 1;
Serial.print(F("\n[OLED] 頁面切換 -> 第 "));
Serial.print(currentPage);
Serial.println(F(" 頁"));
}
// 持續刷新 OLED 畫面內容 (以維持底部 NTP 秒數即時更新)
updateOLED();
}
// OLED 初始顯示連網與校時進度
void connectWiFiWithOLED() {
Serial.print(F("[Wi-Fi] 正在連接至 SSID: "));
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
u8g2.clearBuffer();
// 頂部黃色標題
u8g2.setFont(u8g2_font_unifont_t_chinese2);
u8g2.setCursor(0, 14);
u8g2.print("系統初始化中");
u8g2.drawHLine(0, 16, 128);
// 下方藍色訊息
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.setCursor(0, 32);
u8g2.print("Connecting WiFi: ");
u8g2.print(ssid);
u8g2.setCursor(0, 48);
u8g2.print("Attempt: "); u8g2.print(attempts + 1); u8g2.print(" / 20");
u8g2.sendBuffer();
delay(500);
Serial.print(F("."));
attempts++;
}
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_unifont_t_chinese2);
u8g2.setCursor(0, 14);
u8g2.print("系統初始化中");
u8g2.drawHLine(0, 16, 128);
u8g2.setFont(u8g2_font_6x10_tr);
if (WiFi.status() == WL_CONNECTED) {
Serial.println(F("\n[Wi-Fi] 連線成功!"));
u8g2.setCursor(0, 32);
u8g2.print("WiFi Connected!");
u8g2.setCursor(0, 48);
u8g2.print("IP: "); u8g2.print(WiFi.localIP().toString());
} else {
Serial.println(F("\n[Wi-Fi] 連線逾時,將在背景持續試連。"));
u8g2.setCursor(0, 38);
u8g2.print("WiFi Connect Timeout!");
}
u8g2.sendBuffer();
delay(1000);
}
// 讀取感測器數值 (非阻斷)
void readSensors() {
ldrAO = analogRead(PIN_LDR_AO); // 讀取光敏類比值 (0~4095)
ldrDO = digitalRead(PIN_LDR_DO); // 讀取光敏數位值 (0:亮, 1:暗)
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t)) temperature = t;
if (!isnan(h)) humidity = h;
}
// 根據光照強度自動進行 RGB LED PWM 調光
void updateRGBControl() {
int mappedBrightness = map(ldrAO, 0, 4095, 0, 255);
mappedBrightness = constrain(mappedBrightness, 0, 255);
pwmRed = mappedBrightness;
pwmGreen = 255 - mappedBrightness;
pwmBlue = 128;
ledcWrite(PIN_RGB_R, pwmRed);
ledcWrite(PIN_RGB_G, pwmGreen);
ledcWrite(PIN_RGB_B, pwmBlue);
}
// 繪製與刷新 OLED (3 頁輪播 + 繁體中文排版)
void updateOLED() {
u8g2.clearBuffer();
// ================= 頂部黃色標題欄 (Y: 0~14) =================
u8g2.setFont(u8g2_font_unifont_t_chinese2);
u8g2.setCursor(0, 14);
if (currentPage == 1) {
u8g2.print("溫溼度感測");
} else if (currentPage == 2) {
u8g2.print("光照感測");
} else if (currentPage == 3) {
u8g2.print("RGB 腳位狀態");
}
// 黃藍區域分割線 (Y: 16)
u8g2.drawHLine(0, 16, 128);
// ================= 藍色主數據區 (Y: 17~52) =================
if (currentPage == 1) {
// ---------- 第一頁:溫溼度感測 ----------
u8g2.setCursor(0, 34);
u8g2.print("[溫度] "); u8g2.print(temperature, 2); u8g2.print(" C");
u8g2.setCursor(0, 50);
u8g2.print("[濕度] "); u8g2.print(humidity, 2); u8g2.print(" %");
} else if (currentPage == 2) {
// ---------- 第二頁:光照感測 ----------
u8g2.setCursor(0, 34);
u8g2.print("[AO] "); u8g2.print(ldrAO);
u8g2.setCursor(0, 50);
u8g2.print("[DO] ");
if (ldrDO == LOW) {
u8g2.print("亮"); // 強光
} else {
u8g2.print("暗"); // 弱光
}
} else if (currentPage == 3) {
// ---------- 第三頁:RGB 腳位狀態 ----------
u8g2.setCursor(0, 42);
u8g2.print("[R,G,B]: [");
u8g2.print(pwmRed); u8g2.print(",");
u8g2.print(pwmGreen); u8g2.print(",");
u8g2.print(pwmBlue); u8g2.print("]");
}
// ================= 底部藍色固定時間欄 (Y: 54~63) =================
u8g2.drawHLine(0, 53, 128);
u8g2.setFont(u8g2_font_6x10_tr); // 使用 ASCII 英數精準字型以充份容納時間
u8g2.setCursor(4, 63);
u8g2.print(getFormattedTime());
u8g2.sendBuffer();
}
// 格式化 NTP 時間字串 (yyyy-mm-dd hh:mm:ss)
String getFormattedTime() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
return "2026-11-10 00:00:00";
}
char timeBuffer[25];
strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", &timeinfo);
return String(timeBuffer);
}
// 序列埠詳細除錯與對照訊息輸出 (波特率 115200)
void printSerialDebug() {
Serial.println(F("\n=================================================="));
Serial.print(F("[NTP 時間] ")); Serial.println(getFormattedTime());
Serial.print(F("[Wi-Fi 狀態] "));
if (WiFi.status() == WL_CONNECTED) {
Serial.print(F("已連線 (SSID: ")); Serial.print(ssid);
Serial.print(F(" | IP: ")); Serial.print(WiFi.localIP()); Serial.println(F(")"));
} else {
Serial.println(F("未連線 (試連中)"));
}
Serial.print(F("[OLED 畫面] 當前輪播頁面: 第 "));
Serial.print(currentPage);
Serial.println(F(" / 3 頁"));
Serial.println(F("--------------------------------------------------"));
Serial.println(F(" 【感測器與腳位即時數據對照】"));
Serial.print(F(" • 第一頁 [溫溼度感測] -> [溫度]: "));
Serial.print(temperature, 2); Serial.print(F(" C | [濕度]: ")); Serial.print(humidity, 2); Serial.println(F(" %"));
Serial.print(F(" • 第二頁 [光照感測] -> [AO]: "));
Serial.print(ldrAO); Serial.print(F(" | [DO]: "));
if (ldrDO == LOW) {
Serial.println(F("0 (亮/強光)"));
} else {
Serial.println(F("1 (暗/弱光)"));
}
Serial.print(F(" • 第三頁 [RGB 腳位狀態] -> [R,G,B]: ["));
Serial.print(pwmRed); Serial.print(F(","));
Serial.print(pwmGreen); Serial.print(F(","));
Serial.print(pwmBlue); Serial.println(F("]"));
Serial.println(F("=================================================="));
}
編譯正確,上傳,驗證
參考 客製 u8g2 中文字
提示詞
OLED 顯示某些中文字無法顯示。
我已經將顯示會用到的中文字 自己製作字庫檔,檔名 u8g2_font_custom.c, u8g2_font_custom.h。會將這二個檔案與 .ino檔放在同一資料夾下編譯上傳。
修改程式,將 OLED 要顯示的中文字,設定使用客製的 u8g2_font_custom 字庫檔,其餘不做更動。
修改程式前,先提出使用客製字型會修改的程式碼 給我確認。
Code
// 引入系統與硬體相關函式庫
#include <Wire.h> // I2C 通訊函式庫
#include <WiFi.h> // ESP32 Wi-Fi 核心庫
#include <time.h> // 內建 NTP 時間同步庫
#include <U8g2lib.h> // U8g2 高階繪圖庫
#include <DHT.h> // DHT11 感測器驅動庫
// ---------- 引入客製化 16x16 中文字庫標頭檔 ----------
#include "u8g2_font_custom.h" // 與 .ino 同目錄下的自訂中文字庫標頭檔
// ---------- 硬體腳位定義 ----------
#define PIN_LDR_AO 34 // 光敏電阻類比輸入腳位 (ADC1)
#define PIN_LDR_DO 19 // 光敏電阻數位輸入腳位
#define PIN_DHT 4 // DHT11 資料腳位
#define PIN_RGB_R 25 // RGB LED 紅光 PWM 腳位
#define PIN_RGB_G 26 // RGB LED 綠光 PWM 腳位
#define PIN_RGB_B 27 // RGB LED 藍光 PWM 腳位
// ---------- 硬體參數設定 ----------
#define DHTTYPE DHT11 // 感測器型號指定為 DHT11
#define OLED_ADDR 0x3C // OLED I2C 位址
// ---------- Wi-Fi 與 NTP 設定 ----------
const char* ssid = "H3F2"; // Wi-Fi SSID
const char* password = "12345678"; // Wi-Fi 密碼
const char* ntpServer = "pool.ntp.org"; // NTP 伺服器網址
const long gmtOffset_sec = 8 * 3600; // 台灣時區 (UTC+8)
const int daylightOffset_sec = 0; // 日光節約時間偏移 (台灣無)
// ---------- 全域物件實體化 ----------
// 使用全功能 HW I2C 構造函數 (專為 SSD1306 128x64 藍黃螢幕優化)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
DHT dht(PIN_DHT, DHTTYPE);
// ---------- 全域變數定義 ----------
unsigned long lastSensorReadTime = 0; // 上次感測器讀取時間 (10秒)
unsigned long lastPageSwitchTime = 0; // 上次頁面切換時間 (10秒)
int currentPage = 1; // 輪播頁碼:1 = 溫溼度, 2 = 光照, 3 = RGB
// 感測器與狀態數據變數
float temperature = 0.0;
float humidity = 0.0;
int ldrAO = 0;
int ldrDO = 0;
int pwmRed = 0, pwmGreen = 0, pwmBlue = 0;
// ---------- 函式宣告 ----------
void connectWiFiWithOLED();
void readSensors();
void updateRGBControl();
void updateOLED();
void printSerialDebug();
String getFormattedTime();
void setup() {
// 1. 初始化序列埠通訊
Serial.begin(115200);
Serial.println(F("\n=================================================="));
Serial.println(F("[系統] 物聯網專案 (方案 B - 客製字庫版) 啟動中..."));
// 2. 設定光敏電阻腳位
pinMode(PIN_LDR_AO, INPUT);
pinMode(PIN_LDR_DO, INPUT);
// 3. 初始化 ESP32 LEDC PWM 通道
ledcAttach(PIN_RGB_R, 5000, 8); // 5000Hz, 8-bit (0~255)
ledcAttach(PIN_RGB_G, 5000, 8);
ledcAttach(PIN_RGB_B, 5000, 8);
// 4. 初始化 DHT11 感測器
dht.begin();
// 5. 初始化 U8g2 顯示器並開啟 UTF8 中文支援
u8g2.begin();
u8g2.enableUTF8Print();
// 6. OLED 顯示開機初始連網與 NTP 校時進度
connectWiFiWithOLED();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
// 7. 首次讀取感測數據與控制
readSensors();
updateRGBControl();
updateOLED();
printSerialDebug();
}
void loop() {
unsigned long currentMillis = millis();
// 背景自動檢查 Wi-Fi 連線,若斷線自動發起重連
if (WiFi.status() != WL_CONNECTED) {
Serial.println(F("\n[警告] Wi-Fi 連線中斷!啟動背景自動重連..."));
connectWiFiWithOLED();
}
// 任務 1:每 10 秒讀取一次感測器、計算 RGB PWM 調光
if (currentMillis - lastSensorReadTime >= 10000) {
lastSensorReadTime = currentMillis;
readSensors();
updateRGBControl();
printSerialDebug();
}
// 任務 2:每 10 秒輪流切換 OLED 頁面 (1 -> 2 -> 3 -> 1)
if (currentMillis - lastPageSwitchTime >= 10000) {
lastPageSwitchTime = currentMillis;
currentPage++;
if (currentPage > 3) currentPage = 1;
Serial.print(F("\n[OLED] 頁面切換 -> 第 "));
Serial.print(currentPage);
Serial.println(F(" 頁"));
}
// 持續刷新 OLED 畫面內容 (以維持底部 NTP 秒數即時更新)
updateOLED();
}
// OLED 初始顯示連網與校時進度
void connectWiFiWithOLED() {
Serial.print(F("[Wi-Fi] 正在連接至 SSID: "));
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
u8g2.clearBuffer();
// 頂部黃色標題:切換使用客製中文字庫
u8g2.setFont(u8g2_font_custom);
u8g2.setCursor(0, 14);
u8g2.print("系統初始化中");
u8g2.drawHLine(0, 16, 128);
// 下方藍色訊息 (英數字型)
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.setCursor(0, 32);
u8g2.print("Connecting WiFi: ");
u8g2.print(ssid);
u8g2.setCursor(0, 48);
u8g2.print("Attempt: "); u8g2.print(attempts + 1); u8g2.print(" / 20");
u8g2.sendBuffer();
delay(500);
Serial.print(F("."));
attempts++;
}
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_custom);
u8g2.setCursor(0, 14);
u8g2.print("系統初始化中");
u8g2.drawHLine(0, 16, 128);
u8g2.setFont(u8g2_font_6x10_tr);
if (WiFi.status() == WL_CONNECTED) {
Serial.println(F("\n[Wi-Fi] 連線成功!"));
u8g2.setCursor(0, 32);
u8g2.print("WiFi Connected!");
u8g2.setCursor(0, 48);
u8g2.print("IP: "); u8g2.print(WiFi.localIP().toString());
} else {
Serial.println(F("\n[Wi-Fi] 連線逾時,將在背景持續試連。"));
u8g2.setCursor(0, 38);
u8g2.print("WiFi Connect Timeout!");
}
u8g2.sendBuffer();
delay(1000);
}
// 讀取感測器數值 (非阻斷)
void readSensors() {
ldrAO = analogRead(PIN_LDR_AO); // 讀取光敏類比值 (0~4095)
ldrDO = digitalRead(PIN_LDR_DO); // 讀取光敏數位值 (0:亮, 1:暗)
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t)) temperature = t;
if (!isnan(h)) humidity = h;
}
// 根據光照強度自動進行 RGB LED PWM 調光
void updateRGBControl() {
int mappedBrightness = map(ldrAO, 0, 4095, 0, 255);
mappedBrightness = constrain(mappedBrightness, 0, 255);
pwmRed = mappedBrightness;
pwmGreen = 255 - mappedBrightness;
pwmBlue = 128;
ledcWrite(PIN_RGB_R, pwmRed);
ledcWrite(PIN_RGB_G, pwmGreen);
ledcWrite(PIN_RGB_B, pwmBlue);
}
// 繪製與刷新 OLED (3 頁輪播 + 客製繁體中文字庫排版)
void updateOLED() {
u8g2.clearBuffer();
// ================= 頂部黃色標題欄 (Y: 0~14) =================
// 切換使用客製化中文字庫
u8g2.setFont(u8g2_font_custom);
u8g2.setCursor(0, 14);
if (currentPage == 1) {
u8g2.print("溫溼度感測");
} else if (currentPage == 2) {
u8g2.print("光照感測");
} else if (currentPage == 3) {
u8g2.print("RGB 腳位狀態");
}
// 黃藍區域分割線 (Y: 16)
u8g2.drawHLine(0, 16, 128);
// ================= 藍色主數據區 (Y: 17~52) =================
if (currentPage == 1) {
// ---------- 第一頁:溫溼度感測 ----------
u8g2.setCursor(0, 34);
u8g2.print("[溫度] "); u8g2.print(temperature, 2); u8g2.print(" C");
u8g2.setCursor(0, 50);
u8g2.print("[濕度] "); u8g2.print(humidity, 2); u8g2.print(" %");
} else if (currentPage == 2) {
// ---------- 第二頁:光照感測 ----------
u8g2.setCursor(0, 34);
u8g2.print("[AO] "); u8g2.print(ldrAO);
u8g2.setCursor(0, 50);
u8g2.print("[DO] ");
if (ldrDO == LOW) {
u8g2.print("亮"); // 強光
} else {
u8g2.print("暗"); // 弱光
}
} else if (currentPage == 3) {
// ---------- 第三頁:RGB 腳位狀態 ----------
u8g2.setCursor(0, 42);
u8g2.print("[R,G,B]: [");
u8g2.print(pwmRed); u8g2.print(",");
u8g2.print(pwmGreen); u8g2.print(",");
u8g2.print(pwmBlue); u8g2.print("]");
}
// ================= 底部藍色固定時間欄 (Y: 54~63) =================
u8g2.drawHLine(0, 53, 128);
u8g2.setFont(u8g2_font_6x10_tr); // 使用 ASCII 英數精準字型以充份容納時間
u8g2.setCursor(4, 63);
u8g2.print(getFormattedTime());
u8g2.sendBuffer();
}
// 格式化 NTP 時間字串 (yyyy-mm-dd hh:mm:ss)
String getFormattedTime() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
return "2026-11-10 00:00:00";
}
char timeBuffer[25];
strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", &timeinfo);
return String(timeBuffer);
}
// 序列埠詳細除錯與對照訊息輸出 (波特率 115200)
void printSerialDebug() {
Serial.println(F("\n=================================================="));
Serial.print(F("[NTP 時間] ")); Serial.println(getFormattedTime());
Serial.print(F("[Wi-Fi 狀態] "));
if (WiFi.status() == WL_CONNECTED) {
Serial.print(F("已連線 (SSID: ")); Serial.print(ssid);
Serial.print(F(" | IP: ")); Serial.print(WiFi.localIP()); Serial.println(F(")"));
} else {
Serial.println(F("未連線 (試連中)"));
}
Serial.print(F("[OLED 畫面] 當前輪播頁面: 第 "));
Serial.print(currentPage);
Serial.println(F(" / 3 頁"));
Serial.println(F("--------------------------------------------------"));
Serial.println(F(" 【感測器與腳位即時數據對照】"));
Serial.print(F(" • 第一頁 [溫溼度感測] -> [溫度]: "));
Serial.print(temperature, 2); Serial.print(F(" C | [濕度]: ")); Serial.print(humidity, 2); Serial.println(F(" %"));
Serial.print(F(" • 第二頁 [光照感測] -> [AO]: "));
Serial.print(ldrAO); Serial.print(F(" | [DO]: "));
if (ldrDO == LOW) {
Serial.println(F("0 (亮/強光)"));
} else {
Serial.println(F("1 (暗/弱光)"));
}
Serial.print(F(" • 第三頁 [RGB 腳位狀態] -> [R,G,B]: ["));
Serial.print(pwmRed); Serial.print(F(","));
Serial.print(pwmGreen); Serial.print(F(","));
Serial.print(pwmBlue); Serial.println(F("]"));
Serial.println(F("=================================================="));
}
程式碼另存新檔,將客製化的中文字型庫 u8g2_font_custom.c, u8g2_font_custom.h 放入同一資料夾,開啟 .ino 檔
編譯錯誤
提示詞
編譯錯誤,找出原因,提出解決方案,修改程式前,先提出你的規畫給我確認
#出現在檔案 u8g2_font_custom.c 中
#訊息:
exit status 1
unknown type name 'uint8_t'
修改 u8g2_font_custom.c, u8g2_font_custom.h
u8g2_font_custom.h
#ifndef U8G2_FONT_CUSTOM_H
#define U8G2_FONT_CUSTOM_H
#include <stdint.h> // 解決 uint8_t 未定義問題
#include <clib/u8g2.h> // 引入 U8g2 核心巨集定義
#ifdef __cplusplus
extern "C" {
#endif
// 宣告客製字庫陣列
extern const uint8_t u8g2_font_custom[] U8G2_FONT_SECTION("u8g2_font_custom");
#ifdef __cplusplus
}
#endif
#endif // U8G2_FONT_CUSTOM_H
u8g2_font_custom.c
#include <stdint.h> // 確保純 C 檔案能解析 uint8_t 型別
#include "u8g2_font_custom.h" // 引入對應的標頭檔
編譯正確,上傳,驗證
提示詞
OLED 顯示中文字正確。OLED 輪播做下列修改,其餘不做更動。修改程式前,先提出你的規畫給我確認
#第一頁
-第二行:[溫度] xx.xx °C
#第三頁
-第一行為標題:RGB 腳位狀態
-第二行:[R,G,B]
-第三行:[125,152,144]
-底部輸出日期 時間
Code
// 引入系統與硬體相關函式庫
#include <Wire.h> // I2C 通訊函式庫
#include <WiFi.h> // ESP32 Wi-Fi 核心庫
#include <time.h> // 內建 NTP 時間同步庫
#include <U8g2lib.h> // U8g2 高階繪圖庫
#include <DHT.h> // DHT11 感測器驅動庫
// ---------- 引入客製化 16x16 中文字庫標頭檔 ----------
#include "u8g2_font_custom.h" // 與 .ino 同目錄下的自訂中文字庫標頭檔
// ---------- 硬體腳位定義 ----------
#define PIN_LDR_AO 34 // 光敏電阻類比輸入腳位 (ADC1)
#define PIN_LDR_DO 19 // 光敏電阻數位輸入腳位
#define PIN_DHT 4 // DHT11 資料腳位
#define PIN_RGB_R 25 // RGB LED 紅光 PWM 腳位
#define PIN_RGB_G 26 // RGB LED 綠光 PWM 腳位
#define PIN_RGB_B 27 // RGB LED 藍光 PWM 腳位
// ---------- 硬體參數設定 ----------
#define DHTTYPE DHT11 // 感測器型號指定為 DHT11
#define OLED_ADDR 0x3C // OLED I2C 位址
// ---------- Wi-Fi 與 NTP 設定 ----------
const char* ssid = "chuangHome"; // Wi-Fi SSID
const char* password = "0988087024"; // Wi-Fi 密碼
const char* ntpServer = "pool.ntp.org"; // NTP 伺服器網址
const long gmtOffset_sec = 8 * 3600; // 台灣時區 (UTC+8)
const int daylightOffset_sec = 0; // 日光節約時間偏移 (台灣無)
// ---------- 全域物件實體化 ----------
// 使用全功能 HW I2C 構造函數 (專為 SSD1306 128x64 藍黃螢幕優化)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
DHT dht(PIN_DHT, DHTTYPE);
// ---------- 全域變數定義 ----------
unsigned long lastSensorReadTime = 0; // 上次感測器讀取時間 (10秒)
unsigned long lastPageSwitchTime = 0; // 上次頁面切換時間 (10秒)
int currentPage = 1; // 輪播頁碼:1 = 溫溼度, 2 = 光照, 3 = RGB
// 感測器與狀態數據變數
float temperature = 0.0;
float humidity = 0.0;
int ldrAO = 0;
int ldrDO = 0;
int pwmRed = 0, pwmGreen = 0, pwmBlue = 0;
// ---------- 函式宣告 ----------
void connectWiFiWithOLED();
void readSensors();
void updateRGBControl();
void updateOLED();
void printSerialDebug();
String getFormattedTime();
void setup() {
// 1. 初始化序列埠通訊
Serial.begin(115200);
Serial.println(F("\n=================================================="));
Serial.println(F("[系統] 物聯網專案 (方案 B - 客製字庫與排版優化版) 啟動中..."));
// 2. 設定光敏電阻腳位
pinMode(PIN_LDR_AO, INPUT);
pinMode(PIN_LDR_DO, INPUT);
// 3. 初始化 ESP32 LEDC PWM 通道
ledcAttach(PIN_RGB_R, 5000, 8); // 5000Hz, 8-bit (0~255)
ledcAttach(PIN_RGB_G, 5000, 8);
ledcAttach(PIN_RGB_B, 5000, 8);
// 4. 初始化 DHT11 感測器
dht.begin();
// 5. 初始化 U8g2 顯示器並開啟 UTF8 中文支援
u8g2.begin();
u8g2.enableUTF8Print();
// 6. OLED 顯示開機初始連網與 NTP 校時進度
connectWiFiWithOLED();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
// 7. 首次讀取感測數據與控制
readSensors();
updateRGBControl();
updateOLED();
printSerialDebug();
}
void loop() {
unsigned long currentMillis = millis();
// 背景自動檢查 Wi-Fi 連線,若斷線自動發起重連
if (WiFi.status() != WL_CONNECTED) {
Serial.println(F("\n[警告] Wi-Fi 連線中斷!啟動背景自動重連..."));
connectWiFiWithOLED();
}
// 任務 1:每 10 秒讀取一次感測器、計算 RGB PWM 調光
if (currentMillis - lastSensorReadTime >= 10000) {
lastSensorReadTime = currentMillis;
readSensors();
updateRGBControl();
printSerialDebug();
}
// 任務 2:每 10 秒輪流切換 OLED 頁面 (1 -> 2 -> 3 -> 1)
if (currentMillis - lastPageSwitchTime >= 10000) {
lastPageSwitchTime = currentMillis;
currentPage++;
if (currentPage > 3) currentPage = 1;
Serial.print(F("\n[OLED] 頁面切換 -> 第 "));
Serial.print(currentPage);
Serial.println(F(" 頁"));
}
// 持續刷新 OLED 畫面內容 (以維持底部 NTP 秒數即時更新)
updateOLED();
}
// OLED 初始顯示連網與校時進度
void connectWiFiWithOLED() {
Serial.print(F("[Wi-Fi] 正在連接至 SSID: "));
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
u8g2.clearBuffer();
// 頂部黃色標題
u8g2.setFont(u8g2_font_custom);
u8g2.setCursor(0, 14);
u8g2.print("系統初始化中");
u8g2.drawHLine(0, 16, 128);
// 下方藍色訊息 (英數字型)
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.setCursor(0, 32);
u8g2.print("Connecting WiFi: ");
u8g2.print(ssid);
u8g2.setCursor(0, 48);
u8g2.print("Attempt: "); u8g2.print(attempts + 1); u8g2.print(" / 20");
u8g2.sendBuffer();
delay(500);
Serial.print(F("."));
attempts++;
}
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_custom);
u8g2.setCursor(0, 14);
u8g2.print("系統初始化中");
u8g2.drawHLine(0, 16, 128);
u8g2.setFont(u8g2_font_6x10_tr);
if (WiFi.status() == WL_CONNECTED) {
Serial.println(F("\n[Wi-Fi] 連線成功!"));
u8g2.setCursor(0, 32);
u8g2.print("WiFi Connected!");
u8g2.setCursor(0, 48);
u8g2.print("IP: "); u8g2.print(WiFi.localIP().toString());
} else {
Serial.println(F("\n[Wi-Fi] 連線逾時,將在背景持續試連。"));
u8g2.setCursor(0, 38);
u8g2.print("WiFi Connect Timeout!");
}
u8g2.sendBuffer();
delay(1000);
}
// 讀取感測器數值 (非阻斷)
void readSensors() {
ldrAO = analogRead(PIN_LDR_AO); // 讀取光敏類比值 (0~4095)
ldrDO = digitalRead(PIN_LDR_DO); // 讀取光敏數位值 (0:亮, 1:暗)
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t)) temperature = t;
if (!isnan(h)) humidity = h;
}
// 根據光照強度自動進行 RGB LED PWM 調光
void updateRGBControl() {
int mappedBrightness = map(ldrAO, 0, 4095, 0, 255);
mappedBrightness = constrain(mappedBrightness, 0, 255);
pwmRed = mappedBrightness;
pwmGreen = 255 - mappedBrightness;
pwmBlue = 128;
ledcWrite(PIN_RGB_R, pwmRed);
ledcWrite(PIN_RGB_G, pwmGreen);
ledcWrite(PIN_RGB_B, pwmBlue);
}
// 繪製與刷新 OLED (3 頁輪播 + 客製繁體中文字庫排版)
void updateOLED() {
u8g2.clearBuffer();
// ================= 頂部黃色標題欄 (Y: 0~14) =================
u8g2.setFont(u8g2_font_custom);
u8g2.setCursor(0, 14);
if (currentPage == 1) {
u8g2.print("溫溼度感測");
} else if (currentPage == 2) {
u8g2.print("光照感測");
} else if (currentPage == 3) {
u8g2.print("RGB 腳位狀態");
}
// 黃藍區域分割線 (Y: 16)
u8g2.drawHLine(0, 16, 128);
// ================= 藍色主數據區 (Y: 17~52) =================
if (currentPage == 1) {
// ---------- 第一頁:溫溼度感測 ----------
u8g2.setCursor(0, 34);
u8g2.print("[溫度] "); u8g2.print(temperature, 2); u8g2.print(" \xb0""C"); // \xb0 為 ° 符號之 Hex 碼
u8g2.setCursor(0, 50);
u8g2.print("[濕度] "); u8g2.print(humidity, 2); u8g2.print(" %");
} else if (currentPage == 2) {
// ---------- 第二頁:光照感測 ----------
u8g2.setCursor(0, 34);
u8g2.print("[AO] "); u8g2.print(ldrAO);
u8g2.setCursor(0, 50);
u8g2.print("[DO] ");
if (ldrDO == LOW) {
u8g2.print("亮"); // 強光
} else {
u8g2.print("暗"); // 弱光
}
} else if (currentPage == 3) {
// ---------- 第三頁:RGB 腳位狀態 (分列顯示) ----------
u8g2.setCursor(0, 34);
u8g2.print("[R,G,B]");
u8g2.setCursor(0, 50);
u8g2.print("[");
u8g2.print(pwmRed); u8g2.print(",");
u8g2.print(pwmGreen); u8g2.print(",");
u8g2.print(pwmBlue); u8g2.print("]");
}
// ================= 底部藍色固定時間欄 (Y: 54~63) =================
u8g2.drawHLine(0, 53, 128);
u8g2.setFont(u8g2_font_6x10_tr); // 使用 ASCII 英數精準字型以充份容納時間
u8g2.setCursor(4, 63);
u8g2.print(getFormattedTime());
u8g2.sendBuffer();
}
// 格式化 NTP 時間字串 (yyyy-mm-dd hh:mm:ss)
String getFormattedTime() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
return "2026-11-10 00:00:00";
}
char timeBuffer[25];
strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", &timeinfo);
return String(timeBuffer);
}
// 序列埠詳細除錯與對照訊息輸出 (波特率 115200)
void printSerialDebug() {
Serial.println(F("\n=================================================="));
Serial.print(F("[NTP 時間] ")); Serial.println(getFormattedTime());
Serial.print(F("[Wi-Fi 狀態] "));
if (WiFi.status() == WL_CONNECTED) {
Serial.print(F("已連線 (SSID: ")); Serial.print(ssid);
Serial.print(F(" | IP: ")); Serial.print(WiFi.localIP()); Serial.println(F(")"));
} else {
Serial.println(F("未連線 (試連中)"));
}
Serial.print(F("[OLED 畫面] 當前輪播頁面: 第 "));
Serial.print(currentPage);
Serial.println(F(" / 3 頁"));
Serial.println(F("--------------------------------------------------"));
Serial.println(F(" 【感測器與腳位即時數據對照】"));
Serial.print(F(" • 第一頁 [溫溼度感測] -> [溫度]: "));
Serial.print(temperature, 2); Serial.print(F(" °C | [濕度]: ")); Serial.print(humidity, 2); Serial.println(F(" %"));
Serial.print(F(" • 第二頁 [光照感測] -> [AO]: "));
Serial.print(ldrAO); Serial.print(F(" | [DO]: "));
if (ldrDO == LOW) {
Serial.println(F("0 (亮/強光)"));
} else {
Serial.println(F("1 (暗/弱光)"));
}
Serial.print(F(" • 第三頁 [RGB 腳位狀態] -> [R,G,B]: ["));
Serial.print(pwmRed); Serial.print(F(","));
Serial.print(pwmGreen); Serial.print(F(","));
Serial.print(pwmBlue); Serial.println(F("]"));
Serial.println(F("=================================================="));
}
u8g2_font_custom.h
#ifndef U8G2_FONT_CUSTOM_H
#define U8G2_FONT_CUSTOM_H
#include <stdint.h> // 解決 uint8_t 未定義問題
#include <clib/u8g2.h> // 引入 U8g2 核心巨集定義
#ifdef __cplusplus
extern "C" {
#endif
// 宣告客製字庫陣列
extern const uint8_t u8g2_font_custom[] U8G2_FONT_SECTION("u8g2_font_custom");
#ifdef __cplusplus
}
#endif
#endif // U8G2_FONT_CUSTOM_H
u8g2_font_custom.c
/*
Fontname: -gnu-Unifont-Medium-R-Normal-Sans-16-160-75-75-c-80-iso10646-1
Copyright: Copyright (C) 1998-2024 Roman Czyborra, Paul Hardy, Qianqian Fang, Andrew Miller, Johnnie Weaver, David Corbett, Nils Moskopp, Rebecca Bettencourt, Ho-Seok Ee, et al. License: SIL Open Font License version 1.1 and GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html> with the GNU Font Embedding Exception.
Glyphs: 317/57084
BBX Build Mode: 0
*/
#include <stdint.h> // 確保純 C 檔案能解析 uint8_t 型別
#include "u8g2_font_custom.h" // 引入對應的標頭檔
const uint8_t u8g2_font_custom[10621] U8G2_FONT_SECTION("u8g2_font_custom") =
"=\0\3\2\5\5\4\5\6\20\20\0\376\12\376\13\377\1\233\3\64\4\334 \6\0\240G\1!\10A"
"\61DqH\4\42\10\205(F\221\271\5#\17F%D\325\323\60$Q\313\60D=\1$\22G%"
"D\27\16J\24I\341<FRe\20\63\0%\24G%D\243II)\211\224\64N\23)\211\222\222"
"\246\0&\22G%D\265\225\262J(&\221\226\210I&M\1'\7\201\60F\61\4(\14\203\355C"
"\225DI\324[\224\5)\15\203\351C\221EY\324K\224D\0*\15\347dDW\252\264mIS-"
"\3+\14\347dD\27\327\206!\213k\0,\11\202\254C\241$\12\0-\7$(E\61\4.\7B"
",D\61\4/\14F%D[\254\206iXM\1\60\22F%D\245EI\250M\211\22mb\22e"
"\22\0\61\13E)D\225II\330\247A\62\17F%D\63$\241\230fZXM\207\1\63\20F%"
"D\63$\241\230Fs*\212\311\220\0\64\20F%D\31jIT\311\222,\31\306\264\2\65\17F%"
"DqH\253\203\234\246b\62$\0\66\17F%D\65\205i:(\241c\62$\0\67\13F%Dq"
"-\246\305\264\11\70\20F%D\63$\241\61\31\222\320\61\31\22\0\71\16F%D\63$\241\61\31\324"
"\306h\2:\11\342lD\61\304C\0;\12\42\355C\61\304J\242\0<\11%)D\231u\355\0="
"\11\246\244Dq'\16\3>\11%%D\221v\353\10\77\17F%D\63$\241\230\206\325\34L#\0"
"@\22F%D\65eR\242$K\244DJ$-\361\20A\16F%D\245E-\241\70\14\242c\0"
"B\16F%D\61(\241qXB\307a\1C\16F%D\63$\241\265\243\230\14\11\0D\16F%"
"D\61DY\22\372-\31\42\0E\15F%DqH\253\203\222\266\16\3F\14F%DqH\253\203"
"\222v\5G\16F%D\63$\241\265\64\204\66e\11H\13F%D\21:\16\203\350\61I\13E)"
"D\61Ha\77\15\2J\16G%D\65\210qOY\224e\33\0K\21F%D\21jIT\311D"
"\61\311\242Z\22\6L\11F%D\221\366\327aM\15F%D\21\212\323\20-\36\35\3N\20F%"
"D\21n\233\22)\221\224H\211v\14O\14F%D\63$\241\77&C\2P\15F%D\61(\241"
"qX\322\256\0Q\27g\345C\63Da\22&a\22&a\22&\211\222H\322\220\3\2R\20F%"
"D\61(\241qX\242Z\222%\241\30S\15F%D\63$\241\331QL\206\4T\12G%Dq\310"
"\342\376\6U\13F%D\21\372\307dH\0V\21G%D\221Z\223,\312\242\254\22&i\234\1W"
"\15F%D\21zq\231\206h\24\3X\17F%D\21\212I\324&jQK(\6Y\16G%D"
"\221\252I\26e\225\64\356\6Z\13F%Dq-\366\232\16\3[\12\203\361C\61D\375\323\0\134\14"
"F%D\221\306\325\70\215\253\1]\12\203\345C\61\365OC\0^\11fdF\245EI\30_\7'"
"\344Cq\10`\7c\250F\221\25a\16\6%D\63$a\232\14\243MY\2b\16f%D\221\266"
",\232\350\270)\13\0c\15\6%D\63$\241\332\61\31\22\0d\14f%D\333\262h\243\67e\11"
"e\17\6%D\63$\241\70\14j\61\31\22\0f\14e%D'\205\245A\12{\2g\23f\245C"
"\233,Z\222%Y\264\245C\22\212\311\220\0h\14f%D\221\266,\232\350c\0i\13e)D\25"
"\346\210\330\247Aj\14\245\245CYG\304>J\221\4k\21f%D\221\266%Q%\23\223,\252%"
"a\0l\12e)D#\366O\203\0m\22\7%D\261(Q$ER$ER$ER\1n\13"
"\6%D\221,\232\350c\0o\14\6%D\63$\241\217\311\220\0p\16F\245C\221,\232\350\270)"
"K\232\2q\14F\245C\263h\243\67eI\13r\13\6%D\221,\232\250v\5s\15\6%D\63"
"$\241\354\230\14\11\0t\13E%D\25\226\6)\354*u\12\6%D\21\372MY\2v\14\6%"
"D\21\32\223\250\67Q\2w\21\7%D\221J\221\24I\221\24I\221T\261\0x\17\6%D\21\212"
"I\224\211Z\224\204b\0y\16F\245C\21zL\42KZ\31\22\0z\12\6%Dq\15{\35\6"
"{\16\244\251C\245da\26\25kQ\26\12|\7\301\261C\361A}\17\244\251C!fQ\26\226j"
"a\226H\0~\12g$F\243I\221\246\0\177#\20\242\203\221\364;I\347\244\223\16I\66%a\32"
"%C\222MI\230NC\62\354\234tN:)\351\7\0\0\0\10fo\20\12\377\377N\0\10/\240"
"\205\361\3N\11\20\217!\204\63<\344\374\303\203\316\377\360\3N\12 \357\341\203\235\23rBN\310\11"
"\71!'\34r$'\344\204\234\220\23rBN\310\221\341\3N\13\42\357\241\203\361sN\310\11\71!"
"'D\71\230\345X\230Ci\216\244\71\222\23rBN\310\11\71\2N&%\357\341\203\227\3\71\220\346"
"P\26\17\17q\226cu\254\234u\312\212I\26\245I\226\344H\226cu\254:|N-\36\13\252\203"
"\233c\71\226c\351\360\30\212\241\30\212\241\30\16\17a\65\307r,\307R\0N\62 \15\246\203\235\243"
"\71\360 \205\245\260\64\34t G\343\341AM\325t\370Z\316\321\34\215\1N; \357\341\203\235S"
"r\246\341!GrBN\310\11\71\64\34t('\344\204\234\220\23r\340\7N\214\16Oa\204\65\34"
"t\376\377m\370\0N\256&\17\242\203\235Srdx\310\311\303\220#i\216\14CN\36\36\224\34\25"
"\7-\316r\254\16\205%\65\334\221AN\345'\15\252\203\17\305iV\314\302\254\230\325\221,G\262\34"
"\10s \314\201\60\312\322$L\62\61\213\302\264\16d\71\22N\366-\17\242\203\231\346H\232#Q\35"
"\310\242\34\310\206!\223\262X\312\322\244X\212s \31\16Y\234\3q\16\304\71\20\347@\234\3q\10"
"N\373,\17\242\203\31\347@\70\304\311\220#i\216\244\71\240\346\200\32'\303C\224\346H\232#i\216"
"\244\71\222\346H\232#\311p\314A\0N\375.\17\242\203\31\347@\26\345@\26\305a\226fa\250\245"
"\231\24GIeX\244\60K\303,\15\263\64\314\322,L\263\60\215\262$M\322\14O:\61\16\242\203"
"\231\303\311\60\210\71\222\345P\226\14C\22\351P\244CIi\220\244Z\224EY\224EY\224E\203\224"
"\345P\226CY\16$Y\216$\0OM'\17\242\203\231\345X\230Ca\216\344\204d\270\351\260\216d"
"I\224F\255\265rV\316\312Y\224\3q\16|\313A\0O\177,\17\242\203\227\346H\232#\303S\234"
"\3q,\15')\253\224\262\246\341\26\347@\26\345@\230\344@\232#a\222\3Y\246&\362\0P<"
"+\17\242\203Y\207\302\34\32\16a\232#i\16H\303\20Ji\226D\303\20\265\226\206!\215\322\322\60"
"\244QZJ\323\341-\7\1PO-\16\242\203\27\346H\232\3\311p\252\3Q\35H\244\341\42\345H"
")\307\242\341\24)\245J\247J\62\14R\322\251\322)\222\222R-\25Q*\61\17\242\203\227\14\307:"
"\24\15C\230\245a\66\14\231\226f\312\360RK\243J)\214\222,\311\222lH\262\60\207\262a\10\243"
"$\254\16i\62\204\3QE)\17\242\203\235Sr\340\327\234\220\303i\16\344@:\34t \213r "
"\313\261:V\207\302,\15\263\60\315\42\35\30\22\0QI%\17\242\203\17\344PX\315\302\70\312r "
"\252\203\71\360k\226cu\254\216\325\241\260\267\64\214r\340\220\303\0Qe'\17\242\203\233SrJN"
"\310\11\71\234\344h\222\243I\16f\71V\207\322\34Is \7\322\34\11s,\312Q\1Qg$\15"
"\246\203\231\303\71\232\3\37\324TM\305$\24\223P\253Ii\244\344@\242\203:\250\203:\224\350X\2"
"Qh(\357\341\203\17\344\204\34Nr\60\313\241\64\7r M\206!\211\324T\7rB\216\15w,"
"'\344\204\34\31\36\22\0Q\372\42\15\246\203\235\243\71\20\226\302RX\12K\303A\7\302\34\310\321\70"
"M\325TM\325tx\320\321\0Q\375$\355\245\203\63\34r\60\7+\231\224HMZRSS-\251"
"IMJV\321\222T\214upx\320\321\0R\6)\17\242\203\17\345X\35\13s$\315\201\34Hs"
"(\313\301$\32\6-\15s(\314\241\60G\322\34Is .&q\31R\7'\16\242\203\227\303\71"
"\234\15C\226f\325,\33\242lP\263jV\315J\265JV\323\302,\254\3i\234%q\32\1R\27"
"*\16\242\203\17'\303 \326\221\60\215\262A\252\225\32\243\306\250R\312\42-\311\242\64\214\322\60\12s"
"$\313\241:\322\216%\0R\35&\16\242\203\225\23r\332\360\220\25k\325\254\232EJVI\304LJ"
"\262\226\254\261\26\326\252Y\224%a\222F\0R\66,\16\242\203\233\3Q\35\210\352@\64\14I\224d"
"a\224\206\321\360\224\206Q\32F\321\60$Q\227\250KTR\302\250\222\266\244q\2R\67\62\17\242\203"
"O\210\206A\214\342\60\212\303h\30\224\250\26F\265\60\252\205Q\64\14J\224\224*QR\252DI\251"
"\22I\221\22fQ\222\306i\22W\0RM+\17\242\203\227\3\71\20\347@\32\16\337i\203\32fQ"
"\61\213\212\203T\314\242b\26\25\7\251\230\245a\226\206I-\11\243\64\3Rr/\16\242\203\227Ca"
"\216\14\207P\7\222(\214Z\206!*\246Q\62\14Q\61\215\206C\22\205i\224\14CTI+ie"
"\30\262\244\32&\0R\237'\16\242\203\17\345p\16\207\303\20\345@\66\14Y\232U\263jV\315\212\265"
"\260\66$\341\240\245IZ\316\222\70\215\0R\325.\16\242\203\71D\341\220\346@\232\15\227\34\10\7e"
"\30\242J\324e\30\242J\324e\30\242b\32%\303\20\25k\341\260\15[\224T\303\4R\331-\17\242"
"\203\17\245\303\226c\321\60)Y\65K\352@\32\16Cb\215TI\311r@\215\223h\30\244$\315\222"
"(\354)\311*Y\65\2S\26)\17\242\203\231\345X\35+\266\245a\224\212I,\312I\30G\231\16"
"Ur(\211r(L\263\60\315\302\64K\207-\7\1S@'\316\345\203\361!\311\341h\30\264(\316"
"\242\70\213\206A\313\341d\210\206(\211\272D]\206h\210\222\250;<<\10Ss(\355\245\203q\32"
"\306(\23\243l\30\242L\214\62\61\312\206!\312t \223\302L\253$R\22UJE\71Gs\64\4"
"S\303(\17\242\203\233\303i\16<\67eQ\66,\303\216\344\250\242#Z\242m\342\250\345\200&\345\210"
"\26o:\266#C\216\1S\326*\357\241\203\361\216d\311\260\225\262l\220\262\246\254)\313\6)\353\22"
"f\225\60\32\222\322\240\204\335\241,\311\221(\313\201$\15S\360%\15\246\203\233\243\71\230\203i\35\310"
"r(\31\36\222\34\313)\303\61\7\302\34\10s \314\201p\70\346@\4T\14%\354\251\203\361!\307"
"tL\31\6E\307tL\32&)\214\244\60\222\302H\32&)\214tLG\22\35J\0TJ#\17"
"\242\203\17\344X\35\253c\303!\254#iN\310\201\337\271\16\347\34\210s \316\201x\70\347@\6U"
"_\61\17\242\203\231\346P\30\17S\34F\203\24&Y\61\311\262a\311\252I\224\345@\22\16S\22j"
"\305$+&Y\224D\321 %i\226d\71\224\6Vh*\357\241\203\65H\203V\312\232\262l\220\6"
"\35\211r\60\313\206\317I\216\231u@\322Ai\220\6\255\224\65e\331 \15\12\0V\372$\355\245\203"
"\361AM\325T\31.j\252\246\322\60DR\32Ii$\245\221\64\14\221\224F:\70|\7\3W\26"
")\15\246\203\361A\7\245a\210\244\64\222\206!R\323\341k\252\14\27%\7\22%\31\224D\251%\211"
"\62\134tp\370\16\6W((\17\242\203\235\23r\70\207\206\217\71!\314\221\64\7\324\70I\206K\224"
"\346H\232#i\216\244\71\222\346\310\360\226\203\0W@/\357\341\203\27\347@\234\3q\16dQ\16d"
"Q\70,Q\16d\321\20fQ\16dQ\16dQ\16dQ\16,Q\70Fi)\207\206C\0W\213"
"*\357\341\203\17G\303 \226\262(\214\262(\214\262(\31.Q\30eQV\316\212I\224\206Q\234C"
"\303A\207rB\16\374\0W\337\60\17\242\203\225#\265\34\211\352H\32\15\207(G\262AN\263!\211"
"jQ\22\325\242$\252EI-\33\222\332\16D\353\322\266)q\26\351P\30W\340-\17\242\203\27\347"
"@\232#\331\60\204Y\32f\303\220\14K\216e\303\20fi\230\15C\30\347\300\30n\303%GrB"
"N\310\11!\0Y\313-\17\242\203\27\347@\234\3q\16\244\351\260EaT\314\242d\30\244\326\250\216"
"E\331\260%Y\330-\311\302(\213\302$\7\356P\230\0[W$\17\242\203\235SrdxPrT"
"G\303a\320\341\34\316\341\34\370\35\310\11\71!'\344h\222\303\71\2[\232&\17\242\203\235Srd"
"xPrTGs\342p\320\241\234\220cu,\33\326,\307\352P\22\345H\66\34t\2[\243\42\357"
"\341\203\235SrdxPrTG\263\341\240\223\207s\16\304\303\71\7\342\341\234\3\71q\370\0[\271"
"+\17\242\203\235SrdxPrTK\243\60\253fI\226\3Y\16\245\261\16H\312p\321r \316"
"\201\70\7\342\341\234\3\31\0[\306'\17\242\203\235SrdxPrT\315\242\64Jr \311\232\22"
"-\11\327l\32\206\34\313\241\260\77\16\7\235\20\1[\346*\17\242\203\17\344\310\360\240\344\250\64\134\302"
"\254\66|\312\302t\70\304\71\20\17\347\34\210\207s\16\304\303\35Hs \7\62\0\134\7\61\17\242\203"
"\233\345@\224\15Z\24i\245$J\302\250u\210\222\34\312\242\34H\264h\230\303(\31\6\251\34F\265"
"\60\312\242\254\34\205\225\34H\63\0\134\10'\17\242\203\17\344\310\360\220#\71\64\34\304\306\341 \66\16"
"\7\35J\223\341;\226\15\37\302\64\207\302\34\252\344p\12\134\15.\17\242\203\227\304i\22gIc\250"
"\250\225lx\20sB\230E\325$L\262a\210\222\70\7\302A\215s \36\302lH\223\60G\62\0"
"]\362 \314\351\203\361\220\203\71\230\203Q\216D\71\22\15\207(\7s\60\7sL\307t,\31\16\2"
"^U'\17\242\203\233\305\303C\234\345\310p\316\201x\70\347@<\334\221\34\32\276\265\16\7I\311*"
"Z\245\234E\71\230\3^\217(\17\242\203\17\344\224\34\31\36\242\234\220\15C\230#i%\256\3\311p"
"\220\342\60\212\263r\35\210s \215\223\34\216\1^\225/\17\242\203\17\344\224\34\31\36\242\234\220\3C"
"\26\15:\20e\71\20e\71\20e\71\20\15\207\250\30Ga\34\245Q\222%aR\223\262\261\26^\246"
"%\17\242\203\17\344\224\34\31\36\242\254\234\225\207\267\346\254\234\15rNH\206A\215\322\260\226\246k\250"
"\251;\60^\253)\17\242\203\17\344\224\34\31\36\242\64G\206\267\64G\222\341\230d\305d\70&Y\61"
"\31\216i\16$\303K\134\7rB\14^\372-\17\242\203\17\345\204tP\206!\16\243\64\31\16Y\32"
"\205\331\60dC\230C\311\60\304a\34\205\71\240\14\307\64\7\22-\16\207CN_\17*\17\242\203\17"
"\325\301,\307\352X:|\207rB\16\14R\16\205\71\24\346P\232#i\26\16Y\224\14\71\220D\71"
"\246\23\2_\25#\14\246\203\17\16\203\30\207q\30\207\311\60&\71\224\344\220\216\15\203\30\207q\30\207"
"q\230%i\30\7_\61(\355\245\203q\31\326\70\215\207!\31\206\70\215\323a\31\226,\312\242Z\24"
"\252\222\222)[\242\245q\224\204I\30G\0_\67,\17\242\203\217\205\203\230CY\71J+\303eH"
"\263$G\322p\270\204YeH\262b\62\34\323\34I\243\264\226T\206C\224\203\1_\303%\317\341\203"
"\235SrJN\310\301\234\20\207Q\16dQ\16dQ\216\324r\244\226Fb\232#i\16\15\33\0`"
"o'\357\341\203\17\344p\16\16\347\34\210\207s\16\304\303\71\7\342\341\234\3\71\226\203Y\30\325\242J"
"\24G\342\60d\0a\37\62\17\242\203\217%\71\32e\303C\224\3q\62(Q\226\3Q\226\14J\61"
"\311\42\61\311\242J\64(\335\201l\7\322\34\310\221(\13\243r$\16C\24aK\62\17\242\203\27\346"
"HT\322\206eG\303h\230\6)\314\241a\211\264\60Y\207%\214\302$\214\62i\320\201\34J\262\64"
"J\262\222\224\3Q\70\14\32\0b\20)\17\242\203\17%\71\32\345`\16|\210\342\34\210s \316\262"
"A\312\232\262.aV\11\263\60\252\324\244$+%\345p.c\1.\17\242\203\27\347@\234\3q\16"
"d\303\220\14c\16\304\71\20\15\267\34Iu \224\206\203\222#i\230\245\245\64G\262$N\302\34\311"
"\0c\7.\17\242\203W\307\252a\26\255\331:,qV\316\302a\310\222\34\225\206!\323\322D\311\322"
"\60\33\206\60K\303,\215\222l\30\262\60M\0c\222/\17\242\203\27Fq\30\305a\24'C\64\334"
"\242\70\214\342\60\212\225i\23\243L\11\243\70\214\342d\210\206,\214\342\60\12\223\60JK\31\0c\245"
"-\17\242\203\27\346P\232#\321\60\210\71\70,a\32Fq\62\34\262\64G\304\34P\206\227\60K\263"
"\60\15\243\70U\223L\22#\71\1c\247.\17\242\203\227\346H\234\3q\16D\303\203\222\3Y\22U"
"\302,L\225\70\323A%\33\206\60\316\201\70\7\342\34\210\323$\31\16Q\216\2c\333\60\17\242\203\27"
"\346P\230C\331\260Fi\64\254q\64\334\242\34\310\224(\211\42)\311\222%\312\232s \31\16Y\232"
"\304aV\311\322,\322\201\0c\364\60\17\242\203\225\3C\26\15:\220EY\61J\242AG\263a\320"
"\322\34\251\345\210\62<\204\71\24\16c\226da\26%i\24Vj\212\26\252\2d\255\61\17\242\203\25"
"\17a\64\350@\26\25+\265A\31\6-L\252YTLt@\222\206!\332\242b\26\25\263a\10\263"
"\250\230E-\331\60da\32\1d\332\62\17\242\203\227\346H:\210i\216D\303\207(\314\242hH\243"
"!\13\23m\10\225\34\223\206\203\22\325\221h\211\302\250\242&\211%JjIT\211\24\15e/(\17"
"\242\203\17\344\204\234\220#\303C\216\344\204\234\220C\303!\316\201\34Hs(\313\301$\207sT\321\21"
"U\333\241\1eO\62\17\242\203\223#i\216\244\303\20\205\71\64(\303\224Ea\224EIT\213*M"
"\311p\251\205Y\22\65f\225\60\33\6)\311\221\250\232Da\32\305\1ex\60\17\242\203\31\207\303\220"
"\205Q-\33.iT\32\224aH\262\264\24\15C\324O\303\20%i\234D\303\240\245a\224dS\226"
"\304Ze\210\326\0e\207'\17\242\203\235SrB\16\374\226\3q\16\344@\232#i\16e\71\230\344"
"p\16'\71\230\345P\32\353\200\244\203\2e\231-\17\242\203\231\3q\326\224D\305\244TL\344Zi"
"\30\242\252\30\245s\230T\247\244\62hQ\16DY\16\304\71\20\347@\234\3\21\0e\260/\17\242\203"
"\227Sr$\32\206d\310\301\34\310\242\34\251\245\303\240\14c)\16\243l\30\222(\16\243\60)\325\242"
"JTkL\262j\26F\0e\267-\16\246\203WU\302hI\226\266(Z\262\244TK\36\264Je"
"\270\64eQ\322\30%\225\245\247h\351\251R\271\264eR\62\334\22\0e\271%\17\242\203\235SrB"
"\16\374\232\23rBN\70\344H\232#i\216\244\71\20\347@\134\7\322J\232\3)\0e\345\34\351\255"
"\203\361\220\3:\240\3:\240\3\17:\240\3:\240\3:\240\3\17:\20fB)\16\246\203\17\345p"
"\70\210\265d\30\222,\254\205\265\341s\224\305Q\226\14\267\70\312\242,\32\264\250\26\347`\222\243\31\0"
"fo%\17\242\203\67\234s \36\316\71\20\17w,\7~'\16\347\34\210s \36\356X\16\205\265"
"J\232\3\71\2f\227+\16\246\203\17\344\204tH\206A\211r,*F\265(\213\206\357X\24\15S"
"c\324\30\225\206i\210\302\250\61G\206\35\11#\0f\364 \357\241\203\361;\220\23rh\70\210}\34"
"\16b\37\207\203\232\345`\222\303:(\255;\60\10g\15\60\357\241\203\65(\303V\11\263J\230U\242"
"$\33\224,\314\222\34\311\222a\253\224\262A)e\225\306,i\314\222(\254\264EI\242%a\224\6"
"g*$\17\242\203\17\344\204\234\220C\303A\207rBN\310\201\237w\60\251C\355@V\15Kj\252"
"\3\71!\7g\345$\357\341\203\17\344\204\34\370\65\251C\355@V\35\16\222\222\3\211\226\3\361p\316"
"\201\70\7\342\341N\34>h!+\17\242\203\227\346H\234\3q\16d\303\203\216\206Y\250\245\331\222\3"
"ISV\11\263\250\232\304i\22\327\201\64\211\303,\215T\1h\70*\17\242\203\27\347@\16\304\71\20"
"G\303\307\34H\303H\254M\303\226T\303\244\230I\241\26Fb\232\3q%\15\265\60\222\3h\71/"
"\17\242\203\227\23\242aH\243\264\224F\203\62\14i\224\206R\32.\303\220%\211\224EI\324\67\71\312"
"r \12\343(\311\322H\325\242\34\1h</\17\242\203\27\346P\230C\341\240fa\64(Z\234D"
"I\254\346\300\226\244I\233\226D\361\224$\303\232\205i\26\246Y\230f\303\232\205\21\0hH(\17\242"
"\203\17\344\310\360\20\345`\234C\303\307\64\7\206(\7\207\34\30\302\35\312\201_\223:\324\254e\222\232"
"\352@\16i\313.\17\242\203\27fi\230\245\321p\13\263hX\206!\254\205\322pZ+\225a\210\222"
"\246J\224\15C\230E\305h\270U\303\254\22fa\4j\31.\17\242\203\227\23\242\341\226&q\66\14"
"\311\260t\314\222\66m\30\262\35L*\303\20%u\250\64\334\342\34\10\223j\26\25\243\244VM\1j"
"_\60\17\242\203W*fQ\61J\272EK\62\14Rc\224t\222\226d\232\272$\303!\251\65e\225"
"\60\253\204Q\222%Y\324\222Ea\242%a\26j\242,\17\242\203\27\347@\234\3i\22\207Y\64,"
"i\30%\203\22\351\360\16&\225S\322OY\322\230-c-\15\263\64K\32\243\254\0k\4\71\17\242"
"\203\27\15\311\220E\225(\213\206d\310\242J\64,C\62dQV\222\222A\211\226\254RQ\6\245)"
"i\221*\203\222EI\267(\31\224,\212\246,J\272EY$k!+\17\242\203\217\344@\234#i"
"\216\244\303\216\244\245\60N\242\34\251\345H\232\256I\234&q\230\245a\226fi\30\345@\232C\1k"
"c \317\341\203\63<\344HN\310\11\71!\307\352X\66\254Y\216\325\261:V\307\352X\35\370\1k"
"e$\17\242\203\17\344\204\34\313\206\65\313\261:V\7~\7rB\216\25\323\254\32Fi\254\203:\266"
"C;\12k\317'\17\242\203\227\23\36\264\234\220\303\311p\213r \216\302\70\253\15\237r$\315\302\64"
"\314\322\341\35\315\301$\207S\0k\324 \355\351\203]\215\323\70\15\223\64\213\322(\33\224b\252\226\323"
"\70\215\323TR\322!K\345al\342/\17\242\203\217\305\71\220\3q\16D\303!\315*a\26\265d"
"\71\222\14\203\30%a)\311\242-*fQ\222f\345(K\322\250\26G\251\0mA*\17\242\203\217"
"\344H\234#\311p\310\302\270\26V\263l\70\346P\230\303YT\332\242b\26\25\263\250\230E\225\250\26"
"%aMn,\67\17\242\203O\210\242A\314\222,\314\222,\211\304A\211\222\254\22%Y%\312\222A"
"\211\262$K\242\266$\32\242A\211\352`\224%i\224EaT\213\222\60G\22\0n\226,\17\242\203"
"\25&\71\222Ea:\14R\22\325\221d\30\304\244\224n\303\20fQ\16d\303\240\325)\71\360;\220"
"\23rBN\310\1n\253.\357\341\203\223S\262a\10\263\250\234DmI\267$\22\263$\32\206\60K"
"\303\34\325\206S\26%Q-J\242Z\224D\265(\211J\303An\274*\317\341\203\225\14\7-\12\343"
"(\314\302(\211j\323\26\226\223(\211\302dH\206,N\347\34\210\206[\234\3q\16$\303Ao\325"
".\356\241\203\25\15\267$\7\262d\70\204\71\220d\303%\254&Q\22e\311\264\25\243\251\22\225\206d"
"\210r\70JJYT\211\302,\211\2pc+\17\242\203\223u\311\226\60Y\207\60\232Z\322\244\264\334"
"\221rRYJ\71\234\15\203\244c\265a\320\352X\66\234r\60\7'\0p\272,\16\242\203\235cQ"
"\16&\71\62\34t \314\221,G\206k\16\204\71\220\16\7)\307\242\244S\22%MR\16\345X\222"
"\243\21\0q!'\16\242\203\227\303\71<\34\244\244c\224\264\3I\343\360\20&\355@\322\16$\215\303"
"CNjk\312\222\60\312t\64qg,\356\241\203\63(\303\220d-YK\326\222E\225dP\302("
"\213\206%\213\302$\213\302$\213\302d\220\206\235\22e\245\254&f\5rH\63\17\242\203\231\3Y\24"
"O\245!\215\352HTG\6e\30\242\64\311\242\64\311\242!J\262\250K\26\265dQ-\214ja\224"
"dI\26%Q\226dR\32ri,\16\242\203\27\346H\30'a\234\204\303\62(}\252$R\232\324"
"\302(\311\246\312\240E\225R\26eiV\315\212\265R\22\346@\4ry-\17\242\203\27\347@\234&"
"q\232d\303\20\15b\232\304a\24\15\267\34I\347h\310\206A\251#i\230\245\245\64G\322\70Is"
" \3r\300/\17\242\203\33\306Q\230\204Q\30eQ\30eQ\30\17\311\60\250a\16\205\341\60\306Q"
"\226\244Q\226\244Q\226\244Q\255)kI\223P\7\2s\207&\17\242\203\235Srdx\310\201\34\312"
"\272\14Z\232U\263\250\26\15C\224\203\71\230\3\277\3\71!'\344\204\34u($\355\241\203\65\34\244"
"\260\24\226\302\322p\220\302RX\12K\303A\12Ka)\254\244a\222F\211\216%\0uk%\17\242"
"\203\17\344\320p\320\241\60\32\276\3a\70\34t(G\206\207\234\70\34\304\306\341 \66\16\7\235\66|"
"uv&\17\242\203\17\344X;\20U\207\7%G\245\341\22\346@\234\3\361p'\17\7\261q\70\210"
"\215\303A\314\241\10v|.\17\242\203\217D\341\260\324\241\254%M\322\34H\207h\220\264\250\242F\325"
"!\11\265\34N\207m\220s Rr$\214\223\60\211\63-\2v\370)\16\242\203\231\303\331\60fa"
"-\34\6%\254\15\233\26fS\30%\215QR\31\226(\13\265b-\254\205\265a\314\302\0xl\61"
"\357\241\203\17|\20s \316\201l\30\262\60\252\15IT\222\222a\210\244$\252\224\222\250\26%\303\220"
"EI\24\17Q\22GY\16\244\211\16\211\3x\274,\357\241\203\17|\210\312\341\60da\224\246\303\20"
"\15Q\65j\224\242aP\242\34J\242\244OI/C\322\247$\7r\60\311\341\10y:\34\357\241\203"
"\65\34t\376<|\7rB\216\65gak\224\306r\65\311\341\34\1y\322-\17\242\203\231\346\300\30"
"\16q\16\204I\65L\242d\230\222\254)\213\264(^\262\250R\214*\305$\252#i\16\304u \324"
"\221H\7y\373*\17\242\203\231\346\300\30\16\351 vK\242h\30\345\262\32\205\223\24&\305Ai\13"
"\245L\211\302\34Ps Nu \223\1{,+\17\242\203\25\347\300eX\242,\312\302$\14\207\203"
"\16\205\71\24\206\303A\254Ca\16\15\357\200\32'i(U\242\61\313\261\34{\227*\17\242\203\25\347"
"\300 \15K\224EYN\32\16b\16\205\303A\314\241p\70\210\71\24\16\7\71\15\207\217i\16\304u"
" \4{\300\61\17\242\203\227\346H\232\3\227a\211\262(\13\223\60'\16\323 \205Q\26\15S\26\205"
"Q\26\15S\26\25\263(\253\224\22%\252iQ\234\3)\0|\276,\17\242\203\27\347@\34FI\62"
"\14Jc\232h\303\30\207\303\62\14\222\16o\303\224\224\302()\15K\24\66\16cOIX\213\0|"
"\373#\15\246\203\17\14\322\260c\71\230\266\16C\16\346\230\30\352@\62<\304i\26U\263\306J\226\244"
"u\0}\4+\16\242\203\227\346@\32\247\71\20E\303\222%\351\260\304Y\16\25\323$\312\302a\310\252"
"Y\35N\332\201\244\35\320\241$G#\0}\5&\357\341\203\227\23r\70\34\206,\12\323,\14\7\65"
"\7\342:\220Fi\70\214\71\24\346\204\64)\246I\261:\34}\15\60\17\242\203\27\347@\134\7\342("
"\31\6%K\242l\230jMY\24FY\22UJ\303\220\224\222\64\321\222\34\210\223\316Ig\71Lr"
" M\0}\60,\16\242\203\227\303\71\32\16CT\211*Y\22E\303\324Vj\214*Q\64<$Q"
"\65\211\352@T\351T\351\24\311\303\220\3i\0}q-\17\242\203\227\346H\134\7\342$\31.Q\230"
"\16a\226fi\226\15\227(\213\242a\210\352HT\7\243\60i\213\222n\221\232\256\71\4}\240/\17"
"\242\203\27\346P\70\210i\26&Q\230E\321\240\15\71\20\347@\232\15\227(\15\7%+F\311\222C"
"I))\265t\313t \311\341\24}\255\60\17\242\203\27&\71\20Fi\35\211\222aP\262$\313\6"
"E\213\223d\30\262\60\13\243Z\66,\303\20'Y\216\25\223\266\60\251\14\347\234\220\3}\262/\17\242"
"\203\227\23\262a\220\302\70\12\265\244T)\15\323\60h\245,\12\263\250\70\14JT\11\7\245\61\7\222"
"!\351\71\351\234tL\344\64\1}\332\62\17\242\203\227\346H\230#\341\60dI\224F\245aH\206,"
"\15\263a\310r \215RiX\26%\315\222:T\211\222RK\267(\11\243$\313\241\24~A\60\17"
"\242\203\225\3\361\60\245\71\62,\203TLz\32\6)\311\242$\14\207!Jr \313\304,\207\6\35"
"\16\323\341\240Ca\232\224\342,\314\0~j\60\17\242\203\27\347@\232\244\265\60\211\222%\252\344\300\240"
"\15C\230E\265PI\244^\206e\30\342\234\66hI)\253\224\6)\7\262\34\33$\0~\214\63\17"
"\242\203\27\347@\64\234r N\242a\210\352\330\20\15\267\250\22\325\206KTM\206e\30\342$\315\221"
"a\210\222\326(\251\14C\222\3Y\16\245\11\0\177\251+\17\242\203\231\346P\26\17\17\71\222C\303A"
"\207r\340\347(\7\206,\311\201\60\212\206\217\245xJ\302!\215\322\60)J\232\2\200o:\17\242\203"
"\17\204\321\260\224\222R\22%Q\22%\323!\12\263(\211\222(\211\222!\31\222!\254DY\24FI"
"\251\22-QeH\246a\312\242\34\310\242\34\310\242\34\210\262\14\200\314)\16\242\203\33\345X\24I\203"
"\264#Q\230M\341\20e\203\222S\206k\16\244\303\65\7\322\341\232\3i\16\244\225\64\316\0\200\375\61"
"\17\242\203\227\346@\224\225\302\250\64\14\312\216%\71!L\206)L\302lP\302\34\32\246,\12\243Z"
"\30\215\303\24\207Q\230DI\24&Y\70\10\201sA\357\241\203\63$\245!\211\222(\211\222(\211\222"
"(\211\224\246d\210\262(\211\222R%J\242$J\42QJ\206dH\242$J\242D)%QR\252"
"DI\30%Q\22F\311\220d\221\22%\31\0\201\352\35\11\256\203\231\3\71\20\17\207\34\320\1\35x"
"\320\1\35x\320\1\35\320\201\7\35\10\201\363\42\317\341\203\63<\344@\16\347p\232\3\71\220\16\7\235"
"\220C\71!\207\206\203\16\345\204\234\220\3\77\202\7+\17\242\203\231\344\230\224\215\331\26fi\270dc"
"\266\205i\26.\225\61K\262\60K\262h\370N\255\3\71\220\346P\226\203\11\0\202r\42\357\341\203\231"
"\23rx\30\344\34Hs M\206\203\330\77\16\7\61'\344`\224\203Q\16f\303\3\202\345'\17\242"
"\203\231\346H\32\16\37\323\34\251\345X\216\14_s\70\207\207k\22\207Q\234\225s \316\201\203\16\304"
"\31\0\202\361$\17\242\203\231\346H\32\16\37\323\34\314\11\71\66\234\263rV\316j\303\347$\7\263\34"
"Jc\35\220tP\205\315(\17\242\203\231\206\303\307\64g\70\305Q\30\17\323 \205I\16\134\252Q-"
"\34\306\234:\34\304(\213\302(\213\242\341\3\206_\62\17\242\203\217\245C:(QZ\212\206A\211\332"
"\222!\212vh\10\207%\312\242\60\33\222\64G\206(\31\342(\211\342(\211\342$\252\64\265D\215\2"
"\207\242)\17\242\203\231\306I\247\222\22%i\222%i\226d\331\360\240\244\261\234\206\303\71+g\345\341"
"\216\345\204\60\33\36rB\2\210L)\17\242\203\231\23\242a\10s\70\207\243\234\220\303\321p\222\323$"
"\16\243\70\7\342\34\210s \316\201\70\7\302$\7\322\24\210\375.\17\242\203U\7\262aH\242Z\30"
"%\303%\212\303(\33\206\60\213Z\262(\321r$\247\344\300\257Q\26kI\252\204\241\226d: \13"
"\212\10*\17\242\203\225\3\71\20\347@\34\16C\226\23\322A\314\261\341\62\210\71!\35\304\64\13\323,"
"L\263\60\35\304\64\13sB\10\212\12\60\17\242\203\225S\242a\7\243l\230\352`\24\16Y\224\203Q"
"\70$\303\16F\341\220Ea\224Ea\224E\225(\253$CV)e\241\216\244\1\212-.\17\242\203"
"\225S\302A\15\263h\30\222,\307j\203\222\312\71\64(\303\220Ca\64Ha\224\225\262\226\60\13\323"
"AK\302,\312rD\25\212f.\17\242\203\225#q\16$\71\32\15C\232C\303e\210sB\70D"
"K\16F\341\220Ea\224Ea\224\65eKeH\266\244\224#:!\212s(\17\242\203\25\246a;"
"\224E\303N\31.C\232\23\322!\32\206\34L\207\264\224\14\227(-\245\351\220\226\322\234\20\2\212\277"
"\62\16\242\203\225\23\262a\310\201(\32\206$\252\3\311\222\14Q;pH\206(\315\201dI\206(\351"
"T\351TYJ\325dH\342$J\302$\215\23\0\213f-\17\242\203U\14\207A\212\243lX\206E"
"K\322,\311\206$,%Y\222\15I\242\251I\16\374N\33\16:u\70\210\71\24\16\7\11\213\200\60"
"\17\242\203\225\3\71\20\15w,\34\226a\310iC\62\234\243$J\206d\270\3i\64D\303\20\265F"
"\245a\210Z\243!\32\206\250V\207\322\4\213\212-\17\242\203\25\246Q\66H\265\70\32\244e\310r("
"\213\222)\32\326A\236\223\276(\225\245\61\207\207C\230\244\71\64\350\210\252\355\320\0\214\307+\17\242\203"
"\223\346P\70\14i\22ea\32Ek\22\207\231\30\311\332p\316\201x\70\347@<\234s \36\356@"
"\232\3\71\220\1\215w,\17\242\203\231\23rB\66h\303\32\347@\234\3\321\60$\203\234\25\223,G"
"j\71\222,a\226da\226\204\203\224\350`\64\274\23\217*\61\17\242\203\27\347@\234\3i\222\15C"
"\222\245Y\32\15\313\240t\307\6e\30\242\244O\203\322\61\33\206d\30\224\306,i\314\222\306,\15\263"
"P\1\217\70\65\17\242\203\25\347@\232\304a\226\15J\32F\311\240\14C\216%\225!K\6%J\372"
"\224T\6eHj\245\244\62(QR\313\206\244V\312\232\222ZE\212\0\220\32(\357\241\203\223\15\203"
"\230#i\250\344p\216\15\227)k\312\232\206[\224\65eM\303-\312\232\262\244\224\224\243l\70\10\220"
" *\357\341\203\17\345@\26\345H\64\14i\222\345\204\34\33.CN\310\11\321\60\244QZJK\303"
"\220Fi\230\344`\66\34\2\220#'\357\341\203\17\305\71\220\3\311p\220\342\34\33\316Ye\32nQ"
"\326\64\334\342\34H\206\203\24\347@\134Ik\303A\220\62\61\357\341\203\17$\71\20F\71\20\15\267("
"\313!-G\222aP\206(\313\201(\313\201h\30\304(\313\201(\313\201h\270E\71\226\344`\66\34"
"\2\220>/\17\242\203\17\305\225\70\314\322,\215\225A\321)\323\20fQ%\312\242!\211\262\250\22e"
"\321\220DYT\314\242\226,J\264J\16f\303A\220S(\357\341\203\235\206\265\70'$\303\35\312\321"
"a\210\206(-\15C\32\245\245aH\243\264\64\14i\224\206I\16f\303!\220\350\60\17\242\203\227S"
"\322A\31\6%\313\261(\13\243b\224%\321p\220r\60\312\301,\32\246,\12\243,\12#%\13\243"
"$\34\246\70\214rB\10\221\315\42\357\341\203\17\346\340 \16;!\7~\7rl\70g\345\341\234\225"
"\207;\226C\303A\207r\340\7\223/\62\17\242\203\227Fi)L\302(\214\222aP\302\250e\310\242"
"\64\33\6-\7\207!\31\306\226(\211\302(\321\206)\11\213C\22&C\70Lq\230\0\225\213*\354"
"\245\203\61H\303\26e\303\64lQ\66L\303\216I\303\244E\231\26e\312\60(Z\224iQ&e\231"
"\22&\211\16%\0\225\223(\354\245\203\61H\303\26e\303\64lQ\66L\303\216I\303$\205\221\64L"
"R\30I\303$\205\221\216\351H\242C\11\0\225\334\62\15\246\203\61h\303V\33\266a\253\15\333\60\205"
"\231\22%Q\242L\223\224DI\242\14\311\220\210I\250DI\224(C\62$b\22jQE\312*\0"
"\226;,\356\245\203\61(\303\224%a\324\30\65FI\66L\215Qc\224%a\224%\303\224%a\244"
"Da\224da\224\206Q\66\34t\30\226d,\356\245\203\61\204i\24\246I\230\204IV\323\322(\251"
"\14\312\24\246Q\230F\311\60(J\230&YR\13\243\266\226\64\311\222\70\6\226\216\60\16\246\203\235e"
"\203\222EZ\262\64\325\244\226\222\222I\221\324\62Da\232%\303\220dI\232dI\232(\321\60$\265"
"\64I+\351\60$i\5\226\373)\17\242\203\65\34t(G\206\7%\215\245%Yr$\307\226d'"
"\17\7\261q\70\210\215\303A\207\342\34\210sd\30\2\227^%\17\242\203\233\345X\35\253\16\333\260f"
"\71V\307\312\203\66\310Y\216\325\261:V\35\266a\315r\254\216U\1\227b,\357\241\203\361sN\310"
"\341\34\34\16b\224Ea\224Ea\64Ha\224Ea\224Ea\64Ha\224Ea\224E\341p\20s"
"(\2\230\1'\356\245\203\361A\315\321\34\34\16a\216\204\71\22\16\207\60G\302\341\20\346H\230#\341"
"pH\343\64G\262\34K\0\230\2*\357\241\203\17\374\226\3i\216d\303\20fi\230\245a\66\14a"
"\226\206\331\60\204Y\32fi\230\15CX\253diV\7\2\230L/\357\241\203\65(\303\20ea:"
"hq\26\15\332 e\71\66(\303\240di\70\250aV\31\222AK\322$L\302,J\264\64\211\206"
"w\2\230^/\17\242\203\227\203Qe\30\222\326l\30\262X\34\246\244\24&Q%\314\241aL\242\260"
"\224\14\313\60$a\267$\33\266(\213\262R\230\304\5\230o.\357\241\203qH\206A\15\263a\310\302"
"\64\31\226aH\302(\213\302\244\313\260\134\302(\213\206\245\307\344\22\346\320\260tj\351\230#q\0\231"
"\226*\17\242\203\227\3\71\220\206\303\347\34\316\301\341 \346P\230C\341p\20s(\314\241p\70\210\71"
"\24\346P\70\34\304\34\212\0\232E\60\357\241\203\61(\303!\311rdP\242A\251EY\62(Q\226"
"\324\242A\251\345\310\240$\307\244\213\322\27\245/J\313!Kr\64\31N:\10\232\324\65\17\242\203\17"
"%\341\240\14C\224%\235\226d\30\242\244/\303\203\222\346\320\240\14C\224\345\330\240\14C\224%i\64"
"(\303\20eQ\326%L\222\341\220D\71\6\232\330(\17\242\203\235Sr\340w\352\60\344H\232#i"
"\216\14CN\36\36\242\34\214\242a\210Z\243\326\250\64\14Q\35S\0\236\303%\17\242\203\231\246\303C"
"ZG\206!\247\16\337\201\34\33\316Y\71+\17\347\254\234\225\207;\220\346@\16d\0\0";
編譯正確,上傳,驗證