📋 專案概述
上個月你把 3W 雷雕機升級成切 3mm 椴木的「接單小炮」,第一張椪糖造型吊飾單就做焦了八片:切面焦黑、板邊被煙燻出一圈黃漬,客人傳訊問「這是燒過的嗎」。買家不知道,你的雷雕機根本沒有空氣輔助。於是你買了台氣泵接上——新問題接踵而來:這種泵是 92dB 的怒吼型選手,雕刻一個 40 分鐘的 job,氣泵要全程嗷嗷叫;更扯的是它跟雷射完全是兩個世界,開機要記得開氣、暫停要記得關氣。上週五晚上你趕單到凌晨兩點,忘記開氣跑了 40 分鐘——今天打開機殼,focus 鏡片鍍膜燒出一個直徑 0.5mm 的白點。鏡片 NT$450 事小,「氣壞了」这件事,你的機器一點都不知道:氣管早就從接頭崩開,雷射照樣噴了半小時。
市售商品:
- 獨立氣泵(含氣管):NT$900 上下,只有開/關;「跟雷射同步」這件事要你自己手動——手動的東西在凌晨兩點一定會失敗。
- 雷雕機內建氣泵套件(原厂高階機):NT$15,000 起才有;而且多數也只有「開機即轉」,不會斷氣保護。
- 智慧插座+定時器:NT$400,能「開機時一起開」,但它不知道雷射有沒有在點火——照樣空轉把碎屑吹滿工作台。
本模組把「氣」和「光」綁成硬連鎖(hard interlock):用兩顆光耦合器從雷射主板隔空偷看 FIRE(點火)與 EN(使能)兩條訊號——不共地、不碰主板邏輯——然後管理一台幫浦和一顆電磁閥的完整氣序:
- 預吹 0.5s:EN 一上電(job 開始)先開閥把鏡片和切口餘灰吹穩,火才有資格點——順序由狀態機保證,凌晨兩點也不例外
- 切割跟火、後吹 0.8s:最後一個火脈衝結束後氣再吹 0.8 秒帶走餘煙,然後閥關、幫浦軟停
- job 結束排煙吹 15s:偵測 job 結束再多吹 15 秒,把煙道和切割-platform 下的積煙清出去(切皮革的靈魂步驟)
- 斷氣保護 0.3s 反應:ACS712 量幫浦電流——氣管脫落≠電流變化?錯!隔膜幫浦出口失壓、負載驟降,電流會掉到閾值以下;濾芯塞死則電流飆高。太薄=沒氣、太胖=塞死,兩個方向都抓
- 幫浦 PWM 軟啟動 1.5s:深夜開單不被泵的一記獅吼嚇到;MOSFET 線性區快速通過設計,不燒管子
- 統計推 NAS:火數/點火秒數/幫浦時數/濾芯剩餘小時(100h 週期)→ 每日彙報;「濾芯該拍了」這種事再也不用記
💰 成本效益:全機 ~NT$705。一片 focus 鏡片 NT$450、一次焦壞的客戶訂單 NT$1,200+心情。氣assist 把切口焦黑率直接砍半(氧被吹走=燃燒變缺氧碳化),接單品質的提升是立即的。
為什麼選 ESP32-C3?
| 需求 | C3 的優勢 |
|---|---|
| 反應要快 | 單核 RISC-V 160MHz+GPIO 中斷延遲 <2µs——斷氣保護從電流異常到蜂鳴器響,全鏈路 300ms 內完成,綽綽有餘 |
| ADC 量電流 | ADC1 掛 GPIO0-4,ACS712 的模擬輸出直接吃;C3 的 ADC1 與 Wi-Fi 共電源的噪訊問題用中位數濾波解決 |
| 模組比咖啡便宜 | NT$120 就有 Wi-Fi+藍牙+中斷能力;這台的功能本就不需要雙核,把钱花在幫浦上 |
| 貼著機器裝 | 單板低功耗、可直接固定在雷雕機電源上蓋內;C3 溫度範圍也撐得住機殼裡夏天 55°C |
🧱 硬體清單
| 項目 | 型號 | 數量 | 預估單價 | 備註 |
|---|---|---|---|---|
| 主控板 | ESP32-C3 SuperMini / DevKitM-1 | 1 | ~120 元 | ADC1 於 GPIO0-4 |
| 幫浦 | 12V 無油隔膜幫浦(60L/min 級) | 1 | ~280 元 | 附減震腳;92dB 是常態,靠軟啟動減驚嚇 |
| 電磁閥 | 12V 二通常閉電磁閥(銅閥座) | 1 | ~90 元 | N/C:斷電=關氣,安全デフォルト |
| 電流感測 | ACS712-05A 模組 | 1 | ~60 元 | 串在幫浦負端;100mV/A |
| 光耦 | PC817C 光耦合器模組 | 2 | ~10 元 | FIRE/EN 各一顆,隔離 <5ms 延遲不影響 |
| MOSFET | IRLZ44N(邏輯閘極)+10kΩ 下拉 | 2 | ~40 元 | Q1 幫浦(PWM)/Q2 閥(開關) |
| 顯示 | SSD1306 0.96" I2C | 1 | ~90 元 | 狀態機一目了然 |
| 蜂鳴器 | 源式有源蜂鳴器 5V | 1 | ~15 元 | 斷氣警報,比推播快 |
| 電源 | 沿用雷雕機 12V(或獨立 12V 3A) | 1 | ~0 元 | ACS712 需 5V→從 C3 板取 |
| 列印+氣材 | PETG 列印件+4/6mm 軟管+快速接頭 | — | ~100 元 | 分流塊、泵架、控制盒 |
| 合計 | ~705 元 | 原厂高階機的 5%;一片鏡片的 1.6 倍 |
🔌 接線圖
詳細接線
| ESP32-C3 腳位 | 連接 | 說明 |
|---|---|---|
| GPIO2 | PC817#1 輸出端 | FIRE 點火訊號(經 1kΩ 驅動內側 LED,輸入側並接主板觸發線) |
| GPIO3 | PC817#2 輸出端 | EN 使能訊號(主板 L/ON 線) |
| GPIO0 | ACS712 OUT | ADC1;串於幫浦電源負端量總電流 |
| GPIO6 | Q1 IRLZ44N 閘極 | 幫浦 PWM 軟啟動(1kHz);10kΩ 閘極下拉 |
| GPIO5 | Q2 IRLZ44N 閘極 | 電磁閥開關;閥線圈並 1N4007 續流 |
| GPIO8 / GPIO9 | OLED SDA / SCL | 狀態機與即時電流顯示 |
| GPIO1 | 按鈕(對 GND) | 短按重設統計、長按強制通氣(除塵用) |
雷雕機主板 本模組(PC817 隔離)
FIRE ──┬─ 主板側 C3 側:輸出的 C-E 並 10kΩ 上拉 3V3
│ 1kΩ→PC817 LED→GND └──→ GPIO2(火脈衝=低電位)
EN ──┴─(同 topology,第二顆)└──→ GPIO3
┌──────────────────────────────┐
12V+ ──┬─幫浦+─┘ 幫浦− → ACS712 IP+ / IP− ─→ Q1 D│S→GND (G←GPIO6 PWM)
│ OUT→1k→GPIO0 │
└─電磁閥+ ── 閥− → Q2 D│S→GND (G←GPIO5) │ 閥線圈 ∥ 1N4007
OLED ←GPIO8/9 按鈕 GPIO1→GND 蜂鳴器 ←5V源式
⚠️ ACS712 供電 5V、輸出零點 2.5V:進 C3 的 3.3V ADC 前串 1kΩ 並對 3.3V 接蕭特基鉗位二極體——幫浦卡死瞬間輸出可能衝過 3.3V,這顆二極體是你 ADC 的保險絲。
⚠️ 電磁閥是感性負載:1N4007 續流二極體沒焊,Q2 下個 job 就走。幫浦同理(MOSFET 端已有體二極體,但建議再加一顆 TVS/RC -snubber)。
🖨️ 3D 列印設計
| 零件 | 材質/設定 | 數量 | 備註 |
|---|---|---|---|
| manifold 分流塊 | PETG/0.2mm/3 壁/40%(氣壓要受) | 1 | 一進三出、三噴嘴同心夾持 360° 環繞光軸 |
| nozzle-ring 噴嘴適配環 | PETG/0.2mm/3 壁/50% | 2 | 內徑對應 12/14mm 雷射頭外徑,M3 鎖固 |
| pump-mount 泵架 | PETG/0.28mm/2 壁/25% | 1 | 四角穿矽膠減震柱,泵體懸吊固定 |
| duct-clip 軟管夾組 | PETG/0.24mm/2 壁/30% | 6 | X 臂隨行鏈走線,留 8cm 應變環 |
| box-control 控制盒 | PETG/0.2mm/2 壁/25% | 1 | 鎖在機殼電源上蓋,OLED 觀察窗 |
雷雕機台附近有鏡片反射的紫外與高溫,PETG 是底線、PLA 會蠕變;分流塊噴嘴角度設計為下傾 15°,氣簾正好壓住切口的上升煙柱。
💻 Arduino 程式碼
Arduino IDE 2.x+espressif 3.x core(開發板 ESP32C3 Dev Module);函式庫:Adafruit SSD1306、Adafruit GFX。
/* ============================================================
* 雷雕同步空氣輔助 laser-airassist.ino
* ------------------------------------------------------------
* 功能:氣光硬連鎖(預吹/跟火/後吹/排煙四段氣序)、
* 幫浦電流斷氣保護、軟啟動、統計推 NAS
* 硬體:ESP32-C3|GPIO2←FIRE、GPIO3←EN(PC817,低有效)
* GPIO6→泵 PWM、GPIO5→閥、GPIO0←ACS712、
* GPIO8/9 I2C、GPIO1 按鈕、GPIO10 蜂鳴器
* 供電:12V 共用電源、GND 共點;ACS712 供 5V
* 相依:Adafruit_SSD1306、Adafruit_GFX(core 3.x)
* ============================================================ */
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <Preferences.h>
/* --- 0. 配置 --- */
const char* WIFI_SSID = "YOUR_SSID";
const char* WIFI_PASS = "YOUR_PASS";
const char* NAS_URL = "http://192.168.8.60:1880/airassist";
const char* NAS_TOKEN = "change-me";
#define PIN_FIRE 2 // PC817 低有效:脈衝=LOW
#define PIN_EN 3
#define PIN_CUR 0 // ADC1!GPIO6/7 是 ADC2,Wi-Fi 開著會死
#define PIN_PUMP 6
#define PIN_VALVE 5
#define PIN_BTN 1
#define PIN_BUZ 10
const uint32_t PRE_BLOW_MS = 500; // 預吹
const uint32_t POST_BLOW_MS = 800; // 後吹
const uint32_t SCAVENGE_MS = 15000; // job 結束排煙吹
const uint32_t ARM_TIMEOUT_MS= 180000; // 3 分鐘無火自動離崗
const float I_MIN = 0.40f; // 斷氣(失壓)門檻 A
const float I_MAX = 2.20f; // 塞死門檻 A
const uint32_t I_MAX_HOLD = 10000; // 塞死持續判定
const float ACS_ZERO = 2.50f, ACS_K = 0.100f; // 零點 V、V/A
/* --- 1. 狀態機 --- */
enum St { IDLE, ARM, CUT, BLOW, SCAV, ALARM } st = IDLE;
uint32_t stEnter = 0, lastFire = 0, enSince = 0;
float amps = 0; uint32_t pumpDuty = 0; // 0..1000
uint32_t firePulses = 0, fireUs = 0; float pumpSecs = 0, filterSecs = 0;
uint32_t jobs = 0, alarmSince = 0; uint8_t alarmCode = 0; // 1=斷氣 2=塞死
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
Preferences prefs;
/* --- 2. FIRE 中斷:記錄與計數 ---
* 參數:無|回傳:無
* 為什麼放在 ISR:火脈衝可短至幾十 µs,主迴圈輪詢必漏拍。
* millis() 在 ESP32 ISR 内是安全讀取(僅讀計數變數) */
volatile uint32_t firePulseCnt = 0;
volatile uint32_t fireStampMs = 0;
void IRAM_ATTR fireISR() {
if (digitalRead(PIN_FIRE) == LOW) { // 只看有效邊
firePulseCnt++;
fireStampMs = millis();
}
}
/* --- 3. 電流讀取:中位數濾波 ---
* 參數:無|回傳:幫浦電流 A(零點誤差由全域 curOffset 扣除)
* 為什麼中位數:C3 ADC1 與 Wi-Fi PA 共享電源路徑,偶發毛刺用均值扛不住 */
float readAmps() {
uint16_t s[9];
for (int i = 0; i < 9; i++) { s[i] = analogRead(PIN_CUR); delayMicroseconds(200); }
for (int i = 0; i < 8; i++) for (int j = i + 1; j < 9; j++)
if (s[j] < s[i]) { uint16_t t = s[i]; s[i] = s[j]; s[j] = t; }
float v = s[4] * 3.3f / 4095.0f; // 11dB 衰減檔:滿量程約 3.3V
return constrain((v - ACS_ZERO) / ACS_K, 0, 5); // ACS712@5V:零點 2.5V
}
float curOffset = 0; // 零點校準值(A),setup 裡量
/* --- 4. 氣序狀態機:本模組的靈魂 ---
* 參數:dtMs 本拍毫秒|回傳:無
* IDLE→ARM(預吹)→CUT(跟火)→BLOW(後吹)→SCAV(排煙)→IDLE */
void sequencer(uint32_t nowMs, uint32_t dtMs) {
bool enActive = digitalRead(PIN_EN) == LOW;
bool firing = (nowMs - fireStampMs) < 50; // 50ms 內有火
if (firing) lastFire = nowMs; // 後吹倒數從最後一次火算起
switch (st) {
case IDLE:
/* EN 優先;無 EN 訊號的機型由首個 FIRE 直接進 CUT(fallback) */
if (enActive) { st = ARM; stEnter = nowMs; jobs++; valve(false); pumpRamp(0); }
else if (firing) { st = CUT; stEnter = nowMs; jobs++; }
break;
case ARM: // 預吹:幫浦全速+開閥
pumpRamp(1000); valve(true);
/* 預吹滿 0.5s 才承認火——雷射早點幾毫秒會少吹一點,但狀態不被抢跑 */
if (firing && nowMs - stEnter >= PRE_BLOW_MS) { st = CUT; stEnter = nowMs; }
else if (nowMs - stEnter > PRE_BLOW_MS + ARM_TIMEOUT_MS) st = IDLE;
break;
case CUT:
pumpRamp(1000); valve(true);
if (nowMs - lastFire > POST_BLOW_MS) { st = SCAV; stEnter = nowMs; }
break;
case SCAV: // 排煙:先全吹再軟停
if (nowMs - stEnter < SCAVENGE_MS) pumpRamp(1000);
else if (nowMs - stEnter < SCAVENGE_MS + 1500) pumpRamp(300);
else { pumpRamp(0); valve(false); st = IDLE; stEnter = nowMs; }
break;
case ALARM: // 斷氣/塞死:關閥停泵+蜂鳴
valve(false); pumpRamp(0); digitalWrite(PIN_BUZ, (nowMs / 120) % 2);
break;
}
if (st != IDLE && st != ALARM) { pumpSecs += dtMs / 1000.0f; filterSecs += dtMs / 1000.0f; }
if (st != ALARM && firing) fireUs += 5; // 估算點火時數(脈衝×解析度)
}
/* --- 5. 幫浦軟啟動斜坡:1.5s 線性 --- */
void pumpRamp(uint32_t target) { // target 0..1000
if (pumpDuty < target) pumpDuty = min(target, pumpDuty + 10); // 每 15ms 拍
else if (pumpDuty > target) pumpDuty = max(target, (uint32_t)((int32_t)pumpDuty - 10));
ledcWrite(PIN_PUMP, pumpDuty);
}
void valve(bool on) { digitalWrite(PIN_VALVE, on ? HIGH : LOW); }
/* --- 6. 斷氣保護:雙向電流判讀 ---
* 參數:dtMs|回傳:無
* 氣管脫落→出口失壓→電流掉;濾芯塞死→負載升→電流漲 */
void watchdog(uint32_t nowMs, uint32_t dtMs) {
bool running = (st == CUT || st == ARM || st == SCAV) && pumpDuty > 800;
if (!running) { alarmSince = 0; return; }
bool low = amps < I_MIN;
bool high = amps > I_MAX;
if (high) { if (alarmSince == 0) alarmSince = nowMs;
else if (nowMs - alarmSince > I_MAX_HOLD) trip(2); }
else if (low) { if (nowMs - stEnter > 300) trip(1); } // 升速後 300ms 仍沒電流=沒氣
else alarmSince = 0;
}
void trip(uint8_t code) {
alarmCode = code; st = ALARM; stEnter = millis();
alarmSince = millis(); // ALARM 30 秒後自動復歸 IDLE(人工排除後重試)
}
/* --- 7. NAS 推送:統計+警報 --- */
void pushStats() {
if (WiFi.status() != WL_CONNECTED) return;
HTTPClient http; http.begin(NAS_URL);
http.addHeader("Content-Type", "application/json");
http.addHeader("X-Device-Token", NAS_TOKEN);
String body = "{\"jobs\":" + String(jobs) + ",\"pulses\":" + String(firePulses) +
",\"fire_s\":" + String(fireUs / 1e6, 1) + ",\"pump_h\":" + String(pumpSecs / 3600, 2) +
",\"filter_h\":" + String(filterSecs / 3600, 1) + ",\"st\":" + String((int)st) +
",\"amps\":" + String(amps, 2) + ",\"alarm\":" + String(alarmCode) + "}";
http.POST(body); http.end();
}
/* --- 8. 面板 --- */
void drawPanel() {
const char* names[] = {"IDLE", "PRE-BLOW", "CUTTING", "BLOW-OFF", "SCAVENGE", "ALARM"};
oled.clearDisplay(); oled.setTextSize(1); oled.setCursor(0, 0);
oled.printf("%-9s %4.2fA\n", names[st], amps);
oled.printf("jobs %lu plse %lu\n", (unsigned long)jobs, (unsigned long)firePulses);
oled.printf("pump %2.1fh filt %3.0fh\n", pumpSecs / 3600, filterSecs / 3600);
if (alarmCode) oled.printf("! %s\n", alarmCode == 1 ? "NO AIR!" : "BLOCKED!");
oled.display();
}
/* --- 9. setup --- */
void setup() {
Serial.begin(115200);
pinMode(PIN_FIRE, INPUT_PULLUP); pinMode(PIN_EN, INPUT_PULLUP);
pinMode(PIN_BTN, INPUT_PULLUP); pinMode(PIN_BUZ, OUTPUT);
pinMode(PIN_VALVE, OUTPUT); digitalWrite(PIN_VALVE, LOW);
ledcAttach(PIN_PUMP, 1000, 10); // 1kHz/10bit 軟啟動
prefs.begin("airassist");
jobs = prefs.getUInt("jobs", 0); filterSecs = prefs.getFloat("filt", 0);
Wire.begin(8, 9); oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
oled.setTextColor(SSD1306_WHITE);
for (int i = 0; i < 5; i++) curOffset += analogRead(PIN_CUR); // 零點校準
curOffset = curOffset / 5 * 3.3f / 4095.0f / ACS_K;
attachInterrupt(digitalPinToInterrupt(PIN_FIRE), fireISR, FALLING);
WiFi.begin(WIFI_SSID, WIFI_PASS);
analogSetPinAttenuation(PIN_CUR, ADC_11db);
}
/* --- 10. loop:15ms 控制拍 --- */
void loop() {
static uint32_t tCtl = 0, tNet = 0, tOled = 0, btnHold = 0, btnLast = HIGH;
uint32_t m = millis();
if (m - tCtl >= 15) {
tCtl = m;
firePulses += firePulseCnt; firePulseCnt = 0;
amps = readAmps() - curOffset;
sequencer(m, 15);
watchdog(m, 15);
if (st == ALARM && m - stEnter > 30000) { st = IDLE; alarmCode = 0; }
}
if (m - tOled >= 500) { tOled = m; drawPanel(); }
if (m - tNet >= 60000) {
tNet = m; pushStats();
prefs.putUInt("jobs", jobs); prefs.putFloat("filt", filterSecs);
}
/* 按鈕:短按重設濾芯計時;長按 2s 強制通氣(除塵吹灰) */
uint8_t b = digitalRead(PIN_BTN);
if (b == LOW && btnLast == HIGH) btnHold = m;
if (b == HIGH && btnLast == LOW) {
if (m - btnHold > 2000) { forceBlow(); }
else { filterSecs = 0; }
}
btnLast = b;
}
void forceBlow() { // 除塵模式:閥開泵轉 5 秒後自動關
valve(true); for (uint32_t t = 0; t < 333; t++) { pumpRamp(1000); delay(15); }
pumpRamp(0); valve(false);
}
⚠️ ADC 腳位必選 GPIO0-4(ADC1):GPIO6/7 是 ADC2,Wi-Fi 啟用時讀值亂跳——本設計 PWM 輸出佔了 GPIO6,電流輸入固定在 GPIO0,就是為了把 ADC2 讓出去避免踩雷。
📦 NAS 端規則(Node-RED):①
alarm==1 → Telegram 電話級「雷雕機斷氣!幫浦還在轉但電流只有 0.2A——氣管檢查」;②filter_h>100 → 每日輕提醒「濾芯該拆下來拍一拍了」;③每週彙報:fire_s/pump_h 比值就是「這週氣泵白叫了幾小時」的羞愧指數(本模組通常把它壓到 0.6 以下)。🏗️ 組裝步驟
📘 圖文版組裝說明書:每一步附示意圖、預估時間與難度標記,可直接列印 A4 帶到工作檯 → 開啟 HTML 組裝說明書
- 列印後處理:分流塊噴嘴孔用 2mm 鑽頭通一遍、適配環內徑試套雷射頭(緊配但不強壓)
- 氣路組裝:泵→軟管→電磁閥→分流塊→三噴嘴環繞光軸;所有接頭上紮帶,運轉後拉拽測試
- 光耦接訊號:FIRE/EN 各一顆 PC817,輸入側 1kΩ 串聯並接主板訊號線——先斷機電、用三用表確認接錯不會短路再上電
- 功率級焊接:Q1 泵/Q2 閥,閘極 10kΩ 下拉、閥線圈 1N4007;ACS712 串幫浦負端
- 零點校準:幫浦未運轉時 Serial 讀 ADC,把漂移量寫進 curOffset(出廠零點有 ±50mV 誤差)
- 靜態驗證:手動短接 PC817 輸入側模擬 EN/FIRE 序列,驗證 IDLE→預吹→跟火→後吹→排煙全流程與時序
- 斷氣演練:切割模擬中拔開氣管快速接頭,300ms 內蜂鳴+ALARM 顯示——演練通過才準真實雕刻
- 隨機固定與試雕:控制盒鎖機殼、軟管隨行鏈留 8cm 應變環;先雕廢料角料確認無焦邊、鏡片 30 分鐘後無霧化
🔧 問題排除
| 問題 | 原因 | 解決方法 |
|---|---|---|
| 完全無反應(永遠 IDLE) | EN 極性接反(该机高有效) | 看 OLED 的 EN 原始值確認極性,韌體把 == LOW 改 == HIGH;光耦接線不動 |
| 誤觸發:按面板鍵氣泵就轉 | FIRE 線上感應毛刺/共地迴路 | PC817 輸入側本來就隔離,檢查是不是把 5V 電源線誤接成訊號線 |
| 電流讀值亂跳 | ADC 腳用到 ADC2(GPIO6/7)或 Wi-Fi PA 毛刺 | 電流輸入固定 GPIO0-4;中位數濾波已內建,仍跳則 OUT 對 GND 加 100nF |
| 幫浦 MMM 叫但不轉(啟動時) | 軟啟動斜坡起點電壓不足以啟動馬達 | 斜坡起點改 350(先給啟動扭矩再爬升),或換 100%→PWM 降頻式軟啟動 |
| 斷氣誤報(SCAV 段常響) | I_MIN 高於幫浦空載電流 | Serial 記錄實測空載 A 值(本泵 0.6A),門檻設空載的 60% |
| 切邊仍然发黄 | 噴嘴角度太淺,氣流從切口上方掠過 | 噴嘴下傾改 25°、距切面 4-6mm;流量不是萬能,角度才是 |
| 鏡片還是霧了 | 預吹不足:EN 訊號機型沒有(只有 FIRE) | 無 EN 機接法:ARM 段由首個 FIRE 觸發、預吹改「後吹加倍+定期鏡片檢查」;或從電源板取激光致能點 |
🎯 進階延伸
- 熄火連鎖:ALARM 時另開一顆光耦反方向「按暫停」——用同一顆 PC817 模擬面板訊號,真正的硬連鎖(要做進韌體前務必先用廢料驗證)
- 煙感聯動:加顆 MQ-2 在排口,切皮革的煙峰直方圖存 NAS——「今天切了 40 張牛皮」有据可查
- 氣壓閉環:MPX5050 接分流塊測靜壓,PWM 維持定壓——長軟管/多噴嘴衰減自動補償
- 多機共享:一台 60L 泵經氣排帶供雷雕+噴墨+雷射焊接三台,每台一顆本模組的閥與感測
📦 檔案清單
laser-airassist/
├── src/
│ └── laser-airassist.ino # ESP32 韌體(本頁完整程式)
├── nas/
│ └── node-red-flow.json # 警報/統計/濾芯週期
├── stl/
│ ├── manifold.stl # 一進三出分流塊
│ ├── nozzle-ring-12.stl # 噴嘴適配環 12mm
│ ├── nozzle-ring-14.stl # 噴嘴適配環 14mm
│ ├── pump-mount.stl # 減震泵架
│ ├── duct-clip.stl # 隨行軟管夾
│ └── box-control.stl # 控制盒
├── f3d/
│ └── airassist-design.f3d # Fusion 360 原始檔
├── test/
│ └── seq-simulator.ino # 短接模擬 EN/FIRE 的台架測試
└── manual/
└── assembly.html # 圖文組裝說明書