[MicroPython] SG90 Servo-Motor mit Einzeltaster ansteuern

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
sansevero
User
Beiträge: 4
Registriert: Dienstag 5. November 2024, 07:34

Hallo liebe Forengemeinde,

zunächst einmal kurz zu meinen Skills: wenig bis gar nicht vorhanden
Ich baue aktuell eine Shuttersteuerung auf, bei der eine Blende auf Tastendruck für eine vordefinierbare Zeit (bspw. 3 sek) geschlossen werden soll.
Danach soll der Servo wieder in die Ausgangslage drehen.

Okay, wahrscheinlich werden jetzt einige gähnen oder mit den Augen rollen, aber als ich mich das letzte mal intensiver mit sowas beschäftigt habe, war basic auf nem Commodore VC20 der letzte heiße Shice :lol:

Hierzu habe ich bereits ein sehr nützliches script gefunden, welches ich im Anschluss anhänge.

Das script stammt von dieser Seite (auf der auch die Beschaltung dargestellt ist): https://www.elektronik-kompendium.de/si ... 706261.htm

Code: Alles auswählen

# Bibliotheken laden
from machine import Pin, PWM
import time

# GPIO für Steuersignal
servo_pin = 28

# GPIO für Taster
btn_pin_up = 14
btn_pin_down = 15

# Wert für 0 Grad
valueMin = 0

# Wert für 180 Grad
valueMax = 180

# Position in Grad
value = 90

# Positionsänderung in Grad
step = 10

# Initialisierung der Taster
btn_up = Pin(btn_pin_up, Pin.IN, Pin.PULL_UP)
btn_up_last = time.ticks_ms()
btn_down = Pin(btn_pin_down, Pin.IN, Pin.PULL_UP)
btn_down_last = time.ticks_ms()

# Initialisierung PWM-Signal
servo = PWM(Pin(servo_pin))
servo.freq(50)

# Taster-Auswertung UP
def push_up(pin):
    global value, valueMin, valueMax, step, btn_up, btn_up_last
    if pin is btn_up:
        # Taster entprellen
        if time.ticks_diff(time.ticks_ms(), btn_up_last) > 200:
            value = value + step
            # Begrenzung des Wertebereichs
            if value < valueMin: value = valueMin
            if value > valueMax: value = valueMax
            servo_control(value)
            btn_up_last = time.ticks_ms()

# Taster-Auswertung DOWN
def push_down(pin):
    global value, valueMin, valueMax, btn_down, btn_down_last
    if pin is btn_down:
        # Taster entprellen
        if time.ticks_diff(time.ticks_ms(), btn_down_last) > 200:
            value = value - step
            # Begrenzung des Wertebereichs
            if value < valueMin: value = valueMin
            if value > valueMax: value = valueMax
            servo_control(value)
            btn_down_last = time.ticks_ms()

# Funktion: Servo steuern
def servo_control(value, minDuty=1638, maxDuty=8192):
    # Tastverhältnis berechnen
    newDuty = int(maxDuty - (value - valueMin) * (maxDuty - minDuty) / (valueMax - valueMin) )
    # Datenausgabe
    print('Grad:', value)
    print('Duty:', newDuty)
    print()
    # PWM-Signal ändern
    servo.duty_u16(newDuty)

# Hauptprogramm
print('STRG + C zum Benden')
print()

try:
    # Grundposition
    servo_control(value)
    # Interrupt für Taster UP
    btn_up.irq(trigger=Pin.IRQ_FALLING, handler=push_up)
    # Interrupt für Taster DOWN
    btn_down.irq(trigger=Pin.IRQ_FALLING, handler=push_down)
    # Wiederholung (damit das Programm weiterläuft)
    while True:
        time.sleep(1)
except (KeyboardInterrupt):
    pass
finally:
    servo.deinit()
    print('Beendet')
In diesem Beispiel wird die Bewegung mittels zweier Taster gesteuert.

Wie bereits geschrieben möchte ich das mittels eines Tasters lösen.

Und jetzt meine Frage: An welcher Stelle muss ich das script abändern? Und vor allem wie?

Ich würde mich freuen, falls mir da jemand Tipps geben könnte!





Vielen Dank im Voraus

Mario
Benutzeravatar
__blackjack__
User
Beiträge: 14045
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Das gleiche Thema gibt es auch schon hier: https://forum-raspberrypi.de/forum/thre ... ansteuern/

In der Python-Dokumentation gibt es ein Grundlagentutorial das man mal durchgearbeitet haben sollte.

Das Programm sieht nicht wirklich aus als wäre es von einem Python-Programmierer. Ich würde das einfach neu schreiben. Ordentlich. Mit den Namenskonventionen von Python, ohne ``global``, dafür dann recht wahrscheinlich mit einer Klasse, weil man sich Zustand über Rückrufe hinweg merken muss. Da Rückrufe hier nur sehr kurz etwas machen sollten, muss man das mit den 3 Sekunden Pause ausserhalb des Rückrufs lösen, also am besten die komplette Ablaufsteuereung. Hier bin ich etwas überfragt weil in MicroPython gibt es ja nix. In Python würde man da eine `queue.Queue` nehmen, oder ein `threading.Event` und dann im Hauptprogramm da blockierend drauf warten. Vielleicht ist ja `asyncio` auf dem Pico eine Lösung für solche Probleme.

(Die ``global`` finde ich ja besonders nett hier. Da steht einfach alles drin, inklusive Konstanten, was nicht bei 3 auf'm 🌳 war. 🙄)
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
sansevero
User
Beiträge: 4
Registriert: Dienstag 5. November 2024, 07:34

erstmal vielen Dank für die offensichtlich fundierte Antwort!

Und ich gebe Dir da vollkommen recht. Einfach neu schreiben, dabei schön clean und elegant. Kann ich nur leider nicht.... Das Projekt (schon die Bezeichnung Projekt wäre zu hoch gegriffen) wird für mich mit großer Wahrscheinlichkeit das Einzige dieser Art bleiben, auch wenn die Materie sicher viel Interessantes mitbringt. Und machen wir uns nix vor - die wenigsten werden dafür extrem tief einsteigen.

Ob der Ersteller des ursprünglichen Programms Python-Programmierer ist, wage ich nicht zu beantworten. Zumindest funktioniert es bei mir in der Version perfekt.

Ich dachte halt, man könne einfach den Part des "down"-Tasters weglassen und durch eine Art "wait 3 sec" und "goto Ausgangslage" ersetzen.

Aber wer weiß - vielleicht muss ich mich wirklich mal intensiver damit beschäftigen....
Benutzeravatar
__blackjack__
User
Beiträge: 14045
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@sansevero: Wenn das tatsächlich nicht mehr tun soll, könnte man sich vielleicht auch die ”Interrupts” sparen und einfach den Zustand des Buttons in einer Schleife abfragen. Also wirklich in Richtung BASIC auf einem 8-Bitter. Es gibt kein GOTO, aber das brauchts ja auch nicht wenn „Ausgangslage“ einfach wieder an den Anfang zurück bedeutet, denn das wäre dann eine ``while True:``-Schleife die halt den Code immer wieder wiederholt.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
sansevero
User
Beiträge: 4
Registriert: Dienstag 5. November 2024, 07:34

GEEEEEENAU! Das war/ist mein Lösungsansatz... über dieses while true bin ich tatsächlich auch schon gestolpert, und habe auch die Grundlagen teilweise verstanden, nur irgendwann heute morgen halb drei hab ich dann die konkrete Umsetzung aufgegeben. Dafür fehlen mir eben dann doch die Fähigkeiten.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1238
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Bei langsamen Vorgängen (>10 ms) bevorzuge ich asyncio + polling.
Bei "gleichzeitigen" Vorgängen hilft asyncio auch weiter, selbst dann, wenn der Mikrocontroller nur einen Kern hat.

Beispiel mit ein wenig OOP:

Code: Alles auswählen

import asyncio
from machine import PWM, Pin


class Servo:
    def __init__(
        self,
        name: str,
        pin: int,
        *,
        min_duty: int = 1638,
        max_duty: int = 8192,
        degrees_min: float | int = 0,
        degrees_max: float | int = 180,
        initial_angle=0,
        preferred_angle=90,
        print_debug=False,
    ):
        self.name = name
        self.min_duty = min_duty
        self.max_duty = max_duty
        self.degrees_min = degrees_min
        self.degrees_max = degrees_max
        self.initial_angle = initial_angle
        self.preferred_angle = preferred_angle
        self.pwm = PWM(pin, freq=50, duty_u16=self.new_duty(initial_angle))
        self.busy = False
        self.print_debug = print_debug

    def __call__(self, degrees: float | int):
        duty_u16 = self.new_duty(degrees)
        if self.print_debug:
            print(f"[{self.name}] Grad:", degrees)
            print(f"[{self.name}] Duty:", duty_u16)
            print()
        self.pwm.duty_u16(duty_u16)

    def new_duty(self, value: float | int) -> int:
        return int(
            self.max_duty
            - (value - self.degrees_min)
            * (self.max_duty - self.min_duty)
            / (self.degrees_max - self.degrees_min)
        )

    async def do_task(self, degrees: float | int | None = None):
        if self.busy:
            return

        degrees = degrees or self.preferred_angle
        self.busy = True
        self(degrees)
        await asyncio.sleep(3)
        self(self.initial_angle)
        # z.B. nach der Auslösung 200 ms warten, bis die
        # nächste Auslösung ermöglicht wird
        await asyncio.sleep_ms(200)
        self.busy = False


async def main():
    button_servo_mapping = {
        Pin(15, mode=Pin.IN, pull=Pin.PULL_DOWN): Servo(
            "Servo 1", 8, initial_angle=0, preferred_angle=90, print_debug=True
        ),
        Pin(16, mode=Pin.IN, pull=Pin.PULL_DOWN): Servo(
            "Servo 2", 3, initial_angle=90, preferred_angle=180, print_debug=True
        ),
    }

    while True:
        for button, servo in button_servo_mapping.items():
            if button() and not servo.busy:
                asyncio.create_task(servo.do_task())

        await asyncio.sleep_ms(10)


if __name__ == "__main__":
    asyncio.run(main())

Ob der Code richtig ist, um den Duty-Cycle richtig zu berechnen, habe ich nicht überprüft.
Viel Spaß beim Lernen von Funktionen, Klassen und am Ende asyncio.

PS: Ich habe andere Pins verwendet, da ich einen anderen Mikrocontroller habe. Getestet auf einem ESP32S3.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
sansevero
User
Beiträge: 4
Registriert: Dienstag 5. November 2024, 07:34

Super, vielen Dank! Das werde ich heute Abend mal genauer ansehen und nach Anpassung testen... Ich denke, durch die Unterschiede in den verschiedenen Ansätzen kann man auch Grundstrukturen erkennen...
Antworten