Forum: Mikrocontroller und Digitale Elektronik Arduino: Problem mit Timer in loop


von Jonny H. (jonny123) Flattr this


Lesenswert?

Hallo,
ich möchte gerne LEDs mit Hilfe einer PWM ansteuern.
analogWrite liefert mir eine zu geringe Auflösung
Deswegen habe ich mich in das Thema Timer eingelesen und ein Testsketch 
erstellt. Dieses Sketch habe ich nun noch um das Thema LED-Fading 
(https://www.mikrocontroller.net/articles/LED-Fading)  erweitert.
Es läuft so weit auch ganz gut.

Aber in dem Testsketch wird die Helligkeit erstmal in einer For-Schleife 
erhöht.
Dann soll die LED für eine Sekunde ausgehen, damit mir signalisiert 
wird, dass die erste For-Schleife abgearbeitet ist. Die LED geht 
allerdings nur aus, wenn "Serial.println(ZeitHigh);" nicht 
auskommentiert ist. Danach wird die nächste For-Schleife ganz normal 
ausgeführt.

Kann mir vielleicht jemand sagen wieso die LED nur ausgeht, wenn 
"Serial.println(ZeitHigh);" aktiv ist? Ich habe auch schon ein delay 
direkt vor "ZeitHigh = 0;" dies hat aber auch nichts gebracht.

Der Code:
1
#define LampePin 9
2
int ZeitHigh = 0;
3
int PWMWert = 0;
4
int ZeitHighMax = 9950;       // eigentlich 10000 (bei 100 Hz), aber dies führt zu Unstabiität. Wahrscheinlich weil die ISR-Schleife ca 50µs zum Schalten der digitalen Pins benötigt
5
float HelligkeitMax = 5000;   // Warum muss dies ein Float sein? Wenn Int, dann werden von PWMWert nur die Extremwerte berechnet
6
7
int Basis = 200;              // Basis zur Anpassung an Menschliche Wahrnehmung -> je nach Empfinden einstellen
8
9
10
void setup()
11
{
12
  Serial.begin(9600);
13
  pinMode(LampePin, OUTPUT);
14
  digitalWrite(LampePin, LOW);
15
16
  cli(); // Interrupts deaktivieren
17
18
  // Reset
19
  TCCR1A = 0; // Setze TCCR1A Register auf 0
20
  TCCR1B = 0; // Setze TCCR1B Register auf 0
21
  TCNT1  = 0; // Zählerwert zurücksetzen
22
23
  OCR1A = 155.25; // Compare Match Register -> PWM Freqeunz: 100 Hz
24
25
  // Prescaler einstellen
26
  TCCR1B |= (1 << CS12) | (1 << CS10);
27
28
  TCCR1B |= (1 << WGM12); // Auf CTC-Mode setzen
29
  TIMSK1 |= (1 << OCIE1A); // Timer-Vergleichsinterrupt aktivieren
30
31
  sei(); // Interrupts zulassen
32
}
33
34
void loop()
35
{
36
37
  for (int i = 0; i < HelligkeitMax; i++)
38
  {
39
    PWMWert = ((pow(Basis, (i / HelligkeitMax)) - 1) / (Basis - 1)) * HelligkeitMax;    // Anpassung an Menschliche Wahrnehmung
40
    ZeitHigh = map(PWMWert, 0, HelligkeitMax, 0 , ZeitHighMax);                         // delayMicroseconds in ISR wird beschrieben
41
    //    Serial.print("PWMWert: ");
42
    //    Serial.print(PWMWert);
43
    //    Serial.print(" - Zähler: ");
44
    //    Serial.print(i);
45
    //    Serial.print(" - ZeitHigh: ");
46
    //    Serial.println(ZeitHigh);
47
    delay(1);
48
  }
49
  ZeitHigh = 0;                         // delayMicroseconds in ISR wird beschrieben
50
  Serial.println(ZeitHigh);
51
  delay(1000);
52
  for (int i = HelligkeitMax; i > 0; i--)
53
  {
54
    PWMWert = ((pow(Basis, (i / HelligkeitMax)) - 1) / (Basis - 1)) * HelligkeitMax;    // Anpassung an Menschliche Wahrnehmung
55
    ZeitHigh = map(PWMWert, 0, HelligkeitMax, 0 , ZeitHighMax);                         // delayMicroseconds in ISR wird beschrieben
56
    //        Serial.print("PWMWert: ");
57
    //        Serial.print(PWMWert);
58
    //        Serial.print(" - Zähler: ");
59
    //        Serial.print(i);
60
    //        Serial.print(" - ZeitHigh: ");
61
    //        Serial.println(ZeitHigh);
62
    delay(1);
63
  }
64
  delay(1000);
65
}
66
67
ISR(TIMER1_COMPA_vect)    // Diese Funktion wird beim Auftreten eines Interrupts von Timer 1 aufgerufen
68
{
69
  if (ZeitHigh == 0)digitalWrite(LampePin, LOW);
70
  else if (ZeitHigh == ZeitHighMax) digitalWrite(LampePin, HIGH);
71
  else
72
  {
73
    digitalWrite(LampePin, HIGH);
74
    delayMicroseconds(ZeitHigh);
75
    digitalWrite(LampePin, LOW);
76
  }
77
}

Vielen Dank.

MfG
Jonny

von Veit D. (devil-elec)


Lesenswert?

Hallo,

nulle/resete alle Timer Register, die sind nämlich mit anderen 
Einstellungen vorbelegt bei Arduino. Desweiteren hat ein delay in einer 
ISR nichts zu suchen. Das Duty Cycle stellst du über den Wert von OCR1x 
ein. Timerregister können nur Ganzzahlen aufnehmen. Also die 155.25 
werden zu 155.

Du musst den Timer mit einer festen Frequenz konfigurieren. Das geht 
entweder mit dem IRC1 oder OCR1A Register. Das Dutycycle dann entweder 
mit OCR1A oder OCR1B.

Frequenz bestimmt mit IRC1  >  PWM mit OCR1A und/oder OCR1B möglich
Frequenz bestimmt mit OCR1A >  PWM nur mit OCR1B möglich

Aktuell taktet der Timer, wenn er es so macht, mit 100Hz. Und du 
schaltest irgendwann die LED ein oder aus. Bedeutet, die mögliche hohe 
Auflösung verpufft komplett. Weil du überlässt das ja nicht den Timer.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

weil ich das schon einmal zusammengetragen hatte, kann ich das hier mal 
zu Besten.
1
Hallo,
2
3
nun denn. Die Frequenz habe ich jetzt mal mit 100Hz festgelegt.
4
5
Die Einstellungen sehen dann wie unten aus.  Wie kommt man darauf?
6
7
Timer 1
8
9
Fast-Modi gibt es mehrere. Tabelle auf Seite 172. Modi 5, 6, und 7 sind auf feste TOP Werte festgelegt.
10
Das heißt die Timerfrequenz ist in diesen drei Modi nur noch mittels Prescaler einstellbar.
11
12
Tipp, älteres Manual sind verständlicher wie das aktuelle.
13
14
Der flexibelste Modi ist immer der, aus meiner Sicht, wenn man einen verwenden kann wo man den TOP Wert in einem OCR1A Regsiter einstellen kann. Das ist nämlich gepuffert, dass heißt man kann den Wert live in seinem Programm ändern ohne den Timer stoppen/nullen zu müssen, wenn alles korrekt laufen soll mit dem Compare Match.
15
16
Wenn die Frequenz konstant bleibt, kann man für die TOP Wert Einstellung auch einen Modi nehmen wo ein ICR1 Register für TOP zuständig ist. Man könnte auch hier die Frequenz später im Programm ändern, hat nur im Code mehr Aufwand, wenn alles korrekt laufen soll, dass am Rande.
17
18
Wir nehmen Modi 15. Damit sind schon 2 Dinge festgelegt laut Tabelle Seite 172.
19
Das Frequenz bestimmende Register ist OCR1A und das Duty Cycle Register ist OCR1B.
20
Der Timer 1 beim 328P hat nur zwei OCR1x Register. A und B.
21
Einen verwenden wir als TOP, nämlich OCR1A, damit bleibt nur OCR1B.
22
23
Die LED Frequenz ist auf 100Hz festgelegt.
24
25
Jetzt benötigen wir den TOP Wert zusammen mit dem Prescaler der die 100Hz Frequenz erzeugt.
26
Dazu suchen wir uns die passende Formel raus.
27
Abschnitt Fast-PWM, Seite 164.
28
Die Formel stellen wir nach TOP um.
29
TOP = (CPU Takt / Prescaler / Takt ) - 1
30
31
Mit Prescaler 64 erhalten wir:   2499 ... etwas wenig für einen 16 Bit Counter
32
Mit Prescaler  8 erhalten wir:  19999 ... schon besser
33
Mit Prescaler  1 erhalten wir: 159999 ... zu viel, 16 Bit geht nur bis 65535
34
35
Also nehmen wir den Prescaler 8 und TOP Wert 19999.
36
Damit taktet der Timer schon einmal mit 100Hz.
37
38
Jetzt wollen wir aber den Pin bei 25% der Periodendauer abschalten.
39
Das heißt, wir brauchen noch einen zweiten Vergleichswert für OCR1B.
40
41
25% von unseren TOP 19999 sind 5000. Das ist der Wert für unser OCR1B Register.
42
43
Damit der Timer zugehörige Pin auch schaltet, müssen wir das noch aktivieren.
44
Und zwar das COM1B1 Bit, Seite 171, restliche Tabelle oben.
45
46
In welchen Registern befinden sich nun die vielen Bits die wir setzen müssen?
47
48
Nun, dazu müssen wir uns im Datenblatt die Registerbelegungen mit ihren Bits anschauen.
49
Das beginnt ab Seite 170.
50
51
Für Fast Mode 15 müssen wir alle 4 WGM1x Bits einschalten.
52
Die verstreuen sich auf 2 Register. Seite 170 und 173.
53
TCCR1A und TCCR1B.
54
55
Prescaler soll 8 sein, wird ebenfalls im Register TCCR1B eingestellt, Seite 173.
56
Das sind die CS Bits. Wir benötigen hier Bit CS11.
57
58
Mehr benötigen wir nicht, alles in Code umgesetzt sieht es dann so aus. Wichtig wäre noch das man in der IDE alle Register vorher nullt, weil die intern vorgeladen werden für analogWrite usw. Sonst macht der Timer nicht das was man möchte.

In diesem Bsp. müßtest du nur den Wert für OCR1B ändern und damit die 
Pulsweite/Helligkeit. Kannst natürlich auch den CTC Modus nehmen.
Desweiteren immer darauf achten das der Wert für die Pulsweite nie 
größer wird wie der Werte für TOP. Sonst gibts Aussetzer.
1
/*
2
  IDE 1.8.5
3
  Arduino Mega2560
4
  
5
  LED mit 25% Duty Cycle bei 100Hz
6
7
  Pinouts  >>> http://www.pighixxx.com/test/pinoutspg/boards/
8
  Uno      >>> http://www.pighixxx.com/test/portfolio-items/uno/?portfolioID=314
9
  Mega2560 >>> http://www.pighixxx.com/test/portfolio-items/mega/?portfolioID=314
10
*/  
11
12
// Mega
13
// const byte Takt_Pin = 12;     // OC1B bzw. PB6, nicht invertiert
14
15
// Uno
16
const byte Takt_Pin = 10;  // OC1B bzw. PB2, nicht invertiert
17
18
void setup()  {
19
  
20
  pinMode(Takt_Pin, OUTPUT);
21
  set_Timer1();
22
}
23
24
25
void loop() {
26
27
    
28
}   // loop Ende
29
30
31
// ****** Funktionen ******* //
32
    
33
void set_Timer1()   //  Fast-PWM, Mode 15
34
{
35
  cli();         // Interrupts ausschalten
36
  TCCR1A = 0;    // Reset TCCR1A Register
37
  TCCR1B = 0;    // Reset TCCR1B Register
38
  TIMSK1 = 0;    // Reset TIMSK1 Register (disable Timer Compare Interrupts)
39
  TCNT1  = 0;    // Start 0
40
  OCR1A = 19999; // TOP Wert bestimmt Auflösung und mit Prescaler den PWM Takt
41
  OCR1B = 5000;  // Pulsweite, OCR1B <= OCR1A
42
  TCCR1A = (1<<COM1B1) | (1<<WGM11) | (1<<WGM10);  // nicht invertiert
43
  TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11);    // Prescaler 8
44
  sei();         // Interrupts einschalten
45
}  // end Funktion

: Bearbeitet durch User
von Jim M. (turboj)


Lesenswert?

Jonny H. schrieb:
> Die LED geht
> allerdings nur aus, wenn "Serial.println(ZeitHigh);" nicht
> auskommentiert ist.

Da fehlt ein volatile:
1
volatile int ZeitHigh = 0;


Die Begründung liegt im C/C++ Optimizer, der ohne das Serial.println() 
den Wert gar nicht erst in die entsprechende Speicherstelle schreibt. 
Denn er kann nicht wissen das die ISR den lesen will.

Übrigens sollte man hier auch auf Probleme mit Atomizität achten, denn 
als int könnte die ISR schon mal einen Wert erwischen wo von den 2 Bytes 
erst eins geschrieben wurde.

von Jonny H. (jonny123) Flattr this


Lesenswert?

Vielen Dank für die schnellen Antworten.
Leider leuchtet die LED noch ganz leicht, wenn OCR1B 0 ist.Gibt es eine 
elegante Möglichkeit den Pin dann low zu setzen?

von Veit D. (devil-elec)


Lesenswert?

Hallo,

zeig mal deinen neuen kompletten Code.

: Bearbeitet durch User
von Jonny H. (jonny123) Flattr this


Lesenswert?

Habe den Code von dir kopiert und testweise OCR1B in void set_Timer1() 
Null-gesetzt .
Mehr habe ich nicht gemacht. Ausser halt noch ein paar sachen probiert, 
die aber leider nichts gebracht haben.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ja, das ist ein Problem im Fast PWM Mode, der erzeugt mit 0 immer kurze 
Peaks, wenn man sich das mit einem Oszi anschaut. Abhilfe schafft man 
entweder den Pin direkt auf 0 zu setzen oder einen anderen PWM Modus zu 
verwenden. Frequenz correct sollte das nicht mehr machen. Oder du 
invertierst das Signal im Fast pwm Modus. Dann hast du ein sauberes aus. 
Der umgekehrte Peak mit 100% Duty Cycle sollte nicht stören denke ich.

von Jonny H. (jonny123) Flattr this


Lesenswert?

Hallo,

Danke! Mit dem Invertieren läuft es. Ich habe vorher schon versucht den 
Pin LOW zu setzen, aber ich habe es nicht hinbekommen, danach wieder die 
PWM zu starten.

Zwei Fragen habe ich allerdings noch:
1.: Kann vielleicht jemand sagen, warum ich für "HelligkeitMax" den 
Datentyp float verwenden muss, damit es läuft?
int geht nicht. Eigentlich dürfte das doch nichts ausmachen, weil die 
Variable eh kein Komma hat und auch nicht verändert wird. Oder sehe ich 
das falsch?

2.: Gibt es eine Möglichkeit 3 Ausgänge mit dem Timer auf einem Arduino 
laufen zu lassen? Zwecks RGB-LEDs

Hier noch der aktuelle Code:
1
// Mega
2
#define Takt_Pin 12     // OC1B bzw. PB6
3
4
// Uno
5
//#define Takt_Pin 10  // OC1B bzw. PB2
6
7
int TimerTopWert = 19999;     // Topwert des Timers -> OCR1B auf Topwert -> 0V am Ausgang -> OCR1B auf 0 -> 5V am Ausgang (aufgrund Invertierung)
8
int PWMWert = 0;
9
float HelligkeitMax = 5000;   // Warum muss dies ein Float sein? Wenn Int, dann werden von PWMWert nur die Extremwerte berechnet
10
11
int Basis = 200;              // Basis zur Anpassung an Menschliche Wahrnehmung -> je nach Empfinden einstellen
12
13
14
void setup()
15
{
16
  pinMode(Takt_Pin, OUTPUT);
17
  set_Timer1();
18
}
19
20
void loop()
21
{
22
  for (int i = 0; i < HelligkeitMax; i++)
23
  {
24
    PWMWert = ((pow(Basis, (i / HelligkeitMax)) - 1) / (Basis - 1)) * HelligkeitMax;    // Anpassung an Menschliche Wahrnehmung
25
    OCR1B = map(PWMWert, HelligkeitMax, 0, 0, OCR1A);
26
    delay(1);
27
  }
28
  OCR1B = TimerTopWert;
29
  delay(2000);
30
  for (int i = HelligkeitMax; i > 0; i--)
31
  {
32
    PWMWert = ((pow(Basis, (i / HelligkeitMax)) - 1) / (Basis - 1)) * HelligkeitMax;    // Anpassung an Menschliche Wahrnehmung
33
    OCR1B = map(PWMWert, HelligkeitMax, 0, 0, OCR1A);
34
    delay(1);
35
  }
36
  delay(1000);
37
}
38
39
40
void set_Timer1()   //  Fast-PWM, Mode 15, 100 HZ
41
{
42
  cli();         // Interrupts ausschalten
43
  TCCR1A = 0;    // Reset TCCR1A Register
44
  TCCR1B = 0;    // Reset TCCR1B Register
45
  TIMSK1 = 0;    // Reset TIMSK1 Register (disable Timer Compare Interrupts)
46
  TCNT1  = 0;    // Start 0
47
  OCR1A = TimerTopWert; // TOP Wert bestimmt Auflösung und mit Prescaler den PWM Takt
48
  OCR1B = TimerTopWert;  // Pulsweite, OCR1B <= OCR1A -> auf Topwert gesetzt, damit Leuchte beim einschalten aufgrund der Invertierung nicht hell aufleuchtet
49
  //TCCR1A = (1 << COM1B1) | (1 << WGM11) | (1 << WGM10); // nicht invertiert
50
  TCCR1A = (1 << COM1B0) | (1 << COM1B1) | (1 << WGM11) | (1 << WGM10); // invertiert
51
  TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11); // Prescaler 8
52
  sei();         // Interrupts einschalten
53
}

Gruß
Jonny

von ratazong (Gast)


Lesenswert?

zu 1)
Wenn HelligkeitMax ein int ist kommt bei dem Ausdruck

 (i / HelligkeitMax)) (fast) immer 0 raus

(Zeile bei Dir)
PWMWert = ((pow(Basis, (i / HelligkeitMax)) - 1) / (Basis - 1)) * 
HelligkeitMax;

von Veit D. (devil-elec)


Lesenswert?

Hallo,

vermutlich haste einen Wertebereichsüberlauf mit int.
Nimm mal "unsigned int".
int ist nur immer bequem zum tippen. Für 16Bit Timer ungeeignet.
Ich denke du solltest alle int zu unsigned int ändern.

https://www.arduino.cc/reference/en/language/variables/data-types/unsignedint/

von Veit D. (devil-elec)


Lesenswert?

Hallo,

wegen der Ganzzahlrechnung, man kann mit Fließkomma rechnen, dann mit 
+0.5 runden und einen cast auf unsigned int machen.

Den ersten Wert in der Formel mit 1.0 multiplizieren, dann wird in float 
gerechnet.

ungetestet:
1
PWMWert = (unsigned int) ((((pow(Basis, (1.0*i / HelligkeitMax)) - 1) / (Basis - 1)) * HelligkeitMax) +0.5);

: Bearbeitet durch User
von Frank (Gast)


Lesenswert?

Jim M. schrieb:
> Jonny H. schrieb:
>> Die LED geht
>> allerdings nur aus, wenn "Serial.println(ZeitHigh);" nicht
>> auskommentiert ist.
>
> Da fehlt ein volatile:
> volatile int ZeitHigh = 0;
>
> Die Begründung liegt im C/C++ Optimizer, der ohne das Serial.println()
> den Wert gar nicht erst in die entsprechende Speicherstelle schreibt.
> Denn er kann nicht wissen das die ISR den lesen will.

Danke! Danke! Danke!
Ich war schon am Verzweifeln, warum meine ISR nicht funktionierte und 
Google führte mich zu diesem Thread. Mit 'volatile' vor meinen globalen 
Variablen (später wird das eine Klasse) funktioniert die ISR wie 
erwartet.
Danke! Danke! Danke!

von Stefan F. (Gast)


Lesenswert?

Mache einfache alle Variablen volatile, die sowohl außerhalb als auch 
innerhalb von ISR benutzt werden.

Es gibt zwar ein paar Spezialfälle, wo das nicht nötig ist, aber über 
die würde ich mir erst Gedanken machen, wenn eine Optimierung der 
Performance and dieser Stelle wirklich wirklich notwendig wird.

von Veit D. (devil-elec)


Lesenswert?

Stefan ⛄ F. schrieb:
> Mache einfache alle Variablen volatile, die sowohl außerhalb als auch
> innerhalb von ISR benutzt werden.

"Bin dagegen." Dann könnte man auch sagen nimm immer den größten 
Datentyp oder gleich Fließkomma, da passt alles rein.
Bitte nicht machen.

Die Nutzung von volatile hängt meistens auch sehr stark mit atomic 
zusammen. Damit sollte man sich schon einmal tiefer beschäftigen.

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.