10 Tasten effizient mit einem einzigen Interrupt auf Arduino UNO steuern
Komponenten und Materialien
| Bauteil | Menge | Hinweise |
|---|---|---|
| Arduino UNO | 1 | Alternativ jeder andere Mikrocontroller |
| Steckbrett (Breadboard) | 1 | |
| Jumper-Kabel | 21 | |
| Taster (Tactile Switch) | 10 | Beliebige Größe |
Software und Online-Dienste
- Arduino IDE
Projektbeschreibung
Einführung
Interrupts sind äußerst nützlich: Sie vereinfachen nicht nur den Code, sondern ermöglichen auch präzises Timing oder das Aufwecken des Arduino aus dem Schlafmodus.
Stellen Sie sich eine batteriebetriebene Benutzeroberfläche vor – etwa eine Fernbedienung. Um Energie zu sparen, soll der Arduino (oder ein standalone ATmega) in den Power-Down-Modus versetzt werden. In diesem Modus kann der Mikrocontroller nur durch einen externen Interrupt aufgeweckt werden. Der ATmega328P auf dem Arduino Uno verfügt jedoch nur über zwei externe Pin-Interrupts (INT0 und INT1 an den Pins 2 und 3). Eine typische Benutzeroberfläche hat aber mehr als zwei Tasten – ein Problem.
Die herkömmliche Lösung besteht darin, alle Tasten normal anzuschließen und zusätzlich über Dioden mit einem Interrupt-Pin zu verbinden. Das verkompliziert die Schaltung jedoch erheblich.
Zusätzlich zu den externen Interrupts besitzt der ATmega328P auch Pin-Change-Interrupts. Es gibt Bibliotheken dafür, die eine gute Lösung darstellen. Doch während eines Programmierwettbewerbs fand ich einen Weg, dies mit Standard-Extern-Interrupts ohne zusätzliche Bauteile zu lösen.
Die Schaltung
Ich habe zehn Tasten verwendet, die gut auf mein Steckbrett passen (mehr hatte ich nicht). Pro Pin kann eine Taste angeschlossen werden – bis zu 20 auf einem Uno und bis zu 70 auf einem Mega! (Falls Sie tatsächlich 70 Tasten benötigen, empfehle ich Multiplexing; dafür brauchen Sie keinen ganzen Mega.)
Jede Taste wird mit einer Seite an einen beliebigen Pin angeschlossen (in meinem Fall Pins 4–13). Die anderen Seiten aller Tasten werden gemeinsam an einen einzelnen interruptfähigen Pin geführt (in meinem Fall Pin 2).
Der Code
Der vollständige Code findet sich unten. Laden Sie ihn auf Ihr Board hoch, öffnen Sie den Seriellen Monitor. Bei Tastendruck erscheint die Tastennummer. Beachten Sie: Die loop()-Funktion bleibt vollständig ungenutzt.
Funktionsweise
Es wird ein Interrupt verwendet – in meinem Fall an Pin 2, konfiguriert als FALLING.
Um auf Dioden zu verzichten, schaltet der Arduino die Schaltung „on the fly“ um. Es gibt zwei Betriebsmodi: Common Mode und Distinct Mode.
Common Mode
Die meiste Zeit verbleibt die Schaltung im Common Mode. Der Interrupt-Pin ist als INPUT_PULLUP konfiguriert, alle anderen Pins als OUTPUT und 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);
}
}
In diesem Modus zieht das Drücken einer beliebigen Taste den Interrupt-Pin auf LOW und löst den Interrupt aus. Die Interrupt-Service-Routine (ISR) schaltet dann sofort in den Distinct Mode um.
Distinct Mode
Sobald der Interrupt auslöst, wechselt das System schnell in den Distinct Mode – das genaue Gegenteil des Common Mode: Der Interrupt-Pin wird OUTPUT und LOW, alle Tasten-Pins werden 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);
}
}
Im Distinct Mode werden nur die Pins der tatsächlich gedrückten Tasten auf LOW gezogen. Der Mikrocontroller kann nun einfach alle Pins abfragen, um festzustellen, welche Taste den Interrupt ausgelöst hat.
Anschließend schaltet der Arduino zurück in den Common Mode und wartet auf den nächsten Interrupt. Je nach Anwendung kann er auch im Distinct Mode verbleiben und die Benutzereingaben normal verarbeiten – bevor er in den Schlafmodus geht, muss er jedoch wieder in den Common Mode wechseln.
Erweitertes Beispiel: Servo mit Schlafmodus
Als komplexeres Beispiel steuern wir einen Servo: Jede Taste entspricht einem Winkel (1=0°, 2=20° … 10=120°). Der Arduino wird batteriebetrieben und nach 5 Sekunden Inaktivität in den Power-Down-Modus versetzt, um Strom zu sparen. Der Servo wird über einen Transistor vom Strom getrennt, wenn er nicht benötigt wird.
Der Code für dieses Beispiel ist ebenfalls unten angehängt.
Code
Serial Logger (Grundbeispiel)
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() {
// Leer – alles läuft über Interrupt
}
void pressInterrupt() {
if (millis() - lastFire < 200) return; // Entprellung
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);
}
Servo mit Schlafmodus
#include <Servo.h>
#include <avr/sleep.h>
#include <avr/power.h>
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));
}
Schaltpläne
Fritzing-Dateien:
multiinterrupt_hoF76Oc4T5.fzzservo_with_sleep_u9ZqxF0jhY.fzz
Herstellungsprozess
- DTMF-Decoder mit Arduino bauen – ganz ohne zusätzlichen IC
- Monitor-Ambilight selbst bauen mit Arduino Nano & WS2812B LEDs
- Ultraschall-Schwebegerät mit Arduino bauen – Akustische Levitation Schritt für Schritt
- Intelligentes Voltmeter mit Arduino & Smartphone bauen – Einfaches DIY-Projekt
- Echtzeit-IoT-Herzfrequenzmonitor mit Arduino & MAX30100
- Arduino UNO WiFi Webserver: LED per Browser steuern
- Intelligenter Parkplatzzähler: Echtzeit-Fahrzeugverfolgung mit Arduino, Processing & PHP
- Arduino-gesteuertes automatisiertes Chrome Dino-Spiel bauen
- Kompaktes FM-Radio mit Arduino Nano und RDA5807M selbst bauen
- Drahtloses Arduino-Programmier-Shield mit HC-05 Bluetooth – Kein USB-Kabel nötig