Guten Morgen,
Ich habe hier eine Routine gefunden, um einen Servomotor zu steuern. Das
klappt bis jetzt auch ziemlich gut.
Nur löst der leider nicht fein genug auf für meine Zwecke.
Ich benutze einen : BMS-630MG und steuere diesen mit einem MEGA32U4.
Diese Routine verwende ich. Der Quarz ist auf 16MHz festgelegt.
Jan H. schrieb:> Nur löst der leider nicht fein genug auf für meine Zwecke.> //> // Der Prescaler muss so gewählt werden, dass der Ausdruck> // für MILLISEC_BASE einen Wert kleiner als 128 ergibt> // MILLISEC_BASE ist der Timerwert, der 1 Millisekunde Zeitdauer ergeben> // soll.> //> #define PRESCALER 1024> #define PRESCALER_BITS (1<<CS02 | 1<<CS00)>> #define MILLISEC_BASE ( F_CPU PRESCALER 1000 )
Das wundert mich nicht, dass du da kaum Auflösung hast. Wenn ich das mal
mit 16 Mhz durchrechne, dann erhalte ich
1
16000000 / 1024 -> 15625
2
15625 / 1000 -> 15
D.h. dein Servo steht bei einem Wert von ca 7 in der Mittelstellung und
du kannst es gerade mal +- 7 Positionen links bzw. rechts aus dieser
Mittelstellung rausfahren.
Aber der Timer kann ja auch noch andere Prescaler.
Zum Beispiel einen Prescaler von 256.
Dann würde sich für MILLISEC_BASE immerhin schon ein Wert von
1
16000000 / 256 -> 62500
2
62500 / 1000 -> 62
ergeben. Sie Servomittelstellung wäre dann schon bei 31 und du hättest
links und rechts jeweils 31 anfahrbare Servopositionen.
Der nächst kleinere Prescaler wäre 64. Würde der auch noch gehen?
1
16000000 / 64 -> 250000
2
250000 / 1000 -> 250
250 ist schon zu gross. Die Bedingung im Servocode lautet: kleiner als
128 und das ist nicht erfüllt.
Ohne also den Timer auf einen CTC Modus umzustellen, ist ein Prescaler
von 256 das Maximum.
1
#define PRESCALER 256
2
#define PRESCALER_BITS (1<<CS02)
wenn du noch feiner auflösen willst, müsste man entweder einen der 16
Bit Timer opfern oder für den 8 Bit Timer mit einem CTC Modus arbeiten
um sich in der Berechnung so nahe wie möglich an die 128er Grenze
heranzutasten.
Karl H. schrieb:> Der nächst kleinere Prescaler wäre 64. Würde der auch noch gehen?>
1
> 16000000 / 64 -> 250000
2
> 250000 / 1000 -> 250
3
>
> 250 ist schon zu gross. Die Bedingung im Servocode lautet: kleiner als> 128 und das ist nicht erfüllt.
Wobei:
Wenn man die ISR überarbeitet und den Puls auf 2 Interrupt Aufrufe
verteilt, wäre auch das noch machbar, wenn es sonst keine anderen
Interrupts im System gibt.
Moin,
vielen dank für deine Antwort.
Der 16 Bit Timer ist bis jetzt ohne Nutzung und könnte für diesen Fall
ruhig herangezogen werden.
Die Berechnung sollte ja fast die gleiche sein.
Da ich zum ersten mal mit Servos arbeite, habe ich noch eine Frage.
Den Servo kann ich manuel mit der Hand nen stückchen weiter drehen als
ich es mit der Software hinbekomme. Ist das normal ?
Habe es auch hart mal mit delay versucht...
Jan H. schrieb:> Den Servo kann ich manuel mit der Hand nen stückchen weiter drehen als> ich es mit der Software hinbekomme. Ist das normal ?
Ja, sonst könnte es passieren, dass er bei entsprechenden Steuerpulsen
in den mechanischen Anschlag fährt und den Motor nicht abschaltet, weil
er seine Sollposition noch nicht erreicht hat.
Jan H. schrieb:> Moin,>> vielen dank für deine Antwort.> Der 16 Bit Timer ist bis jetzt ohne Nutzung und könnte für diesen Fall> ruhig herangezogen werden.>> Die Berechnung sollte ja fast die gleiche sein.
Ja. Ist genau die gleiche.
Nur hast du nicht mehr das Limit aufgrund des Zählumfangs des Timers,
dass du für 1 Millisekunde einen errechneten Wert kleiner 128 haben
musst, sondern du kannst den Prescaler soweit verkleinern, bis du einen
maximalen Millisekundenwert von 32767 hast. Dann hast du massenhaft
Servopositionen, die das Servo mechanisch wahrscheinlich gar nicht mehr
exakt anfahren kann :-)
Damit stelle ich jetzt aber nur den Puls (High Zeit) ein oder ? Wie
generiere ich denn meine ~ 20 ms. Pause ?
Nachtrag:
Habe mir gerade noch einmal den Artikel durchgelesen, es ist also nicht
"wirklich" wichtig, dass eine Pause von ca. 20 ms. eingehalten wird.
Also ist es wirklich nur wichtig, wenn ich eine möglichst feine
Positions Auflösung haben will, die Zeit so fein auf zu lösen wie es
geht (was der Servo halt so mit macht), richtig ?
Jan H. schrieb:> Habe mir gerade noch einmal den Artikel durchgelesen, es ist also nicht> "wirklich" wichtig, dass eine Pause von ca. 20 ms. eingehalten wird.
Analoge Servos lassen ihren Regler nur einmal pro Puls durchlaufen, zu
langsam ist also schlecht für Präzision und reaktionszeit des Servos. Zu
schnell kommt irgenwann der Servo nicht mehr mit. Also man sollte schon
grob in der Nähe bleiben.
Edit: nicht verwirren lassen, bin ein anderer Jan H.
Jan H. schrieb:> Also ist es wirklich nur wichtig, wenn ich eine möglichst feine> Positions Auflösung haben will, die Zeit so fein auf zu lösen wie es> geht (was der Servo halt so mit macht), richtig ?
Richtig.
Die Pause für jedes einzelne Servo ergibt sich dadurch, dass prinzipiell
8 Servos angesteuert werden. Wenn 1 Servo gerade seinen Puls bekommt,
haben die jeweils anderen 7 Pause. Die einzelnen Servos erhalten
nacheinander ihren Puls!. Und wie du schon gelesen hast, ist die Länge
der Pause nicht wirklich wichtig. Ob das 12 oder 16 oder 20 oder 22 ms
sind, spielt keine grosse Rolle.
1
<----- hier entsteht ganz ---->
2
von alleine eine
3
Pause, weil in dieser
4
Zeit die anderen Servos
5
ihre Pulse erhalten
6
7
+--+ +--+
8
| | | |
9
1 --+ +-------------------------------+ +------
10
11
12
+--+ +--+
13
| | | |
14
2 -----+ +-------------------------------+ +----
15
16
+--+
17
| |
18
3 --------+ +---------------------- .....
19
20
+--+
21
| |
22
4 -----------+ +------------------- .....
23
24
.
25
.
26
.
27
<-->
28
Der Timer muss mit seinem Zählumfang nur diesen Teil
Verstanden.
Ich will die Positionen über RS232 entgegen nehmen.
Am Anfang will ich via. PC eine default Position in das EEPROM schreiben
(je nach dem wie viele Servos ich im Einsatz habe).
Also brauche ich schon mal ein Commando für diese Aktion.
Dann will ich die Position der verschiedenen Servos übergeben.
Habe mir gedacht ich werde das so machen :
Ist das eine gute Idee oder sollte man das anderst machen?
Nur wie sortiere ich die ganzen Befehle? Wartet man darauf bis es ein
RX_Int gibt und schreibt das erste Byte (Command?) in eine Variable und
wartet auf das zweite Byte (Value?) und springt dann je nach Commando in
die jeweilige Funktion?
Würde ich nicht machen.
Es geht zwar aus deinem Beispiel nicht explizit hervor, aber ich würde
da eine Textschnittstelle machen. Vorteil: kann mit jedem
Terminalprogramm getestet und in Betrieb genommen werden, ohne dass man
dazu auf dem PC erst mal ein Programm (mit seinen eigenen
Fehlermöglichkeiten) schreiben muss.
Jedes Kommando ist eine Zeile Text, abgeschlossen mit einem Return.
Kommandos sind 1 Buchstabe, nowtwendige weitere Angaben folgen dahinter,
jeweils mit einem Komma getrennt
Als Kommandos würde ich erst mal vorsehen
1
L Load positions from EEPROM
2
S Save positions to EEPROM
3
Pn,m Position Servo 'n' to position 'm'
Wenn du mal genau überlegst, dann ist zb dein Laden der
Defaultpositionen nix anderes als erst mal alle 8 Servos auf ihre
Positionen zu verfahren und dann das Kommando 'S' zu geben, welches die
Positionen aller 8 Servos speichert. Wenn nur eines eine neue
Defaultposition kriegen soll, dann geb ich zuerst das Kommando 'L',
daraufhin fahren alle 8 Servos diese Position an, ich richte sie
entsprechend mit P Befehlen ein und mit einem S werden alle wieder im
EEPROM gespeichert.
> Nur wie sortiere ich die ganzen Befehle? Wartet man darauf bis> es ein RX_Int gibt und schreibt das erste Byte (Command?) in eine> Variable und wartet auf das zweite Byte (Value?) und springt dann> je nach Commando in die jeweilige Funktion?
Zum Beispiel. Möglichkeiten gibt es viele. Wenn ich eine
Textschnittstelle mache, dann lasse ich erst mal eine komplette Zeile
zusammenkommen und analysiere dann den String was zu tun ist und wie die
Argumente aussehen. Ganz normale Stringverarbeitung, die hier umso
einfacher ist, da alle Kommandos nur aus 1 Buchstaben bestehen :-) und
für die Zahlenwandlung dann eben beispielsweise atoi.
Den Empfang würde ich Interrupt getrieben machen, in der ISR wird das
jeweils nächste Zeichen in einen Buffer geschrieben, sofern es kein '\n'
ist. Erkennt die ISR einen '\n' dann setzt sie ein Jobflag und ein
Teilzweig in der Hauptschleife aktiviert sich und sieht sich den Buffer
an.
Das mit den ASCII Zeichen ist eine gute Idee. So in der Art hatte ich es
auch erst angedacht.
Meine default Werte möchte ich aber auch gerne schreiben (vom
Terminalprogramm z.B)
Also würde ich das am besten so machen (default Werte schreiben):
1
"S""1""80"// S = save default position , 1 = for servo 1, 80 = position
Jan H. schrieb:> Das mit den ASCII Zeichen ist eine gute Idee. So in der Art hatte ich es> auch erst angedacht.>> Meine default Werte möchte ich aber auch gerne schreiben (vom> Terminalprogramm z.B)>> Also würde ich das am besten so machen (default Werte schreiben):>>
1
>
2
>"S""1""80"// S = save default position , 1 = for servo 1, 80 =
3
>position
4
>
5
>
>> hoffe habe das richtig verstanden.
Das ist doch Quatsch. Du wirst doch nicht am Terminnal die ganzen
Gänsefüsschen eingeben.
Am Terminal gibst du ein
1
S1,80<Return>
(das Return ist ein Druck auf die Enter Taste) und der µC klamüsert sich
das auseinander. Du wirst dir doch nicht selbst die Eingabe mit lauter
Sonderzeichen anfüllen, für die du jedesmal auf der Shift-Taste
rumklopfen musst.
Ist doch nicht schwer
1
volatileuint8_thaveLine;
2
volatileuint8_tlineLength;
3
volatilecharline[20];
4
5
ISR(UARTEmpfangsInterrupt)
6
{
7
c=UDR;
8
9
if(haveLine)
10
return;// Hauptschleife hat das vorhergehende noch nicht verdaut
11
12
elseif(c=='\r')
13
return;// ignorieren
14
15
elseif(c=='\n'){// Ende der Zeile (Return wurde gedrückt)
16
line[lineLength]='\0';
17
haveLine=1;
18
lineLength=0;
19
}
20
21
elseif(lineLength<sizeof(line)){
22
line[lineLength++]=c;
23
uart_putc(c);// eventuell Echo schicken, damit der Benutzer auch was sieht
24
}
25
}
26
27
28
intmain()
29
{
30
....
31
32
while(1){
33
34
if(haveLine){// Benutzer will was
35
36
if(line[0]=='S'){// Servo soll neue Position kriegen
37
actServo=line[1]-'0';
38
if(line[2]==',')
39
servoPos[actServo]=atoi(&line[3]);
40
}
41
42
elseif(line[0]=='+')
43
servoPos[actServo]++;
44
45
elseif(line[0]=='-')
46
servoPos[actServo]--;
47
48
elseif(line[0]=='?){
49
itoa(tmp,servoPos[actServo],10);
50
uart_puts(tmp);
51
}
52
53
haveLine=0;// alles bearbeitet, ISR darf wieder empfangen
Jan H. schrieb:> if (received_data[3] == '\n')
Du willst das nicht an fixe Zeilenlängen koppeln, wenn es nicht sein
muss.
> case 'P' : {set_servo_position(received_data[4],> received_data[3]);}break;
genausowenig, wie du hier davon ausgehen willst, dass die
Positionsangabe immer 2 stellig ist. Mach einen String draus und setz
atoi darauf an. Das kann damit umgehen.
Wie man das ganze ohne Annahme einer Nachrichtenlänge macht, hab ich dir
ja schon gezeigt. Character von der UART rein und solange es kein \n
ist, kommt er in ein Array hinten drann.
Ob man diese Character_Sammel_Sache in einer ISR macht oder direkt in
der Hauptschleife, spielt erst mal weniger die grosse Rolle. In einer
ISR hat es halt den Vorteil, dass es auch dann funktioniert, wenn der µC
gerade anderweitig beschäftigt ist.
Edit:
ach ja. Den Backspace Character sollte man auch noch auswerten, damit
ein Benutzer an einem Terminal auch eine Chance hat eine ungültige
Eingabe zu korrigieren.
1
ISR(UARTEmpfangsInterrupt)
2
{
3
c=UDR;
4
5
if(haveLine)
6
return;// Hauptschleife hat das vorhergehende noch nicht verdaut
7
8
elseif(c=='\r')
9
return;// ignorieren
10
11
elseif(c=='\b'){// Backspace
12
if(lineLength>0)
13
lineLength--;
14
uart_puts("\b\b");// Echo, damit auch am Terminal ausradiert wird
15
}
16
17
elseif(c=='\n'){// Ende der Zeile (Return wurde gedrückt)
18
line[lineLength]='\0';
19
haveLine=1;
20
lineLength=0;
21
}
22
23
elseif(lineLength<sizeof(line)){
24
line[lineLength++]=c;
25
uart_putc(c);// eventuell Echo schicken, damit der Benutzer auch was sieht
Jan H. schrieb:> Damit ich nicht immer 'P' senden muss, sende ich ein "+" oder "-" an> erster stelle um zu incrementieren oder dekrementieren, richtig?
Ja, zum Beispiel.
Ich hab mir einfach überlegt, wie ich das wohl machen würde, wenn ich
ein Servo an eine bestimmte Position einrichten möchte.
Da werde ich ganz sicher nicht dauernd
1
P5,80
2
P5,81
3
P5,82
4
P5,83
tippen wollen (ich bin ja faul), sondern ich hab mir überlegt das sich
das Positionierkommando das zuletzt benutzte Servo merken könnte. Auch
will ich das Positionierkommando auch ohne Angabe einer Position
benutzen können, um es sozusagen einfach nur als das zuletzt benutzte
Servo bekannt zu machen um es dann in weiterer Folge ansprechen zu
können. Dann kann ich einfach tippen
1
P5,80
2
+
3
+
4
+
5
-
oder wenn ich die aktuelle Position des Servos gar nicht kenne
1
P5
2
+
3
+
4
+
5
-
6
-
7
+
und das Servo verfährt.
+ und - hab ich genommen, weil sie am Zehnerblock so schön daliegen.
Denk dir was aus! Auf deinem µC bist du Gott! Was immer dir gefällt
kannst du implementieren.
genau. Nicht mehr Aufwand als notwendig.
Der Unterschied zwischen
1
P5,80
und
1
P5
besteht augenscheinlich darin, dass hier
1
P5
2
^
3
|
kein ',' eingegeben wurde
Das ist nicht 100% nach den Regeln der Kunst, für eine einfache
Testumgebung, bei der das Programm nicht gleich bei jeder Falscheingabe
auf die Nase fällt, sollte es aber reichen.
Also wenn ich via. Terminal 1 x P sende klappt das. Sende ich jetzt aber
1 x S, so reagiert er einfach nicht drauf und überschreibt P nicht...
An was kann das liegen?
Jan H. schrieb:> Wenn ich jetzt im Terminal folgendes als ASCII Sende :
1
P150
>> Übergebe ich meiner> Funktionset_servo_position(servo_nr_tmp,servo_pos_value_tmp);>> folgende Werte
1
P49???
>> Ich kann doch in ASCII keine 50 angeben ?!
Du vermischt in der zweiten Ausgabe dezimal und asccii, das ist
verwirrend. Wenn das Erste wirklich genauso eingegeben wurde, ist
received_data[1] der Abstand, und du verwendest atoi falsch, dass nimmt
ein nullterminiertes array von chars, deins ist nicht nullterminiert.
Wenn du 2 zahlen in ascii hast, sind diese in 2 chars, dann rechnet man
erste Zahl mal 10 plus zweite zahl, so macht das auch atoi und
wiederholt es nis zum ersten nullbyte.
Jan H. schrieb:> Nur wie mache ich das wenn ich einen Wert > 100 habe?
Du musst dir ein geschicktes Dateiformat überlegen, bei dem erkennbar
ist wo etas Anfängt und wo etwas Aufhört. Dann kannst du so lange die
Nächste Zahl zum Zwischenergebnis addieren, bis die Endbedungung
Auftritt, z.B. das Zeichen keine zahl mehr ist oder es das letzte
Zeichen war, etc.