Verständnisfrage Interrupt-Programmierung

#4179079
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...
#4179111
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.
#4180300
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
}
Gast #4180313
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?!
#4180321
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.
#4180392
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
#4180397
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.
#4180423
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...
#4180462
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.
#4180536
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?
Gast #4180569
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.
#4180687
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren