บทนำ
เวลาเราเริ่มต้นเขียนโค้ด 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);
}

ข้อดี: ใช้ง่าย เหมาะกับงานเล็ก ๆ เช่นไฟกระพริบ
ข้อเสีย: ระหว่าง 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);
}
ข้อดี: ทำงานหลายอย่างพร้อมกันได้ เช่น อ่านเซนเซอร์, ควบคุมรีเลย์, คุยกับแอปมือถือ
ข้อเสีย: โค้ดดูซับซ้อนขึ้นเล็กน้อยสำหรับมือใหม่

ตารางเปรียบเทียบ delay() vs millis()
| ฟังก์ชัน | ข้อดี | ข้อเสีย | เหมาะกับงาน |
|---|---|---|---|
| delay() | ใช้ง่าย, อ่านโค้ดสั้น | บล็อกการทำงานทั้งหมด | งานเล็ก ๆ, Demo, Blink LED |
| millis() | ทำงานหลายอย่างพร้อมกันได้ | โค้ดยาวขึ้น, ต้องคิด logic | งานจริง, Smart Farm, IoT |

แล้วควรใช้แบบไหนดี?
- ถ้าเป็น งานทดลองเล็ก ๆ เช่น ไฟกระพริบ, เซนเซอร์ 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 ทำงานได้ตลอดเวลา
}
- อยากติดตั้ง Arduino IDE อย่างถูกต้อง? ไปที่ Arduino IDE Download & Install สำหรับคำแนะนำการดาวน์โหลดและติดตั้งบน Windows, macOS, Linux
- ก่อนจะเริ่มทำโปรเจกต์ ลองไปทำความรู้จัก ESP32 และฟีเจอร์เด่นที่คุณควรรู้ กันก่อน
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 วัน) เพื่อให้โปรเจกต์ซับซ้อนทำงานได้ต่อเนื่องในระยะยาว






