Hallo zusammen,
ich habe nach langer Zeit beschlossen, mein eingerostetes Wissen über
uC-Programmierung aufzufrischen (für kleinere private Projekte).
Grundlegendes C und Assemblerwissen vorhanden.
Gestern bin ich auf ein Problem gestoßen, welches ich weder verstehen
noch lösen konnte: Ich habe einen Timer konfiguriert, damit er alle
100ms einen Interrupt auslöst. Im Interrupt sollen nur Flags gesetzt
werden, auf die in der main-Loop reagiert werden soll.
Hier mal den Code auf das Nötigste eingedampft:
Code:
1
#include<avr/io.h>
2
#include<stdlib.h>
3
#include<util/delay.h>
4
#include<avr/interrupt.h>
5
#include<stdint.h>
6
#include<avr/cpufunc.h>
7
8
volatileuint8_tflag=0;
9
10
ISR(TIMER1_COMPA_vect){
11
// debug: toggle LED at PA3
12
PORTA^=(1<<PA3);
13
14
flag|=0xFF;
15
}
16
17
int
18
main(void)
19
{
20
// set up PA0/1/2/3 as outputs
21
DDRA|=(1<<PA3)|(1<<PA2)|(1<<PA1)|(1<<PA0);
22
23
// set prescaler to 256 -> Timer runs with 16000000/256 = 62500 Hz
24
TCCR1B|=(1<<CS12)|(0<<CS11)|(0<<CS10);
25
26
// enable CTC mode 4 on Timer1
27
TCCR1B|=(0<<WGM13)|(1<<WGM12);
28
TCCR1A|=(0<<WGM11)|(0<<WGM10);
29
30
// set timer resolution to 6250 - 1 = 6249 to fire interrupt every 100ms
31
OCR1A=6249;
32
33
// enable TIMER1_COMPA interrupt mask
34
TIMSK|=(1<<OCIE1A);
35
36
// enable global interrupts
37
sei();
38
39
// debug: turn on LED at PA0
40
PORTA|=(1<<PA0);
41
42
while(1)
43
{
44
45
// debug: turn on LED at PA1
46
PORTA|=(1<<PA1);
47
48
// flag should be set in ISR
49
if(flag)
50
{
51
//reset flag
52
flag=0;
53
54
// debug: toggle LED at PA2
55
PORTA^=(1<<PA2);
56
}
57
}
58
59
returnEXIT_SUCCESS;
60
}
Meine Erwartung ist, dass die LEDs an PA0 und PA1 aufleuchten und die
LEDs an PA2 und PA3 regelmäßig blinken.
Das beobachtete Verhalten ist jedoch, dass die LEDs an PA0 und PA1
leuchten, die LED an PA3 blinkt, aber die LED an PA2 bleibt dunkel. (Die
LED funktioniert aber, das habe ich schon getestet).
Ich kann mir das Verhalten jedoch nicht erklären: Die Interrupt-Routine
scheint regelmäßig ausgelöst zu werden (LED an PA3 blinkt), aber das
'flag' scheint nicht gesetzt zu werden.
Habe schon im Assembler-Listing geschaut, das 'flag' wird wirklich als
volatile behandelt (LDS / STS).
Für jede Hilfe oder Input bin ich dankbar.
Viele Grüße
Chris L.
Erstmal: Um welchen Mikrocontroller geht es? Den ATmega64?
Aber ich muss dich mal loben, du bist einer der wenigen, die gleich im
Eröffnungsbeitrag den Quelltext, das erwartete Verhalten und das
beobachtete Verhalten beschreiben.
Chris L. schrieb:> Meine Erwartung ist, dass ...> die LEDs an PA2 und PA3 regelmäßig blinken.
Du toggelst in der ISR() PA3 und in main() PA2. Sieht soweit gut aus.
Ich sehe da keinen Fehler.
Hast du vielleicht vergessen, AVCC anzuschließen?
ich würde mal zur Gegenprobe einfach alle LED's einschalten - ohne
Timer, ohne Schleifen.
Stefan ⛄ F. schrieb:> Erstmal: Um welchen Mikrocontroller geht es? Den ATmega64?>> Aber ich muss dich mal loben, du bist einer der wenigen, die gleich im> Eröffnungsbeitrag den Quelltext, das erwartete Verhalten und das> beobachtete Verhalten beschreiben.>> Chris L. schrieb:>> Meine Erwartung ist, dass ...>> die LEDs an PA2 und PA3 regelmäßig blinken.>> Du toggelst in der ISR() PA3 und in main() PA2. Sieht soweit gut aus.> Ich sehe da keinen Fehler.>> Hast du vielleicht vergessen, AVCC anzuschließen?>> ich würde mal zur Gegenprobe einfach alle LED's einschalten - ohne> Timer, ohne Schleifen.
Hey Stefan,
klar, hier noch ein paar Informationen, die gefehlt haben:
Controller: ATMega64A
Board: So ein fertiges China Board ohne uC oder Schaltplan; uC separat
gekauft und verlötet.
Compiler: AVR-GCC 5.3.0 mit avr-libc 2.0.0
Wegen der Gegenprobe: Ich habe die LEDs getestet, alle funktionieren.
Bevor ich mit Timer und Interrupts angefangen habe, habe ich ein kleines
Lauflicht mit delays implementiert, das hat funktioniert.
Kleiner Nachtrag:
Setze ich das 'flag' manuell aus der main-loop, springt er auch in den
if-Block.
Habt ihr weitere Ideen, wie ich das Problem weiter eingrenzen könnte?
Viele Grüße
Chris L.
Chris L. schrieb:> Habt ihr weitere Ideen, wie ich das Problem weiter eingrenzen könnte?
Nö. Das ist der Moment, wo ich den Debugger aus der Mottenkiste holen
würde und das Programm Zeile für Zeile durchsteppen würde. Manchmal
sieht man dabei Fehler, die man beim Betrachten des Quelltextes nicht
gesehen hat.
Falls du keinen Debugger hast, kannst du das vielleicht im Simulator des
AVR Studio austesten.
Ich hab mir's genau angeschaut und kann keinerlei Grund sehen, warum es
nicht funktionieren sollte.
Klingt vielleicht blöd, aber hast du auch so Trivialitäten
ausgeschlossen, wie z.B. dass du vielleicht das falsche Binary auf den
µC flashst - irgendeinen alten Stand statt das gerade frisch compilerte
oder sowas?
Chris L. schrieb:> Habt ihr weitere Ideen, wie ich das Problem weiter eingrenzen könnte?
In der Schleife machst du ein Read-Modify-Write welches durch die ISR
unterbrochen werden kann und ebenfalls auf PORTA schreibt:
main()
// debug: toggle LED at PA2
PORTA ^= (1 << PA2);
11a: 8b b3 in r24, 0x1b ; 27
11c: 89 27 eor r24, r25
11e: 8b bb out 0x1b, r24 ; 27
ISR:
// debug: toggle LED at PA3
PORTA ^= (1 << PA3);
c2: 9b b3 in r25, 0x1b ; 27
c4: 88 e0 ldi r24, 0x08 ; 8
c6: 89 27 eor r24, r25
c8: 8b bb out 0x1b, r24 ; 27
Du solltest die Interrupts in main() kurz sperren um race conditions zu
vermeiden:
cli();
PORTA ^= (1 << PA2);
sei();
Das geht übrigens auch atomar ohne read/xor/write (zumindest bei
ATMega328) und spart auch cli/sei, siehe Datasheet:
"Writing a logic one to PINxn toggles the value of PORTxn, independent
on the value of DDRxn. Note that the SBI instruction can be used to
toggle one single bit in a port."
Ob das mit Deinem Problem zu tun hat, kann ich nicht abschätzen, ist
eher ein zusätzliches Problem welches sporadisch auftreten kann.
Michael
Stefan ⛄ F. schrieb:> Chris L. schrieb:>> Habt ihr weitere Ideen, wie ich das Problem weiter eingrenzen könnte?>> Nö. Das ist der Moment, wo ich den Debugger aus der Mottenkiste holen> würde und das Programm Zeile für Zeile durchsteppen würde. Manchmal> sieht man dabei Fehler, die man beim Betrachten des Quelltextes nicht> gesehen hat.>> Falls du keinen Debugger hast, kannst du das vielleicht im Simulator des> AVR Studio austesten.
Guter Punkt. Habe leider keinen Debugger, sondern nur einen dieser
usbasp-clone (mkII clone), darüber geht HW-Debugging ja nicht, oder? AVR
Studio nutze ich nicht, aber werde am Wochenende mal simulavr probieren.
Mario M. schrieb:> Lass mal das Toggeln von PA3 in der ISR weg. Tut sich dann was an PA2?
Gerade getestet, hat leider nichts geändert (außer, dass die LED an PA3
nicht mehr blinkt ;-P )
Rolf M. schrieb:> Klingt vielleicht blöd, aber hast du auch so Trivialitäten> ausgeschlossen, wie z.B. dass du vielleicht das falsche Binary auf den> µC flashst - irgendeinen alten Stand statt das gerade frisch compilerte> oder sowas?
Klingt nicht blöd, war auch eine meiner Vermutungen. Habe schon mehrfach
alles außer Makefile und *.c gelöscht und alles neu bauen lassen - ohne
Erfolg.
Die ATMega64A habe ich im 5er Pack aus China über AliExpress. Könnten
die Chips eventuell Schrott sein? Oder beim Löten verbrutzelt? Habe zwei
Boards bestückt und beide uC verhalten sich identisch. Die Chips melden
sich auch mit der korrekten DeviceSignature beim Programmer an.
Chris L. schrieb:> darüber geht HW-Debugging ja nicht, oder?
Nee, geht nicht
Chris L. schrieb:> Die ATMega64A habe ich im 5er Pack aus China über AliExpress. Könnten> die Chips eventuell Schrott sein? Oder beim Löten verbrutzelt?
Unwahrscheinlich, denn du hast ja alle LEDs per Software einschalten
können, und die eine LED die gerade nicht geht, soll ja immer noch per
Software geschaltet werden.
Michael D. schrieb:> In der Schleife machst du ein Read-Modify-Write welches durch die ISR> unterbrochen werden kann und ebenfalls auf PORTA schreibt:
Ein durchaus verfolgenswerter Ansatz.
Der mögliche Fehler entsteht aber nicht an der genannten Stelle, sondern
hier:
Chris L. schrieb:> PORTA |= (1 << PA1);
Da hält sich das Programm die meiste Zeit auf. Entweder sperrt man dafür
die Interrupts oder setzt diese Anweisung in den zeitgesteuerten Teil
der Schleife. Die Anweisung macht an dieser Stelle sowieso keinen Sinn.
Was in dem if(flag)-Teil passiert ist praktisch vom Timing her
geschützt.
Michael D. schrieb:> Chris L. schrieb:>> Habt ihr weitere Ideen, wie ich das Problem weiter eingrenzen könnte?>> In der Schleife machst du ein Read-Modify-Write welches durch die ISR> unterbrochen werden kann und ebenfalls auf PORTA schreibt:> main()> // debug: toggle LED at PA2> PORTA ^= (1 << PA2);> 11a: 8b b3 in r24, 0x1b ; 27> 11c: 89 27 eor r24, r25> 11e: 8b bb out 0x1b, r24 ; 27>> ISR:> // debug: toggle LED at PA3> PORTA ^= (1 << PA3);> c2: 9b b3 in r25, 0x1b ; 27> c4: 88 e0 ldi r24, 0x08 ; 8> c6: 89 27 eor r24, r25> c8: 8b bb out 0x1b, r24 ; 27>> Du solltest die Interrupts in main() kurz sperren um race conditions zu> vermeiden:> cli();> PORTA ^= (1 << PA2);> sei();>> Das geht übrigens auch atomar ohne read/xor/write (zumindest bei> ATMega328) und spart auch cli/sei, siehe Datasheet:> "Writing a logic one to PINxn toggles the value of PORTxn, independent> on the value of DDRxn. Note that the SBI instruction can be used to> toggle one single bit in a port.">> Ob das mit Deinem Problem zu tun hat, kann ich nicht abschätzen, ist> eher ein zusätzliches Problem welches sporadisch auftreten kann.>> Michael
Danke dir, habe ich gerade getestet, wie es sich mit gesperrten
Interrupts während dem Schreiben nach PORTA verhält / leider ohne
Erfolg.
1
cli();
2
PORTA^=(1<<PA2);
3
sei();
Danke für den Tipp mit dem Togglen der Pins! Kenne das Feature aus der
Automotive / ATmegaxx8 Reihe, ist aber für den ATmega64A nicht verfügbar
(zumindest nicht dokumentiert)
Thomas E. schrieb:> Der mögliche Fehler entsteht aber nicht an der genannten Stelle, sondern> hier:> Chris L. schrieb:>> PORTA |= (1 << PA1);>> Da hält sich das Programm die meiste Zeit auf.
Das stimmt prinzipiell schon, allerdings hat der Compiler laut Listing
aus dieser Zeile eine atomare Operation gemacht:
// debug: turn on LED at PA1
PORTA |= (1 << PA1);
10c: d9 9a sbi 0x1b, 1 ; 27
Lass es mal weg, vielleicht beeinflusst das ja tatsächlich den Ablauf.
Michael
Stefan ⛄ F. schrieb:> Chris L. schrieb:>> Die ATMega64A habe ich im 5er Pack aus China über AliExpress. Könnten>> die Chips eventuell Schrott sein? Oder beim Löten verbrutzelt?>> Unwahrscheinlich, denn du hast ja alle LEDs per Software einschalten> können, und die eine LED die gerade nicht geht, soll ja immer noch per> Software geschaltet werden.
Ja, stimmt. Beim verbrutzelt meinte ich eher "Den internen RAM
verbrutzelt"...Ist das erste mal, dass ich den RAM bei den neuen Chips
genutzt habe.
Aber ja, war eher der verzweifelte Versuch eine Erklärung zu finden ^^
Michael D. schrieb:> Das stimmt prinzipiell schon, allerdings hat der Compiler laut Listing> aus dieser Zeile eine atomare Operation gemacht:>> // debug: turn on LED at PA1> PORTA |= (1 << PA1);> 10c: d9 9a sbi 0x1b, 1 ; 27>> Lass es mal weg, vielleicht beeinflusst das ja tatsächlich den Ablauf.>> Michael
Leider auch erfolglos, aber gute Idee
Peter D. schrieb:> Hat der Mega64 nicht die m103 Fuse?
Ja hat er; gerade gesehen, dass die standardmäßig auch 'programmed' ist
o.O
Werde später den Kompatibilitätsmodus ausschalten und erneut testen.
Chris L. schrieb:> Ja, stimmt. Beim verbrutzelt meinte ich eher "Den internen RAM> verbrutzelt"...Ist das erste mal, dass ich den RAM bei den neuen Chips> genutzt habe.
Klingt zwar irgendwie unwahrscheinlich, aber kannst Du ja mal testen,
indem Du für Dein Flag 0xFF als default nimmst und im Interrupt auf 0
setzt.
// set prescaler to 256 -> Timer runs with 16000000/256 = 62500 Hz
2
TCCR1B|=(1<<CS12)|(0<<CS11)|(0<<CS10);
3
4
// enable CTC mode 4 on Timer1
5
TCCR1B|=(0<<WGM13)|(1<<WGM12);
6
TCCR1A|=(0<<WGM11)|(0<<WGM10);
das "0 << x" zwar schön dokumentiert, dass du das Bit nicht setzen
willst, es aber auch nicht explizit gelöscht wird - ist es schon 1,
bleibt es 1.
Das mit dem cli/sei in main um den "PORTA ^= x" wurde schon genannt
("^=" geht nicht atomar), sollte dann aber eher dazu führen, dass die
LED der ISR "spinnt".
Sonst seh ich keinen Programmfehler. Ich würd erstmal PA2 und PA3 in
ISR und main tauschen und schauen, ob nun die andere blinkt. Wenn du
ein Scope hast, mal an den Pins testen, ob die LEDs nicht doch mal sehr
kurz blinken. Ansonsten den Assemblercode posten.
Die ISR toggelt doch erst den Pin und setzt erst danach das Flag. Daraus
ergibt sich, dass sich die beiden Zugriffe aus der ISR und der
Hauptschleife nicht überlappen können - selbst wenn der Compiler das als
nicht-atomare Operation übersetzt hätte.
> Die ISR toggelt doch erst den Pin und setzt erst danach das Flag. Daraus> ergibt sich, dass sich die beiden Zugriffe aus der ISR und der> Hauptschleife nicht überlappen können - selbst wenn der Compiler das als> nicht-atomare Operation übersetzt hätte.
Die ISR ist immer atomar - es geht um das ^= in main, das von der ISR
unterbrochen wird:
1
main: read port(00)
2
toggle bit(01)
3
ISR: read port(00)
4
toggle bit(02)
5
write port(02)
6
write port(01)
Hier wird die LED der ISR nur für ein paar Takte kurz blitzen, bis sie
von main wieder gelöscht wird.
foobar schrieb:> Die ISR ist immer atomar - es geht um das ^= in main, das von der ISR> unterbrochen wird:
Ja aber das passiert doch erst 100ms später in nächsten Interrupt.
Oliver S. schrieb:> Problem gelöst, Ende der Diskussion...
Du bist da vermutlich auf der falschen Baustelle. Er hat weder ein
Problem mit ISP, noch mit den "besonderen" Ports.
Chris, hast du diese 103er Fuse gelöscht?
> Ja aber das passiert doch erst 100ms später in nächsten Interrupt.
Ja, wenn der Timer richtig programmiert ist ;-) Feuert er ständig ...
(oder main trödelt irgendwo rum - hier nicht der Fall)
Die Chance, dass das sein Problem ist, scheint mir eher gering (würde
die flasche LED betreffen). Ist aber ein genereller Fehler und sollte
man vermeiden.
foobar schrieb:> Ja, wenn der Timer richtig programmiert ist ;-)
Wenn das nicht der Fall wäre, hätte er es am Blinken von PA3 erkannt.
Chris, was ist mit der Fuse?
Peter D. schrieb:> Hat der Mega64 nicht die m103 Fuse?Oliver S. schrieb:> Problem gelöst, Ende der Diskussion...>> https://www.mikrocontroller.net/articles/AVR_Checkliste#Besonderheiten_bei_ATmega128_und_seinen_Derivaten_im_64-Pin-Geh.C3.A4useStefan ⛄ F. schrieb:> Chris, hast du diese 103er Fuse gelöscht?
Hey zusammen,
habe die M103C-Fuse gelöscht und das hat das Problem beseitigt.
Ich bin nochmals durch die Doku und das AssemblerListing gegangen und
konnte den Fehler nun nachvollziehen:
Das 'flag'-Byte wurde an Adresse 0x0100 gelegt, was in beiden Modi eine
gültige RAM-Adresse ist, jedoch hat der Compiler im Epilog den
StackPointer für den nicht-Kompatibilitätsmodus initialisiert (0x10FF),
was mit der M103C-Fuse im "External Ram" Adressbereich liegt. Ich gehe
also davon aus, dass am Ende der ISR das RETI einfach 0x0000 vom Stack
'geholt' hat und daher der uC einen Soft-Reset gestartet hat. Da ich den
PORTA über die Veroderung setze, wurde die LED dann auch nicht sofort
ausgeschaltet, sondern blieb bis zum Toggle aktiv, bis die ISR wieder
getroffen wurde. Das würde das Verhalten (auch das 50/50 Tastverhältnis
der LED) erklären.
Vielen Dank an alle, die mitgeholfen haben, das Problem zu lösen; auch
nach 10 Jahren uC-Abstinenz seid ihr immer noch das beste Forum =)
> jedoch hat der Compiler im Epilog den StackPointer für den> nicht-Kompatibilitätsmodus initialisiert (0x10FF),> was mit der M103C-Fuse im "External Ram" Adressbereich liegt.
Uh, fiese Falle.
Ich hatte mich schon gewundert, was in dem verlinkten Artikel die
Aussage mit dem RET sollte. Evtl könnte ja jemand den Absatz im Artikel
auf folgendes ändern:
> Dies hat zur Folge, dass sich an der Adresse, an der ein für den> ATmega64 oder ATmega128 geschriebenes Programm den Stack erwartet,> kein RAM befindet. Spätestens beim ersten RET-Befehl stürzt das> Programm ab.