Kommunnizieren mit der seriellen Schnitstelle /USB

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

kann man mit der seriellen Schnittstelle/USB auch ohne queue kommunizieren, wenn auf die GUI verzichtet wird ?

ohne queue :

Code: Alles auswählen

#/usr/bin/env python
# -*- coding: utf-8

import time
from threading import Thread, Event
from pylibftdi import BitBangDevice

class ToggleLed(object):
    def __init__(self):
        self.run_event = None
        
    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.release()
        
    def stop(self):
        self.run_event.set()
        
    def toggle(self, port):
        self.port = port
        
    def off(self):
        self.port = self.port_off

    def start(self):
        self.run_event = Event()
        self.thread = Thread(target=self.worker_thread)
        self.thread.daemon = True
        self.thread.start()
        
    def worker_thread(self):
        with BitBangDevice() as bb:
            while not self.run_event.is_set():
                bb.port = self.port 
                        
    def release(self):
        if self.run_event:
            self.stop()
            
def main():
    with ToggleLed() as toggle_led:
        toggle_led.start()
        while True:
            toggle_led.toggle(0)
            time.sleep(0.5)
            toggle_led.toggle(1)
            time.sleep(0.5)
                    
if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print("KeyboardInterrupt")
mit queue:

Code: Alles auswählen

#/usr/bin/env python
# -*- coding: utf-8

import time
from threading import Thread, Event
from pylibftdi import BitBangDevice
import queue

class ToggleLed(object):
    def __init__(self, queue):
        self.queue = queue
        self.run_event = None
        
    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.release()
        
    def stop(self):
        self.run_event.set()

    def start(self):
        self.run_event = Event()
        self.thread = Thread(target=self.worker_thread)
        self.thread.daemon = True
        self.thread.start()
        
    def worker_thread(self):
        with BitBangDevice() as bb:
            while not self.run_event.is_set():
                if not self.queue.empty():
                    while True:
                        try:
                            bb.port = self.queue.get_nowait()
                        except queue.Empty:
                            break
                        
    def release(self):
        if self.run_event:
            self.stop()
            
def main(queue):
    queue = queue.Queue()
    with ToggleLed(queue) as toggle_led:
        toggle_led.start()
        while True:
            time.sleep(0.5)
            queue.put(0)
            time.sleep(0.5)
            queue.put(1)
                    
if __name__ == '__main__':
    try:
        main(queue)
    except KeyboardInterrupt:
        print("KeyboardInterrupt")
Gruß Frank
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Klar kann man. Was ich nicht verstehe ist der extra Thread im Queue-losen Beispiel. Der ballert 100% der Rechenzeit raus, und ist doch unnötig.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Danke deets ,

ohne GUI :

Code: Alles auswählen

#/usr/bin/env python
# -*- coding: utf-8

import time
from pylibftdi import BitBangDevice


def main():
    with BitBangDevice() as bb:
        while True:
            bb.port = 0
            time.sleep(0.5)
            bb.port = 1
            time.sleep(0.5)
                    
if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print("KeyboardInterrupt")
Alle Versionen funktionieren bei mir ohne merkliche Probleme bei der Leistung und so poste ich alle meine Versuche, da ohne Code die Fragen immer sehr oberflächlich bleiben. Welche Version richtig oder falsch ist ...?

Gruß Frank
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Extra Aufwand, ohne erkennbaren Nutzen, ist tendenziell falsch. Mehr Code heißt mehr Fehlerquelle, mehr CPU heisst mehr Anfälligkeit wenn die anderweitig angefordert wird.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Eine einfache Pulweitenmodulation erfordert erfordert doch schon diese Version ?

Code: Alles auswählen

#/usr/bin/env ^
# -*- coding: utf-8

import time
from threading import Thread, Event
from pylibftdi import BitBangDevice

class PwmLed(object):
    HERZ50 = 20
    def __init__(self):
        self.run_event = None
        self.width = 0
        
    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.release()
        
    def stop(self):
        self.run_event.set()
        
    def set_pwm(self, width):
        if width >= 0 and width <= self.HERZ50:
            self.width = width
                
    def start(self):
        self.run_event = Event()
        self.thread = Thread(target = self.worker_thread)
        self.thread.daemon = True
        self.thread.start()
        
    def worker_thread(self):
        with BitBangDevice() as bb:
            while not self.run_event.is_set():
                for port, t in ((1, 0.001 * self.width), 
                                (0, 0.001 * (self.HERZ50 - self.width))):
                    bb.port = port
                    time.sleep(t)
                        
    def release(self):
        if self.run_event:
            self.stop()
                        
with PwmLed() as pwm_led:
    pwm_led.start()
    while True:
        for step in range(20):
            time.sleep(0.2)
            pwm_led.set_pwm(step)
        for step in range(20, 0, -1):
            time.sleep(0.2)
            pwm_led.set_pwm(step)
Gruß Frank
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ja, allerdings wird die in Bezug auf Wiederholgenauigkeit katastrophal sein. Für sowas ist weder Python noch USB und dein OS gedacht. Darum würde ich das nie so machen, sondern dedizierte Hardware dafür verwenden.
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kaytec: `run_event` ist irritierend weil das genau das Gegenteil ist: ein Ereignis das gesetzt wird, wenn gestoppt werden soll.

Dass das nicht in der `__init__()` erstellt wird, macht den Code unnötig kompliziert weil man eine `stop()`-Methode hat die auf die hart auf die Nase fällt wenn vorher nicht `start()` aufgerufen wurde, und deshalb eine zusätzliche `release()`-Methode da ist.

Das `thread`-Attribut wird ausserhalb der `__init__()` eingeführt und der Code kommt nicht damit klar wenn `start()` mehr als einmal aufgerufen wird.

Durch das setzen von `daemon` auf `True` wird der Thread hart mittendrin beendet. Damit greift das ``with`` für das `BitBangDevice` nicht mehr. Man sollte also beim stoppen explizit auf das Ende des Threads warten.

Ungetestet:

Code: Alles auswählen

#/usr/bin/env python3
import time
from threading import Event, Thread

from pylibftdi import BitBangDevice


class PwmLed:
    HERZ_50 = 20

    def __init__(self):
        self._width = 0
        self.run_event = Event()
        self.thread = None

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.stop()

    @property
    def width(self):
        return self._width

    @width.setter
    def width(self, value):
        if not 0 <= value <= self.HERZ_50:
            raise ValueError(f"value must be in 0..{self.HERZ_50}")
        self._width = value

    def stop(self):
        self.run_event.set()
        if self.thread:
            self.thread.join()
            self.thread = None

    def start(self):
        if not self.thread:
            self.run_event.clear()
            self.thread = Thread(target=self._worker_thread, daemon=True)
            self.thread.start()

    def _worker_thread(self):
        with BitBangDevice() as bit_bang_device:
            while not self.run_event.is_set():
                for state, delay in [
                    (1, 0.001 * self.width),
                    (0, 0.001 * (self.HERZ_50 - self.width)),
                ]:
                    bit_bang_device.port = state
                    time.sleep(delay)


def main():
    with PwmLed() as pwm_led:
        pwm_led.start()
        while True:
            for step in range(20):
                time.sleep(0.2)
                pwm_led.width = step
            for step in range(20, 0, -1):
                time.sleep(0.2)
                pwm_led.width = step


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo deets,

ist nur eine Spielerei, doch man hat ja acht Ein/Ausgänge und ein Kran aus Fischertechnik könnte damit angesteuert werden. Schöner Spaß für meine Kinder ! Mein selbstgebasteltes 2-Kanalsoundkartenoszilloskop zeigt mal die Frequenz von 49Hz an. Wird genauso ungenau sein. Möchte der Elektroniker Genauigkeit, dann greift auch er auf die Mechanik zurück.

Gruß Frank
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Bei 8 Ausgängen werden die Ungenauigkeiten sich allerdings aufaddieren. Denn dann musst du ja die potentiell 8 verschiedenen Zeitpunkte mit ggf kaum unterschiedlichen Zykluszeiten abwarten.

Wenn’s dir reicht, dann kannst du das ja so machen. Nur nicht wundern, wenn es eben nicht reicht.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo deets,

wenn man die Geschwindigkeit nicht über ein PWM-Signal steuert, sonder über die Untersetzung "drosselt", dann haben die alten F-Technikmotoren auch genug Kraft. So würde man pro Motor zwei Leitungen für eine H-Brücke brauchen. Auf eine Encoderscheibe könnte auch verzichtet werden, da es ja auf Sicht bespielt wird. Falls es nicht klappt, dann ist es ja auch ok und man nimmt den guten alten Batteriestab. Es gibt tolle Hardware, doch mir langen solche Bastellösungen und diese passen auch besser zu meinen finanziellen Möglichkeiten.

Gruß Frank
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Na „Hardware“ ist ein arduino für 5€. Oder ein PCA-irgendwas, der via I2C über dein FTDI angesteuert wird.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo deets,

mehr als 5-10 Euro gebe ich nicht für Spielereien im Monat nicht aus und das gebrauchte Lernpaket hat schon 7 Euro gekostet. Da war der FDTI- Adapter drin und ich versuche die VB-Beispiele mit Python nachzubasteln. Ist für mich eine entspannende Beschäftigung ! Mit der beiliegenden Zusatzplatine lassen sich auch Microcontroller beschreiben. Damit bin ich bestimmt noch lange beschäftigt und falls ich es nicht hinbekomme, zerstöre etc. geht die Welt auch nicht unter.

Hallo BlackJack,
Danke für das fertige Script. Das mit dem“daemon“ habe ich noch nicht verstanden. Versuche ich mal nachzulesen.

Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

deamon = True:
Das Hauptprogramme beendet die threads und sie laufen nach dem Beenden nicht im Hintergrund weiter.

thread.join():
Ordnet die threads und wartet bis alle beendet sind.

Warum werden sie geordnet ?

Class PwmLed:
Alle Klassen sind Objekte und die explizite Angabe ist nicht mehr notwendig.

@property und @width.setter macht die wichtigen Merkmale öffentlich und „erleichtern“ den Zugriff bzw. Veränderung.

Würde es mit Gui so aussehen ?

Code: Alles auswählen

#/usr/bin/env python3
import time
import tkinter as tk
import queue
from threading import Event, Thread
from pylibftdi import BitBangDevice

class PwmLed:
    HERZ_50 = 20

    def __init__(self, queue):
        self.queue = queue
        self._width = 0
        self.run_event = Event()
        self.thread = None

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.stop()

    def stop(self):
        self.run_event.set()
        if self.thread:
            self.thread.join()
            self.thread = None

    def start(self):
        if not self.thread:
            self.run_event.clear()
            self.thread = Thread(target=self._worker_thread, daemon=True)
            self.thread.start()

    def _worker_thread(self):
        with BitBangDevice() as bit_bang_device:
            while not self.run_event.is_set():
                if not self.queue.empty():
                    try:
                        self._width = self.queue.get_nowait()
                        if not 0 <= self._width <= self.HERZ_50:
                            raise ValueError(
                                f"value must be in 0..{self.HERZ_50}")
                    except queue.Empty:
                        break
                else:
                    for state, delay in [
                        (1, 0.001 * self._width),
                        (0, 0.001 * (self.HERZ_50 - self._width)),
                    ]:
                        bit_bang_device.port = state
                        time.sleep(delay)
            
            
class PwmLedUI(tk.LabelFrame):
    def __init__(self, 
                 parent,
                 queue,
                 pwm_led):
        tk.LabelFrame.__init__(self, 
                               parent, 
                               text = "PWM-LED", 
                               relief = "solid")
        self.parent = parent
        self.queue = queue
        self.pwm_led = pwm_led
        self.pwm_led.start()
        self.rate_scale = tk.Scale(self, from_ = 0,
                                   to = 20,
                                   resolution = 1,
                                   width = 10,
                                   length = 400,
                                   showvalue = 0,
                                   orient = "horizontal",
                                   relief = "flat",
                                   command = self.set_pwm_width)
        self.rate_scale.pack()
                                   
    def set_pwm_width(self, rate):
        pulse_width = self.rate_scale.get()
        self.config(text = "PWM-LED: {0}".format(pulse_width))
        self.queue.put(pulse_width)
        
    def release(self):
        self.pwm_led.stop()
        self.parent.destroy()
        
def main(queue):
    queue = queue.Queue()
    root = tk.Tk()
    root.title("PWM-LED")
    root.resizable(0, 0)

    with PwmLed(queue) as pwm_led:
        pwm_led_ui = PwmLedUI(root, queue, pwm_led)
        pwm_led_ui.pack(expand=tk.YES, padx = 10, pady = 10)
        root.protocol("WM_DELETE_WINDOW",pwm_led_ui.release)
        root.mainloop()
    
if __name__ == '__main__':
    main(queue)
Antworten