www.mikrocontroller.net

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


Autor: T. F. (n3ssaja)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: ohforf (Gast)
Datum:

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

Autor: ohforf (Gast)
Datum:

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

Autor: ohforf (Gast)
Datum:

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

Autor: T. F. (n3ssaja)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ok Danke für Eure Hilfe:

Mein Code für ein PWM Signal sieht jetzt folgendermaßen aus:
#include <kamavr.h>

// PWM Frequenz:   f_pwm = f_cpu/(2*Prescaler*TOP)
//                 f_pwm = 16MHz/(2*8*20000) = 50Hz

void timer_start()
{  
  TCCR1A = (1<<COM1A1) | (1<<WGM11);              //  Modus 14: Steuerung des Ausgangsport: Set at BOTTOM, Clear at match
  TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11);   // Prescaler auf 8


  ICR1 = 0x4E20;                //  den Endwert (TOP) für den Zähler setzen
                                //  der Zähler zählt bis 20000

  OCR1A = 0x3FFF;         // der Compare Wert
                          // Wenn der Zähler diesen Wert erreicht, wird mit
                          // obiger Konfiguration der OC1A Ausgang abgeschaltet
                          // Sobald der Zähler wieder bei 0 startet, wird der
                          // Ausgang wieder auf 1 gesetzt
                          //
                          // Durch Verändern dieses Wertes, werden die unterschiedlichen
                          // PWM Werte eingestellt.              
}


void main(void)
{
  timer_start();          // Timer starten
  DDRB = (1 << PB1 );       // Pin OC1A auf Ausgang, gibt das Servo Signal aus  

  while(TRUE)
  {}
}


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

Autor: MWS (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 ;-)

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Und noch was.

WEnn du das Register mit 20000 laden willst, dann schreib auch 20000
  ICR1 = 20000;                //  den Endwert (TOP) für den Zähler setzen
  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!

Autor: Hannes Lux (hannes)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> und kann mir vllt sogar ein kleines Beispielprogramm dazu schicken?

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

...

Autor: T. F. (n3ssaja)
Datum:

Bewertung
0 lesenswert
nicht 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!

Autor: T. F. (n3ssaja)
Datum:

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

Autor: MWS (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: T. F. (n3ssaja)
Datum:

Bewertung
0 lesenswert
nicht 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?
TCCR1A = (1<<COM1A1) | (1<<WGM11);        //  Modus 14: Steuerung des Ausgangsport: Set at BOTTOM, Clear at match
TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11);   // Prescaler auf 8


ICR1 = 20000;             //  den Endwert (TOP) für den Zähler setzen
                          //  der Zähler zählt bis 20000

OCR1A = 0x3FFF;           // der Compare Wert
                          // Wenn der Zähler diesen Wert erreicht, wird mit
                          // obiger Konfiguration der OC1A Ausgang abgeschaltet
                          // Sobald der Zähler wieder bei 0 startet, wird der
                          // Ausgang wieder auf 1 gesetzt
                          //
                          // Durch Verändern dieses Wertes, werden die unterschiedlichen
                          // PWM Werte eingestellt.              
}


Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: MWS (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: T. F. (n3ssaja)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
WOW dein vorheriger Beitrag war TOP!!
Ich glaube so langsam fällt der Cent ;-)
Also nach deiner Rechnung muss
ICR1 = 40000;   // Ergibt 50Hz beim eingestellten Prescaler
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?
TCCR1A = (1<<COM1A1) | (1<<WGM11);   
TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11);  

Autor: Spezi (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 ...

Autor: T. F. (n3ssaja)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Hannes Lux (hannes)
Datum:

Bewertung
0 lesenswert
nicht 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.

...

Autor: T. F. (n3ssaja)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: MWS (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
TCCR1A = (1<<COM1A1) | (1<<WGM11);        //  Modus 14: Steuerung des Ausgangsport: Set at BOTTOM, Clear at match
TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11);   // Prescaler auf 8
Allerdings auf Bascom geschrieben, hab' zur Verdeutlichung die 
Bitnotation verwendet.
$regfile = "m32def.dat"
$crystal = 8000000

Ddrd.5 = 1

             ' COM1A1 COM1A0  COM1B1 COM1B0   FOC1A   FOC1B   WGM11   WGM10
Tccr1a = &B______1______0_______0_______0_______0_______0_______1_______0
             ' ICNC1  ICES1     -      WGM13   WGM12   CS12    CS11    CS10
Tccr1b = &B______0______0_______0_______1_______1_______0_______1_______0

Icr1 = 19999
Ocr1a = 1999

Do

For Ocr1a = 999 To 1999
  Waitms 1
Next

Loop

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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.