Forum: Mikrocontroller und Digitale Elektronik Ansteuerung von GPIO Pins eines Raspberrys aus 2 Threads


von ManuH (Gast)


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

von Stefan F. (Gast)


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.

von PittyJ (Gast)


Lesenswert?

Ein Objekt erstellen, was das GPIO abbildet. Dann beide Threads mit dem 
Objekt arbeiten lassen, und den Zugriff über ein Lock verrriegeln.
So wäre mein Standard-Vorgehen.

von ManuH (Gast)


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

von Mikro 7. (mikro77)


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

: Bearbeitet durch User
von ManuH (Gast)


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 ()

von Georg B. (diereinegier)


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.

: Bearbeitet durch User
von Georg B. (diereinegier)


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

: Bearbeitet durch User
von ManuH (Gast)


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

von Mikro 7. (mikro77)


Lesenswert?

ManuH schrieb:
> ...das Problem besteht aber leider weiterhin.

Wie sind die Pins physisch beschaltet?

Wie stellst du das Problem fest? Loggst du mit einem anderen Programm 
den Pin Level? Hast du einen Logikanalyzer angeschlossen?

Dir ist der Unterschied zwischen GPIO.setmode(GPIO.BOARD) und 
GPIO.setmode(GPIO.BCM) bewußt?

von ManuH (Gast)


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

von Mikro 7. (mikro77)


Lesenswert?

Hast du das Programm nur mit einem Thread laufen lassen.

Also nur für Pin-8. Ergebnis ok?
Dann nur für Pin-24. Ergebnis ok?

von ManuH (Gast)


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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.