Motorkarte Library

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Dominikin9
User
Beiträge: 13
Registriert: Mittwoch 4. Januar 2017, 17:39

Samstag 11. Februar 2017, 23:30

Guten Abend!

Ich bin gerade dabei eine Motorsteuerung zu programmieren und wollte gleichzeitig auf die selbe library zugreifen,
aber dann bekomme ich den Fehler "wiringPiSetup*: You must only call this one per program run. This is a fatal error.
Please fix your code."

Was kann ich da dagegen machen?
(im Anhang ist die library)

Mit freundlichen Grüßen
Dominic

Code: Alles auswählen

import wiringpi

# Motor speeds for this library are specified as numbers
# between -MAX_SPEED and MAX_SPEED, inclusive.
_max_speed = 480  # 19.2 MHz / 2 / 480 = 20 kHz
MAX_SPEED = _max_speed

io_initialized = False
def io_init():
  global io_initialized
  if io_initialized:
    return

  wiringpi.wiringPiSetupGpio()
  wiringpi.pinMode(12, wiringpi.GPIO.PWM_OUTPUT)
  wiringpi.pinMode(13, wiringpi.GPIO.PWM_OUTPUT)

  wiringpi.pwmSetMode(wiringpi.GPIO.PWM_MODE_MS)
  wiringpi.pwmSetRange(MAX_SPEED)
  wiringpi.pwmSetClock(2)

  wiringpi.pinMode(22, wiringpi.GPIO.OUTPUT)
  wiringpi.pinMode(23, wiringpi.GPIO.OUTPUT)
  wiringpi.pinMode(24, wiringpi.GPIO.OUTPUT)
  wiringpi.pinMode(25, wiringpi.GPIO.OUTPUT)

  io_initialized = True

class Motor(object):
    MAX_SPEED = _max_speed

    def __init__(self, pwm_pin, dir_pin, en_pin):
        self.pwm_pin = pwm_pin
        self.dir_pin = dir_pin
        self.en_pin = en_pin

    def enable(self):
        io_init()
        wiringpi.digitalWrite(self.en_pin, 1)

    def disable(self):
        io_init()
        wiringpi.digitalWrite(self.en_pin, 0)

    def setSpeed(self, speed):
        if speed < 0:
            speed = -speed
            dir_value = 1
        else:
            dir_value = 0

        if speed > MAX_SPEED:
            speed = MAX_SPEED

        io_init()
        wiringpi.digitalWrite(self.dir_pin, dir_value)
        wiringpi.pwmWrite(self.pwm_pin, speed)

class Motors(object):
    MAX_SPEED = _max_speed

    def __init__(self):
        self.motor1 = Motor(12, 24, 22)
        self.motor2 = Motor(13, 25, 23)

    def enable(self):
        self.motor1.enable()
        self.motor2.enable()

    def disable(self):
        self.motor1.disable()
        self.motor2.disable()

    def setSpeeds(self, m1_speed, m2_speed):
        self.motor1.setSpeed(m1_speed)
        self.motor2.setSpeed(m2_speed)

motors = Motors()
BlackJack

Sonntag 12. Februar 2017, 01:27

@Dominikin9: Ich sehe da jetzt in dem Code kein Problem. Wie sieht denn der komplette Traceback aus?

Allerdings ist das mit dem ``global`` auch nicht gut. Ruf die Funktion einfach nur *einmal* auf. In der Funktion sind auch ein paar Sachen die ich dort nicht hinein schreiben würde. Die Pins der Motoren tauchen da beispielsweise auf. Ich würde es in der Verantwortung des Motorobjekts sehen wenn es erstellt wird, *seine* Pins entsprechend zu einzurichten.
Dominikin9
User
Beiträge: 13
Registriert: Mittwoch 4. Januar 2017, 17:39

Sonntag 12. Februar 2017, 11:23

@BlackJack wie könnte man das mit dem global lösen?

Und den anderen Fehler hab ich gefunden. Der Fehler war, dass ich aus zwei Threats gleichzeitig das motors.enable() aufrufen wollte,
aber man das anscheinend nicht darf, ich weiß zwar nicht warum aber jetzt funktioniert es.

Vielleicht findest du eine Erklärung dazu und kannst es mir vielleicht erklären, wenn nicht auch ok :D.

Danke schon man für die Hilfe.

so funktioniert es nicht und gibt die Fehler Meldung wie zuerst (wiringPiSetup*: You must only call this one per program run. This is a fatal error.
Please fix your code.) und das ist der ganze Traceback

Code: Alles auswählen

from __future__ import print_function
import time
from dual_mc33926_rpi import motors, MAX_SPEED
from threading import Thread

def m_1_control():
	
	motors.enable()
	motors.setSpeeds(0, 0)
	
	while True:	
		print("Motor 1 forward")
		motors.motor1.setSpeed(480)
		time.sleep(3)
		
		print("Motor 1 reverse")
		motors.motor1.setSpeed(-480)
		time.sleep(1)
		motors.motor1.setSpeed(0)
        

def m_2_control():
	
	motors.enable()
	motors.setSpeeds(0, 0)
	
	while True:	
		print("Motor 2 forward")
		motors.motor2.setSpeed(480)
		time.sleep(2)
		
		print("Motor 2 reverse")
		motors.motor2.setSpeed(-480)
		time.sleep(5)
		motors.motor2.setSpeed(0)
        

m_1 = Thread(target = m_1_control)
m_2 = Thread(target = m_2_control)

m_1.start()
m_2.start()

while True:
	time.sleep(0.001)  
Und so funktioniert es

Code: Alles auswählen

from __future__ import print_function
import time
from dual_mc33926_rpi import motors, MAX_SPEED
from threading import Thread

def m_1_control():
	
	while True:	
		print("Motor 1 forward")
		motors.motor1.setSpeed(480)
		time.sleep(3)
		
		print("Motor 1 reverse")
		motors.motor1.setSpeed(-480)
		time.sleep(1)
		motors.motor1.setSpeed(0)
        

def m_2_control():

	while True:	
		print("Motor 2 forward")
		motors.motor2.setSpeed(480)
		time.sleep(2)
		
		print("Motor 2 reverse")
		motors.motor2.setSpeed(-480)
		time.sleep(5)
		motors.motor2.setSpeed(0)
        

m_1 = Thread(target = m_1_control)
m_2 = Thread(target = m_2_control)
motors.enable()
motors.setSpeeds(0, 0)
m_1.start()
m_2.start()

while True:
	time.sleep(0.001)  
Mit freundlichen Grüßen
Dominic
BlackJack

Sonntag 12. Februar 2017, 12:30

@Dominikin9: Das mit dem ``global`` kann man wie gesagt dadurch lösen das man die Funktion nur einmal aufruft.

Threads laufen parallel. Wenn Du zweimal *gleichzeitig* `moter.enable()` aufrufst, wird auch von beiden Aufrufen `io_init()` gleichzeitig aufgerufen, und wenn das beide tun, dann ist für beide am Anfang der `io_init()` das globale `io_initialized()`-Flag noch `False` also führen auch beide den Code in der `io_init()` aus. Was zu zwei Aufrufen von `wiringPiSetupGpio()` führt. Erst wenn die `io_init()` komplett ausgeführt wurde ist das globale `io_initialized` auf `True` gesetzt.

Bevor Du jetzt auf die Idee kommst die letzte Zeile von `io_init()` weiter nach vorne zu ziehen: Es könnte dann so aussehen das es funktioniert, aber das wäre zufällig und ist dann auch nicht immer garantiert, denn das grundsätzliche Problem bleibt dabei bestehen, nur das Zeitfenster in dem der Fehler auftreten kann ist dadurch kleiner geworden.

Das Verändern von Daten aus verschiedenen Threads sollte man vermeiden oder man muss es entsprechend Absichern um zu garantieren das kritischer Code immer nur von einem Thread gleichzeitig ausgeführt werden kann. Das `threading`-Modul bietet Werkzeuge dafür. Nebenläufige Programmierung (also zum Beispiel mit Threads) ist ein schwieriges Thema und blöderweise kann man damit fehlerhafte Programme schreiben, die scheinbar funktionieren, aber dann irgendwann doch einmal über den Fehler stolpern.

Nochmal: Problem ist ganz einfach dadurch zu lösen, dass man `io_init()` überhaupt nur einmal aufruft.

Warum steckst Du erst die beiden Motoren in eine Klasse um dann doch von aussen auf beide einzeln zuzugreifen? Und Du hast da im Grunde auch wieder eine „race condition“ wenn beide gleichzeitig am Anfang die Geschwindigkeit beider Motoren auf 0 setzen und dann gleich darauf jeweils bei ihren eigenen Motor eine andere Geschwindigkeit setzen. Wenn einer der Threads an der Stelle geringfügig schneller ist und seinen Motor loslaufen lässt, dann kann es passieren das der andere Thread den danach wieder stoppt.

Das ist auch einer der Unterschiede zwischen den beiden Varianten die Du zeigst.

Es macht nicht so viel Sinn zwei Threads zu starten und im dritten dann *nichts* zu machen, in einer Schleife die Rechenzeit verbraucht. Da könnte man auch einen Thread starten und die Arbeit des anderen im Hauptthread erledigen.

Daneben das man ``global`` nicht verwenden sollte, gehören generell keine Variablen und kein Code auf Modulebene. Der Code dort sollte nur Konstanten, Funktionen, und Klassen definieren. Alles andere gehört in Funktionen. Man sollte ein Modul immer ohne weitere Effekte importieren können.
Dominikin9
User
Beiträge: 13
Registriert: Mittwoch 4. Januar 2017, 17:39

Montag 13. Februar 2017, 20:04

@BlackJack Danke für die ausführliche Erklärung, sie hat mir einiges weitergeholfen und ich versuche es mal umzusetzen.

Mit freundlichen Grüßen
Dominic Amon
Antworten