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
constintledPin=13;// Pin der LED
3
intalle_x_sekunden=5;// Intervallzeit in Sekunden
4
5
voidblinken(){// Funktion für Blink-Code
6
inti=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
voidsetup(){
18
pinMode(ledPin,OUTPUT);
19
Timer1.initialize(alle_x_sekunden*1000000);
20
Timer1.attachInterrupt(blinken);
21
}
22
23
voidloop(){}
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...
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.
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"
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
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
inti=0;// counter, der pro interrupt incrementiert wird
3
4
voidsetup()
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
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?!
@ 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.
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
@ 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.
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:
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...
@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.
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
voidblinken(){
30
31
switch(i){
32
case1:// 100ms
33
digitalWrite(ledPin,LOW);// LED1 an
34
TXLED1;// LED2 an
35
break;
36
case2:
37
digitalWrite(ledPin,HIGH);// LED1 aus
38
break;
39
case3:
40
digitalWrite(ledPin,LOW);// LED1 an
41
break;
42
case4:
43
digitalWrite(ledPin,HIGH);// LED1 aus
44
break;
45
case20:
46
TXLED0;// LED2 aus
47
break;
48
}
49
}
50
51
52
voidloop(){
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?
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.
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
volatileinti=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
voidblinken(){
10
11
switch(i){
12
case1:// 100ms
13
digitalWrite(ledPin,LOW);// LED1 an
14
TXLED1;// LED2 an
15
break;
16
case2:
17
digitalWrite(ledPin,HIGH);// LED1 aus
18
break;
19
case3:
20
digitalWrite(ledPin,LOW);// LED1 an
21
break;
22
case4:
23
digitalWrite(ledPin,HIGH);// LED1 aus
24
break;
25
case20:
26
TXLED0;// LED2 aus
27
break;
28
29
case30:
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.
@ 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
volatileuint8_ttimer_flag;
2
3
ISR(TIMER1_COMPA_vect){// timer compare interrupt service routine
4
timer_flag=1;
5
}
6
7
voidblinken(){
8
9
staticuint8_ti;
10
11
switch(i){
12
case1:// 100ms
13
digitalWrite(ledPin,LOW);// LED1 an
14
TXLED1;// LED2 an
15
break;
16
case2:
17
digitalWrite(ledPin,HIGH);// LED1 aus
18
break;
19
case3:
20
digitalWrite(ledPin,LOW);// LED1 an
21
break;
22
case4:
23
digitalWrite(ledPin,HIGH);// LED1 aus
24
break;
25
case20:
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
voidloop(){
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.