Raspberry PI + Servo

Ansteuerung eines Servos mit dem Raspberry PI 3.
Hierbei geht es nur um die grundlegende Technik, der Einsatz könnte beispielsweise ein Mobiler Roboter sein.

Der Servo soll hierbei über den Software PWM angesteuert werden um die Hardwar PWMs für andere Funktionen frei zu halten.

Zusätzlich soll der Servo nicht wie üblich über Integer Werte gesteuert werden sondern über eine Gradzahl im Bezug zur Mittelstellung des Servos.

Das unten stehende Bild zeigt was hiermit gemeint ist.

Auf die eigentliche Funktionsweise eines Servos gehe ich hier nicht ein, dies kann bei Bedarf bei http://rn-wissen.de/wiki/index.php/Servos oder anderen Websites nachgelesen werden.

Benötigte Hardware

  • PI3 (oder älter bitte auf die richtigen Pins achten)
  • Standard-Servo
  • Diverse Kabel ( Vorgefertigte Pin-Header Kabel)

Aufbau der Schaltung

Die Ansteuerung des Servos geschieht in meinem Fall über GPIO 27. Die Spannungsversorgung liefert der PI. Für größere Servos müsste evtl. auf eine externe Spannungsversorgung zurückgegriffen werden.

(Die GPIOs gelten nur in Verbindung mit pi4j und wirePi)

 

Software

Als Entwicklungsumgebung verwende ich BlueJ auf dem PI selbst. Hiermit habe ich die folgenden Klassen erstellt.


Code

Ein Erster Test

Ein guter Anfang ist immer den im Internet gefundenen Code eine Main Funktion zu packen, um zu schauen ob überhaupt was klappt. Dies habe ich in Form der FirstTest Klasse getan. Sie fährt alle Positionen des Servos ab. Die Funktionalität ist hier nicht gekapselt.

import com.pi4j.wiringpi.SoftPwm;

public class FirstTest
{
    static int max = 27;
    static int min = 4;
    static int pin = 27;

    static void setMin(int min){
        FirstTest.min = min;
    }

    static void setMax(int max){
        FirstTest.max = max;
    }

    public static void main(String[] args) throws InterruptedException {

        // initialize wiringPi library
        com.pi4j.wiringpi.Gpio.wiringPiSetup();

        // create soft-pwm pins (min=0 ; max=100)
        SoftPwm.softPwmCreate(pin, 0, 200);

        int j = 0;
        // continuous loop
        while (j < 10) {
            // fade LED to fully ON
            for (int i = min; i <= max; i++) {
                SoftPwm.softPwmWrite(pin, i);
                Thread.sleep(100);
            }

            // fade LED to fully OFF
            for (int i = max; i >= min; i--) {
                SoftPwm.softPwmWrite(pin, i);
                Thread.sleep(100);
            }
            j++;
        }

        // make sure to stop software PWM driver/thread if you done with it.
        //
        SoftPwm.softPwmStop(1);
    }
}

Gekapselter Code

Als nächsten Schritt habe ich den Code aus dem ersten Test genommen und ihn mit ein paar Änderungen in eine Klasse gepackt. Hierbei habe ich auch die Umrechnung von absoluten Werten in Grad implementiert. Für den Test der Klasse habe ich wiederum eine Main-Klasse erstellt die alles mal aufruft.

Servo-Klasse

Klasse zur Steuerung des Servos. Die zwei wichtigsten Methoden sind:

  • setPos – Setzen der Position, Wertebereich von posMin bis posMax wobei (posMax-posMin+1)  die Mittelstellung ist.
  • setDeg – Diese Methode projeziert die angegebene Gradzahl auf die möglichen Positionen (aktuell sehr ungenau 🙁 )
import com.pi4j.wiringpi.SoftPwm;
import com.pi4j.io.gpio.Pin;
import com.pi4j.io.gpio.RaspiPin;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioPin;

/**
 * Control class for servo motors
 * @author Stefan Fambach (With help of many others)
 * @version 0.1
 * 
 * This class can either set position by values between posMin and posMax
 * or it can caluculate the position by an angle where degMin <= angle <= degMax
 * 
 * No warranty free for use see GPL v3.
 */
public class Servo
{
    Pin pin;

    int degMin = -90;
    int degMax = 90;

    int posMin = 4; // to have a center
    int posMax = 25;
    int pos = -1;

    /**
     * range between min and max degree
     */
    double degDelta = degMax - degMin;

    /**
     * range between min and max position
     */
    double posDelta = posMax - posMin;

    /**
     * multiplier for degree to pos calculation
     */
    double multiplier = posDelta/degDelta;

    /**
     * Time in ms to wait after new position is set
     */
    int timeout = 1000;

    /**
     * Default constructor, default pin is GPIO_27
     */
    public Servo(){
        this(RaspiPin.GPIO_27);
    }

    /**
     * Constructor for objects of class Servo
     */
    public Servo(Pin controlPin) 
    {
        this.pin = controlPin;
        // initialize wiringPi library
        com.pi4j.wiringpi.Gpio.wiringPiSetup();

        // create soft-pwm pins (min=0 ; max=100)
        SoftPwm.softPwmCreate(this.pin.getAddress(), 0, 200);

        moveCenter();

    }

    /** 
     * set position in degree, only 20 positions are possible, each step will be calculated
     */
    boolean setDeg(int deg) throws Exception {
        if(deg > degMax){
            throw new Exception( "Entered deg: "+ deg + "is greater maxDeg "+degMax);
        }

        if(deg < degMin){
            throw new Exception( "Entered deg: "+ deg + "is smaller maxDeg "+degMin);            
        }

        if(deg < 0){
            deg = (-degMin)+ deg;
        }else{
            deg -= degMin;
        }

        return setPos((int) (deg * multiplier));
    }

    /** 
     * set position posMin - posMax (default 0-21)
     */
    public boolean setPos(int pos)throws Exception{
        if(pos < 0 ){
            throw new Exception (pos + " Pos to small (<0) ");
        }

        if(pos > posMax ){
            throw new Exception (pos + " Pos to big > "+posMax+")");
        }
        if(pos != this.pos){
            this.pos = pos;
            SoftPwm.softPwmWrite(pin.getAddress(), posMin + pos);
            sleep(timeout);
            SoftPwm.softPwmWrite(pin.getAddress(), 0);
            return true;
        }
        return false;
    }

    /** 
     * set Servo to center position
     */
    public void moveCenter(){
        try{
            setPos((posMax-posMin+1)/2);
        } catch (Exception ex){
            // will hopefully never happen
        }
    }

    /** 
     * set Servo to min position
     */
    public void moveToMinPos(){
        try{
            setPos(0);
        } catch (Exception ex){
            // will hopefully never happen
        }
    }

    /** 
     * set Servo to max position
     */
    public void moveToMaxPos(){
        try{
            setPos(posMax-posMin);
        } catch (Exception ex){
            // will hopefully never happen
        }
    }

    public int getPos(){
        return this.pos;
    }

    public int getDegMin(){
        return degMin;
    }

    public int getDegMax(){
        return degMax;
    }

    public void setDegMin(int min){
        this.degMin = min;
        calculateVariables();
    }

    public void getDegMax(int max){
        this.degMax = max;
        calculateVariables();
    }

    public void setPosMin(int min){
        this.posMin = min;
        calculateVariables();
    }

    public void setPosMax(int max){
        this.posMax = max;
        calculateVariables();
    }

    public int getPosMin(){
        return  this.posMin;
    }

    public int getPosMax(){
        return  this.posMax;
    }

    /**
     * calculate the ranges for deg and pos 
     * as well as the multiplier for degree positioning of the servo
     */
    private void calculateVariables(){
        degDelta = degMax - degMin;
        posDelta = posMax - posMin;
        multiplier = posDelta/degDelta;
    }

    private void sleep(int msec){
        try
        {
            Thread.sleep(msec);
        }
        catch ( InterruptedException e)
        {
        }
    }

    public void release(){
        SoftPwm.softPwmStop(pin.getAddress());
        GpioController gpio = GpioFactory.getInstance();
        gpio.unprovisionPin(new GpioPin[]{gpio.getProvisionedPin(pin)});


    }
}

Test-Klasse

Klasse mit Main-Methode zum Testen der Servo-Klasse. In der Main-Methode werden alle Positionen abgefahren wobei die Positionierung über den Winkel relativ zur 0 Position erfolgt.

import com.pi4j.io.gpio.RaspiPin;


public class TestServo
{

    public static void main(String args[]){

        // Create the range sensor
        Servo servo = new Servo(RaspiPin.GPIO_27); 

        int j = 0;

        do {
            System.out.println("***** Start degree test from left to right *****");     

            int curPos = servo.getDegMin()-1;
            // from left to right
            for(int i = servo.getDegMin(); i < servo.getDegMax(); i++){
                try{
                    servo.setDeg(i);
                }catch(Exception e){
                    System.out.println(e.getMessage());
                }
                int cpos = servo.getPos();
                if(cpos > curPos){
                    curPos = cpos;
                    System.out.print("\n "+curPos+" : ");
                }

                System.out.print(i+" , ");

            }

            System.out.println("***** Start degree test from right to left *****");  
            curPos = servo.getDegMax() + 1;
            // from left to right
            for(int i = servo.getDegMax(); i > servo.getDegMin(); i--){
                try{
                    servo.setDeg(i);
                }catch(Exception e){
                    System.out.println(e.getMessage());
                }
                int cpos = servo.getPos();
                if(cpos < curPos){
                    curPos = cpos;
                    System.out.print("\n "+curPos+" : ");
                }
                System.out.print(i+" , ");

            }
            j++;
        } while (j < 5);

        servo.release();
    }
}

Tips

Das Positionieren über die PI4j Lib ist sehr ungenau für die 180° Bewegungsspielraum des Servos stehen gerade mal rund 20 (+-2) Positionen zur Verfügung. Das sind rund 9° pro Position.  Evtl. sollte man vorher testen welche Positionen für den Aktuellen Zweck am besten geeignet sind. Sollte der genaue Winkel wichtig sein, ist es besser diesen vorab zu messen als ihn über die setDeg Methode berechnen zu lassen.

Es gibt noch weitere Lib’s (Servoblast, … ) die evtl genauer sind, man könnte auch überlegen die Programmiersprache zu wechseln und dann den Ensprechenden Code über das Nativ-Interface anzusprechen. Als nächsten Versuch klingt Servoblast ersteinmal vielversprechend.

Weitere Schritte

PWM in Thread

Ich habe versucht den Software PWM innerhalb eines Threads laufen zu lassen, jedoch ohne Erfolg. Der Servo weigert sich zu funktionieren.  Über die Ursache hierfür bin ich mir noch im unklaren. Ich nehme allerdings an, dass hierdurch der Timer zerschossen wird.

ServoBlast

Nach diversen Tests mit unterschiedlichen Frequenzen habe ich mich entschlossen ServoBlaster od.  besser die PI variante (PiBits) zu verwenden. Die Installation gestaltet sich einfach wie folgt.

# holen der sourcen
wget https://github.com/richardghirst/PiBits/zipball/master

#umbenennen
mv master pibits.zip

#entpacken
unzip pibits.zip

#bauen
cd richardghirst-PiBits-96014c8/ServoBlaster/user
sudo make install 

#ändern des timeout von 2000 auf 500#
sudo nano /etc/init.d/servoblaster 

#eine reboot ist nicht unbedingt erforderlich 
sudo reboot

#ein starten reicht aus 
sudo /etc/init.d/servoblaster start

Programmierbeispiel

An dieser Stelle sollte nun eigentlich ein Code-Beispiel mit ServoBlast folgen. Da ich aktuell aber die Arbeiten am PI eingestellt  habe und mit dem ESP8266 und ESP32 unterwegs bin, sei auf die folgenden Links verwiesen:

https://github.com/richardghirst/PiBits/tree/master/ServoBlaster

http://www.leenabot.com/en/Servo-Motor-driver/

 

Problembehandlung

Exec Funktioniert nicht

Wenn die exec Funktion von Java nix macht ist hier ein guter Link zum Bugfixing.

http://www.javaworld.com/article/2071275/core-java/when-runtime-exec—won-t.html

Probleme mit println

Weiterhin könnte es helfen nicht die println Methode zu verwenden, sondern die print. Da zumindest mein PI den Abschluss der Zeile nicht richtig interpretieren konnte. print( „5=100\n“) hat jedoch funktioniert.

https://github.com/richardghirst/PiBits/tree/master/ServoBlaster

http://www.leenabot.com/en/Servo-Motor-driver/

Quellen

http://razzpisampler.oreilly.com/ch05.html
https://github.com/Pi4J/pi4j/blob/master/pi4j-example/src/main/java/MaestroServoExample.java
https://www.bluej.org/raspberrypi/ServoMotor.html
https://phyks.me/2015/05/controlling-servomotors-on-a-raspberry-pi.html
http://rn-wissen.de/wiki/index.php/Servos
https://github.com/richardghirst/PiBits/tree/master/ServoBlaster
https://github.com/BioMachinesLab/drones/wiki/Installing-Servoblaster

Raspberry Pi Servo Motor Steuerung

Between 0 and 1 – PWM with Raspberry Pi

 


Anhang

Pin-Belegung

 

2 Gedanken zu „Raspberry PI + Servo“

  1. Dafür, dass der Artikel über ein Jahr alt ist finde ich es recht schade, dass man an einigen Stellen weiterhin „… kommt noch“ lesen muss.
    Ebenso fehlt wohl irgendwas bei den „gistpen id“-Zeilen.. – oder ich verstehs nicht.

    1. Hallo und danke für deinen Kommentar.
      Ja das mit dem Alter des Beitrags ist so eine Sache, ich habe diesen im letzten Urlaub angefangen und bin dann nicht mehr dazu gekommen, bzw. habe mich in letzter Zeit mehr mit dem ESP8266/ESP32 beschäftigt. Ich bin leider noch zu keinem dokumentierten Test mit ServoBlast gekommen vielleicht im nächsten Urlaub ;). Ich habe den Beitrag mal dementsprechend angepasst um den Leser hierüber zu informieren. Es sind nun nochmal die zwei Seiten gelinkt, welche die Verwendung von ServoBlast aus meiner Sicht ganz gut beschreiben.
      Das mit der Gistpen id ist ärgerlich. Eine Version von Gistpen hatte leider nicht funktioniert, diese führte dazu, dass jede Seite mit Quellcode nicht mehr angezeigt wurden. Aus diesem Grunde hatte ich es leider deaktivieren müssen und wie immer hatte ich keine Zeit die alten Seiten zu überarbeiten. Auf neunen Seiten biete ich jetzt einen Download an. lange Rede kurzer Sinn, Ich habe Gistpen in der aktuellen Version reaktiviert und es scheint wieder zu funktionieren.
      Gruß SFA

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.