ปรับแก้โค้ดให้เหมาะสม
จากสเก็ตช์ที่ทำงานได้ ไปสู่โคมไฟที่ใช้งานได้จริง
โค้ดในขั้นที่ 2–3 ทำงานได้ แต่ถ้าลองใช้จริงจะพบปัญหา หน้านี้พาคุณ สังเกตปัญหา → หาสาเหตุ → ปรับแก้ → วัดผล ทีละข้อ โดยแก้เฉพาะโค้ด Arduino โค้ดที่ปรับแล้วทั้งหมดอยู่ใน devices/04_smart_lamp_tuned/04_smart_lamp_tuned.ino เปิดเทียบกับ 03_smart_lamp.ino แล้วดูว่าแตกต่างตรงไหนบ้าง
| ปัญหาที่พบ | สาเหตุ | วิธีปรับแก้ | ที่ไหน |
|---|---|---|---|
| ไฟกะพริบเมื่อแสงใกล้เกณฑ์ | ค่าที่อ่านมีสัญญาณรบกวน และเกณฑ์มีค่าเดียว | Smoothing + Hysteresis | บอร์ด |
| ค่าเซนเซอร์ไม่เต็ม 0–1023 | วงจร/เซนเซอร์แต่ละตัวต่างกัน | Calibration | บอร์ด |
| ละเอียดไม่พอ | ADC 10 บิตค่าเริ่มต้น | analogReadResolution(14) |
บอร์ด |
| หลอดไฟกระโดด ติด-ดับทันที | ใช้ digitalWrite |
PWM fade | บอร์ด |
| โปรแกรมค้างรอ ไม่ตอบสนอง | ใช้ delay() |
millis() ไม่หยุดรอ |
บอร์ด |
1. สังเกตปัญหา: ไฟกะพริบ
อัปโหลดสเก็ตช์ 02_local_nightlight แล้วค่อย ๆ หมุน potentiometer ให้ค่าแกว่งอยู่ใกล้ 358 (ประมาณ 35%) ดู Serial Monitor จะเห็น lamp:1 กับ lamp:0 สลับกันถี่ ๆ
ถ้าสัญญาณจริงคือ 35% แต่สัญญาณรบกวนทำให้ค่าที่อ่านแกว่ง 34% ↔︎ 36% ทุกครั้งที่ข้ามเส้น 35% กฎจะกลับคำตัดสิน เรามีสองวิธีแก้ที่ใช้ร่วมกันได้: ให้เส้นตัดสินมีสองเส้น (hysteresis) และทำให้ค่านิ่งขึ้น (smoothing)
2. Hysteresis: เกณฑ์สองเส้น
แทนที่จะมีเกณฑ์เดียว ให้มี เกณฑ์เปิด ต่ำกว่า เกณฑ์ปิด ในช่วงกลางให้คงสถานะเดิม
ค่าที่อ่านได้ 0 ───────── 307 ═══════════ 409 ───────── 1023
[ เปิดไฟ ] [ คงสถานะเดิม ] [ ปิดไฟ ]
(ต่ำกว่า 30%) (ตั้งแต่ 40% ขึ้นไป)
ลองทำ: แก้ 02_local_nightlight.ino ให้มีเกณฑ์สองค่า
const int ON_BELOW = 307; // ประมาณ 30% ของ 1023
const int OFF_ABOVE = 409; // ประมาณ 40% ของ 1023
bool lampOn = false; // จำสถานะไว้นอก loop()
void loop() {
int raw = analogRead(SENSOR_PIN);
if (raw < ON_BELOW) lampOn = true;
else if (raw >= OFF_ABOVE) lampOn = false;
// ค่าอยู่ระหว่างสองเกณฑ์: ไม่แตะ lampOn (คงสถานะเดิม)
digitalWrite(LED_PIN, lampOn ? HIGH : LOW);
...
}หมุน potentiometer ให้ค่าอยู่ช่วง 307–409 ไฟควรนิ่ง สังเกตว่าตัวแปร lampOn ต้องประกาศนอก loop() เพราะต้องจำค่าข้ามรอบ
สเก็ตช์ขั้นที่ 4 ใช้หลักการเดียวกันใน offlineDecision() สำหรับโหมด offline
เมื่อบอร์ดเชื่อมต่อ gateway การตัดสินใจโหมดอัตโนมัติอยู่ที่ gateway ซึ่งผู้สอนตั้ง hysteresis ได้ด้วย --on-below 30 --off-above 40 (ดูหน้า Gateway) นักเรียนไม่ต้องแก้ส่วนนี้
3. Smoothing: เฉลี่ยค่าหลายครั้ง
แทนที่จะส่งค่าที่อ่านได้ครั้งเดียวทุก 3 วินาที ให้อ่านบ่อย ๆ (ทุก 100 ms) แล้วเก็บค่าล่าสุด 16 ค่า ส่งค่าเฉลี่ย นี่คือ moving average
04_smart_lamp_tuned.ino
void addSample(long value) {
if (sampleCount == WINDOW) sampleSum -= samples[sampleIndex];
else sampleCount++;
samples[sampleIndex] = value;
sampleSum += value;
sampleIndex = (sampleIndex + 1) % WINDOW;
}สังเกตว่าเก็บ ผลรวม ไว้และเพิ่ม/ลบทีละค่า ไม่ต้องบวกครบ 16 ค่าใหม่ทุกรอบ เป็นเทคนิคที่ประหยัดเวลาประมวลผลบนไมโครคอนโทรลเลอร์
WINDOW
เพิ่มจาก 16 เป็น 64 ค่านิ่งขึ้นแต่ตอบสนองช้าลง (เฉลี่ยค่าย้อนหลัง 6.4 วินาที) ลดเหลือ 4 ตอบสนองเร็วแต่ยังแกว่ง เลือกค่าที่สมดุลสำหรับห้องของคุณ นี่คือ trade-off พื้นฐานของการกรองสัญญาณ
4. Calibration: ปรับเทียบเซนเซอร์
เซนเซอร์แต่ละตัวมีช่วงค่าที่ใช้งานจริงไม่เท่ากัน เช่น ห้องมืดสุดอ่านได้ 800 และสว่างสุดอ่านได้ 14,000 (จากเต็ม 16,383) โค้ดจะมองว่ามืดสุดคือ 5% และสว่างสุดคือ 85% ซึ่งไม่ตรงความจริง ให้วัดสองค่านี้จาก Serial Monitor แล้วใส่ใน
04_smart_lamp_tuned.ino
const long SENSOR_DARK = 800; // ค่า 14 บิตเมื่อมืดสุดในห้องของคุณ
const long SENSOR_BRIGHT = 14000; // ค่า 14 บิตเมื่อสว่างสุดฟังก์ชัน lightRaw10() จะแปลงช่วงนี้ให้เป็น 0–1023 ด้วยสูตรเส้นตรง (เหมือน map() ของ Arduino) แล้ว constrain ไม่ให้เกินช่วง ถ้าเซนเซอร์ของคุณให้ค่ากลับด้าน (มืด = ค่ามาก) ให้สลับค่าสองตัวนี้
5. อ่านละเอียดขึ้นด้วย 14 บิต
analogReadResolution(14) ทำให้ ADC ของ UNO R4 ให้ค่า 0–16383 (ละเอียดกว่า 10 บิต 16 เท่า) ค่าที่ส่ง gateway ยังเป็น 10 บิตตามสัญญา API เดิม แต่คำนวณหลังเฉลี่ยและปรับเทียบแล้ว ทำให้ได้ค่าที่นิ่งกว่า
6. PWM fade: ไฟค่อย ๆ สว่าง
ต่อ LED ภายนอกที่ D5 ตามแผนผังวงจร แล้วใช้ analogWrite ปรับความสว่าง 0–255 ทีละน้อย
04_smart_lamp_tuned.ino
void setLamp(bool on) { fadeTarget = on ? 255 : 0; }
void updateFade() {
if (millis() - lastFade < FADE_INTERVAL_MS) return;
lastFade = millis();
if (fadeLevel < fadeTarget) fadeLevel = min(fadeLevel + FADE_STEP, fadeTarget);
else if (fadeLevel > fadeTarget) fadeLevel = max(fadeLevel - FADE_STEP, fadeTarget);
analogWrite(LED_PIN, fadeLevel);
}setLamp() เพียงตั้งเป้าหมาย ส่วน updateFade() ขยับความสว่างเข้าหาเป้าหมายทีละ 5 ระดับทุก 5 ms (ใช้เวลาราว 0.25 วินาทีจากดับถึงสว่างสุด) ลองเปลี่ยน FADE_STEP ให้เป็น 1 แล้วดูว่าหลอดไฟเปลี่ยนไปอย่างไร
7. millis() แทน delay()
delay(3000) ทำให้บอร์ดหยุดทำทุกอย่าง 3 วินาที (อ่าน Serial ไม่ได้ ปรับไฟไม่ได้) ทางที่ดีกว่าคือจดเวลาไว้แล้วเช็คว่าถึงเวลาหรือยัง
if (millis() - lastSample < SAMPLE_INTERVAL_MS) return; // ยังไม่ถึงเวลา ไปทำอย่างอื่นก่อน
lastSample = millis();ด้วยรูปแบบนี้ loop() วนรอบเร็วมาก และงานแต่ละอย่าง (อ่านเซนเซอร์ ปรับไฟ ส่ง Wi-Fi) ทำตามจังหวะของตัวเอง
การเชื่อมต่อ Wi-Fi และส่ง HTTP ในสเก็ตช์นี้ยังหยุดรอ (สูงสุดราว 5 วินาทีเมื่อเครือข่ายมีปัญหา) ระหว่างนั้น fade จะหยุดชั่วคราว ถ้าอยากให้ไม่หยุดเลย ต้องเขียนส่วนเครือข่ายเป็น state machine ซึ่งเป็นโจทย์ท้าทายด้านล่าง
8. วัดผลก่อนและหลัง
เติมตารางนี้ขณะทดลองหมุน potentiometer ช้า ๆ ผ่านจุดเกณฑ์ (ดูจำนวนครั้งที่ lamp สลับระหว่าง 0 กับ 1 ใน 1 นาทีจาก Serial Monitor)
| เงื่อนไข | จำนวนครั้งที่ไฟสลับใน 1 นาที |
|---|---|
| ขั้นที่ 2 เดิม (เกณฑ์เดียว) | |
| ขั้นที่ 2 + hysteresis 30%/40% (โค้ดที่คุณแก้เอง) | |
| ขั้นที่ 4 (smoothing + calibration + hysteresis) |
โจทย์ท้าทาย
- ปรับ
WINDOWและช่วง hysteresis ให้ไฟไม่กะพริบแต่ยังตอบสนองไม่เกิน 5 วินาที เมื่อห้องเปลี่ยนจากสว่างเป็นมืดจริง - ปุ่มกดสลับโหมด: ต่อปุ่มกดที่ขา D2 (
INPUT_PULLUP) ให้กดแล้วสลับโหมด offline ระหว่าง “ตามแสง” กับ “เปิดตลอด” (เซนเซอร์แบบ digital) อย่าลืมจัดการปัญหาปุ่มเด้ง (debounce) ด้วยmillis() - ไม่หยุดรอ: เขียน
requestDecision()ใหม่ให้ทำงานเป็นขั้น ๆ ในloop()ด้วยmillis()โดยไม่ใช้delay()หรือclient.readStringUntil()ที่รอเป็นวินาที - เพิ่มข้อมูลที่ส่ง: ให้บอร์ดส่งค่า RSSI ของ Wi-Fi (
WiFi.RSSI()) ไปด้วย ฝั่ง gateway ต้องแก้อะไรบ้าง และทำไมต้องเปลี่ยนschema_version(ถามผู้สอน) - หรี่ตามความสว่าง: ให้ความสว่างของหลอดสวนทางกับความสว่างห้อง (ห้องมืดมาก ไฟสว่างมาก) ตัดสินใจที่บอร์ดหรือ gateway ดีกว่ากัน เพราะอะไร