Forum: Mikrocontroller und Digitale Elektronik Volatile sollte doch atomares Lesen garantieren?


von daniel (Gast)


Lesenswert?

Hallo,

auf einem ATMEGA32 in der ISR des TIMER1:
1
volatile uint16_t pwm_cycle_counter;
2
ISR(TIMER1_COMPA_vect) 
3
{
4
    static uint8_t pwm_cnt;
5
...
6
    
7
    if (pwm_cnt == 0) 
8
  {
9
        pwm_cnt++;
10
  pwm_cycle_counter++;
11
    }
12
    else 
13
  {
14
...
15
        if (pwm_cnt == pwm_cnt_max) 
16
    {
17
            pwm_sync = 1;                       // Update jetzt möglich
18
            pwm_cnt  = 0;
19
        }
20
        else pwm_cnt++;
21
    }
22
}

und in der main
1
main()
2
{
3
...
4
5
  while(1)
6
  {
7
    // Zeit berechnen
8
    if( (pwm_cycle_counter - last_pwm) >= F_PWM )
9
    {
10
      uart_puts("Sekunde:");  
11
      itoa((pwm_cycle_counter - last_pwm),rs232buf,10);
12
      uart_puts(rs232buf);
13
      uart_putc('\n');
14
      second_counter++;
15
    }
16
...
17
        }
18
}

Nun passiert etwas, das ich mir nicht ganz erklären kann. Über UART wird 
ein Wert ausgegeben, der kleiner F_PWM ist. Was aber eigentlich gar 
nicht sein darf, da die Abfrage eine Zeile darüber ja genau das abprüft. 
Zusätzlich wird pwm_cycle_counter nur inkrementiert, die Differenz kann 
also gar nicht kleiner werden im Laufe der Zeit.

Weiß jemand was genau da schief läuft?
Eventuell kann etwas schief gehen wenn der Interrupt genau dann kommt 
wenn ich auf die Größe zugreife?

Gruß
Daniel

von Εrnst B. (ernst)


Lesenswert?

daniel schrieb:
> Volatile sollte doch atomares Lesen garantieren?

Nein. Dafür brauchst du sei()/cli(), oder die praktischen Atomic-Wrapper 
aus der <util/atomic.h>.

von gerd (Gast)


Lesenswert?

Entweder ich bin blind, oder du hast die Variable last_pwn schön aus 
deinen Code-Schnipseln ausgespart. Da die aber ja nicht ganz unbeteiligt 
ist, wäre es vielleicht auch ganz gut zu wissen, wie sie im Kontext zu 
dem Ganzen steht.

Weiterhin vergeht von deiner if-Abfrage zu deiner itoa-Wandlung noch 
einiges an Zeit - u.a. eine (wenn Fleury) interrupt-basierte 
UART-Routine. Du meinst nicht, dass zwischenzeitlich nochmal was 
passiert sein könnte?

Also, so wie der Code dargestellt ist, ist eine Analyse (wieder mal) 
schwierig. ;)

- gerd

von Stefan E. (sternst)


Lesenswert?

> Volatile sollte doch atomares Lesen garantieren

Nein, wie kommst du darauf?
Die Notwendigkeiten zu "volatile" und Atomarität kommen zwar gerne als 
Pärchen daher, aber das eine hat mit dem anderen nichts zu tun.

Um die Atomarität musst du dich schon noch gesondert kümmern.

von Karl H. (kbuchegg)


Lesenswert?

In deinem Code sind ein paar Probleme

* volatile und Atomarer Zugriff haben nichts miteinander zu tun
  Für atomaren Zugriff musst du kurzfristig die Interrupts ausschalten

* uint16_t pwm_cycle_counter;
  itoa

  Das passt nicht zusammen.
  itoa heißt itoa, weil das i da vorne für _i_nteger steht. Also eine
  vozeichenbehaftete Zahl. Das hast du aber nicht. Du hast einen
  unsigned integer. Und die Funktion dafür heißt utoa. u wie _u_nsigned

von Falk B. (falk)


Lesenswert?

Siehe Interrupt

von daniel (Gast)


Lesenswert?

Hallo,

danke für die Antworten. Mit dem Abschalten des Interrupts vor dem 
if-Statement und dem Anschalten direkt danach besteht keinerlei Problem 
mehr.

Mit einem 8 Bit Datum wäre das Lesen wahrscheinlich schneller vorbei, 
während pwm_cycle_counter ja ein 16 Bit Wert ist, länger dauert und 
damit der IRQ häufiger dazwischenfunken kann.

Was ich jedoch nicht verstehe - selbst wenn in der Zwischenzeit noch 
etwas mit pwm_cycle_counter passiert, dann nur die Inkrementoperation im 
IRQ. Dass das allerdings dazu führt, dass der später ausgegebene Wert 
kleiner ist als der zuvor im If-Statement verglichene erscheint mir 
komisch. :)

Gruß,
Daniel

von Karl H. (kbuchegg)


Lesenswert?

daniel schrieb:

> IRQ. Dass das allerdings dazu führt, dass der später ausgegebene Wert
> kleiner ist als der zuvor im If-Statement verglichene erscheint mir
> komisch. :)

Nochmal:
Du benutzt das falsche Werkzeug!

itoa ist für signed integer.
Du hast unsigned Werte.

Und ja. Wenn ein unsigned Wert groß genug wird, dann stellt ihn eine 
Funktion die dasselbe Bitmuster als signed Wert interpretiert mit einem 
- dar.

von daniel (Gast)


Lesenswert?

Hallo Karl,

den Typunterschied habe ich verstanden, danke für den Hinweis.

Aber relevant ist itoa/utoa ja nur für die Ausgabe über RS232. Den Code, 
den ich meine, hat damit nichts zu tun.

Hier nochmal die Stelle:

    // Zeit berechnen
    if( (pwm_cycle_counter - last_pwm) >= F_PWM )
    {...

Funktioniert so:

    // Zeit berechnen
    cli();
    if( (pwm_cycle_counter - last_pwm) >= F_PWM )
    {
       sei();
       uart_puts("Sekunde:");
       ...


Über die RS232 Ausgabe wird klar, dass der Ausdruck
(pwm_cycle_counter - last_pwm) >= F_PWM
nicht wahr ist. D.h. der Wert von pwm_cycle_counter muss kurze Zeit 
vorher größer gewesen sein um in die If-Abfrage zu kommen. Dann kommt 
der IRQ und schwups ist pwm_cycle_counter kleiner. Und das kann ich mir 
nicht erklären.

von (prx) A. K. (prx)


Lesenswert?

daniel schrieb:

> vorher größer gewesen sein um in die If-Abfrage zu kommen. Dann kommt
> der IRQ und schwups ist pwm_cycle_counter kleiner. Und das kann ich mir
> nicht erklären.

Wenn er erst das obere Byte läd, dann der Interrupt reinrutscht, dann 
das untere Byte läd, dann ist der geladene Wert ab und zu kleiner als 
der gespeicherte:

anfangs:        00FF
oberes Byte:    00
nach Interrupt: 0100
unteres Byte;     00
geladen also;   0000

von holger (Gast)


Lesenswert?

>Was ich jedoch nicht verstehe - selbst wenn in der Zwischenzeit noch
>etwas mit pwm_cycle_counter passiert, dann nur die Inkrementoperation im
>IRQ. Dass das allerdings dazu führt, dass der später ausgegebene Wert
>kleiner ist als der zuvor im If-Statement verglichene erscheint mir
>komisch. :)

Wenn pwm_cycle_counter überläuft ist daran gar nichts komisch.

von daniel (Gast)


Lesenswert?

Hallo Holger,

Überlauf ist kein Problem mit unsigned Typen. Bsp: 0x01 - 0xFF = 0x02.

A.K. hat wohl die richtige Erklärung gefunden. Dankeschön!!

von Erdbeere (Gast)


Lesenswert?

>    cli();
>    if( (pwm_cycle_counter - last_pwm) >= F_PWM )
>    {
>       sei();

Auch Programmieren kann tödlich sein: sei() findet nur statt, wenn if(1) 
ausgeführt wird.
Am besten nimmt man eine Hilfsvariable, die einmalig unter 
Interruptsperre gelesen wird. Unter Umständen ist auch eine kleine 
Funktion zum Lesen sinnvoll.

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.