Forum: Mikrocontroller und Digitale Elektronik Anfängerproblem: Im Zustand "gefangen"


von Daniel J. (Gast)


Lesenswert?

Hallo,

zunächst mal muss ich dazu sagen, dass ich gerade erst angefangen habe, 
mit der Programmierung von AVRs, verzeiht mir daher bitte, wenn ich 
möglicherweise noch etwas grün wirke :-)

Ich habe hier das STK 500 mit einem ATmega 32. Nachdem ich mir die Timer 
angeschaut habe, beschäftige ich mich nun mit Interrupts im Allgemeinen 
und nutze dazu die Taster am STK. Über eine Zustandsmaschine möchte ich 
die Zustände "alle LEDs an", "LEDs laufen von hinten bis vorne durch" 
und "alle LEDs aus" realisieren. Den Code findet ihr unten.

Ihr könnt es euch denken: Wenn ich den Taster an PD2 drücke und wieder 
los lasse, passiert leider gar nichts. Durch die Simulation in AVR 
Studio konnte ich folgendes feststellen: Der ext. Interrupt wird 
grundsätzlich ausgelöst und die Variable "state" auch entsprechend 
verändert. Aber es scheint, als wäre das Programm in der "if(state == 
0)"-Verzweigung gefangen. Jedenfalls springt der Debugger nur zwischen 
den beiden Befehlen hin und her, obwohl "state" nicht 0 ist.

Könnte mir jemand dabei helfen, die Tomaten von den Augen zu bekommen? 
:-)

LG,
Daniel
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <inttypes.h>
4
5
int i    = 0;      // Timer Overflow counter
6
int state  = 0;      // Status
7
8
int main(void)
9
{
10
  DDRB  = 0xFF;      // Port B as output
11
  PORTB  = 0xFF;      // LEDs off
12
13
  DDRD  = 0x00;      // Port D as input
14
15
  MCUCR  |= 0x02;    // INT 0 falling edge
16
  GICR  |= 0x40;    // INT 0 enable
17
18
  TCCR0  = 0x05;      // Normal Timer Mode, Prescaler: 1024
19
20
  sei();
21
  
22
  while(1) {
23
    if(state == 0) {
24
      TIMSK &= ~(1 << TOIE0);
25
      PORTB = 0x00;
26
    }
27
    else if(state == 1) {
28
      PORTB = 0xFE;
29
      TIMSK |= (1 << TOIE0);
30
    }
31
    else {
32
      TIMSK &= ~(1 << TOIE0);
33
      PORTB = 0xFF;
34
    }
35
  }
36
}
37
38
ISR(TIMER0_OVF_vect)
39
{
40
  if(++i == 14) {
41
    if(PINB == 0x7F) {
42
      PORTB = 0xFE;
43
    }
44
    else {
45
      PORTB = ~(~PINB << 1);
46
    }
47
    
48
    i = 0;
49
  }
50
}
51
52
ISR(INT0_vect)
53
{
54
  if(state < 2) {
55
    state++;
56
  }
57
  else {
58
    state = 0;
59
  }
60
}

von Jens (Gast)


Lesenswert?

Setz mal volatile vor die Variablen.
volatile int i    = 0;      // Timer Overflow counter
volatile int state  = 0;      // Status

von Daniel J. (Gast)


Lesenswert?

Hey Jens,

vielen Dank, das funktioniert (und dadurch gleich den Fehler 
festgestellt, dass das Laufmuster im zweiten Zustand immer gleich zurück 
gesetzt wird ^^).
Kannst du mir erklären, was sich dadurch bei der Abarbeitung im 
Controller ändert?

LG
Daniel

von Jens (Gast)


Lesenswert?

Kann ich so genau auch nicht. Mit volatile zwingst du dem Compiler, dass 
die Variablen bei jedem aufruf auf dem Speicher geholt, verändert und 
wieder in den Speicher zurückgeschrieben wird usw.

Ich glaube ohne volatile erzeugt der ISR eine Kopie der Variable, die 
nur für den ISR gültig ist.

von Volkmar D. (volkmar)


Lesenswert?

Kurz gesagt, volatile teilt dem Compiler mit, das diese Variabel 
außerhalb des normalen Programmablaufs (zum Beispiel in einer ISR) 
verändert werden kann. Es ist zum Beispiel #1 in der FAQ: 
http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_volatile

Was mir noch aufgefallen ist:
Daniel J. schrieb:
> ISR(TIMER0_OVF_vect)
> {
>   if(++i == 14) {
>     if(PINB == 0x7F) {
>       PORTB = 0xFE;
>     }
>     else {
>       PORTB = ~(~PINB << 1);
>     }
>
>     i = 0;
>   }
> }

Hier solltest Du nicht PINB einlesen, denn damit wird der Wert an den 
Portpins eingelesen und der kann sich von dem Wert, den Du geschrieben 
hast, unterscheiden. Du verwendest den PORTB an dieser Stelle aber als 
Variable, dann solltest Du auch PORTB wieder einlesen:
1
> ISR(TIMER0_OVF_vect)
2
{
3
  if(++i == 14) {
4
    if(PORTB == 0x7F) {
5
      PORTB = 0xFE;
6
    }
7
    else {
8
      PORTB = ~(~PORTB << 1);
9
    }
10
    
11
    i = 0;
12
  }
13
}

von Volkmar D. (volkmar)


Lesenswert?

Jens schrieb:
> Kann ich so genau auch nicht. Mit volatile zwingst du dem Compiler, dass
> die Variablen bei jedem aufruf auf dem Speicher geholt, verändert und
> wieder in den Speicher zurückgeschrieben wird usw.

Richtig

> Ich glaube ohne volatile erzeugt der ISR eine Kopie der Variable, die
> nur für den ISR gültig ist.

Falsch. Ohne volatile erkennt der Compiler nicht, daß diese Variable 
außerhalb des 0815-Programmablaufs verändert wird, und optimiert 
entsprechend. Siehe FAQ-Link weiter oben.

von Christian Erker (Gast)


Lesenswert?

Der Compiler liest eine Variable nur neu ein, wenn er eine Änderung 
erwartet, ein kurzes Beispiel:

a = eingabewert();
b = a + 3;
c = a + 4;
d = a + 6;

Hier wird A nur einmal gelesen und dann 3 mal verarbeitet, im Normalfall 
geht dies problemlos gut.

Schlägt nun aber ein Interrupt dazwischen:


a = eingabewert();
b = a + 3;
  [ a = interruptwert; ]
c = a + 4;
d = a + 6;

Sind c und d, die EVENTUELL noch mit dem alten Wert von a arbeiten (es 
ist kein muss, wenn kein Register frei ist wird a auch vielleicht nach C 
nochmal gelesen und stimmt für d, das kann man nicht vorhersehen (es ist 
zwar deterministisch, also, das gleiche Programm sollte immer den 
gleichen Code erzeugen .. aber es gibt keine einfachen allgemeingültigen 
Regeln).

"volatile" erzwingt das die Variable vor jeder Verwendung neu gelesen 
wird UND nach jeder Änderung neu geschrieben, um dies zu vermeiden.

Beispiel zum schreiben:

a = 5;
{Haufen anderer Code der nichts mit A macht}
a = Eingabewert();
b = a + 3;

Hier wird a, zunächst offensichtlich, nur mit Eingabewert, nie mit 5 
beschrieben, da 5 nie verwendet wird. Möchte man aber z.B. das ein 
Interrupt während der Ausführung des Codeblocks zwischen den 2 
Zuweisungen etwas anderes macht (oft der Fall bei gemeinsam genutzter 
Hardware) und die 5 als Marker verwenden, ist auch hier "volatile" zu 
verwenden.

Gruß,
Christian

von Daniel J. (Gast)


Lesenswert?

Vielen Dank Jens und Volkmar!
Ich habe parallel ein wenig zu "volatile" gelesen und versucht aus dem 
Disassembly was herauszulesen, zumindest die Unterschiede zwischen 
beiden Varianten.

"state" verändert sich auf jeden Fall, das zeigt mit auch der Debugger 
an. Habe mir daher auch überlegt, dass der Compiler da wohl was weg 
optimiert und durch Volker weiß ich jetzt auch, warum er das macht (er 
betrachtet den ISR dabei nicht).

Danke auch für den Tipp mit der Verwendung von PINB!

von Daniel J. (Gast)


Lesenswert?

Dir natürlich auch ein Dankeschön, Christian. Super Erklärung, da wirds 
dann spätestens komplett klar! :-)

von Daniel J. (Gast)


Lesenswert?

Insofern erklärt sich auch, warum ich "volatile" bei "state" brauche, 
bei "i" hingegen nicht. Das hat nämlich vorher auch ohne funktioniert.

von Volkmar D. (volkmar)


Lesenswert?

Daniel J. schrieb:
> Insofern erklärt sich auch, warum ich "volatile" bei "state" brauche,
> bei "i" hingegen nicht. Das hat nämlich vorher auch ohne funktioniert.

Ergänzend möchte ich noch hinweisen, daß "i" üblicherweise als lokaler 
Schleifenzähler verwendet wird und daher als globale Variable nicht 
verwendet werden sollte. Da wäre ein aussagekräftiger Name angebracht.

Volkmar

von Karl H. (kbuchegg)


Lesenswert?

Volkmar Dierkes schrieb:
> Daniel J. schrieb:
>> Insofern erklärt sich auch, warum ich "volatile" bei "state" brauche,
>> bei "i" hingegen nicht. Das hat nämlich vorher auch ohne funktioniert.
>
> Ergänzend möchte ich noch hinweisen, daß "i" üblicherweise als lokaler
> Schleifenzähler verwendet wird und daher als globale Variable nicht
> verwendet werden sollte. Da wäre ein aussagekräftiger Name angebracht.

Entweder das, oder überhaupt dieses 'i' komplett in die ISR verbannen
1
ISR(TIMER0_OVF_vect)
2
{
3
  static int i = 0;
4
5
  if(++i == 14) {
6
    if(PINB == 0x7F) {
7
      PORTB = 0xFE;
8
    }
9
    else {
10
      PORTB = ~(~PINB << 1);
11
    }
12
    
13
    i = 0;
14
  }
15
}

Am besten wäre natürlich beides. Denn Volkmar hat natürlich schon recht: 
Dieser Zähler hier ist ein wichtiger Zähler, so dass er sich einen etwas 
bessereren Namen als 'i' verdient hätte. Einbuchstabige Variablen-Namen 
sollte man ausschliesslich für Hilfsvariablen (wie zb Laufvariablen in 
for-Schleifen) benutzen, deren Gültigkeitsbereich im überschaubaren 
Rahmen liegt. Globale Variablen bzw. wichtige Variablen sollten auch 
angemessen benannt werden. Die Codequalität inklusive Lesbarkeit des 
Codes wird einem dafür dankbar sein.

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.