![]() |
Arduino: 1-Wire Temperatursensor DS1820Prof. Jürgen Plate |
Die 1-Wire Temperatursensoren sind teilweise recht populär geworden, da sie recht einfach einzusetzen und verhältnismässig preiswert und in verschiedenen Bauformen erhältlich sind. Eine ideale Möglichkeit um Sensoren an einen Arduino zu koppeln, auch über weitere Strecken. Dank einer vorhandenen Bibliothek ist es nicht allzu schwer, sogenannte 1-Wire-Sensoren am Arduino zu betreiben. Doch zunächst einige Hintergrundinformationen.
1-Wire ist ein digitaler, serieller Bus des Herstellers Maxim (ehem. Dallas), der mit einer Datenader und einer Masseleitung auskommt. Die Bezeichnung 1-Wire leitet sich daraus ab, dass zu der ohnehin vorhandenen Masseleitung nur eine weitere Ader für die gesamte Buskommunikation und die Energieversorgung erforderlich ist. Die Form der Energieübertragung bei diesem Bus heißt "parasitic power": Wenn gerade keine Daten übertragen werden, saugt sich der Chip seine Energie aus der aktiven Leitung und speichert sie in einem kleinen Kondensator, der während der Sendeaktivität zur Überbrückung dient. Aber auch eine kontinuierliche Versorgung über einen zweiten Draht ist möglich. 1-Wire wird auch als "MicroLAN" bzw. "Single Wire Serial Interface" bezeichnet. 1-Wire eignet sich insbesondere für Sensorik (Temperaturmessung, Akkuüberwachung, Spannung, Temperatur, Stromfluss), zur Steuerung und Meldung und für Identifikation durch einmalige, eindeutige und nicht veränderbare 64-Bit-ID. Diese ID setzt sich aus einer 48-Bit Seriennummer, einem 8-Bit langen Family-Code und einer 8-Bit-CRC zusammen.
Ursprünglich für die Kommunikation zwischen den Komponenten eines Gerätes bzw. Schaltschrankes entwickelt, wurde der 1-Wire-Bus zu einem einfach handhabbaren Bussystem für Strecken bis zu mehreren hundert Metern erweitert. Dies wurde insbesondere durch verbesserte Hostinterfaces mit Kontrolle der Signalanstiegszeit und aktivem Pull-Up Widerstand sowie durch Rauschfilter und ein optimiertes Timing erreicht. Auch die zusätzliche Versorgung des Bausteins verbessert die Qualität der Übertragung. Jeder Chip besitzt eine eigene Zeitbasis, die die ankommenden Signale nach ihrem logischen Gehalt unterscheiden kann. Die Datenübertragung erfolgt in Blöcken zu 64 Bit und ist bidirektional, seriell, asynchron und Halbduplex, da dieselbe Datenleitung für Senden und Empfangen benutzt wird. Die gesamte Kommunikation wird durch den Busmaster gesteuert.
Hierbei können mehrere Dutzend Slaves an einem Busmaster angeschlossen werden. Die Geschwindigkeit der Datenübertragung reicht von 15,4 kbps (Standard) bis 125 kbps (Overdrive). Hierbei ist "parasitic power" nur bei der Standard-Übertragungsrate möglich, welche jedoch völlig ausreichend ist.
Beim DS18S20 und den verwandten Bausteinen DS18B20 sowie DS1822 handelt es sich um integrierte Schaltkreise im TO-92 Gehäuse, die Temperatursensor, Analog-Digital-Wandler und 1-Wire-Interface enthalten. Die Bausteine sind Anschluss- und Softwarekompatibel, sie unterscheiden sich im Wesentlichen in der Messgenauigkeit und im Preis. Den Temperatursensor gibt es auch noch im DIL-Gehäuse, wobei in der Regel die TO92-Variante verwendet wird. Ich verwende die Bezeichung DS18x20 als Synonym für alle Typen. Die Sensoren können direkt am Arduino angeschlossen werden.
Der Sensor benutzt dabei die drei Leitungen Masse, Vcc (5 V) und eine Signalleitung. Der DS18x20 wird an die Arduino-Pins für 5 V (Vcc), Ground (GND) und einen Digitaleingang (in den folgenden Beispielen Pin 2) für die Datenleitung angeschlossen. Zwischen die 5-V-Leitung und die Datenleitung wird ein 4,7-Kiloohm-Widerstand geschaltet. Bei sehr langen Verbindungsleitungen kann man auch bis auf 1,8 Kiloohm heruntergehen.
Werden mehrere Sensoren benötigt, schließt man alle Sensoren parallel an, wobei der Pullup-Widerstand nur einmal benötigt wird. Er kann in der Nähe der Arduino-Buchsenleiste untergebracht werden.
Der parasitäre Modus ist prinzipiell auch möglich. In diesem Fall wird der Vdd-Pin mit dem GND-Pin am Baustein verbunden (1 und 3), die Versorgungsleitung entfällt dann. In diesem Fall muss die Datenleitung auch die Speisung der Sensoren übernehmen, was die Übertragung etwas unsicherer macht. Insofern sollte man bei drei Adern bleiben.
Die Bibliothek stellt eine Handvoll Methoden für den Zugriff auf 1-Wire-Devices zur Verfügung, sodass sich der Benutzer nicht mit dem Decodieren der Impulsbreiten usw. herumschlagen muss. Die Bibliothek ist nicht von Haus aus bei der Arduino-IDE dabei, man lädt sie von Paus Stoffregens Guthub-Repository als .ZIP herunter. Dann muss man in der Arduino-IDE nur noch Sketch → Bibliothek einbinden → .ZIP-Bibliothek hinzufügen anklicken, um sie zu installieren. Um eine OneWire-Instanz zu erzeugen, genügt der folgende Constructor, dem man den verwendeten Datenpin als Parameter mitgibt:
#define OneWirePin 2 ... OneWire DS1820(OneWirePin)Folgende Funktionen bzw. Methoden bietet die Bibliothek an:
Anfangs wird man sicher erst eine Liste der vorhandenen Temperatursensoren benötigen. Das folgende Programm scannt den 1-Wire-Bus nach DS18x20-Devices ab. Von jedem Chip werden, wie obe beschrieben acht Bytes empfangen. Anhand des ersten Bytes läßt sich der Typ des Sensors bestimmen. Das letzte Byte enthält die CRC-Prüfsumme. Im Programm wird die CRC-Prüfsumme der empfangenen Bytes berechnet und mit dieser verglichen. Die Funktion loop() bleibt leer, es genügt ja, einmal alle Devices zu scannen, was in der Funktion init() erfolgt.
#include <OneWire.h> OneWire DS1820(2); void showAddress(byte address[8]) { // Im Array address enthaltene Daten kompakt sedezimal ausgeben byte i; if (OneWire::crc8(address, 7) != address[7]) { Serial.println("hat keinen gueltigen CRC!"); } else { //alles ist ok, anzeigen for (i = 0; i < 8; i++) { if (address[i] <= 0xF) { Serial.print("0"); } Serial.print(address[i],HEX); } Serial.println(""); } } void lookUpSensors() { // Alle DS18x20-Sensoren suchen, Adresse ausgeben byte address[8]; bool found = false; bool detect = false; Serial.println("--Suche gestartet--"); while (DS1820.search(address)) { detect = false; switch (address[0]) { case 0x10: Serial.print("DS18S20: "); // oder alter DS1820 detect = true; break; case 0x28: Serial.print("DS18B20: "); detect = true; break; case 0x22: Serial.print("DS1822: "); detect = true; break; default: Serial.println("Device ist kein DS18x20-Sensor."); } // Adresse anzeigen, wenn ein Temperatursensor gefunden wurde if (detect) { found = true; showAddress(address); } // ... if detect }// ... while if (!found) { Serial.println("Keine Sensoren gefunden"); } Serial.println("--Suche beendet--"); } void setup(void) { Serial.begin(9600); lookUpSensors(); } void loop(void) { // nichts tun ... }
Erweitert man das obige Programm um die Temperaturmessung, kann man für alle angeschlossenen DS18x20-Sensoren nicht nur die Adresse, sondern auch Temperatur abfragen und anzeigen. Das Programm ähnelt dem vorhergehenden, nur ist die Anzeige der Adressen nun nur noch verkürzt. Nach dem gleichen Schema wie die Anzeige der Adressen werden auch hier wieder in der Funktion readSensors() alle DS18x20-Sensoren gesucht. Da der DS18S20 eine geringere Auflösung als die anderen Sensoren hat, wird auch der Typ in einer Bool-Variable festgehalten. Danach erfolgt das Auslesen und Ausgeben der Temperatur.
Kern der Erweiterung ist die Funktion getTemperature(). Hier wird die Messung angestoßen udn dan mindestens 750 ms gewartet, bis die Messdaten bereit stehen (sicherheitshaber wartet das Programm sogar eine ganze Sekunde). Dann werden neun Byte ausgelesen.Abhängig vom Typ und von der zurückgegebenen Konfigurationsinfo) wird dann der Temperaturwert aus den Rohdaten berechnet und als Float zurückgegeben:
#include <OneWire.h> OneWire DS1820(2); void showAddress(int number, byte address[8]) { // Im Array address enthaltene Daten kompakt sedezimal ausgeben byte i; Serial.print(number); Serial.print(" - "); if (OneWire::crc8(address, 7) != address[7]) { Serial.println("hat keinen gueltigen CRC!"); } else { //alle ist ok, anzeigen for (i = 0; i < 8; i++) { if (address[i] <= 0xF) { Serial.print("0"); } Serial.print(address[i],HEX); } Serial.println(""); } } float getTemperature(bool type_s, byte address[8]) { // Temperaturwert des adressieren Sensors auslesen // Dabei wird zwischen DS18S20 udn den anderen unterschieden byte data[12]; int16_t raw; byte i; DS1820.reset(); DS1820.select(address); DS1820.write(0x44, 1); // Start Messung, parasitaere Versorgung an delay(1000); // eventuell reichen auch 750 ms DS1820.reset(); DS1820.select(address); DS1820.write(0xBE); // Read Scratchpad for ( i = 0; i < 9; i++) { data[i] = DS1820.read(); } raw = (data[1] << 8) | data[0]; if (type_s) { raw = raw << 3; if (data[7] == 0x10) // Vorzeichen expandieren { raw = (raw & 0xFFF0) + 12 - data[6]; } } else { byte cfg = (data[4] & 0x60); // Aufloesung bestimmen, bei niedrigerer Aufloesung sind // die niederwertigen Bits undefiniert -> auf 0 setzen if (cfg == 0x00) raw = raw & ~7; // 9 Bit Aufloesung, 93.75 ms else if (cfg == 0x20) raw = raw & ~3; // 10 Bit Aufloesung, 187.5 ms else if (cfg == 0x40) raw = raw & ~1; // 11 Bit Aufloesung, 375.0 ms // Default ist 12 Bit Aufloesung, 750 ms Wandlungszeit } return ((float)raw / 16.0); } void lookUpSensors() { // Alle DS18x20-Sensoren suchen, Adresse ausgeben byte address[8]; bool type_s = false; bool found = false; bool detect = false; int count = 0; while (DS1820.search(address)) { // Adresse anzeigen, wenn ein Temperatursensor gefunden wurde if ((address[0] == 0x10) or (address[0] == 0x22) or (address[0] == 0x28)) { found = true; count++; showAddress(count, address); } } // ... while if (!found) { Serial.println("Keine Sensoren gefunden"); } } void readSensors() { // Bei allen DS18x20-Sensoren die Temperatur auslesen und ausgeben byte address[8]; bool type_s = false; bool detect = false; float celsius; int count = 0; while (DS1820.search(address)) { detect = false; // Ist es ein DS18x20? Wenn ja, welcher Typ? switch (address[0]) { case 0x10: detect = true; type_s = true; break; case 0x28: detect = true; type_s = false; break; case 0x22: detect = true; type_s = false; break; } if (detect) { // Temperatur auslesen und anzeigen count++; celsius = getTemperature(type_s, address); Serial.print(count); Serial.print(" - "); Serial.print("Temperatur: "); Serial.print(celsius); Serial.println(" °C"); } } // ... while DS1820.reset_search(); } void setup(void) { Serial.begin(9600); lookUpSensors(); } void loop(void) { readSensors(); Serial.println(""); delay(2000); }
Normalerweise muss man beim Programm aber gar keinen so große Aufwand treiben wie oben, denn die Sensoradressen sind ja recht statisch, wenn die Sensoren mal verdrahtet sind. Da kann man dann das erste Programm verwenden, um die Adressen einmal auszulesen und diese dan statisch im Programm speichern. Damit bleibt dann nur noch die Funktion getTemperature() übrig - und eine Ausgabe im der loop():
// OneWire-Bibliothek unter https://github.com/PaulStoffregen/OneWire // Als .zip herunterladen, dann in der Arduino-IDE: // Sketch -> Bibliothek einbinden -> .ZIP-Bibliothek hinzufuegen #include <OneWire.h> OneWire DS1820(2); // Definition von Sensor-Adressen und -Namen byte Sensor1[8] = {0x10,0x4D,0x63,0xBF,0x02,0x08,0x00,0xA9}; char Name1[] = "Innen: "; byte Sensor2[8] = {0x10,0x5D,0xB1,0xCF,0x02,0x08,0x00,0x87}; char Name2[] = "Aussen: "; // Wer will, kann auch alles in mehrdimensionale Array packen, // aber bei nur zwei Sensoren lohnt das nicht float getTemperature(bool type_s, byte address[8]) { // Temperaturwert des adressieren Sensors auslesen // Dabei wird zwischen DS18S20 udn den anderen unterschieden byte data[12]; int16_t raw; byte i; DS1820.reset(); DS1820.select(address); DS1820.write(0x44, 0); // Start Messung, parasitaere Versorgung aus delay(1000); // eventuell reichen auch 750 ms DS1820.reset(); DS1820.select(address); DS1820.write(0xBE); // Read Scratchpad for ( i = 0; i < 9; i++) { data[i] = DS1820.read(); } raw = (data[1] << 8) | data[0]; if (type_s) { raw = raw << 3; if (data[7] == 0x10) { raw = (raw & 0xFFF0) + 12 - data[6]; } } else { byte cfg = (data[4] & 0x60); // Aufloesung bestimmen, bei niedrigerer Aufloesung sind // die niederwertigen Bits undefinier -> auf 0 setzen if (cfg == 0x00) raw = raw & ~7; // 9 Bit Aufloesung, 93.75 ms else if (cfg == 0x20) raw = raw & ~3; // 10 Bit Aufloesung, 187.5 ms else if (cfg == 0x40) raw = raw & ~1; // 11 Bit Aufloesung, 375.0 ms // Default ist 12 Bit Aufloesung, 750 ms Wandlungszeit } return ((float)raw / 16.0); } void setup(void) { Serial.begin(9600); } void loop(void) { float celsius; celsius = getTemperature(true, Sensor1); Serial.print(Name1); Serial.print(celsius); Serial.println(" °C"); celsius = getTemperature(true, Sensor2); Serial.print(Name2); Serial.print(celsius); Serial.println(" °C"); Serial.println(""); delay(2000); }
Eine Möglichkeit, die Anzahl der angeschlossenen Sensoren zu erhöhen, wäre das Ausweichen auf andere Schnittstellen, z. B. seriell oder I2C. Dazu bräuchte man dann einen sogenannten Hostadapter wie er z. B. von Sheepwalk Electronics angeboten wird ( http://www.sheepwalkelectronics.co.uk/product_info.php?products_id=30 oder http://www.sheepwalkelectronics.co.uk/product_info.php?products_id=33) Es können dann mehrere Hostadapter angeschlossen werden, die ihrerseits jeweils eine bestimmte Anzahl Sensoren verwalten.
Eine weitere Idee wäre das Gruppieren der Sensoren, wie es unter "Switched Networks" in den "Guidelines for Reliable Long Line 1-Wire Networks" (AN148, siehe Literatur) beschrieben ist. Nach Angabe des Autors der Bibliothek können mehrere OnoeWire-Instanzen auf verschiedene Digitalpins gelegt werden. Wenn das nicht reicht, können für die Umschaltung digitale Multiplexer oder auch Analogschalter verwendet werden. Damit gibt es im Grunde keine Grenze mehr nach oben.
Jeder hat schon in einem Wetterbericht den Begriff "Windchill" oder "gefühlte Temperatur" gehört. Damit soll berücksichtigt werden, dass bei starkem Wind die von einem Menschen gefühlte Temperatur niedriger sein kann als die tatsächliche Temperatur.
Die Lufttemperatur, die wir empfinden, wird als "gefühlte Temperatur" bezeichnet. Diese gefühlte Lufttemperatur ist von vielen Faktoren abhängig, beispielsweise
Die Angabe der "gefühlten Temperatur" im Wetterbericht bezieht sich auf einen Modell-Menschen. Der Deutsche Wetterdienst verwendet dazu das Klima-Michel-Modell. Dies ist ein computersimulierter Durchschnittsmensch mit folgenden Eigenschaften: Es ist ein 35 Jahre alter und 1,75 Meter großer Mann mit einem Körpergewicht von 75 Kilogramm, der sich mit ca. 5 km/h im Freien fortbewegt und der Witterung entsprechend gekleidet ist.
Man kann die gefühlte Temperatur nach der folgenden, relativ einfachen Formel ausrechnen, in die nur die tatsächliche Temperatur (t) in Grad Celsius und die Windgeschwindigkeit (v) in km/h eingehen. Der Windchill (W) in Grad Celsius ergibt sich aus:
W = 13,12 + 0,6215 * t – 11,37 * v0,16 + 0,3965 * t * v0,16Ein Beispiel dazu: Bei einer Temperatur von –5 Grad Celsius und einer Windgeschwindigkeit von 15 Kilometer pro Stunde beträgt die gefühlte Temperatur rund –11 Grad Celsius.