Interrupt

Hallo,
ich wollte mitm Arduino Interrupts machen. Aber irgendwie möchte der
nicht so wirklich.

int pin2 = 2;
int pin3 = 3;
int pin4 = 4;

void setup() {
  pinMode (pin2, INPUT);
  pinMode (pin3, INPUT);
  pinMode (pin4, INPUT);
  
  attachInterrupt(pin2, pin2_alarm, FALLING);
  attachInterrupt(pin3, pin3_alarm, FALLING);
  attachInterrupt(pin4, pin4_alarm, FALLING);
}



void loop() {
}

void pin2_alarm(){
  Serial.println("pin_2");
}

void pin3_alarm(){
  Serial.println("pin3_alarm");
}

void pin4_alarm(){
  Serial.println("pin4_alarm");
}

ich habe mal sehr viel aus meinem Code gelöscht, sodass er mir nur
anzeigen soll, dass er ein der routine reingegangen ist. Aber es
passiert einfach nichts.

Weis einer, was daran noch falsch ist?

Serial hat in Interrupts nichts verloren. Wenn du sowas machen willst, setzte in der ISR eine Variable (die dann als volatile deklariert werden muss) und frage in loop() darauf ab.

Diese Art von Interrupts geht außerdem nur auf bestimmten Pins:
http://arduino.cc/en/Reference/AttachInterrupt

Der UNO hat nur zwei externe Interrupts auf Pins 2 und 3. Diese werden über die Interrupt Nummern 0 und 1 aufgerufen. Also da nicht die Pin Nummer übergeben! Siehe Tabelle in dem Link.

Um Interrupts auf mehr Pins auszulösen brauchst du das:

Chandler_B:
Weis einer, was daran noch falsch ist?

Interrupts funktionieren unterschiedlich auf dem DUE oder auf AVR-basierenden Boards.

Was für ein Arduino-Board verwendest Du?

Was hast Du an den angesteuerten Pins als äußere Beschaltung angeschlossen?

Und was soll der Quatsch, "Serial.print" innerhalb einer Interruptbehandlungsroutine zu verwenden?

Zumindest auf den 8-Bit Atmega-basierten Arduino Boards funktioniert "Serial.print" zwar so lange, bis der Serial-Ausgangspuffer überläuft. Aber bei einer hohen Interruptrate kann der Serial-Ausgangspuffer unter Umständen sogar schon übergelaufen sein, noch bevor das erste Byte vollständig gesendet wurde. Bei Interruptbehandlungsroutinen bist Du NICHT frei, was Du da als Code reinschreibst. Die Interruptbehandlung darf

  • nur Interrupt-sichere Funktionen verwenden
  • und muss eine möglichst kurze Laufzeit haben

Das Haupt-Problem ist wie gesagt, dass er die Pin-Nummern übergibt. Das geht nur auf dem Due.

Auf den anderen Boards muss man die Interrupt Nummer übergeben. Also für den UNO 0 für Pin 2 und 1 für Pin 3

Ich wiederhole:

Was für ein Arduino-Board verwendest Du?

Ok, das mit der eingabe hatte ich falsch verstanden (ICh dachte das wäre der Pin den ich eingeben soll).
Dann habe ich zwischenzeitig auf RCArduino: Need More Interrupts To Read More RC Channels ? so etwas gefunden wie Serenifly gesagt hat, das hat aber nicht funktioniert. Darauf hin habe ich hier gesehen, das Serenifly eine andere Seite mit der Library geschickt hat. Hier bekomme ich zumindest keine Fehlermeldung mehr. Das Serial.print() hatte ich nur drinn, um zu sehen, ob er überhaupt in der routine reingeht. Jetzt habe ich das noch einmal geändert. Ach ja, ich verwende ein Arduino UNO

#include <PinChangeInt.h>

int pin2 = 2;
int pin3 = 3;
int pin4 = 4;
int pin7 = 7;
volatile int x = 0;

void setup() {
  pinMode (pin2, INPUT);
  pinMode (pin3, INPUT);
  pinMode (pin4, INPUT);
  pinMode (pin7, OUTPUT);
  digitalWrite (pin7, LOW);
  
  PCintPort::attachInterrupt(pin2, pin2_alarm, FALLING);
  PCintPort::attachInterrupt(pin3, pin3_alarm, FALLING);
  PCintPort::attachInterrupt(pin4, pin4_alarm, FALLING);
}



void loop() {
  digitalWrite(pin7, LOW);
  if (x == 1){
    digitalWrite(pin7, HIGH);
    delay(1000);
    x = 0;
  }
}

void pin2_alarm(){
  x = 1;
}

void pin3_alarm(){
  x = 1;
}

void pin4_alarm(){
  x = 1;
}

Das ganze funktioniert aber immer noch nicht. Ich weiss, dass die Taster-Abfrage so nicht geschickt ist, das werde ich später noch machen. Die led (an pin7) sollte ja trotzdem leuchten.
Chandler

Wieso brauchst da da überhaupt Interrupts? Was spricht dagegen die Taster per Polling abzufragen?

Chandler_B:
Ich weiss, dass die Taster-Abfrage so nicht geschickt ist, das werde ich später noch machen. Die led (an pin7) sollte ja trotzdem leuchten.

Mechanische Taster prellen, und daher läßt sich eine sichere Abfrage von Tastern mittels Interrupts überhaupt nicht realisieren. Taster mußt Du aus dem laufenden Programm heraus abfragen. Und damit das bei interaktiven Programmen gut funktioniert, ist "delay()" beim Programmieren sowas wie ein "verbotener Befehl", den Du nicht verwenden darfst. Ebenso alle anderen blockierenden Komforbefehle der Arduino-IDE.

Übrigens: Taster, die Du mit pinMode "INPUT" initialisierst, müssen in der Schaltung zwingend einen Pull-Widerstand am Schalter angeschlossen haben? Hast Du? Schaltung des Schalters und des Pull-Widerstands korrekt?

Lediglich in speziellen Ausnahmefällen ergibt die Abfrage mechanischer, prellender Taster über Interrupt einen Sinn, z.B. wenn ein Schalter nur sehr selten, aber dafür nur sehr kurze Zeit betätigt wird, z.B. bei Zählkontakten.

Es gibt zwei gründe, warum ich dies mit interrupts machen möchte. Zunächst möchte ich es einfach lernen, daher so ein einfaches Programm. Desweiteren habe ich ein Programm, wo ein Programm durchlaufen werden soll, aber nur wenn ein Taster gedrückt wird. Dabei werden 5 Taster zum Einsatz kommen, die einfach nur andere Parameter bringen.
So ein Tasterdruck wird sehr selten kommen Dazwischen mindestens 1min nichts. Da auch nur max 5x am Tag ein Tastendruck kommt, würde ich im späteren Verlauf versuchenversuchen den Arduino in den sleep-Modus zu schicken (auch um das zu lernen wie das geht).
Bei den Eingangpins (Pin2/3/4) habe ich einen Pull-down Widerstand vorgesetzt.

Das ganze funktioniert aber immer noch nicht

Du hast die Pullups auf den Pins nicht aktiviert !?!?

Bei den Eingangpins (Pin2/3/4) habe ich einen Pull-down Widerstand vorgesetzt.

Wie angeschlossen?
Wie den Widerstand, wie den Schalter/Taster?

Eine Zeichnung wäre nicht schlecht......

PS:
Pin 2 und 3 haben eigene Interrupts.
Bei Pin 4 würde die PinChangeInt.h Sache Sinn machen, bei 2 + 3 nicht.
Auch können nur 2 + 3 deinen Uno aus dem Tiefschlaf holen. Pin 4 nicht.

Anbei ein Schaltplan

combie:
Auch können nur 2 + 3 deinen Uno aus dem Tiefschlaf holen. Pin 4 nicht.

Stimmt nicht. Die Pin Change Interrupts können das auch. Aus dem Datenblatt:

Only an External Reset, a Watchdog System Reset, a Watchdog Interrupt, a Brown-out Reset, a 2-wire Serial Interface address match, an external level interrupt on INT0 or INT1, or a pin change interrupt can wake up the MCU.

Wird hier mit einer Keypad Matriz gezeigt:

Wenn es unbedingt Interrupts sein müssen ist hier vielleicht eine Entprellung der Taster per RC-Glied sinnvoll:
http://www.mikrocontroller.net/articles/Entprellung#Hardwareentprellung

Chandler_B:
Anbei ein Schaltplan

Und warum willst Du zu einer vernünftigen Schaltung kein ebenso vernünftiges Programm machen, sondern unvernünftigerweise Interrupts benutzen?

Ich habe das immer noch nicht verstanden.

Weil Du "Interrupt-Programmierung" lernen möchtest?

Warum möchtest Du komplexe "Interrupt-Programmierung lernen", wenn Du
a) bisher weder eine "normale" Tasterauswertung programmieren kannst und
b) die Verwendung von Interrupts für Deinen Anwendungsfall total kontraproduktiv ist

Es ist mir unbegreiflich!

Also normalerweise frage ich einen Taster wie folgt ab

...
void loop() {
  CurrentState = digitalRead(Pin); // taster abfragen
  delay(10); 
  if (CurrentState == digitalRead(Pin)) { // nochmal abfragen damit der taster wirklich gedrückt wurde
    currentMillis = millis(); 
    if (CurrentState != PreviousState && CurrentState == LOW) {
      previousMillis = currentMillis;
      MACH WAS ...
    }
  PreviousState = CurrentState;

So hab habe ich das gelernt. nun möchte ich aber nicht nur ein pin abfragen, sondern 5. Im prinzip machen die alle das selbe, nur mit anderen Parametern.

So wie hier, habe ich es auch in meinem anderen Programm. Bis ich auf die Problemheit mit mehreren Tastern kam. Daher habe ich ein ganz einfaches Programm versucht zu machen, nur um zu sehen ob das mit den Interrupts funktioniert oder nicht. Das Interrupts im Programm im obigen post kein Sinn macht, ist mir klar. Aber darum ging es mir bei dem Programm auch nicht.

Wenn du das einfacher haben willst, gibt es die Bounce Library:

https://github.com/thomasfredericks/Bounce2/archive/master.zip

Die kapselt den ganzen Entprellungs-Code

Chandler_B:
So hab habe ich das gelernt.

Im Prinzip funktioniert das so.

Aber ich würde das nicht ganz so machen, weil es wegen des delay()-Aufrufs weder besonders effektiv noch wegen des Mixens von Verarbeitung und Ausgabe wirklich strukturiert ist und es bei Erweitungen ziemlich unübersichtlich werden kann.

Wenn es in einem Mikrocontrontrollerprogramm sowohl "viele Eingaben" von Buttons und anderen Eingabegerätschaften gibt und ebenso "viele Verarbeitungsmöglichkeiten" und "viele Ausgaben" produziert werden sollen, dann bietet es sich an, die Funktionsbereiche Eingabe, Verarbeitung und Ausgabe zuerst mal komplett voneinander zu trennen.

Hast Du schon mal was vom EVA-Prinzip (Eingabe, Verarbeitung, Ausgabe) oder vom IPO principle (Input, Processing, Output) bei der Programmierung gehört?

Chandler_B:
Im prinzip machen die alle das selbe, nur mit anderen Parametern.

Gerade solche Dinge, die "fast alle dasselbe" machen, sind in einem Programm immer besonders einfach zu erschlagen, durch Wahl geeigneter Datenstrukturen (z.B. "array" oder "array of struct"), und Algorithmen (z.B. "for-Schleife").

Also grobe Gliederung der loop-Funktion:

void loop()
{
  eingabe();
  verarbeitung();
  ausgabe();
}

oder wenn Du mehr auf Neudeutsch stehst:

void loop()
{
 input();
 processing();
 output();
}

und zum Abfragen der Buttons eine "eingabe()" Funktion schreiben, die in einer "for-Schleife" alle fünf Buttons nacheinander abfragt und die Ergebnisse der Buttonabfrage dann bereitstellt.

Kommt so eine Art der Gliederung für Dein Programm in Frage, oder findest Du es völlig abwegig, dass Du über die Buttons etwas eingibst, der Controller die erfolgten Eingaben verarbeitet, und am Ende irgendeine Art Ausgabe erfolgt (z.B. LEDs oder andere Geräte an und aus geschaltet oder sogar irgendwo Texte angezeigt werden)?

Ich hänge Dir mal ein Demo-Programm zur Abfrage von 5 Buttons an, mit nur zwei selbstgeschriebenen Funktionen:

  • eingabe()
  • ausgabe()

Die Ausgabefunktion zeigt die Pin-Nummer des gedrückten oder losgelassenen Buttons an. Eine Ausgabe "-2" steht also für das Drücken vom Button an Pin-2 und "2" steht für das Loslassen des Buttons an diesem Pin.

Eine weitergehende Verarbeitung findet gar nicht statt.

Mit demselben Code können beliebig viele Buttons an beliebig vielen Pins behandelt werden, einfach durch eine Erweiterung des deklarierten Arrays "byte buttonPins[]=..."

Anpassmöglichkeit: Wenn anstelle von

#define INPUTMODE INPUT    // INPUT oder INPUT_PULLUP

deklariert wird

#define INPUTMODE INPUT_PULLUP    // INPUT oder INPUT_PULLUP

dann brauchen die Buttons keine PullDown-Widerstände in der Schaltung haben, sondern dann werden die internen (im Controller vorhandenen) PullUp-Widerstände aktiviert, was eine brauchbare Lösung ist, wenn die Buttons nur mit kurzen Leitungen am Controllerboard angeschlossen sind.

Probier's mal aus:

#define INPUTMODE INPUT    // INPUT oder INPUT_PULLUP
#define BOUNCETIME 5              // maximale Prellzeit in Millisekunden
byte buttonPins[]={2, 3, 4, 5, 6};// Pin-Nummern der angeschlossenen Buttons
#define NUMBUTTONS sizeof(buttonPins) // Anzahl der Buttons (automatisch definiert)
byte buttonState[NUMBUTTONS];  // Speichert den aktuellen HIGH/LOW Status der Pins
byte buttonChange[NUMBUTTONS]; // Speichert Flankenwechsel an den Pins
enum{UNCHANGED,BUTTONUP,BUTTONDOWN};

void eingabe(){
// Tasterstatus und Flankenwechsel an den Tastern auswerten  
  static unsigned long lastButtonTime; // Zeitstempel, wann die Routine zuletzt durchlief
  memset(buttonChange,0,sizeof(buttonChange)); // Alle alten Flankenwechsel verwerfen
  if (millis()-lastButtonTime<BOUNCETIME) return;  // innerhalb der Prellzeit die Funktion verlassen
  lastButtonTime=millis(); // Zeitstempel der letzten Buttonabfrage aktualisieren
  for (int i=0;i<NUMBUTTONS;i++) 
  {
    byte curState=digitalRead(buttonPins[i]);        // Aktueller Buttonstatus
    if (INPUTMODE==INPUT_PULLUP) curState=!curState; // Vertauschte Logik bei INPPUT_PULLUP
    if (curState!=buttonState[i])                    // Flankenwechsel erkannt
    {
      if (curState==HIGH) buttonChange[i]=BUTTONDOWN;
      else buttonChange[i]=BUTTONUP;
    }
    buttonState[i]=curState;  // den jetzigen Buttonzustand speichern
  }
}


void ausgabe(){
// Bei Tastendruck Ausgabe des Pins auf Serial
// Button gedrückt: Minus-Pinnummer
// Button losgelassen: Pinnummer
  byte action;
  for (int i=0;i<NUMBUTTONS;i++)
  {
    switch (buttonChange[i])  
    {
      case BUTTONUP: Serial.println(buttonPins[i]);break;
      case BUTTONDOWN: Serial.println(-buttonPins[i]);break;
    }
  }
}


void setup() {
  Serial.begin(9600);
  Serial.println("Press any button to continue...");
  for (int i=0;i<NUMBUTTONS;i++) pinMode(buttonPins[i],INPUTMODE);
}

void loop() {
  eingabe();
  ausgabe();
}

Wenn jetzt noch irgendeine Art Verarbeitung stattfinden soll, kann man natürlich auch noch eine Funktion dafür schreiben und deren Aufruf mit in die loop-Funktion aufnehmen.