LoRaWAN UDP Packet Forwarder 1
17.12.2024
Elektronik | Funk | Software
Der Technik-Blog
Nahezu jeder Mikrocontroller verfügt über mindestens einen oder mehrere Interrupt-Pins. Doch was bedeutet Interrupt? Gibt man im Übersetzer das Wort ein, so lautet die deutsche Übersetzung "Unterbrechung". Diese geniale Funktion gibt es wortwörtlich und ist gerade bei Anfängern eine eher unbekannte Sache, obwohl es ein geniales Feature ist. In diesem Artikel geht es allgemein um Interrupts und um die praktische Anwendung dieser Funktion.
Ein Interrupt ist eine Programmunterbrechung, die nach einem definierten Muster ausgelöst wird. Dabei wechselt das Hauptprogramm in ein eigenes Interrupt-Programm und arbeitet die dort definierten Befehle ab. Bei einem Interrupt handelt es sich also aus Sicht von der Software um ein unvorhersehbares Ereignis, welches jederzeit und beliebig oft ausgelöst werden kann. Das Hauptprogramm wird dabei einfach pausiert bzw. eingefroren und zu einem späteren Zeitpunkt nach der Abarbeitung der Interrupt-Befehle wieder fortgesetzt. Das Hauptprogramm wird dadurch nicht neu gestartet wie Beispielsweise nach einem Reset, sondern einfach an der Stelle fortgesetzt, wo es unterbrochen wurde.
Ein Interrupt wird mit folgender Zeile eingerichtet:
attachInterrupt(digitalPinToInterrupt(interruptPin), interrupt_call, RISING);Dabei wird als erstes der Interrupt Pin definiert. Beim Arduino Uno fuktioniert das nur am digitalen Pin 2 und 3. Andere Boards wie Beispielsweise ein STM Nucleo oder ESP32 oder auch der Arduino Mega haben noch viel mehr externe Interrupt Pins. Nach der Pin-Definition wird die Funktion definiert, die bei einem Interrupt aufgerufen werden soll. In unserem Beispiel ist das das Programm in "void interrupt_call()". Als letztes muss noch der Trigger definiert werden. Dabei handelt es sich um die Art, wie ein Interrupt erkannt werden soll. Beispiel: Wenn sich der Pegel am Input von High auf LOW ändert soll das Interrupt aktiviert werden. Es gibt dabei vier verschiedene Typen, auf die im nächsten Absatz näher eingegangen wird.
Es gibt verschiedene Flanken und Level, auf die der Interrupt eingestellt werden kann.
RISING: Mit steigender Flanke wird der Interrupt ausgelöst. Vereinfacht bedeutet das, dass der Interrupt ausgelöst wird, wenn der Pegel von Low auf High bzw. die Spannung am digitalen Pin von 0 auf 5 Volt wechselt.
FALLING: Der Interrupt wird mit fallender Flanke ausgelöst. Das bedeutet, der Pegel am digitalen Input muss von High auf Low wechseln, um den Interrupt zu triggern.
CHANGE: Hierbei handelt es sich um eine Kombination aus FALLING und RISING. Der Interrupt löst aus, sobald sich der Pegel bzw. die Spannung am digitalen Input ändert.
LOW: Der Interrupt wird durchgehend ausgelöst, solange der Pegel auf Low ist. Mit diesem Modus kann man auch das Hauptprogramm so lange unterbrechen, bis der Pegel wieder auf High wechselt.
HIGH: Einige andere Boards (z. B. Arduino MKR) unterstützen auch noch einen HIGH-Modus. Der Interrupt löst dabei invertiert zu LOW aus.
Da bei einem Interrupt das Hauptprogramm und alle Timer gestoppt werden, funktioniert während einem Interrupt auch kein Delay. Außerdem zählt die interne Clock auch nicht weiter. Funktionen wie millis(), micros() usw. können zwar abgefragt werden, aber zählen eben nicht weiter. Außerdem ist zu beachten, das sämtliche Daten die von einer anderen Hardware kommen während des Interrupts nicht registriert werden. Daten die über die serielle Schnittstelle, SPI, I2C usw. kommen gehen während dieser Zeit verloren. Variablen, die innerhalb der Interrupt-Funktion geändert werden, sollten übrigens als volatile gekennzeichnet werden. Der Grund liegt am Compiler, der beim kompilieren im schlimmsten Fall die Variable entfernt, da diese im Hauptprogramm nicht genutzt wird.
Nehmen wir an, wir haben einen Taster, der gelegentlich betätigt wird. Um eine Betätigung des Tasters zu erfassen, müsste man diesen im void loop ständig und ununterbrochen abfragen. Das kostet Ressourcen, aber hauptsächlich auch Zeit, die bei jedem Durchgang des Programmes in Anspruch genommen wird und eventuell anderen Operationen weggenommen wird. Hier wäre in Interrupt im RISING-Mode sinnvoll, denn dann muss der Status vom Taster nicht abgefragt werden, sondern nur bei der Betätigung des Tasters wird gewünschte Operation durchgeführt. Ein weiteres praktisches Beispiel, wofür Interrupts gerne verwendet werden, ist dort, wo kurze Reaktionszeiten eine wichtige Rolle spielen. Bei einem Sicherheitssystem (Alarmanlage, Wassermelder, Brandmelder, Airbag....) sollte der Mikrocontroller auch schnell und zu jeder Zeit reagieren, genau an dieser Steller ist auch wieder ein externer Interrupt die beste Lösung.
Einen Interrupt kann man zu jeder Zeit und an jeder Stelle im Programm erstellen oder eben auch wieder löschen. Dies macht beispielsweise auch bei einer softwareseitigen Entprellung von einem Taster sinn, indem man den definierten Interrupt für eine kurze Zeit deaktiviert und anschließend erneut setzt. Mit folgenden Befehl kann ein Interrupt für einen bestimmten Pin dauerhaft deaktiviert werden:
detachInterrupt(digitalPinToInterrupt(pinnumber));
Im nachfolgenden Beispielcode wird ein Interrupt auf dem digitalen Pin D2 definiert. Das Hauptprogramm erhöht nach einer Sekunde einen Zähler um den Wert 1. Dieser Wert wird auch im Serial Monitor ausgegeben. Ausgelöst wird das Interrupt durch einen Taster, der dann den Pegel am Input von Low auf High zieht. Der eigentliche Interrupt-Befehl ist nur eine Ausgabe im Serial Monitor.
int counter; int interruptPin = 2; void setup() { pinMode(interruptPin, INPUT); Serial.begin(9600); attachInterrupt(digitalPinToInterrupt(interruptPin), interrupt_call, RISING); } void loop() { Serial.println(counter); counter++; delay(1000); } void interrupt_call() { Serial.println("Interrupt"); }
Wird der Interrupt von einem Taster oder einem anderen mechanischen Bauteil auslöst, kann es vorkommen, dass er Interrupt mehrfach ausgelöst wird. Um dies zu verhindern, muss ein erneuter Interrupt nach dem ersten Interrupt ignoriert werden. Die einfachste Lösung ist hier mit einem Timer zu arbeiten. Dabei wird während des Interrupts von der Software verglichen, ob der letzte Zeitpunkt des vorherigen Interrupts mehr als 250 Millisekunden zurückliegt. Ist dies der Fall, wird im Beispielcode eine entsprechende Meldung im Serial Monitor ausgegeben, ansonsten wird dies übersprungen. Achtung: Der Zugriff auf Timer während eines Interrupt kostet Zeit und sollte daher bei zeitkritischen Anwendungen berücksichtigt werden.
int interruptPin = 2; int counter; static unsigned long last_interrupt_time = 0; unsigned long interrupt_time; void setup() { pinMode(interruptPin, INPUT); Serial.begin(9600); attachInterrupt(digitalPinToInterrupt(interruptPin), interrupt_call, RISING); } void loop() { Serial.println(counter); counter++; delay(1000); } void interrupt_call() { interrupt_time = millis(); if (interrupt_time - last_interrupt_time > 250) {//if interrupt comes faster than 250ms, ignore it Serial.println("Interrupt"); } last_interrupt_time = interrupt_time; }
Die folgende Grafik zeigt, dass ein einfacher Taster am Arduino nicht schlagartig umschaltet, was für das Auslösen eines mehrfachen Interrupt verantwortlich ist.
PT1000 Sensoren können nicht direkt analog mit einem Mikrocontroller gemessen werden. Wie baut man einen Messwandler mit dem LM358 für den PT1000 und Arduino?
WeiterlesenDer PT100 ist ein sehr präziser industrieller Temperatursensor. In diesem Artikel geht es um den Bau eines Messverstärkers zum Einlesen eines PT100 am Arduino
WeiterlesenAEQ-WEB © 2015-2024 All Right Reserved