PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Servo-Steuerung



Spongebob85
09.10.2007, 23:43
Moin!
Ich habe mir an meinen Roboter letztens einen Servo gebaut. Das Problem was ich jetzt hab ist die ansteuerung. Erstmal stellt sich mir die Frage ob ich den direkt an mit dem Taktanschluß an meinen uC schließen kann, aber denke schon, oder?

Die Programmierung hab ich folgendermaßen gemacht:


//-----PWM-Inizialisierung-----
DDRD |= (1<<PWM_ServoPort);
TCCR2 |= ((1<<WGM20) | (1<<WGM21)); //Fast PWM
TCCR2 |= (1<<COM1); //Clear OC0 on Compare Match, Set OC0 on Top
TCCR2 |= ((1<<CS00) | (1<<CS02)); // CLK/1024 = 15625Hz (15625p/s), 1p=64µs


dann muss ich mir ja jetzt irgendwie ausrechen wo ich diesen OCR2 setzen muss, damit ich 1-2ms impulse bekomme. Die Periodenlänge am ausgang bleibt ja immer 64µs, bloß Tein und Taus sind ja unterschiedlich. Irgendwie steh ich jetzt aufm Schlauch. Geht die PWM-Methode vielleicht gar nicht? Gibts bessere Möglichkeiten? Würde mich über Vorschläge echt freuen!

MfG Jan

Zimble
10.10.2007, 12:59
Gut, dass ich diese Woche grade an meiner 3 Kanal Servoansteuerung saß :)

Den Taktanschluß steckst du direkt an den PWM des Atmel. Beim Mega 8 sind das PB1,2,3.

Dann solltest du einen Prescaler und ein entsprechend breites Vergleichsregister wählen, damit du auf eine Periode von etwa 20ms kommst.

Ich betreibe bspw. gerade einen Mega8 mit 12MHz. 256er Prescaler und 9Bit PWM machen dann: 1 / (12.000.000 / 256 / 512) * 2 = 0.0218s

Den OCR rechne ich indem ich die maximale Pulsbreite die der Servo hat durch 180 teile und mit der gewünschten Gradzahl multipliziere. Das ergibt die Pulsbreite für die gewünschte Position in ms. Und danach:

OCR1A = 512 - Pulsbreite * F_CPU/256/1000000 / 2
(also Breite * Zeit für einen Tick durch 2, weil er ja auch wieder runterzählt und das Ganze halt von den 9BIT abgezogen)

Hoffe, das hilft dir weiter. Ansonsten fragen :)

PS.: Ich betreibe die Servos in inverted PWM

Spongebob85
10.10.2007, 15:22
Dann solltest du einen Prescaler und ein entsprechend breites Vergleichsregister wählen, damit du auf eine Periode von etwa 20ms kommst.

Ein endsprechend breites vergleichsregister???
Wie heißt das denn bei dir? Ich hab im Datenblatt gar nichts davon gesehen. 8-[
Dann würde ich mein Prescaler auf CLK/512 stellen (CLK = 16MHz). Und dann auch einen 9 bit PWM nehmen.
Am ende würde ich dann auf eine Periode von 16,384ms kommen. Das wäre doch ok, oder?



also Breite * Zeit für einen Tick durch 2, weil er ja auch wieder runterzählt und das Ganze halt von den 9BIT abgezogen


Meiner Zählt glaub ich nur rauf.

Wenn ich meinen servo anschließe macht der keinen Mucks. Der zuckt nicht mal. Hab den aus einem alten Fernsteuerauto. Der is von Robbe...
Hoffe der läuft auch bei 5V.

MfG Jan

Spongebob85
10.10.2007, 15:37
Hab den grade an meinem Netzteil getestet. Heil is der. Wenn ich den PWM offen lasse und 5Vund Masse anschließe zuckt der.

MfG Jan

MartinFunk
10.10.2007, 15:57
hi, schau dir mal den code an:
https://www.roboternetz.de/wissen/index.php/Servos#Unterschied_zwischen_Digital-_und_Analogservos

Der code funktionierte bei mir mit bis zu 8 Servos!

MfG Martin

Zimble
10.10.2007, 16:12
Vergleichsregister... naja, ich meine halt den PWM-Mode. 8 o. 9 o. 10 Bit



Meiner Zählt glaub ich nur rauf.


Das wär mir wiederrum neu. Ich lass mich natürlich gerne eines besseren belehren.
Bei 16MHz müsstest du m.E. den Prescaler auf 256 und den Timer auf 9Bit nehmen. Damit zählt er also in 8.192ms einmal auf 512 (9Bit) hoch, danach in der gleichen Zeit wieder runter. Macht 16.384ms Periodendauer. Sollte hoffentlich für die Servos reichen.

-> ein Tick (Inkrement des Zählers) dauert 16.384/512 = 0.032ms
-> für eine Pulsdauer von beispielsweise 1.5ms: 1.5ms/0.032ms = 46.875 Ticks
-> das Ganze durch 2 Teilen und ins OCR reinschreiben: 46/2 = 23 = OCR2 oder bei inverted PWM: 512 - 23 = 489 = OCR2

Hast du dir schon mal die Grafiken auf http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Die_PWM-Betriebsart
angeschaut? Ich finde dort ist das Ganze relativ gut nachzuvollziehen.

Und nicht vergessen den Port zu aktivieren (da bin ich nämlich dran gescheitert) :)


DDRB |= (1 << PB3); //PB3 output
PORTB |= ( 1 << PB3 ); //enable PB3 Pull-up

Spongebob85
10.10.2007, 23:17
@Zimble:
Ich blick irgendwie nich richtig durch.
Hab im datenblatt wieder "bisschen" rumgesucht. Ich konnte nur enddecken das man die CPU Frequenz per vorteiler (prescaler) teilern kann. Dann hat man die Frequenz mit der Periodenlänge von Tein+Taus. Diese ein/ausschaltzeit kann man dann per OCR2 beliebig verändern. Wie heißt den das Register bzw. die Bits mit denen du den Timer auf z.B. 9Bit nehmen kannst, also nochmal durch 512 teilen?

@MartinFunk:
Ich versteh den Code nicht ganz. Also:


"servopos" beinhaltet die aktuelle Dauer des Signals in 1/100ms.

Wo wird das den Vorgegeben? Das is doch nur als Variable deklariert, oder?
TIMSK|=(1<<TOIE2) Bedeutet das bei jedem overflow ein Interrupt ausgelöst wird, oder? Aber wozu?
WGM21 gesetzt. Laut Datenblatt CTC-Mode.(TOP=OCR2, Update of OCR2 Immediate, TOV2-Flag set on MAX) ??? Naja, verstehe ich nicht so ganz...
CS20= No Prescaling (Das verstehe ich :-) )

Ich verstehe nicht wie das ganze so zusammen funktioniert.

Dumm ist auch das ich kein Osci da hab.

Achja, wieso sind bis zu 8 Servos mit dem Code möglich bei einem genutzten Pin? Das verstehe ich nicht.

MfG Jan

Spongebob85
11.10.2007, 14:22
Kann das sein das ich für diese ganze sache ein 16-Bit PWM-Register brauche? Hab da nämlich grade im Datenblatt die auswahlmöglichkeit zwischen 8, 9 und 10-Bit PWM gesehen.

MfG Jan

Zimble
11.10.2007, 14:59
Beim Code vom Roboternetz, nimmt man den Timer2 dazu, aller 10us einen Overflow auszulösen.
Dies geschieht, indem man den OCR in Abhängigkeit von der CPU-Frequenz setzt und bei erreichen des OCR-Wertes den Timer wieder auf null setzt (CTC=Clear Timer on Compare)


TCCR2 |= (1<<WGM21) | (1<<CS20); //Prescale=1, CTC mode
OCR2 = F_CPU/100000; //alle 10µS ein IRQ


In der entsprechenden Interrupt-Routine wir nun zum einen bis 2000 gezählt um 2000x10us = 20ms Periode zu erreichen. Ist der count unter bspw. 150(servopos) wird der Port angeschaltet (150x10us=1500ms), ansonsten wird er abgeschaltet.
Das Ganze kann man dann auf beliebig viele Servos ausweiten, indem man einfach die entsprechenden Ports und Servo-Positionen da mit einbaut.
Als Port nimmst du da die ganz normalen I/O-Pins, da das hier ja eher ein Software-PWM ist (das togglen der Pins wird ja vom Programmierer vorgegeben)

Ja und mit den Registern... ich red ja blöderweise auch die ganze Zeit vom Timer1, weil ich alle 3 PWM Channels benutze. Da kann man das noch angeben, beim Timer2 (den du ja benutzen willst) nicht.
Bei mir sieht das Ganze dann so aus:


cli(); //disable interrupts

TCCR1A |= (1<<WGM11)|(1<<COM1A1)|(1<<COM1A0); //9BIT PWM, inverted
TCCR1B = (1<<CS12); //Prescaler 256
// macht bei meinem 12MHz mega8 21.84ms

servo1_move(defaultPos); //setzt den OCR1A entsprechend


//TIMSK |= (1<<OCIE1A); //activate timer1 compare match interrupt for timing debugging
DDRB |= (1 << PB1); //set PB1 output
PORTB |= ( 1 << PB1 ); //set PB1 Pull up on


sei();


Was benutzt du zum programmieren? AVRStudio? Da kann man das relativ gut debuggen und sieht, was für Timings rauskommen. Spart man sich den Oszi.

Spongebob85
11.10.2007, 21:37
Dann benutz ich auch einen anderen Timer. Bis jetzt is an meinem Roboter nur ein LDR-Spannungsteiler auf ADC0. Dann will ich noch meine Antriebsmotoren über PWM steuern (Kette links, Kette rechts), aber das kann ich ja auch mit jedem PWM machen.



cli();

Das ist bestimmt eine fertige Function, oder? Woher kennst du die?



servo1_move(defaultPos); //setzt den OCR1A entsprechend

Dazu hast du noch ne Function in deinem Code, oder?



sei();

Das ist sicherlich auch eine fertige function, oder? Was macht die?

Glaub ich komm klar, wenn du das noch erklährst :-)

MfG Jan

Zimble
11.10.2007, 23:31
Die Funktion cli() sperrt die Behandlung von Interrupts, die Funktion sei() schaltet diese wieder ein. Also kann zwischen cli() und sei() kein anderer Interrupt irgendwelche Daten verändern.

Gebraucht hatte ich dass, da ich zu Testzwecken ins TCNT-Register schreiben wollte um den aktuellen Zählerstand zu ändern. Und laut Doku sollen vorher alle Interrupts gesperrt werden.
Na gut, laut Doku soll man auch alle Interrupts sperren, wenn man einen neuen Vergleichswert in OCR setzt. Vielleicht kann sich da jemand dazu äußern, der mehr Ahnung von der Sache hat.

servo1_move() erwartet bei mir eine Gradzahl der Servos, rechnet und setzt den entsprechenden Vergleichswert OCR

Spongebob85
12.10.2007, 21:27
So, ich hab mir jetzt diesen Code zusammengebaut.


#include <avr/io.h>
#include <stdint.h>
#include <avr/interrupt.h>
//#include "servo.h" //da meckert der irgendwie rum
#include <util/delay.h>

#ifndef F_CPU
#define F_CPU 16000000
#endif

#define Scheinwerfer 0
#define Rueckleuchten 1
#define PWM_ServoPort 7

int main(void)
{
cli();
uint8_t i;
TCCR1A |= (1<<WGM11)|(1<<COM1A1)|(1<<COM1A0); //9-Bit PWM, Inverted Mode
TCCR1B = (1<<CS12); //Prescaler 256

DDRD |= (1<<PD5);
PORTD |= ( 1 << PD5 );
//OC1A = 480;
sei();

while(1)
{
OCR1A = 480;
for (i=0; i<=100; i++)
{
_delay_ms(10);
}
OCR1A =450;
for (i=0; i<=100; i++)
{
_delay_ms(10);
}

}

}


Ich hab mir gedacht die Zeit für eine Periode ist [1/(16.000.000/256/512)*2]=16,384ms. In der zeit zählt der Counter 1mal hoch und runter. Also ist diese zeit auf 1024 (2mal 512) schritte aufgeteilt.
und dann hab ich mit 3-Satz ausgerechnet, wieviele schritte 0,5 ms sind, da der impuls dann durch invertmode beim hochzählen 0,5ms von compare bis Top und beim runterzählen wieder 0,5ms von Top bis Compare dauert also 1ms.
Die rechnung sieht so aus:
512 - 8,192ms
1 - 0,016ms ... 0,5ms/0,016 = 31,25
31,25 - 0,5ms
32 -0,512ms
512-32=480 also OCR1A auf 480 stellen für Tein=1ms bei einer Periodendauer von 16,384.
Wenn man das für Tein = 2ms ausrechnet kommt 450 raus.

Hab ich da einen Denkfehler? Mein Servo macht nicht das was ich will.
Hoffe es hat jemand lust sich damit auseinanderzusetzen. Würde mich auch jeden Fall freuen!!!

Ach ja. Wegen #include "servo.h" meckert der irgendwie rum. Hab das erstmal kommentiert.

MfG Jan

Spongebob85
12.10.2007, 23:32
Ich hab´s grade nochmal ausprobiert, und hab gesehen das es irgendwie doch klappt. der Servo fährt hin und her, nur er fährt nicht ganz in die jeweilige Endstellung. Der fährt ca. von 45° bis 135°. Ich versteh aber nicht wieso. Hab ich irgendwo "mal 2" vergessen? Wüsste aber nicht wo und wieso. Hab gemessen das sich mein 9V-Akku irgendwie auf 3V entladen hat. Macht mir irgendwie sorgen...

MfG Jan