globaler counter funzt net wie erwartet

#2739230
Lesenswert?

Hallo zusammen.

System:
ATMEGA8, STK500, AVR-Studio4, Gcc-Compiler

Code:
1
#define F_CPU    8000000UL
2

3
#include <avr/io.h>
4
#include <stdint.h>
5
#include <util/delay.h>
6
#include <stdlib.h>
7
#include <avr/interrupt.h>
8

9

10

11
/* HILFSDEFINITIONEN **********  */
12

13
#define CM_Val       3      /*Compare Match Value*/
14
#define MS_INKR     2800     /*Inkrement von ms zu 1 Sekunde */
15

16

17
#define BEEP_DDR    DDRB
18
#define BEEP_PORT   PORTB
19
#define BEEP_Pin    0
20

21

22

23
#define sbi(sfr, bit)   (sfr|=(1<<bit))
24
#define cbi(sfr, bit)   (sfr&=~(1<<bit)) 
25

26
/************** Globale Variablen ****************/
27

28
uint8_t    sec_takt;
29
uint16_t sys_ms_Inkr;
30

31
/******************* Initialisierung *************/
32
void ioinit()
33
{
34
  // Timer init/////////////////////////////// 
35
  TCNT0 = 256 - CM_Val;
36
  TCCR0 |= ((1 << CS00) | (1 << CS02)); //Prescale 1024
37
  // Overflow Interrupt
38
  TIMSK = (1 << TOIE0);
39
  //Timer init////////////////////////////////  
40

41
  DDRB = 0xFF;                 //Beep auf Ausgang
42
  PORTB = 0x00;                //Beep low setzen
43
        
44
}
45

46
/*Interupt von Timer*/
47

48
ISR(TIMER0_OVF_vect)
49
{
50
  
51
  if (sys_ms_Inkr >= MS_INKR){  
52
    sec_takt++;
53
    sys_ms_Inkr = 0;
54
    
55
  }
56
  else
57
  {
58
    sys_ms_Inkr++;
59
  }
60
  
61
  TCNT0 = 256 - CM_Val;
62
  
63
}
64

65
/****************************/
66
/*            MAIN      */
67
/****************************/
68
int main(void)  {
69
  
70
  //INIT
71
  ioinit();
72
  
73
  sec_takt = 0;
74
  sys_ms_Inkr = 0;
75
  
76
  sei();
77

78
while (1)
79
{  
80

81
    cbi(BEEP_PORT,1);
82
    _delay_ms(100);
83
    sbi(BEEP_PORT,1);
84
    _delay_ms(100);
85

86
    
87
    if(sec_takt == 1){
88
    sbi(BEEP_PORT,BEEP_PIN);
89
    }
90
    
91
    if(sec_takt >= 2){
92
    sec_takt = 0;      
93
    cbi(BEEP_PORT,BEEP_PIN);
94
    }
95
    
96
    
97
  
98
}    
99
}

Problem:

So funktioniert es nicht. Der Compiler sagt nix. Der Simulator muckt 
nicht. Der Timer läuft. Die sec_takt Variable wird wie erwartet 
inkrementiert. Die LED auf PINB1 blinkt im 100ms-Takt ( naja - in etwa 
jedenfalls ). Also läuft sowohl der Interrupt, als auch die Main-While. 
Nur funktioniert:
1
    if(sec_takt == 0){
2
         sbi(BEEP_PORT,BEEP_PIN);
3
    }
4
    
5
    if(sec_takt >= 1){
6
         sec_takt = 0;      
7
         cbi(BEEP_PORT,BEEP_PIN);
8
    }

zwar in der ISR ( oder einer von ihr aufgerufenen Routine ). Aber nicht 
in der Main-While.

Das verstehe ich nicht. Die sec_takt ist global und der Compiler sagt 
mir auch nix, in keiner der Optimize-Stufen.

Bitte helft mir. Sicher ist es ein Denkfehler.

Vielen Dank für eure Mühe!
#2739247
Lesenswert?

@  Matthias T. (goldencue)

>Tatsache. Danke danke danke! Ich weiss wofür volatile ist. Aber das es
>hier greift konnte ich mir nicht vorstellen.

Jetzt weißt du es. Es ist aber immer noch ein Fehler drin. Dein 
Variablenzugiff ist NICHT atomar, muss er aber, siehe Interrupt.

Und NEIN, nur weil es 24h ohne Fehler läuft ist das KEIN Beweis, dass es 
OK ist, siehe Artikel.

Und lass den Käse mit dem Timer laden, das geht mit CTC beium AVR 
deutlich besser.
#2739251
Lesenswert?

@  gordon51freeman (Gast)

>> variabeln die in der ISR und main verwendet werden müssen volatile sein.

>MÜSSEN? Seit wann?

Seit es C gibt.

> Habe bei mir in einer ISR div. globale Variabeln als
>normale int deklariert

Kaum, denn das wären lokale Variablen, an die man ausserhalb der ISR gar 
nicht rankommt.

> und funzt trotzdem (benutze sie auch in der main).

Dann sit es Glück, dass der Optimizer das nicht soweit optimiert hat, 
dass es nicht mehr geht. Und nein, das ist kein Compilerfehler sondern 
ein Programmiererfehler.
#2739273
Lesenswert?

gordon51freeman schrieb:
> Peter II schrieb:
>> variabeln die in der ISR und main verwendet werden müssen volatile sein.
>
> MÜSSEN? Seit wann?

Eigentlich erst, seit dem es Compiler gibt, deren Optimizer 
Datenflussanylse betreiben.
Wenn man C naiv in Maschinencode übersetzt, braucht man es nicht. Aber 
so einen Compiler würde keiner haben wollen.


> Habe bei mir in einer ISR div. globale Variabeln als
> normale int deklariert und funzt trotzdem (benutze sie auch in der
> main).

Dann hast du noch nicht verstanden, wo das Problem liegt.

FAQ: Was hat es mit volatile auf sich
Gast #2739275
Lesenswert?

Falk Brunner schrieb:
> @  Peter II (Gast)
>
>>> Jetzt weißt du es. Es ist aber immer noch ein Fehler drin. Dein
>>> Variablenzugiff ist NICHT atomar, muss er aber, siehe Interrupt.
>
>>welchen zugriff meinst du überhaupt?
>
> if(sec_takt == 1){

uint8_t    sec_takt;

damit ist es doch immer atomar, oder kommt gcc auf die idee die bits 
einzeln zu setzen?
#2739279
Lesenswert?

hier stehts:

Variablen, auf die sowohl innerhalb wie auch außerhalb einer 
Interruptserviceroutine zugegriffen wird (schreibend oder lesend), 
müssen (ähnlich wie Hardwareregister) mit dem Schlüsselwort volatile 
(flüchtig) versehen werden, damit der C-Compiler berücksichtigen kann, 
dass diese Variablen jederzeit (durch das Auftreten des Interrupts) 
gelesen oder geschrieben werden können. Ansonsten würde der C-Compiler 
das regelmäßige Abfragen oder Beschreiben dieser Variablen ggf. 
wegoptimieren, da er nicht damit rechnet, dass auf die Variable auch 
"ohne sein Zutun" zugegriffen wird.

Zitat Gcc-Tur

:)

und atomar bedeutet, das ich jegliche "Bearbeitungen" von Interrupts 
z.B. unterbinden muss, WÄREND ich auf die Variable zugreife oder sie 
verändere. Wenn ich also if ( sec_takt == 1) abfrage, könnte ( bei nicht 
atomar ) der Interrupt den Wert der Variable ändern WÄREND ich auslese. 
Und das führt zu sporadischen Laufzeitfehlern.
Gast #2739282
Lesenswert?

Matthias T. schrieb:
> und atomar bedeutet, das ich jegliche "Bearbeitungen" von Interrupts
> z.B. unterbinden muss, WÄREND ich auf die Variable zugreife oder sie
> verändere. Wenn ich also if ( sec_takt == 1) abfrage, könnte ( bei nicht
> atomar ) der Interrupt den Wert der Variable ändern WÄREND ich auslese.
> Und das führt zu sporadischen Laufzeitfehlern.

das ist halt das Problem wenn man text zitiert und nicht versteht was 
der grund ist.

Das gilt hier nicht für 8bit variablen ( bzw. für alle Variabeln die die 
CPU direkt verarbeiten kann)
Gast #2739298
Lesenswert?

Matthias T. schrieb:
> das war nicht zitiert

dann hast du dir es falsch gemerkt.

> ändern WÄREND ich auslese.
> Und das führt zu sporadischen Laufzeitfehlern.
es kommt auch zu keinen laufzeitfehler sonder der verhalten ist nicht 
das was man erwartet.

eine 16bit variable wird auf einem 8bit system in 2 oder mehr schritten 
geändert, damit gibt es ungwollte zwischenwerte. Beim Vergleichen kann 
es dann zu unerwarteten ergebnissen kommen.

Bei einer 8bit Variable kann das hier aber nicht passieren.

Anderes währe wenn in der main etwas wie

wert++;

und in der isr auch

wert++

gemacht wird, dann können dabei schritte verloren gehen. Aber hier wird 
in der main nur auf 0 gesetzt und in der isr ++ gemacht. Dabei kann kein 
Fehler entstehen.
#2739368
Lesenswert?

@  Peter II (Gast)

>uint8_t    sec_takt;

>damit ist es doch immer atomar,

Ahhh stimmt, Tomaten auf den Augen. sys_ms_Inkr ist ja nur 16 Bit.

>oder kommt gcc auf die idee die bits
>einzeln zu setzen?

Eher nicht.

>gemacht wird, dann können dabei schritte verloren gehen. Aber hier wird
>in der main nur auf 0 gesetzt und in der isr ++ gemacht. Dabei kann kein
>Fehler entstehen.

Stimmt.
#2739384
Lesenswert?

Matthias T. schrieb:
> aber das habe ich! Ich habe den Compare Match Mode genutzt

Wo genau hast du den Compare Match Mode benutzt?

> immer nur bis
1
TCNT0 = 256 - CM_Val;
 zählen.

genau das nennt sich Timer vorladen.
Und ist Quatsch. Das kann der Timer im CTC viel besser. Nämlich 
taktgenau.

>
> Was meintest du genau?

Lies den Link. Ich hab mir nicht umsonst die Mühe gemacht, da zumindest 
die häufigsten Timer Sachen im Detail runterzubrechen.
Gast #2739411
Lesenswert?

Peter II schrieb:
> uint8_t    sec_takt;
>
> damit ist es doch immer atomar, oder kommt gcc auf die idee die bits
> einzeln zu setzen?

Weißt du denn, ob deine Architektur uint8_ts atomar inkrementiert? Weißt 
du, ob dein Compiler die passende Instruktion aufruft?

Vielleicht ja. Aber portabel ist das dann dennoch nicht.
#2739423
Lesenswert?

Sascha schrieb:

> Aber portabel ist das dann dennoch nicht.

Das wird er ohnehin nie sein können. Auf eine Maschine mit 
beispielsweise 4-Bit breiten Datenoperationen ist ein unvermeidlich 
Eigenschaften eines bestimmten AVR Controllers voraussetzender Code 
prinzipiell nicht portabel.

> Weißt du denn, ob deine Architektur uint8_ts atomar inkrementiert? Weißt
> du, ob dein Compiler die passende Instruktion aufruft?

Entweder er arbeitet byteweise, dann ist die Inkrementierung in einer 
normalen AVR-ISR atomar, oder der Compiler wurde nur geschrieben, um 
Anwender in den Irrsinn zu treiben.
#2739457
Lesenswert?

die Diskussion hat sich hier offenbar zu ner öffentlichen Debatte 
entwickelt :)

Mir soll es recht sein. Ich entwickle als Hobby ( muss also nicht alles 
im Detail wissen wie die Vollpros ). Allerdings entwickle ich nur AVR 
und habe nicht vor zu portieren.

Ich finde eure Unterhaltung sehr lehrreich und verfolge mit großem 
Interesse weiter. Lasst euch nicht stören :)
#2739520
Lesenswert?

Stefan Wagner schrieb:
> Thomas Eckmann schrieb:
>> Was soll er denn sonst machen? Unpassenden Code erzeugen?
>
> Ja.
>
> Irgendwann unterzieht jemand den Compiler einem Redesign, und schon ist
> die Annahme nicht mehr erfüllt.

Ähm, wir reden über ein Inkrement IN EINER ISR.
DIe ist per Def. atomar, solange die Interrupts nicht explizit innerhalb 
der ISR aufgedreht werden.


Ausserhalb einer ISR muss ein
  i++;
nicht atomar sein. Soweit hast du schon recht.


Ich glaube, ihr redet aneinander vorbei.
Stefan redet vom allgmeinen Fall und der Rest von einem Inkrement in 
einer ISR
#2739614
Lesenswert?

Wer es gerne offiziell hat: C99 definiert via signal.h einen atomaren 
Datentyp sig_atomic_t, der entweder mindestens -127..+127 oder 
mindestens 0..255 umfasst. Wer es also auf maximale Portabilität anlegt, 
der verwendet diesen Typ und hat damit definiert atomaren Zugriff 
(freilich keine atomare Inkrementierung).
#2739774
Lesenswert?

Matthias T. schrieb:
> if(sec_takt >= 2){
>     sec_takt = 0;

ich stelle mir hier vor dass hier trotz 8-Bit Variable auch der 
Interrupt gesperrt werden muss.
Sonst kann doch zwischen Abfrage und Nullsetzen der Interrupt kommen und 
so theoretisch eine Sekunde verloren gehen. (Praktisch dann evtl. nicht 
unter der Voraussetzung dass die main-Schleife schnell genug durchlaufen 
wird)
Oder sehe ich das falsch?
Gast #2739863
Lesenswert?

Walter S. schrieb:
> Sonst kann doch zwischen Abfrage und Nullsetzen der Interrupt kommen und
> so theoretisch eine Sekunde verloren gehen. (Praktisch dann evtl. nicht
> unter der Voraussetzung dass die main-Schleife schnell genug durchlaufen
> wird)
> Oder sehe ich das falsch?

ja und nein, die sekunde geht auch denn verloren wenn die main zu lange 
braucht. das muss nicht zwischen abfrage und 0 setzen passieren. Und 
diesen Fall verhindert auch ein atomic nicht.
Gast #2739932
Lesenswert?

Walter S. schrieb:
> da hast Du recht,
> dann also doch die altbewährte Methode, nämlich Zähler nur im IRQ
> verändern, im main nur abfragen.
nein das hilft hier auch nicht.

man müsste ihn nicht auf 0 setzen sonder 2 abziehen;


sec_takt -= 2;

jetzt hat man sich damit aber wirklich ein atomar problem eingehandelt. 
Da aber selbst ISR verloren gehen können, weil sie auch nur ein flag 
haben spielt das überhaupt keine rolle in der Praxis. Man muss es nur 
wissen und dafür sorgen das die main schnell genug ist.
#2739973
Lesenswert?

@  Walter S. (avatar)

>> if(sec_takt >= 2){
>>     sec_takt = 0;

>ich stelle mir hier vor dass hier trotz 8-Bit Variable auch der
>Interrupt gesperrt werden muss.
>Sonst kann doch zwischen Abfrage und Nullsetzen der Interrupt kommen und
>so theoretisch eine Sekunde verloren gehen. (Praktisch dann evtl. nicht
>unter der Voraussetzung dass die main-Schleife schnell genug durchlaufen
>wird)

Die MUSS so oder so schnell genug sein, dass zwischen zwei Veränderungen 
von sec_takt das Ganze einmal abgearbeitet wird. Sonst funktioniert es 
nicht, mit oder ohne atomaren Zugriff.

Allerding gibt es Konstellationen, wo es atomar sein muss, 
beispielsweise bei einem Software-FIFO, dort werden ja Lese- und 
Schreibzeiger sowie der Füllstand manipuliert.

>dann also doch die altbewährte Methode, nämlich Zähler nur im IRQ
>verändern, im main nur abfragen.

Das ist natürlich noch besser, vor allem wenn die Programme größer 
werden. Wenn da an mehreren Stellen eine Schreibzugriff auf solche 
Variablen gemacht wird, hat man schnell Chaos. UNd wenn man in der ISR 
dann direkt für die jeweiligen Ereignisse die Flags schon ausdekodiert 
und setzt, wird sowohl die Auswertung als auch das Handshake minimiert 
und gesichert. Denn dann kann z.B. die ISR prüfen, ob das FLag, das 
gerade gesetzt werden soll, schon gelöscht ist. Wenn nein, ist die 
main-Schleife zu lang oder irgendwo hängen geblieben und man kann einen 
Fehler ausspucken. Optimale Laufzeitprüfung!

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren