กันปุ่มสั่น (Debounce) 3 ปุ่มละ นี่คือโจทย์คลาสสิกเลยครับ พอเราทำ 1 ปุ่มได้แล้ว จะขยายเป็น 3, 5, หรือ 10 ปุ่มยังไงให้โค้ดไม่ “บวม” (Code Bloat)

ถ้าเราใช้วิธี Copy-Paste โค้ด 3 ชุด แล้วเปลี่ยนชื่อตัวแปรเป็น lastDebounceTime1, lastButtonState1, lastDebounceTime2, lastButtonState2… โค้ดจะยาวมากและแก้ไขลำบากทันที

ขอเสนอ 2 วิธีในการจัดการ 3 ปุ่ม (หรือกี่ปุ่มก็ได้) อย่างมีประสิทธิภาพ

💡 วิธีที่ 1: ใช้ Array (เก็บสถานะ) และ for Loop (วนทำงาน)

วิธีนี้คือการใช้ Array เก็บค่าของแต่ละปุ่ม แล้วใช้ for loop วนทำงานลอจิกเดิมซ้ำๆ

หลักการ:

  1. ประกาศ Array: สร้าง Array สำหรับเก็บ “ขา” (Pins) และ “สถานะ” (States) ของแต่ละปุ่ม
  2. วน Loop: ใน void loop() ให้ใช้ for loop วนตั้งแต่ 0 ถึง 2 (สำหรับ 3 ปุ่ม)
  3. ใช้ Logic เดิม: นำลอจิก Debounce และ Toggle ที่สมบูรณ์แล้ว มาใส่ใน for loop โดยอ้างอิงค่าใน Array ผ่าน [i]

🍼 ตัวอย่างโค้ด (Toggle Switch 3 ปุ่ม, คุม LED 3 ดวง)

C++
// --- 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):

  1. สร้าง Class DebouncedButton: สร้างพิมพ์เขียวที่รวม “คุณสมบัติ” (ขา Pin, สถานะ) และ “ความสามารถ” (ลอจิก Debounce, ลอจิก Toggle) ไว้ในที่เดียว
  2. สร้าง Object: สร้าง “ปุ่มจริงๆ” (Object) จากพิมพ์เขียวนั้น (เช่น button1, button2, button3)
  3. loop() ที่สะอาด: ใน void loop() เราแค่สั่ง button1.update(); button2.update(); โค้ดจะสะอาดและอ่านง่ายมาก

🍼 ตัวอย่างโค้ด (ใช้ Class)

C++
// --- 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)