Эффективное управление 10 кнопками с помощью одного прерывания на Arduino UNO
Компоненты и материалы
- Arduino UNO (или любой другой микроконтроллер) — 1 шт.
- Беспаяная макетная плата — 1 шт.
- Перемычки — 21 шт.
- Тактильные кнопки (любые) — 10 шт.
Необходимые инструменты и сервисы
- Arduino IDE
О проекте
Введение
Прерывания — удобный инструмент. Они не только упрощают код, но и позволяют реализовать точное таймирование или пробуждение Arduino из спящего режима.
Представьте, что у вас есть пользовательский интерфейс, например пульт управления, работающий от батареи. Желательно переводить Arduino (или отдельный чип ATmega) в режим глубокого сна для экономии энергии. В этом режиме пробудить микроконтроллер можно только внешним прерыванием. Чип ATmega328P, используемый в Arduino Uno, имеет всего два внешних пина прерываний (INT0 и INT1 на пинах 2 и 3). Если в интерфейсе больше двух кнопок — возникает проблема.
Стандартное решение — подключить все кнопки обычным способом, а также соединить их с пином прерывания через диоды. Однако это существенно усложняет схему.

Помимо внешних прерываний, ATmega328P поддерживает прерывания по изменению состояния пина (Pin Change Interrupts). Существуют библиотеки для работы с ними, и они хорошо решают эту задачу.
Однако во время программистского соревнования я нашел способ использовать стандартные внешние прерывания без дополнительных электронных компонентов.
Схема подключения



Используется 10 кнопок, которые идеально поместились на макетной плате. Можно подключить одну кнопку на пин — до 20 на Uno и до 70 на Mega! (Если действительно нужно 70 кнопок, лучше использовать мультиплексирование, не обязательно покупать Mega.)
Одна сторона каждой кнопки подключена к произвольному пину (в данном случае 4–13). Вторые контакты всех кнопок соединены вместе и подключены к одному пину, поддерживающему прерывания (пин 2).

Программный код
Код приведен ниже. Загрузите его в плату, откройте монитор порта. При нажатии кнопки выводится её номер. Обратите внимание: функция loop() не используется.

Принцип работы
Используется прерывание на пине 2, настроенное на спад (FALLING).
Чтобы обойтись без диодов, Arduino динамически переключает конфигурацию пинов. Существует два режима: Общий режим (Common mode) и Раздельный режим (Distinct mode).
Общий режим
Большинство времени схема находится в общем режиме. Пин прерывания настроен как INPUT_PULLUP, остальные пины — как OUTPUT и установлены в LOW.

void configureCommon() {
pinMode(commonPin, INPUT_PULLUP);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], OUTPUT);
digitalWrite(buttonPins[i], LOW);
}
}
В этом режиме нажатие любой кнопки притягивает пин прерывания к земле, срабатывает прерывание. Обработчик прерывания переключает пины в раздельный режим.
Раздельный режим
После срабатывания прерывания быстро переключаемся в раздельный режим.
Это зеркальная конфигурация: пин прерывания становится OUTPUT и LOW, а пины кнопок — INPUT_PULLUP.

void configureDistinct() {
pinMode(commonPin, OUTPUT);
digitalWrite(commonPin, LOW);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
}
В разделенном режиме только пины, соответствующие нажатым кнопкам, будут притянуты к земле. Легко опросить все пины и определить, какая кнопка вызвала прерывание.
После этого Arduino может вернуться в общий режим и ждать следующего прерывания. Или, в зависимости от задачи, остаться в разделенном режиме для обработки ввода, переключаясь в общий перед уходом в сон.
Более сложный пример: сервопривод с режимом сна
Подключим сервопривод и назначим каждой кнопке угол поворота (1=0°, 2=20°… 10=120°). Питание Arduino — от батареек.



В этом примере Arduino уходит в режим глубокого сна через 5 секунд неактивности. Сервопривод питание через транзистор, чтобы отключать его когда не используется.
Код для этого примера также приведен ниже.
Исходный код
Простой логгер в Serial
const int commonPin = 2;
const int buttonPins[] = {4,5,6,7,8,9,10,11,12,13};
unsigned long lastFire = 0;
void setup() {
configureCommon();
attachInterrupt(digitalPinToInterrupt(commonPin), pressInterrupt, FALLING);
Serial.begin(9600);
}
void loop() {
// Пусто!
}
void pressInterrupt() {
if (millis() - lastFire < 200) return; // Антидребезг
lastFire = millis();
configureDistinct();
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
if (!digitalRead(buttonPins[i])) {
press(i);
}
}
configureCommon();
}
void configureCommon() {
pinMode(commonPin, INPUT_PULLUP);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], OUTPUT);
digitalWrite(buttonPins[i], LOW);
}
}
void configureDistinct() {
pinMode(commonPin, OUTPUT);
digitalWrite(commonPin, LOW);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
}
void press(int button) {
Serial.println(button + 1);
}
Сервопривод с режимом сна
#include
#include
#include
const int commonPin = 2;
const int buttonPins[] = {4,5,6,7,8,9,10,11,12,13};
const int servoEnablePin = A1;
const int servoPin = A0;
Servo servo;
unsigned long lastFire = 0;
int status = 0;
void setup() {
pinMode(commonPin, INPUT_PULLUP);
configureCommon();
attachInterrupt(digitalPinToInterrupt(commonPin), pressInterrupt, FALLING);
servo.attach(servoPin);
pinMode(servoEnablePin, OUTPUT);
}
void loop() {
if (millis() - lastFire > 5000) {
digitalWrite(servoEnablePin, LOW);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_mode();
}
delay(10);
}
void pressInterrupt() {
sleep_disable();
power_all_enable();
if (millis() - lastFire < 200) return;
lastFire = millis();
configureDistinct();
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
if (!digitalRead(buttonPins[i])) {
press(i);
}
}
configureCommon();
}
void configureCommon() {
pinMode(commonPin, INPUT_PULLUP);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], OUTPUT);
digitalWrite(buttonPins[i], LOW);
}
}
void configureDistinct() {
pinMode(commonPin, OUTPUT);
digitalWrite(commonPin, LOW);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
}
void press(int button) {
digitalWrite(servoEnablePin, HIGH);
servo.write(map(button, 0, 9, 0, 180));
}
Схемы


Файлы схем для Fritzing: multiinterrupt_hoF76Oc4T5.fzz, servo_with_sleep_u9ZqxF0jhY.fzz
Производственный процесс
- Создание DTMF-декодера на Arduino без внешних микросхем
- Создание потрясающей системы Ambilight для монитора на Arduino
- Создание ультразвукового левитатора на Arduino: пошаговое руководство
- Умный вольтметр на Arduino и смартфоне — простое DIY-проект
- IoT-монитор пульса в реальном времени на Arduino и MAX30100
- Веб-сервер на Arduino Uno WiFi: управление LED через браузер
- Умный счетчик парковки: Отслеживание машин в реальном времени (Arduino, Processing, PHP)
- Автоматизация игры «Динозавр» в Chrome на Arduino и LDR с Python
- Сборка компактного FM-радио на Arduino Nano и RDA5807M
- Беспроводная прошивка Arduino через Bluetooth HC-05: щит без USB-кабеля