🔘 EP1.1: Button Class & OOP Style

การห่อหุ้มการทำงานของ “ปุ่มกด + Debounce” ให้อยู่ใน Class เพื่อให้โค้ดอ่านง่าย นำกลับใช้ซ้ำได้ และรองรับหลายปุ่มได้สบาย


🎯 จุดประสงค์การเรียนรู้

  • เข้าใจแนวคิด Class / Object ใน Arduino C++
  • สร้างคลาสสำหรับ ปุ่มกด (Button) ที่ดูแลเรื่อง:
    • กำหนดขา (pin)
    • ใช้ INPUT_PULLUP อัตโนมัติ
    • จัดการ Debounce
    • ตรวจว่า “เพิ่งถูกกดหนึ่งครั้ง”
  • ใช้งานปุ่มหลายปุ่มได้ด้วยโค้ดที่สะอาดและสั้นลง
  • เห็นความแตกต่างระหว่างโค้ด “ธรรมดา” vs “แบบ OOP”

🧠 1) ทบทวนปัญหาจาก EP1 (แบบไม่ใช้ Class)

โค้ดแบบเดิม (ปุ่มเดียว, Debounce แบบง่าย):

C++
const int button = 4;
const int led = 2;
unsigned long lastPress = 0;
bool ledState = false;

void setup() {
  pinMode(button, INPUT_PULLUP);
  pinMode(led, OUTPUT);
}

void loop() {
  int reading = digitalRead(button);

  if (reading == LOW && millis() - lastPress > 300) {
    lastPress = millis();
    ledState = !ledState;
    digitalWrite(led, ledState);
  }
}

❗ ถ้ามี 3 ปุ่ม → เราต้องมี button1, button2, button3, lastPress1, lastPress2, ... โค้ดจะยาว เละ และดูแลยาก 👉 ตรงนี้แหละครับที่ OOP / Class เข้ามาช่วย


🧩 2) แนวคิด Class แบบง่าย ๆ

Class = แบบพิมพ์เขียว (Template) ของ “วัตถุ” (Object) ในที่นี้เราจะสร้าง:

class Button = ตัวแทนของ “ปุ่มหนึ่งตัว”

แต่ละปุ่มจะมี:

  • หมายเลขขา → pin
  • เวลาที่กดล่าสุด → lastPress
  • สถานะปัจจุบัน → pressed
  • เวลากันเด้ง → debounceMs

และมี “ความสามารถ” เช่น:

  • begin() → ตั้งค่า pinMode
  • update() → อ่านค่า & เช็ก Debounce
  • wasPressed() → ตอบว่า “มีการกดครั้งใหม่หรือไม่”

💻 3) ตัวอย่าง Class: Button

C++
class Button {
  private:
    int pin;
    bool usePullup;
    unsigned long lastChangeTime;
    unsigned long debounceMs;
    bool lastStableState;
    bool currentState;
    bool pressedEvent; // true เมื่อตรวจพบการกดครั้งหนึ่ง (ขอบ LOW)

  public:
    // Constructor: ระบุขา และว่าจะใช้ PULLUP หรือไม่
    Button(int pinNumber, bool enablePullup = true, unsigned long debounce = 50) {
      pin = pinNumber;
      usePullup = enablePullup;
      debounceMs = debounce;
      lastChangeTime = 0;
      lastStableState = HIGH;
      currentState = HIGH;
      pressedEvent = false;
    }

    void begin() {
      if (usePullup) {
        pinMode(pin, INPUT_PULLUP);
        lastStableState = HIGH;
      } else {
        pinMode(pin, INPUT);
        lastStableState = LOW;
      }
      currentState = lastStableState;
    }

    void update() {
      int reading = digitalRead(pin);
      unsigned long now = millis();
      pressedEvent = false;

      // ถ้ามีการเปลี่ยนสถานะแบบฉับพลัน → รอ debounce
      if (reading != currentState) {
        currentState = reading;
        lastChangeTime = now;
      }

      // เมื่อผ่านมาเกินเวลา debounce → ยืนยันสถานะใหม่
      if ((now - lastChangeTime) > debounceMs) {
        if (lastStableState != currentState) {
          // เกิดการเปลี่ยนสถานะที่ "นิ่งแล้ว"
          // ตรวจขอบ: ถ้าใช้ PULLUP, การกด = จาก HIGH → LOW
          if (usePullup) {
            if (lastStableState == HIGH && currentState == LOW) {
              pressedEvent = true; // เพิ่งกดหนึ่งครั้ง
            }
          } else {
            // ถ้าเป็น pull-down, การกด = LOW → HIGH
            if (lastStableState == LOW && currentState == HIGH) {
              pressedEvent = true;
            }
          }
          lastStableState = currentState;
        }
      }
    }

    // ปุ่มตอนนี้อยู่ในสถานะ "กด" หรือไม่ (ค้างอยู่)
    bool isPressed() {
      if (usePullup) {
        return (lastStableState == LOW);
      } else {
        return (lastStableState == HIGH);
      }
    }

    // ปุ่ม "เพิ่งถูกกดหนึ่งครั้ง" หรือไม่ (ใช้สำหรับ toggle)
    bool wasPressed() {
      return pressedEvent;
    }
};

วาง class Button { … } ไว้ด้านบนสุดของไฟล์ .ino ได้เลย


💻 4) ใช้ Class Button ในโปรแกรมจริง (ปุ่มควบคุม LED)

ตอนนี้โค้ดใน setup() และ loop() จะอ่านง่ายขึ้นมาก 👇

C++
// ประกาศวัตถุ (Object) จากคลาส Button
Button btn(4, true, 80);   // ใช้ขา 4, INPUT_PULLUP, debounce 80 ms

const int led = 2;
bool ledState = false;

void setup() {
  Serial.begin(115200);
  btn.begin();             // ตั้งค่า pinMode ของปุ่ม
  pinMode(led, OUTPUT);
}

void loop() {
  btn.update();            // อัปเดตสถานะปุ่ม (ต้องเรียกทุก loop)

  if (btn.wasPressed()) {  // ถ้ามีการกดครั้งใหม่
    ledState = !ledState;
    digitalWrite(led, ledState);
    Serial.println(ledState ? "LED ON" : "LED OFF");
  }
}

จุดสังเกต:

  • โค้ดใน loop() สั้นและชัด: “อัปเดตปุ่ม → ถ้าเพิ่งกด → สลับ LED”
  • เรื่อง Debounce, การตรวจขอบสัญญาณ, millis() → ถูกซ่อนไว้ใน Class แล้ว

🧩 5) ข้อดีของการใช้ Class ใน Lab นี้

ก่อนใช้ Class

  • มีตัวแปรกระจัดกระจาย: buttonPin, lastPress, reading, state...
  • ถ้ามี 3 ปุ่ม → โค้ดยาว 3 เท่า
  • เด็กจะเริ่มสับสนระหว่างตัวแปรแต่ละชุด

หลังใช้ Class Button

  • เขียน แนวคิด ครั้งเดียว → ใช้ได้กับหลายปุ่ม
  • ถ้าต้องการ 3 ปุ่ม:
C++
Button btn1(4, true, 80);
Button btn2(5, true, 80);
Button btn3(18, true, 80);

void setup() {
  btn1.begin();
  btn2.begin();
  btn3.begin();
}

void loop() {
  btn1.update();
  btn2.update();
  btn3.update();

  if (btn1.wasPressed()) { /* ทำอย่างหนึ่ง */ }
  if (btn2.wasPressed()) { /* ทำอีกอย่าง */ }
  if (btn3.wasPressed()) { /* ทำอย่างอื่น */ }
}

โค้ดจะอ่านง่ายขึ้นอย่างชัดเจน และจะเริ่มเห็น “ความหมายของ OOP” แบบจับต้องได้ครับ ✅


💡 6) ตัวอย่างต่อยอด: 3 ปุ่มควบคุม 3 LED

C++
Button btn1(4, true, 50);
Button btn2(5, true, 50);
Button btn3(18, true, 50);

const int led1 = 2;
const int led2 = 16;
const int led3 = 17;

bool led1State = false;
bool led2State = false;
bool led3State = false;

void setup() {
  btn1.begin();
  btn2.begin();
  btn3.begin();

  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  pinMode(led3, OUTPUT);
}

void loop() {
  btn1.update();
  btn2.update();
  btn3.update();

  if (btn1.wasPressed()) {
    led1State = !led1State;
    digitalWrite(led1, led1State);
  }

  if (btn2.wasPressed()) {
    led2State = !led2State;
    digitalWrite(led2, led2State);
  }

  if (btn3.wasPressed()) {
    led3State = !led3State;
    digitalWrite(led3, led3State);
  }
}

จะเห็นว่า:

  • โค้ดไม่ “รก” แม้มีหลายปุ่ม
  • ลอจิกชัด: แต่ละปุ่ม → ควบคุมแต่ละ LED

🧪 7) แบบฝึกหัด EP1.1 (แนว OOP)

  1. แก้ไขคลาส Button ให้มีเมธอดเพิ่ม:
    • bool isHeld() → true ถ้ากดค้างเกิน X ms
  2. สร้างระบบ:
    • กดสั้น → Toggle LED
    • กดค้าง 2 วินาที → ปิดไฟทุกดวง
  3. ทำ “เมนูควบคุม” ด้วย 2 ปุ่ม:
    • ปุ่ม A = เลื่อนตำแหน่งเลือก (LED ตัวที่กำลังเลือก)
    • ปุ่ม B = เปิด/ปิด LED ตัวนั้น

🧾 8) สรุปแนวคิด EP1.1

หัวข้อสรุป
Class / OOPช่วยจัดโค้ดให้เป็นระเบียบ แยก “หน้าที่” เป็นกล่อง ๆ
Button Classซ่อนรายละเอียดการอ่านปุ่ม, debounce, ขอบสัญญาณ ไปในคลาสเดียว
การใช้งานประกาศ Button btn(pin);btn.begin(); → ใน loop: btn.update();, btn.wasPressed()
เหมาะกับโปรเจกต์ที่มีหลายปุ่ม หลาย I/O และต้องการโค้ดที่อ่านง่าย