Ansteuerung von GPIO Pins eines Raspberrys aus 2 Threads

Gast #4970268
Lesenswert?

Momentan versuche ich zwei Schrittmotoren parallel über eine 
Pythonsteuerung auf einem RaspberryPi zu steuern. Die beiden Threads 
greifen jeweils auf eine Funktion Motorsteuerung1 und 2 zu und schalten 
unterschiedliche GPIO Pins (18 und 24), allerdings wenn ich nachmesse 
wird immer nur einer der GPIO Pins geschalten und am anderen tut sich 
gar nichts.

Die vermutung ist das der Raspi nicht zulässt dass ich von zwei Threads 
gleichzeitig auf GPIO's zugreife, allerdings weiß ich nicht wie ich das 
umgehen kann.

Mit vielen Grüßen,

Manuel
Gast #4970362
Lesenswert?

Was ist, wenn die Ports nur Byteweise (oder noch mehr Bits gleichzeitig) 
angesprochen werden und dieses Byte irgendwo als Variable vorgehalten 
wird?

Thread 1:
  1) Port x in Variable y einlesen.
  2) Ein Bit in y ändern.
  3) Variable y auf Port x Port ausgeben.
  4) Ein Bit in y ändern.
  5) Variable y auf Port x Port ausgeben.
  6) Ein Bit in y ändern.
  7) Variable y auf Port x Port ausgeben.

Thread  2:
  1) Port x in Variable z einlesen.
  2) Ein Bit in z ändern.
  3) Variable z auf Port x Port ausgeben.
  4) Ein Bit in z ändern.
  5) Variable z auf Port x Port ausgeben.
  6) Ein Bit in z ändern.
  7) Variable z auf Port x Port ausgeben.

Diesen Code müsste man so ändern, dass vor jedem Bit-Ändern der Port 
erneut gelesen wird. Vielleicht liegt es prinzipiell daran.
Gast #4971142
Lesenswert?

Grundsätzlich würde ich auch zur Objekt-Methode mit dem Lock greifen, 
allerdings ist dies für meine Anwendung nicht sehr praktisch da die 
Geschwindigkeit der Schrittmotoren über ein delay-Zeit zwischen zwei 
Schritten geregelt wird. Muss jetzt der eine Thread warten wird dadurch 
die Geschwindigkeit meiner Motoren verändert.

Was mir bei weiteren versuchen aufgefallen ist, ist eine ungewöhnliche 
abhängigkeit der GPIO's die ich mir nicht erklären kann. Beispielsweiße 
wenn ich in Thread 1 versuche den GPIO 24 zu steuern und in Thread 2 
GPIO 8 (BOARD Layout) Funktioniert Pin 8 und 24 nicht, ändere ich 
allerdings den Pin 24 in der Software auf GPIO 18 funktioniert auf 
einmal gar kein Pin mehr.

Außerdem bekomme ich bei manchen Pins wie z.B. GPIO 4 eine meldung Pins 
is invalid on Raspberry Pi obwohl es sich bei diesem um einen normalen 
Pin handeln sollte. Vielleicht haben Sie eine Idee woher dieses Phänomen 
kommen könnte.

Mit vielen Grüßen,

Manuel
#4971183
Lesenswert?

Die Ports werden über separate Register zum Setzen (GPIO Pin Output Set 
Registers) und Rücksetzen (GPIO Pin Output Clear Registers) angesprochen 
[BCM2835 ARM Peripherals,§6]: "Separating the set and clear functions 
removes the need for read-modify-write operations." Einer Ansteuerung 
aus unterschiedlichen Threads/Prozessen sollte technisch nichts entgegen 
stehen.

Versuche das Problem zu isolieren. / Zeige ein Minimalprogramm, das 
anderen ermöglicht, dein Problem zu reproduzieren. / Poste direkt im 
raspberry.org Forum wo du wahrscheinlich mehr Feedback bekommst.

Edit: BCM2835
Gast #4971247
Lesenswert?

Vielen Dank für den Hinweiß Mikro 77, hier eine auf die Grundfunktion 
beschränkte variante meines Programmes :
1
#!/usr/bin/python
2
# -*- coding: latin-1 -*-
3
import os, sys
4
import RPi.GPIO as GPIO
5
import time
6
from threading import Thread
7
GPIO.setmode(GPIO.BOARD)
8

9
def main () :
10
    
11
    try :
12
        def MotorSteuerung1(MotorSchritteWeg, iDelay, Drehrichtung):
13
            print("Thread 1 opened")
14
            print(GPIO.getmode())
15
         
16
            GPIO.setup(15, GPIO.OUT)
17
            GPIO.setup(8, GPIO.OUT)
18
        
19
        
20
            while True:
21
        
22
                if Drehrichtung == 1:
23
        
24
        
25
                    GPIO.output(8, GPIO.HIGH)
26
                    time.sleep((iDelay / 1000))
27
                    GPIO.output(8, GPIO.LOW)
28
                    time.sleep((iDelay / 1000))
29
                    print("1")
30
        
31
                elif (Drehrichtung == 2):
32
                   
33
        
34
                    GPIO.output(18, GPIO.HIGH)
35
                    time.sleep((iDelay / 1000))
36
                    GPIO.output(18, GPIO.LOW)
37
                    print("2")
38
        
39
        
40
        def MotorSteuerung2(MotorSchritteWeg, iDelay, Drehrichtung):
41
        
42
            print("Thread 2 opened")
43
            print(GPIO.getmode())
44
        
45
            GPIO.setup(23, GPIO.OUT)
46
            GPIO.setup(24, GPIO.OUT)
47
          
48
            while True:
49
        
50
                if Drehrichtung == 1:
51
        
52
        
53
                    GPIO.output(24, GPIO.HIGH)
54
                    time.sleep((iDelay / 1000))
55
                    GPIO.output(24, GPIO.LOW)
56
                    time.sleep((iDelay / 1000))
57
        
58
                elif (Drehrichtung == 2):
59
                   
60
        
61
                    GPIO.output(24, GPIO.HIGH)
62
                    time.sleep((iDelay / 1000))
63
                    GPIO.output(24, GPIO.LOW)
64

65
        iMotorSchritteWeg = 1.8
66
        iDelay = 5
67
        iDrehrichtung = 1
68
        
69
        t1 = Thread(target=MotorSteuerung1, args=(iMotorSchritteWeg, iDelay, iDrehrichtung))
70
        t2 = Thread(target=MotorSteuerung2, args=(iMotorSchritteWeg, iDelay , (iDrehrichtung)))
71
        t1.start()
72
        t2.start()
73

74
        t1.join()
75
        t2.join()
76
        
77
    except KeyboardInterrupt :
78
        print ("Execution Interrupted")
79
        
80
    finally:
81
        GPIO.cleanup()
82
        
83
if __name__ == '__main__' :
84
    main ()
#4971279
Lesenswert?

Vielleicht liegt der Hase ganz woanders im Pfeffer.

Python führt Threads nicht parallel aus, sondern immer nur genau einen. 
Das Scheduling der Threads ist darauf optimiert, mehrere I/O-abhängige 
Aufgaben nebenläufig abzuarbeiten.

Das Schlag- und Suchwort zum Thema ist "GIL" (Global Interpreter Lock) 
und die Diskussion darum, ob und wie man dieses Lock loswerden 
kann/soll/muß wird mit viel emotionalem Einsatz geführt.

Wenn es also Python sein soll, dann kann man entweder selbst die beiden 
Aufgaben schedulen, oder man nimmt zwei Python-Prozesse.

Was ist denn daran gut, fragt man sich? Wenn man eine C-Bbliothek 
schreibt, die von Python aufgerufen werden soll, dann kann nur ein 
Thread in der C-Bibliothek sein und man muß sich um Race-Conditions etc. 
keine Gedanken machen. Das heißt aber auch, daß ein Call in eine 
C-Bibliothek durch Thread A verhindert, daß Thread B gescheduled werden 
kann. Das trifft hier auf alle Calls der GPIO-Bibliothek zu - und 
hoffentlich nicht auf time.sleep(), da solltest Du mal schauen, ob man 
explizit den Current Thread schlafen legen kann, damit der andere zum 
Zuge kommt.
#4971301
Lesenswert?

ManuH schrieb:
>         def MotorSteuerung1(MotorSchritteWeg, iDelay, Drehrichtung):
>             print("Thread 1 opened")
>             print(GPIO.getmode())
>
>             GPIO.setup(15, GPIO.OUT)
>             GPIO.setup(8, GPIO.OUT)
Dieses Setup sollte besser in main() passieren.
>             while True:
>                 if Drehrichtung == 1:
>                     GPIO.output(8, GPIO.HIGH)
>                     time.sleep((iDelay / 1000))
>                     GPIO.output(8, GPIO.LOW)
>                     time.sleep((iDelay / 1000))
>                     print("1")
Dieses print dauert lang und hat evtl. Einfluß auf das 
Thread-Scheduling.
>                 elif (Drehrichtung == 2):
>                     GPIO.output(18, GPIO.HIGH)
>                     time.sleep((iDelay / 1000))
>                     GPIO.output(18, GPIO.LOW)
Hier fehlt das Delay.

Die beiden Funktionen sind ja mehr oder weniger identisch. Die 
Pin-Nummern könnten Parameter werden, dann bräuchte man nur eine.

time.sleep() scheint ok zu sein:
https://docs.python.org/3.5/library/time.html#time.sleep
Gast #4971442
Lesenswert?

Vielen dank für die Verbesserungsvorschläge, ich habe das Programm 
entsprechend angepasst das Problem besteht aber leider weiterhin.
1
#!/usr/bin/python
2
# -*- coding: latin-1 -*-
3
import os, sys
4
import RPi.GPIO as GPIO
5
import time
6
from threading import Thread
7
GPIO.setmode(GPIO.BOARD)
8

9
def main () :
10
    GPIO.setup(8, GPIO.OUT)
11
    GPIO.setup(24, GPIO.OUT)
12
    GPIO.setup(23, GPIO.OUT)
13
    GPIO.setup(10, GPIO.OUT)
14

15
    try :
16
        def MotorSteuerung(MotorSchritteWeg, iDelay, Drehrichtung, Pin):
17
            print("Thread 1 opened")
18
            print(GPIO.getmode())
19

20

21
            while True:
22

23
                if Drehrichtung == 1:
24

25

26
                    GPIO.output(Pin, GPIO.HIGH)
27
                    time.sleep((iDelay / 1000))
28
                    GPIO.output(Pin, GPIO.LOW)
29
                    time.sleep((iDelay / 1000))
30

31

32
                elif (Drehrichtung == 2):
33
                    # GPIO.output(16, GPIO.LOW)
34

35
                    GPIO.output(Pin, GPIO.HIGH)
36
                    time.sleep((iDelay / 1000))
37
                    GPIO.output(Pin, GPIO.LOW)
38
                    time.sleep((iDelay / 1000))
39

40

41
        iMotorSchritteWeg = 1.8
42
        iDelay = 5
43
        iDrehrichtung = 1
44
        Pin_1 = 8
45
        Pin_2 = 24
46

47
        t1 = Thread(target=MotorSteuerung, args=(iMotorSchritteWeg, iDelay, iDrehrichtung, Pin_1))
48
        t2 = Thread(target=MotorSteuerung, args=(iMotorSchritteWeg, iDelay , (iDrehrichtung), Pin_2))
49
        t1.start()
50
        t2.start()
51

52
        t1.join()
53
        t2.join()
54

55
    except KeyboardInterrupt :
56
        print ("Execution Interrupted")
57

58
    finally:
59
        GPIO.cleanup()
60

61
if __name__ == '__main__' :
62
    main ()

Ich bin mir nicht ganz sicher wie die Sache mit dem GPIO scheduling 
gemeint war, ist der Zugriff auf die GPIO's begrenzt (es kann also nur 
ein Thread gleichzeitig darauf zugreifen) oder eben nicht ? Falls ja 
könnte es ja sein dass der zweite Thread seine angegebene Delayzeit 
wartet, in der Zeit allerdings nicht auf die GPIO's zugreifen kann da 
sie bereits verwendet werden und sich dann nach ablauf der Delayzeit 
verabschiedet ohne wirklich etwas gemacht zu haben ( das würde zumindest 
das Phänomen erklären warum immer nur ein Pin gesetzt wird).

Mit vielen Grüßen

Manuel
Gast #4971528
Lesenswert?

Ich messe die Pins mit einem Mulimeter von Hand aus (darum auch der 
zweite Delay damit die LOW Zeiten auch messbar sind). Wenn ich Nachmesse 
lösche ich den Teiler von 1000 dann liegt das High und das Low Signal 
jeweils 5 sekunden an und können gemessen werden.
1
GPIO.output(Pin, GPIO.HIGH)
2
time.sleep((iDelay ))
3
GPIO.output(Pin, GPIO.LOW)
4
time.sleep((iDelay ))
An einem der Pins funktioniert es mit dieser Methode auch.

Auf das BOARD Layout habe ich auch geachtet und habe mich dabei an 
diesem Pinout orientiert.
https://de.pinout.xyz/#

Mit vielen Grüßen

Manuel
Gast #4971625
Lesenswert?

Problem gelöst, mir war der Unterschied zwischen BOARD und BCM Layout 
zwar bewusst allerdings habe Ich ihre Bedeutung vertauscht.

Vielen Dank für den Hinweiß zu diesem Thema und auch die allgemeinen 
Tipps zum Aufbau.

Mit vielen Grüßen

Manuel

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren