Modellbauservo Ansteuerung

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Soll ein µC über eine Einrichtung mechanische Arbeit verrichten, dann arbeitet man gerne mit Schrittmotoren oder eben mit normalen Modellbauservos. Modellbauservos haben den Vorteil, dass sie leicht anzusteuern sind, und die notwendige Leistungselektronik bereits im Servo eingebaut ist. Auch benötigt man keine aufwändigen Rückmeldungen von der Mechanik um eine bestimmte Position anzufahren oder um die Schrittverluste eines Schrittmotors auszugleichen. Ein Servo findet ganz von alleine nach dem Einschalten seine Neutralposition. Servos gibt es in allen möglichen Preiskategorien, wobei die Unterschiede in der Kraft des Motors bzw. in der Qualität des integrierten Getriebes liegen. Angesteuert werden Servos immer gleich. Ganz im Gegenteil: Vieles aus dem Modellbaumarkt, wie zb Fahrtregeler oder Kurskreisel, allesamt Geräte die an handelsübliche Fernsteuerempfänger angesteckt werden können, werden nach exakt dem gleichen Muster angesteuert. Erst in letzter Zeit kamen Servos auf den Markt, bei denen die übliche Ansteuerung mittels Impulsen durch diverse digitale Bussysteme ersetzt wurde.

Allgemeines

Arten von Servos

Analogservos

Digitalservos

Prinzipieller Aufbau

Ein Servo besteht aus mehreren logischen Komponenten

  • Elektronik für die Pulsauswertung
  • Elektronik für eine Regelschleife
  • Leistungselektronik zur Ansteuerung eines Motors
  • Motor samt Getriebe
  • Positionsauswertung

Aus diesen Komponenten wird eine Regelschleife gebildet, so dass der Motor einem Positionssignal nachgeführt wird. Am Motor ist ein Getriebe angeflanscht, welches wiederum die Abtriebsscheibe bewegt, an der die Bewegung mechanisch abgegriffen werden kann.

Servo Regelschleife

Der Positionsencoder, im Regelfall ein mechanisch mit dem Getriebe gekoppeltes Potentiometer, stellt die Positionsinformation wieder der Regelelektronik zur Verfügung, die bei Abweichungen entsprechende Motorbewegungen veranlasst. Man gibt also einem Servo nicht eine Bewegung vor, sondern eine Position die es ansteuern soll. Die Regelelektronik fährt dann diese Position an und hält sie in weiterer Folge.

Konkreter Aufbau

Anstatt hier viele Worte zu verlieren, einige Bilder von realen Servos in diversen Aufbaustadien. So, oder so ähnlich, sind mehr oder weniger alle Servos intern aufgebaut. Gute (und teure) Servos haben anstelle eines Kunststoff-Getriebes eines aus Metall, das dann auch wesentlich robuster ist. Weitere Unterschiede bestehen in der Qualität des Potentiometers, sowie darin, ob die letzte Getriebeachse an der Gehäusedurchführung ein Kugellager besitzt oder ob es sich hier nur um ein Gleitlager handelt. Störungen an Motor und/oder Elektronik kommen selten vor. Wenn ein Servo ausfällt, dann hat das üblicherweise 2 Gründe: Das Getriebe hat "Zahnfraß" oder das Potentiometer ist verschlissen und liefert der Elektronik keine Rückmeldung über die tatsächliche Position des Abtriebshebels.

Das letzte Zahnrad des Getriebes hat normalerweise irgendwo eine Nase, welche bei Endanschlag an einem entsprechenden Anschlag im Gehäuse ansteht und so den Weg begrenzt.

Der Servohebel kann nach Lösen einer Schraube abgenommen werden und durch die Verzahnung in einer anderen Position wieder aufgesteckt werden. Wenn ein Servo also bei einem Mittenimpuls den Hebel nicht in Mittelstellung fährt, dann kann es auch daran liegen, dass der Hebel bereits verdreht aufgesteckt wurde. In so einem Fall einfach die Schraube im Hebel lösen, den Hebel abziehen und in der gewünschten Position neu aufstecken. Achtung: Oft sind die Verzahnungen so angeordnet, dass sich beim Drehen des Hebels um 180° etwas andere Positionen ergeben.

kleines Servo komplett
kleines Servo, Servohebel abgenommen
kleines Servo, Gehäuse demontiert, Getriebe
kleines Servo, Motor + Elektronik
großes Servo komplett
großes Servo, Gehäuseschrauben
großes Servo, Motor + Elektronik
großes Servo, Potentiometer
großes Servo, Getriebe mit aufgesetztem Abtriebshebel

Anschluss

Gab es früher je nach Herstellerfirma unterschiedliche Stecksysteme, so hat sich im Laufe der Zeit der sog. Uni-Anschluss durchgesetzt. Unterschiede gibt es eigentlich nur noch in der Lage, Größe und Position diverser Verpolschutznasen an den Buchsen, die ein verpoltes Einstecken in den Fernsteuerungsempfänger verhindern sollen. Allerdings sind diese Einrichtungen meistens so "windig" ausgeführt, dass man die Buchse mit etwas "sanfter" Gewalt dann doch auch verkehrt herum an den entsprechenden Stecker anstecken kann. Vorausgesetzt am Empfänger ist überhaupt eine mechanische Blockade gegen Verpolen vorgesehen. Elektrisch ist der Uni-Stecker so aufgebaut, dass er das in der Elektronik übliche 2.54mm Rastermaß benutzt. Er passt also problemlos auf die in der Elektronik üblichen Steckerleisten mit genau demselben Rastermaß.

Dieser Stecker ist mit einem 3-poligen Flachband-Kabel mit der eigentlichen Servoelektronik verbunden. Gebräuchlich sind einige verschiedene Farbschemen bei diesen Kabeln:

  • schwarz - rot - weiß
  • schwarz - rot - gelb
  • braun - rot - orange
  • schwarz - rot - blau

Getreu den in der Elektronik üblichen Gepflogenheiten ist schwarz (bzw. braun) immer Masse, rot immer die Versorungsspannung und die dritte Leitung (weiß, gelb, orange, blau, ...) ist die Signalleitung, über die das Servo mit Pulsen versorgt wird, welche ihm die anzufahrende Position mitteilen. Wenigstens in einem Punkt sind sich aber alle Hersteller einig: Die Versorgungsspannung wird meist über die mittlere der 3 Adern des Flachbandkabels geführt, die aber immer rot ausgeführt wird.

Stromversorgung

Werden Servos an einem µC betrieben, so ist es am Besten, sie aus einer eigenen Stromquelle (Akku) zu betreiben. Manche Servos erzeugen kleine Störungen auf der Versorgungsspannung, die einen µC durchaus zum Abstürzen bringen können. Muss man Servos gemeinsam mit einem µC von derselben Stromquelle betreiben, so sollte man sich gleich darauf einrichten, diesen Störimpulsen mit Kondensatoren zu Leibe rücken zu müssen. Unter Umständen ist hier auch eine Mischung aus kleinen schnellen Kondensatoren (100nF) und etwas größeren, aber dafür auch langsameren Kondensatoren (einige µF) notwendig.

Die eindeutig beste Option ist es aber, die Servos strommässig vom µC zu entkoppeln und ihnen ihre eigene Stromquelle zu geben. Servos sind nicht besonders heikel. Auch im Modellbau müssen sie mit unterschiedlichen Spannungen zurechtkommen, bedingt durch die dort übliche Versorung aus Akkus, die im Laufe der Betriebszeit des Modells natürlich durch die Entladung ihre Spannung immer weiter reduzieren. Im Modellbau werden Akkus mit 4 oder 5 Zellen verwendet, sodass Servos mit Spannungen von ca. 4V bis hinauf zu ca. 6V zurecht kommen müssen, wobei randvolle Akkus diese 6V schon auch mal überschreiten können. Bei sinkender Spannung verlieren Servos naturgemäß etwas an Kraft bzw. werden in ihrer Stellgeschwindigkeit unter Umständen langsamer.

Die Servos werden dann nur mit ihrer Masseleitung und natürlich mit ihrer Impulsleitung mit dem µC verbunden. Die Impulsleitung kann ganz einfach an einen Ausgangspin eines µC direkt angeschlossen werden, der mit 5V arbeitet.

Signalaufbau

Das Signal, das an den Servo geschickt wird, hat eine Länge von ungefähr 20ms. Diese 20ms sind nicht besonders kritisch und sind ein Überbleibsel von der Technik mit der mehrere Kanäle über die Funkstrecke einer Fernsteuerung übertragen werden. Für das Servo wichtig ist die Impulsdauer in der ersten Phase eines Servosignals. Nominell ist dieser Impuls zwischen 1ms und 2ms lang. Wobei das jeweils die Endstellungen des Servos sind, an denen es noch nicht mechanisch begrenzt wird. Eine Pulslänge von 1.5ms wäre dann Servomittelstellung. Für die Positionsauswertung des Servos haben die 20ms Wiederholdauer keine besondere Bedeutung, sieht man einmal davon ab, dass ein Servo bei kürzeren Zeiten entsprechend öfter Positionsimpulse bekommt und daher auch öfter die Position gegebenenfalls korrigiert, was möglicherweise in einem etwas höheren Stromverbrauch resultiert.

Servo Impulsdiagramm

Den meisten Servos macht es nichts aus, wenn die Länge des Servoprotokolls anstelle von 20ms auf z.B. 10ms verkürzt wird. Bei der Generierung des Servosignals muss man daher den 20ms keine besondere Beachtung schenken. Eine kleine Pause nach dem eigentlichen Positionssignal reicht in den meisten Fällen aus und es spielt keine allzu große Rolle, wie lange diese Pause tatsächlich ist. Generiert man das Impulsdiagramm z.B. mit einem Timer, so konzentriert man sich daher darauf, dass man den 1,0 - 2,0ms Puls gut generieren kann und nicht auf die 20ms.

Reale Servos haben allerdings in den Endstellungen noch Reserven, so dass man bei vielen Servos auch Pulslängen von 0,9 bis 2,1 oder sogar noch kleinere/größere Werte benutzen kann. Allerdings sollte man hier etwas Vorsicht walten lassen. Wenn das Servo unbelastet in einer der Endstellungen deutlich zu "knurren" anfängt, dann hat man es übertrieben. Das Servo ist an seinen mechanischen Endanschlag gefahren worden und auf Dauer wird das der Motor bzw. das Getriebe nicht aushalten.

Zu allem Überfluss gibt es auch noch Servos einer bestimmten Marke, die eine etwas andere Impulslänge verwenden. Das ganze Timing ist etwas straffer, so dass die Servomittelstellung bei etwa 1.4ms erreicht wird. Viele programmierbare Fernsteuerungen haben daher für einzelne Servos getrennte Einstellungen und unterscheiden zwischen Standard-Servos und MPX-Servos.

Signalerzeugung für 1 Servo (C)

Für einen ersten Servotest reicht es aus, ein Servo mit seinem Signaleingang an einen Ausgang des µC zu hängen und ganz einfach mittels _delay_us() bzw. _delay_ms() ein entsprechendes Impulsdiagramm zu erzeugen (Einbinden von util/delay.h nicht vergessen):

#define F_CPU 1000000UL

#include <avr/io.h>
#include <util/delay.h>

int main (void)
{
  DDRB = (1<<PB1);

  while( 1 ) {

    PORTB |= (1<<PB1);
    _delay_us( 1500 );    // in den 1500 steckt die Lageinformation
    PORTB &= ~(1<<PB1);

    _delay_ms( 18 );      // ist nicht kritisch
  }

  return 0;
}

Dass diese Technik natürlich nicht für Programme zu gebrauchen ist, die neben einer Servoansteuerung auch noch andere Dinge zu erledigen haben, braucht nicht extra betont zu werden. Wenn diese anderen Dinge in den verbleibenden 18ms untergebracht werden können (z.B. ADC-Abfrage oder Tasterabfrage) mag es noch toleriert werden, aber im Allgemeinen gibt es bessere Methoden. Für einen einfachen Servotest soll es aber zunächst genügen.

Signalerzeugung für 1 Servo mittels Timer (C)

Das Programm erzeugt die Impulse durch Nutzung des Timers im CTC-Mode + Interrupt. Der Timer wird vom

[math]\displaystyle{ \frac{\text{Prozessortakt}}{8} = \frac{1\text{MHz}}{8}=125\text{kHz} }[/math] gespeist.

Durch zwei Taster an PB0 und PB1 kann die Impulslänge, die an den Servo gesendet wird, angepasst werden. Bei jedem Compare Match mit OCR1A wird ein Interrupt ausgelöst, in dem der restliche Teil für die Periode gebildet wird.

Das Servo-Signal kann am OCR1A-Ausgang das Prozessors abgegriffen werden. Diesen Pin muss man laut Datenblatt eventuell auch dann auf Ausgang schalten, wenn er als Compare-Match-Ausgang benutzt wird. Alle Werte und damit alle Zeiten sind auf eine Taktfrequenz von 1Mhz gerechnet, müssen also bei Verwendung einer anderen Zeitbasis (z.B. anderer Quarz) entsprechend angepasst werden.


#define F_CPU 1000000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

ISR( TIMER1_COMPA_vect )                // Interruptbehandlungsroutine
{
  OCR1A = 2500-OCR1A;			// Das Servosignal wird aus der Differenz von
                                        // Periodenlänge (2500*0,008ms=20ms) und letztem
                                        // Vergleichswert (OCR1A) gebildet 
}


int main (void)
{
  DDRB = 0b11111100;
  PORTB = (1<<PB1) | (1<<PB0);          // Pullup für PB0 und PB1

  TCCR1A = (1<<COM1A0);                 // Togglen bei Compare Match
  TCCR1B = (1<<WGM12) | (1<<CS11);      // CTC-Mode; Prescaler 8
  TIMSK  = (1<<OCIE1A);                 // Timer-Compare Interrupt an

  OCR1A = 2312;                         // Neutralposition ((2500-2312)*0.008ms=1,5ms)

  sei();                                // Interrupts global an

  while( 1 ) {

    if ( !(PINB & (1<<PINB0)) ) {       // Impuls-Zeit verlängern
      cli();
      OCR1A = OCR1A + 3;
      sei();
      _delay_ms(50);
    }

    if ( !(PINB & (1<<PINB1)) ) {       // Impuls-Zeit verkürzen
      cli();
      OCR1A = OCR1A - 3;
      sei();
      _delay_ms(50);
    }
  }

  return 0;
}

Signalerzeugung für mehrere Servos mittels Timer (C)

Das Prinzip der Servoansteuerung mehrerer Servos besteht darin, dass die Pulse für die einzelnen Servos nacheinander generiert werden. Dazu wird mit einem Timer im CTC Modus jeweils eine Zeitdauer eingestellt, die der Impulszeit des nächsten Kanals entspricht. Der jeweilige Ausgangspin wird auf 1 gestellt und mittels des CTC-Steuerregisters eine entsprechende Zeitdauer eingestellt. Beim Auftreten des entsprechenden Interrupts wird dann dieser Pin wieder abgeschaltet, der nächste Pin eingeschaltet und dessen Zeitdauer eingestellt. Auf diese Art werden nacheinander alle Servos mit ihren entsprechenden Pulsen versorgt - man könnte das Verfahren auch als Multiplex-Betrieb bezeichnen. Auf die 20ms Wiederholzeit wird hier überhaupt keine Rücksicht genommen. Durch das Reihum-Generieren der einzelnen Pulse entsteht für jedes Servo nach seinem Puls automatisch eine kleine Wartezeit, die zudem auch noch von den tatsächlichen Pulslängen abhängt. Aber wie bereits besprochen: den Servos ist diese Pausenzeit egal, sie kümmern sich nur um ihre Pulszeit und richten sich entsprechend aus.

Das nachfolgende Programm ist für einen ATmega16 geschrieben, der mit 11.056Mhz getaktet wird. Benutzt wird der Timer 2 (ein 8 Bit Timer), der dazu im CTC-Modus betrieben wird. Bei anderen Taktfrequenzen muss der Prescaler des Timers entsprechend angepasst werden, so dass der Ausdruck F_CPU / PRESCALER / 1000 einen Wert nahe an 128 aber auch nicht größer als 128 ergibt. Ist auch logisch: Es wird der 8-Bit Timer verwendet, der von 0 bis 255 zählt. Mit diesen 256 möglichen Timerwerten muss eine Zeit von 2ms abgebildet werden, daher darf der Timerwert für 1ms nicht höher als 127 werden. Je kleiner dieser Wert aber ist, in desto weniger Abstufungen kann diese 1ms vom Timer aufgelöst werden und desto weniger Servopositionen hat man daher dann auch zur Verfügung. Bei 1Mhz Taktfrequenz wäre zb ein Vorteiler von 8 eine gute Wahl.

Jedes einzelne der 8 Servos kann unabhängig von allen anderen auf seine Postion gefahren werden, indem man im Array ServoValue für das Servo einen neuen Wert einschreibt. Zulässige Werte bewegen sich im Bereich 0 bis 2*CENTER (die jeweiligen Endpositionen).

Sollen weniger als 8 Servos angesteuert werden, so werden am besten im Array ServoPuls, die nicht benutzten Servokanäle auf 0 gestellt, und somit die Ausgabe eines Pulses auf diesem Kanal unterdrückt.

//
// Programm fuer einen ATmega16
//
#define F_CPU 11056000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

//
// 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      128
#define PRESCALER_BITS (1<<CS22) | ( 1 << CS20 )

#define MILLISEC_BASE  ( F_CPU / PRESCALER / 1000 )
#define CENTER         ( MILLISEC_BASE / 2 ) 

//
// Konfiguration der Servoleitungen
//
#define NR_SERVOS      8
#define SERVO_DDR      DDRD
#define SERVO_PORT     PORTD
uint8_t ServoPuls[NR_SERVOS] = { 1<<PD0, 1<<PD1, 1<<PD2, 1<<PD3,
                                 1<<PD4, 1<<PD5, 1<<PD6, 1<<PD7 };
//
// Werte für die Servoposition
// Gültige Werte laufen von 0 bis 2 * CENTER
// 0           ... ganz links
// CENTER      ... Mittelstellung
// 2 * CENTER  ... ganz rechts
//
volatile uint8_t ServoValue[NR_SERVOS];

ISR (TIMER2_COMP_vect) 
{
  static uint8_t ServoId = 0;

  //
  // den Puls des aktuellen Servos beenden
  //
  SERVO_PORT &= ~ServoPuls[ServoId];

  //
  // welches ist das nächste aktuelle Servo?
  //
  if( ++ServoId >= NR_SERVOS )
    ServoId = 0;

  //
  // die Ausgangsleitung fuer dieses Servo auf 1; den Puls beginnen
  //
  SERVO_PORT |= ServoPuls[ServoId];

  //
  // den Timer so einstellen, dass bei Pulsende, die ISR erneut aufgerufen wird
  //
  OCR2 = MILLISEC_BASE + ServoValue[ServoId];
}

void InitServo()
{
  uint8_t i;

  //
  // Die Servoleitungen auf Ausgang stellen
  //
  SERVO_DDR = ServoPuls[0] | ServoPuls[1] | ServoPuls[2] | ServoPuls[3] |
              ServoPuls[4] | ServoPuls[5] | ServoPuls[6] | ServoPuls[7];

  //
  // Alle Servos in Mittelstellung
  //
  for( i = 0; i < NR_SERVOS; ++i )
    ServoValue[i] = CENTER;

  //
  // Timer auf CTC Modus konfigurieren
  //
  OCR2 = MILLISEC_BASE + ServoValue[0];
  TIMSK |= (1<<OCIE2);
  TCCR2 = (1<<WGM21) | PRESCALER_BITS;  // CTC mode
}

int main(void)
{
  InitServo();

  sei();

  _delay_ms( 1000 );

  //
  // testweise einfach alle 8 Servos ansteuern
  // jedes Servo soll sich unterschiedlich schnell bewegen
  //
  while( 1 ) {


    ServoValue[0] += 2;
    if( ServoValue[0] > 2*CENTER )
      ServoValue[0] -= 2*CENTER;

    ServoValue[1] += 1;
    if( ServoValue[1] > 2*CENTER )
      ServoValue[1] -= 2*CENTER;

    ServoValue[2] += 2;
    if( ServoValue[2] > 2*CENTER )
      ServoValue[2] -= 2*CENTER;

    ServoValue[3] += 3;
    if( ServoValue[3] > 2*CENTER )
      ServoValue[3] -= 2*CENTER;

    ServoValue[4] += 1;
    if( ServoValue[4] > 2*CENTER )
      ServoValue[4] -= 2*CENTER;

    ServoValue[5] += 3;
    if( ServoValue[5] > 2*CENTER )
      ServoValue[5] -= 2*CENTER;

    ServoValue[6] += 2;
    if( ServoValue[6] > 2*CENTER )
      ServoValue[6] -= 2*CENTER;

    ServoValue[7] += 1;
    if( ServoValue[7] > 2*CENTER )
      ServoValue[7] -= 2*CENTER;

    _delay_ms( 40 );
  }
}

Anstelle des 8-Bit Timers 2 kann natürlich auch jeder andere Timer benutzt werden, wenn zb die Positionsauflösung zu gering ist. Der Timer muss lediglich über einen CTC Modus verfügen und es muss möglich sein, die 2ms möglichst gut mit den möglichen OCR Werten abzudecken. Da man aber des öfteren gar nicht so genaue Servopositionen benötigt, der 16 Bit Timer mit seinen Möglichkeiten beim Mega16 aber ein 'kostbarer Timer' ist, wurde hier bewusst der 8-Bit Timer 2 benutzt.