Hallo, ich habe vor mit dem Timer1 alle 10 ms eine Variable per
Interrupt hochzuzählen. In der Schleife soll dann alle 500 ms eine LED
umgeschaltet werden. Das funktioniert aber nicht. Es funktioniert nur,
wenn mehr Code abgearbeitet wird, hier die auskommentierte For-Schleife,
welche mindestens 17 Iterationen durchlaufen muss. Meine Vermutung ist,
dass die Variable zu schnell ausgelesen wird. Aber das macht ja keinen
Sinn, da die ISR ja abläuft, ohne dass die Variable ausgelesen wird. Wo
liegt hier mein Denkfehler?
Hier noch das Programm:
1
#include<avr/io.h>
2
#include<avr/interrupt.h>
3
4
#define F_CPU 16000000UL
5
6
// Ein- und Ausgänge definieren
7
#define LED PB0
8
9
// Globale Variablen
10
uint8_tgIR_sysTick_counter=0;
11
12
// ISR
13
ISR(TIMER1_COMPA_vect)
14
{
15
gIR_sysTick_counter++;
16
}
17
18
19
intmain()
20
{
21
// Setup ================================
22
cli();
23
24
// Ausgänge Setup
25
DDRB|=(1<<LED);
26
27
28
// Timer1 Mode CTC (Geht auf 0 wenn OCR1A erreicht) & Prescaler auf 256 (Bei 16 MHz wird alle 16 us hochgezählt)
29
TCCR1B|=(1<<WGM12)|(1<<CS12);
30
31
// Output Compare
32
OCR1A=625;// 16 us * 625 = 10 ms
33
34
// Timer1 Interrupt einstellen (Compare mit OCR1A)
Schreibe mal "volatile" for
> uint8_t gIR_sysTick_counter = 0;
Hintergrund: Die CPU kann auf Register schneller zugreifen, als auf RAM.
Deswegen wird der Wert innerhalb der while Schleife in ein Register
kopiert und dann nur noch das Register verwendet. Der Compiler ist nicht
so schlau zu berücksichtigen, dass die ISR dazwischen kommen kann und
den Wert ändern kann.
Mit dem Wort volatile zwingst du den Compiler dazu, bei jedem Zugriff
wirklich auf das RAM zuzugreifen, ohne Abkürzungen zu verwenden.
Normalerweise setzt man den systick aber nicht auch 0 zurück, sondern
lässt ihn durch laufen (samt Überlauf). In einem komplexeren Programm
stößt du bei deiner Methode ganz schnell auf zahlreiche Probleme.
Benutze stattdessen die Subtraktion, um festzustellen, ob 50 Ticks
stattgefunden haben:
vorher=gIR_sysTick_counter;
if (gIR_sysTick_counter - vorher > 50) {...}
Das funktioniert auch nach einem Überlauf korrekt.
In dem Zusammenhang könnte dieser Aufsatz für dich hilfreich sein:
http://stefanfrings.de/multithreading_arduino/index.html
Falls du deine Variable auf mehr als 8 Bit vergrößerst, schreibe dir
eine Hilfsfunktion die beim Lese-Zugriff Interrupts blockiert:
1
uint16_tget_systick()
2
{
3
uint16_tvalue;
4
cli();
5
value=gIR_sysTick_counter;
6
sei();
7
returnvalue;
8
}
Das sieht aufwändig aus, aber der Maschinencode der daraus generiert
wird, ist ziemlich kompakt.
So verhinderst du, dass ein Lesezugriff durch die ISR gestört wird, was
dazu führen würde, dass die oberen 8 Bit und die unteren nicht zusammen
passen. Frage nach, wenn du das weiter ausdiskutieren willst.
Hier tritt bei meinem Ansatz auch ein Problem auf, welches ich mir nicht
erklären kann.
1
uint8_tpast=0;
2
(...)
3
// Programm Loop
4
while(1)
5
{
6
7
if(gIR_sysTick_counter-past>=50)
8
{
9
PORTB^=(1<<LED);
10
past=gIR_sysTick_counter;
11
}
12
}
Die LED blinkt dann drei mal und leuchtet dann permanent. In past müsste
nach dem dritten Mal leuchten ja 250 stehen. Nach meiner Berechnung
sollte die nächste if(..) ausgeführt werden, wenn gIR_sysTick_counter
bei 44 ist. Funktioniert aber nicht. In past müsste doch dann eine 50
stehen, da ja beide unsigned sind.
Da bin ich jetzt überfragt. Das müsste eigentlich funktionieren.
Schaffe dir mal eine Möglichkeit zum Debuggen, oder zumindest
Log-Meldungen ausgeben zu können. Dann kannst du die Werte der Variablen
und auch das Ergebnis der Subtraktion kontrollieren.
Das "volatile" hast du aber noch drin, oder?
Korrektur: "in past müsste doch dann eine 50 stehen" ist falsch, ich
meinte natürlich das Ergebnis der Subtraktion.
Zu deiner Frage, ja, habe volatile stehen.
Hier nochmal der ganze Code:
1
#include<avr/io.h>
2
#include<avr/interrupt.h>
3
4
#define F_CPU 16000000UL
5
6
// Ein- und Ausgänge definieren
7
#define LED PB0
8
9
// Globale Variablen
10
volatileuint8_tgIR_sysTick_counter=0;
11
12
// ISR
13
ISR(TIMER1_COMPA_vect)
14
{
15
gIR_sysTick_counter++;
16
}
17
18
19
intmain()
20
{
21
uint8_tpast=0;
22
23
// Setup ================================
24
cli();
25
26
// Ausgänge Setup
27
DDRB|=(1<<LED);
28
29
30
// Timer1 Mode CTC (Geht auf 0 wenn OCR1A erreicht) & Prescaler auf 256 (Bei 16 MHz wird alle 16 us hochgezählt)
31
TCCR1B|=(1<<WGM12)|(1<<CS12);
32
33
// Output Compare
34
OCR1A=625;// 16 us * 625 = 10 ms
35
36
// Timer1 Interrupt einstellen (Compare mit OCR1A)
Ich habe den Fehler mit dem Debugger und diesem Code nachvollziehen
können:
1
#include<stdint.h>
2
#include<avr/io.h>
3
4
#define LED PB0
5
6
volatileuint8_tgIR_sysTick_counter=250;// damit es schneller geht
7
8
intmain()
9
{
10
uint8_tpast=0;
11
while(1)
12
{
13
if(gIR_sysTick_counter-past>=50)
14
{
15
PORTB^=(1<<LED);
16
past=gIR_sysTick_counter;
17
}
18
19
// simuliere Interrupts
20
gIR_sysTick_counter+=10;
21
}
22
}
Offenbar ist das Ergebnis des Ausdruckes "gIR_sysTick_counter - past"
ein 16 Bit Integer. Erzwinge 8 Bit so, dann geht es:
1
uint8_tdiff=gIR_sysTick_counter-past;
2
if(diff>=50)
Da ich solche Warteschleifen bisher immer nur mit 16 Bit oder größeren
Variablen programmiert habe, hatte ich das nicht auf dem Schirm.
Milli schrieb:> Korrektur: "in past müsste doch dann eine 50 stehen" ist falsch, ich> meinte natürlich das Ergebnis der Subtraktion.
Ist auch falsch, da müsste 250 drin stehen, vom vorherigen Moment wo die
LED zuletzt getoggelt wurde.
Danke, es funktioniert jetzt. Habe es auch mal mit 16-Bit Variablen und
der empfohlenen Funktion getestet. Da läuft es ohne Probleme, auch nach
Überlauf.
Ich würde mir das auch gerne noch mal mit dem Debugger ansehen, welche
IDE nutzt du dafür? Ich programmiere in VS Code mit Platformio, da geht
das Debuggen nicht so gut beim Atmega328P (oder?).
Stefan F. schrieb:> Ist auch falsch, da müsste 250 drin stehen, vom vorherigen Moment wo die> LED zuletzt getoggelt wurde.
Ja, in past müsste 250 stehen. Ich meinte im Ergebnis von
gIR_sysTick_counter - past = 44 - 250 = 50.
Milli schrieb:> Ich würde mir das auch gerne noch mal mit dem Debugger ansehen, welche> IDE nutzt du dafür?
Für solche kleinen Tests bevorzuge ich das alte AVR Studio in einer
virtuellen Maschine mit Windows XP.
Ernsthafte Programme compiliere ich unter Linux im Terminalfenster mit
einem Makefile. Als Editor nutze ich Qt Creator. Visual Studio Code tut
es auch gut.
Debuggen kannst du AVR nach meinem Kenntnisstand nur mit den IDEs von
Atmel/Microchip. Es gab mal Frickeleien mit Open-Source Tools (sorry,
falls sich da jemand betroffen fühlt), die habe ich aber nicht ans
Laufen gebracht.
Siehe http://stefanfrings.de/avr_tools/index.html