Python exemplarisch
deutsch     english

ADC, SPI PROTOKOLL

 

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:

  • Eingangsspannungsbereich: In der Regel 0..10V (Single-Ended: Spannung zwischen Eingang und GND) oder +-5V (Differential : Spannungsdifferenz zwischen zwei Eingängen)
  • Auflösung: Anzahl Bits, die der ADC am Ausgang liefert, meistens 8, 10, 12 oder 16 Bits
  • Geschwindigkeit (Abtastrate): Maximale Anzahl Messungen (Konversionen) pro Sekunde, in der Regel 1000 /s bis zu 106 /s (und höher)
  • Ausgang-Interface/Protokoll: Parallel (3.3V, 5V), I2C, SPI
  • Anzahl Eingänge: typisch 1, 4, 8

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:

hand1

Die Spannung an GPIO-Eingängen muss im Bereich 0..3.3V liegen. Wenn Sie eines externes I2C-Modul mit 5 V versorgen, so müssen Sie sich versichern, dass auf dem externen Modul die Leitungen SDA und SCL nicht über Pullup-Widerstände mit der 5 V-Versorgung verbunden sind, was sehr oft der Fall ist. Entweder müssen Sie die Pullup-Widerstände entfernen oder Sie speisen das Modul mit 3.3 V.

 

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.

Eine andere sichere Lösung besteht darin, einen bidirektionalen Spannungswandler 3.3V-5V (wie z.Bsp. BSS138 oder AN97055) zu verwenden, der auch als kleine Platine erhältlich ist.  

 

 

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:

A6
A5
A4
A3
A2
A1
A0
1
0
0
1
x
x
x
A2, A1, A0 entsprechen den drei 3 Input-Pins. Beispiel: Alle 3 Pins auf GND->Adresse b1001000 = 0x48

Ziel:
Digitalisieren Sie eine Spannung 0..5V und zeigen Sie das Ergebnis in der Konsole und auf einer Digitalanzeige (falls vorhanden) an.

Schaltschema:
Mit VCC = 5V ergeben sich für Eingangsspannungen im Bereich 0..5 V Messwerte im Bereich 0...255. Die Schaltung ist sicher, da SDA und SCL ohne Pullup-Widerstände verbunden sind.

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)
Progammcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Bemerkungen:
Wenn Sie keinen Display (z.B. ELV) angeschlossen haben, kommentieren Sie die mit xxx markierten Zeilen aus. Weitere Informationen über das Control-Byte finden Sie im Datenblatt.

Der PCF8591 wird nicht mehr hergestellt, aber Sie finden mehrere Lieferanten im Internet (Ebay, aliexpress, und andere). Wenn Sie ein Modul verwenden, das mit 5V betrieben wird (z. B. CP12001), so vergewissern Sie sich, dass die Pullup- Widerstände an den SDA/SCL-Leitungen entfernt sind (eine Zwickzange genügt).  

 

 

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

Die ADS1x15 Serie hat vier durch den Benutzer frei wählbare Adressen, die davon abhängig sind, wie man den ADDR Pin verdrahtet (siehe Tabelle). Wie Sie sehen, ist die Adresse b1001000 = 0x48, wenn ADDR mit GND verbunden ist.  

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:
Digitalisieren Sie eine Spannung im Bereich 0..3.3V oder 0..5V und schreiben Sie die Ergebnisse in der Konsole und (falls vorhanden) auf einem angeschlossenen Display aus.

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.

Vorsicht: Wenn Sie die Platine mit 5V speisen, so müssen Sie die beide Pullup-Widerstände R1 und R2, (rot markiert), entfernen. Wenn Sie das Modul mit 3.3V versorgen (oder einen Pegelwandler verwenden), ist keine Anpassung notwendig.

Wenn Sie den ALRT-Pin verwenden, so muss bei 5V-Speisung auch der Pullup-Widerstand R4 entfernt werden.

 


Schema handelsüblicher Module (ohne Garantie!):

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)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Bemerkungen:
Falls Sie keine digitale Anzeige angeschlossen haben, kommentieren Sie die mit xxx-markierten Zeilen aus.

 

 

Experiment 3: Verwendung des MCP3021

 

Bezugsquelle von Modulen: KAmodRPI (KAMAMI), Didel RaspEasy (www.didel.com)

Der MCP3021 ist ein 10-bit ADC mit einer I2C-Schnittstelle Die Verwendung ist extrem einfach. Leider steht nur eine SMT-Version (SOT23) zur Verfügung. Da er nur 5 Pins hat, kann er aber einfach auf einen SMT-to-DIL Adapter gelötet und mit dieser Platine verwendet werden.

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:
Digitalisieren Sie eine Spannung 0..3.3V und zeigen Sie das Ergebnis in der Konsole und auf einer Digitalanzeige (falls vorhanden) an.

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)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Bemerkungen:
Falls Sie keine digitale Anzeige angeschlossen haben, kommentieren Sie die mit xxx-markierten Zeilen aus.

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)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

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)
Highlight program code (Ctrl+C kopieren, Ctrl+V einfügen)

 

Mein "Digitales Potentiometer"

 

ADC15

adc14

 

 

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:
Digitalisieren Sie eine Spannung 0..3.3V und zeigen Sie die Ergebnisse in der Konsole und mit einer Digitalanzeige (falls vorhanden) an.

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)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Bemerkungen:
Falls Sie keine digitale Anzeige angeschlossen haben, kommentieren Sie die mit xxx-markierten Zeilen aus.

Um einen 10-Bit Wert vom Device abzufragen, wird ein Kontrollbyte gesendet, das wie folgt zusammengesetzt ist:

Higher nibble: binary 0001 (1 genannt Startbit)
Lower nibble: binary S/D, D2, D1, D0 (gemäss der Tabelle).

 

Beispiele:

  • Beim Single-ended Input an CH0 ist das Controlbyte 0x18

  • Beim Differential Input an CH2/CH3 ist das Controlbyte 0x12

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.