Wie viele Interrupt-Pins kann ein Uno verarbeiten?

15

Ich suche einen 7-Kanal-RC-Empfänger mit dem Arduino Uno R3. In der Dokumentation werden maximal 2 Interrupt-Pins erwähnt, während in einigen anderen Blogs die Verwendung von bis zu 20 Pins als Interrupt mit der PinChangeInt-Bibliothek erwähnt wurde. Wie viele Interrupts kann der Arduino nativ verarbeiten? Und unterscheidet sich dies davon, wie viele mit Softwareunterstützung wie PinChangeInt gehandhabt werden können?

Ascheshr
quelle

Antworten:

12

Es gibt zwei Arten von Interrupts vom Typ "Stiftwechsel". Die externen Interrupts, von denen es zwei auf dem Uno gibt. Sie heißen 0 und 1, beziehen sich jedoch auf die digitalen Pins 2 und 3 auf der Platine. Diese können so konfiguriert werden, dass sie ansteigen, abfallen, sich ändern (ansteigen oder abfallen) oder NIEDRIG erkennen.

Zusätzlich gibt es "Pin-Wechsel" -Interrupts, die einen Wechsel des Pin-Zustands in einem der 20 Pins (A0 bis A5 und D0 bis D13) erkennen. Diese Pin-Wechsel-Interrupts basieren ebenfalls auf Hardware und sind an sich so schnell wie die externen Interrupts.

Beide Typen sind auf Registerebene etwas umständlich zu verwenden, aber die Standard-IDE enthält attachInterrupt (n) und detachInterrupt (n), wodurch die Schnittstelle zu externen Interrupts vereinfacht wird. Sie können auch die Pin-Change-Bibliothek verwenden , um die Pin-Change-Interrupts zu vereinfachen.

Wenn Sie sich jedoch für eine Minute von der Bibliothek fernhalten, können Sie feststellen, dass Pin-Wechsel-Interrupts genauso schnell oder schneller sind als externe Interrupts. Zum einen müssen Sie, obwohl Pin-Wechsel-Interrupts für Stapel von Pins funktionieren, nicht den gesamten Stapel aktivieren. Wenn Sie beispielsweise Änderungen an Pin D4 erkennen möchten, reicht dies aus:

Beispielskizze:

ISR (PCINT2_vect)
 {
 // handle pin change interrupt for D0 to D7 here
 if (PIND & bit (4))  // if it was high
   PORTD |= bit (5);  // turn on D5
 else
   PORTD &= ~bit (5); // turn off D5
 }  // end of PCINT2_vect

void setup ()
  { 
  // pin change interrupt (example for D4)
  PCMSK2 |= bit (PCINT20);  // want pin 4
  PCIFR  |= bit (PCIF2);    // clear any outstanding interrupts
  PCICR  |= bit (PCIE2);    // enable pin change interrupts for D0 to D7
  pinMode (4, INPUT_PULLUP);
  pinMode (5, OUTPUT);
  }  // end of setup

void loop ()
  {
  }

Mein Test ergab, dass es 1,6 µs dauerte, bis der "Test" -Pin (Pin 5) auf eine Änderung am Interrupt-Pin (Pin 4) reagierte.


Wenn Sie nun den einfachen (faulen?) Ansatz wählen und attachInterrupt () verwenden, werden Sie feststellen, dass die Ergebnisse langsamer und nicht schneller sind.

Beispielcode:

void myInterrupt ()
 {
 if (PIND & bit (2))  // if it was high
   PORTD |= bit (5);  // turn on D5
 else
   PORTD &= ~bit (5); // turn off D5
 }  // end of myInterrupt

void setup ()
  { 
  attachInterrupt (0, myInterrupt, CHANGE);
  pinMode (2, INPUT_PULLUP);
  pinMode (5, OUTPUT);
  }  // end of setup

void loop ()
  {
  }

Das Wechseln des Teststifts dauert 3,7 µs, viel mehr als die obigen 1,6 µs. Warum? Weil der Code, den der Compiler für den "generischen" Interrupt-Handler generieren muss, jedes denkbare Register beim Eintritt in die ISR speichern (pushen) und sie dann vor der Rückkehr wiederherstellen (popen) muss. Hinzu kommt der Overhead eines weiteren Funktionsaufrufs.


Jetzt können wir das umgehen, indem wir attachInterrupt () vermeiden und es selbst tun:

ISR (INT0_vect)
 {
 if (PIND & bit (2))  // if it was high
   PORTD |= bit (5);  // turn on D5
 else
   PORTD &= ~bit (5); // turn off D5
 }  // end of INT0_vect

void setup ()
  { 
  // activate external interrupt 0

  EICRA &= ~(bit(ISC00) | bit (ISC01));  // clear existing flags
  EICRA |=  bit (ISC00);    // set wanted flags (any change interrupt)
  EIFR   =  bit (INTF0);    // clear flag for interrupt 0
  EIMSK |=  bit (INT0);     // enable it

  pinMode (2, INPUT_PULLUP);
  pinMode (5, OUTPUT);
  }  // end of setup

void loop ()
  {
  }

Das ist mit 1,52 µs die schnellste von allen - es sieht so aus, als wäre irgendwo ein Taktzyklus gespeichert worden.


Es gibt jedoch eine Einschränkung für PIN-Wechsel-Interrupts. Sie sind gestapelt. Wenn Sie also Interrupts an vielen Pins haben möchten, müssen Sie innerhalb des Interrupts testen, welcher geändert wurde. Sie können dies tun, indem Sie den vorherigen Pin-Status speichern und mit dem neuen Pin-Status vergleichen. Dies ist nicht unbedingt besonders langsam, aber je mehr Pins Sie prüfen müssen, desto langsamer wird es.

Die Chargen sind:

  • A0 bis A5
  • D0 bis D7
  • D8 bis D13

Wenn Sie nur ein paar weitere Interrupt-Pins wünschen, können Sie jedes Testen vermeiden, indem Sie nur Pins aus verschiedenen Chargen verwenden (z. B. D4 und D8).


Weitere Details unter http://www.gammon.com.au/interrupts

Nick Gammon
quelle
8

Es gibt zwei Arten von Interrupts. Was der Arduino-Spielplatz gesagt hat:

Der Prozessor im Herzen eines Arduino verfügt über zwei verschiedene Arten von Interrupts: "external" und "pin change". Es gibt nur zwei externe Interrupt-Pins am ATmega168 / 328 (dh im Arduino Uno / Nano / Duemilanove), INT0 und INT1, und sie sind den Arduino-Pins 2 und 3 zugeordnet. Diese Interrupts können so eingestellt werden, dass sie bei RISING oder auslösen FALLING-Signalflanken oder auf niedrigem Pegel. Die Trigger werden von der Hardware interpretiert und der Interrupt ist sehr schnell. Das Arduino Mega verfügt über einige weitere externe Interrupt-Pins.

Auf der anderen Seite können die Pinwechsel-Interrupts für viele weitere Pins aktiviert werden. Bei ATmega168 / 328-basierten Arduinos können sie auf einem oder allen 20 Signalpins des Arduino aktiviert werden. Auf den ATmega-basierten Arduinos können sie auf 24 Pins aktiviert werden. Sie werden gleichermaßen bei RISING- oder FALLING-Signalflanken ausgelöst. Es liegt also am Interrupt-Code, die richtigen Pins für den Empfang von Interrupts festzulegen, um festzustellen, was passiert ist (welcher Pin? ... ist das Signal gestiegen oder gefallen?) Und richtig damit umgehen. Darüber hinaus sind die Pinwechsel-Interrupts in 3 "Ports" auf der MCU gruppiert, sodass nur 3 Interrupt-Vektoren (Subroutinen) für den gesamten Pin-Körper vorhanden sind. Dies macht das Lösen der Aktion in einem einzelnen Interrupt noch komplizierter.

Grundsätzlich sind die externen Interrupts extrem schnell, da sie alle hardwarebasiert sind. Es gibt jedoch auch die PIN-Wechsel-Interrupts, aber diese scheinen viel langsamer zu sein, da sie meistens softwarebasiert sind.

tl; dr: Die 20 Interrupt-Pins zusammen sind viel langsamer. Die 2 Interrupt-Pins sind die schnellsten und effizientesten.


BEARBEITEN: Ich habe mir gerade das Datenblatt angesehen und es heißt, dass ein Pinwechsel-Interrupt für einen der ausgewählten Pins ausgelöst wird, ohne dass angegeben wird, welcher Pin geändert wurde (obwohl er in drei Interrupt-Vektoren aufgeteilt ist).

  • Bei externen Interrupts wird angezeigt, dass Pin 3 gerade geändert wurde
  • Beim PIN-Wechsel wird Ihnen mitgeteilt, dass sich die PIN geändert hat, die Sie überwacht haben!

Wie Sie sehen, fügt ein PIN-Wechsel-Interrupt dem ISR viel Overhead hinzu, den Sie bewältigen müssen, indem Sie frühere Zustände speichern und prüfen, ob es sich um den PIN handelt, um den Sie sich Sorgen machen. Für einen Ruhezustand ist das vielleicht in Ordnung, aber es ist besser, die externen Interrupts zu verwenden.

Anonymer Pinguin
quelle