Forum: Mikrocontroller und Digitale Elektronik Timer CTC Mode Atmega328P Verständnisproblem


von Milli (millsen)


Lesenswert?

Hallo, ich habe vor mit dem Timer1 alle 10 ms eine Variable per 
Interrupt hochzuzählen. In der Schleife soll dann alle 500 ms eine LED 
umgeschaltet werden. Das funktioniert aber nicht. Es funktioniert nur, 
wenn mehr Code abgearbeitet wird, hier die auskommentierte For-Schleife, 
welche mindestens 17 Iterationen durchlaufen muss. Meine Vermutung ist, 
dass die Variable zu schnell ausgelesen wird. Aber das macht ja keinen 
Sinn, da die ISR ja abläuft, ohne dass die Variable ausgelesen wird. Wo 
liegt hier mein Denkfehler?

Hier noch das Programm:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define F_CPU 16000000UL
5
6
// Ein- und Ausgänge definieren
7
#define LED     PB0
8
9
// Globale Variablen
10
uint8_t gIR_sysTick_counter = 0;
11
12
// ISR
13
ISR(TIMER1_COMPA_vect)
14
{
15
  gIR_sysTick_counter++;
16
}
17
18
19
int main()
20
{
21
  // Setup ================================
22
  cli();
23
24
  // Ausgänge Setup
25
  DDRB |= (1 << LED);
26
27
28
  // Timer1 Mode CTC (Geht auf 0 wenn OCR1A erreicht) & Prescaler auf 256 (Bei 16 MHz wird alle 16 us hochgezählt)
29
  TCCR1B |= (1 << WGM12) | (1 << CS12);
30
31
  // Output Compare
32
  OCR1A = 625;  // 16 us * 625 = 10 ms
33
34
  // Timer1 Interrupt einstellen (Compare mit OCR1A)
35
  TIMSK1 |= (1 << OCIE1A);
36
37
  sei();
38
  // Ende Setup ===========================
39
40
  // Programm Loop
41
  while(1)
42
  {
43
      if(gIR_sysTick_counter >= 50)
44
      {
45
        PORTB ^= (1 << LED);
46
        gIR_sysTick_counter = 0;
47
      }
48
      
49
      /*
50
      for(int i = 0; i < 17; i++)
51
      {}
52
      */
53
  }
54
}

von Stefan F. (Gast)


Lesenswert?

Schreibe mal "volatile" for

> uint8_t gIR_sysTick_counter = 0;

Hintergrund: Die CPU kann auf Register schneller zugreifen, als auf RAM. 
Deswegen wird der Wert innerhalb der while Schleife in ein Register 
kopiert und dann nur noch das Register verwendet. Der Compiler ist nicht 
so schlau zu berücksichtigen, dass die ISR dazwischen kommen kann und 
den Wert ändern kann.

Mit dem Wort volatile zwingst du den Compiler dazu, bei jedem Zugriff 
wirklich auf das RAM zuzugreifen, ohne Abkürzungen zu verwenden.

Normalerweise setzt man den systick aber nicht auch 0 zurück, sondern 
lässt ihn durch laufen (samt Überlauf). In einem komplexeren Programm 
stößt du bei deiner Methode ganz schnell auf zahlreiche Probleme.

Benutze stattdessen die Subtraktion, um festzustellen, ob 50 Ticks 
stattgefunden haben:

vorher=gIR_sysTick_counter;
if (gIR_sysTick_counter - vorher > 50) {...}

Das funktioniert auch nach einem Überlauf korrekt.

In dem Zusammenhang könnte dieser Aufsatz für dich hilfreich sein: 
http://stefanfrings.de/multithreading_arduino/index.html

von Milli (millsen)


Lesenswert?

Vielen Dank, jetzt geht es! Werde mir das noch mal genauer anschauen.

von Stefan F. (Gast)


Lesenswert?

Falls du deine Variable auf mehr als 8 Bit vergrößerst, schreibe dir 
eine Hilfsfunktion die beim Lese-Zugriff Interrupts blockiert:
1
uint16_t get_systick()
2
{
3
    uint16_t value;
4
    cli();
5
    value=gIR_sysTick_counter;
6
    sei();
7
    return value;
8
}

Das sieht aufwändig aus, aber der Maschinencode der daraus generiert 
wird, ist ziemlich kompakt.

So verhinderst du, dass ein Lesezugriff durch die ISR gestört wird, was 
dazu führen würde, dass die oberen 8 Bit und die unteren nicht zusammen 
passen. Frage nach, wenn du das weiter ausdiskutieren willst.

von Milli (millsen)


Lesenswert?

Hier tritt bei meinem Ansatz auch ein Problem auf, welches ich mir nicht 
erklären kann.
1
  uint8_t past = 0;
2
  (...)
3
  // Programm Loop
4
  while(1)
5
  {
6
      
7
      if(gIR_sysTick_counter - past >= 50)
8
      {
9
        PORTB ^= (1 << LED);
10
        past = gIR_sysTick_counter;
11
      }
12
  }
Die LED blinkt dann drei mal und leuchtet dann permanent. In past müsste 
nach dem dritten Mal leuchten ja 250 stehen. Nach meiner Berechnung 
sollte die nächste if(..) ausgeführt werden, wenn gIR_sysTick_counter 
bei 44 ist. Funktioniert aber nicht. In past müsste doch dann eine 50 
stehen, da ja beide unsigned sind.

von Stefan F. (Gast)


Lesenswert?

Da bin ich jetzt überfragt. Das müsste eigentlich funktionieren.

Schaffe dir mal eine Möglichkeit zum Debuggen, oder zumindest 
Log-Meldungen ausgeben zu können. Dann kannst du die Werte der Variablen 
und auch das Ergebnis der Subtraktion kontrollieren.

Das "volatile" hast du aber noch drin, oder?

von Milli (millsen)


Lesenswert?

Korrektur: "in past müsste doch dann eine 50 stehen" ist falsch, ich 
meinte natürlich das Ergebnis der Subtraktion.

Zu deiner Frage, ja, habe volatile stehen.

Hier nochmal der ganze Code:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define F_CPU 16000000UL
5
6
// Ein- und Ausgänge definieren
7
#define LED     PB0
8
9
// Globale Variablen
10
volatile uint8_t gIR_sysTick_counter = 0;
11
12
// ISR
13
ISR(TIMER1_COMPA_vect)
14
{
15
  gIR_sysTick_counter++;
16
}
17
18
19
int main()
20
{
21
  uint8_t past = 0;
22
23
  // Setup ================================
24
  cli();
25
26
  // Ausgänge Setup
27
  DDRB |= (1 << LED);
28
29
30
  // Timer1 Mode CTC (Geht auf 0 wenn OCR1A erreicht) & Prescaler auf 256 (Bei 16 MHz wird alle 16 us hochgezählt)
31
  TCCR1B |= (1 << WGM12) | (1 << CS12);
32
33
  // Output Compare
34
  OCR1A = 625;  // 16 us * 625 = 10 ms
35
36
  // Timer1 Interrupt einstellen (Compare mit OCR1A)
37
  TIMSK1 |= (1 << OCIE1A);
38
39
  sei();
40
  // Ende Setup ===========================
41
42
  // Programm Loop
43
  while(1)
44
  {
45
      
46
      if(gIR_sysTick_counter - past >= 50)
47
      {
48
        PORTB ^= (1 << LED);
49
        past = gIR_sysTick_counter;
50
      }
51
  }
52
}

von S. Landolt (Gast)


Lesenswert?

Was ist das Resultat der if-Abfrage, wenn past>(256-50), z.B. past=250?

von Stefan F. (Gast)


Lesenswert?

Ich habe den Fehler mit dem Debugger und diesem Code nachvollziehen 
können:
1
#include <stdint.h>
2
#include <avr/io.h>
3
4
#define LED     PB0
5
6
volatile uint8_t gIR_sysTick_counter = 250; // damit es schneller geht
7
8
int main() 
9
{
10
    uint8_t past=0;
11
    while(1)
12
    {      
13
        if(gIR_sysTick_counter - past >= 50)
14
        {
15
            PORTB ^= (1 << LED);
16
            past = gIR_sysTick_counter;
17
        }
18
19
        // simuliere Interrupts
20
        gIR_sysTick_counter += 10;
21
    }
22
}

Offenbar ist das Ergebnis des Ausdruckes "gIR_sysTick_counter - past" 
ein 16 Bit Integer. Erzwinge 8 Bit so, dann geht es:
1
        uint8_t diff=gIR_sysTick_counter - past;
2
        if(diff >= 50)

Da ich solche Warteschleifen bisher immer nur mit 16 Bit oder größeren 
Variablen programmiert habe, hatte ich das nicht auf dem Schirm.

Milli schrieb:
> Korrektur: "in past müsste doch dann eine 50 stehen" ist falsch, ich
> meinte natürlich das Ergebnis der Subtraktion.

Ist auch falsch, da müsste 250 drin stehen, vom vorherigen Moment wo die 
LED zuletzt getoggelt wurde.

Beitrag #7353813 wurde von einem Moderator gelöscht.
von Milli (millsen)


Lesenswert?

Danke, es funktioniert jetzt. Habe es auch mal mit 16-Bit Variablen und 
der empfohlenen Funktion getestet. Da läuft es ohne Probleme, auch nach 
Überlauf.

Ich würde mir das auch gerne noch mal mit dem Debugger ansehen, welche 
IDE nutzt du dafür? Ich programmiere in VS Code mit Platformio, da geht 
das Debuggen nicht so gut beim Atmega328P (oder?).

Stefan F. schrieb:
> Ist auch falsch, da müsste 250 drin stehen, vom vorherigen Moment wo die
> LED zuletzt getoggelt wurde.

Ja, in past müsste 250 stehen. Ich meinte im Ergebnis von 
gIR_sysTick_counter - past = 44 - 250 = 50.

von Stefan F. (Gast)


Lesenswert?

Milli schrieb:
> Ich würde mir das auch gerne noch mal mit dem Debugger ansehen, welche
> IDE nutzt du dafür?

Für solche kleinen Tests bevorzuge ich das alte AVR Studio in einer 
virtuellen Maschine mit Windows XP.

Ernsthafte Programme compiliere ich unter Linux im Terminalfenster mit 
einem Makefile. Als Editor nutze ich Qt Creator. Visual Studio Code tut 
es auch gut.

Debuggen kannst du AVR nach meinem Kenntnisstand nur mit den IDEs von 
Atmel/Microchip. Es gab mal Frickeleien mit Open-Source Tools (sorry, 
falls sich da jemand betroffen fühlt), die habe ich aber nicht ans 
Laufen gebracht.

Siehe http://stefanfrings.de/avr_tools/index.html

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.