Forum: Mikrocontroller und Digitale Elektronik Servo Ansteuerung mit interner PWM vom ATmega8


von T. F. (n3ssaja)


Lesenswert?

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.

von ohforf (Gast)


Lesenswert?

http://derjulian.net/mikrocontroller#servo
Ich hab keine Ahnung von Atmel Zeugs,aber der Link könnte helfen.

von ohforf (Gast)


Lesenswert?

Und das hier : http://mil.ufl.edu/~achamber/servoPWMfaq.html
Eine google suche hilft.

von ohforf (Gast)


Lesenswert?

Auch hier im Forum ist das Thema nicht neu.
Beitrag "Probleme mit PWM bei AtMega8"

von T. F. (n3ssaja)


Lesenswert?

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
void timer_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
void main(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

von MWS (Gast)


Lesenswert?

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 ;-)

von Karl H. (kbuchegg)


Lesenswert?

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!

von Hannes L. (hannes)


Lesenswert?

> und kann mir vllt sogar ein kleines Beispielprogramm dazu schicken?

Aber sicher doch...:
http://www.hanneslux.de/avr/mobau/7ksend/7ksend02.html

...

von T. F. (n3ssaja)


Lesenswert?

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!

von T. F. (n3ssaja)


Lesenswert?

@ Hannes Lux
Danke für den Link. GIbt es den Code auch in C???

von MWS (Gast)


Lesenswert?

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.

von T. F. (n3ssaja)


Lesenswert?

@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
15
                          // PWM Werte eingestellt.              
16
}

von Karl H. (kbuchegg)


Lesenswert?

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?

von Karl H. (kbuchegg)


Lesenswert?

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

von MWS (Gast)


Lesenswert?

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.

von T. F. (n3ssaja)


Lesenswert?

WOW dein vorheriger Beitrag war TOP!!
Ich glaube so langsam fällt der Cent ;-)
Also nach deiner Rechnung muss
1
ICR1 = 40000;   // Ergibt 50Hz beim eingestellten Prescaler
2
OCR1A = 3000;   // Entspricht 1,5ms => Servo Mittelstellung

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?
1
TCCR1A = (1<<COM1A1) | (1<<WGM11);   
2
TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11);

von Spezi (Gast)


Lesenswert?

> 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 ...

von T. F. (n3ssaja)


Lesenswert?

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.

von Hannes L. (hannes)


Lesenswert?

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.

...

von T. F. (n3ssaja)


Lesenswert?

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.

von MWS (Gast)


Lesenswert?

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.
1
$regfile = "m32def.dat"
2
$crystal = 8000000
3
4
Ddrd.5 = 1
5
6
             ' COM1A1 COM1A0  COM1B1 COM1B0   FOC1A   FOC1B   WGM11   WGM10
7
Tccr1a = &B______1______0_______0_______0_______0_______0_______1_______0
8
             ' ICNC1  ICES1     -      WGM13   WGM12   CS12    CS11    CS10
9
Tccr1b = &B______0______0_______0_______1_______1_______0_______1_______0
10
11
Icr1 = 19999
12
Ocr1a = 1999
13
14
Do
15
16
For Ocr1a = 999 To 1999
17
  Waitms 1
18
Next
19
20
Loop
21
22
End

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. ;-)

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.