Класи C++ в Arduino: Led і Button без хаосу в loop()
Коли кнопок і світлодіодів стає кілька, глобальні змінні перетворюються на кашу. Пишемо два маленькі класи з конструкторами й методами, складаємо об'єкти в масиви — і loop() вміщується в п'ять рядків.
Про проєкт
У статті про кнопку одна кнопка потребувала трьох глобальних змінних: попередній стан, час зміни і стан світлодіода. Дві кнопки — вже шість змінних і дубльований код, п'ять — хаос. Правильна відповідь — класи: кожен об'єкт зберігає власний стан, а код пишеться один раз.
Що знадобиться
- ESP32 DevKit
- 2 кнопки, 2 світлодіоди, 2 резистори 220 Ом
Схема
| Деталь | GPIO |
|---|---|
| Кнопка 1 (червона) | GPIO4 ↔ GND |
| Кнопка 2 (зелена) | GPIO0 ↔ GND |
| Світлодіод 1 | GPIO23 → 220 Ом → LED → GND |
| Світлодіод 2 | GPIO22 → 220 Ом → LED → GND |
GPIO0 — це кнопка BOOT на платі, тож другу кнопку можна навіть не ставити. Тільки не тримайте її під час увімкнення, інакше ESP32 перейде в режим прошивки.
Анатомія класу
class Led {
public: ← те, що доступно ззовні: інтерфейс
Led(int pin); ← конструктор: викликається при створенні об'єкта
void toggle(); ← методи: що об'єкт уміє
private: ← внутрішній стан, недоступний ззовні
int pin;
bool state;
};
- Список ініціалізації
: pin(pin)заповнює поля ще до виконання тіла конструктора. constпісля методу (isOn() const) обіцяє, що метод нічого не змінює в об'єкті.- Значення за замовчуванням (
debounceMs = 30) дозволяють писатиButton(4)абоButton(4, 50).
Важлива деталь для Arduino: глобальні об'єкти створюються до setup(), коли периферія ще не готова. Тому pinMode() не викликають у конструкторі, а виносять в окремий метод begin(). Так само влаштовані Serial.begin() і Wire.begin().
Код
// ООП в Arduino: маленькі класи тримають loop() охайним
class Led {
public:
Led(int pin) : pin(pin) {}
void begin() { pinMode(pin, OUTPUT); off(); }
void on() { set(true); }
void off() { set(false); }
void toggle() { set(!state); }
bool isOn() const { return state; }
private:
int pin;
bool state = false;
void set(bool s) { state = s; digitalWrite(pin, s ? HIGH : LOW); }
};
class Button {
public:
Button(int pin, unsigned long debounceMs = 30) : pin(pin), debounce(debounceMs) {}
void begin() { pinMode(pin, INPUT_PULLUP); }
// true один раз на кожне натискання
bool pressed() {
int now = digitalRead(pin);
if (now != last && millis() - changedAt > debounce) {
changedAt = millis();
last = now;
return now == LOW;
}
return false;
}
private:
int pin;
unsigned long debounce;
int last = HIGH;
unsigned long changedAt = 0;
};
Led leds[] = { Led(23), Led(22) };
Button buttons[] = { Button(4), Button(0) };
void setup() {
Serial.begin(115200);
for (Led &l : leds) l.begin();
for (Button &b : buttons) b.begin();
}
void loop() {
for (int i = 0; i < 2; i++) {
if (buttons[i].pressed()) {
leds[i].toggle();
Serial.printf("Кнопка %d → світлодіод %s\n", i + 1, leds[i].isOn() ? "УВІМК" : "ВИМК");
}
}
}
Чому & у циклі
for (Led &l : leds) проходить масив за посиланням. Без & кожна ітерація отримувала б копію об'єкта, і begin() налаштовував би копію, а не справжній світлодіод. Для класів, які зберігають стан, це класична помилка.
Що далі
- Додайте в
Ledметодblink(periodMs), що працює черезmillis()безdelay(), з викликомupdate()уloop(). - Винесіть класи в окремі файли
Led.h/Led.cpp: це перший крок до власної бібліотеки, як описано в документації Arduino за посиланням нижче.