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