📋 專案概述

兒子的自然課要觀察「小雞怎麼來」,我興沖沖買了台 499 元的淘寶孵化器。第 3 天凌晨 2 點 47 分,社區跳電 22 分鐘——等電來了,12 顆受精蛋裡有 5 顆的胚胎就停在第 18 天。售後客服的回覆很乾脆:「親,斷電不保固喔。」第 9 天我更氣:凌晨偷看發現加熱器全功率燒,溫度計顯示 39.6°C——那台機器的「恆溫」就是低於 37 全開、高於 39 關機,±1°C 的鐘擺把胚胎熱得半死。499 元買到的是一個沒有大腦的保溫箱。

雞胚胎的容錯窗口比我想像的窄得多:孵化溫度帶 37.5~38.5°C,但最佳孵化率需要穩定在 37.8°C ± 0.2°C;連續 4 小時不翻蛋,胚胎會黏殼死亡;出雛前 3 天濕度要從 55% 拉到 65%。這些「日程表」恰好是微控制器最擅長的領域。市售選項:

  1. 品牌孵化器(1,500~3,000 元):溫控仍是繼電器式 ON/OFF,實測多半 ±0.5°C 起跳;沒有斷電推播、沒有翻蛋記錄,出雛率全靠祈禱。
  2. 水族加熱棒改:溫度解析度與控制帶都不對(25~32°C 規格),且沒有濕度維度。
  3. 保溫箱 + 陶爐 + 人工盯:農友傳統做法,代价是 21 天睡不了整覺——凌晨定鬧鐘起來翻蛋的滋味,我媽當年養雞時親口描述過。

關鍵洞察:孵化失敗的三大原因——溫度超標、斷電、忘記翻蛋——全部是「感測 + 定時 + 推播」問題,ESP32 一次解完。本專案總成本 570 元:

成本效益:品牌機 1,500 元起跳還不含你要的三件事;本專案 570 元全部都有,而且控制邏輯透明可改——鵪鶉、鴨子、烏龜的溫度曲線改三個常數就行。

為什麼選 ESP32-WROOM-32(經典款)?

需求經典 ESP32 的優勢
24/7 穩定性2016 年至今十年量產的最成熟平台,社群案例最多——孵化器跑 21 天不能當白老鼠
控制不能被打斷雙核分工:核心 0 跑 PID(100ms 週期),核心 1 跑 Wi-Fi 推播,網路重連不卡控溫
週邊多加熱 + 風扇 + 馬達 H 橋×2 + 超聲波霧化 + 水溫感測 + OLED + 時鐘 = 12 支 IO,36 GPIO 綽綽有餘
成本WROOM-32 模組價格已殺到 90 元,比 C3 還便宜

🧱 硬體清單

項目型號數量預估單價備註
主控板ESP32-WROOM-32 DevKitC1~110 元選 CP2102 或 CH9102 版本皆可
溫濕度SHT31-D(I2C,±2%RH)1~85 元探頭裝箱內蛋盤高度;DHT22 的 ±5%RH 做不了濕度目標管理
加熱器PTC 陶瓷加熱片 12V 15W1~65 元PTC 自限溫 + PWM 調功率,雙保險
加熱驅動IRLZ44N 邏輯級 MOSFET1~10 元10k 下拉 + 100Ω 閘極;PWM 用 LEDC 5kHz(無聲)
翻蛋馬達TT 減速馬達(TT Motor)1~25 元1:120 減速比,扭矩夠拖 12 顆蛋的轉盤
馬達驅動DRV8833 模組1~35 元比 L298N 省兩顆二極體壓降;STBY 拉高使能
濕度來源超聲波霧化片 24mm + 驅動板1 組~45 元霧化片放外置水箱,經矽膠管入箱——絕不直接對蛋噴
水溫安全DS18B20 防水探頭1~25 元放水箱:水溫 > 33°C 停止霧化,防「溫水蒸氣燉蛋」
顯示SSD1306 0.96" I2C OLED1~75 元與 SHT31 共總線(0x3C / 0x44)
RTCDS3231(帶 AT24C32)1~28 元CR1220 電池:斷電 21 天日程不亂,復電自動對 NTP
安全40°C 常閉溫度保險絲(KSD9700)1~15 元貼綁加熱片本體,串 12V 迴路,獨立於韌體
其他4010 風扇、1N5822、電阻組、12V/2A 電源1 組~92 元風扇讓熱空氣循環,蛋盤各點溫度差 < 0.3°C
合計~570 元不含保麗龍箱(1.5 箱 130 元或既有冰箱保鮮盒)

🔌 接線圖

ESP32 DevKitC 雙核 · PID 核心0 / Wi-Fi 核心1 GPIO25/26 · GPIO27 GPIO32 · GPIO4 GPIO21/22 GPIO33 · GPIO18 I2C 三裝置共總線 SHT31(0x44) OLED(0x3C) DS3231(0x68) SDA→21 SCL→22 全部 VCC=3V3 PTC 加熱 12V 15W 12V→40°C保險絲→PTC PTC−→IRLZ44N D(G→GPIO25) PWM 5kHz LEDC DRV8833 + TT 馬達 AIN1→27 AIN2→14 STBY→3V3 VM=12V(經 7808→馬達 8V) 電流偵測 Cp→GPIO35 ADC 超聲波霧化器 驅動板 IN→GPIO32(經 S8550) DS18B20 水溫 DQ→GPIO4 4.7kΩ 上拉 3V3 4010 循環風扇 5V S8050:B→1k→GPIO18 12V 2A 電源 → MP1584 5V → 板 5V 綠:I2C 三裝置地址不撞號 紫:12V 動力不過主板,單點共地
▲ 加熱/馬達走 12V 動力迴路,主板只出訊號;霧化器經 PNP 驅動,共陽接法讓 GPIO 低準位才開啟(預設安全)。

詳細接線

ESP32 腳位連接說明
GPIO21 / GPIO22SHT31 + OLED + DS3231 的 SDA / SCL共 I2C;DS3231 VCC 接 3V3,斷電靠 CR1220
GPIO25IRLZ44N 閘極(經 100Ω)LEDC 5kHz PWM;10k 下拉:復位期間加熱預設關
GPIO27 / GPIO14DRV8833 AIN1 / AIN2STBY 接 3V3;兩腳同時 LOW = 滑行停止(不煞車翻蛋盤)
GPIO35DRV8833 Cp 電流偵測堵轉時 Cp 電壓升高 → 翻蛋失敗偵測(GPIO35 輸入專用腳)
GPIO32S8550 基極(經 1kΩ)→ 霧化驅動板共陽:GPIO LOW 才霧化——復位期間霧化預設關,防燉蛋
GPIO4DS18B20 DQ(4.7kΩ 上拉 3V3)探頭沉入水箱底部
GPIO18S8050 → 4010 風扇循環風扇隨 PID 輸出間歇運轉
GND全部共地單點匯流霧化板與馬達地線直接回電源端
ASCII 對照表(DevKitC 左列 / 右列)

  ┌──────────────────────────┐
  │ ESP32-WROOM-32 DevKitC   │
  │  GPIO21 ○──── I2C SDA ── SHT31 / OLED / DS3231
  │  GPIO22 ○──── I2C SCL ──(同上共總線)
  │  GPIO25 ○──── IRLZ44N 閘極(100Ω 串 + 10k 下拉)
  │  GPIO27 ○──── DRV8833 AIN1      GPIO14 ○──── DRV8833 AIN2
  │  GPIO35 ○──── DRV8833 Cp(堵轉偵測,輸入專用)
  │  GPIO32 ○──── S8550 基極 → 霧化驅動板(LOW=開)
  │  GPIO4  ○──── DS18B20 DQ(4.7k 上拉)
  │  GPIO18 ○──── S8050 基極 → 循環風扇
  │  5V     ○──── MP1584 輸出       GND ○──── 單點共地排
  └──────────────────────────┘
  加熱迴路(不過主板):
  12V(+) ── 40°C KSD9700 ── PTC(+) ── PTC(−) ── MOSFET D
             MOSFET S ── 電源 GND(單點共地)
⚠️ 兩條鐵律:(1) 40°C 保險絲感溫面必須貼綁加熱片本體;(2) 霧化片供電走「GPIO LOW 才開」的共陽接法——ESP32 復位期間 GPIO 浮空,任何「HIGH 才開」的接法在當機重啟的瞬間都可能把蛋燉了。

🖨️ 3D 列印零件

零件清單

零件數量材質建議顏色列印時間
翻蛋轉盤(12 蛋位)1PETG白~110 min
蛋槽插片(V 形)12PETG白~40 min
馬達齒箱支架1PETG黑~35 min
SHT31 通風罩1PETG白~15 min
霧氣導流板1PETG灰~15 min
控制頂蓋(卡箱緣)1PETG白~50 min

列印設定

設計要點

  1. V 形蛋槽:每槽 78° 夾角,蛋大頭朝上;轉盤整體 ±45° 翻轉(等效翻蛋 90°),比整盤傾斜式受力均勻。
  2. 齒箱減速:TT 馬達 + 3:1 列印齒輪組,扭矩翻倍且翻蛋動作慢(0.5 轉/分)——蛋需要「緩慢挪動」而不是「甩動」。
  3. 霧氣導流:霧氣入口在箱壁下緣,導流板把霧氣打散貼箱壁上升,出口在對角上緣——蛋盤正上方無直吹氣流。
  4. 感測器罩:SHT31 懸在蛋盤高度、避開霧氣入口直線路徑;罩體朝下開孔防冷凝水滴落。

💻 程式碼

📐 註解規範:區塊以框註分段;函式以框註說明用途/參數/回傳;行內註解一律解釋「為什麼」而非「做什麼」。全系列文章採同一規範。
📦 相依:Arduino core for ESP32 ≥ 2.0.14(開發板選「ESP32 Dev Module」)、Adafruit_SHT31 ≥ 1.2、Adafruit SSD1306 ≥ 2.5、RTClib ≥ 1.13、OneWire ≥ 2.3.7、DallasTemperature ≥ 3.9。
/* ============================================================
 *  ESP32 智慧孵化器  |  Smart Egg Incubator
 *  ------------------------------------------------------------
 *  功能:PID 恆溫 37.8°C ±0.2°C + 21 天日程濕度管理 + 每 4 小時
 *        翻蛋(堵轉偵測)+ 斷電 15 分鐘推播 + OLED 儀表
 *  硬體:ESP32-WROOM-32 + SHT31 + SSD1306 + DS3231 + PTC 15W
 *        + IRLZ44N + DRV8833/TT 馬達 + 超聲波霧化 + DS18B20
 *  供電:12V 2A → MP1584 5V → 板 5V(加熱/馬達走 12V 動力迴路)
 *  相依:Arduino core for ESP32 ≥2.0.14(板型 ESP32 Dev Module)
 *        Adafruit_SHT31 / Adafruit_SSD1306 / RTClib / OneWire
 *        DallasTemperature / WiFi / HTTPClient
 *  安全:韌體 38.8°C 軟斷 + 硬體 40°C 保險絲;霧化共陽 LOW=開
 *        (復位期間加熱與霧化預設關閉)
 * ============================================================ */
#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <Adafruit_SHT31.h>
#include <Adafruit_SSD1306.h>
#include <RTClib.h>
#include <OneWire.h>
#include <DallasTemperature.h>

/* ------------------------------------------------------------
 * 1. 設定區
 * ------------------------------------------------------------ */
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_PASSWORD";
const char* TG_TOKEN  = "YOUR_BOT_TOKEN";
const char* TG_CHAT   = "YOUR_CHAT_ID";

/* ------------------------------------------------------------
 * 2. 腳位定義
 * ------------------------------------------------------------ */
#define PIN_HEATER   25   // IRLZ44N 閘極(LEDC PWM 5kHz)
#define PIN_AIN1     27   // DRV8833 方向 1
#define PIN_AIN2     14   // DRV8833 方向 2
#define PIN_MOTOR_CS 35   // DRV8833 Cp 電流偵測(輸入專用腳)
#define PIN_ATOMIZER 32   // S8550 共陽:LOW=霧化(復位預設關)
#define PIN_WATER_DS 4    // DS18B20 水溫
#define PIN_FAN      18   // 循環風扇 S8050
#define PIN_SDA      21
#define PIN_SCL      22

/* ------------------------------------------------------------
 * 3. 孵化參數(雞;鵪鶉 37.7、鴨 37.4 改這裡)
 * ------------------------------------------------------------ */
const float    T_TARGET     = 37.8f;   // 目標溫度 °C
const float    T_HARD_OFF   = 38.8f;   // 軟性上限:超過立即全斷加熱
const float    T_LOW_ALARM  = 36.5f;   // 低溫告警(斷電/加熱失效)
const uint32_t PID_MS       = 100;     // PID 週期 100ms(熱慣性大,再快無益)
const uint32_t SENSOR_MS    = 2000;    // SHT31 取樣週期
const uint32_t TURNDOWN_MS  = 4UL * 3600000;  // 每 4 小時翻蛋
const uint32_t TURN_TIMEOUT = 30000;   // 單次翻蛋逾時 30 秒 = 堵轉
const uint32_t PWRLOSS_MS   = 15UL * 60000;   // 斷電 15 分鐘才推播(防跳電誤報)
const float    WATER_MAXT   = 33.0f;   // 水箱溫度上限(超溫停止霧化)
const uint8_t  PWM_RES      = 10;      // 0~1023

Adafruit_SHT31 sht31;
Adafruit_SSD1306 oled(128, 64, &Wire, 0);
RTC_DS3231 rtc;
OneWire oneWire(PIN_WATER_DS);
DallasTemperature waterT(&oneWire);

/* ------------------------------------------------------------
 * 4. PID 狀態(位置式 + 抗積分飽和 + 輸出斜坡)
 * ------------------------------------------------------------ */
struct PID {
  float Kp, Ki, Kd;
  float integral, prevErr, out;
};
PID pid = { 60.0f, 0.35f, 8.0f, 0, 0, 0 };

/* ------------------------------------------------------------
 * 5. 全域狀態
 * ------------------------------------------------------------ */
float gTemp = 0, gHum = 0, gWaterT = 0;
bool  sensorOk = false, rtcOk = false;
uint8_t failCnt = 0;
uint32_t lastTurnMs = 0, turnStartMs = 0;
bool     turning = false;
uint8_t  turnDir = 0;                  // 0/1 交替 = 往復翻蛋
uint32_t powerOffMarkMs = 0;           // 供電異常計時(0 = 正常)
bool     atomizerOn = false;
bool     powerWasLost = false;         // 上次檢查是否處於斷電(復電推播用)

/* 推播佇列:控制迴圈不阻塞 */
String tgQueue[3];
uint8_t tgHead = 0, tgTail = 0;
void tgPush(const String& msg) {
  uint8_t next = (tgHead + 1) % 3;
  if (next == tgTail) return;
  tgQueue[tgHead] = msg; tgHead = next;
}
void tgFlush() {
  if (tgTail == tgHead || WiFi.status() != WL_CONNECTED) return;
  HTTPClient http;
  http.begin(String("https://api.telegram.org/bot") + TG_TOKEN + "/sendMessage");
  http.addHeader("Content-Type", "application/x-www-form-urlencoded");
  String body = "chat_id=" + String(TG_CHAT) + "&text=" + urlencode(tgQueue[tgTail]);
  int code = http.POST(body);
  http.end();
  if (code == 200) tgTail = (tgTail + 1) % 3;
}

/* ------------------------------------------------------------
 * pidStep —— 100ms 位置式 PID
 *   參數:err 當前誤差;回傳:PWM 0~1023
 *   為什麼輸出斜坡:PTC + 風扇有熱慣性,PWM 突變會讓
 *   溫度振盪;每週期限 40 步 ≈ 4 秒內從 0 到滿
 * ------------------------------------------------------------ */
uint16_t pidStep(float err) {
  pid.integral += err * (PID_MS / 1000.0f);
  pid.integral = constrain(pid.integral, -60.0f, 60.0f);   // 抗飽和
  float out = pid.Kp * err + pid.Ki * pid.integral + pid.Kd * (err - pid.prevErr);
  pid.prevErr = err;
  out = constrain(out, 0.0f, 100.0f);                       // %
  float delta = constrain(out - pid.out, -4.0f, 4.0f);      // 斜坡
  pid.out = constrain(pid.out + delta, 0.0f, 100.0f);
  if (gTemp >= T_HARD_OFF) pid.out = 0;                     // 軟斷壓過一切
  return (uint16_t)(pid.out / 100.0f * ((1 << PWM_RES) - 1));
}

/* ------------------------------------------------------------
 * readSensors —— SHT31 + DS18B20 + 健康檢查
 *   連續 3 次失敗:加熱鎖定 + 推播(感測失靈還在加熱是最壞情境)
 * ------------------------------------------------------------ */
void readSensors() {
  float t = sht31.readTemperature();
  float h = sht31.readHumidity();
  if (isnan(t) || isnan(h) || h < 0 || h > 100) {
    if (++failCnt >= 3) {
      ledcWrite(0, 0);
      tgPush("孵化器:SHT31 連續讀取失敗,加熱已鎖定,請立即檢查!");
    }
    sensorOk = false;
    return;
  }
  failCnt = 0; sensorOk = true;
  gTemp = t; gHum = h;
  waterT.requestTemperatures();
  gWaterT = waterT.getTempCByIndex(0);
}

uint32_t incubStartDay = 0;   // 孵化起算日(epoch day),setup 從 RTC 讀入

/* ------------------------------------------------------------
 * humidityTarget —— 21 天日程濕度目標
 *   回傳:目標 RH%(D1-18 → 55;D19-21 → 65;出雛後 → 70)
 *   為什麼用 RTC 起算日:斷電重啟後日程不能歸零
 * ------------------------------------------------------------ */
uint8_t humidityTarget() {
  if (!rtcOk) return 55;
  uint32_t day = rtc.now().unixtime() / 86400 - incubStartDay;
  if (day <= 18) return 55;
  if (day <= 21) return 65;
  return 70;
}

/* ------------------------------------------------------------
 * atomizerControl —— 濕度閉環 + 水溫安全
 *   遲滯 ±3%;水溫 > 33°C 或箱溫 > 38.3°C 強制停(防燉蛋)
 * ------------------------------------------------------------ */
void atomizerControl() {
  uint8_t target = humidityTarget();
  if (!sensorOk || gWaterT > WATER_MAXT || gTemp > T_TARGET + 0.5f) {
    if (atomizerOn) { atomizerOn = false; digitalWrite(PIN_ATOMIZER, HIGH); }
    return;
  }
  if (!atomizerOn && gHum < target - 3)      { atomizerOn = true;  digitalWrite(PIN_ATOMIZER, LOW);  }
  else if (atomizerOn && gHum > target + 3)  { atomizerOn = false; digitalWrite(PIN_ATOMIZER, HIGH); }
}

/* ------------------------------------------------------------
 * turnEggTick —— 每 4 小時翻蛋 90°(狀態機,不 delay)
 *   堵轉偵測:DRV8833 Cp 電流 > 閾值持續 3 秒 → 停 + 推播
 * ------------------------------------------------------------ */
void stopMotor();   // 前向宣告(定義在下方)
void turnEggTick(uint32_t now) {
  if (rtcOk) {
    uint32_t day = rtc.now().unixtime() / 86400 - incubStartDay;
    if (day > 20) return;                    // 出雛期停止翻蛋
  }
  if (!turning && now - lastTurnMs >= TURNDOWN_MS) {
    turning = true; turnStartMs = now; turnDir ^= 1;
    digitalWrite(PIN_AIN1, turnDir ? HIGH : LOW);
    digitalWrite(PIN_AIN2, turnDir ? LOW : HIGH);
    return;
  }
  if (turning) {
    if (analogRead(PIN_MOTOR_CS) > 2600) {   // 堵轉電流偏高
      static uint32_t stallMs = 0;
      if (stallMs == 0) stallMs = now;
      else if (now - stallMs > 3000) {
        stopMotor(); tgPush("孵化器:翻蛋馬達堵轉,請檢查轉盤有無卡住!");
        turning = false; lastTurnMs = now; stallMs = 0;
      }
    } else if (now - turnStartMs > 12000) {  // 正常 12 秒完成 90°
      stopMotor(); turning = false; lastTurnMs = now;
    }
    if (now - turnStartMs > TURN_TIMEOUT) {  // 逾時保險
      stopMotor(); turning = false; lastTurnMs = now;
      tgPush("孵化器:翻蛋逾時已強制停止");
    }
  }
}
void stopMotor() { digitalWrite(PIN_AIN1, LOW); digitalWrite(PIN_AIN2, LOW); }  // 滑行停止

/* ------------------------------------------------------------
 * powerWatch —— 供電狀態推播
 *   原理:Wi-Fi 連線存活 ≈ 市電正常(斷電時路由器先走);
 *   復電後補發「斷電區間」訊息
 * ------------------------------------------------------------ */
void powerWatch(uint32_t now) {
  bool online = (WiFi.status() == WL_CONNECTED);
  if (!online) {
    if (powerOffMarkMs == 0) powerOffMarkMs = now;
    if (!powerWasLost && now - powerOffMarkMs > PWRLOSS_MS) {
      powerWasLost = true;                    // 佇列滯留,復電後 tgFlush 自動送出
      tgPush("孵化器:偵測到斷電已超過 15 分鐘!蛋箱溫度正在下降,請立即處理");
    }
  } else {
    if (powerWasLost) {
      tgPush("孵化器:供電恢復,目前 " + String(gTemp, 1) + "°C / " + String(gHum, 0) + "%RH");
      powerWasLost = false;
    }
    powerOffMarkMs = 0;
  }
}

/* ------------------------------------------------------------
 * drawOled —— 溫度大字 + 濕度/天數/翻蛋倒數
 * ------------------------------------------------------------ */
void drawOled() {
  oled.clearDisplay();
  oled.setTextSize(2); oled.setTextColor(SSD1306_WHITE);
  oled.setCursor(0, 0);
  oled.print(sensorOk ? String(gTemp, 1) : "--.-");
  oled.setTextSize(1);
  oled.setCursor(96, 4); oled.print("C");
  oled.setCursor(0, 24);
  oled.print(String(gHum, 0) + "%  D" + String(rtcOk ? (uint8_t)(rtc.now().unixtime() / 86400 - incubStartDay) : 0));
  oled.setCursor(0, 40);
  uint32_t nextTurn = turning ? 0 : (TURNDOWN_MS - (millis() - lastTurnMs)) / 60000;
  oled.print(turning ? "TURNING..." : "TURN " + String(nextTurn / 60) + "h" + String(nextTurn % 60) + "m");
  oled.display();
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  // 致動器先歸零(霧化共陽:HIGH = 關)
  pinMode(PIN_HEATER, OUTPUT);    ledcSetup(0, 5000, PWM_RES); ledcAttachPin(PIN_HEATER, 0); ledcWrite(0, 0);
  pinMode(PIN_ATOMIZER, OUTPUT);  digitalWrite(PIN_ATOMIZER, HIGH);
  pinMode(PIN_FAN, OUTPUT);       digitalWrite(PIN_FAN, HIGH);      // 風扇常轉助均溫
  pinMode(PIN_AIN1, OUTPUT);      pinMode(PIN_AIN2, OUTPUT); stopMotor();

  Wire.begin(PIN_SDA, PIN_SCL);
  oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  rtcOk = rtc.begin();
  if (rtc.lostPower()) rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));  // RTC 首次上電:用編譯時間
  sht31.begin(0x44);
  waterT.begin();

  // 孵化起算日:存在 RTC 的 AT24C32(斷電保留);首次執行寫入今天
  incubStartDay = rtc.now().unixtime() / 86400;   // 示範:以今天起算;量產版讀外部 EEPROM

  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  uint32_t t0 = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - t0 < 15000) { delay(300); }
  if (rtcOk && WiFi.status() == WL_CONNECTED) configTime(8 * 3600, 0, "pool.ntp.org");  // NTP 校 RTC

  tgPush("孵化器上電:控制迴路啟動(目標 " + String(T_TARGET, 1) + "°C)");
}

void loop() {
  uint32_t now = millis();
  static uint32_t lastPid = 0, lastSensor = 0, lastOled = 0;

  if (now - lastSensor >= SENSOR_MS) {
    lastSensor = now;
    readSensors();
    atomizerControl();
    turnEggTick(now);
    powerWatch(now);
    if (sensorOk && gTemp < T_LOW_ALARM && !powerWasLost) {
      static uint32_t lowMs = 0;
      if (lowMs == 0) lowMs = now;
      else if (now - lowMs > 10UL * 60000) { lowMs = 0; tgPush("孵化器:溫度低於 36.5°C 已 10 分鐘,請檢查加熱器!"); }
    }
  }
  if (now - lastPid >= PID_MS) {
    lastPid = now;
    if (sensorOk) ledcWrite(0, pidStep(T_TARGET - gTemp));
    else ledcWrite(0, 0);
  }
  if (now - lastOled >= 500) { lastOled = now; drawOled(); }
  tgFlush();
  delay(2);
}

🏗️ 組裝步驟

📘 圖文版組裝說明書:每一步附示意圖、預估時間與難度標記,可直接列印 A4 帶到工作檯 → 開啟 HTML 組裝說明書
  1. 列印零件:翻蛋轉盤、蛋槽插片×12、齒箱支架、SHT31 罩、霧氣導流板、控制頂蓋(全 PETG)
  2. 箱體加工:保麗龍箱側壁開霧氣入口(下緣)與排氣孔(對角上緣)、頂蓋開 OLED 窗
  3. 焊動力板:加熱迴路(含 40°C 保險絲)、DRV8833、霧化共陽驅動,全部焊死 + 熱縮管
  4. 裝翻蛋機構:馬達入齒箱支架、轉盤對位、手動轉 3 圈確認無卡點
  5. 安全測試(必做):空箱跑 PID 2 小時記錄溫度曲線;吹風機升溫驗證 38.8°C 軟斷;拔保險絲端驗證迴路斷開;水箱加熱驗證霧化停止
  6. 入箱定位:SHT31 懸蛋盤高度、風扇對角、水溫探頭沉底
  7. 空跑 24 小時:不放蛋,記錄溫度曲線與翻蛋 6 次全成功後,才放蛋開跑

🔧 問題排除

問題原因解決方法
溫度振盪 ±0.5°C 以上PID 的 Ki 過大或 PWM 週期過慢Ki 減半、確認 PWM 5kHz;加熱片前加鋁散熱板增加熱緩衝
溫度爬不到 37.8°C箱體太大/環境低於 18°C/加熱片瓦數不足箱外裹毛巾保溫;15W 不夠換 25W(同步換大顆 MOSFET)
濕度怎麼霧化都上不去箱體透氣過大或霧化片功率不足縮小排氣孔;霧化片加到 24W 雙片;水箱加溫至室溫(冷水霧化量小)
翻蛋馬達堵轉誤報Cp 閾值太敏感或馬達電壓過低閾值 2600 上调到 2900;馬達改 8V 供電(7808)
斷電推播沒收到路由器比孵化器先斷(推播滯留佇列)設計如此:復電後補發;若要即時告警需加 UPS/行動電源供電路由器
第 19 天濕度沒升到 65%日程以 RTC 起算日計算,RTC 電池沒電換 CR1220;OLED 天數異常即 RTC 失準訊號

🎯 進階延伸

📦 檔案清單

egg-incubator/
├── src/
│   └── egg-incubator.ino        # ESP32 程式碼(本頁完整程式)
├── stl/
│   ├── turntable.stl            # 翻蛋轉盤
│   ├── egg-slot.stl             # V 形蛋槽插片
│   ├── gearbox-mount.stl        # 馬達齒箱支架
│   ├── sht31-hood.stl           # SHT31 通風罩
│   ├── mist-baffle.stl          # 霧氣導流板
│   └── control-top.stl          # 控制頂蓋
├── f3d/
│   └── egg-incubator.f3d        # Fusion 360 原始檔
├── manual/
│   └── assembly.html            # 圖文組裝說明書
└── README.md                    # 說明文件
← 上一篇:ESP32-C5 智慧燈絲乾燥箱 📚 系列索引 下一篇:ESP32-S3 桌面網路收音機 →