ภาพปกบทความขนาด 1024x1024 แสดงหัวข้อ “delay() vs millis เลือกใช้อะไรดีใน ESP32 และ Arduino?” พร้อม Flowchart สองฝั่ง ฝั่งซ้าย delay() เปิด/ปิด LED พร้อมรอคอย ส่วนฝั่งขวา millis() ตรวจสอบเวลาแล้วทำงานอื่นได้พร้อมกัน

delay() vs millis(): เลือกใช้อะไรดีใน ESP32 และ Arduino?

บทนำ

เวลาเราเริ่มต้นเขียนโค้ด ESP32 หรือ Arduino มักจะเจอคำสั่งยอดนิยมอย่าง delay() แต่พอทำโครงงานซับซ้อนขึ้น จะได้ยินคนแนะนำให้ใช้ millis() แทน หลายคนอาจงงว่าแตกต่างกันยังไง และควรเลือกใช้อะไรดีกว่า?

ทำความรู้จัก delay()

delay() คือฟังก์ชันที่สั่งให้บอร์ด หยุดทำงานทั้งหมด ตามเวลาที่เรากำหนด (หน่วยเป็นมิลลิวินาที) เช่น

int ledPin = 2;

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

void loop() {
  digitalWrite(ledPin, HIGH);  // เปิดไฟ
  Serial.println("ON");
  delay(1000);  // รอ 1 วินาที

  digitalWrite(ledPin, LOW);  // ปิดไฟ
  Serial.println("OFF");
  delay(1000);  // รอ 1 วินาที

  // โค้ดอื่น ๆ ยังทำงานได้ เช่น อ่าน sensor หรือเช็กปุ่ม
  int sensorValue = analogRead(34);
  Serial.println(sensorValue);
}

ESP32 Arduino IDE Serial Monitor ตัวอย่างการทำงานด้วย millis เปรียบเทียบกับ delay แสดงค่าจากเซนเซอร์ 1602, 1597, 1598 พร้อมข้อความ ON และ OFF สลับกันทุกวินาที

ข้อดี: ใช้ง่าย เหมาะกับงานเล็ก ๆ เช่นไฟกระพริบ
ข้อเสีย: ระหว่าง delay() บอร์ดไม่ทำอะไรเลย → ขาดความต่อเนื่อง

ทำความรู้จัก millis()

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

ตัวอย่าง Blink แบบไม่ใช้ delay():

int ledPin = 2;
unsigned long previousMillis = 0;
const long interval = 1000; // 1 วินาที
bool ledState = LOW;

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

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

  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;   // อัปเดตเวลา
    ledState = !ledState;             // สลับสถานะไฟ
    digitalWrite(ledPin, ledState);

    if (ledState) {
      Serial.println("ON");
    } else {
      Serial.println("OFF");
    }
  }

  // โค้ดอื่น ๆ ยังทำงานได้ เช่น อ่าน sensor หรือเช็กปุ่ม
  int sensorValue = analogRead(34);
  Serial.println(sensorValue);
}

ข้อดี: ทำงานหลายอย่างพร้อมกันได้ เช่น อ่านเซนเซอร์, ควบคุมรีเลย์, คุยกับแอปมือถือ
ข้อเสีย: โค้ดดูซับซ้อนขึ้นเล็กน้อยสำหรับมือใหม่

ESP32 Arduino IDE Serial Monitor แสดงผลทดลองเปรียบเทียบ delay กับ millis ขณะยังไม่เชื่อมต่อบอร์ด ขึ้นข้อความ Not connected และค่าที่อ่านจากเซนเซอร์ 1620, 1618 พร้อมสถานะ OFF

ตารางเปรียบเทียบ delay() vs millis()

ฟังก์ชันข้อดีข้อเสียเหมาะกับงาน
delay()ใช้ง่าย, อ่านโค้ดสั้นบล็อกการทำงานทั้งหมดงานเล็ก ๆ, Demo, Blink LED
millis()ทำงานหลายอย่างพร้อมกันได้โค้ดยาวขึ้น, ต้องคิด logicงานจริง, Smart Farm, IoT
flowchart 2 แบบ (delay vs แผนภาพ Flowchart เปรียบเทียบการทำงาน delay() และ millis() ใน ESP32/Arduino ฝั่งซ้าย delay() เป็นเส้นตรง: เปิด LED → รอ → ปิด LED → รอ ส่วนฝั่งขวา millis() เป็นการเช็คเวลา ถ้าครบ interval จะสลับไฟ LED และยังสามารถทำงานอื่นไปพร้อมกันได้

แล้วควรใช้แบบไหนดี?

  • ถ้าเป็น งานทดลองเล็ก ๆ เช่น ไฟกระพริบ, เซนเซอร์ 1 ตัว → ใช้ delay() ไปเลย
  • ถ้าเป็น โครงงานจริง เช่น Smart Farm ที่ต้องอ่านค่าเซนเซอร์ + ควบคุมปั๊ม + ส่งข้อมูลไปแอป → ใช้ millis() จะดีกว่า

👉 ในระบบ PoPo Smart Farm ของ DevaDIY เราใช้ millis() ร่วมกับ DevaTimer เพื่อจัดการเวลาอย่างเป็นระบบ ทำให้ ESP32 อ่านเซนเซอร์ + ควบคุมรีเลย์ + เชื่อมต่อแอป ได้พร้อมกันแบบไม่สะดุด

ทำไม millis() สำคัญเมื่อใช้งาน WiFi, MQTT และเซนเซอร์หลายตัว

หลายคนอาจสงสัยว่า delay() แค่หยุดรอ ทำไมถึงทำให้ WiFi หลุดหรือ MQTT ค้าง?
เหตุผลคือ ESP32 มี งานเบื้องหลัง (background task) ที่ต้องทำงานตลอดเวลา เช่น การเชื่อมต่อ WiFi และการประมวลผลเครือข่าย
ถ้าเราใช้ delay() → งานเหล่านี้หยุดทันที → การเชื่อมต่อจึงขาดหาย

ปัญหาที่เกิดจากการใช้ delay()

  • WiFi หลุด → เพราะ delay() บล็อก network stack ภายใน
  • MQTT หลุดการเชื่อมต่อ → client.loop() ไม่ถูกเรียกทันเวลา ทำให้ broker ตัดการเชื่อมต่อ
  • อ่านหลายเซนเซอร์ไม่ได้ → เช่น DHT22, BH1750, Soil Moisture เมื่อใช้ delay จะอ่านค่าได้ทีละตัวและไม่ทันเวลา

ตัวอย่างโค้ดที่ผิดพลาด (ใช้ delay)

void loop() {
  // อ่านค่า DHT22
  readDHT();
  delay(2000);   // ระหว่างนี้ WiFi task ไม่ทำงาน → หลุดได้

  // ส่ง MQTT
  client.publish("sensor/data", "25.4");
  delay(5000);   // MQTT loop ไม่ทำงาน → disconnect
}

ตัวอย่างโค้ดที่ถูกต้อง (ใช้ millis)

unsigned long prevDHT = 0;
unsigned long prevMQTT = 0;

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

  if (now - prevDHT >= 2000) {
    prevDHT = now;
    readDHT();  // อ่านค่า DHT ทุก 2 วิ
  }

  if (now - prevMQTT >= 5000) {
    prevMQTT = now;
    client.publish("sensor/data", "25.4"); // ส่ง MQTT ทุก 5 วิ
  }

  client.loop(); // WiFi + MQTT ทำงานได้ตลอดเวลา
}

millis() จะ overflow หลัง ~50 วัน

millis() เป็นตัวเลขแบบ unsigned long (32 บิต) นับได้สูงสุด 4,294,967,295 ms
คิดเป็นเวลาประมาณ 49.7 วัน หลังจากนั้นค่าจะกลับไปเริ่มที่ 0 → เราเรียกว่าการ overflow

ผลกระทบจากการ overflow

ถ้าเราเขียนโค้ดแบบเปรียบเทียบตรง ๆ เช่น

if (millis() > prevMillis + 1000) { ... }

เมื่อ millis() รีเซ็ตกลับเป็นศูนย์ เงื่อนไขนี้จะให้ผลผิดทันที

วิธีเขียนโค้ดที่ถูกต้อง

ควรใช้การลบ (subtraction) แทน:

if (millis() - prevMillis >= 1000) {
  prevMillis = millis();
  // ทำงานตามกำหนด
}

การเขียนแบบนี้ยังคงทำงานถูกต้องแม้ millis() จะ overflow แล้วก็ตาม

ทำไมเรื่องนี้ถึงสำคัญ

ถ้าเป็นโปรเจกต์เล็ก ๆ เปิดไม่นาน อาจไม่เจอปัญหา

แต่สำหรับ Smart Farm, IoT Gateway หรือเครื่องจักรอุตสาหกรรม ที่ต้องทำงานต่อเนื่องเป็นเดือน → ถ้าไม่รองรับ overflow อาจเกิดบั๊กที่แก้ยากในระยะยาว

สรุป 

  • delay() → หยุดการทำงานทั้งบอร์ด เหมาะกับงานเล็ก ๆ ที่ไม่ต้องทำอะไรระหว่างรอ เช่น กระพริบไฟ LED หรือโค้ดตัวอย่างพื้นฐาน

  • millis() → ใช้จับเวลาโดยไม่บล็อกบอร์ด เหมาะกับงานจริงที่ต้องทำหลายอย่างพร้อมกัน เช่น อ่านเซนเซอร์หลายตัว, รักษาการเชื่อมต่อ WiFi/MQTT, ควบคุมอุปกรณ์ในระบบ Smart Farm หรือ IoT

  • มือใหม่สามารถเริ่มเรียนรู้จาก delay() ได้ แต่ควรต่อยอดไปใช้ millis() และเขียนโค้ดแบบ ป้องกัน overflow (~50 วัน) เพื่อให้โปรเจกต์ซับซ้อนทำงานได้ต่อเนื่องในระยะยาว
Shopping Cart
Scroll to Top