Temperatur Messung mit dem DS18x20
Einleitung
Für die Aktualisierung einer älteren Wetterstation, habe ich diesen Sensor als Ersatz verwendet. Da ich die Software dieser Wetterstation seiner Zeit in C++ geschrieben habe, war es natürlich unumgänglich auch eine Abfrage dieser Sensoren in C++/C zu realisieren. Im folgenden beschreibe ich meine Erfahrungen.
Schnittstelle
1-Wire (Eindraht-Bus) beschreibt einen digitalen, seriellen Bus des Herstellers Dallas Semiconductor Corp. (seit 2001 Maxim), der mit lediglich einer Daten- und einer Masseleitung auskommt. Die Bezeichnung 1-Wire leitet sich daraus ab und beschreibt damit den Umstand dass zu der ohnehin vorhandenen Masseleitung lediglich eine weitere Leitung zur Versorgung und zur Buskommunikation benötigt wird.
Der Sensor kann die benötigte Energieversorgung direkt von der Datenleitung ableiten ("parasite power"), wodurch die Notwendigkeit einer externen Stromversorgung entfällt. Er kann aber auch herkömmlich über eine weitere Leitung versorgt werden.
Ursprünglich wurde die Technik, ähnlich wie auch der I2C-Bus, für die Kommunikation innerhalb eines Gerätes entwickelt. Sie eignet sich besonders gut für den Einsatz in der Sensorik z.B. Temperaturmessung, Akkuüberwachung, Echtzeituhr usw..
Kenndaten des Bus
- 1- Wire ist ein serieller/bidirektional Bus, er verwendet eine gemeinsame Datenleitung für das Senden und Empfangen.
- Die Datenübertragung erfolgt asynchron, also ohne Übertragung eines Taktsignals.
- Die Datenübertragung geschieht im Halbduplexverfahren. Es wird entweder gesendet oder empfangen aber nie beides gemeinsam.
- Der Bus arbeitet nach dem One-Master/Multi-Slave Prinzip. Pro Bus kann es nur einen Master (Steuereinheit) aber bis zu 100 Slaves also Geräte (Sensoren, Speicher etc.) geben.
- Jeder Slave wird durch eine 64-Bit-ROM-ID adressiert. Diese besteht aus einem 8-Bit-Family-Code, einer 48-Bit-Seriennummer (Unique-Device-ID) sowie einer 8-Bit-CRC-Prüfsumme.
8-Bit CRC 48-Bit Seriennummer 8-Bit Family Code MSB LSB MSB LSB MSB LSB - Die Slaves versorgen sich über einen internen Kondensator, der über die Datenleitung aufgeladen wird. Eine externe Spannungsversorgung ist dadurch nicht notwendig.
DS18x20
Bei den Temperatursensoren der DS18x20 Reihe handelt es sich um bereits geeichte Temperatursensor mit einem Messbereich von -55°C bis +125°C, der DS1820 (Nachfolger ist der DS18S20) bildet hier eine Ausnahme, sein Messbereich reicht lediglich von -10°C bis 85°C. Alle Sensoren, mit Ausnahme des DS1822, besitzen im Bereich von -10 °C bis +85 °C, eine Genauigkeit von ±0,5°C. Der DS1822 besitzt in diesem Temperaturbereich lediglich eine Genauigkeit von ±2.0°C. Die Auflösung der Sensoren beträgt 9- oder 12-Bit.
Anhand des "Family-Code" können die Sensoren eindeutig einer Gruppe "Family" zugeordnet werden die Folgende Tabelle verdeutlicht dies noch einmal.
Sensor | 1-Wire-Family-Codes |
---|---|
DS1820, DS1820S, DS18S20 | 10h |
DS18B20 | 28h |
DS1822 | 22h |
Alle Temperatursensoren der DS18xxx Serie besitzen einen 9 Byte großen internen Speicher, das s.g. „SCRATCHPAD“. Der Aufbau dieses Speichers hat, in Abhängigkeit vom Sensor, einen anderen Aufbau. In der folgenden Tabelle ist die Bedeutung der 9 Byte des „SCRATCHPAD“ aufgeführt.
Byte Nr. | DS1820, DS18S20, DS1820S | DS18B20 | Read/Write |
---|---|---|---|
0 | Temperatur LSB | Read | |
1 | Temperatur MSB (0x00 = positiv; 0xff = negativ) | Read | |
2 | TH Register User Byte 1 | Read / (Write) (DS 18B20) | |
3 | TL Register User Byte 2 | Read / (Write) (DS 18B20) | |
4 | Reserviert immer FFh = 256 | Read / (Write) (DS 18B20) | |
5 | Reserviert immer FFh = 256 | Read | |
6 | Count Remain | Read | |
7 | Count Per °C | Read | |
8 | CRC; berechnet über die Byte 0 … 7 | Read |
Anschluss
Der Anschluß eines DS18x20 kann auf unterschiedliche Weise erfolgen, allen gemeinsam ist die Verwendung eines Pull-Up Widerstands für die Datenleitung (DQ), dieser wird im Datenblatt mit 4,7 Kiloohm angegeben.
Anschlussarten des DS18x20
Die wohl am häufigsten verwendete, Anschlussart dürfte die Variante 1 sein. Sie ist für kurze Leitungen gedacht. Für längere Leitungen ist die Variante 2 besser geeignet.
Allerdings muss darauf geachtet werden, dass am GPIO-Pin des Raspberry Pi niemals eine Spannung angelegt wird die höher als 3v3 ist, dies würde ihn unweigerlich zerstören.
- Variante 1 - Für kurze Leitungslängen (z.B. innerhalb eines Gerätes) Die Datenleitung (DQ)
wird durch den den Widerstand (R1) auf High-Pegel gelegt. - Variante 2 – Für große Leitungslängen (theoretisch sind bis zu 100 Meter möglich)
Bei dieser Variante wird der Sensor (U1) mit einer Spannung von 5 Volt versorgt. Dies wäre jedoch für den GPIO des Raspberry Pi zu hoch, aus diesem Grund wird die Datenleitung (DQ) über den Widerstand (R1) auf einen Pegel von 3v3 gelegt. Somit können auch bei großen Leitungslängen die Sensoren mit dem Raspberry Pi betrieben werden.
Weiterhin gibt es noch eine dritte Variante, bei dieser werden lediglich zwei Leitungen benötigt (GND und DQ). Die Sensorpins GND (1) und VCC (3) werden miteinander verbunden. Die Versorgungsspannung bezieht der Sensor dann über die Datenleitung (DQ), die einen kleinen Kondensator im Sensor auflädt. Auf diese Weise lässt sich also eine Zuleitung einsparen. Die Datenleitung DQ wird genau wie bei den Varianten 1 und 2 mit dem Pull-Up Widerstand auf 3v3 Pegel gelegt.
Die zwei noch benötigten Zuleitungen sollten jedoch verdrillt Leitungen (twisted pair) sein.
Der in der Abbildung gezeigte Pull-Up Widerstand muss am 1-Wire Bus nur einmal vorhanden sein. Er kann sich direkt in der Nähe des Raspberry Pi befinden oder auf auf einer Erweiterungsplatine. Ich habe mir für den 1-Wire Bus einen Adapter auf einem Stück Lochrasterplatine aufgebaut und den Pull-Up Widerstand dort untergebracht.
Software
CRC Berechnung
Viel gibt es dazu eigentlich zu sagen. Der CRC der DS 18x20 Sensoren wird über die Bytes 0 bis 7 des "SCRATCHPAD" berechnet. Der folgende Code zeit eine Funktion zur Berechnung dieses CRC.
// Scratchpad-Registern verwendet wird.
//
// *addr - Puffer (Byte-Array)
// len - Anzahl der Byte, die in die Berechnung einfließen
uint8_t crc8(const uint8_t *addr, uint8_t len) {
uint8_t crc = 0;
while (len--) {
uint8_t inbyte = *addr++;
for (uint8_t i = 8; i; i--) {
uint8_t mix = (crc ^ inbyte) & 0x01;
crc >>= 1;
if (mix) crc ^= 0x8C;
inbyte >>= 1;
}
}
return crc;
}
Als nächstes folgt noch ein kurzes Beispiel, das die Anwendung demonstriert.
#include <stdio.h>
// Funktion crc8 einfügen
//
// Compilieren mit z.B. cc -o ds18x20_crc_bsp ds18x20_crc_bsp.c
int main() {
// SCRATCHPAD Daten
// CRC = Byte Nr. 8 = 0x78
uint8_t buf[9] = {0x2f, 0x00, 0x4b, 0x46, 0xff, 0xff, 0x08, 0x10, 0x78};
uint8_t crc;
crc = crc8(buf, 8); // Bytes 0 ... 7 einbeziehen
if (crc == buf[8]) {
printf("Ok. CRC ist gültig.\n");
printf("buf[8] = 0x%02x\n", buf[8]);
printf("berechnet = 0x%02x\n", crc);
}
return 0;
}
1-Wire Bus durchsuchen
Wie bereits erwähnt sind die Sensoren mit einem "Family" Code versehen, anhand diesem können alle am 1-Wire Bus angeschlossenen DS18x20 gefunden werden. Die folgende Funktion beschreibt eine Art der Suchfunktion.
/*
* Struktur, um die DS18x20 Daten in einer verketteten Liste zu
* speichern.
*/
struct DS18x20 {
char deviceID[16];
struct DS18x20 *next;
};
/* Suche alle, am mit path, angegebenen 1-Wire Bus und
* erzeugt eine Liste mit den gefunden Geräten.
*
* @param path 1-Wire Bus
* @param devices Rückgabewert verkette Liste
* @return Anzahl der gefunden Geräte
*
*/
int8_t findDevices(const char *path, struct DS18x20 *devices) {
DIR *dir;
struct dirent *dirent;
struct DS18x20 *newDev;
uint8_t famcode = 0; // Temp Family Code
uint8_t devsFound = 0; // Anzahl gefundener Sensoren
dir = opendir(path);
if (dir != NULL) {
while ((dirent = readdir(dir))) {
// DS18x20 Family Code ermitteln, 10, 22 oder 28
(void) sscanf(dirent->d_name, "%2x-", &famcode);
// Die Geräte Namen in /sys/bus/w1/devices sind Links
// also werden auch nur diese verarbeitet
if (dirent->d_type == DT_LNK) {
// Alle DS18x20 zählen
switch (famcode) {
case 0x10: // DS1820, DS1820S, DS18S20
case 0x22: // DS1822
case 0x28: // DS18B20
devsFound++;
// Speicher reservieren für neuen Listeneintrag
newDev = malloc( sizeof(struct DS18x20) );
//
strcpy(newDev->deviceID, dirent->d_name);
newDev->next = 0;
devices->next = newDev;
devices = devices->next;
break;
}
}
}
(void) closedir(dir);
} else {
devsFound = -1;
}
return devsFound;
}
Temperatur lesen
// Auslesen des DS1820 Temperaturwertes
//
float readTemperatur() {
FILE *devFile = fopen(path, "r");
if (devFile == NULL) {
printf("Count not open %s\n", path);
perror("\n");
}
float temp = -1;
if (devFile != NULL) {
if (!ferror(devFile)) {
unsigned int tempInt;
int buf[10];
// Skip 1. Line, 39 = length of first line
fseek(devFile, 39, SEEK_SET);
// Read 2. Line, and get data
(void) fscanf(devFile, "%x %x %x %x %x %x %x %x %x t=%5d",
&buf[0], &buf[1], &buf[2], &buf[3], &buf[4],
&buf[5], &buf[6], &buf[7], &buf[8], &tempInt);
if (crc8(buf, 8) == buf[8]) {
temp = (float) tempInt / 1000.0;
} else {
// Fehler
printf("CRC %x invalid.\n", buf[9]);
temp = DEVICE_ERROR;
}
}
fclose(devFile);
}
return temp;
} // readTemperatur
Alle zuvor gezeigten Funktionen habe ich in einer Klasse zusammen gefasst die hier geladen werden kann.
Da die dargestellten Schaltungen und Programme nur dem Grundverständnis dienen sollen, kann ich für die Funktion keine Gewähr übernehmen.
Wie üblich kann ich für Schäden die durch die Verwendung der hier veröffentlichten Schaltungen und Programme entstehen keine Haftung übernehmen.
Alle genannten und durch Dritte geschützten Marken- und Warenzeichen unterliegen uneingeschränkt den Bestimmungen des jeweils gültigen Kennzeichenrechts und den Besitzrechten der jeweiligen eingetragenen Eigentümer. Allein aufgrund der bloßen Nennung ist nicht der Schluss zu ziehen, dass Markenzeichen nicht durch Rechte Dritter geschützt sind!