Forum: Mikrocontroller und Digitale Elektronik ATmega16 Modulo ISR Problem (Arduino IDE)


von lex7 (Gast)


Lesenswert?

Hallo Zusammen,

wenn ich diesen Code ausführe
1
volatile uint16_t countISRTimer1=0;
2
3
void setup() {
4
//...
5
}
6
7
void loop() {
8
 if(countISRTimer1%128==0) 
9
  Serial.println(countISRTimer1);
10
 countISRTimer1++;
11
}
funktioniert es wie gewünscht. (Alle 128 wird der aktuelle Wert 
ausgegeben)
Lasse ich den counter in der ISR hochzählen
1
volatile uint16_t countISRTimer1=0;
2
3
ISR( TIMER1_COMPA_vect )                            // every 10ms
4
{
5
 countISRTimer1++;
6
}
7
8
void setup() {
9
//...
10
}
11
12
void loop() {
13
 if(countISRTimer1%128==0) 
14
  Serial.println(countISRTimer1);
15
}

sieht die Ausgabe so aus:
0
4
128
132
256
260
usw.

Was kann hier passieren?

von leo (Gast)


Lesenswert?

lex7 schrieb:
> Lasse ich den counter in der ISR hochzählenvolatile uint16_t
> countISRTimer1=0;
>
> ISR( TIMER1_COMPA_vect )                            // every 10ms
> {
>  countISRTimer1++;
> }
>
> void setup() {
> //...
> }
>
> void loop() {
>  if(countISRTimer1%128==0)
>   Serial.println(countISRTimer1);
> }
>
> sieht die Ausgabe so aus:
> 0
> 4
> 128

Das Problem hier ist der nicht-atomare Zugriff auf countISRTimer1 bei 
der Ausgabe. Waehrend die Teile der 16-bit Zahl gelesen werden 
inkrementiert die ISR die Zahl.
S. zB. http://www.gammon.com.au/interrupts (Atomic access)

leo

von lex7 (Gast)


Lesenswert?

Guter Punkt.
1
int16_t getCountISRTimer1( void )         
2
{
3
  int16_t val;
4
  cli();
5
  val = countISRTimer1; 
6
  sei();
7
  return val;
8
}
9
10
void loop() {
11
 if(getCountISRTimer1()%128==0) {
12
  Serial.println(countISRTimer1);
13
  }
14
}

Leider bleibt das Ergebnis wie oben.
Noch Ideen?

von Dr. Sommer (Gast)


Lesenswert?

lex7 schrieb:
> Leider bleibt das Ergebnis wie oben.
> Noch Ideen?

In der ISR die Interrupts abschalten bringt gar nix, weil sie da schon 
aus sind. Sie wieder einzuschalten ist aber gefährlich. Du musst sie in 
der loop abschalten.
Wie wäre es damit, aus der Variablen eine uint8_t zu machen und immer 
nur bis 127 zu zählen und dann auf 0 zu setzen? Dann erledigt sich das 
Problem komplett, denn 8-Bit Typen können atomar gelesen werden. 
Effizienter ist es auch. Aber Vorsicht: der per println ausgegebene Wert 
kann dann anders sein als der in der if Bedingung. Daher am Besten eine 
lokale temporäre Kopie machen.

von Dr. Sommer (Gast)


Lesenswert?

Dr. Sommer schrieb:
> In der ISR die Interrupts abschalten bringt gar nix, weil sie da schon
> aus sind

Oh, falsch gelesen! Äh, der letzte Teil meiner Aussage ist hier das 
Problem - die Variable wird 2x ausgelesen, einma im if und einmal im 
println. Und beim letzteren auch ohne Interrupt Sperre.

von Stefan F. (Gast)


Lesenswert?

Außerdem soll die Variable als "volatile" gekennzeichnet werden, damit 
bei jedem Zugriff auf das RAM zugegriffen wird und kein Caching in einem 
CPU Register stattfindet.

Das hat lex7 richtig gemacht, ich wollte es in diesem Kontext nur 
nochmal erklären, weil es oft vergessen wird.

von Dr. Sommer (Gast)


Lesenswert?

Ich würde es wenn möglich so machen:
1
volatile uint8_t countISRTimer1=0;
2
3
ISR( TIMER1_COMPA_vect )                            // every 10ms
4
{
5
  if (countISRTimer1 == 127)
6
    countISRTimer1 = 0;
7
  else
8
    ++countISRTimer1;
9
}
10
11
void setup() {
12
//...
13
}
14
15
void loop() {
16
  uint8_t temp = countISRTimer1;
17
  if (temp==0) 
18
    Serial.println(temp);
19
}

Wenn aber wirklich bis 65408 gezählt werden soll, dann so:
1
volatile uint16_t countISRTimer1=0;
2
3
ISR( TIMER1_COMPA_vect )                            // every 10ms
4
{
5
  ++countISRTimer1;
6
}
7
8
void setup() {
9
//...
10
}
11
12
void loop() {
13
  cli ();
14
  uint16_t temp = countISRTimer1;
15
  sei ();
16
  if (temp % 128 == 0) 
17
    Serial.println(temp);
18
}

von lex7 (Gast)


Lesenswert?

Besten Dank an alle, da hatte ich wohl einen Knoten im Kopf.


hiermit bekomme ich auch eine doppelte Ausgabe:
Dr. Sommer schrieb:
> Wenn aber wirklich bis 65408 gezählt werden soll, dann so:
>
1
volatile 
2
> uint16_t countISRTimer1=0;
3
> 
4
> ISR( TIMER1_COMPA_vect )                            // every 10ms
5
> {
6
>   ++countISRTimer1;
7
> }
8
> 
9
> void setup() {
10
> //...
11
> }
12
> 
13
> void loop() {
14
>   cli ();
15
>   uint16_t temp = countISRTimer1;
16
>   sei ();
17
>   if (temp % 128 == 0)
18
>     Serial.println(temp);
19
> }

0
0
128
128
256
256 usw.

Aber klar, die ISR wird nur alle 10ms ausgeführt, da kann der loop() 
doppelt durchlaufen werden.

von Dr. Sommer (Gast)


Lesenswert?

lex7 schrieb:
> hiermit bekomme ich auch eine doppelte Ausgabe:

Die andere Version mit uint8_t liefert genauso doppelte Ausgaben - die 
sind halt nur immer "0" ;-) Daher ist das println(temp) da auch etwas 
sinnlos, weil temp da eh immer 0 ist.

lex7 schrieb:
> Aber klar, die ISR wird nur alle 10ms ausgeführt, da kann der loop()
> doppelt durchlaufen werden.

Ja, eigentlich sogar noch viel schneller. Das println bremst es 
wahrscheinlich.

von Falk B. (falk)


Lesenswert?

Dr. Sommer schrieb:
> lex7 schrieb:
>> hiermit bekomme ich auch eine doppelte Ausgabe:
>
> Die andere Version mit uint8_t liefert genauso doppelte Ausgaben - die
> sind halt nur immer "0" ;-) Daher ist das println(temp) da auch etwas
> sinnlos, weil temp da eh immer 0 ist.
>
> lex7 schrieb:
>> Aber klar, die ISR wird nur alle 10ms ausgeführt, da kann der loop()
>> doppelt durchlaufen werden.
>
> Ja, eigentlich sogar noch viel schneller. Das println bremst es
> wahrscheinlich.

Sicher, aber das Grundkonzept ist schon Unsinn. Wenn man einen Vorgang 
mit einer Frequenz X anstoßen will, dann DIREKT!
1
volatile uint16_t countISRTimer1=0;
2
volatile uint8_t  isr_flag=0;
3
4
ISR( TIMER1_COMPA_vect )                            // every 10ms
5
{
6
   ++countISRTimer1;
7
  if ( (countISRTimer1 & 0x7F) == 0) isr_flag = 1;   // fast modulo operation
8
}
9
10
void setup() {
11
//...
12
}
13
14
void loop() {
15
16
  if (isr_flag) {
17
    isr_flag=0;
18
    cli ();
19
    uint16_t temp = countISRTimer1;
20
    sei ();
21
    Serial.println(temp);
22
  }
23
}

Das ist eindeutig und laufzeitunabhängig (es sei denn, die Ausgabe per 
UART läuft mit 10 Baud).

von Dr. Sommer (Gast)


Lesenswert?

Falk B. schrieb:
> Sicher, aber das Grundkonzept ist schon Unsinn. Wenn man einen Vorgang
> mit einer Frequenz X anstoßen will, dann DIREKT!

Da kann man sich die ISR aber auch sparen und es direkt so machen:
1
uint8_t countISRTimer1 = 0;
2
void loop() {
3
  if (TIFR & (1 << TOV1)) {
4
    TIFR = 1 << TOV1;
5
    if (countISRTimer1 == 128) {
6
      countISRTimer1 = 0;
7
      Serial.println ("Overflow\n");
8
    } else
9
      ++countISRTimer1;
10
    
11
  }
12
}
Spart die Fummelei mit cli/sei, volatile, Überlegung wo die ISR den 
eigentlichen Programmfluss unterbrechen kann...

von Falk B. (falk)


Lesenswert?

Dr. Sommer schrieb:
> Falk B. schrieb:
>> Sicher, aber das Grundkonzept ist schon Unsinn. Wenn man einen Vorgang
>> mit einer Frequenz X anstoßen will, dann DIREKT!
>
> Da kann man sich die ISR aber auch sparen und es direkt so machen:

Naja, das geht aber nur, wenn die Überlauffrequenz eher klein ist und 
die Abfrage des Überlaufbits ausreichend schnell. Das ist schon wieder 
ein Sonderfall.

"Ich bin dafür, die Dinge soweit wie möglich zu vereinfachen, aber nicht 
weiter."

Albert Einstein

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.