Forum: Compiler & IDEs Problem mit PWM Timer1 - kurze Blitze beim ein-/ausschalten


von Florian (Gast)


Lesenswert?

Hallo allerseits,

ich habe eine Lampe gebaut, in der eine recht helle LED von einem 
ATmega8 via MOSFET gedimmt wird.

Der Mega8 erzeugt das PWM-Signal mit Timer 1:
1
void PWM_Init(void)
2
{
3
  // Non-inverted PWM on OC1A, 16 Bit Fast PWM
4
  TCCR1A = (1<<COM1A1) | (1<<WGM11);
5
  TCCR1B = (1<<WGM13) | (1<<WGM12);
6
7
  // Precaler of 1 -> ~122 Hz PWM frequency
8
  TCCR1B &= ~((1<<CS12) | (1<<CS11));
9
  TCCR1B |= (1<<CS10);
10
11
  // TOP for PWM, full 16 Bit
12
  ICR1 = 0xFFFF;
13
}

Um die Helligkeit zu setzen gibt es folgende Funktion, die passend zur 
Helligkeitsstufe einen 16-Bit-Wert aus dem Speicher lädt (Stichwort 
lineare Helligkeit für's Auge durch exponentiellen PWM-Anstieg):
1
void setBrightness(uint8_t bright)
2
{
3
  if(bright == 0)
4
    PWM_off();
5
  else
6
    PWM_on();
7
8
  // Set the PWM for the brightness
9
  OCR1A = pgm_read_word(PWM_TABLE + bright);
10
11
  // Store the current brightness
12
  brightness = bright;
13
}

Der Getter ist eher unbedeutend, soll der Vollständigkeit halber aber 
erwähnt werden:
1
// Get the current brightness
2
uint8_t getBrightness(void)
3
{
4
  return brightness;
5
}

Da merkwürdigerweise bei OCR1A=0 die LED noch minimal leuchtet, habe ich 
zwei Zusatzfunktionen PWM_off() und PWM_on() geschrieben. PWM_off() soll 
die Verbindung zum Pin OC1A kappen und somit den Pin PB1 auf GND ziehen. 
Das funktioniert auch hervorragend. PWM_on() soll den Timer wieder mit 
dem Pin verbinden und somit wieder das gepulste Signal ausgeben.
1
// Turn the PWM signal on
2
void PWM_on(void)
3
{
4
  // Reenabling the PWM
5
  TCCR1A |= (1<<COM1A1);
6
}
7
8
// Turn the PWM signal off
9
void PWM_off(void)
10
{
11
  // Disabling the PWM
12
  TCCR1A &= ~(1<<COM1A1);
13
}

Nun zum Problem...
Wenn ich die Helligkeit auf 0 gesetzt habe und anschließend langsam die 
Helligkeit anhebe (0, 1, 2, ...), dann gibt es ab und zu direkt nach 0 
zuerst einen sehr hellen Blitz und dann wird erst gedimmt.
Ich konnte den Fehler auf PWM_on() bzw. somit auch PWM_off() einkreisen.
Könnt ihr mir vielleicht eine elegantere Möglichkeit nennen, wie man den 
Pin vollständig auf GND ziehen kann, ohne dass ein "Blitz" beim 
Wiedereinschalten auftaucht?
Es scheint ja so zu sein, dass der Pin für einen Bruchteil einer Sekunde 
auf Vcc liegt...

Besten Dank für eure Hilfe!

Viele Grüße
Florian

von anfaenger (Gast)


Lesenswert?

Moin,

Ich würd tippen, dass du mit COM1A1 ein Clear on Compare-Match hast.

Das heißt, dein PWM ist AN, und geht AUS, wenn er den Compare-Wert hat. 
Heißt, er ist AN, prüft, welche Zahl vorliegt, sieht eine null, und 
schaltet dann AUS.

Kann sein, muss aber nicht.

Lösung: Du machst auch COM1A0 auf 1. Dann hast du ein Set on Compare 
Match. Problem ist, dass du dann deine Tabelle einmal invertieren 
musst... Eleganter fällts mir aber nicht ein.

von anfaenger (Gast)


Lesenswert?

Gerade noch eine Idee:
Wenn du den Pin kappst, bin ich mir gerad nicht sicher, was er mit dem 
OCR-Register macht. Könnte sein, dass er da noch einen Wert gespeichert 
hat, ab dem er dann weiterzählt, sobald du den Pin wieder "ranklemmst". 
Somit ist die LED dann für einen PWM-Zyklus noch an.

also vielleicht das OCR-Register beim Ausschalten auch auf 0 setzen.

von Stefan E. (sternst)


Lesenswert?

Florian schrieb:
> Da merkwürdigerweise bei OCR1A=0 die LED noch minimal leuchtet,

Der Grund ist dieses:
1
The extreme values for the OCR1x Register represents special cases when
2
generating a PWM waveform output in the fast PWM mode. If the OCR1x is set
3
equal to BOTTOM, the output will be a narrow spike for each MAX+1 timer
4
clock cycle.

Lösungsalternativen:

1)
Invertierten Output nehmen und dann:
1
OCR1A = 0xffff - pgm_read_word(PWM_TABLE + bright);
(oder halt die Werte in der Tabelle gleich entsprechend setzen)

2)
"Phase and Frequency Correct PWM Mode" verwenden.

von Florian (Gast)


Lesenswert?

Hallo anfaenger (so würde ich Dich nicht bezeichnen ;) ),
Hallo Stefan,

vielen Dank für eure Hilfe!

Ich habe den Code nun folgendermaßen geändert, zum Einen habe ich den 
invertierten Modus von Fast PWM verwendet und COM1A0 gesetzt:
1
void PWM_Init(void)
2
{
3
  // Inverted PWM on OC1A, 16 Bit Fast PWM, ICR1 is TOP value
4
  TCCR1A = (1<<COM1A1) | (1<<COM1A0) | (1<<WGM11);
5
  TCCR1B = (1<<WGM13) | (1<<WGM12);
6
7
  // Precaler of 1 -> ~122 Hz PWM frequency
8
  TCCR1B &= ~((1<<CS12) | (1<<CS11));
9
  TCCR1B |= (1<<CS10);
10
11
  // TOP for Fast PWM
12
  ICR1 = 0xFFFF;
13
14
  // Turn the lamp off
15
  setBrightness(BRIGHT_OFF);
16
}

Zum Anderen habe ich wie vorgeschlagen erst einmal OCR1A invertiert, 
indem ich von 0xFFFF abgezogen habe:
1
void setBrightness(uint8_t bright)
2
{
3
  /*if(bright == 0)
4
    PWM_off();
5
  else
6
    PWM_on();*/
7
8
  // Set the PWM for the brightness
9
  OCR1A = (0xFFFF - pgm_read_word(PWM_TABLE + bright));
10
11
  // Store the current brightness
12
  brightness = bright;
13
}

Es funktioniert und meine Lösung mit PWM_on() und PWM_off() ist nicht 
mehr notwendig...
Nun ist mir aber aufgefallen, wenn ich die Elektronik einschalte, dann 
blitzt die LED bei jedem Reset erst einmal kurz auf.
Wie kann ich das verhindern?

Viele Grüße und nochmals besten Dank!
Florian

von Karl H. (kbuchegg)


Lesenswert?

Florian schrieb:

> Nun ist mir aber aufgefallen, wenn ich die Elektronik einschalte, dann
> blitzt die LED bei jedem Reset erst einmal kurz auf.
> Wie kann ich das verhindern?

Wenn du nach solchen Sachen suchst, dann musst du dir im Detail klar 
machen, was jede einzelne Anweisung bewirkt und wie die Hardware darauf 
reagieren wird.

zb hier

void PWM_Init(void)
{
  // Inverted PWM on OC1A, 16 Bit Fast PWM, ICR1 is TOP value
  TCCR1A = (1<<COM1A1) | (1<<COM1A0) | (1<<WGM11);
  TCCR1B = (1<<WGM13) | (1<<WGM12);

  // Precaler of 1 -> ~122 Hz PWM frequency
  TCCR1B &= ~((1<<CS12) | (1<<CS11));
  TCCR1B |= (1<<CS10);

  // TOP for Fast PWM
  ICR1 = 0xFFFF;



Der Code beginnt damit, dass du den Ausgabepin an den Timer übergibst, 
und dass du den Timer in den PWM Modus schaltest.

Aber: Dadurch dass du den Timer in den PWM Modus schaltest fängt der 
schon mit seiner PWM-Arbeit an. Die besteht darin, dass er den Inhalt 
des Compare Registers mit dem Timer-Zählregister vergleicht und bei 
Übereinstimmung schaltet der den Pin.

Das tun deine Register aber! Dein Compare Register hat seinen 
Einschaltwert, der da ist: 0
Und das Zählregister hat auch noch seinen Einschaltwert, der da ist: 0

Das wiederrum bedeutet, dass du sofort einen Compare Match hast und die 
Hardware macht das, was sie in so einem Fall machen muss: Sie schaltet 
den Pin.

Danach gibst du den Vorteiler für den Timer frei. Und der Timer beginnt 
mit seiner Zählung. Durch den PWM Modus wird ihm aufgetragen bis zum 
Wert in ICR1 zu zählen. Wenn du den Prescaler frei gibst, welchen Wert 
hat denn ICR1? Richtig. immer noch seinen Einschaltwert, der da war: 0
Und wieder greift die PWM-Hardware und macht, was sie machen muss: sie 
setzt den Timer wieder auf 0 und schaltet den Pin.


Also: Darauf achten in welcher Reihenfolge die Dinge passieren! In 
welcher Reihenfolge müssen welche Register befüllt werden, damit am 
Anfang nichts schief geht. Wenn automatische Hardware Aktionen von 
Registern abhängen, dann ist es eine ziemlich gute Idee erst einmal 
dafür zu sorgen, dass die Register vernünftige Anfangswerte haben, ehe 
man die Funktionalität aktiviert.

Und ja: Das kann manchmal aus dem Datenblatt etwas tricky herauszulesen 
sein, weil oft nicht ganz klar ist, wann genau jetzt eine bestimmte 
Hardwareaktion gemacht wird. Wird der Registervergleich gemacht, nachdem 
der Timer um 1 hochgezählt hat und wird dieser Vergleich nur einmal 
gemacht oder werden die beiden ständig miteinander verglichen?

von Florian (Gast)


Lesenswert?

Hallo Karl,

so scheint es gut zu funktionieren! :)
Besten Dank an alle!!!

Gibt es an dieser Reihenfolge noch Optimierungsvorschläge, oder ist die 
Reihenfolge nun so korrekt?
1
void PWM_Init(void)
2
{
3
  // Inverted PWM on OC1A, 16 Bit Fast PWM, ICR1 is TOP value
4
  TCCR1A = (1<<COM1A1) | (1<<COM1A0) | (1<<WGM11);
5
  TCCR1B = (1<<WGM13) | (1<<WGM12);
6
7
  // TOP for Fast PWM
8
  ICR1 = 0xFFFF;
9
10
  // Turn the lamp off
11
  setBrightness(BRIGHT_OFF);
12
13
  // Activate clock source and set precaler of 1 -> ~122 Hz PWM frequency
14
  TCCR1B &= ~((1<<CS12) | (1<<CS11));
15
  TCCR1B |= (1<<CS10);
16
}

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.