Erstellung eines Temperaturloggers

Ziel

Für eine Projektarbeit soll mit Hilfe eines Temperatursensors und und einem Mikrocontroller ein Temperaturlogger erstellt werden. Dieser soll folgende Eigenschaften haben:

  • Temperaturbereich von -20°C bis 43°C
  • Auflösung: 6 Bit
  • Temperaturwerte mit Zeitstempel
  • min. 30 Temperaturwerte speichern
  • Datenübertragung an einen PC (z.B. über USB oder LAN)

Umsetzung

Komponenten

DS18B20

Als Temperatursensor wird der digitale Sensor DS18B20 von Dallas verwendet. Über das One-Wire Protokoll können die Daten abgefragt werden. Der Sensor benötigt für die Kommunikation mit dem BUS nur einen Pin. Über ihn erfolgt die Adressierung und die Datenübermittlung. Außerdem kann die Stromversorgung auch über den Datenpin erfolgen. Dabei wird ein interner Kondensator geladen, wenn der Sensor nicht arbeitet. Der Sensor misst Temperaturen von -5◦C bis 125◦C bei einer Auflösung von 12 Bit.

Arduino Pro Ethernet

Arduino ist eine open-source entwicklungs Plattform, mit der es relativ einfach ist komplexe Projekte zu realisieren. Der Grund dafür ist unter anderem eine große, gut erklärte, C++ Bibliothek. Da ich nicht mehr viel Zeit über hatte, musste ich also mit dem Arduino Board weiterarbeiten. Bei Sparkfun.com fand ich ein Arduino kompatibles Board mit dem W5100 Internet-Chip. So musste ich mich nicht mehr um die Schaltung kümmern.

Programm

Das Programm lässt sich in 4 Hauptteile zerlegen: Temperaturmessung, Zeitermittlung, Speicherung, Weboberfläche.

Nachdem der Mikrocontroller gestartet ist, und den Internetchip initialisiert hat wird alle 60 Sekunden die Aktuelle Temperatur gespeichert. Sobald jemand die Weboberfläche des Arduino aufruft werden alle gesammelten Daten an den Browser geschickt. Über die serielle Schnittstelle werden zusätzliche Informationen wie IP-Adresse oder Aktuelle UNIX Zeit ausgegeben.


/***********************************************************************************
*                                        INCLUDES                                  *
***********************************************************************************/

// Für die Ethernet kommunikation
#include 
#include 

// Für die Temperaturmessung
#include 

// Um Daten in den EEPROM abzulegen
#include 

// Um mit dem NTP-Server Kommunizieren zu können
#include 



/***********************************************************************************
*                                        SETUP                                     *
***********************************************************************************/
//################
//###  Serial port
//################

const long int baud = 9600;                           // Baudrate (bits pro Sekunde)

//################
//###  Ethernet
//################

byte mac [] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEA };   // MAC
byte ip[] = { 192, 168, 1, 177 };                       // IP address
byte gateway[] = { 192, 168, 0, 1 };                    // Gateway
byte subnet[] = { 255, 255, 255, 0 };                   // Subnet
int port = 80;                                          // Port für Weboberfläche
unsigned int localPort = 8888;            // Port für die UDP Pakete (NTP-Antwort)

IPAddress timeServer(192,53,103,108);                // NTP Server (ptbtime1.ptb.de)

//###########################
//###  DS18x20 temperaturechip
//############################

OneWire ds(2);                                        // Temperatursensor an Pin 2

long interval = 60000;             // Millisekunden zwischen jeder Temperaturmessung


/***********************************************************************************
*                                        SETUP END                                 *
***********************************************************************************/

/***********************************************************************************
*                                        Initialisation                            *
***********************************************************************************/

long previousMillis = 0;                               // Letzte Temperaturmessung

EthernetServer server(port);                           // Port einstellen

const int NTP_PACKET_SIZE= 48;       // NTP Timestamp ist in den ersten 48 Bytes
byte packetBuffer[ NTP_PACKET_SIZE]; // Puffer zum Halten von einund ausgehenden 									//Paketen 

EthernetUDP Udp;                              // UDP starten


void setup(void) {
  
  for (int i = 0; i < 512; i++) {             // EEPROM löschen
    EEPROM.write(i, 0);                       //
  }                                           //	
  
  Serial.begin(baud);                         // Serielle Schnittstelle starten 
    
  Ethernet.begin(mac);                        // Internetchip initialisieren (W5100)  
  
  Serial.println("Hallo!");                   // Ausgabe auf das Terminal
  Serial.print("Die IP-Adresse lautet: ");    //
  Serial.println(Ethernet.localIP());         //
  
  
  server.begin();                             // Server starten
  Udp.begin(localPort);                       // UDP Server starten
}


/***********************************************************************************
*                                        Main programm                             *
***********************************************************************************/

void loop(void) {         // diese Schleife wird immer wieder durchlaufen
 
/***********************************************************************************
*                               Aufruf der Weboberfläche                           *
***********************************************************************************/
  
  EthernetClient client = server.available();         // auf Anfragen achten
  if (client) {
    boolean currentLineIsBlank = true;                // eine HTTP Anfrage endet mit 
                                                      //einer leeren Zeile
    while (client.connected()) {						 // TCP Verbindung aufgebaut
      if (client.available()) {
        char c = client.read();
 
        if (c == '\n' && currentLineIsBlank) { // wenn das Ende der Zeile empfangen 
                                               // wurde (neue Zeile Befehl erhalten)
                                               // und die Zeile leer ist, ist die 
                                               // HTTP anfrage beendet, 
                                               // nun kann geantwortet werden
          client.println("HTTP/1.1 200 OK");   // sende standart HTTP Antwort Kopf
          client.println("Content-Type: text/html");
          client.println();               // sende leeres Zeichen um Kopf zu beenden 
         
          // Nun folgt die Ausgabe einer normalen HTML Datei         
          client.println("  Templogger  ");

          client.write("UNIX Zeit ; Temperatur 
"); int g = 0; while ( g < 512 ) { // Kompletten Speicher abarbeiten unsigned long Epoch = 0; // Gespeicherte Daten aus dem EEPROM laden Epoch = (((unsigned long)EEPROM.read(g)) << 24); Epoch += (((unsigned long)EEPROM.read(g+1)) << 16); Epoch += (((unsigned long)EEPROM.read(g+2)) << 8); Epoch += ((unsigned long)EEPROM.read(g+3)); // int TReading = EEPROM.read(g+4) << 8; // Temperaturdaten aus TReading += EEPROM.read(g+5); // EEPROM laden // ###### binären Temperaturwert verarbeiten int SignBit, Tc_100, Whole, Fract; SignBit = TReading & 0x8000; // höchstwertiger bit gesetzt? if (SignBit) // (negativer Temperaturwert) { TReading = (TReading ^ 0xffff) + 1; // zweier Komplement } Tc_100 = (6 * TReading) + TReading / 4; // mal (100 * 0.0625) oder 6.25 //Tc_100 = (TReading*100/2); // Für DS18S20 verwenden Whole = Tc_100 / 100; // Ganzzahlen und Brüche trennen Fract = Tc_100 % 100; // // ###### Zeit ausgeben client.print((Epoch % 86400L) / 3600); // print the hour (86400 equals secs per day) client.print(':'); if ( ((Epoch % 3600) / 60) < 10 ) { // In the first 10 minutes of each hour, we'll want a leading '0' client.print('0'); } client.print((Epoch % 3600) / 60); // print the minute (3600 equals secs per minute) client.print(':'); if ( (Epoch % 60) < 10 ) { // In the first 10 seconds of each minute, we'll want a leading '0' client.print('0'); } client.println(Epoch %60); // print the second // ###### Temperatur ausgeben client.print("; "); if (SignBit) // negativer Wert? { client.print("-"); } client.print(Whole); client.print("."); if (Fract < 10) { client.print("0"); } client.print(Fract); g += 6; // EEPROM Addresszähler um 6 erhöhen } // Speicher abarbeiten ende break; } if (c == '\n') { // you're starting a new line currentLineIsBlank = true; } else if (c != '\r') { // you've gotten a character on the current line currentLineIsBlank = false; } } } delay(1); // Dem Webbrowser Zeit geben um die Daten zu erhalten client.stop(); // TCP Verbindung schliessen } /*********************************************************************************** * Speicherung der Temperatur * ***********************************************************************************/ unsigned long currentMillis = millis(); // Wartefunktion (während des if(currentMillis - previousMillis > interval) { // Wartens kann anderer Code previousMillis = currentMillis; // ausgeführt werden Serial.println(" Datenerfassung "); // Ausgabe an das Terminal int r = 0; // Alle Daten um einen Datensatz im EEPROM nach while (r < 6) { // "unten" schieben, damit an erster Stelle int f = 512; // wieder Platz vorhanden ist while (f > 0) { // (letzter Datensatz geht logischerweise verloren) EEPROM.write(f, EEPROM.read(f-1)); f--; } r++; } unsigned long timeReaded = gNTP(); // Zeit vom NTP Server holen (grösse: 4byte) int temperatureReaded =readTemperature();// Temperatur ermitteln (grösse: 2byte) EEPROM.write(0, timeReaded >> 24); // Daten in den EEPROM legen EEPROM.write(1, timeReaded >> 16); // erst Zeit EEPROM.write(2, timeReaded >> 8); EEPROM.write(3, timeReaded); EEPROM.write(4, temperatureReaded >> 8); // dann Temperatur EEPROM.write(5, temperatureReaded); } } /*********************************************************************************** * DS18B20 auslesen * ***********************************************************************************/ int readTemperature(void) { int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract; byte i; byte data[12]; ds.reset(); ds.write(0xCC); // alle one-wire geräte auswählen ds.write(0x44); // starte Temperaturkonvertierung delay(750); // warten bis der Sensor fertig ist ds.reset(); ds.write(0xCC); // alle one-wire geräte auswählen ds.write(0xBE); // Speicherinhalt des Sensors anfordern for(i=0;i<2;i++) { // Temperatur ist in den ersten beiden Bytes der Antwort data[i] = ds.read(); // BUS lesen } LowByte = data[0]; HighByte = data[1]; TReading = (HighByte << 8) + LowByte; return TReading; } /************************************************************************************ * UNIX Zeit anfordern * ***********************************************************************************/ unsigned long gNTP(void) { unsigned long epoch = 0; sendNTPpacket(timeServer); // sende NTP Paket zum Zeit Server delay(1000); if ( Udp.parsePacket() ) { // Paket empfangen Udp.read(packetBuffer,NTP_PACKET_SIZE); // Paket in den Puffer schreiben //Der Zeitstempel startet bei Byte 40 des erhaltenden Pakets und ist vier Bytes, // oder zwei Wörter, lang. Die beiden Wörter extrahieren: unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); // die vier Bytes in einen "long integer" schreiben // dies ist die NTP Zeit (Sekunden seit 1. Januar 1900): unsigned long secsSince1900 = highWord << 16 | lowWord; // NTP Zeit in UNIX Zeit convertieren Serial.print("Unix time = "); // Unix Zeit startet am 1. Januar 1970. In Sekunden sind das 2208988800: const unsigned long seventyYears = 2208988800UL; // 70 Jahre abziehen: epoch = secsSince1900 - seventyYears; Serial.println(epoch); } return epoch; } unsigned long sendNTPpacket(IPAddress& address) { memset(packetBuffer, 0, NTP_PACKET_SIZE); // alle Bytes im Puffer auf 0 setzten // Werte für eine NTP Anfrage initialisieren: packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; // Paket senden: Udp.beginPacket(address, 123); //NTP Anfragen gehen an Port 123 Udp.write(packetBuffer,NTP_PACKET_SIZE); Udp.endPacket(); }

Funktion

Die Stromversorgung läuft hier über den USB Anschluss für den UART zu Seriell Adapter. Wahlweise kann auch ein einfaches Wandnetzteil mit Hohlstecker verwendet werden. Nach einschalten des Temperaturloggers können über seine IP die Temperaturwerte angeschaut werden. Das sieht dann ungefähr so aus:

Über die Serielle Schnittstelle sendet der Temperaturlogger weitere Daten, wie z.B. die IP-Adresse. Diese Funktion ist hilfreich um Fehler zu entdecken oder auszuschließen.

Probleme

Leider bleiben noch einige Probleme. Das größte entsteht, sobald der Zeitserver aus welchem Grund auch immer nicht erreichbar ist. Dadurch würde die Zeit fehlen. Eine Lösung währe eine Funkuhr an den Mikrocontroller anzubinden. Ein Ausfall dieses Kanals ist unwahrscheinlich. Eine andere Möglichkeit währe einen Stromsparenden Mikrocontroller mit einer kleinen Batterie zu versorgen. Dieser würde dann die Zeit an den Hauptcontroller senden. Somit müsste man nur einmalig die Zeit einstellen.

Ein weiteres Problem ist der knappe Speicherplatz. Da der EEPROM nur 512 Speicherplätze á 1 Byte zur Verfügung stellt und pro Datensatz 6 Bytes benötigt werden (2 Bytes für die Temperatur und 4 Bytes für die Zeit), können maximal 85 Datensätze gespeichert werden. Um mehr Speicherplatz zu erhalten könnte man entweder einen größeren EEPROM oder eine SD Karte anbinden oder alle Daten an einen SQL-Server senden. Die letztere Methode setzt allerdings wieder einen Internetanschluss voraus.

Quellen

http://arduino.cc/en/Tutorial/UdpNtpClient
http://arduino.cc/en/Reference/EEPROM
http://arduino.cc/en/Reference/Ethernet
http://www.arduino.cc/playground/Learning/OneWire-DE
http://www.arduino.cc/playground/Code/WebServer


(c) Paul Szymanski