Pulsweitenmodulation für Servos

Es muss wieder mehr Bewegung in die Sache kommen.

Also wird ein Servomotor an den Mikrokontroller angeschlossen.
… aber an welchen?

Ein Servo-Shield oder die Bibliothek für den ATmega328 wäre zu einfach.

Wie wäre es mit dem ATtiny?

Und die nächste Frage ist quasi “vorprogrammiert”:

Wie bekommt man jetzt das richtige PWM Signal hin?


Warum einfach wenn’s auch kompliziert geht?

Mir wird ja gerne vorgeworfen nie den einfachen Weg zu gehen. Das ist kein angeborener Masochismus sondern meist eine eigene Form von Wissensdurst.

Und Detailswissen ist um so wichtiger, wenn es den einfachen Weg nicht gibt. Ich zwinge mich also selbst dazu tiefer zu graben, und auch wenn es anfangs nutzlos erscheint, aus solchen “Sinnlosigkeiten” konnte ich (oft erst Jahre später) unverzichtbares Wissen gewinnen, das sich manchmal auch wirtschaftlich niederschlägt.

Unsere Bastel-Servos werden durch digitale Pulse gesteuert. Da geht es darum wie lange (bzw. wie breit) ein Strompuls andauert im Verhältnis zur Zeit, in der kein Strom fließt. Deshalb spricht man von Pulslängen- oder Pulsweitenmodulation (engl. Pulse Width Modulation, kurz PWM).

Aus einem solchen binären Signal mit einer bestimmten Länge wird an Hand der Dauer des Pulses bestimmt, auf welche Position sich ein Servo hindrehen soll.

Die genauen Werte und Frequenzen “erlernt” man übrigens am Besten aus dem Arduino Servo.h Header:

1#define MIN_PULSE_WIDTH       544     // the shortest pulse sent to a servo  
2#define MAX_PULSE_WIDTH      2400     // the longest pulse sent to a servo 
3#define DEFAULT_PULSE_WIDTH  1500     // default pulse width when servo is attached
4#define REFRESH_INTERVAL    20000     // minumim time to refresh servos in microseconds 
  • Die Servos arbeiten mit Pulsen von 50 Hertz, also jedes Puls-Interval dauert 20 Millisekunden (oder 20000 Mikrosekunden).
  • Ein Puls besteht aus einem kurzen High-Signal und einem ausfüllenden Low-Signal.
  • Der kürzste Puls hat eine Dauer von 544 Mikrosekunden (das wäre dann 0 Grad Drehung).
  • Der längste Puls hat eine Dauer von 2400 Mikrosekunden (je nach Modell sind das dann ca. 90, 180 oder 360 Grad Drehung).

Die genaue Graddrehung ist aber so spezifisch, dass man den Servo immer kalibrieren muss, das heißt, wir müssen rausfinden und speichern, welcher Pulswert welchem Winkel entspricht. Einige meiner Servos können sich nämlich noch ein paar Grad weiter drehen, als es eigentlich spezifiziert ist, und andere regieren auf die Minimum and Maximum Werte nicht, und setzen erst die Bewegung erst etwas darüber oder darunter um.

Software Puls-Wellen

Die meisten MCUs haben eigene PWM-Pins, wo man Pulse per Hardware generieren lassen kann. Auf dem Arduino UNO sind sie mit einem Wellensymbol ~ markiert.

Unglücklicherweise ist der Frequenz oft mit dem Takt des Chips verknüpft (bzw. mit einer ganzzahligen Teilmenge davon).
Die Servo Bibliothek der Arduino-IDE nutzt daher Timer und Interrupts, über die die Pins dann manuell ein und ausgeschaltet werden.

Doch für den ATtiny ist die Bibliothek leider nicht implementiert. Wenn es jetzt aber nicht um hoch präzise Positionierung geht, sondern einfach nur eine Demo durchgeführt werden soll, können wir die Pulse auch mit den Arduino Warte-Routinen “quick-and-dirty” nachbauen. (Oder anders gesagt, ich bin zu faul mir jetzt die Registercodes herauszusuchen um mir eine Timer-Variante nachzubauen.) Die Funktion delayMicroseconds soll uns hier anfangs dienen.

Und so wird es gemacht:

  • Output-Pin auf HIGH setzen mit digitalWrite()
  • Zwischen 544 und 2400 Mikrosekunden per delayMicroseconds() warten
  • Pin auf LOW setzen mit digitalWrite()]
  • Nun delayMicroseconds(20000 - vorheriges_delay); ausführen
  • Und das ganze wieder von Vorne ausführen …

Und tatsächlich, es funktioniert. Der Servo wackelt zwar wie ein alter Alkoholiker, weil das Timing der Pulse recht ungenau ist, doch er dreht sich ruckelig ungefähr dort hin, wo er hingehört.

Jetzt kann man die Methode verfeinern. Man kann mit micros() feststellen, wieviel Zeit zwischen den Wartezeiten und anderen ausgeführten Codes vergangen ist und das nächste delayMicroseconds() entsprechend verringern.
Auch kann man am Richtwert 20000 herumspielen, ihn etwas erhöhen oder erniedrigen, denn je nach CPU Frequenz sieht das Timing etwas anders aus.

Mir ist es so nach etwas Herumprobieren gelungen, einen kleinen Servo-Tester umzusetzen, der nur noch wenig zittert und die Position eines verstellbaren Widerstands (Potentiometers) auf die Servoausrichtung überträgt.

Servo-PWM-Tester

Mein bester ermittelter delayMicroseconds() Referenzwert ist übrigens 20260 (anstatt von 20000) bei einer Taktung meines ATtiny45 von 1 MHz. Da zittert der Servo dann fast gar nicht mehr.

Fazit

Und ja, mit einem ATmega328 wäre das in 4 Zeilen Code auch erledigt gewesen, doch aktuell habe ich ein paar ATtiny45 herumliegen, die ich nicht brauche, und andererseits hätte ich sonst nie gelernt, dass Servos mit 50 Hertz laufen und mit 0.5 bis 2.5 Millisekunden Pulsen gesteuert werden.

Der Servotester ist jedenfalls praktisch, denn wenn man Servos verbaut, sollte man sie vor dem Einbau auf eine Position stellen, die für die weitere Anwendung günstig ist.

Natürlich kann man sich so ein Gerät auch für ein paar Euro kaufen, aber … selber machen ist viel cooler ;)


Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!