Forum: Projekte & Code Servocontroller mit ATmega8 für 20 Servos


von Stefan May (Gast)


Lesenswert?

Hallo,

http://www.4finger.net/~smay/avr/servomaster/
http://www.4finger.net/~smay/avr/servotester/

ermöglicht die Steuerung von bis zu 20 Servos über ein TWI-Interface.
Programmiert wurde das auf einem ATmega8 mit internem RC-Oszillator bei
8 MHz. Die Funktion ist ähnlich dem SD20 Servocontroller auf PIC-Basis.
Dazu gibt es auch gleich ein Testprogramm, welches auch mit dem SD20
zusammenarbeitet.

Viel Spaß.

mfg, Stefan.

von thkais (Gast)


Lesenswert?

Sehr schöner Code, der mir auch beim Lernen von C weitergeholfen hat.
Danke !

von Stefan May (Gast)


Lesenswert?

Wow, das ging runter wie Öl. :-)
Bitte schön, gern geschehen.

von Dominc R. (derdingsder)


Lesenswert?

Hallo, ich hätte da mal de frage zur funktionsweise.

Hab ich das so richtig verstnden:
im Interrupt SIG_OVERFLOW1 wird zuerst der gerade aktive Servo bzw. der
entsprechende Pin ausgeschaltet, dann in einer schleife gesucht bis der
nächste servo kommt dessen Registerwert != 0 ist, der Timer auf die
entprechende zeit gestellt.
Dann den Pin einschalten, und warten bis der Interrupt wieder kommt und
ihn ausschaltet und alles von vorne losgeht.

wenn er mit allen servos durch ist, wird Timer1 gestoppt, und durch den
timer2 alle 33ms (wenn ich mich nicht verrechnet habe) reaktiviert.

Ich habe im Internet bis jetzt nix genaues dazu gefunden, wie lang die
Zeit zwichen den Pulsen sein darf, es wird immer nur gesagt, dass es ca
20ms sein sollen, aber bei 20 servos kommt mal ja auf (im schlimmsten
fall) 40ms. Kennt jemand zufällug eine Quelle, wo das Präzise
dokumentiert ist?

Und noch eine frage:
wenn der Hauptinterrupt wieder aktiviert wird (servo-rollover = true)
wird ja die variable which auf eins gesetzt, und die entsprechende zeit
für servo1 geladen. was ist nun aber wenn der servo1 deaktiviert ist
(servo_register[1] == 0)

wäre es nicht geschikter das so zu machen:

    /* nächsten servo bestimmen */
    if (servo_rollover) {
  which = 0;
  servo_rollover = false;
    }
    for (which++; which <= SERVO_MAX; which++) {
  if (servo_register[which] != 0)
      goto next;
    }


mfg
Dominic Rathje

von Ludwig W. (lordludwig)


Lesenswert?

ein servo kann fast so langsam gepulst sein wie er will, aber bei 40ms
verliert er schon deutlich an stärke.

von smay4finger. (Gast)


Lesenswert?

Das ist richtig, wenn alle 20 Servos verwendet werden, dann ist das
Interval bei maximal 40 ms. Dasselbe Problem hat der SD20 auch, ich
schätze, daß ich den Algorithmus des SD20 ganz gut "nachempfunden "
habe.

Zur Update-Zeit wird immer behauptet, daß es 20ms sein müssen. Das ist
so nicht richtig. Entscheidend ist die Pulslänge! Allerdings, wie von
lordludwig schon geschrieben, je länger die Update-Zeit, desto mehr
Kraft verliert der Servo. Die meisten Servos schalten nach dem Puls den
Motor ab und warten dann auf einen weiteren Puls.

@Dominic: Deinen letzten Einwand verstehe ich nicht ganz. Vielleicht
kläre ich nochmal die Funktion der beiden Timer auf. Timer2
(SIG_OVERFLOW2) sorgt dafür, daß das 20ms-Intervall eingehalten wird.
Timer1 (SIG_OVERFLOW1) sorgt für die richtige Pulsdauer. Die Pulsdauer
wird innerhalt der Interrupt-Routine für Timer1 gesetzt. Wenn kein
Servo aktiviert ist, oder bei wenigen Servos alle Pulslängen
abgearbeitet wurden, dann wird Timer1 deaktiviert. Aktiviert wird
Timer1 durch die Interrupt-Routine von Timer2.

Noch ein Einwand: Wenn es ums verstehen der Funktion geht, schaut Euch
gern den Code an, ansonsten flasht das lieber in einen Mega8 und
konzentriert Euch auf die Ansteuerung über den I2C-bus.

mfg, Stefan.

von Dominc R. (derdingsder)


Lesenswert?

Hi,

was ich mit dem Codeschnipsel sagen wollte:

Wenn der Timer1 durch den Timer2 wieder gestartet wird, wird ja auch
servo_rollover auf true gesetzt, was zur folge hat das er in der timer1
routine den code in der if bedingung ausführt, und which auf 1 setzt,
richtig?

anschliessend macht er ganz normal weiter und setzt den timer auf die
zeit von Servo 1 (weil which ja auf eins gesetzt wurde).
Was passiert nun aber wenn der Servo 1 deaktiviert ist, und der erste
serve der überhaupt beachtet werden soll Servo2 ist?

Normalerweise wird ja geprüft, ob das register für den entsprechenden
Servo einen Wert ungleich 0 hat, und nur dann wird  die entsprechende
Zeit in den timer geladen.

Deshald habe ich mir gedacht, dass man die Überprüfung auf rollover vor
die abfrage nach dem nächsten gültigen Servo setzt.

mfg
Dominic Rathje

von smay4finger. (Gast)


Lesenswert?

> anschliessend macht er ganz normal weiter und setzt den timer auf die
> zeit von Servo 1 (weil which ja auf eins gesetzt wurde).
> Was passiert nun aber wenn der Servo 1 deaktiviert ist, und der
erste
> serve der überhaupt beachtet werden soll Servo2 ist?

Öhm, ich glaube Du hast recht. Ich muß mir das nochmal durch den Kopf
gehen lassen, was dann überhaupt passiert. Das ist mir bisher noch
nicht aufgefallen, Danke für den Hinweis.

mfg, Stefan.

von Rolf Magnus (Gast)


Lesenswert?

Man kann die Servosignale sich überschneiden lassen. Also Servo 1 an,
nach einer Millisekunde Servo2 an, dann nach der passenden Zeit Servo 1
aus. Wenn die nächste Millisekunde rum ist, Servo 3 an, dann Servo 2
nach der passenden Zeit aus, u.s.w.
Also ein Timer, der einmal pro Millisekunde den nächsten Puls
einschaltet und einer, der nach entsprechender Zeit den anderen Puls
wieder ausschaltet. Damit sollten also 20 Servos in 20 Millisekunden
möglich sein.

Was die Kraft betriff, so lässt bei zu großer Pausenlänge bei
Analogservos durchaus die Kraft und die Reaktionsgeschwindigkeit nach.
Bei Digitalservos sollte es keine Rolle spielen.

von Dominc R. (derdingsder)


Lesenswert?

Hi,

@Rolf Gute Idee, danke

Ich habe mir noch folgende Lösung überlegt:
Man Lässt den 16-Bit timer einfach durchlaufen, und lässt ihn im Output
Compare Modus laufen, somit hat man 2  Interrupts aus dem Timer.
Dann Teilt man die Servos quasi auf, und lässt jede Interrupt routine
10 Steuern und dann müsste es auch passen.

Also quasi in der Interrupt Routine einach den entsprechenden Zeitwert
vom Servo auf den aktuellen Zählerstand aufaddieren und das ganze in
das compare register laden. Damit die Interrupts sich nicht gegenseitig
blockieren, hab ich mir überlegt, dass man Direkt nach dem Ausschalten
des Pins die Interrupts wieder frei gibt. Man müsste natürlich
irgendwie sicherstellen, dass der selbe interrupt in der zeit nicht
mehr auftritt, aber das geht schon irgendwie.

Die Interrupt Routinen sehen im prinzip so aus wie die von Sefan, nur
eben 2.

Naja, ich werd das mal diese oder nächste woche machen, dann seht ihr
schon was ich meine.

von Hannes L. (hannes)


Lesenswert?

Man kann es auch so organisieren, dass Timer0 alle 21ms (20ms geht
schlecht, da 10 Servoimpulse mit 2ms schon 20ms sind und etwas
Sicherheit vorhanden sein sollte) beide "Kreise" anschubst und jeder
Kreis 10 Servos nacheinander durchklappert und am Ende auf den Schubs
von Timer0 wartet. Jeder Kreis benutzt dann einen Compare-Interrupt des
Timer1.

Eine ähnliche Schaltung für 7 Servos am Mega48 ist hier zu finden:
http://www.hanneslux.de/avr/mobau/7ksend/7ksend02.html
Sie ist aber für Handbetrieb (Steuerknüppel) ausgelegt und in ASM
programmiert und ist in der Lage, ein Modellbau-Sendemodul anzusteuern.
Man kann am Quelltext aber sehen, was ich mit "Kreis" gemeint hatte.

...

von Dominc R. (derdingsder)


Lesenswert?

genau so hab ich mir das vorgestellt.
in asm muss man es wohl prgrammieren, um auszuschließen, dass siech die
beiden kreise gegenseitig blockieren (die ISR muss möglichst kurz sein)
allerdings würde ich etwas mehr als 21ms, so eher gegen 25ms nehmen, da
man die Servos mit einer impulslänge von 0.75ms bis 2.25ms ansteuern
muss, um 180° Drehwinkel zu erreichen.

von smay4finger. (Gast)


Lesenswert?

@Hannes, ich nehme mal an, das ist Deine Webseite. Wo hast Du diesen
Mini-Joystick her?

mfg, Stefan.

von Hannes L. (hannes)


Lesenswert?

> @Hannes, ich nehme mal an, das ist Deine Webseite. Wo hast Du
> diesen
> Mini-Joystick her?

Den gibt's bei http://www.micromotor.de/elektronikzubehoer.htm, aber
wohl auch beim großen blauen chr$(67)...

> genau so hab ich mir das vorgestellt.
> in asm muss man es wohl prgrammieren, um auszuschließen, dass siech
> die
> beiden kreise gegenseitig blockieren (die ISR muss möglichst kurz
> sein)

Nunja, in ASM ist das nicht weiter tragisch (da blocliert sich nix), in
C oder BASCOM hätte ich Probleme. Das liegt aber nicht an C oder BASCOM
sondern an mir und meiner Erfahrung mit C oder BASCOM...

Die ISRs sollen kurz sein, aber so tragisch ist das nun auch wieder
nicht, es ist genügend Rechenzeit vorhanden. Ich hatte sogar schon
überlegt, den Mega48 mit 128kHz laufen zu lassen, damit schafft der das
immernoch.

> allerdings würde ich etwas mehr als 21ms, so eher gegen 25ms
> nehmen, da
> man die Servos mit einer impulslänge von 0.75ms bis 2.25ms
> ansteuern
> muss, um 180° Drehwinkel zu erreichen.

Das ist ja klar. Wenn die Impulse länger sein sollen, dann dauert das
alles auch etwas länger. Man kann aber auch auf die Erholpause
verzichten und jeden Kreis reiherum klappern lassen.

Besser wäre es allerdings, drei Kreise zu machen, denn da ist ja noch
ein Timer übrig. Ich kann dazu aber jetzt nix Genaueres sagen weil ich
seine Features nicht im Kopf habe.

Weiterhin wäre denkbar, mit freilaufendem Timer1 und 1MHz Takt mit dem
Überlauf alle 15,259ms das Spiel anzuschubsen und die Servos auf 4
Kreise aufzuteilen (Comp1a, Comp1b, Timer0, Timer2). Die Auflösung wird
dabei wegen der 8-Bit-Timer etwas gröber (10µs pro Zahlenwert), aber
damit kann man sicher leben. Die ISRs bleiben auch kurz, sie müssen
lediglich
- mitzählen, welches Servo dran ist,
- den zugehörigen Impulsbreitenwert aus dem SRAM holen,
- den Timer auf den nächsten "Termin" setzen,
- den alten Portpin abschalten und
- den neuen Portpin einschalten.

Das ist bestimmt mit unter 50 Takten zu machen.

Interessantes Projekt, nur habe ich dafür keinen Bedarf...

...

von Dominc R. (derdingsder)


Lesenswert?

Das mit dem gegenseitigen blockieren habe ich mir so gedacht, dass wenn
eine ISR gerade aktiv ist und im selben moment auch eine andere kommt,
dann wird diese verzögert, bis die erste fertig ist, und somit wird das
sugnal etwas zu lang.
deshalb habe ich mir gedacht dass man die interrupts wieder frei gibt
sobald man den entsprechenden pin Low gesetzt hat.
Dafür sollte man nicht länger als 32 Takte brachen, dann bleiben die
abweichungen unter einer Abstufung bei 8 Bit auflösung (wenn das ganze
mit 8MHz läuft)

von Walter (Gast)


Lesenswert?

ich hatte mir zur Ansteuerung von 16 Servos mal einen Algorithmus
überlegt der mir sehr übersichtlich erscheint (und je übersichtlicher
desto weniger Fehlermöglichkeiten):

eine Tabelle mit max. 16 Einträgen mit je 3 Byte
das erste Byte enthält einen Timervergleichswert, die anderen beiden
Bytes die Bits für die Ausgabe an 2 Ports mit den Servos.

jetzt erzeugt ein Timer alle 1/256 ms einen Interrupt
in diesem wird wird ein mitlaufender Softwarezähler mit dem nächsten
Tabellenwert verglichen und bei Übereinstimmung die beiden Bytes an den
Port ausgegeben.
Wenn man durch ist noch die Ruhezeit abwarten und wieder von vorn
anfangen.

Schöne Ostern
Walter

von Gast (Gast)


Lesenswert?

Hi alle beisammen,
der post ist zwar schon alt (xcus fürs aufwärmen).
Aber was 20 Servos und ATmega8 betrifft hab ich da was für euch.

http://www.roboternetz.de/phpBB2/zeigebeitrag.php?t=38160&highlight=hopix

von Hannes L. (hannes)


Angehängte Dateien:

Lesenswert?

Gast wrote:
> Hi alle beisammen,
> der post ist zwar schon alt (xcus fürs aufwärmen).
> Aber was 20 Servos und ATmega8 betrifft hab ich da was für euch.
>
> http://www.roboternetz.de/phpBB2/zeigebeitrag.php?t=38160&highlight=hopix

Hmmm... - Muss man sich da erst anmelden, um sich den Code mal 
anzusehen? Dazu fehlt mir irgendwo die Lust, ist mir zu klickibunti.

Wenn Du willst, dass Andere was davon haben, dann poste es hier.

...

von Remo Hug (Gast)


Lesenswert?

Hallo zusammen

Hat schonmal jemand versucht den Code auf einen PIC zu übersetzen?
Währe es auch möglich den ablauf des Codes mal genau zu erläutern?

Bin gerade auch mit Servos am experimentieren und suche genau sowas.
Nur möchte ich es nicht einfach nur brennen, sondern auch noch verstehen 
und da ich sowieso mit PIC's arbeite komme ich ums umschreiben 
(verstehen) nicht herum.


freundliche Grüsse

Remo

von Stefan M. (Gast)


Lesenswert?

Hallo Remo,

Mit PIC kenne ich mich zwar nicht aus, aber das Grundprinzip solltest Du 
auch ohne AVR-Kenntnisse verstehen können.

Was Du Dir anschauen solltest sind die Routinen SIGNAL(SIG_OVERFLOW1) 
und SIGNAL(SIG_OVERFLOW2). Beides sind Timer-Interrupts, welche mit 
einer festen Rate aufgerufen werden. Die Funktionen sbi() und cbi() 
setzen bzw. löschen ein Bit im Port-Register. Den Rest, insbesondere 
SIGNAL(SIG_2WIRE_SERIAL) kannst Du ignorieren.

Wenn Du spezifische Fragen hast, dann stelle sie einfach in diesen 
Thread, ich beantworte sie Dir gern.

von Remo Hug (Gast)


Lesenswert?

Hallo Stefan

Danke für deine angebotene Hilfe.
Dann werde ich dies doch gleich mal nützen und dir ein loch in den Bauch 
fragen. ;-)

1. Was genau bedeutet das "ul" bei: (CLK / 256000ul)
2. Was haben dein Timer für eintellungen?

freundliche Grüsse

Remo

von Stefan M. (Gast)


Lesenswert?

Zu 1) Schau mal in ein C-Buch. ul bedeutet, daß die Konstante als 
unsigned long betrachtet wird.

Zu 2) CLK ist 8MHz, d.h. 8.000.000. Timer 2 ist ein 8-bit Zähler, Timer 
1 ist ein 16-bit Zähler. Von da aus kannst Du die Interrupt-Frequenz 
leicht berechnen. Timer 2 ist für die Wiederholrate der Impulse 
verantwortlich (sollte idealerweise 20ms sein) und Timer 1 legt die 
Impulsbreite fest (idealerweise zwischen 1 und 2ms). Der Rest ist ein 
bisschen Rechnerei mit den Registern REPEAT, OFFSET und DELTA.

von Remo Hug (Gast)


Lesenswert?

Hallo Stefan

Also ich bin noch nicht der Chef was C-Code angeht.
Ich meinte bei 2. ob du irgendwelche Vorteier verwendest.

Aber ich hab da immer noch ein Knopf.

Du stellst doch mit "TCCR2 = 5" den Presaler von
TIMER2 auf CLKt2s / 128 ein.
Was aber meinst du im Code mit "CK1024" (Kommentar)?
Was ist eigentlich CLKt2s?

Kannst du mir bitte die Timer i deinem Projekt genauer erklären?

freundliche Grüsse

Remo

von Gast (Gast)


Lesenswert?

Du solltest aber schon mal ins Datenblatt schaun, da ist jedes Bit der 
Timersteuerung exakt erklärt. Und nimm nicht irgendein Datenblatt, 
sondern das zum Controller (für den der Code geschrieben wurde) 
passende, denn die Ausstattung der Hardware-Einheiten ist sehr 
unterschiedlich.

von Stefan M. (Gast)


Lesenswert?

Hallo Remo,

Dein Blick in das Datenblatt war scheinbar richtig. Der Vorteiler ist 
auf 128 gestellt, sollte aber 1024 sein. Der Kommentar suggeriert dies 
zumindest.

CLKt2s ist der Takt für den Timer 2. Den gibt es, weil Timer 2 auch 
durch einen externen Quarz asynchron getaktet werden kann. CLKt2s kann 
man gut in dem Übersichtsschaltbild des Timer 2 im Datenblatt vom Mega8 
sehen. Ist ein paar Seiten vor der Tabelle mit den Prescaler-Werten.


Ich kann im Moment nicht genau sagen, warum der Code so funktioniert, 
wie er da steht. Hab bitte Geduld bis heute Abend. Schick mir doch mal 
eine Mail, dann kann man das per Chat klären.

mfg, Stefan.

@Gast: Remo scheint das Datenblatt gelesen zu haben.

von Gast (Gast)


Lesenswert?

Ich frage mich immer, warum das Berechnen der Timerwerte so große 
(Verständnis-)Probleme bereitet. So ein Timer ist doch erstmal nur ein 
stinknormaler Zähler, der den über Vorteiler verminderten CPU-Takt zählt 
(ja, es gibt auch andere Betriebsarten). Was interessiert nun, wenn man 
Servoimpulse generieren will?

- Telegramm-Wiederhol-Abstand (etwa 20 ms, nicht allzu kritisch)

- Impulsbreite eines Impulses (0,7 ms bis 2,3 ms, meist enger)

Ein 16-Bit Timer kann bis 65535 zählen, legt man als Zeitraster 1 µs 
fest (Zählfrequenz 1 MHz), dann ergibt das Werte von 20000 für den 
Telegrammabstand und 700 bis 2300 für die Impulsbreite. Der minimale 
Zeitabstand zwischen zwei Interrupts beträgt somit 700 Timerticks, bei 
Vorteiler 1 zu 1 und CPU-Takt von 1 MHz also 700 Takte (da kann eine CPU 
schon ganz schön was zusammenrechnen...).

Muss ein 8-Bit-Timer verwendet werden, so bietet sich das Zeitraster von 
10 µs an, das ergibt Werte von 70 bis 230 für die Impulsbreite, der 
Telegrammstart muss dann anderweitig ermittelt werden.

Der korrekte Telegrammabstand ist eigentlich nur dann wichtig, wenn alle 
Impulse zusammengefasst werden sollen, um sie z.B. über einen Sender zum 
Empfänger zu schicken (der Empfänger bzw. Decoder braucht eine Lücke zur 
Synchronisation). Werden die Impulse nicht zusammengefasst, so ist ein 
korrekter Telegrammabstand nicht erforderlich. Wenn das letzte Servo 
fertig ist, wird eben mit dem ersten Servo begonnen, so als Ring, immer 
im Kreis der Reihe nach. Das erspart nämlich den Timer für den 
Telegrammabstand, wodurch eine Interrupt-Einheit eines Timers für eine 
Impuls-Sequenz frei wird.

Sollte UART im Spiel sein, sollte man auch einen Baudratenquarz 
verwenden. Dann sind die oben genannten Teiler und Zeiten natürlich neu 
zu berechnen. Dabei ist ein Kompromiss zwischen voller Ausnutzung des 
Zählumfangs der 8-Bit-Timer (also der Auflösung) und der Querzfrequenz 
unter Beachtung der Vorteiler-Möglichkeiten zu finden. Der schnellste 
mögliche Quarz muss nicht immer das Optimum sein, der 
UART-Standard-Quarz meist auch nicht.

Die eigentliche Rechnerei hat dann Unterstufen-Niveau und ist (beim 
Evaluieren mehrerer Möglichkeiten) eine reine Fleißaufgabe.

von hopix (Gast)


Angehängte Dateien:

Lesenswert?

Hier mal mein Code dazu

ATMega8 (leicht portierbar)
max 20 Servos mit 20ms gepulst ca 2/3 Leistungsreserve
Ausgangspins frei einstellbar.

von Stefan M. (Gast)


Lesenswert?

Den Trick mit den Pin-Adressen finde ich nett. :-)

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.