ATmega16 Modulo ISR Problem (Arduino IDE)

Gast #5713410
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?
Gast #5713438
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
Gast #5714376
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?
Gast #5714380
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.
Gast #5714381
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.
Gast #5714399
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.
Gast #5714538
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
}
Gast #5715746
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.
Gast #5715759
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.
#5715768
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).
Gast #5715778
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...
#5715792
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

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