Über den SPI-Bus einen MCP3008-Analog-Digitalwandler auslesen

Einen Analog-Digital-Wandler, oder auf englisch Analog Digital Converter, kurz ADC, hatten wir schon einmal in dem Projekt Über den I2C-Bus des Raspberry Pi einen Analog-Digital-Wandler (PCF8591) ansteuern. Ein AD-Wandler wandelt eine am Eingang anliegende Spannung, die zwischen 0 Volt und der Versorgungsspannung (beim Raspberry Pi meist 3.3V) liegt, als digitale Zahl zurück. Je nachdem, wieviel Bit Auflösung der AD-Wandler hat, liegen diese zurückgegebenen Zahlen zwischen 0 und 255 (8 Bit), 0 und 1023 (10 Bit), 0 und 4095 (12 Bit) oder 0 und 65535 (16 Bit). Dementsprechend ist die Feinheit der Messung unterschiedlich. Während z. B. bei 3.3 Volt 8 Bit Wandler nur mit 12,94 mV Genauigkeit messen, sind dies bei 16 Bit 0,05 mV Genauigkeit, das ist 256 mal genauer.

Das Ergebnis und damit die digitale Zahl des AD-Wandlers muss irgendwie übermittelt werden. Dies geschieht als Byte(s) über einen Bus. Beim PCF8591 war es der I2C-Bus mit 2 Datenleitungen. Der MCP3008, der über 10 Bit Auflösung und gleich 8 Eingänge verfügt, benötigt den SPI-Bus.

Der SPI-Bus

Der SPI-Bus benötigt drei Datenleitungen und eine Select-Leitung, über die kommuniziert wird. Diese sind: SPI Server (Raspi) SPI Client 1 SPI Client 2 SCLK (Serial Clock) ---> SCLK SCLK MOSI (Master Out / Slave In) ---> MOSI MOSI MISO (Master In / Slave Out) <--- MISO MISO CS0 (Chip Select 0) ---> CS CS1 (Chip Select 1) ---------------------> CS Der Raspberry Pi verfügt über zwei SPI-Busse (0 und 1) mit jeweils zwei CS-Leitungen CS0 und CS1. Diese sind defaultmäßig: Bus 0 Bus 1 SCLK 11 21 MOSI 10 20 MISO 9 19 CS0 8 16 CS1 7 Der Raspberry Pi verfügt also nur über 2 Client-Select-Leitungen. Damit lassen sich zwei SPI-Geräte anschließen. Ein drittes könnte man über den 2. Bus anschließen, bräuchte dann aber nochmals 4 Kabel. Da erkläre ich doch spontan den I2C-Bus zum Sieger, der mit nur 2 Kabeln über 100 Geräte ansteuern kann. Aber manche Geräte unterstützen halt nur den SPI-Bus und dann muss man den verwenden. Den Unterschied zwischen den Bussen sollte man beim Bauteile-Kaufe im Hinterkopf behalten, denn oft gibt es SPI und I2C-Versionen. Manche Bauteile unterstützen sogar beides.

Der Vorteil des I2C-Busses ist, dass man nur 2 Datenleitungen benötigt. Allerdings muss man die Adresse des Bauteils (Device-ID) kennen, um das Bauteil ansprechen zu können; selbst wenn es nur ein einziges I2C-Bauteil gibt.

Den Nachteil hat man bei SPI nicht. Hier hat jedes Bauteil sein eigenes physisch angeschlossenes Chip-Select-Kabel. Allerdings braucht SPI damit 4 Datenleitungen.

SPI ist allerdings auch schneller als I2C. Normalerweise betreiben Mikrocontroller den SPI-Bus mit Geschwindigkeiten von bis zu 10Mhz. Bei I2C sind 100 bis 400 kHz üblich und das Maximum sind etwa 5 MHz. Deshalb werden größere Displays mit zum Beispiel 320 x 200 Pixeln oder SD-Kartenleser meistens per SPI angebunden.

Den SPI-Bus auf dem Raspi installieren

Zuerst rufen wir mit sudo raspi-config das Raspberry-Konfigurationsprogramm in der Kommandozeile auf. Unter Interfacing Options finden wir den Eintrag SPI, den wir auswählen und enablen.

Danach sollte mindestens ein Device und ls /dev/spi* gelistet sein. pi@raspberrypi:~ $ ls /dev/spi* /dev/spidev0.0 /dev/spidev0.1 Wenn nicht, ist es Zeit für einen Reboot (sudo reboot).

Danach brauchen wir noch die Libraries für Python: sudo apt-get install -y python-dev python3-dev sudo apt-get install -y python-spidev python3-spidev

Der MCP3008


Kommen wir zum AD-Wandler, dem MCP3008 von Microchip. Er verfügt gleich über 8 Analog-Eingänge (sein kleiner Bruder MCP3004 hat übrigens nur 4) und wandelt mit 10 Bit Genauigkeit. Das ergibt Werte zwischen 0 und 1023, die über die SPI-Schnittstelle übertragen werden.

Technische Daten: "ksps" meint übrigens"kilo samplings per second"; 200 ksps meint also 200'000 Messungen pro Sekunde. Unter optimalen Bedingungen und wollte man so etwa wie ein rudimentäres Oszilloskop nachbauen, könnte man damit also Frequenzen bis 200 kHz abbilden. Da man aber meist mehrere Messungen pro Messpunkt nimmt, um Ausreißer auszugleichen und da nicht immer die maximalen 5V anliegen, wird da allerdings nur ein Bruchteil von, vielleicht ein Zehntel, am Ende bei herumkommen.
Zum Vergleich: Mein Oszilloskop (OWON SDS1102) mit 100 MHz Bandbreite hat eine Samplerate von 1 GSa/s, also 1 Milliarde Samples pro Sekunde. Der MCP3008-Analog-Digitalwandler dagegen eben nur 200 Tausend Samples pro Sekunde, das ist Faktor 50'000 weniger.

Anschlüsse: CH0 - CH7: Analog-Eingänge <-- analoge Sensoren VDD --> 3.3V (5V möglich) VRef --> 3.3V (5V möglich) AGND --> GND CLK --> SCLK SPI0 (BCM 11) (orange) DOUT --> MISO (Master IN) (BCM 9) (grün) DIN --> MOSI (Master OUT) (BCM 10) (gelb) CS --> Chip Select CS0 (BCM 8) (braun) DGND --> GND An den linken 8 Pins (Kerbe oben) des MCP3008 stehen uns nun 8 analoge Eingänge zur Verfügung. Bitte darauf achten, hier nicht mehr Spannung anzulegen als 3.3V bzw. VRef. In der nachfolgenden Schaltung habe ich wieder einen Fotowiderstand verbaut, weil sich diese so wunderbar einfach mit einer Taschenlampe überprüfen lassen.

Die Schaltung



Der MCP3008 wird wie oben beschrieben verdrahtet. Hier daran denken: Slave OUT = Master IN. Das MISO-Kabel (Master In / Slave Out) vom Raspi (grün) gehört also an den Data OUT-Port vom MCP3008.

Leider liegen die SPI-Kabel an der GPIO-Leiste des Raspberry Pi ein wenig verteilt. Die CS0-Leitung (manchmal auch mit CE0 auf GPIO Extension Boards bezechnet) liegt auf der anderen Seite.

Sind die vier Leitungen zum MCP3008 gezogen, ist der GPIO-Teil auch schon abgefertigt und wir können uns den Sensoren an den Analog-Eingängen am MCP3008 zuwenden.









Der linke untere Pin (Chip-Ausrichtung: die IC-Kerbe links) ist Channel 0. Darauf folgen die ganze untere Pin-Reihe lang die anderen Channels bis einschließlich 7.

Ich habe an Channel 0 ein Foto-Widerstand angeschlossen und das andere Ende mit Masse verbunden.

Mit einem zweiten Widerstand von 1 kΩ zu 3.3V vom MCP-Channel 0-Pin realisiere ich einen Spannungsteiler, so dass ein je nach Widerstand des Foto-Widerstands eine andere Spannung an Channel 0 anliegt. Um einen möglichst großen Wertebereich zu bekommen, empfiehlt es sich, den Teilungswiderstand in etwa so groß wie den Normalwiderstand des Fotowiderstands zu wählen.

Da beim Foto-Widerstand der Widerstand mit einfallendem Licht abnimmt, wird die maximale Voltzahl von 3.3V bei absoluter Dunkelheit erreicht. 0V hingegen bei absoluter Helligkeit.

Allerdings ist der Verlauf der Helligkeit nicht linear, sondern ergibt eine irgendwie geartete Kurve, die vom Foto-Widerstand abhängig ist. Die beste Lösung ist, die jeweilige Helligkeitsstufe, die man braucht, in der Umgebung herzustellen und sich dann die von MCP3008 ausgegebenen Messwerte zu notieren.

Hat man eine variable Lichtquelle und ein Luxmeter zur Hand, kann man auch für bestimmte Lux-Werte die vom ADC zurückgegebenen Werte notieren. Mit vieler dieser Messwerte lässt sich eine für diesen Fotowiderstand geltende Kurve aufzeichnen, für die man vielleicht eine mathematische Funktion finden kann, die diese Kurve möglichst gut abbildet. Dann könnte man die ADC-Werte auch in Lux umrechnen und sich sozusagen sein eigenes Luxmeter basteln. Falls es nur um das Experimentieren und nicht um Genauigkeit geht: Es gibt auch Luxmeter-App für das Smartphone. Voraussetzung ist natürlich, dass das Smartphone einen Lichtsensor eingebaut hat.

Python-Sourcecode

Die Messergebnisse wollen wir natürlich weiterverarbeiten, um sie z. B. auf dem Always-On-Display anzuzeigen. Dazu schreiben wir ein kleines Python-Programm. Die Installation der benötigten Libraries haben wir ja bereits weiter oben schon erledigt. # -*- encoding: utf-8 -*- # (C) 2018 by Oliver Kuhlemann # Bei Verwendung freue ich mich um Namensnennung, # Quellenangabe und Verlinkung # Quelle: http://cool-web.de/raspberry/ import spidev from time import sleep # damit müssen wir nur noch sleep() statt time.sleep schreiben spi = spidev.SpiDev() # SPI-Bus initialisieren spi.open(0,0) # SPI Bus 0, Client 0 öffnen spi.max_speed_hz=1000000 # Max. Geschw. begrenzen für stabiles Auslesen def readMCP3008(channel): adc=spi.xfer2([1,(8+channel)<<4,0]) wert = ((adc[1]&3) << 8) + adc[2] return wert # --- Ende Funktionen --- Beginn Hauptprogramm ------------------------------------------- while 1: for i in range(0,8): v=readMCP3008(i) print "Channel", i, ": ", v, "Einh. = ", round(v/1023.*3.3,4), "V = ", round(v/10.23,1), "%" sleep (1) print Wir benutzen die SpiDev Library, die wir anfangs importieren und dann initialisieren. Dabei ist wichtig, die maximale Geschwindigkeit herabzusetzen mit der Zeile spi.max_speed_hz=1000000. Bei mir kamen sonst keine Daten an.

In der Funktion readMCP3008(channel) wird das schicken von Daten an den MCP3008 (welcher Channel soll es sein?) und das Empfangen des Ergebnisses bewerkstelligt. Diese Funktion führen wir dann in einer Schleife aus und geben die jeweiligen Werte der 8 Channel aus. Das sieht dann in etwa so aus:
pi@raspberrypi:~ $ python mcp3008-spi.py Channel 0 : 928 Einh. = 2.9935 V = 90.7 % Channel 1 : 0 Einh. = 0.0 V = 0.0 % Channel 2 : 0 Einh. = 0.0 V = 0.0 % Channel 3 : 1 Einh. = 0.0032 V = 0.1 % Channel 4 : 2 Einh. = 0.0065 V = 0.2 % Channel 5 : 11 Einh. = 0.0355 V = 1.1 % Channel 6 : 34 Einh. = 0.1097 V = 3.3 % Channel 7 : 58 Einh. = 0.1871 V = 5.7 % An Channel 0 hängt unserer Foto-Widerstand. Der vermeldet: 90.7% Dunkelheit bzw. 9.3% Helligkeit. Halte ich den Sensor zu, geht der Wert auf über 1000 und blende ich den Sensor mit der Taschenlampe, fällt der Wert auf unter 150 Einheiten.

Die anderen Channels zeigen irgendwelche kleinen Werte an - Luftspannung sozusagen - und haben keine weitere Aussagekraft. Hier kann ich später noch weitere Sensoren anschließen. Wen die flatterhaften Ausgaben an den nicht belegten Pins stören, kann diese einfach auf Masse ziehen, also mit GND verbinden. Dann liefert der MCP dafür durchweg den Wert Null.

Beim Raspberry Pi braucht man einen externen AD-Wandler für analoge Spannungen. Andere Mikrocontroller wie der Arduino, ESP8266, ESP32, aber auch der Raspberry Pi Pico verfügen über bereits integrierte Analog-In-Ports.

Aber auch bei den zuletzt genannten Kandidaten kann ein externer AD-Wandler Sinn machen, wenn man eine höhere Auflösung und damit Genauigkeit haben möchte. Denn die meisten Mikrocontroller haben nur eine Auflösung von 10 Bit, der MCP3008 allerdings von 16 Bit, was einen großen Unterschied machen kann.