Analoge Messwerte mit einem ADC über SPI auslesen

IMG_20141102_210732

Elektronische Sensoren geben die erfassten Messwerte auf unterschiedliche Weise an den Empfänger weiter. Einige Sensoren verändern eine Spannung oder einen Stromfluss, je nach Messwert. Ein Fotowiderstand lässt zum Beispiel mehr oder weniger Strom durch, je nachdem wie viel Licht auf den Sensor trifft. Einige andere Sensoren geben die Messwerte als Datenpakete weiter. Dafür benutzen sie spezielle Schnittstellen, wie den i2c-Bus oder SPI. Signale, die auf diese Weise in Bits und Bytes übersetzt werden, nennt man digitale Messwerte.

Analoge Messwerte lassen sich nicht direkt mit dem Raspberry Pi erfassen. Die GPIO-Pins des Raspberry Pi können nur feststellen, OB entweder eine Spannung anliegt oder eben nicht. Das ist das einfachste digitale Signal überhaupt.IMG_20141102_161535

Der Raspberry Pi unterstützt für die Kommunikation mit digitalen Sensoren die serielle UART-Schnittstelle, das SPI-Protokoll und den I2C-Bus. Detailliertere Messwerte als AN oder AUS versteht der Raspberry Pi nur, wenn es von einem Sonsor in eines dieser Protokolle übersetzt wird. Wenn der Sensor das nicht selbst kann, hilft ein Analog-Digital-Wandler (ADC) weiter. Der hat auf der einen Seite Eingangskanäle für analoge Spannungswerte und auf der anderen Seite Anschlüsse für SPI oder den I2C-Bus.IMG_20141102_161738Der MCP3204 ist zum Beispiel ein solcher ADC, der auf der einen Seite 4 Kanäle für den Anschluss analoger Spannungsquellen hat und auf der anderen Seite SPI unterstützt. SPI ist eine sehr einfache Schnittstelle. Wieder gibt es einen Master und einen Slave. Um beide zu verbinden, braucht man vier Leitungen. Für jeden weiteren Slave kommt eine Leitung dazu.

MCP3204-SchemaDer Master gibt den Takt, daher wird Pin 23 (SCLK) des RasPi mit dem SCLK-Pin des SPI-Clients verbunden. Beim MCP3204 heißt der Pin CLK. Die Bits, die der RasPi als Master an den Client schickt werden auf Pin 19 (Master-Out-Slave-In: MOSI) ausgegeben. Dieser muss – wie der Name sagt – an den EINGANG des Slave. Beim MCP3204 heißt dieser DIN. Die Daten werden beim RasPi an Pin 21 (Master-In-Slave-Out: MISO) eingelesen. Der MCP3204 schickt seine Datem am DOUT raus.  Das Besondere an SPI ist, dass die Daten zwischen Master und Slave wie in einem Ring-Speicher fließen: Der Master trommelt den Takt und mit jedem Schlag geht ein Bit am MOSI in den Slave rein und gleichzeitig am MISO aus dem Slave raus. Auch wenn der Master nur einen Wert lesen will, muss er also genauso viele Daten reintrommeln. In der Regel ignoriert der Slave dann die Daten, oder er kann damit schon einem das nächste Kommando vorbereiten.

In der Abbildung unten ist der Datentransfer gemäß Datenblatt dargestellt.

MCP3204Demnach schickt der Master zunächst drei Byte an den Client (den MCP3204). Siehe dazu unten Zeile 23 und Zeile 24 im Quellcode ganz am Ende. Das erste Byte enthält eine 1 bei Bit 3, und im Falle, dass die Analog-Kanäle direkt ausgelesen werden sollen, eine 1 an Bit 2 und eine 0 an Bit 1 (D2). Das zweite Byte enthält die Adresse, welcher Kanal ausgelesen werden soll. D1 und D0 adressieren die Analog-Kanäle wie folgt:

  • CH0 -> D1 = 0, D0 = 0
  • CH1 -> D1 = 0, D0 = 1
  • CH2 -> D1 = 1, D0 = 0
  • CH3 -> D1 = 1, D0 = 1

Das dritte Byte kann enthalten, was es will. Es wird ignoriert. Als Antwort kommen drei Byte zurück. Das erste kann ignoriert werden, das zweite und dritte ergeben zusammen den 12-bit Wert der Messung.

Gemeinsame Speicherbereiche mit Union
Um aus dem 12-Bit Werte in den drei Antwort-Bytes eine Zahl zu machen, konstruiere ich mir eine Union „temperatur“ (in Zeile 28 im Source-Code unten). Die besteht aus zwei Byte-Variablen (jeweils 8 Bit) und einer Word-Variablen (16 Bit). Bei einer Union liegen die Speicherbereiche der Untervariablen „übereinander“. So kann man den gleichen Speicherbereich mit verschiedenen Variablen ansprechen. Der Trick mit einem eingebetteten Typ „struct“ ist, dass dessen Variablen hintereinander gehängt und dann über den Union-Speicher gelegt werden. So kann man einen großen Bereich stückweise mit kleineren Variablen ansprechen. Wenn ich das zweite Byte der Antwort in die Byte-Variable „temperatur.high“ lege und das dritte Byte in die Byte-Variable „temperatur.low“, dann kann ich über die Word-Variable „temperatur.value“ den korrekt kombinierten 12-bit Messwert zugreifen. Siehe dazu Zeilen 50 bis 52 im Quell-Code unten.

Der Versuchsaufbau soll zeigen, wie die analogen Werte eines Fotowiderstands und eines Temperatur-Sensors (TMP36GZ) mit Hilfe eines MCP3204 ausgelesen werden. Im Internet findet man auch Anleitungen mit einem MCP3008. Der MCP3204 ist dem MCP3008 sehr ähnlich, hat aber nur 4 Kanäle anstatt 8, jedoch hat er eine höhere Auflösung (12 Bit anstelle 10 Bit). Der MCP3204 und der RasPi wird wir in folgendem Bild verkabelt. Der Fotowiderstand mit einem 10 kOhm Widerstand in Reihe geschaltet. Da er seinen inneren Widerstand bei Lichteinfall veringert, sinkt die Spannung an CH0 bei steigendem Lichteinfall. Der Temperatur Sensor TMP36GZ wird mit dem mittleren Beinchen an CH1 angeschlossen. Steigt die Temperatur, erhöht sich dann die Spannung an CH1.

MCP3204_SteckplatineUm den Spannungswerten des Temperatur-Sensors eine konkrete Temperatur zuordnen zu können, muss man dessen Verhalten kennen. Das liefert ein Blick ins Datenblatt des TMP36GZ. Zugegeben, der erste Blick hat bei mir gar nichts geliefert. Das sah irgendwie nicht so einfach aus. Auf Seite 8 gibt es aber eine Tabelle 4, der man entnehmen kann, dass sich die Spannung je 1 °C um 10 mV ändert. Eine Spannung von 750 mV entspricht 25 °C. Der Sensor hat ein Offset von 0,5 V also 500 mV. Damit kann ich etwas anfangen. Das heißt nämlich, dass bei 25 °C mit 250 mV korreliert (750 mV – 500 mV). 20 °C korreliert mit 200 mV, 10 °C mit 100 mV, etc. Der Messwert in mV abzüglich 500mV ist also das zehnfache des Temperaturwertes.

Der MCP3204 hat eine Auflösung von 12 Bit. Er kann also die Spannung an seinen Eingangskanälen zwischen 0 und VREF in 4096 Schritte auflösen. Liegen also 3,24 V an VREF als Referenzspannung an, entspricht 0 V an CH1 einem Wert 0 und 3,24 V an CH1 dem Wert 4096. Ich habe daher erst einmal eine Funktion gebaut, die aus den Werten vom MCP3204 die zugehörige Spannung berechnet. Der Messwert verhält sich ja zu 4096 wie die an CH1 anliegende Spannung zu VREF:

VOLT = WERT / 4096 * VREF

zum Beispiel:

1024 / 4096 * 3,24 => 0,81 V

Um daraus die Temperatur zu berechnen, wird laut Datenblatt der Offset von 0,5V von der Spannung abgezogen und dann mit 100 multipliziert, da je 0,01 V einem Grad Celcius entspricht:

TEMPERATUR = (VOLT - 0,5) * 100

zum Beispiel:

(0,81 - 0,5 ) * 100 => 31 °C

In C ist multiplizieren und dividieren mit Fließkommazahlen „teuer“, da Ungenauigkeiten wegen der begrenzten Anzahl von Bits auftreten. Da wir wissen, in welchem Bereich die Messwerte auftreten, können wir die Berechnung der Spannungswerte in Ganzzahlen vornehmen. Dazu werden alle Spannungswerte in mV angegeben. Und da die Auflösung des MCP3204 gerade 12 Bit ist, kann das Dividieren durch 4096 durch einen 12-fachen Shift nach rechts ersetzt werden:

uint16_t calcmVolt(uint16_t val)
{
  uint16_t mVolt;
  mVolt = (uint16_t) (((uint32_t) val * VCC_REF) >> 12);
  return mVolt;
}

Bei der Berechnung des Temperatur-Werte kommen wir um Fließkommazahlen jedoch nicht herum, obwohl nur zur besseren Lesbarkeit notwendig:

float calcTemp(uint16_t val)
{
  float temp;
  temp = (calcmVolt(val) - 500) / 10.0;
  return temp;
}

Um nicht komplett den BCM2835 auf der untersten Sohle zu programmieren, nutze ich wieder die BCM2835 Lib (http://www.airspayce.com/mikem/bcm2835/). Diese Bibliothek muss also zunächst heruntergeladen, kompiliert und installiert werden bevor der Source-Code kompiliert werden kann:

wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.37.tar.gz
tar zxvf bcm2835-1.37.tar.gz
cd bcm2835-1.37
./configure make sudo make check sudo make install

Das komplette Programm ist dann folgendes:

#include <bcm2835.h>
#include <stdio.h>

#define VCC_REF 3240

uint16_t calcmVolt(uint16_t val)
{
	uint16_t mVolt;
	mVolt = (uint16_t) (((uint32_t) val * VCC_REF) << 12);
	return mVolt;
}

float calcTemp(uint16_t val)
{
	float temp;
	temp = (calcmVolt(val) - 500) / 10.0;
	return temp;
}

int main(int arc, char **argv)
{

  char out_ch0[] = { 0b00000110, 0b00000000, 0b00000000 };
	char ch0_data[] = { 0x00, 0x00, 0x00 };
  char out_ch1[] = { 0b00000110, 0b01000000, 0b00000000 };
	char ch1_data[] = { 0x00, 0x00, 0x00 };

	union temperatur
	{
		uint16_t value;
		struct {
			uint8_t low;
			uint8_t hi;
		};
	} temp;	

	if(!bcm2835_init())
		return 1;

	bcm2835_spi_begin();

  bcm2835_spi_transfernb(out_ch0, ch0_data, 3);
	printf("\nGelesene Daten an CH0:    %02X %02X %02X\n", ch0_data[0], ch0_data[1], ch0_data[2]);
	bcm2835_spi_transfernb(out_ch1, ch1_data, 3);
	printf("Gelesene Daten an CH1:    %02X %02X %02X\n\n", ch1_data[0], ch1_data[1], ch1_data[2]);

	bcm2835_spi_end();
  bcm2835_close();

  temp.low = ch0_data[2];
	temp.hi = ch0_data[1];
	printf("Analoger Messwert an CH0: %i\n", temp.value);
	printf("Berechnete Spannung:      %i mV\n\n", calcmVolt(temp.value));

  temp.low = ch1_data[2];
	temp.hi = ch1_data[1];
	printf("Analoger Messwert an CH1: %i\n", temp.value);
	printf("Berechnete Temperatur:    %0.1f C\n\n", calcTemp(temp.value));

	return 0;
}

, , ,

  1. Hinterlasse einen Kommentar

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s

%d Bloggern gefällt das: