Styring av servomotor fra Raspberry Pico og Micropython

 65 total views,  2 views today

Her følger en enkel veiledning i hvordan man kan styre en servomotor fra Raspberry Pico. En servomotor lar deg stille rotoren i bestemte posisjoner, og kan brukes til å f.eks. styre et kamera eller en antenne.

(c) Anders Fongen, februar 2024

Innledning

Akslingen på en servomotor skal ikke snurre rundt og rundt, men stille seg i besteme posisjoner. Den egner seg derfor til å stille roret på en modellbåt, eller la et kamera eller en antenne følge et bevegelig objekt. Servomotoren SG90 er billig og lett, bruker lite strøm og er relativt sterk.

Servomotoren SG90

Vi skal i den følgende teksten viser hvordan vi kan styre SG90 fra Micropython på en Raspberry Pico, men løsningen blir veldig lik for andre kontrollere, f.eks. en ESP32.

Pulsbreddemodulasjon

SG90 har tre tilkoplingsledninger. To er for strømtilførsel (spenning+ og jord-), den tredje er for å stille rotoren i ønsket posisjon. Måten dette gjøres på er med såkalt pulsbreddemodulasjon.

En firkantpuls med en bestemt frekvens veksler mellom to spenningsnivåer, f.eks. 5 volt og 0 volt et visst antall ganger i sekundet, men ikke nødvendigvis med like lang tid på hver av spenningsnivåene. Den brøkdelen av tiden hvor spenningen er høy (f.eks. 5 volt) kaller vi duty cycle (av og til kalt arbeidssyklus på norsk), og illustrasjonen nedenfor viser tre firkantpulser med henholdsvis 50%, 75% og 25% duty cycle.

Tre ulike verdier av duty cycle for en firkantpuls. Kilde: wikipedia.org

PWM-signal i Micropython

Pulsbreddemodulasjon, heretter kalt PWM, er innebygget i Micropython og det er lett å sette en GPIO-port til å sende en firkantpuls med varierende duty cycle. Et eksempel på programsetninger som skaper en slik firkantpuls ser slik ut:

# Testkode for WPM-signal
from machine import PWM, Pin
import time
gpioport = 28 # Settes etter behov
pwmpin = PWM(Pin(gpioport, Pin.OUT))
pwmpin.freq(500) 

while True:
    for x in range(0,5):
        pwmpin.duty_u16(16384*x)
        time.sleep(1)

Som det fremgår av programkoden over styres duty cycle med metodekallet duty_u16(verdi), hvor parameterverdien kan variere mellom 0 og 65353 (som er den høyeste verdien for et usignert 16-bits tall). Under vises hvordan det avgitte signalet ser ut på et oscilloskop:

PWM-signal med varierende duty cycle, vist på et oscilloskop.

Oppkopling av SG90

Som tidligere nevnt, SG90 har tre tilkoplingsledninger: Rød ledning for forsyningsspenning koples til pin 40 (VBUS), evt. 39 (VSYS) på Raspberry Pico. Brun ledning koples til jord, f.eks. på pin 38. Orange ledning skal ha PWM-signalet, og du må velge en GPIO-port til dette formålet. I programeksemplet over er GPIO nr. 28 valgt, den finnes på pin 34. En såkalt “pinout” for Raspberry Pico er vist under.

Pinout for Raspberry Pico

Her følger en kort video for å vise effekten av varierende duty-cycle i PWM-signalet:

Her vises hvordan Raspberry Pico koples til en SG90 servomotor

Kalibrering av duty cycle

Når oppkoplingen er gjort kan du teste at motoren reagerer på ulike duty cycle-verdier ved å stille seg i en bestemt posisjon. Nå kan du kalibrere disse verdiene, ved at du noterer hvilke verdier som tilsvarer de posisjonene du ønsker å stille rotoren i. Min erfaring er at forholdet mellom rotorvinkel og duty cycle-verdier er noenlunde lineært, så du kan skrive kode som interpolerer mellom de observerte verdiene. Programmet som kjøres i videoen ovenfor ser slik ut:

from machine import Pin, PWM
import time
gpioport = 28 # Settes etter behov
pwmpin = PWM(Pin(gpioport, Pin.OUT))
pwmpin.freq(50) # Firkanpulsen har 50 Hz
for x in range(2000,7000,100):
    pwmpin.duty_u16(x)
    time.sleep(0.2)

I dette bestemte eksperimentet finner jeg ut at rotoren beveger seg en halv omdreining (180 grader) med duty cycle-verdier mellom 1150 og 7600, altså med et intervall på 7600-1150=6450. Dersom jeg antar at det er et lineært forhold mellom rotorvinkelen og duty cycle-verdier kan jeg lage en funksjon for å stille rotoren i en bestemt vinkel med denne programfunksjonen:

def setRotorAngle(pwmpin,degrees):
    dcvalue = int(degrees*6450/180+1150)
    pwmpin.duty_u16(dcvalue)

En forbedring av programmet ovenfor er derfor slik:

# Testkode for WPM-signal
from machine import Pin, PWM
import time

def setRotorAngle(pwmpin,degrees):
    dcvalue = int(degrees*6450/180+1150)
    # For rotating clockwise, change the previous line to:
    #dcvalue = int(7600-degrees*6450/180)
    pwmpin.duty_u16(dcvalue)

gpioport = 28 # Settes etter behov
pwmpin = PWM(Pin(gpioport, Pin.OUT))
pwmpin.freq(50) # Firkantpulsen har 50 Hz
for x in range(0,181,10):
    setRotorAngle(pwmpin,x)
    time.sleep(0.8)

Her beveger rotoren seg mot klokken med økende gradtall, om du ønsker bevegelsen slik gradtallet brukes på et kompass, altså med klokken må dcvalue kalkuleres slik:

dcvalue = int(7600-degrees*6450/180)

Eksempel på kamerastyring i to akser

En anvendelse av SG90 som jeg har selv har fattet interesse for er å styre et kamera eller en antenne i to akser for å kunne peke på et punkt oppe i luften eller verdensrommet. Et punkt på himmelrommet kan beskrives med to vinkler: Azimuth, som er vinkelen i horisontalplanet relativt til nord, og Elevation, som angir den vertikale vinkelen relativt til horisonten (les her for flere detaljer). En slik innretning kalles en Azimuth-Elevation rotor. Disse finnes i alle størrelser, er bygget for utendørs bruk og er ganske dyre. For eksperimentformål finnes det enkle konstruksjoner som benytter to SG90-motorer for det samme formålet. Søk etter “2 Axis Pan Tilt Mounting Kit” på Ebay: Her vises et bilde av en slik enhet:

Enkel Azimuth Elevation rotor basert på to SG90 motorer

For å styre denne enheten til et punkt på himmelen må du kople begge motorene til Raspberry Pico med felles spenning og jord, men med de orange ledningene til hver sin GPIO-port som du styrer med programsetningene vist ovenfor.

Rekkevidden til en SG90-motor er 180 grader. Skal du rekke over hele himmelkulen trenger du en rekkevidde for Azimuth på 360 grader, og 90 grader for Elevation. Et lite knep kan allikevel gi deg full dekning av himmelkulen over deg:

  1. Dersom Azimuth er 0-179 grader, skal den horisontale rotoren stilles til denne vinkelen, og den vertikale rotoren (Elevation) stilles til den ønskede vinkelen 0-90 grader.
  2. Dersom Azimuth er 180-359 grader, skal den horisontale rotoren stilles til Azimuth-180 grader, og den vertikale skal stilles til 180-Elevation grader. Dvs, at den vertikale rotoren legger seg “bakover” for å dekke den venstre delen av kompassrosen. Dersom det er et kamera på denne plattformen må du ta hensyn til at bildene da blir opp-ned.

Når du skriver kode for en slik innretning må du dessuten ta hensyn til at azimuth-rotoren går “mot klokka” med økende duty cycle-verdi, og du må snu litt om på regnestykket, som vist over.

Python-klasse for styring av en Az-El plattform

Nå setter vi sammen tidligere detaljer, inkludert observerte verdier for 0 og 180 graders rotasjon, til en Python-klasse som styrer begge aksene under ett:

# Python class to control a simple Azimuth-Elevation
# platform. It uses two SG90 servo motors for the two
# axes. Since they only rotate 180 degrees, the 180
# degree range of the elevation rotor is used to cover
# the left half (180-359 degrees) of the azimuth

from machine import Pin, PWM
class AzElPlatform:

    def __init__(self,azrotor, elrotor):
        self.azport = PWM(Pin(azrotor))
        self.elport = PWM(Pin(elrotor))

        self.azport.freq(50)
        self.elport.freq(50)

    def setDirection(self,az,el): # Angle in degrees
        # Check parameters: 0-359 and 0-90 allowed
        if not az in range(0,360): return
        if not el in range(0,91): return
        if az>180:
            az = az-180
            el = 180-el # Bend elevation backwards for left half
        # Experimentally established values for
        # Calculation of duty cycles corresponding
        # to rotor angles
        dutyAz = 7800 - az * 6600/180
        #dutyAz = ((180-az)/180*6600) + 1200
        dutyEl = el * 7000/180 + 1200

        self.azport.duty_u16(int(dutyAz))
        self.elport.duty_u16(int(dutyEl))

Demonstrasjonsvideo

Her følger en video hvor denne Python-klassen blir demonstrert med følgende testprogram:

from AzElPlatform import AzElPlatform
import time
azel = AzElPlatform(16,17) # GPIO-porter for Az og El
el = 20
for az in range(90,271,10):
    azel.setDirection(az,el)
    time.sleep(0.5)

Denne programkoden beveger plattformen fra 90 til 270 grader i horisontalplanet, og må derfor skifte mellom innstilling nr.1 og 2 fra diskusjonen ovenfor. Legg derfor merke til på videoen hvordan Elevation-rotoren snur seg rundt samtidig som Azimuth-rotoren vrir seg en halv omdreining. Videoen demonstrerer altså hvordan vi dekker hele himmelkuppelen med to rotorer med rekkevidde 180 grader.

Demonstrasjon av Azimuth-Elevation platform med to SG90 servomotorer.

One thought on “Styring av servomotor fra Raspberry Pico og Micropython

  1. Pingback: Mottak og bruk av ADS-B signaler fra fly | Nettverk, distribusjon, radio

Leave a Reply

Your email address will not be published. Required fields are marked *

5 × three =