Alle Programme können von hier heruntergeladen werden. Vorbemerkung: Dieses Kapitel kann auch als unabhängige Einführung in die Sensorik behandelt werden. Es enthält daher gewisse Wiederholungen. |
Einführung |
Um die grundlegenden Experimente mit dem Raspberry Pi zu vereinfachen, empfehlen wir, ein billiges, aber gut durchdachtes Modul von Didel, genannt RaspEasy, zu verwenden. Sie stecken einfach das Modul in den 40-Pin GPIO-Steckplatz ein und schon sind Sie bereit für grundlegende Experimente , ohne sich um die Verbindungskabel kümmern zu müssen. Bezugsquelle:
Funktionsschema:
Steckerbelegung:
|
Experiment 1: GPIO-Port als digitalen Input/Output verwenden |
Ziel: Programm: [►] # RaspEasy1.py # Button press/release to switch on/off LED import RPi.GPIO as GPIO P_BUTTON = 12 # Button A #P_BUTTON = 13 # Button B P_LED = 7 # LED A #P_LED = 11 # LED B def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(P_BUTTON, GPIO.IN) GPIO.setup(P_LED, GPIO.OUT) print "starting..." setup() while True: if GPIO.input(P_BUTTON) == GPIO.HIGH: GPIO.output(P_LED, GPIO.HIGH) else: GPIO.output(P_LED, GPIO.LOW) Bemerkungen: Im obigen Beispiel wird der Zustand des Input-Pins in einer while-True-Schleife abgefragt und das Programm wird erst beendet, wenn der Python-Prozess abgebrochen wird. Dies ist gemäss der GPIO-Dokumentation eigentlich kein sauberes Programmende, da die Funktion GPIO.cleanup() nie aufgerufen wird. (Es können einige GPIO-Zustände bis zur Ausführung des nächsten Python-Programms "überleben".) Da mit unseren Entwicklungsumgebungen (TigerJython und Geany) bei jedem Programmstart alle laufenden Python-Prozesse "gekillt" werden, gibt es mit while-True-Schleifen keine Schwierigkeiten. Wenn Sie es aber besser machen möchten, so werfen Sie einen Blick auf Experiment 4. |
|
Experiment 2: Zählen von Tastenbetätigungen, Schalterprellen |
Ziel: Programm: [►] # RaspEasy2.py # Button counter import RPi.GPIO as GPIO P_BUTTON = 12 # Button A def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(P_BUTTON, GPIO.IN) print "starting..." setup() count = 0 while True: if GPIO.input(P_BUTTON) == GPIO.HIGH: count += 1 print count Bemerkungen: Am besten betrachten Sie das System als einen Automat, der sich in bestimmten Zuständen befinden kann. Es gibt offenbar zwei Zustände: "Losgelassen" (ButtonUp) und "Gedrückt" (ButtonDown). Nur wenn eine Zustandsänderung von ButtonUp zu ButtonDown eintritt, darf der Zähler um 1 erhöht werden. Für die Umsetzung dieser Idee definiert man am besten eine boolesche Variable isButtonPressed, die je nach Zustand wahr oder falsch ist. Der nächste Versuch sieht dann wie folgt aus: Programm:[►] # RaspEasy2a.py # Button counter, 2nd attempt import RPi.GPIO as GPIO P_BUTTON = 12 # Button A def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(P_BUTTON, GPIO.IN) print "starting..." setup() count = 0 isButtonPressed = False while True: if GPIO.input(P_BUTTON) == GPIO.HIGH and not isButtonPressed: isButtonPressed = True count += 1 print count elif GPIO.input(P_BUTTON) == GPIO.LOW and isButtonPressed: isButtonPressed = False Bemerkungen:
Das Gleiche kann beim Loslassen passieren: Der Kontakt öffnet und schliesst mehrere Male kurz hintereinander. Dieser Effekt, der als Schalterprellen bezeichnet wird, bewirkt bei Anwendungen, bei denen die Anzahl der Schliess- oder Öffnungsoperationen eine Rolle spielt, unvorhersehbare Resultate. Wie kann man die Auswirkungen des Schalterprellens vermeiden: Wir können Prellen auf zwei Arten bekämpfen: hardwaremässig oder softwaremässig. Im ersten Fall fügen wir zusätzliche Elektronik-Elemente hinzu (ein RS-Flip-Flop oder ein RC-Glied), oder wir beseitigen das Problem softwaremässig und ändern das Programm gemäss folgender Idee:
Programm:[►] # RaspEasy2b.py # Button counter, 3rd attempt import RPi.GPIO as GPIO import time P_BUTTON = 12 # Button A def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(P_BUTTON, GPIO.IN) print "starting..." setup() count = 0 isButtonPressed = False while True: if GPIO.input(P_BUTTON) == GPIO.HIGH and not isButtonPressed: isButtonPressed = True count += 1 print count time.sleep(0.1) elif GPIO.input(P_BUTTON) == GPIO.LOW and isButtonPressed: isButtonPressed = False time.sleep(0.1) Bemerkungen: Programm:[►] # RaspEasy2c.py # Button counter, use Oled or PyTell display import RPi.GPIO as GPIO import time from OLED1306 import OLED1306 #from pytell import PyTell P_BUTTON = 12 # Button A def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(P_BUTTON, GPIO.IN) print "starting..." oled = OLED1306() oled.setFontSize(50) #pyTell = PyTell() setup() count = 0 oled.setText(str(count)) # pyTell.showText("%4d" %count) isButtonPressed = False while True: if GPIO.input(P_BUTTON) == GPIO.HIGH and not isButtonPressed: isButtonPressed = True count += 1 oled.setText(str(count)) # pyTell.showText("%4d" %count) time.sleep(0.1) elif GPIO.input(P_BUTTON) == GPIO.LOW and isButtonPressed: isButtonPressed = False time.sleep(0.1) Bemerkungen: |
|
Experiment 3: LED Umschalter |
Ziel: Programm:[►] # RaspEasy3.py # LED switcher import RPi.GPIO as GPIO import time P_BUTTON = 12 # Button A P_LED = 7 # LED A def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(P_BUTTON, GPIO.IN) GPIO.setup(P_LED, GPIO.OUT) GPIO.output(P_LED, GPIO.LOW) # Turn off LED print "starting..." setup() isLedOn = False isButtonPressed = False while True: if GPIO.input(P_BUTTON) == GPIO.HIGH and not isButtonPressed: isButtonPressed = True if isLedOn: isLedOn = False GPIO.output(P_LED, GPIO.LOW) else: isLedOn = True GPIO.output(P_LED, GPIO.HIGH) time.sleep(0.1) elif GPIO.input(P_BUTTON) == GPIO.LOW and isButtonPressed: isButtonPressed = False time.sleep(0.1) Bemerkungen: |
|
Experiment 4: Start/Stop/Exit-Manager |
Ziel:: Programm:[►] # RaspEasy4.py # Start/Stop, Terminate import RPi.GPIO as GPIO import time RUN_BUTTON = 12 # Button A EXIT_BUTTON = 13 # Button B def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(RUN_BUTTON, GPIO.IN) GPIO.setup(EXIT_BUTTON, GPIO.IN) setup() count = 0 isCounting = False isRunButtonPressed = False print "Stopped" isExiting = False while not isExiting: if GPIO.input(RUN_BUTTON) == GPIO.HIGH and not isRunButtonPressed: isRunButtonRunPressed = True if not isCounting: isCounting = True print "Counting..." else: isCounting = False print "Stopped" time.sleep(0.1) elif GPIO.input(RUN_BUTTON) == GPIO.LOW and isRunButtonPressed: isRunButtonPressed = False time.sleep(0.1) if isCounting: count += 1 print count time.sleep(0.01) if GPIO.input(EXIT_BUTTON) == GPIO.HIGH: isExiting = True GPIO.cleanup() print "Programm terminated" Bemerkungen: Man könnte die while-Schleife auch mit einem break verlassen und damit auf das Flag isExiting verzichten: while True: ... |
|
Experiment 5: Morsecode mit einem Buzzer abspielen |
Ziel:: Programm:[►] # RaspiEx5.py import RPi.GPIO as GPIO import time #from OLED1306 import OLED1306 P_BUZZER = 40 # adapt to your wiring dt = 0.1 # adapt to your Morse speed morse = { 'a':'.-' , 'b':'-...' , 'c':'-.-.' , 'd':'-..' , 'e':'.' , 'f':'..-.' , 'g':'--.' , 'h':'....' , 'i':'..' , 'j':'.---' , 'k':'-.-' , 'l':'.-..' , 'm':'--' , 'n':'-.' , 'o':'---' , 'p':'.--.' , 'q':'--.-' , 'r':'.-.' , 's':'...' , 't':'-' , 'u':'..-' , 'v':'...-' , 'w':'.--' , 'x':'-..-' , 'y':'-.--' , 'z':'--..' , '1':'.----', '2':'..---', '3':'...--', '4':'....-', '5':'.....', '6':'-....', '7':'--...', '8':'---..', '9':'----.', '0':'-----', '-':'-....-', '?':'..--..', ',':'--..--', ':':'---...', '=':'-...-'} def s(n): # wait time.sleep(n * dt) def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(P_BUZZER, GPIO.OUT) GPIO.output(P_BUZZER, GPIO.LOW) # Turn off buzzer def dot(): GPIO.output(P_BUZZER, GPIO.HIGH) s(1) GPIO.output(P_BUZZER, GPIO.LOW) s(1) def dash(): GPIO.output(P_BUZZER, GPIO.HIGH) s(3) GPIO.output(P_BUZZER, GPIO.LOW) s(1) def transmit(text): # sent = "" # initialize string buffer for c in text: # sent += c # oled.setText(sent) if c == " ": s(4) else: c = c.lower() if c in morse: k = morse[c] for x in k: if x == '.': dot() else: dash() s(2) print "starting..." #oled = OLED1306() #oled.setFontSize(16) setup() transmit("cq de hb9abh") GPIO.cleanup() print "all done" Bemerkungen: Wenn Sie einen OLED-Display haben, entfernen # aus den auskommentierte Zeilen. Dann wird das Zeichen, das abgespielt wird, auch gleichzeitig angezeigt. Das ist sehr praktisch, vor allem, wenn Sie nicht gelernt haben, den Morsecode akustisch zu entschlüsseln. |
|
Experiment 6: Button-Events verwenden |
Die Programmierung mit Events (Ereignissen) kann den Programmcode erheblich vereinfachen, da dadurch mehrere Programmaktivitäten voneinander entkoppelt werden. Das Grundprinzip ist Folgendes: Sie definieren eine sogenannte Callbackfunktion (oder kurz Callback), die nie explizit von Ihrem Programm, sondern vom System aufgerufen wird, wenn ein bestimmtes Ereignis auftritt. Der Name der Callbackfunktion wird dem System durch eine sogenannte Registrierung mitgeteilt. (Event-Programmierung kann aber auch zu unvorhersehbaren Verhalten führen, da der Callback meist in einem separaten Thread aufgerufen wird, der mit anderen Programm-Threads in Konflikt stehen kann, wenn gemeinsame Ressourcen verwendet werden.) Ziel: Das GPIO Modul unterstützt GPIO-Events: Die Funktion add_event_detect() registriert den Callback. Gleichzeitig wird dem System mitgeteilt, um welchen GPIO-Port es sich handelt und ob der Event beim Übergang von LOW auf HIGH, umgekehrt oder in beiden Fällen ausgelöst werden soll. Programm:[►] # RaspEasy6.py # Using button events import RPi.GPIO as GPIO import time COUNT_BUTTON = 12 # Button A EXIT_BUTTON = 13 # Button B def onCountButtonEvent(channel): global count, btnReady if btnReady and GPIO.input(COUNT_BUTTON) == GPIO.HIGH: btnReady = False count += 1 else: btnReady = True time.sleep(0.1) def onExitButtonEvent(channel): global isExiting isExiting = True def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(COUNT_BUTTON, GPIO.IN) GPIO.setup(EXIT_BUTTON, GPIO.IN) GPIO.add_event_detect(COUNT_BUTTON, GPIO.BOTH, onCountButtonEvent) GPIO.add_event_detect(EXIT_BUTTON, GPIO.BOTH, onExitButtonEvent) setup() btnReady = True count = 0 oldCount = 0 print "starting..." isExiting = False while not isExiting: if count != oldCount: oldCount = count print count time.sleep(0.001) GPIO.cleanup() print "Programm terminated" Bemerkungen: Im Widerspruch zur GPIO-Dokumentation beseitigt der zusätzliche Parameter bouncetime in add_event_detect () das Schalterprellen nicht vollständig. Deshalb deaktivieren wir weitere PressEvents mit dem btnReady Flag so lange, bis ein Release-Event eintritt. Zusätzlich deaktivieren wir die Events für 0,1 s durch Aufruf von time.sleep(0.1) in der Callbackfunktion, obwohl man generell Verzögerungen in Callbacks vermeiden sollte. |
|
Experiment 7: Lesen von Analogdaten |
Programm:[►] # RaspEasy7.py # Read ADC0 or ADC1 and write value to stdout import smbus import time print "starting..." bus = smbus.SMBus(1) adc_address = 0x48 # ADC0 # adc_address = 0x4D # ADC 1 while True: # Reads word (16 bits) as int rd = bus.read_word_data(adc_address, 0) # Exchanges high and low bytes data = ((rd & 0xFF) << 8) | ((rd & 0xFF00) >> 8) # Ignores two least significiant bits data = data >> 2 print "data:", data time.sleep(1) Bemerkungen: Diese "Bitschieberei" ist zwar etwas unangenehm und echte Low-Level-Programmierung, aber in der Sensorik nicht zu vermeiden. Nehmen Sie es sportlich. Damit Sie die Sache ein für alle Mal erledigt haben, schreiben Sie eine Funktion readdata(), die Sie später immer wieder verwenden können, ohne das "Innere" zu beachten. Dies ist ja gerade der Sinn der prozeduralen Programmierung. Programm:[►] # RaspEasy7a.py # Read ADC with a function call import smbus import time def readData(port = 0): if port == 0: adc_address = 0x48 elif port == 1: adc_address = 0x4D rd = bus.read_word_data(adc_address, 0) data = ((rd & 0xFF) << 8) | ((rd & 0xFF00) >> 8) data = data >> 2 return data print "starting..." bus = smbus.SMBus(1) while True: v = readData() print "data:", v time.sleep(1) Bemerkungen: Programm:[►] # RaspEasy7b.py # Read ADC and show on Oled or 7-segment display import smbus import time from OLED1306 import OLED1306 #from pytell import PyTell def readData(port = 0): if port == 0: adc_address = 0x48 elif port == 1: adc_address = 0x4D rd = bus.read_word_data(adc_address, 0) data = ((rd & 0xFF) << 8) | ((rd & 0xFF00) >> 8) data = data >> 2 return data print "starting..." oled = OLED1306() oled.setFontSize(50) #pyTell = PyTell() bus = smbus.SMBus(1) while True: v = readData() oled.setText(str(v)) # pyTell.showText("%4d" %v) time.sleep(1) Bemerkungen: Da Sie nun wissen, wie man einen analogen Wert einliest, können Sie das RaspEasy Modul verwenden, um Sensorwerte irgendeines analogen Sensors zu verarbeiten. |
|
Experiment 8: Verwendung eines Temperatursensors |
Die Temperaturmessung ist in vielen Situationen sehr wichtig. Daher gibt es viele verschiedene Temperatursensoren. In dieser Übung verwenden wir den LM35, einen 3-Pin Gerät und einer Ausgangspannung, die (nahezu) linear zur Temperatur ist. Der Sensor benötigt keine Kalibrierung. Mehr Informationen finden Sie im Datenblatt. Ziel: Schaltschema:
Das Programm zeigt die aktuelle Temperatur in Grad Celsius an. Betrachten Sie die folgende Umwandlung: Da der Sensor eine Spannung von a = 0.01 V pro Grad Celsius abgibt, gilt für die Ausgangsspannung u = 0.01 * T . Der 10-bit ADC, der mit 3.3V versorgt ist, gibt bei einer Eingangsspannung von 3.3V den digitalen Wert 1023 ab. Liegt also die Spannung u am ADC, so ist sein digitaler Ausgabewert v = u / 3.3 * 1023. Die Beziehung zwischen der Temperatur und dem digitalen Wert lautet daher v = 0.01 / 3.3 * 1023 * T = 3.1 * T. Programm:[►] # RaspEasy7c.py # LM35 temperature sensor import smbus import time #from OLED1306 import OLED1306 def readData(port = 0): if port == 0: adc_address = 0x48 elif port == 1: adc_address = 0x4D rd = bus.read_word_data(adc_address, 0) data = ((rd & 0xFF) << 8) | ((rd & 0xFF00) >> 8) data = data >> 2 return data print "starting..." #oled = OLED1306() #oled.setFontSize(50) bus = smbus.SMBus(1) while True: v = readData() T = v / 3.1 print "T = %4.1f centigrades" %T # oled.setText(str(int(T + 0.5))) # rounded to int time.sleep(1) |
|
Experiment 9: Verwendung eines Lichtsensors (LDR) |
Ziel: Schaltschema:
Sie müssen nur ein Paar Zeilen im Programm RaspEasy7c.py ändern. Programm:[►] # RaspEasy7d.py # LDR sensor import smbus import time #from OLED1306 import OLED1306 def readData(port = 0): if port == 0: adc_address = 0x48 elif port == 1: adc_address = 0x4D rd = bus.read_word_data(adc_address, 0) data = ((rd & 0xFF) << 8) | ((rd & 0xFF00) >> 8) data = data >> 2 return data print "starting..." #oled = OLED1306() #oled.setFontSize(50) bus = smbus.SMBus(1) while True: v = readData() print "v =", v # oled.setText(str(v)) # time.sleep(0.1) |