Forum: Mikrocontroller und Digitale Elektronik ISR-Handler: Probleme bei Wertzuweisung von volatile Variable


von Chris L. (Gast)


Angehängte Dateien:

Lesenswert?

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
volatile uint8_t flag = 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
    return EXIT_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.

von Stefan F. (Gast)


Lesenswert?

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.

von Chris L. (Gast)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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.

von Mario M. (thelonging)


Lesenswert?

Lass mal das Toggeln von PA3 in der ISR weg. Tut sich dann was an PA2?

von Rolf M. (rmagnus)


Lesenswert?

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?

von Michael D. (nospam2000)


Lesenswert?

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

von Chris L. (Gast)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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.

von Thomas E. (thomase)


Lesenswert?

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.

von Chris L. (Gast)


Lesenswert?

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)

von Michael D. (nospam2000)


Lesenswert?

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

: Bearbeitet durch User
von Chris L. (Gast)


Lesenswert?

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 ^^

von Chris L. (Gast)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

Hat der Mega64 nicht die m103 Fuse?

von Chris L. (Gast)


Lesenswert?

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.

von mIstA (Gast)


Lesenswert?

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.

von foobar (Gast)


Lesenswert?

Du weißt schon, dass bei diesen Zeilen
1
    // 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.

von Stefan F. (Gast)


Lesenswert?

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.

von foobar (Gast)


Lesenswert?

> 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.

von Oliver S. (oliverso)


Lesenswert?

Chris L. schrieb:
> 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.

Problem gelöst, Ende der Diskussion...

https://www.mikrocontroller.net/articles/AVR_Checkliste#Besonderheiten_bei_ATmega128_und_seinen_Derivaten_im_64-Pin-Geh.C3.A4use

Oliver

von Stefan F. (Gast)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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?

von foobar (Gast)


Lesenswert?

> 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.

von Stefan F. (Gast)


Lesenswert?

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?

von Chris L. (Gast)


Lesenswert?

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.A4use

Stefan ⛄ 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 =)

von foobar (Gast)


Lesenswert?

> 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.

von Stefan F. (Gast)


Lesenswert?

foobar schrieb:
> Evtl könnte ja jemand den Absatz im Artikel
> auf folgendes ändern

Guter Vorschlag. Mache das.

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.