Arduino Interrupt Tid

Fra Holstebro HTX Wiki
Skift til: navigering, søgning

Interruptet er generelt i processorer en meget hurtig reaktion på en hændelse.

Det foregår ved at man indstiller hvilken type hændelse der skal reageres på, og hvilken rutine der skal servicere. Når hændelsen indtræder, så afbrydes det normale kodeforløb, og service-rutinen bliver kaldt.

Der er forskellige ting der kan interrupte i en Arduino, men det er på Arduino UNO kun pin 2 og 3 der kan understøttes af de normale serviverutiner, men fx. den serielle kommunikation anvender interrupt til at lægge nye karakterer ud fra bufferen til sending, og timer-interruptet er med til at time millis() og micros().

Måleopstilling til måling af interruptet

Til at måle hvor hurtigt interruptet reagerer sættes et program op, der interrupter sig selv via en ekstern pin.

Måleopstillingen er sat sammen på følgende måde:

Opstilling til målinge af interrupt-tid
Opstilling til målinge af interrupt-tid

De to voltmetre er faktisk et oscilloscop, der gør at man kan måle tiderne for programmet. Udgangen på pin-8 er den der leverer et signal til interruptet på pin-2.

Kode til måling af interruptet

Der sættes nogle af de ben op som skal være output og input, dette gøres som normalt, selvom outputtet anvendes lidt alternativt.

Sidst i setup() tilføjes interruptet til det ben der er bestemt som interrupt-ben, og der tilknyttes den serviceRoutine der skal kaldes når interruptet kommer. Dette setup laves med rutinen attachInterrupt[1], der knytter tingene sammen og angiver at interruptet skal ske på en stigende kant.

I serviceRoutine sker der kun det at der vippes med en pin (13) der ellers ikke anvendes i programmet, så man kan måle at interruptet er udført.

Nede i loopet startes med at vippe med en pulse-pin, der skal give en høj puls hvert 0,125μs, så man kan se hvornår interruptet afbryder.
Samtidigt med den anden puls på pulse-pin sendes et signal til interruptet, så man kan se hvornår det skal reagere.

Dette er realiseret med koden herunder, der også kan hentes som interrupt_time i denne ZIP-fil.

byte activatePin = 8;
byte pulsePin = 9;
byte signalPin = 13;
byte interruptPin = 2;

void setup() {
  pinMode(activatePin, OUTPUT);
  pinMode(pulsePin, OUTPUT);
  pinMode(signalPin, OUTPUT);
  pinMode(interruptPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(interruptPin), serviceRoutine, RISING);
}

void serviceRoutine() {
  PORTB = B00100000;  // Send en kort puls på signal-pin, så det kan måles
  PORTB = B00000000;
}

void loop() {
  PORTB = 0B00000010;  // vip med pulse-pin
  PORTB = 0B00000000;
  PORTB = 0B00000011;  // Set både activate-pin og pulse-pin samtidigt
  PORTB = 0B00000000;
  PORTB = 0B00000010;  // vip med pulse-pin flere gange, så man kan måle hvornår interruptet sker 
  PORTB = 0B00000000;
  PORTB = 0B00000010;  // vip med pulse-pin
  PORTB = 0B00000000;
  PORTB = 0B00000010;  // vip med pulse-pin
  PORTB = 0B00000000;
  PORTB = 0B00000010;  // vip med pulse-pin
  PORTB = 0B00000000;
  PORTB = 0B00000010;  // vip med pulse-pin
  PORTB = 0B00000000;
  PORTB = 0B00000010;  // vip med pulse-pin
  PORTB = 0B00000000;
  PORTB = 0B00000010;  // vip med pulse-pin
  PORTB = 0B00000000;
  PORTB = 0B00000010;  // vip med pulse-pin
  PORTB = 0B00000000;
  PORTB = 0B00000010;  // vip med pulse-pin
  PORTB = 0B00000000;
  PORTB = 0B00000010;  // vip med pulse-pin
  PORTB = 0B00000000;
  PORTB = 0B00000010;  // vip med pulse-pin
  PORTB = 0B00000000;
  delay(1);
}

Målinger på interruptet

Målingerne er lavet med et digitalt oscilloscop, og de er ikke helt perfekte, da man måler i grænsen af hvad oscilloscopet kan følge med til, og specielt at de signaler er så hurtige, at man får påvirkninger mellem målekanalerne. Det kræver at man tolker på de målinger man får, og at de steder hvor der ikke er et tydeligt signal, så er det indstråling fra den anden kanal.

På det samme signaler er der målt med en logik-analysator som viser at signalerne kommer som programmet har bestemt at de skal.

Samme måling på foretaget med en logik-analysator
Samme måling på foretaget med en logik-analysator

Hele målingen kan også ses i et billede som vist i afsnittene herunder. Målingerne er lavet med 3 forskellige målinger for at illustrere hvad der sker i interruptet.

Processorens reaktionstid

Måling på hvor hurtigt processoren får afbrudt den kørende kode
Måling på hvor hurtigt processoren får afbrudt den kørende kode

Som angivet i koden, så sker interruptet ved den anden puls, der hvor starten af tidsmålingen er angivet.

Da pulserne holder op med at køre efter 0,19μs kan vi se at der er gennemført 3 maskininstruktioner efter interruptbenet er sat høj, men altså 4 instruktioner incl. det at sætte høj - under alle omstændigheder ret hurtig reaktion.

Årsagen til at vi kan se at interruptet er i gang er, at den normale kodeafvikling stopper (koden i loop() fortsætter med at pulse med puls-benet), så det må være interrupt-koden der er gået i gang.

Dette svarer til den reaktionstid, som kan ses i databladet for ATMEGA328[2] side 70, hvor der er en timing-figur der angiver hvordan interruptet forplanter sig igennem elektronikken ind til interruptflaget der sætter interrupt-koden i gang:

Interrupt-kredsløbet i indgangen af processoren for pin-change interrupt
Interrupt-kredsløbet i indgangen af processoren for pin-change interrupt

Som det kan ses på timingen under diagrammet, så går der mindst 3 CLK-perioder (svarende til 3 maskininstruktioner) inden interrupt-flaget sættes, hvilket er det der aktiverer kaldet til interrupt-vektoren, der starter interruptkoden, og dermed afbryder den normale kode-afvikling.

Reaktionstid ind til interrupt-koden

Den kode der er etableret i serviceRoutinen er ganske simpel, og den udføres faktisk ret hurtigt. Den sætter signal-pin høj og trækker den lav igen, hvilket tager to maskininstruktioner eller 0,125μs, hvilket er det vi måler på den anden (blå) kanal.

Måling på hvor lang tid der går fra signalet på interruptet kommer til koden i interruptet afvikles
Måling på hvor lang tid der går fra signalet på interruptet kommer til koden i interruptet afvikles

Som det kan ses på målingen, så starter koden ikke lige efter at interruptet har reageret, der går faktisk lang tid - 3,11μs fra kanten af interruptet til at koden inde i interruptet udføres, det svarer til ca. 50 instruktioner.

Forklaringen er til dels at der for at servicere interruptet er det nødvendigt at hoppe til interrupt-vectoren, og derfra hoppes til Arduinos system for interruptet, hvor man starter med at gemme processorens registre, og der skal kaldes ud til serviceRoutinen. Dette kan dog ikke forklare mere end ca. 10 instruktioner, så derfor må der foregå en del mere, der kan forklares med at attachInterrupt håndterer en del flere ting, så derfor er der en del delay inden koden går i gang - hvis man ønsker at optimere på dette, så må man selv kode det i assemblerkode, og tage ansvaret for at man ikke konflikter med noget i Arduino firmwaren.

Tiden hvor den kørende kode er afbrudt af interruptet

Måling på hvor lang tid den kørende kode er afbrudt af interruptet
Måling på hvor lang tid den kørende kode er afbrudt af interruptet

Den sidste måling her er hvor lang tid den kørende kode er afbrudt for at servicere interruptet.

Tiden er selvfølgelig afhængig af hvor lang tid det tager at afvikle den kode man har liggende inde i interrupt serviceRoutinen, men som før omtalt tager koden ikke mere end 0,125μs at afvikle, så med en måling der angiver at den samlede tid at loop()-koden er afbrudt (fra de første pulser til det efterfølgende pulstog, som faktisk er sammenhængende kode, men som bliver afbrudt af interruptet) kan måles til 5,49μs, så den tid Arduinos kode anvender til serviceringen af interruptet er 5,36μs. Til dette skal så føjes den tid det tager for koden i interrupt serviceRoutinen at afvikle, hvilket gerne skal være kort tid.

Denne tid kan det være nødvendigt at tage højde for i sin kode, hvis der i loop()-koden er noget der er meget tidskritisk.

Man kan også sikre en del af sin kode ved at slå interruptet fra[3] som vist her:

noInterrupts();
// Kritisk kode placeres her
interrupts();

Referencer

  1. Arduinos reference på attachInterrupt()
  2. Databladet for ATMEGA328
  3. Arduinos reference omkring noInterrupts()