Forum: Mikrocontroller und Digitale Elektronik ATmega644 PWM Anfängerfragen


von Michael M. (aesis)


Lesenswert?

So jetzt wo der Sommer kommen sollte, dieser doch auf sich warten lässt, 
habe ich meinen ATmega 644 vom letzten Jahr mal wieder rausgekramt und 
mich der für mich unverständlichen Anleitung von Atmel durch das 
Datenblatt erneut zugewandt.

Ich wollt eig nur mal schaun, ob ich das mit ein paar Handgriffen sofort 
hin bekomme, doch hat mein Netzteil das 1 Jahr Lagerung mit einem 
Raucher kritisiert.

Da ich beim Manual zu 50% nur Bahnhof verstehe und mal hier mal da was 
von einem Prescaler lese, doch alle Happen verstreut sind, ist doch vll. 
was falsch.

Ich nutze nicht meinen externen 16MHz Oszi, da ich keine hohen 
Frequenzen brauche. Somit läuft der @ default bei 1MHz.

Ich möchte ein PWM von 100Hz generieren. Soll dann damit ein LED Treiber 
gespeist werden. Doch wie setzte ich den Timer 0 durch einen Prescaler 
von 1MHz auf 100Hz?

Laut Handbuch gilt die Formel: f_PWM = f_clock / (N*256)

Doch wie setze ich N? Wird das N durch CS0_2:0 aus dem Reg. TCCR0B 
gesetzt?

Bitte korrigiert mich, wenn in Folgendem Fehler sind ;)

So wie ich das sehe muss ich den "Fast PWM Mode" wählen.

Für den Timer 0 (TCNT0) bedeutet das also OCR0A und OCR0B als Output 
Comp. Reg.

So wie ich das lese gibt es hier die 2 Reg. weil der Timer0 2 PWM 
Outputs befeuern kann. Wenn ich also nur den einen Output brauche, 
reicht mit zBsp der OCR0A?

Die obere Grenze des Counters wird mit WGM2_2:0 definiert, was jetzt 
hier dann bei mir über das Reg OCR0A definiert werden soll. Also muss 
bei mir WGM2_2:0 = 7b sein?

Ohne das der Timer auf 100Hz läuft würde das wohl zunächst so 
aussschauen:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
// Register definieren
5
6
TCCR0A = (1<<WGM00) | (1<<WGM01)| (1<<COM0A1);
7
TCCR0B = (1<<WGM02) | (1<<CS01) | (1<<CS00);
8
9
// - ich will 100Hz -> CS0_2:0 = 7b -> ~1kH -> zuviel :((
10
11
12
// OC0A als PWM-Port definieren
13
14
    DDRB |= (1 << PB3);
15
16
OCR0A = 128; //50% PWM Bsp.
17
18
19
int main (void)
20
21
while( 1 ) {}

von Conny G. (conny_g)


Lesenswert?

Hallo Michael,

also ich hab' noch kein PWM verwendet, aber Timer und Prescaler.

Also zum PWM gibt es in meinem Verständnis 2 Schritte:
- mit dem Prescaler teilst Du die Prozessorfrequenz herunter um dann mit 
einem 8- oder 16 Bit Counter arbeiten zu können
- danach musst Du für's PWM einerseits über einen Counter die genaue 
Frequenz definieren, das wäre TCNT0, das bestimmt dann den gesamten 
PWM-Zyklus, d.h. die Länge desselben
- und im PWM-Zyklus brauchst Du dann den Duty Cycle, wann soll das 
Signal angehen und wann wieder aus, das sind OCR0A und OCR0B
- über Register-Flags kannst Du dann setzen, ob zum OCR0A und OCR0B 
bestimmte Output-Ports geschaltet werden, damit das Signal auch aus dem 
AVR rauskommt
- hier gibt's bestimmte Modi, ob nur Sägezahn-mässig (in bezug auf den 
Counter) geschaltet wird, also Hochzählen und dann auf "0" (Fast PWM) 
oder ob der Counter dann bei Max wieder runterzählt (Normal PWM)

Ansonsten sind die die WGM02 etc. nur Flags, d.h. gesetzte oder nicht 
gesetzte Bits, keine Counter-Werte.

Beispiel für das Ganze:
- Dein Prozessortakt ist 1 Mhz
- Du möchtest auf 100 Hz als Basisfrequenz für PWM kommen
- welchen Teiler würdest Du benötigen?
   1.000.000 / 100 = 10.000
   das ist ein 16-bit-Wert, der Timer0 ist aber 8 Bit, also musst Du per 
Prescaler runterteilen, schauen wir, ob Faktor 8 reicht, also:
   TCCR0B |= _BV( CS01 )       (unabhängig davon ob Du hier noch andere 
Bits brauchst)
   dann kommst also raus bei:
   1.000.000 / 8 = 125.000 als Basisfrequenz
   jetzt kommt der TCNT0:
    125.000 / 100 = 1.250    das ist immer noch 16 Bit, also noch eins 
mehr,
   den Teiler 64, also:
    TCCR0B |= _BV( CS00) | _BV ( CS01 )
   und die Basisfrequenz ist dann:
   1.000.000 / 64 = 15.625,
   wenn wir das nun mit dem TCNT0 weiter teilen wollen, brauchen wir 
folgenden Wert für TCNT0:
   15625 / 100 = 156      das ist ein 8-bit wert und funktioniert

Jetzt bestimmen innerhalb des Zyklus von 156 Takten die OCR0A und OCR0B 
wann das Signal an und wieder ausgeht.

Soweit ich das verstehe, wie gesagt noch nicht gemacht, aber schon 
genügend mit Prescaler und TCNT0 etc. gearbeitet, nur PWM (also OCR0A 
und OCR0B) noch nicht verwendet.

VG,
Konrad

von Conny G. (conny_g)


Lesenswert?

Halt, falsch, da hattest Du Recht, dass oder Ocr0a und b für 2 Outputs 
sind. Für den Duty Cycle von A braucht man per Ocr0a nur den Punkt 
setzen, wo das Signal gesetzt wird und bei 0 wird es wieder 
zurückgesetzt.
Und dann gibt Fast Pwm und normal pwm ob der tcnt0 beim erreichen von 
ocr0a auf Null gesetzt wird oder wieder runter zählt.
Das weiß ich jetzt aber wirklich nicht mehr, das ist jetzt geraten ohne 
das Datasheet nochmal zu lesen.

von Karl H. (kbuchegg)


Lesenswert?

Michael M. schrieb:

> Ich möchte ein PWM von 100Hz generieren.

Warum genau 100Hz?

> Soll dann damit ein LED Treiber
> gespeist werden.

Den LED ist das doch völlig wurscht, ob das 100Hz oder auch mehr sind. 
Für dich als Mensch darf die PWM Frequenz nicht zu gering sein, weil es 
dann zu flimmern beginnt. Ist die Frequenz aber höher, dann stört das 
keinen.

> Doch wie setzte ich den Timer 0 durch einen Prescaler
> von 1MHz auf 100Hz?

gar nicht.


> So wie ich das sehe muss ich den "Fast PWM Mode" wählen.

Aus deinen Betrachtungen hast du schon gesehen: nur mit der voegegebenen 
PWM und nur dem Vorteiler kriegst du keine exakten 100Hz.

2 Möglichkeiten
* du akzeptierst, dass du die 100Hz nicht exakt erreichst und nimmst 
einfach die nächst höhere Frequenz, die sich aus Takt und geeignetem 
Vorteiler ergibt
* oder aber du benutzt nicht den Fast-PWM Modus, der immer bis zu einer 
vorgegebenen Bitzahl arbeitet, sondern suchst dir einen der beiden 
Fast-PWM Modi aus, bei denen du in einem Register die Zählgrenze 
vorgeben kannst, wenn dein µC dies erlaubt

Da es sich bei LEDs um nichts frequenzkritisches handelt, würde ich den 
ersten Weg gehen. Aber die Entscheidung liegt bei dir.


> Für den Timer 0 (TCNT0) bedeutet das also OCR0A und OCR0B als Output
> Comp. Reg.
>
> So wie ich das lese gibt es hier die 2 Reg. weil der Timer0 2 PWM
> Outputs befeuern kann.

Jein. Das ist eine Folge. Dieser Timer hat halt einfach 2 Compare Match 
Register.

> Wenn ich also nur den einen Output brauche,
> reicht mit zBsp der OCR0A?

Zum Beispiel


> Die obere Grenze des Counters wird mit WGM2_2:0 definiert, was jetzt
> hier dann bei mir über das Reg OCR0A definiert werden soll. Also muss
> bei mir WGM2_2:0 = 7b sein?

Im Datenblatt, beim Timer unter "Register Summary" gibt es eine schöne 
Tabelle mit allen Modi, die der Timer kann. Da suchst du dir die 
Belegung der WGM Bits raus für den Modus, den du haben willst.

> Ohne das der Timer auf 100Hz läuft würde das wohl zunächst so
> aussschauen:

> #include <avr/io.h>
> #include <avr/interrupt.h>
>
> // Register definieren
>
> TCCR0A = (1<<WGM00) | (1<<WGM01)| (1<<COM0A1);
> TCCR0B = (1<<WGM02) | (1<<CS01) | (1<<CS00);
>
> // - ich will 100Hz -> CS0_2:0 = 7b -> ~1kH -> zuviel :((


Wieso der :((

Für LEDS ist das doch völlig wurscht, ob 1kHz oder 100Hz


> // OC0A als PWM-Port definieren
>
>     DDRB |= (1 << PB3);
>
> OCR0A = 128; //50% PWM Bsp.
>
>
> int main (void)
>
> while( 1 ) {}


Alles schön und gut. Aber das ist kein C Programm. Das ist eine 
Anhäufung von "Worten", die so auch in C gibt, aber C-Programme haben 
auch sowas wie eine Grammatik an die du dich halten musst.

von Karl H. (kbuchegg)


Lesenswert?

Konrad G. schrieb:

> Beispiel für das Ganze:
> - Dein Prozessortakt ist 1 Mhz
> - Du möchtest auf 100 Hz als Basisfrequenz für PWM kommen
> - welchen Teiler würdest Du benötigen?
>    1.000.000 / 100 = 10.000
>    das ist ein 16-bit-Wert, der Timer0 ist aber 8 Bit, also musst Du per
> Prescaler runterteilen, schauen wir, ob Faktor 8 reicht, also:
>    TCCR0B |= _BV( CS01 )       (unabhängig davon ob Du hier noch andere
> Bits brauchst)
>    dann kommst also raus bei:
>    1.000.000 / 8 = 125.000 als Basisfrequenz
>    jetzt kommt der TCNT0:
>     125.000 / 100 = 1.250    das ist immer noch 16 Bit, also noch eins
> mehr,
>    den Teiler 64, also:
>     TCCR0B |= _BV( CS00) | _BV ( CS01 )
>    und die Basisfrequenz ist dann:
>    1.000.000 / 64 = 15.625,


Ohne das jetzt nachgerechnet zu haben: Bis hier her ist die 
Grundüberlegung in Ordnung.

>    wenn wir das nun mit dem TCNT0 weiter teilen wollen, brauchen wir
> folgenden Wert für TCNT0:
>    15625 / 100 = 156      das ist ein 8-bit wert und funktioniert

Hier hast du einen entscheidenden Fehler. Du kannst nicht mit TCNT0 
weiterteilen. TCNT0 ist das Register in dem gezählt wird. Das ist der 
eigentliche Timer-Kern.

Es gibt aber Timer-Modi, bei denen dieses TCNT0 Register zb mit dem 
OCR0A verglichen wird und wenn beide übereinstimmen, dann wird TCNT0 
wieder auf 0 gesetzt. Man nennt das den CTC-Modus.

Und. Dasselbe gibt es auch in der PWM-Version. D.h. deine 156 kommen zb 
ins OCR0A Register, so dass sich in TCNT0 wieder die Zählesequenz 0, 1, 
2, ... , 154, 155, 0, 1, 2, ... ergibt.
Nachteil: Damit fällt das OCR0A Register zur Erzeugung der PWM aus und 
man kann nur noch mit OCR0B eine PWM erzeugen.

von Karl H. (kbuchegg)


Lesenswert?

FAQ: Timer

Da werden die Zusammenhänge vielleicht etwas klarer.
Denkt nicht zu komplizert. Das ganze ist in Wirklichkeit extrem einfach. 
Muss es auch sein, wenn es in Silizium gegossen werden kann :-)

von Michael M. (aesis)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Michael M. schrieb:
> Alles schön und gut. Aber das ist kein C Programm. Das ist eine
> Anhäufung von "Worten", die so auch in C gibt, aber C-Programme haben
> auch sowas wie eine Grammatik an die du dich halten musst.

Scheint ja so, als wäre meine Überlegung aus dem Manual zum µC mal 
richtig.

Also kann ich wirklich nur den Takt mit einem Prescaler von max. 1024 
herabsetzen, was 1kHz entspricht.

Ich habe noch gelernt, dass LEDs frequenzkritische Bauteile sind und mit 
zunehmender Frequenz schneller altern. Gerade bei Hochleistungs-LEDs.

Ob da jetzt schon meine 1W LEDs dazu zählen weiß ich nicht.

Was meinst du jetzt wegen C?

Versteht der Compiler das nicht?

von spess53 (Gast)


Lesenswert?

Hi

>Also kann ich wirklich nur den Takt mit einem Prescaler von max. 1024
>herabsetzen, was 1kHz entspricht.

Nein, da kommt eine PWM-Frequenz von ca. 3,8 Hz raus (1MHz/1024/256). 
Einen Wert in der Nähe von 100 bekommst du z.B. wenn du den Controller 
mit 8MHz (CKDIV8-Fuse löschen) laufen lässt und einen Prescaler von 256 
nimmst. Das gibt dann 122 Hz.

MfG Spess

von Michael M. (aesis)


Lesenswert?

Wo kommen denn jetzt die 256 her? Hab ich nirgends eingestellt. Da ich 
kein Oszi hab kann ich das leider nicht prüfen.

von spess53 (Gast)


Lesenswert?

Hi

>Wo kommen denn jetzt die 256 her? Hab ich nirgends eingestellt.

Das ist ein 8-Bit-Timer. Und der braucht für eine PWM-Periode genau 256 
Takte vom Vorteiler.

> Da ich kein Oszi hab kann ich das leider nicht prüfen.

3,8 Hz sieht man deutlich als Blinken.

MfG Spess

von Michael M. (aesis)


Lesenswert?

Ah ja verdammt stimmt. Na dann passt das ja doch ganz nett :).

Jetzt muss ich nur noch schaun, dass der Syntax stimmt und dann wirds 
mal getestet. Hol heute noch ein neues Netzteil.

von Conny G. (conny_g)


Lesenswert?

> Hier hast du einen entscheidenden Fehler. Du kannst nicht mit TCNT0
> weiterteilen. TCNT0 ist das Register in dem gezählt wird. Das ist der
> eigentliche Timer-Kern.
>
> Es gibt aber Timer-Modi, bei denen dieses TCNT0 Register zb mit dem
> OCR0A verglichen wird und wenn beide übereinstimmen, dann wird TCNT0
> wieder auf 0 gesetzt. Man nennt das den CTC-Modus.

Hast Recht! Bisher hab ich nur CTC verwendet...

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.