Hallo zusammen,
Ich habe folgendes Problem. Ich will einen Servo Motor mittels PWM
ansteuern. Die Grundlagen dafür sind mir bekannt und ich habe auch eine
erste Ansteuerung bereits realisiert. Jedoch habe ich da die unschöne
Variante über delays benutzt. Das soll so nicht bleiben.
Da der Atmega8 ja über eine interne PWM verfügt möchte ich die im
folgenden verwenden. Trotz mehrmaligen lesens verschiedener
Forenbeiträge dazu bekomme ich es noch nicht hin die benötigten Register
sinnvoll zu setzen.
Zur Info:
Die Erzeugung des PWM Signals für den Servo geschieht über ein POTI das
mir über den ADC einen digitalen Wert zwischen 0 und 1024 gibt. Dieser
Wert liegt mir bereits vor:
0 soll dabei den linken Anschlag darstelle, sprich PWM Signal mit 1ms
High
512 den Mittelanschlag, sprich PWM Signal mit 1,5 ms High
und 1024 den rechten Anschlag 2ms High
Alle Werte dazwischen ergeben die entsprechenden PWM Signale
Meine Frage ist jetzt, hat jemand von euch schonmal den internen PWM
Modus vom Atmega8 benutzt und kann mir vllt sogar ein kleines
Beispielprogramm dazu schicken? (Ich weiß es ist unschön nach einem
fertigen Beispielprogramm zu fragen, aber daran versteh ich es immer am
besten)
Ich danke euch schon jetzt für die Mühe.
Ok Danke für Eure Hilfe:
Mein Code für ein PWM Signal sieht jetzt folgendermaßen aus:
1
#include<kamavr.h>
2
3
// PWM Frequenz: f_pwm = f_cpu/(2*Prescaler*TOP)
4
// f_pwm = 16MHz/(2*8*20000) = 50Hz
5
6
voidtimer_start()
7
{
8
TCCR1A=(1<<COM1A1)|(1<<WGM11);// Modus 14: Steuerung des Ausgangsport: Set at BOTTOM, Clear at match
9
TCCR1B=(1<<WGM13)|(1<<WGM12)|(1<<CS11);// Prescaler auf 8
10
11
12
ICR1=0x4E20;// den Endwert (TOP) für den Zähler setzen
13
// der Zähler zählt bis 20000
14
15
OCR1A=0x3FFF;// der Compare Wert
16
// Wenn der Zähler diesen Wert erreicht, wird mit
17
// obiger Konfiguration der OC1A Ausgang abgeschaltet
18
// Sobald der Zähler wieder bei 0 startet, wird der
19
// Ausgang wieder auf 1 gesetzt
20
//
21
// Durch Verändern dieses Wertes, werden die unterschiedlichen
22
// PWM Werte eingestellt.
23
}
24
25
26
voidmain(void)
27
{
28
timer_start();// Timer starten
29
DDRB=(1<<PB1);// Pin OC1A auf Ausgang, gibt das Servo Signal aus
30
31
while(TRUE)
32
{}
33
}
Wird mir so nun ein PWM Signal mit einer Frequenz von 50Hz am PIN OC1A
in Abhängigkeit von dem Wert in OCR1A erzeugt?
Oder sieht noch jemand einen Fehler?
Danke für Eure zahlreiche Hilfe
Du bekommst mit diesen Werten eine Wiederholrate von 100 Hz,
entsprechend 10ms, OC1A ist während 8,2ms High und 1,8ms Low. Also nicht
ganz richtig geraten :D
Auch gibt's ne Formel für CTC im ATM8 DB, schau die mal nach und rechne
damit, die 2 im Teiler muss weg, da hier nix getoggelt wird, sondern
eine komplette Schwingung innerhalb eines CTC Zyklus entsteht.
Dann siehst Du übrigens auch noch daß OCRn = fClock/fOCn -1 sein muss.
D.h., wäre 0x4E20 der fast korrekte Wert, so müsste er eigentlich 0x4E1F
lauten. Wobei ein Fehler um 1 hier nicht auffallen würde, das ist nur
für's Protokoll ;-)
Und noch was.
WEnn du das Register mit 20000 laden willst, dann schreib auch 20000
1
ICR1=20000;// den Endwert (TOP) für den Zähler setzen
2
OCR1A=1500:
Das ist doch Unsinn, wenn du da Hex-Schreibweise benutzt um dir dann im
Kommentar hinzuschreiben, was der Zahlenwert in Dezmal ist.
Man nimmt immer die Schreibweise, die dem Problem angemessen ist!
Hallo Herr Buchegger,
Danke für Ihre Hilfe. Tut mir leid wenn ich in in Ihren Augen vllt etwas
überflüssige Fragen stelle, aber ich bin Neuling auf dem Gebiet und
versuche mich jetzt nach und nach an die Probleme heran zu tasten.
Jedoch habe ich noch einige Fragen zu ihrer Hilfestellung.
1)
Warum bekomme ich eine Wiederholrate von 100Hz? Ich habe mich an die
Seite http://mil.ufl.edu/~achamber/servoPWMfaq.html gehalten. Dort wurde
es mit der Formel: f_pwm = f_cpu/(2*Prescaler*TOP)
berechnet. Diese Formel finde ich auch im Datenblatt wieder und die gibt
in meinen Augen genau 50Hz wenn man es nachrechnet?! Wo liegt da der
Fehler? Was muss ich da anders machen?
2)
Wo kommt diese Formel her? OCRn = fClock/fOCn -1
Was für einen Wert entspricht da f0Cn ??
3)
Die Dezimalschreibweise sehe ich ein. Aber ich weiß noch nicht genau
warum 20000 hier genau 20ms entspricht? Kann mir das noch wer erklären?
Habe den Wert dem vorherigen Link entnommen
Danke für Eure Mühe!
Auch wenn ich nicht der Herr Buchegger bin, aber ich hab' Dir das mit
den 100Hz und der Formel geschrieben :D
Die Formel zu CTC findest Du im ATMega8 Datenblatt, wenn's das
2486V–AVR–05/09 ist, dann auf Seite 89.
Faktor 2 wenn der Ausgang bei jedem CTC Durchlauf getoggelt wird, denn
dann braucht's 2 Durchläufe um eine komplette Schwingung zu erhalten.
Hier ist's aber anders, denn in einem CTC Durchlauf wird bei Compare
Match der Ausgang gesetzt und bei Zähler 0 wieder gelöscht. Damit findet
eine vollständige Schwingung in einem CTC Durchlauf statt.
Und deswegen muss die 2 raus.
@MWS
Sorry, dachte der Beitrag von Herrn Buchegger ist ein Nachtrag zu dem
vorherigen ;-)
Also ich hab auf Seite 89 lediglich die f_pwm = f_clocl / 2*N*(1+OCRA)
Aber ok, angenommen deine Formel stimmt. WIe muss ich dann jetzt meine
Register anpassen das ich einen 1,5ms HIGH Impuls bekomme und den Rest
der 20ms LOW habe??
So?
1
TCCR1A=(1<<COM1A1)|(1<<WGM11);// Modus 14: Steuerung des Ausgangsport: Set at BOTTOM, Clear at match
2
TCCR1B=(1<<WGM13)|(1<<WGM12)|(1<<CS11);// Prescaler auf 8
3
4
5
ICR1=20000;// den Endwert (TOP) für den Zähler setzen
6
// der Zähler zählt bis 20000
7
8
OCR1A=0x3FFF;// der Compare Wert
9
// Wenn der Zähler diesen Wert erreicht, wird mit
10
// obiger Konfiguration der OC1A Ausgang abgeschaltet
11
// Sobald der Zähler wieder bei 0 startet, wird der
12
// Ausgang wieder auf 1 gesetzt
13
//
14
// Durch Verändern dieses Wertes, werden die unterschiedlichen
Tobias F. schrieb:> 1)> Warum bekomme ich eine Wiederholrate von 100Hz? Ich habe mich an die> Seite http://mil.ufl.edu/~achamber/servoPWMfaq.html gehalten. Dort wurde> es mit der Formel: f_pwm = f_cpu/(2*Prescaler*TOP)> berechnet. Diese Formel finde ich auch im Datenblatt wieder und die gibt> in meinen Augen genau 50Hz wenn man es nachrechnet?! Wo liegt da der> Fehler? Was muss ich da anders machen?
Vergiss mal die Formeln, wir kriegen das auch ohne Formeln, nur durch
das Verständnis dessen, was bei deiner PWM passiert, raus. Ich bin
nämlich der fixen Überzeugung, dass es viel wichtiger ist, wenn du weißt
wie der Timer arbeitet als dass du eine Formel irgendwo abschreiben
kannst.
Die WGM Bits, die du eingestellt hast, ergeben eine Fast-PWM.
Im Datenblatt gibt es eine Tabelle, dort sieht man, dass deine gesetzten
WGM Bits den Modus 14, Fast PWM mit ICR1 als Top Wert ergeben
Was heißt das jetzt im Klartext.
Fast PWM bedeutet, dass der Timer einfach vor sich hinzählt. Wenn er bei
seiner Obergrenze angelangt ist, dann beginnt er wieder bei 0 (und macht
gleichzeitig eine Operation: Den Ausgangspin auf 1 Pegel bringen)
Wie weit zählt der Timer? So weit wie es ihm das ICR1 Register vorgibt.
Genau das ist die Bedeutung von 'ICR1 ist Top Wert'
Der Timer zählt also vor sich hin
0, 1, 2, 3, 4 ... 5000, 50001, 50002, ... , 19997, 19998, 19999
jetzt ist die Übereinstimmung mit dem ICR1 Register da, der Timer wird
auf 0 gesetzt (und gleichzeitig der Ausgangspin auf 1) und weiter gehst
0, 1, 2, ... 19997, 19998, 19999, 0, 1, 2, ... 19998, 19999, 0, ...
OK.
Nachdem jetzt klar ist, wie der Timer zählt, wollen wir uns mal dem
Timing zuwenden. Jeder dieser Zählschritte braucht ja eine gewisse Zeit.
Wie lang ist denn das?
Ganz einfach: Das ist zunächst einfach mal der CPU-Takt. Bei dir 16Mhz.
Aber: Da ist noch ein Vorteiler. In deinem Fall ist der 8.
D.h. nur bei jedem 8ten CPU-Takt wird er Timer um 1 weitergeschaltet.
Wenn nun in 1 Sekunde 16 Millionen CPU-Takte erfolgen, dann macht der
Timer in 1 Sekunde daher nur 2 Millionen Zählschritte (16/8).
D.h. der Timer würde in 1 Sekunde bis 2 Millionen zählen. Wenn er
könnte.
Kann er aber nicht. Denn wir haben ja schon gesehen: Jedes mal wenn der
Timer 19999 erreicht hat (also 20000 Zählschritte gemacht hat) wird er
wieder auf 0 zurückgesetzt.
Das heißt: Wenn wir wissen wollen, wie oft der Timer daher in 1 Sekunde
wieder auf 0 zurückgesetzt wird, dann rechnen wir
2 Millionen / 20000
und das macht 100
D.h. In 1 Sekunde beginnt der Timer 100 mal wieder bei 0 zu zählen.
Jedesmal wenn er das tut wird der Ausgang auf 1 gesetzt (und irgendwann
später wieder auf 0). Das heißt aber auch, du hast am Ausgangspin damit
100 komplette 'Schwingungen' in der Sekunde. Also 100Hz
Fazit: Entweder du, oder derjenige bei dem du abgeschrieben hast, hat
die falsche Formel benutzt.
So jetzt bist du dran:
Ganz ohne Formel: Welchen Wert musst du in ICR1 einstellen, damit der
Timer in 1 Sekunde nur 50 mal rundum zählen kann?
Oder anders ausgedrückt: Wie gross muss das x in
2 Millionen / x = 50
sein?
Tobias F. schrieb:> Also ich hab auf Seite 89 lediglich die f_pwm = f_clocl / 2*N*(1+OCRA)
Gut.
Jetzt nimm das Datenblatt, geh auf Seite 89, such die Formel und sieh
nach wie die Kapitelüberschrift von diesem Kapitel lautet.
Da steht "Clear Timer on Compare Match Mode"
Hast du diesen Modus?
Nein!
Du hast Fast-PWM
Nun, wie ich sehe wurde alles bereits bis in's Detail erläutert :-)
> f_pwm = f_clocl / 2*N*(1+OCRA)
Musst doch nur mal Deine Werte einsetzen und ausrechnen, wobei die
Formel aus den erklärten Gründen ohne 2 zu schreiben ist:
f_pwm = f_clock / N*(1+OCRA), wobei N = Prescaler = 8 ist.
damit ist: f = 16.000.000 / (8 * (20.000 +1)) = 99,995 Hz
Was glaubst Du, musst Du jetzt in OCRA einsetzen ?
Wobei ich gerade gesehen habe, daß ich die Formel mit OCRn = fClock/fOCn
-1
auch nicht korrekt umgestellt hab', hab' den Prescaler vergessen,
richtig ist:
OCRn = (fClock/(N*fOCn)) -1
wobei fOCn die gewünschte Frequenz ist, bzw. 1/fOCn die Periodendauer.
Ist das soweit korrekt?
Das ich mich jetzt im Fast PWM Mode befinde verstehe ich auch. Aber
warum muss ich die WGM13 und WGM12 in TCCR1B initialisieren und WGM11 in
TCCR1A???
oder ist das auch noch falsch?
> Aber warum muss ich die WGM13 und WGM12 in TCCR1B initialisieren und WGM11> in TCCR1A???
Weil der Controller-Hersteller diese Bits nun mal so plaziert hat ...
Ok dann hab ichs jetzt vollständig verstanden und konnte auch alles mit
dem Datenblatt nachvollziehen. Toll das man hier einem hier so
weitergeholfen wird. Als Newbe ist es halt am Anfang schon schwierig
sich da durchzuwühlen.
Also vielen Dank nochmal an Euch alle.
Tobias F. schrieb:> @ Hannes Lux> Danke für den Link.
Nichts zu danken...
> GIbt es den Code auch in C???
Nein, wie kommst Du darauf? Der AVR kann kein C, der C-Compiler
generiert auch nur ASM-Code (der dann in einem weiteren Pass in
Maschinencode assembliert wird). Da ich den AVR halbwegs kenne, C aber
kryptisches Neuland für mich wäre, spare ich mir den Umweg über C und
schreibe meine Programme gleich in Assembler. Also wird es von mir
keinen C-Quelltext geben.
Kurze Betrachtung zum Timer: Mein Programm läuft auf einem Mega48 (dem
Mega8 sehr ähnlich, ich nutze nur Features, die der Mega8 auch hat) mit
1MHz internem Takt (Du brauchst 16 MHz, ich vermute aus PC-Erfahrung,
"nur das Beste ist gerade gut genug").
Ich lasse Timer1 mit Controllertakt laufen, er zählt also jede µs einen
Schritt weiter. Timer1 hat zwei Compare-Einheiten (A und B). Eine
Compare-Einheit nutze ich, um alle 20ms ein neues Telegramm (für 7
Servos) zu beginnen. Die zweite Compare-Einheit nutze ich, um
nacheinander (wie bei einer echten RC-Anlage) die Impulsbreiten der
einzelnen Servos abzuzählen. Den Timer lasse ich dabei frei durchlaufen,
im Interrupt addiere ich einfach das gewünschte Intervall auf den Termin
des aktuellen Interrupts drauf, was dann als neuer Interrupt-Termin
gilt. Dies sieht im ersten Moment vielleicht komplizierter aus, ist es
aber nicht, denn der Timer läuft dabei frei durch (als Ring Modulo
65536), was mir die Möglichkeit gibt, auch noch den ICP-Interrupt
nebenher zu nutzen, um z.B. Servoimpulse einer RC-Anlage oder
DCC-Signale einer digitalen Modellbahnsteuerung einzulesen.
Die 20ms Telegrammabstand sind übrigens nicht so kritisch. In einem
Projekt für eine Modellbahnschranke steuert ein Mega8 zwei Servos (für
die Schrankenbäume) unabhängig voneinander, wobei die Telegramme nur
alle 64ms wiederholt werden um etwas Strom zu sparen. Der Mega8
generiert nebenher noch zwei unabhängige Blinklichter und gibt über
Hardware-PWM den Sound der Glocke aus (modifizierte WAV-Datei).
Achja, Formeln für Timer nutze ich nicht, das Verständnis für
Taktquelle, Vorteiler, Zählumfang, Interrupt bzw. Hardware-Anbindung
reicht mir dazu völlig aus. Klar, ich mach' das schon ein paar Jahre und
ich musste die entsprechenden Kapitel im Datenblatt mehrfach lesen um
die Zusammenhänge richtig zu verstehen, von nix kommt halt nix.
...
Zum Abschluss habe ich doch nochmal eine Frage:
Gibts es für einen Arm Controller LPC 21xx auch eine Fast PWM oder heißt
die da anders?
Ich besitze nämlich ein Testboard für AVR und ARM Controller.
Tobias,
also, ich hab' mich jetzt hingesetzt und es auf einem ATMega32 zum
Laufen gebracht. Die Register und Waveform Modes sind kompatibel zum
ATMega8. Der OC1A Pin ist allerdings woanders.
Verwendet habe ich Deinen Initialisierungscode hier:
1
TCCR1A=(1<<COM1A1)|(1<<WGM11);// Modus 14: Steuerung des Ausgangsport: Set at BOTTOM, Clear at match
2
TCCR1B=(1<<WGM13)|(1<<WGM12)|(1<<CS11);// Prescaler auf 8
Allerdings auf Bascom geschrieben, hab' zur Verdeutlichung die
Bitnotation verwendet.
Dieser Code erzeugt bei 8MHz Takt die 50Hz/20ms Wiederholrate, mit einem
positiven Puls von 1-2ms durchlaufend. (Zugegeben, der interne R/C läuft
bei meinem ATM32 auf 8270000Hz, den Fehler hab' ich rausgerechnet)
Da mein Controller mit internen R/C Oszillator eben auf ~8MHz läuft,
musst Du für 16MHz die Werte für ICR1/OCR1A auf 39999, resp. 3999
setzen.
Alles mit Oszi überprüft, ein Servo hab' ich auch mal schnell dran
gehängt, alles einwandfrei. ;-)