กันปุ่มสั่น (Debounce) 3 ปุ่มละ นี่คือโจทย์คลาสสิกเลยครับ พอเราทำ 1 ปุ่มได้แล้ว จะขยายเป็น 3, 5, หรือ 10 ปุ่มยังไงให้โค้ดไม่ “บวม” (Code Bloat)
ถ้าเราใช้วิธี Copy-Paste โค้ด 3 ชุด แล้วเปลี่ยนชื่อตัวแปรเป็น lastDebounceTime1, lastButtonState1, lastDebounceTime2, lastButtonState2… โค้ดจะยาวมากและแก้ไขลำบากทันที
ขอเสนอ 2 วิธีในการจัดการ 3 ปุ่ม (หรือกี่ปุ่มก็ได้) อย่างมีประสิทธิภาพ
💡 วิธีที่ 1: ใช้ Array (เก็บสถานะ) และ for Loop (วนทำงาน)
วิธีนี้คือการใช้ Array เก็บค่าของแต่ละปุ่ม แล้วใช้ for loop วนทำงานลอจิกเดิมซ้ำๆ
หลักการ:
- ประกาศ Array: สร้าง Array สำหรับเก็บ “ขา” (Pins) และ “สถานะ” (States) ของแต่ละปุ่ม
- วน Loop: ใน
void loop()ให้ใช้forloop วนตั้งแต่ 0 ถึง 2 (สำหรับ 3 ปุ่ม) - ใช้ Logic เดิม: นำลอจิก Debounce และ Toggle ที่สมบูรณ์แล้ว มาใส่ใน
forloop โดยอ้างอิงค่าใน Array ผ่าน[i]
🍼 ตัวอย่างโค้ด (Toggle Switch 3 ปุ่ม, คุม LED 3 ดวง)
// --- 1. กำหนดค่าพื้นฐาน ---
const int NUM_BUTTONS = 3; // จำนวนปุ่ม
// --- 2. สร้าง Arrays สำหรับ Pins ---
const int buttonPins[NUM_BUTTONS] = {4, 5, 6}; // ขาปุ่มที่ 1, 2, 3
const int ledPins[NUM_BUTTONS] = {12, 13, 14}; // ขา LED ที่ 1, 2, 3
// --- 3. สร้าง Arrays สำหรับเก็บ "สถานะ" (ความจำ) ---
int ledState[NUM_BUTTONS] = {LOW, LOW, LOW};
int buttonState[NUM_BUTTONS] = {HIGH, HIGH, HIGH};
int lastButtonState[NUM_BUTTONS] = {HIGH, HIGH, HIGH};
// --- 4. สร้าง Arrays สำหรับ Debounce ---
unsigned long lastDebounceTime[NUM_BUTTONS] = {0, 0, 0};
long debounceDelay = 50;
void setup() {
Serial.begin(115200);
// ใช้ for loop ในการ setup pin ทั้งหมด
for (int i = 0; i < NUM_BUTTONS; i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
pinMode(ledPins[i], OUTPUT);
digitalWrite(ledPins[i], ledState[i]);
}
}
void loop() {
// --- 5. ใช้ for loop วนเช็คปุ่มทีละปุ่ม ---
for (int i = 0; i < NUM_BUTTONS; i++) {
// *** นี่คือลอจิก Debounce/Toggle เดิมเป๊ะๆ ***
// *** แค่เปลี่ยนจากตัวแปรเดี่ยว เป็น array[i] ***
int reading = digitalRead(buttonPins[i]);
// 1. ตรวจจับการสั่น (เทียบ ค่าดิบ[i] กับ ค่าดิบที่จำไว้[i])
if (reading != lastButtonState[i]) {
lastDebounceTime[i] = millis();
}
// 2. ตรวจสอบความเสถียร
if ((millis() - lastDebounceTime[i]) > debounceDelay) {
// 3. ตรวจจับการเปลี่ยนแปลงสถานะ (เทียบ ค่าที่นิ่งแล้ว[i] กับ ค่าที่นิ่งที่จำไว้[i])
if (reading != buttonState[i]) {
buttonState[i] = reading; // อัปเดต "ความจำ" สถานะที่นิ่งแล้ว
// 4. ถ้าสถานะใหม่คือ "ถูกกด" (Falling Edge)
if (buttonState[i] == LOW) {
Serial.print("Button ");
Serial.print(i);
Serial.println(" Pressed!");
// Toggle LED!
ledState[i] = !ledState[i];
digitalWrite(ledPins[i], ledState[i]);
}
}
}
// 5. อัปเดต "ความจำ" ค่าดิบ
lastButtonState[i] = reading;
}
// (loop() นี้ยังคงเป็น Non-Blocking สามารถทำงานอื่นต่อได้)
}
จุดเด่น: เข้าใจง่าย, ขยายขนาดง่าย (แค่เพิ่ม NUM_BUTTONS และเติมขาใน Array)
จุดด้อย: ตัวแปรสถานะต่างๆ ยังกองรวมกันอยู่ที่ Global Scope
🚀 วิธีที่ 2: ใช้ Class (OOP) – “วิธีแบบมืออาชีพ”
วิธีนี้คือวิธีที่ “ถูกต้อง” ที่สุดในการเขียนโปรแกรมสมัยใหม่ คือการสร้าง “พิมพ์เขียว” (Class) ของปุ่มขึ้นมา
หลักการ (Object-Oriented Programming):
- สร้าง Class
DebouncedButton: สร้างพิมพ์เขียวที่รวม “คุณสมบัติ” (ขา Pin, สถานะ) และ “ความสามารถ” (ลอจิก Debounce, ลอจิก Toggle) ไว้ในที่เดียว - สร้าง Object: สร้าง “ปุ่มจริงๆ” (Object) จากพิมพ์เขียวนั้น (เช่น
button1,button2,button3) loop()ที่สะอาด: ในvoid loop()เราแค่สั่งbutton1.update();button2.update();โค้ดจะสะอาดและอ่านง่ายมาก
🍼 ตัวอย่างโค้ด (ใช้ Class)
// --- 1. สร้างพิมพ์เขียว (Class) ของปุ่ม ---
class DebouncedButton {
private:
// "คุณสมบัติ" ที่ปุ่มแต่ละตัวต้องจำ (เป็นของส่วนตัว)
int pin;
int ledPin;
int ledState;
int buttonState;
int lastButtonState;
unsigned long lastDebounceTime;
long debounceDelay;
public:
// "Constructor" (ฟังก์ชันที่ทำงานตอนสร้าง Object)
DebouncedButton(int bPin, int lPin, long delay) {
pin = bPin;
ledPin = lPin;
debounceDelay = delay;
ledState = LOW;
buttonState = HIGH;
lastButtonState = HIGH;
lastDebounceTime = 0;
}
// "ฟังก์ชัน" ที่ทำงานตอนเริ่ม
void begin() {
pinMode(pin, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, ledState);
}
// "ความสามารถ" (Method) ที่เราจะเรียกใน loop()
void update() {
int reading = digitalRead(pin);
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != buttonState) {
buttonState = reading;
if (buttonState == LOW) {
// Toggle Logic อยู่ในนี้เลย
ledState = !ledState;
digitalWrite(ledPin, ledState);
Serial.print("Button on pin ");
Serial.print(pin);
Serial.println(" Toggled!");
}
}
}
lastButtonState = reading;
}
};
// --- 2. สร้าง "ปุ่มจริงๆ" (Objects) จาก Class ---
DebouncedButton button1(4, 12, 50); // (buttonPin, ledPin, debounceMs)
DebouncedButton button2(5, 13, 50);
DebouncedButton button3(6, 14, 50);
void setup() {
Serial.begin(115200);
// สั่งให้ปุ่มแต่ละตัวเริ่มทำงาน
button1.begin();
button2.begin();
button3.begin();
}
void loop() {
// --- 3. loop() ที่สะอาดมากๆ ---
// แค่สั่งให้ปุ่มแต่ละตัว "อัปเดตตัวเอง"
button1.update();
button2.update();
button3.update();
// (สามารถทำงานอย่างอื่นต่อได้เลย)
}
จุดเด่น:
- Encapsulation: โค้ดเป็นสัดส่วน “ปุ่ม” จัดการเรื่องของ “ปุ่ม” เอง
- Clean
loop():loop()หลักสั้นมาก อ่านง่าย - Reusable: ครูสามารถเซฟ Class นี้ไว้เป็นไฟล์
.hและ.cppเพื่อทำเป็น “Library” ส่วนตัว เรียกใช้ในโปรเจกต์ไหนก็ได้เลยครับ
สรุปเปรียบเทียบ
| วิธีการ | จุดเด่น | จุดด้อย |
วิธีที่ 1: Array + for Loop | เข้าใจง่าย, ต่อยอดจากโค้ดเดิมได้ทันที | ตัวแปรยังกระจัดกระจาย (Global) |
| วิธีที่ 2: Class (OOP) | โค้ดสะอาด, เป็นสัดส่วน, นำกลับมาใช้ง่าย (Reusable) | ต้องมีความเข้าใจเรื่อง Class (OOP) |
