Forum: Compiler & IDEs Verständnisfrage Interrupt-Programmierung


von René J. (renej)


Lesenswert?

Hallo!

Beschäftige mich seit kurzem mit Interrupts, habe bisher nur ohne 
programmiert. Jetzt möchte ich ein wenig rumprobieren, aber irgendwie 
hängts noch irgendwo.

Beispiel: Ich möchte alle paar Sekunden eine LED mehrmals kurz blinken 
lassen um mir per Blink-Code einen Status anzeigen zu lassen. So wie 
z.B. beim PC die BIOS-Piep-Codes beim booten.

Dazu habe ich jetzt mal stumpf folgenden kleinen Code 
zusammengeschustert um es auszuprobieren:

1
#include "TimerOne.h"    // Timerlibrary
2
const int ledPin = 13;   // Pin der LED
3
int alle_x_sekunden = 5; // Intervallzeit in Sekunden
4
5
void blinken() {  // Funktion für Blink-Code
6
  int i = 0;
7
  do { 
8
    digitalWrite(ledPin, digitalRead(ledPin) ^ 1);  // Wenn aus, dann an
9
    delay(200);
10
    digitalWrite(ledPin, digitalRead(ledPin) ^ 1);  // Wenn an, dann aus
11
    delay(200);
12
    i++;
13
  } while (i < 3);  // Beispielhaft: 3x
14
  
15
}
16
17
void setup()   {
18
  pinMode(ledPin, OUTPUT);
19
  Timer1.initialize(alle_x_sekunden * 1000000);
20
  Timer1.attachInterrupt(blinken);
21
}
22
23
void loop(){}

Allerdings blinkt jetzt die LED nur "alle_x_sekunden" einmal kurz auf.

Setze ich blinken() in die loop-Funktion, funktioniert blinken() wie 
erwartet.

Warum blinkt meine LED nicht alle 5 Sek 3x kurz auf?

PS: Ich bin mir bewusst, dass der interrupt-code so kurz als möglich 
sein sollte, delay() benutze ich jetzt nur zum probieren...

von Karl H. (kbuchegg)


Lesenswert?

Die Frage ist, was delay() eigentlich alles intern macht.

Aber eigentlich erübrigt sich die Frage, denn das weitaus stärkere 
Prinzip lautet: delay() und Interrupt Handling passen grundsätzlich 
nicht zusammen. Die willst in einer Interrupt Routine keinen delay 
haben, weil das das ganze Interrupt System ad absurdum führt.

Was du statt dessen haben willst:
Du willst, dass dein Timer alle 200 Millisekunden (um bei deinem 
Beispiel zu bleiben) die Interrupt Funktion aufruft. Dort zählst du mit, 
der wievielte Aufruf das ist und je nach dieser Zahl, schaltest du die 
LED ein oder aus bzw. tust ganz einfach nichts. Kein delay notwendig.

von BirgerT (Gast)


Lesenswert?

René J. schrieb:

> Beispiel: Ich möchte alle paar Sekunden eine LED mehrmals kurz blinken
> lassen um mir per Blink-Code einen Status anzeigen zu lassen. So wie
> z.B. beim PC die BIOS-Piep-Codes beim booten.

Hier wird ein ähnlich gelagerter Fall "behandelt".. könnte helfen

Beitrag "Hindernis ausweichender Roboter mit Arduino"

von René J. (renej)


Lesenswert?

Vielen Dank, das klingt beim ersten überfliegen vielversprechend. Ich 
werde die verlinkten Infos durcharbeiten und nachher hier posten, wie 
ich das gelöst habe (wenn ichs hinkrieg ;) ), falls jemand mal die 
gleiche Frage googlet und diesen Thread hier findet :D

von Falk B. (falk)


Lesenswert?

Siehe Multitasking.

Das geht auch mit Arduino ;-)

von René J. (renej)


Lesenswert?

So, ich habe jetzt etwas Zeit gehabt, das Ganze durchzulesen und es 
scheint so, als ob ich es hinbekommen habe. Allerdings vermute ich sehr 
stark, dass ich eher umständlich programmiert habe.

So sieht mein Ansatz für "alle 3 Sekunden zweimal kurz (100ms) 
aufblinken" aus. Macht man das so?
1
#define ledPin 17   // LED die blinken soll
2
int i = 0;          // counter, der pro interrupt incrementiert wird
3
4
void setup()
5
{
6
  pinMode(ledPin, OUTPUT);
7
  
8
  // initialize timer1 
9
  noInterrupts();           // disable all interrupts
10
  TCCR1A = 0;
11
  TCCR1B = 0;
12
  TCNT1  = 0;
13
  
14
  // 16MHz/256 = 62500 für eine Sekunde, 6250 fuer 100ms
15
  OCR1A = 6250;             // compare match register 16MHz/256/2Hz  
16
  TCCR1B |= (1 << WGM12);   // CTC mode
17
  TCCR1B |= (1 << CS12);    // 256 prescaler 
18
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
19
  interrupts();             // enable all interrupts
20
}
21
22
ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
23
{
24
   // Mein Interrupt-Code, der jede 100ms ausgeführt wird:
25
   
26
   // wenn 100ms, dann LED an
27
   if(i==1) digitalWrite(ledPin, ! digitalRead(ledPin));
28
   
29
   // wenn 200ms, dann LED aus
30
   if(i==2) digitalWrite(ledPin, ! digitalRead(ledPin));
31
   
32
   // wenn 300ms, dann LED an
33
   if(i==3) digitalWrite(ledPin, ! digitalRead(ledPin));
34
   
35
   // wenn 400ms, dann LED aus
36
   if(i==4) digitalWrite(ledPin, ! digitalRead(ledPin));
37
   
38
   // nach 30x 100ms wieder von vorne anfangen
39
   if(i>=30) i=0;
40
   
41
   i++;  // pro 100ms 1x hochzählen
42
}
43
44
void loop()
45
{
46
  
47
 // Als Vergleich geht im Hauptprogramm alle 3 Sekunden eine andere LED an und aus.
48
 TXLED0; 
49
 delay(3000);              // wait for a second
50
 TXLED1;
51
 delay(3000);              // wait for a second
52
}

von BirgerT (Gast)


Lesenswert?

Nun ja.. Nee

Geht es Dir um die Interrupt Programmierung oder um das "Multitasking"?

Die verlinkten Arduino Beispiele verwenden doch schon die Systemfunktion 
millis();
warum zapfst Du die nicht an?

Hast Du das auch gelesen (und verstanden)?
https://learn.adafruit.com/multi-tasking-the-arduino-part-1

Da ist zwar auch noch der Wurm drin - nach einem Überlauf alle 2^32ms 
wird's einen Aussetzer geben, aber bei LEDs?!

von Falk B. (falk)


Lesenswert?

@ René J. (renej)

>scheint so, als ob ich es hinbekommen habe. Allerdings vermute ich sehr
>stark, dass ich eher umständlich programmiert habe.

In der Tat.

>So sieht mein Ansatz für "alle 3 Sekunden zweimal kurz (100ms)
>aufblinken" aus. Macht man das so?

Nein. Lies den Artikel Multitasking noch einmal und mach es richtig. 
Nebenbei solltest du dich auch mit dem switch Konstrukt beschäftigen, 
das verbessert die Struktur und Lesbarkeit deines Programms.

von René J. (renej)


Lesenswert?

Der Artikel "Multitasking" ist für mich wie chinesisch zu lesen.

Beispiel:
1
    UBRRH = UBRR_VAL >> 8;
2
    UBRRL = UBRR_VAL & 0xFF;
3
    UCSRB = (1<<RXEN) | (1<<TXEN);

ja ne is klar :D Für mich sind Interrupts schon nicht leicht zu 
verstehen, da sind Variablennamen wie UBRRH, UBRRL und UCSRB natürlich 
unheimlich hilfreich ;)

Das Adafruit-Tutorial ist eher mein Ding, ich werds einfach noch 2-3mal 
durchackern... Irgendwann muss der Groschen ja fallen :D

von Falk B. (falk)


Lesenswert?

@ René J. (renej)

>Der Artikel "Multitasking" ist für mich wie chinesisch zu lesen.

Weil du dich auf Nebensächlichkeiten konzentrierst!

>    UBRRH = UBRR_VAL >> 8;
>    UBRRL = UBRR_VAL & 0xFF;
>    UCSRB = (1<<RXEN) | (1<<TXEN);

Das ist die Initialisierung des UARTs per "Hand". Darum muss man sich 
beim Arduino nicht kümmern.

>Das Adafruit-Tutorial ist eher mein Ding, ich werds einfach noch 2-3mal
>durchackern... Irgendwann muss der Groschen ja fallen :D

Du musst "nur" das Prinzip verstehen. Ein Timer erzeugt in regelmäßigen 
Abständen ein Signal mittels einer Variablen (Flag). In diesem 
gleichmäßigen Zeitraster werden Funktionen aufgerufen, welche eine 
Statemachine abarbeiten und damit diverse Dinge tun. Der "Trick" 
lautet, niemals ein delay() zu nutzen und auch nie auf ein Ereignis zu 
warten, sondern nur bereits eingetroffene Ereignisse zu bearbeiten.

Steht alles im Artikel. Der Adafruit-Artikel macht fast das Gleiche, nur 
dass er es etwas "cooler" in einer Memberfunktion einer Klasse packt 
(C++), im Gegensatz zum einfachen Beispiel in old school C im Artikel 
hier.

Lass das erstmal mit der C++ Klasse, mach es erstmal einfach in C. Die 
Fehlerwahrscheinlichkeit ist geringer für einen Anfänger wie dich.

von EGS T. (egs_ti)


Lesenswert?

Dann biste einfach noch nicht so weit. Ne Pause soll auch schonmal 
geholfen haben. Aber verstehen musste das schon selber. Und viel lesen.

von René J. (renej)


Lesenswert?

Mein Problem ist ja, dass ich mehrmals kurz blinken will und dann 
erstmal nicht mehr.
Einen Interruptaufruf hingegen erhalte ich ja regelmäßig.

Und ich habe ja versucht, mit der Variablen i eine statemachine 
einzuführen:
1
   // Mein Interrupt-Code, der jede 100ms ausgeführt wird:
2
   
3
   // wenn 100ms, dann LED an
4
   if(i==1) digitalWrite(ledPin, ! digitalRead(ledPin));
5
   
6
   // wenn 200ms, dann LED aus
7
   if(i==2) digitalWrite(ledPin, ! digitalRead(ledPin));
8
   
9
   // wenn 300ms, dann LED an
10
   if(i==3) digitalWrite(ledPin, ! digitalRead(ledPin));
11
   
12
   // wenn 400ms, dann LED aus
13
   if(i==4) digitalWrite(ledPin, ! digitalRead(ledPin));
14
   
15
   // nach 30x 100ms wieder von vorne anfangen
16
   if(i>=30) i=0;
17
   
18
   i++;  // pro 100ms 1x hochzählen

Der Gedanke dabei war, dass NUR EINE if-Schleife pro Interruptvorgang 
durchgearbeitet und i inkrementiert wird. Also eine sehr kurze ISR.

Ich will ja irgendwann auch vllt. anders blinken, kurz und lang 
gemischt, daher die 3 Sekunden lange Zeit und 30 states.


Ich kann jetzt ja nicht einfach nur alle 3 Sekunden die ISR aufrufen und 
dann die blink-Codes ausführen, dann blockier ich ja den Rest des 
Programms für die Dauer die ich blinke...

von Falk B. (falk)


Lesenswert?

@René J. (renej)

>Mein Problem ist ja, dass ich mehrmals kurz blinken will und dann
>erstmal nicht mehr.
>Einen Interruptaufruf hingegen erhalte ich ja regelmäßig.

Auch das ist kein Problem. Man muss nur einfach irgendwann mit dem 
Zählen aufhören.

etwa so

if (i<100) i++;  // zählt nur bis 100

>   // Mein Interrupt-Code, der jede 100ms ausgeführt wird:

Eben DORT gehört er NICHT hin! Das ist in beiden Beispielen klar 
sichtbar!

>Der Gedanke dabei war, dass NUR EINE if-Schleife pro Interruptvorgang

Es gibt keine If-Schleife, sondern nur eine If-Abfrage.

>Ich will ja irgendwann auch vllt. anders blinken, kurz und lang
>gemischt, daher die 3 Sekunden lange Zeit und 30 states.

Ja und?

>Ich kann jetzt ja nicht einfach nur alle 3 Sekunden die ISR aufrufen und
>dann die blink-Codes ausführen, dann blockier ich ja den Rest des
>Programms für die Dauer die ich blinke...

Du hast das Prinzip noch nicht verstanden. Nochmal lesen und nachdenken.

von René J. (renej)


Lesenswert?

Falk B. schrieb:

>
> Eben DORT gehört er NICHT hin! Das ist in beiden Beispielen klar
> sichtbar!

Also dann eher so?

1
#define ledPin 17   // LED die blinken soll
2
int i = 1;          // counter, der pro interrupt incrementiert wird
3
4
void setup()
5
{
6
  pinMode(ledPin, OUTPUT);
7
  digitalWrite(ledPin, LOW);
8
  
9
  // initialize timer1 
10
  noInterrupts();           // disable all interrupts
11
  TCCR1A = 0;
12
  TCCR1B = 0;
13
  TCNT1  = 0;
14
  
15
  // 16MHz/256 = 62500 für eine Sekunde, 6250 fuer 100ms
16
  OCR1A = 6250;             // compare match register 16MHz/256/2Hz  
17
  TCCR1B |= (1 << WGM12);   // CTC mode
18
  TCCR1B |= (1 << CS12);    // 256 prescaler 
19
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
20
  interrupts();             // enable all interrupts
21
}
22
23
ISR(TIMER1_COMPA_vect) {         // timer compare interrupt service routine
24
   // Mein Interrupt-Code, der jede 100ms ausgeführt wird   
25
   i++;            // pro 100ms 1x hochzählen
26
   if(i>=30) i=0;  // nach 30x 100ms wieder von vorne anfangen
27
}
28
29
void blinken () {
30
  
31
  switch (i) {
32
    case 1:  // 100ms
33
       digitalWrite(ledPin, LOW);   // LED1 an
34
       TXLED1;                      // LED2 an
35
       break;
36
    case 2:
37
       digitalWrite(ledPin, HIGH);  // LED1 aus
38
       break;
39
    case 3:
40
       digitalWrite(ledPin, LOW);   // LED1 an
41
       break;
42
    case 4:
43
       digitalWrite(ledPin, HIGH);  // LED1 aus
44
       break;
45
    case 20:
46
       TXLED0;                      // LED2 aus
47
       break;
48
  }
49
}
50
51
52
void loop() {
53
54
blinken();
55
  
56
}

Aber woher weiß ich dann, ob blinken() immer rechtzeitig aufgerufen 
wird? Wenn mein Programm später mal größer wird und der interrupt 2x 
aufgerufen wird, bevor die mainfunktion einmal durch ist, zerhauts mir 
ja das blinken. Oder sind die 100ms SOOOOOOOO viel Zeit, dass das nicht 
passieren wird?

von OldMan (Gast)


Lesenswert?

René J. schrieb:
> 100ms SOOOOOOOO viel Zeit

Ja, 100ms sind fast eine Ewigkeit.
Bei 1 MHz Takt benötigt der AVR 1us pro Befehl (auf Maschinencodeebene).
D.h. Du hast Zeit für 100.000 Befehle in den 100ms.
Das sollte ausreichend sein.

von BirgerT (Gast)


Lesenswert?

René J. schrieb:

> Also dann eher so?
>
>

Jau.. schon besser, aber eigentlich noch immer nicht, was wir Dir gerne 
"beigebracht" hätten..

> Aber woher weiß ich dann, ob blinken() immer rechtzeitig aufgerufen
> wird? Wenn mein Programm später mal größer wird und der interrupt 2x
> aufgerufen wird, bevor die mainfunktion einmal durch ist, zerhauts mir
> ja das blinken. Oder sind die 100ms SOOOOOOOO viel Zeit, dass das nicht
> passieren wird?

Wenn auf delays verzichtet wird, ja

Und noch etwas, damit's funktioniert
1
volatile int i = 1;      // counter, der pro interrupt incrementiert wird
2
:
3
ISR(TIMER1_COMPA_vect) {         // timer compare interrupt service routine
4
   // Mein Interrupt-Code, der jede 100ms ausgeführt wird   
5
   i++;            // pro 100ms 1x hochzählen
6
//   if(i>=30) i=0;  // nach 30x 100ms wieder von vorne anfangen
7
}
8
9
void blinken () {
10
  
11
  switch (i) {
12
    case 1:  // 100ms
13
       digitalWrite(ledPin, LOW);   // LED1 an
14
       TXLED1;                      // LED2 an
15
       break;
16
    case 2:
17
       digitalWrite(ledPin, HIGH);  // LED1 aus
18
       break;
19
    case 3:
20
       digitalWrite(ledPin, LOW);   // LED1 an
21
       break;
22
    case 4:
23
       digitalWrite(ledPin, HIGH);  // LED1 aus
24
       break;
25
    case 20:
26
       TXLED0;                      // LED2 aus
27
       break;
28
29
    case 30:
30
       i=0;  // nach 30x 100ms wieder von vorne anfangen
31
       break;
32
33
  }
34
}

Mit dem Adafruit Beispiel könntest Du die LEDs auch im 10ms- oder 25ms- 
oder 88ms-Takt blinken lassen.

Ich verwende zwar auch einen anderen Ansatz, bei dem die Mainloop immer 
alle 100ms durchgeackert wird, und den Timer1 für einen 1ms Interrupt, 
in den ich 10 Funktionen "einklinken" kann, die dann alle 10ms 
aufgerufen werden.

Wenn's Dich interessiert:
http://www.roboter.cc/index.php?option=com_nicaiwci&view=project&Itemid=41&projectid=3743

Und ich tue mich auch schwer, auf das augenscheinlich bessere "System 
Adafruit" umzusteigen.
Auch einen gut kommentierten Code findest Du auf der rp6 Site von arexx; 
dort werden sogenannte "stopwatches" verwendet.

von Falk B. (falk)


Lesenswert?

@ René J. (renej)

>Also dann eher so?

Besser, aber noch nicht gut. In die ISR kommt nur das Setzen des Flags, 
wie im Beispiel gezeigt. Der Zähler ghört in die einzelnen Funktionen!

1
volatile uint8_t timer_flag;
2
3
ISR(TIMER1_COMPA_vect) {         // timer compare interrupt service routine
4
   timer_flag=1;
5
}
6
7
void blinken () {
8
9
  static uint8_t i;
10
  
11
  switch (i) {
12
    case 1:  // 100ms
13
       digitalWrite(ledPin, LOW);   // LED1 an
14
       TXLED1;                      // LED2 an
15
       break;
16
    case 2:
17
       digitalWrite(ledPin, HIGH);  // LED1 aus
18
       break;
19
    case 3:
20
       digitalWrite(ledPin, LOW);   // LED1 an
21
       break;
22
    case 4:
23
       digitalWrite(ledPin, HIGH);  // LED1 aus
24
       break;
25
    case 20:
26
       TXLED0;                      // LED2 aus
27
       break;
28
  }
29
30
   i++;            // pro 100ms 1x hochzählen
31
   if(i>=30) i=0;  // nach 30x 100ms wieder von vorne anfangen
32
}
33
34
void loop() {
35
  if (timer_flag) {
36
    timer_flag = 0;
37
    blinken();
38
  }
39
}

>Aber woher weiß ich dann, ob blinken() immer rechtzeitig aufgerufen
>wird?

Kann man direkt während der Laufzeit prüfen.

https://www.mikrocontroller.net/articles/Multitasking#Verbesserter_Ansatz_mit_Timer

"Es kann leicht im realen System geprüft werden, ob die Laufzeit der 
Tasks klein genug ist, um den Anforderungen des Timers zu genügen.

Diese Überprüfung kann an zwei Stellen durchgeführt werden. "


Oder durch eine Simulation oder Messung.

https://www.mikrocontroller.net/articles/Interrupt#Wie_lange_dauert_meine_Interruptroutine.3F

Hier geht es zwar um die Messung der Zeit der Interruptroutine, die ist 
in diesem Beispiel extrem kurz. Aber das Gleiche kann man natürlich auch 
mit normalen Funktionen machen.

>ja das blinken. Oder sind die 100ms SOOOOOOOO viel Zeit, dass das nicht
>passieren wird?

Das auch.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.