Alle Programme können von hier heruntergeladen werden. |
Sensorkalibrierung |
Der Zweck vieler Sensoren ist es, eine physikalische Grösse zu messen und in eine Spannung umzuwandeln. Wenn diese Spannung in einer linearen Abhängigkeit zur physikalischen Grösse ist, wird von einem linearen Sensor gesprochen. Manchmal ist die Abhängigkeit nur in einem bestimmten Bereich linear oder vollständig nichtlinear. Die Nichtlinearität kann bei der Datenverarbeitung relativ leicht berücksichtigt werden. Problematischer sind instabile, fluktuierende Messdaten, die durch sporadisches Fehlverhalten oder unkontrollierte Einflüsse aus der Umwelt entstehen (Temperaturänderungen, Feuchtigkeit, etc.) Um die Beziehung zwischen Messgrösse und Ausgangsspannung zu bestimmen, wird oft ein Kalibrierungsverfahren mit bekannten Eingabewerten durchgeführt. Das Verhalten wird dann meist durch einen oder mehrere Sensorparameter beschrieben, die später in den Programmen verwendet werden. |
Analog-Digital-Wandler |
Die meisten Sensoren liefern eine Spannung, die sich kontinuierlich mit der gemessenen physikalischen Grösse ändert. Ein Analog-Digital-Wandler (ADC) wandelt diese Spannung in eine dazu proportionale Zahl um. Diese kann digital verarbeitet und gespeichert werden. Die wichtigsten Eigenschaften eines ADC:
Einige dieser Eigenschaften können durch die Verdrahtung, andere softwaremässig (mit Bits des Control-Registers) konfiguriert werden. Da die Anforderung an einen ADC stark anwendungsspezifisch ist, wurde auf dem Raspberry Pi kein ADC eingebaut. Dies ist im Ausbildungssektor etwas ärgerlich. Mit relativ wenig Aufwand kann aber am GPIO ein externer ADC angeschlossen werden. Allerdings sind diese fast nur noch in Miniaturgehäusen (SMD, Surface Mount Device) erhältlich, die schwieriger zu handhaben sind. Deshalb werden oft kleine Module (auch Breakout Boards genannt) verwendet, auf denen der ADC bereits eingelötet ist. In diesem Kapitel zeigen wir, wie man ADCs mit I2C- und SPI-Interface einsetzt. Für Demonstrationen und Tests nehmen wir meist anstelle eines Sensors eine variable Spannungsquelle. Sehr gut eignet sich dazu ein einfaches Potentiometer von etwa 10 kOhm, das mit einem Ende an VCC (5 V oder 3.3 V) und mit dem anderen Ende an GND hängt. Durch die Spannungsbegrenzung auf den Bereich 0 bis VCC ist das Potentiometer auch weniger gefährlich als ein externes Netzgerät. Bei der Verwendung der I2C-Schnittstelle (GPIO Pin #3, SDA und Pin #5, SCL) ist besondere Vorsicht am Platz. Beachten Sie Folgendes:
Die meisten I2C-Chips, die sich nicht auf einer Modul-Platine befinden, haben keine Pullups (sondern Open-Collector SDA/SCL-Ausgänge). Diese ICs können ohne Gefahr benutzt werden.
|
Experiment 1: Verwendung des PCF8591 |
Der PCF8591 ist ein 8-Bit A/D-D/A Wandler mit vier analogen Eingängen und einem analogen Ausgang. Er verfügt über eine I2C-Schnittstelle und kann direkt an das GPIO angeschlossen werden. Die drei Adresspins A0, A1 und A2 werden für die Festlegung der I2C-Adresse verwendet, so dass bis zu acht ICs am I2C-Bus betrieben werden können. Zur Versorgung kann der 3.3 V-Ausgang (Pin #1) oder 5 V-Ausgang (Pin #2) des Raspberry Pi verwendet werden. Der ADC lässt sich softwaremässig konfigurieren: Entweder 4 Kanäle single-ended oder 3 Kanäle differential . Für weitere Informationen konsultieren Sie das Datenblatt. Die 7-bit I2C -Adresse ist wie folgt zusammengesetzt:
Ziel: Schaltschema: Wenn mehrere Geräte über I2C angeschlossen sind, müssen ihre Adressen unterschiedlich sein. Um zu überprüfen, ob ein Gerät richtig angeschlossen ist, rufen Sie in einem Terminal i2cdetect -y 1 (oder i2cdetect -y 0 für Raspberry Pi version A) auf. Sie sollten den Device PCF8591 an der Adresse 48 (hex) sehen. Programm: [►] # ADC1a.py # Read values from analog input 0 import smbus import time from py7seg import Py7Seg # xxx bus = smbus.SMBus(1) # RPi revision 2 (0 for revision 1) i2c_address = 0x48 control_byte = 0x00 # to read channel 0 ps = Py7Seg() # xxx t = 0 while True: data = bus.read_byte_data(i2c_address, control_byte) print t, "s:", data ps.showText("%4d" %data) # xxx t += 0.1 time.sleep(0.1) Bemerkungen:
|
Experiment 2: Verwendung des ADS1015/ADS1115 |
Der ADS1015 ist ein 12-Bit ADC mit 4 Kanälen, der ADS1115 verwendet 16-Bit und ebenfalls 4 Kanäle (Datenblätter ADS1015 bzw. ADS1115). Die beiden haben die gleiche Pin-Belegung und werden nur als SMD hergestellt. Um das Löten von SMD zu vermeiden, können Modul-Platinen verwendet werden. Diese sind an verschiedenen Orten erhältlich, insbesondere bei Lieferanten von Zusatzmaterial für den Arduino. Auf Ebay finden Sie auch günstige ADS1115-Module, die in Asien produziert werden (falls Sie genügend Geduld haben, auf die Lieferung zu warten).
Der ADS1x15 kann für 4 Single-ended Eingänge oder für 2 Differential Eingänge konfiguriert werden. Im Single-ended Modus beträgt der maximale Spannungsbereich 0 bis VCC. Im Differential-Modus ist der Spannungsbereich -VCC bis VCC. Der tatsächlich verwendbare Bereich ist aber von der programmierbaren Verstärkung (PGA) abhängig. Für den ADS1015 ist der Ausgabebereich 0..2047 (single-ended) und -2048..2047 (differential). Für ADS1115 ist der Ausgabebereich 0..32767 (single-ended) und -32768..32767 (differential). Ziel: Schaltschema: Wenn Sie keine Pullup-Widerstände mit der VDD-Versorgung verwenden, können Sie den IC mit 3.3V oder 5V speisen, aber Sie müssen daran denken, dass Pullup-Widerstände in der Regel im Modul integriert sind.
Im Vergleich zu anderen I2C-Devices ist der ADS1x15 Chip etwas komplizierter zu programmieren, da er softwaremässig konfiguriert wird. Wir empfehlen daher die Verwendung der schön geschriebenen Python-Klassenbibliothek ADS1x15 von Tony DiCola von Adafruit Industries, die er grosszügig als public domain zur Verfügung stellt. Laden Sie die Modul-Datei ADS1x15.py (und einige Beispiele) von hier herunter und speichern Sie das Python-Modul im gleichen Ordner, indem sich Ihr Programm befindet. Konsultieren Sie die Python-Dokumentation und die Kommentare im Programmcode. Durch die Verwendung des Objekts adc, wird das Programm sehr einfach. Programm: [►] # ADC2a.py from ADS1x15 import ADS1015 from py7seg import Py7Seg # xxx import time adc = ADS1015() channel = 0 gain = 1 ps = Py7Seg() # xxx t = 0 while True: data = adc.read_adc(channel, gain) print t, "s:", data ps.showText("%4d" %data) # xxx t += 0.1 time.sleep(0.1) Bemerkungen: |
Experiment 3: Verwendung des MCP3021 |
Bezugsquelle von Modulen: KAmodRPI (KAMAMI), Didel RaspEasy (www.didel.com)
Die I2C-Adresse ist typenspezifisch und kann durch den Benutzer nicht verändert werden. Es gibt 8 Typen im Adressbereich 0b1001000 = 0x48 für den MCP3021A0 bis 0b1001111 = 0x4F für den MCP3021A7. (Weitere Informationen im Datenblatt.) Ziel: Schaltschema: Programm: [►] # ADC3a.py import smbus import time from py7seg import Py7Seg # xxx bus = smbus.SMBus(1) # RPi revision 2 (0 for revision 1) i2c_address = 0x4D # default address ps = Py7Seg() # xxx t = 0 while True: # Reads word (2 bytes) as int rd = bus.read_word_data(i2c_address, 0) # Exchanges high and low bytes data = ((rd & 0xFF) << 8) | ((rd & 0xFF00) >> 8) # Ignores two least significiant bits data = data >> 2 print t, "s:", data ps.showText("%4d" %data) # xxx t += 0.1 time.sleep(0.1) Bemerkungen: Sie können das Device mit dem Raspberry Pi 5V (Pin #2) versorgen und den Eingangsspannungsbereich auf 0..5V erweitern. Es wird ausdrücklich empfohlen, das Programm zu strukturieren, indem Sie beispielsweise eine Klasse erstellen, die den Code für den ADC kapselt. Programm: [►] # ADC3b.py import smbus import time from py7seg import Py7Seg # xxx class MCP3021: VINmax = 3.3 bus = smbus.SMBus(1) def __init__(self, address = 0x4D): self.address = address def setVINmax(self, v): self.VINmax = v def readRaw(self): # Reads word (16 bits) as int rd = self.bus.read_word_data(self.address, 0) # Exchanges high and low bytes data = ((rd & 0xFF) << 8) | ((rd & 0xFF00) >> 8) # Ignores two least significiant bits return data >> 2 def getValue(self): return float(self.VINmax) * self.readRaw() / 1023.0 adc = MCP3021() ps = Py7Seg() # xxx t = 0 while True: v = adc.getValue() w = "%4.3f" %v print t, "s:", w ps.showText(w[0] + w[2] + w[3] + w[4], dp = [0, 0, 1]) # xxx t += 0.1 time.sleep(0.1) Falls Sie das Programm direkt auf dem GUI Desktop des Raspberry Pi laufen lassen (mit einem direkt angeschlossenen Bildschirm oder über VNC), so können Sie die Messdaten in Echtzeit in einer Grafik visualisieren. Sehr einfach geht dies unter Verwendung des Moduls PyGPanel, das ein Grafikfenster mit einem benutzerdefinierten Koordinatensystem zur Verfügung stellt, in das inkrementell gezeichnet werden kann. Die Datei gpanel.py kann von hier heruntergeladen werden und sollte in das gleiche Verzeichnis, in dem sich das Programm befindet, kopiert werden. (Die Distribution enthält auch eine vollständige Dokumentation und viele Beispielprogramme.) Wenn Sie ein 10 kOhm-Potentiometer direkt auf einen SMD Adapter löten, auf dem sich ein MCP3021 ADC befindet, so erhalten Sie ein "Digitales Potentiometer", das sich vielseitig als Testwerkzeug einsetzen lässt. Programm:[►] # ADC3c.py # Read ADC and show graphics import smbus import time from gpanel import * dt = 0.1 def readData(): adc_address = 0x4D rd = bus.read_word_data(adc_address, 0) data = ((rd & 0xFF) << 8) | ((rd & 0xFF00) >> 8) data = data >> 2 return data def init(): clear() setPenColor("gray") drawGrid(0, 10, 0, 1.0) setPenSize(2) setPenColor("blue") bus = smbus.SMBus(1) makeGPanel(-1, 11, -0.1, 1.1) t = -1 while True: v = readData() / 1023.0 if t == -1 or t > 10: init() t = 0 pos(0, v) else: draw(t, v) t += dt time.sleep(dt)
|
Experiment 4: Verwendung des MCP3008 (SPI) |
Das Protokoll SPI (Serial Peripheral Interface) verwendet einen Bus mit 5 Leitungen: GND, SCLK (Clock), MOSI (Master Out Slave In), MISO (Master In Slave Out) und CS (Chip Select). Die Daten werden bitweise übertragen. Um die Daten vom Master (in der Regel der Raspberry Pi) zum Client (hier der MCP3008 Chip), zu senden, aktiviert der Master CS (durch Ziehen auf low), setzt das Bit auf MOSI (high oder low) und sendet einen Clockpuls durch Setzen von SCLK auf high und kurz darauf auf low. Um die Daten vom Client abzufragen, sendet der Master (bei aktiviertem CS) einen Clockpuls und liest den Zustand von MISO. Ziel: Schaltschema: Programm:[►] # ADC4a.py import RPi.GPIO as GPIO import time from py7seg import Py7Seg # xxx SPI_CLK = 23 SPI_MISO = 21 SPI_MOSI = 19 SPI_CS = 24 def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(SPI_MOSI, GPIO.OUT) GPIO.setup(SPI_MISO, GPIO.IN) GPIO.setup(SPI_CLK, GPIO.OUT) GPIO.setup(SPI_CS, GPIO.OUT, initial = GPIO.HIGH) def readADC(channel): LOW = GPIO.LOW HIGH = GPIO.HIGH if channel > 7 or channel < 0: # illegal channel return -1 GPIO.output(SPI_CLK, LOW) # Start with clock low GPIO.output(SPI_CS, LOW) # Enable chip # Send command control = channel # control register control |= 0b00011000 # Start bit at b4, # Single-ended bit at b3 # Channel number (b2, b1, b0) for i in range(5): # Send bit pattern starting from bit b4 if control & 0x10: # Check bit b4 GPIO.output(SPI_MOSI, HIGH) else: GPIO.output(SPI_MOSI, LOW) control <<= 1 # Shift left GPIO.output(SPI_CLK, HIGH) # Clock pulse GPIO.output(SPI_CLK, LOW) # Get reply data = 0 for i in range(11): # Read 11 bits and insert at right GPIO.output(SPI_CLK, HIGH) # Clock pulse GPIO.output(SPI_CLK, LOW) data <<= 1 # Shift left, LSB = 0 if GPIO.input(SPI_MISO): # If high, LSB = 1, data |= 0x1 GPIO.output(SPI_CS, HIGH) # Disable chip return data setup() channel = 0 ps = Py7Seg() # xxx t = 0 while True: data = readADC(channel) print t, "s:", data ps.showText("%4d" %data) # xxx t += 0.1 time.sleep(0.1) Bemerkungen: Um einen 10-Bit Wert vom Device abzufragen, wird ein Kontrollbyte gesendet, das wie folgt zusammengesetzt ist:
Es werden 5 Bits als Befehl gesendet, um die Konversion zu initialisieren. Das Gerät sendet 11 Bits zurück und die unteren 10 Bits sind die digitalisierten Daten (siehe Datenblatt). Das Betriebssystem des Raspberry Pi enthält einen SPI-Treiber, den man in der Konfiguration (Setup mit sudo raspi-config) aktivieren muss. Da dieser nicht immer erwartungsgemäss funktionierte, wird hier eine direkte Programmierung des SPI-Protokolls verwendet. |