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
intZeitHigh=0;
3
intPWMWert=0;
4
intZeitHighMax=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
floatHelligkeitMax=5000;// Warum muss dies ein Float sein? Wenn Int, dann werden von PWMWert nur die Extremwerte berechnet
6
7
intBasis=200;// Basis zur Anpassung an Menschliche Wahrnehmung -> je nach Empfinden einstellen
8
9
10
voidsetup()
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
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.
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.
Jonny H. schrieb:> Die LED geht> allerdings nur aus, wenn "Serial.println(ZeitHigh);" nicht> auskommentiert ist.
Da fehlt ein volatile:
1
volatileintZeitHigh=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.
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?
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.
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.
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
intTimerTopWert=19999;// Topwert des Timers -> OCR1B auf Topwert -> 0V am Ausgang -> OCR1B auf 0 -> 5V am Ausgang (aufgrund Invertierung)
8
intPWMWert=0;
9
floatHelligkeitMax=5000;// Warum muss dies ein Float sein? Wenn Int, dann werden von PWMWert nur die Extremwerte berechnet
10
11
intBasis=200;// Basis zur Anpassung an Menschliche Wahrnehmung -> je nach Empfinden einstellen
12
13
14
voidsetup()
15
{
16
pinMode(Takt_Pin,OUTPUT);
17
set_Timer1();
18
}
19
20
voidloop()
21
{
22
for(inti=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(inti=HelligkeitMax;i>0;i--)
31
{
32
PWMWert=((pow(Basis,(i/HelligkeitMax))-1)/(Basis-1))*HelligkeitMax;// Anpassung an Menschliche Wahrnehmung
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
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;
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:
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!
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.
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.