ESP32 ใช้ delay() แล้วค้าง แก้ยังไง? วิธีทำให้ระบบไม่บล็อก

ESP32 ไม่ได้ค้างเพราะมันไม่เก่ง แต่หลายครั้งมันค้างเพราะโค้ดของคุณสั่งให้มัน “หยุดรอ” แบบไม่รู้ตัว โดยเฉพาะตอนใช้ delay() ยาว ๆ แล้วหวังให้ระบบอ่าน sensor, เช็กปุ่ม, คุมรีเลย์ และต่อ WiFi ไปพร้อมกัน

บทความนี้จะพาคุณเข้าใจแบบภาษาคนทำของว่า delay() ทำให้ระบบหน่วงยังไง, อาการที่เจอบ่อยคืออะไร, และจะแก้ยังไงด้วยแนวคิด non-blocking โดยใช้ millis() แบบที่เอาไปต่อยอดกับงานจริงได้ทันที

  • ถ้าคุณยังใหม่กับบอร์ดนี้ และยังไม่เห็นภาพว่า ESP32 ทำอะไรได้บ้าง, มี WiFi/Bluetooth ในตัวยังไง, หรือทำไมมันเหมาะกับงาน automation และ IoT มากกว่า Arduino บางรุ่น แนะนำให้อ่านบทความแม่ ESP32 คืออะไร? ใช้ทำอะไรได้บ้าง ก่อน แล้วค่อยกลับมาดูเรื่อง delay() จะเข้าใจภาพรวมง่ายขึ้น

delay() คืออะไร และทำไมมือใหม่ถึงชอบใช้

delay() ทำงานยังไงใน Arduino และ ESP32

delay(ms) คือการสั่งให้โปรแกรมหยุดรอเป็นเวลาตามที่กำหนด เช่น delay(1000) คือรอ 1 วินาที ระหว่างนั้นโค้ดส่วนถัดไปจะยังไม่ทำงาน

สำหรับมือใหม่ มันดูง่ายมาก เพราะเขียนตรง ๆ แล้วเข้าใจทันที เช่น เปิด LED → รอ 1 วินาที → ปิด LED → รอ 1 วินาที

ทำไมช่วงเริ่มต้นมันดูง่ายและสะดวก

เพราะในตัวอย่างทดลองเล็ก ๆ มันใช้งานได้จริง และเห็นผลเร็วมาก โดยเฉพาะงานพวก:

  • กระพริบ LED
  • เปิดรีเลย์ค้าง 2 วินาที
  • รอข้อความบน Serial Monitor
  • ทดลอง sensor แบบอ่านทีละช่วง

ตัวอย่างงานที่คนมักเขียนด้วย delay()

ช่วงเริ่มต้นหลายคนจะเขียนแบบนี้:
digitalWrite(ledPin, HIGH);
delay(1000);
digitalWrite(ledPin, LOW);
delay(1000);
ปัญหาคือพอโปรเจกต์โตขึ้น เช่น ต้องอ่าน DHT22, เช็กปุ่ม, ส่งข้อมูลขึ้นมือถือ, หรือควบคุมปั๊มน้ำพร้อมกัน โค้ดลักษณะนี้จะเริ่มทำให้ระบบ “หน่วง” ทันที

ทำไมใช้ delay() แล้ว ESP32 ถึงดูเหมือนค้าง

delay() ทำให้ loop() หยุดรอ

หัวใจของปัญหาคือ ระหว่างที่ delay() กำลังรออยู่ โค้ดใน loop() ก็เดินต่อไม่ได้ นั่นแปลว่า ESP32 ไม่ได้ไปเช็กงานอื่นตามที่คุณหวัง

งานอื่นเลยไม่ได้ทำต่อ

ถ้าในระบบมีหลายงานพร้อมกัน เช่น

  • อ่าน sensor ทุก 1 วินาที
  • เช็กปุ่มกดตลอดเวลา
  • สั่งรีเลย์ตามเงื่อนไข
  • คงการเชื่อมต่อ WiFi หรือ WebSocket

เมื่อมี delay() ยาว ๆ อยู่ งานพวกนี้จะเหมือนโดนแช่แข็งชั่วคราว

อาการที่เจอบ่อยในงานจริง

  • กดปุ่มแล้วติดช้า
  • รีเลย์สั่งงานช้า หรือดูเหมือนตอบสนองไม่ทัน
  • sensor อ่านค่าเป็นช่วง ๆ ไม่ลื่น
  • หน้าเว็บหรือแอปดูหน่วง
  • ระบบ IoT หลุดจังหวะ หรือ reconnect แปลก ๆ

ถ้าคุณเริ่มเห็นแล้วว่า delay() ทำให้ระบบหยุดรอและไปขวางงานอื่นยังไง แต่ยังไม่เห็นภาพรวมชัด ๆ ว่า delay() กับ millis() ต่างกันตรงไหน และแต่ละแบบเหมาะกับงานลักษณะไหน แนะนำให้อ่านบทความเสริม delay() vs millis(): เลือกใช้อะไรดีใน ESP32 และ Arduino? เพิ่ม จะช่วยให้คุณเข้าใจแนวคิด non-blocking ได้ชัดขึ้นก่อนนำไปใช้กับโปรเจกต์จริง

ภาษาคนทำงาน: delay() ไม่ได้ทำให้ชิปพัง แต่มันทำให้ “สมองของระบบมัวแต่นั่งรอ” จนงานอื่นไม่ได้เดิน

สัญญาณเตือนว่าโค้ดของคุณกำลังโดน block

อาการที่พบบ่อยเมื่อใช้ delay มากเกินไปบน ESP32

ปุ่มกดไม่ค่อยติด

คุณกดปุ่มแล้วต้องรอจังหวะ หรือกดบางครั้งติดบางครั้งไม่ติด อันนี้เป็นอาการคลาสสิกมาก เพราะระบบไม่ได้วนมาเช็กปุ่มตลอดเวลา

รีเลย์ตอบสนองช้า

เวลาคุมพัดลม ปั๊มน้ำ หรือไฟ ถ้ามี delay ยาวอยู่ รีเลย์จะไม่ตอบสนองทันที เพราะโค้ดยังไม่กลับมาถึงส่วนเช็กเงื่อนไข

WiFi หรือ WebSocket หน่วง

ในงาน IoT ถ้าคุณต้องสื่อสารกับแอป มือถือ หรือ dashboard การมี delay มากเกินไปจะทำให้ข้อมูลอัปเดตไม่ลื่น

sensor อ่านค่าเป็นช่วง ๆ

บางคนคิดว่า sensor เสีย แต่จริง ๆ แล้วเป็นเพราะโค้ดอ่านค่าห่างเกินไปจาก delay ที่แทรกอยู่หลายจุด

ตัวอย่างโค้ดที่ใช้ delay() แล้วเกิดปัญหา

ตัวอย่างกระพริบ LED แบบง่าย

const int ledPin = 2;

void setup() {
  pinMode(ledPin, OUTPUT);
}

void loop() {
  digitalWrite(ledPin, HIGH);
  delay(1000);
  digitalWrite(ledPin, LOW);
  delay(1000);
}
โค้ดนี้ไม่มีปัญหาถ้าคุณต้องการแค่กระพริบ LED อย่างเดียว

พอเพิ่ม sensor และ relay แล้วเริ่มพัง

const int relayPin = 23;
const int buttonPin = 18;

void setup() {
  pinMode(relayPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
  Serial.begin(115200);
}

void loop() {
  int buttonState = digitalRead(buttonPin);

  if (buttonState == LOW) {
    digitalWrite(relayPin, HIGH);
    delay(3000);
    digitalWrite(relayPin, LOW);
  }

  Serial.println("reading sensor...");
  delay(1000);
}

วิเคราะห์ว่าบล็อกตรงไหน

จุดที่ทำให้ระบบดูค้างคือ:

  • delay(3000) ตอนกดปุ่มแล้วรีเลย์ทำงาน
  • delay(1000) ตอนพิมพ์ข้อความอ่าน sensor

แปลว่าช่วงเวลารวมหลายวินาทีนี้ ระบบจะไม่ค่อยกลับมาเช็กปุ่มใหม่ หรือไปทำงานอื่นต่อได้ทันที

วิธีแก้ delay() แบบพื้นฐานด้วย millis()

non-blocking คืออะไร

non-blocking คือแนวคิดที่ทำให้ระบบ “ไม่หยุดรอ” แต่ใช้วิธีคอยเช็กว่าเวลาผ่านไปพอหรือยัง แล้วค่อยทำงาน

แทนที่จะพูดว่า “หยุดรอ 1 วินาที” เราจะเปลี่ยนเป็น “ถ้าผ่านมาแล้ว 1 วินาที ค่อยทำงาน”

millis() ช่วยจับเวลาโดยไม่หยุดระบบยังไง

millis() จะคืนค่าจำนวนมิลลิวินาทีตั้งแต่บอร์ดเริ่มทำงาน คุณสามารถเอาค่านี้มาเปรียบเทียบกับเวลาล่าสุดที่ทำงาน เพื่อกำหนดช่วงเวลาได้

เปลี่ยนวิธีคิดจาก “รอ” เป็น “เช็กเวลา”

นี่คือจุดเปลี่ยนสำคัญของคนทำ ESP32 จริงจัง

  • แบบเดิม: ทำงาน → รอ
  • แบบใหม่: วนเช็กตลอด → ถึงเวลาเมื่อไรค่อยทำ

เปรียบเทียบ delay() กับ millis() แบบเห็นภาพ

ซ้ายเป็น flow หยุดรอ, ขวาเป็น flow ทำหลายงานพร้อมกัน

แบบ delay()

const int LED_PIN = 2;

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_PIN, HIGH);
  Serial.println("LED ON");
  delay(1000);

  digitalWrite(LED_PIN, LOW);
  Serial.println("LED OFF");
  delay(1000);

  Serial.println("Read Sensor");
  delay(2000);

  Serial.println("Send Notify");
  delay(3000);
}
ข้อดีคือเขียนง่าย แต่ระหว่างรอระบบแทบไม่ได้ทำอะไรอย่างอื่น
รูปแสดงข้อความ serial monitor การทำงานรอคิวเมื่อใช้ delay

แบบ millis()

const int LED_PIN = 2;
bool ledState = false;

unsigned long previousLedMillis = 0;
unsigned long previousSensorMillis = 0;
unsigned long previousNotifyMillis = 0;

const unsigned long ledInterval = 500;
const unsigned long sensorInterval = 2000;
const unsigned long notifyInterval = 5000;

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  unsigned long now = millis();

  if (now - previousLedMillis >= ledInterval) {
    previousLedMillis = now;
    ledState = !ledState;
    digitalWrite(LED_PIN, ledState);
    Serial.println("LED Toggle");
  }

  if (now - previousSensorMillis >= sensorInterval) {
    previousSensorMillis = now;
    Serial.println("Read Sensor");
  }

  if (now - previousNotifyMillis >= notifyInterval) {
    previousNotifyMillis = now;
    Serial.println("Send Notify");
  }
}
รูปแสดงข้อความ serial monitor การทำงานไม่รอคิวเมื่อใช้ millis

ข้อแตกต่างในงานจริง

  • delay() เหมาะกับงานทดลองสั้น ๆ
  • millis() เหมาะกับงานที่มีหลายอย่างต้องทำพร้อมกัน

ถ้าคุณกำลังทำระบบจริง เช่น Smart Farm, เปิดปั๊มตามเงื่อนไข, ส่งข้อมูลขึ้น dashboard หรือคุมผ่านมือถือ แนว millis() จะปลอดภัยกว่าเยอะ

ทำหลายงานพร้อมกันบน ESP32 โดยไม่ใช้ delay()

เช็กปุ่มตลอดเวลา

ปุ่มไม่ควรถูกตรวจแค่ทุก 1-3 วินาที แต่ควรถูกเช็กแทบทุกลูป

อ่าน sensor เป็นช่วงเวลา

สมมุติคุณอยากอ่านอุณหภูมิทุก 2 วินาที ก็ใช้ตัวจับเวลาแยกสำหรับงานนี้

คุม relay โดยไม่กระทบ WiFi

เมื่อไม่ใช้ delay() การเปิดปิดรีเลย์จะไม่ไปขวางการทำงานของระบบสื่อสาร

หลายคนเริ่มเจอปัญหา delay() ตอนคุมรีเลย์ เพราะอยากให้เปิด 3 วินาที ปิด 5 วินาที แล้วก็เลยเขียนหน่วงตรง ๆ ลงไป พอระบบเริ่มซับซ้อนก็เกิดอาการหน่วงทันที ยิ่งถ้าคุณยังสับสนเรื่องรีเลย์แบบ Active LOW ด้วย ควรไปเคลียร์เรื่อง ESP32 Relay Active LOW คืออะไร ให้ชัดก่อน จะช่วยลดบั๊กได้เยอะมาก

ตัวอย่างงาน Smart Farm

unsigned long previousSensorMillis = 0;
unsigned long previousPumpMillis = 0;

const unsigned long sensorInterval = 2000;
const unsigned long pumpInterval = 5000;

void loop() {
  unsigned long now = millis();

  // อ่าน sensor ทุก 2 วินาที
  if (now - previousSensorMillis >= sensorInterval) {
    previousSensorMillis = now;
    Serial.println("read sensor");
  }

  // เช็กเงื่อนไขปั๊มทุก 5 วินาที
  if (now - previousPumpMillis >= pumpInterval) {
    previousPumpMillis = now;
    Serial.println("check pump logic");
  }

  // เช็กปุ่มตลอดเวลา
  if (digitalRead(18) == LOW) {
    Serial.println("button pressed");
  }

  // งาน WiFi / WebSocket / App communication ก็ยังวิ่งต่อได้
}

นี่แหละแนวคิดของระบบที่ “ลื่น” ขึ้นแบบรู้สึกได้จริง

งานแบบไหนที่ควรเลี่ยง delay()

งาน WiFi / IoT

  • Web Server
  • Blynk
  • Firebase
  • Telegram
  • WebSocket

งานพวกนี้ต้องอาศัยการวนเช็กและตอบสนองต่อเนื่อง ถ้าใส่ delay หนัก ๆ ระบบจะเสียจังหวะง่าย

ปัญหาของ delay() จะชัดขึ้นมากเมื่อคุณเริ่มทำงานด้าน WiFi เช่น เชื่อมต่อ router, ขอ IP, reconnect, scan network หรือคุยกับระบบผ่าน web server เพราะงานพวกนี้ต้องอาศัยการวนเช็กสถานะตลอด ถ้าคุณยังไม่คุ้นกับ flow ฝั่งเครือข่าย แนะนำให้อ่าน ESP32 WiFi Library Functions เพิ่ม จะเห็นเลยว่าทำไมโค้ดแบบ non-blocking ถึงสำคัญ

งานที่ต้องตอบสนองเร็ว

  • ปุ่มกด
  • Interrupt-triggered logic
  • การเช็กสถานะหลายอุปกรณ์
  • ระบบที่มี timeout และ retry

งานควบคุมรีเลย์และปั๊มน้ำ

ถ้าคุณคุมปั๊มน้ำ พัดลม หรือหมอกในระบบปลูกผัก การตอบสนองช้าบางครั้งไม่ใช่แค่ “น่ารำคาญ” แต่มีผลต่อการทำงานของระบบจริง

ถ้าจำเป็นต้องหน่วงเวลา ควรทำยังไงแทน

ใช้ millis()

นี่คือพื้นฐานที่ควรฝึกให้คล่องก่อน เพราะเป็นแกนของโค้ด non-blocking เกือบทุกแบบ

ใช้ state machine

ถ้างานของคุณมีหลายขั้น เช่น เปิดปั๊ม 5 วินาที → รอ 10 วินาที → เปิดวาล์ว → อ่าน sensor วิธีคิดแบบ state machine จะช่วยให้โค้ดชัดกว่าใช้ delay ซ้อนกัน

ใช้ timer library

ถ้าคุณเริ่มรู้สึกว่าเขียน millis() เองหลายชุดแล้วเริ่มรก การใช้ timer library จะช่วยให้โค้ดอ่านง่ายขึ้น โดยเฉพาะโปรเจกต์ที่มีหลายงานพร้อมกัน

ประสบการณ์หน้างาน: มือใหม่จำนวนมากไม่ได้พังเพราะ logic ยาก แต่พังเพราะโค้ดเต็มไปด้วย delay() ทีละ 500ms, 1000ms, 3000ms จนสุดท้ายระบบทั้งก้อนตอบสนองช้าแบบหาสาเหตุไม่เจอ

สรุป

delay() ไม่ใช่คำสั่งที่ผิด แต่มันเหมาะกับงานทดลองเล็ก ๆ มากกว่างานระบบจริง ถ้าคุณเริ่มอยากให้ ESP32 ทำหลายอย่างพร้อมกัน เช่น อ่าน sensor, คุม relay, เช็กปุ่ม, ต่อ WiFi, ส่งข้อมูลขึ้นมือถือ คุณควรเริ่มเปลี่ยนจากแนวคิดแบบ “หยุดรอ” ไปเป็น “เช็กเวลาแล้วค่อยทำ”

ถ้าคุณรู้สึกว่า ESP32 ของคุณค้าง ทั้งที่บอร์ดไม่ได้เสีย ให้กลับไปสำรวจโค้ดก่อนเลยว่าใส่ delay() ไว้กี่จุด หลายครั้งปัญหาไม่ได้อยู่ที่ฮาร์ดแวร์ แต่อยู่ที่ flow ของโปรแกรมล้วน ๆ

เริ่มจากฝึก millis() ให้คล่อง แล้วคุณจะเขียนโค้ดที่ลื่นขึ้น ขยายง่ายขึ้น และพร้อมต่อยอดไปงาน IoT จริงได้อีกเยอะ

FAQ: คำถามที่พบบ่อยเกี่ยวกับ delay() บน ESP32

ESP32 ใช้ delay() ได้ไหม

ได้ แต่เหมาะกับงานทดลองง่าย ๆ หรือโค้ดสั้น ๆ ที่ไม่มีหลายงานต้องทำพร้อมกัน

ถ้าใช้ในงานเล็กอาจไม่เป็นไร แต่ถ้าอยู่ในระบบที่ต้องตอบสนองเร็ว มันทำให้ระบบหน่วงชัดเจน

เพราะระหว่าง delay ระบบไม่ได้กลับมาเช็กปุ่มตลอดเวลา จึงพลาดจังหวะกดได้

ช่วงแรกอาจรู้สึกยากกว่า delay() แต่พอเข้าใจแล้วจะคุมระบบหลายงานได้ง่ายกว่าเยอะ

ควรเลี่ยง โดยเฉพาะถ้าต้องมีการรับส่งข้อมูลต่อเนื่อง เช่น WebSocket, Blynk หรือ dashboard

ควรใช้ millis() หรือ timer library แทน delay()

บางกรณีถ้าระบบรวม ๆ หน่วงหรือจัดการงานไม่ดี อาจมีผลทางอ้อมได้ แต่ส่วนใหญ่ปัญหาคือระบบตอบสนองช้า ไม่ใช่ delay ตัวเดียวล้วน ๆ

ได้ในงานทดลอง แต่ถ้าคิดจะขยายระบบในอนาคต แนะนำให้ฝึกเขียนแบบ non-blocking ตั้งแต่ต้น

ให้สังเกตอาการ เช่น ปุ่มช้า, รีเลย์หน่วง, sensor อ่านเป็นช่วง, การเชื่อมต่อ WiFi ไม่ลื่น หรือระบบทำงานทีละอย่าง

แนะนำให้ศึกษาต่อเรื่อง millis(), state machine, timer library, และโครงสร้าง loop ที่ไม่บล็อก เพราะทั้งหมดนี้เป็นฐานสำคัญของงาน ESP32 ที่ใช้งานจริง

อ่านต่อบทความที่เกี่ยวข้อง

ถ้าคุณอยากเข้าใจ ESP32 ให้ลึกขึ้นแบบค่อย ๆ ต่อจากพื้นฐานไปงานจริง แนะนำให้อ่านต่อจากหัวข้อเหล่านี้

Shopping Cart
Scroll to Top