Programmierung mit Arduino (C++)
Für den ATTiny 13 gibt es auch die Möglichkeit, ihn mit der Arduino IDE zu programmieren. Dazu muss die Erweiterung MicroCore installiert werden, die die Arduino Systemaufrufe für den ATTiny 13 zurechtschrumpft.
Unter Datei -> Voreinstellungen findet sich der Eintrag "Zusätzliche Boardverwalter-URLs". Hier die Bezugsadresse
https://mcudude.github.io/MicroCore/package_MCUdude_MicroCore_index.json
eintragen. Unter Werkzeuge -> Board ->Bordverwalter anklicken. Im anschließenden Dialog sollte evtl. nach kurzer Wartezeit der Eintrag MicroCore zu finden sein. Diesen anklicken und installieren.
Aufgrund des geringen Speichers, sind nicht alle Arduino-Befehle verfügbar. Die Arduino-IDE erzeugt natürlich auch einen gewissen zusätzlichen Speicherbedarf (Overhead).
Auf der Projektseite des Entwicklers gibt es eine Beschreibung der zur Verfügung stehenden Arduino Befehle, ggf. mit Einschränkungen.
Anleitungen und Tutorials zu Arduino finden sich hier. Das Programm (in der Arduino-IDE "Sketch" genannt) kommt dann wie hier beschrieben auf das Experimentierboard.
Blinken mit Arduino-IDE
/** * Ein Arduino-Sketch zum Testen des ATTiny-Experimentierboards * Lässt die Leuchtdioden im Rhythmus eine, beide, keine aufleuchten. * * Die Konstanten OUTPUT, HIGH und LOW gehören zum Arduino Framework */ /** * LED1 an pin 3 (entspricht PB3 lt. MicroCore-Konvention) * LED2 an pin 4 (entpsricht PB4 lt. MicroCore-Konvention) */ #define LED1 3 #define LED2 4 /** * Die setup-Funktion wir beim Programmstart nur einmal aufgerufen */ void setup() { // Datenrichtungsregister für LEDs pinMode(LED1, OUTPUT); pinMode(LED2, OUTPUT); } /** * Die loop-Funktion wird in einer Endlosschleife wiederholt aufgerufen. */ void loop() { // LED1 einschalten digitalWrite(LED1,HIGH); delay(500); // LED2 einschalten digitalWrite(LED2,HIGH); delay(500); // beide aus digitalWrite(LED1,LOW); digitalWrite(LED2,LOW); delay(500); }
Arduinotutorials, -experimente und -projekte in den Naturwissenschaften
Die hier veröffentlichen Anleitungen (Tutorials) gehen von den einfachsten Grundlagen der Elektronik und des Aubaus von Experimentierschaltungen über einfache Programmierung bis hin zu komplexeren Programmen. Die Teile bauen beim Verständnis der Komponenten im Prinzip aufeinenader auf, sind aber in sich selbst abgeschlossen.
Grundlagen
1. Das Steckbrett (Breadboard)
2. Steckbrett: Schaltplan und Steckbrett-Aufbau
4. Arduino-Projekt: Blinkschaltung
Projekte und Experimente
5. Arduino-Projekt: Temperaturmessung mit analogem Temperatursensor
6. Experiment Verdunstungskälte
8. Arduino-Projekt: Glättung von Messreihen
9. Arduino-Projekt: Helligkeitsmessung mit Fotowiderstand
10. Arduino-Projekt: Temperaturmessung mit digitalem IC DS18B20
11. Arduino-Projekt: CO2-Messung mit dem Sensor MHZ19B
12. Arduino-Projekt: Luftdruck mit BMP280-Sensor messen
Link zu dieser Seite:
12. Arduino-Projekt: Luftdruck und Temperatur mit BMP280-Sensor messen
Die Hardware
Der Sensor BMP280 kann wegen seiner winzig kleinen SMD-Bauweise (Surface Mounted Device) nicht direkt an den Arduino angeschlossen werden.
Zum Glück finden sich im Internet sogenannte Breakout-Boards, das sind kleine Platinen, auf denen die Kleinteile mit arduinotauglichen Füßchen aufgelötet sind. Oft haben die Breakout-Boards auch Spannungsregler und Widerstände um die Arduinospannung an die Sensorspannung anzupassen.
Das ist beim BMP-280-Sensor auch notwendig, da er mit 3,3 Volt arbeitet. Beim Kauf also auf Arduinokompatibilität achten und auch darauf, dass das Breakout-Board die I2C-Schnittstelle des Sensor nach außen führt.
Breakout-Board für BMP280 mit I2C-Schnittstelle
Die I2C-Verbindung ist ein serieller Zweidrahtbus bestehend aus einer Datenleitung (SDA), welche in beiden Richtungen funktioniert und einer Synchronisationsleitung (SCL), die den Übertragungstakt vorgibt.
Die Verdrahtung kann wie folgt vorgenommen werden:
Und so könnte der Steckbrettaufbau aussehen:
Die beiden von den I2C-Leitungen zur positiven Versorgungsspannung geschalteten Widerstände von 10 kΩ dienen als sogenante "Pull-Up-Widerstände" mit dem Zweck, die Leitungen im unbelastetem Zustand positiv zu halten. Bei vielen Breakout-Boards sind sie schon mit bestückt und könnten hier entfallen - andererseits schadet eine Doppelbeschaltung auch nicht.
Die Software
Zwar gibt es für die I2C-Schnittstelle eine eingebaute Arduino-Library, aber die Umrechnung der Kalibrierungswerte für den BMP280 ist laut Datenblatt etwas aufwendig, so dass sich die Installation einer Library lohnt (Quelle: https://arduino-projekte.webnode.at/meine-libraries/luftdrucksensor-bmp280/).
Die Library ist wie üblich in das Library-Verzeichnis der Arduino-Installation zu kopieren, damit sie vom Compiler gefunden werden kann. Alternativ kann auch das Library-Verzeichnis unter "Dokumente/Arduino" verwendet werden.
/* * Luftdruck- und Temperaturmessung mit dem Sensor BMP280 */ // Für das Einlesen und die Kalibrierungsberechnung wird // die Library von Retian (https://arduino-projekte.webnode.at/meine-libraries/luftdrucksensor-bmp280/) // verwendet. Die entsprechenden Dateien dazu in das Arduino- // Libraryverzeichnis kopieren. Die Library verwendet intern // die Wire-Library von Arduino. Auf die entsprechende Verdrahtung achten! #include <MyBMP280.h> // Hier wird eine Instanz der Libraryklasse MyBMP280 definiert // und dazu die I2C-Adresse des Bausteins übergeben: standard- // mäßig laut Datenblatt 0x76 (Hexadezimal 76). MyBMP280 Bmp(0x76); // Initialisierungen void setup() { // Serielle Schnittstelle zum PC initialisieren Serial.begin(9600); // Verfügbarkeit und Verdrahtung prüfen if ( ! Bmp.isReady()) Serial.println("Error"); // Messgenauigkeit einstellen Bmp.setPressOversampling(BMP280_P_OVERSAMPLING_x2); // und initialisieren Bmp.init(); } // Messungen periodisch durchführen und Ergebnisse im seriellen // Monitor oder Plotter anzeigen void loop() { // Temperaturmessung float temperature = Bmp.readTemp(); Serial.print("Temperatur: "); Serial.print(temperature); Serial.println(" Grad Celsius"); // Luftdruckmessung float pressure = Bmp.readPress(); Serial.print("Luftdruck: "); Serial.print(pressure); Serial.println(" hPa"); // 1 Sekunde pausieren delay(1000); }
Jedes Gerät, das am I2C-Bus hängt, muss eine Adresse haben, unter der es erreichbar ist. Die Adresse wird als Byte-Wert angegeben und ist vom Gerät vorgegeben und aus dem Datenblatt abzulesen. Oft kann durch entsprechende Verdrahtung eines Pins am Sensor eine zweite Adresse gewählt werden, damit zwei baugleiche Sensoren über denselben Bus angesprochen werden können. Die erste Adresse des BMP280 ist 0x76 und wird gleich beim Erzeugen der Instanz mit übergeben (an den sogenannten "Konstruktor" der Klasse):
MyBMP280 Bmp(0x76);
Wenn die Funktion Bmp.isReady() einen als "falsch" eingestuften Wert zurückgibt, z. B. 0, dann ist der Sensor nicht erreichbar, da u. U. falsch verdrahtet, und es wird die Fehlermeldung "Error" ausgegeben:
// Verfügbarkeit und Verdrahtung prüfen if ( ! Bmp.isReady()) Serial.println("Error");
Screenshots wurden mit Fritzing erstellt, falls nicht anders angegeben (https://fritzing.org)
11. Arduino-Projekt: CO2-Messung mit dem Sensor MHZ19B
Sensor und Messprinzip
Der Sensor MHZ19B, erhältlich z. B. bei Reichelt oder Banggood, ist ein Sensor zur Messung des CO2-Gehalts der Luft und der Temperatur.
Quelle: www.banggood.com
Die CO2-Messung basiert auf dem sogenannten nichtdispersiven Infrarotprinzip, dabei wird ein Infrarotlichtstrahl durch eine mit der zu messenden Luft gefüllten Glasröhre geschickt. Ein spezieller Infrarotsensor misst am anderen Ende, wie viel Licht vom CO2 absorbiert wurde, indem die Differenz des einfallenden mit dem austretenden Licht gebildet wird.
NDIP-Messung
Der CO2-Gehalt der Luft wird in PPMs (Parts per Million) angegeben. 1 ppm entspricht bei Gasen 1 µL pro Liter Gas. Normale Luft hat etwa 400 ppm CO2-Gehalt, das entspricht 0,04% CO2-Anteil.
Der Sensor funktioniert mit Spannungen bis 5,5 V und besitzt 5V-kompatible Signalein- und -ausgänge, was wichtig für den Betrieb am Arduino ist.
Der Messwert wird entweder als pulsweitenmoduliertes Rechtecksignal ausgegeben, bei dem die Länge der positiven Halbwelle dem PPM-Wert entspricht oder als serieller Digitalwert über die im Sensor befindliche serielle Schnittstelle (UART = Universal Asynchronous Receiver Transmitter). Genauere Angaben finden sich im Datenblatt. Der Sensor misst entweder 0-2000ppm oder 0-5000ppm. Der Wertebereich kann über die serielle Schnittstelle eingestellt werden.
MHZ19B-Pinout
Im Datenblatt konnte kein Hinweis auf die Defaulteinstellung des Messbereichs gefunden werden, es scheint so, als ob verschiedene Typen des Sensors verkauft würden. Der eingestellte Messbereich kann über die UART abgefragt werden: mhtz19b_uart_get_range.ino
Die Hardware
Die Verdrahtung mit dem Arduino wird etwas erschwert, da die Anschlüsse des MHZ19B-Sensors nicht auf ein Steckbrett passen - die Verdrahtung erfolgt also "fliegend".
Es folgen zwei Verdrahtungsvarianten, die auch kombinierbar sind: einmal für die PWM-Messung und zweitens für die Messung über die UART (serielle Schnittstelle).
PWM-Messung (Pulse Width Modulation)
MatthiasDD - Eigenes Werk, basierend auf: Square wave.svg - CC BY-SA 3.0
Der Messwert ist das Verhältnis von t1/T ausgelesen und dann auf den Wertebereich des CO2-Sensors hochgerechnet. Im Datenblatt kann man die entsprechenden Bezugwerte finden, so wird z. B. bei einer Messbereichswahl von 0-2000ppm der 0ppm-Wert mit einer Zeitdauer von 2ms (Millisekunden) angegeben und der Wert 2000ppm entspricht einer Pulsdauer von 1002ms. Die Gesamtpulsbreite T hat eine Dauer von 1004ms.
Der Anschluss an den Arduino ist sehr einfach und könnte so aussehen:
So gestaltet sich die Verdrahtung mit dem Arduino bei der PWM-Messung:
UART-Messung von CO2 und Temperatur
Für die UART-Messung werden zwei beliebige Pins als RX/TX-Paar verwendet, im Beispiel Pin 2 und Pin 3.
Wichtig: Nicht die mit TXD und RXD gekennzeichneten Pins (0 und 1) verwenden! Sie gehören zur Hardware UART des Arduino und werden schon für die Programmierung über das USB-Kabel verwendet!
Wichtig: Die UART-Verbindungen müssen "gekreuzt" werden: es müssen also der Arduino-RX-Pin (im Beispiel Pin 2) mit dem Sensor-TX-Pin verbunden werden und der Arduino-TX-Pin mit dem Sensor-RX-Pin.
Die Software
Messung der CO2-Werte mit PWM
/** * CO2-Messung mit Sensor Typ MHZ19B * Messwerterfassung durch PWM-Signal */ // Der Sensor hängt an Pin 7 const int pwmpin = 7; // Der eingestellte Messbereich (0-5000ppm) const int range = 5000; // Die setup()-Funktion void setup() { // PWM-Pin auf Eingang setzen pinMode(pwmpin, INPUT); // Serielle Übertragung über USB initialisieren Serial.begin(9600); } // Die loop()-Funktion void loop() { // Messung der PWM-Länge mittels einer eigenen Funktion int ppm_pwm = readCO2PWM(); // Ausgabe der Werte über die serielle USB-Verbindung Serial.print("PPM PWM: "); Serial.println(ppm_pwm); // Messungen alle 3 Sekundn delay(3000); } // Die Messung der PWM-Länge erfolgt in einer eigenen // Funktion readCO2PWM(), was die loop()-Schleife etwas "aufgeräumter" // erscheinen lässt. Die Funktion gibt eine Ganzzahl zurück (int). int readCO2PWM() { // Es werden die für die Umrechnung der Zeitdauer auf // die PPM-Werte benötigten Variablen definiert. // Da es sich bei th um große Werte handeln kann - die verwendete // Arduino-Funktion gibt Mikrosekunden zurück - wird diese Variable // als vorzeichenlose (unsigned) große Ganzzahl (long) definiert. unsigned long th; int ppm_pwm = 0; float pulsepercent; // Alles, was in der do ... while-Schleife steht, wird // solange ausgeführt, bis der Ausdruck nach while, hier // th == 0 als zutreffend (wahr) erkannt wird. // Da die Arduino-Funktion pulseIn() 0 zurückgibt, solange // sie am Messen ist, dient die Schleife dazu, auf den // Messwert zu warten. do { // pulseIn gibt die Dauer des am Pin (pwmpin) anliegenden // Signals in Mikrosekunden an. Die maximale Signallänge ist // 1004ms. Der Timeoutwert der pulseIn-Funktion muss also // mindestens 1004000µs betragen. Für ungünstige Fälle wird // sicherheitshalber ein größerer Wert von 2500000µs gewählt. // Die Ausgabe der pulseIn()-Funktion wird durch 1000 geteilt // und ergibt so für th die Signallänge in Millisekunden (ms). th = pulseIn(pwmpin, HIGH, 2500000) / 1000; // Pulslänge in Prozent (%) float pulsepercent = th / 1004.0; // PPM-Werte bei gegebenem Range ppm_pwm = range * pulsepercent; } while (th == 0); // Der gemessene Wert wird an die loop()-Funktion zurückgegeben, // wo er dann ausgegeben wird. return ppm_pwm; }
Folgendes ist neu:
int ppm_pwm = readCO2PWM();
Neben den beiden Standard-Arduino-Funktionen setup() und loop() wird hier eine eigene neue Funktion verwendet: readCO2PWM(). Die Funktion gibt im Unterschied zu den anderen beiden Funktionen den Typ int zurück.
unsigned long th;
Der Typ "unsigned long" kann sehr große Ganzzahlen fassen: 0 - 4.294.967.295, wobei "unsigned" bedeutet, dass keine negativen Zahlen möglich sind.
do { ... } while (Audruck);
Die do ... while-Schleife läuft, solange der Ausdruck gültig (wahr) ist. Da die Prüfung des Ausdrucks am Ende der Schleife erfolgt ("fußgesteuert") wird der Inhalt der Schleife auf alle Fälle mindestens einmal ausgeführt.
Messung der CO2-Werte mit UART
Die Messung über die UART erfolgt über standardisierte Befehle (commands) von 9 Byte Länge (s. Datenblatt) und ebenso langen Antworten (responses).
/** * CO2-Messung mit Sensor Typ MHZ19B * Messdatenerfassung über UART (serielle Schnittstelle) */ // Da die Hardware-UART des Arduino vom USB-Kabel belegt // und über die Funktionen der Serial-Klasse schon // verwendet werden, braucht es die SoftwareSerial-Klasse // (gehört zu den Arduino-Standardklassen) mit deren Hilfe // beliebige Pins als RX/TX-Verbindungen verwendet werden // können (mit Ausname von Pin 0 und Pin 1) #include <SoftwareSerial.h> // Hier wird eine Instanz der Klasse mit den Pins 2 (RX) und 3 (TX) // initialisiert SoftwareSerial co2Serial(2, 3); // define MH-Z19 RX TX // In der setup()-Funktion werden sowohl die Hardware- // als auch die Software UART initialisiert void setup() { Serial.begin(9600); co2Serial.begin(9600); } // Die loop() Funktion liest mit Hilfe der eigenen // Funktion readSensor() die // Sensorwerte aus und schreibt sie über die serielle // USB-Verbindung auf den angeschlossenen Computer. void loop() { int ppm, temperature = 0; readSensor(&ppm, &temperature); Serial.print("PPM: "); Serial.print(ppm); Serial.print(" Temperature: "); Serial.println(temperature); delay(5000); } // Die Funktion liest die CO2-Werte über die UART des // Sensors ein und schreibt die ermittelten Werte mit // Hilfe der übergebenen Pointer in die Variablen ppm // und temperature. void readSensor(int *ppm, int *temperature){ // Die Befehlskette zum Einlesen des PPM-Wertes laut Datenblatt byte cmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79}; // Speicherplatzreservierung von 9 Byte für die Antwort des Sensors. // Alles Befehle und Antworten des Sensors haben eine Länge von // 9 Byte, wobei das letzte Byte eine Prüfsumme zur Kontrolle // der Übermittlung darstellt. byte response[9]; // Befehl zum Auslesen schreiben co2Serial.write(cmd, 9); // Zuerst den Eingangsbuffer löschen (mit 0 füllen) und // danach in einer while-Schleife darauf warten, bis // die Funktion co2Serial.available() einen Wert ungleich 0 // zurückgibt. memset(response, 0, 9); while (co2Serial.available() == 0) { delay(1000); } // Die Antwort wird in den Speicher eingelesen. co2Serial.readBytes(response, 9); // Die Prüfsumme mit Hilfe einer eigenen Funk- // tion errechnen, um zu klären, ob die // Übertragung fehlerfrei abgelaufen ist. byte check = getCheckSum(response); if (response[8] != check) { Serial.println("Fehler in der Übertragung!"); return; } // PPM-Wert errechnen, sie finden sind // im 3. und 4. Byte der Antwort im Format // HIGH-Byte und LOW-Byte und müssen über die // folgende Formel zu einem Word (int) verkettet // werden. *ppm = 256 * (int)response[2] + response[3]; // Temperaturwert wird als 5. Byte der Response // übermittelt (im Datenblatt nicht angegeben). // Damit auch negative Temperaturen übertragen // werden können, wurde der Wert 40 dazuaddiert, // der jetzt wieder entfernt werden muss. *temperature = response[4] - 40; } // Die Funktion errechnet eine Prüfsumme über die // durch einen Zeiger übergebene Befehls- oder // Antwortkette. Der Algorithmus zur // Prüfsummenberechnung findet sich im // Datenblatt. byte getCheckSum(byte *packet) { byte i; byte checksum = 0; for (i = 1; i < 8; i++) { checksum += packet[i]; } checksum = 0xff - checksum; checksum += 1; return checksum; }
Was ist neu:
void readSensor(int *ppm, int *temperature) { ... }
Die Definition der eigenen Funktion readSensor() ist wie folgt zu verstehen: die Funktion gibt keinen Wert zurück (void) und erhält zwei Zeiger (Pointer) auf int-Werte, durch das Sternchensymbol angedeutet. Der Grund für diese Vorgangsweise ist die Tatsache, dass Funktionen immer nur einen einzigen Wert zurückgeben können, wir hier aber zwei Werte haben wollen. Deswegen erhält die Funktion die Adressen von zwei Variablen, in die sie die Werte schreiben kann. Diese Werte können dann von der aufrufenden Funktion ausgelesen und weiterverwendet werden. Beim Aufruf der readSensor()-Funktion müssen die Adressen der beiden Variablen angegeben werden, was durch das vorangestellt "&" gekennzeichnet wird:
readSensor(&ppm, &temperature);
Beim Beschreiben der Variablen ist dann ebenfalls die Pointer-Schreibweise zu verwenden, damit nicht die Adressen überschrieben werden:
*temperature = response[4] - 40;
Die Funktion
memset(response, 0, 9);
ist fest in Arduino eingebaut. Die Funktionen
co2Serial.available(), co2Serial.write() und co2Serial.readBytes()
sind in der eingebunden SoftwareSerial-Klasse verfügbar (bzw. deren Instanz co2Serial).
Weiterführendes
CO2-Ampel selber bauen (Robert Helling)
Screenshots wurden mit Fritzing erstellt, falls nicht anders angegeben (https://fritzing.org)
10. Arduino-Projekt: Temperaturmessung mit digitalem IC DS18B20
Der digitale Temperatursensor DS18B20 ist ein IC (Integrated Circuit), das die Temperaturwerte über einen seriellen Datenbus an den Arduino weitergibt. Laut Datenblatt arbeitet der Sensor in einem Temperaturbereich von -55°C bis +125°C.
Durch die serielle digitale Datenleitung kann der Sensor auch über eine relativ lange Leitung (ein Meter und mehr) ohne Messwertverfälschung angeschlossen werden.
Es gibt im Internet verschiedene "vergossene" Varianten mit Verbindungskabel, z. B. hier: https://www.az-delivery.de:
Quelle: https://www.az-delivery.de
Hardware
Die Verdrahtung des Sensors ist sehr einfach:
Für die Funktion wird der Widerstand R1 (Pullup-Widerstand genannt, weil er die Spannung bei fehlendem Signal auf die Versorgungsspannung hochzieht) benötigt, was im Datenblatt so nicht gleich ersichtlich ist.
Hier der Steckbrettaufbau:
Und so eine reale Verdrahtung:
Programmierung
Der serielle Datenbus (1-Wire-Bus) und das vom Sensor verwendete Protokoll erfordern erheblichen Programmieraufwand, zum Glück kann die Arduino-IDE um externe Zusatzmodule (Libraries) erweitert werden und für den Sensor gibt es eine solche. Es werden folgende Libraries benötigt:
/** * Temperaturmessung mit dem Sensor D18B20 */ // Die benötigten Libraries werden eingebunden #include <OneWire.h> #include <DallasTemperature.h> // Definition des Pin, an dem der Sensor hängt #define SENSOR 9 // Jetzt wird ein OneWire-Instanz mit dem Namen "wire" erzeugt. // Das ist eine Variable, die alle Funktionen der Klasse // OneWire enthält, wie sie in der OneWire-Library definiert ist. // Die Instanz erhält gleich die Pinnumber mitgeteilt an der // der Sensor hängt. Das ist die One-Wire-Bus-Verbindung. OneWire wire(SENSOR); // Jezt wird eine Instanz der Klasse DallasTemperature // erzeugt, wie sie in der gleichnamigen Library definiert // ist. Die Instanz heißt "sensors" und bekommt bei der // Erzeugung eine Referenz (Zeiger) auf die wire-Variable. // Dass es sich um eine Referenz handelt, wird durch das // vorangestellte "&" deutlich gemacht. DallasTemperature sensors(&wire); // Die setup()-Funktion void setup(void) { // Serielle Verbindung initialisieren Serial.begin(9600); // Die Library initialisieren sensors.begin(); } // In der loop()-Funktion werden die Temperaturwerte eingelesen und // über die serielle Schnittstelle ausgegeben. void loop(void) { // Die Sensoren am Bus (es sind auch mehrere möglich!) werden // angewiesen die Temperaturwerte zu eruieren sensors.requestTemperatures(); // Send the command to get temperatures // Nun werden die Temperaturwerte in Grad Celsius vom ersten (und // einzigen) Sensor geholt, der den Index 0 hat. float temperature = sensors.getTempCByIndex(0); // Hier wird geprüft, ob eine sinnvolle Temperatur zurückgegeben // wird oder der Wert -127 (der in der DallasTemperature-Library // als DEVICE_DISCONNECTED_C definiert ist). if(temperature != DEVICE_DISCONNECTED_C) { // anzeige der Temperatur Serial.print("Grad Celsius: "); Serial.println(temperature); } else { // Ausgabe einer Fehlermeldung Serial.println("Error: Die Temperatur kann nicht gelesen werden!"); } }
Einige Erläuterungen:
// Die benötigten Libraries werden eingebunden #include <OneWire.h> #include <DallasTemperature.h>
Damit der Arduino-Sketch Zugriff auf die Funktionen der Libraries hat, müssen diese ins Programm eingebunden werden, was diese beiden Anweisungen bewirken.
// Definition des Pin, an dem der Sensor hängt #define SENSOR 9
Das ist ein Variante zu Definition einer Konstante und wirkt im Wesentlichen wie eine const-Definition.
OneWire wire(SENSOR);
In C++ kann man Objekte definieren, die verschiedene Variablen, Konstanten und Funktionen beinhalten. Wir haben das bei der Klasse "Serial" gesehen. Im Gegensatz zur Serial-Klasse wird bei der OneWire-Klasse aber nicht direkt auf die Funktion in der Klasse zugegriffen, sondern es wird zuerst eine Instanz (Objekt) der Klasse mit dem Namen "wire" definiert. Auf diese Weise könnten auch mehrere unabhängige Instanzen der Klasse "OneWire" definiert werden, z. B. wireOne, wireTwo, was notwendig wäre, wenn mehrere Sensoren am Arduino hingen.
Die Klasseninstanz "wire" erhält die Pinnummer SENSOR (Pin 9) gleich bei der Erzeugung mitgeteilt.
DallasTemperature sensors(&wire);
Genauso ist "sensors" eine Instanz der Klasse "DallasTemperature", die in der gleichnamigen Library definiert ist. Die Instanz "sensors" erhält bei der Erzeugung ebenfalls gleich einen Wert übergeben, der allerdings durch das vorangestellte & etwas Besonderes ist, nämlich eine Referenz oder Zeiger auf eine Klasse. Im speziellen Fall eine Referenz auf die Instanz von "wire".
Referenzen sind nicht die Objekte selber, sondern nur Verweise darauf, ähnlich wie Adressen oder Internet-Links. Die Referenz spart Speicherplatz, da das referenzierte Objekt (hier die Instanz "wire") nur einmal mit allem Drum und Dran im Speicher existiert und die Funktionen der Instanz "sensors" trotzdem darauf zugreifen können.
Kniffliges
1. Modifizieren Sie die Hard- und Software so, dass ein zweiter Sensor desselben Typs angeschlossen werden kann.
2. Kombinieren Sie einen analogen Sensor des Typs TMP36 wie hier beschrieben und vergleichen Sie die Messwerte.
Screenshots wurden mit Fritzing erstellt, falls nicht anders angegeben (https://fritzing.org)
9. Arduino-Projekt: Helligkeitsmessung mit Fotowiderstand
Ein Fotowiderstand, auch LDR - Light Dependend Resistor - genannt, ändert seinen Widerstandswert mit dem einfallenden Licht: Je mehr Licht, desto geringer ist der Widerstand.
Hardware
Wird ein Fotowiderstand wie in der vorgeschlagenen Schaltung unten als Spannungsteiler geschaltet, ist die gemessene Spannung am Teiler (U2) umso größer, je mehr Licht auf den Widerstand fällt, d. h. je geringer sein Wert ist (vgl. die Formel):
Der Steckbrettaufbau kann wie folgt aussehen:
Programmierung
Das folgende Programm misst die Rohwerte der Spannung an A1, d. h. Werte im Bereich von 0 - 1023, denn der Arduino Analog-Digital-Wandler, der für die Messung verwendet wird, hat eine Auflösung von 10 Bit in der Standardeinstellung. Wird als Referenzspannung die Versorgungsspannung von 5V verwendet entspricht das einem Wertebereich von 0V - 5V.
Wir müssen uns mit den Rohwerten begnügen, da in den seltesten Fällen ein Lichtmessgerät (Luxmeter o. Ä.) zum Eichen zur Verfügung steht.
/** * Helligkeitsmessung mit einem Fotowiderstand */ // Pinbezeichnung des analogen Messeingang const byte lightSensor = A1; // In der setup()-Funktion wird die serielle // Schnittstelle zum Anzeigen der Werte initialisiert // und der Pin "lightSensor" auf INPUT geschaltet (braucht es // eigentlich nicht, denn nach dem Reset sind alle // Pins auf INPUT. void setup() { Serial.begin(9600); pinMode(lightSensor,INPUT); } // Im loop() wird die Spannung am Pin "sensor" gemessen // und in eine Tempteraturangabe in °C umgerechnet und // über die serielle Anzeige (USB-Kabel übernimmt diese // Funktion) ausgegeben. void loop() { // Messwert als Rohwert im Bereich 0 - 1023 // einlesen int light = analogRead(lightSensor); // Rohwerte ausgeben Serial.print("Helligkeit: "); Serial.println(light); // und etwas warten für die nächste Messung delay(1000); }
Kniffliges
1. Verändern Sie die Schaltung so, dass die Messlogik umgekehrt funktioniert, d. h. bei weniger Licht steigt der gemessene Wert.
2. Sie können die Schaltung in Kombination mit der für die Albedomessung vorgeschlagenen Temperaturmessung verwendung um die Bestrahlungsleistung der Heizquelle (Lampe) in beiden Versuchen konstant zu halten.
Screenshots wurden mit Fritzing erstellt, falls nicht anders angegeben (https://fritzing.org)
8. Arduino-Projekt: Glättung von Messreihen
Im "5. Arduino-Projekt: Temperaturmessung mit analogem Temperatursensor" haben Sie vielleicht gemerkt, dass durch die Messfehler starke Ausschläge im seriellen Plot oder Schwankungen in den Daten aufgetreten sind.
Ohne den Aufbau der Schaltung zu ändern kann das programmtechnisch durch eine größere Samplerate (Messungen pro Sekunde) und Durchschnittsbildung der Messwerte aufgefangen werden.
/** * Temperaturmessung mit TMP36 und Durchschnittsbildung */ // Pinbezeichnung des analogen Messeingang const byte sensor = A0; // Anzahl der Summanden, Zähler und summe const int sampleTime = 1000; const int num = 100; int counter = 0; float sum = 0.0; // In der setup()-Funktion wird die serielle // Schnittstell zum Anzeigen der Werte initialisiert // und der Pin "sensor" auf INPUT geschaltet (braucht es // eigentlich nicht, denn nach dem Reset sind alle // Pins auf INPUT. void setup() { Serial.begin(9600); pinMode(sensor,INPUT); } // Im loop() wird die Spannung am Pin "sensor" gemessen // und in eine Tempteraturangabe in °C umgerechnet und // über die serielle Anzeige (USB-Kabel übernimmt diese // Funktion) ausgegeben. void loop() { float temperature; float avg; // Anzahl der Summanden schon erreicht? if (counter >= num) { // durchschnitt errechnen avg = sum / num; // Ausgabe der Werte über die serielle Schnittstelle Serial.print("Grad Celsius: "); Serial.println(avg); // Zähler und summe zurücksetzen counter = 0; sum = 0.0; } // sensorValue enthält den Rohwert der gemessenen // Spannung. Der Arduino kann in der Standard- // konfiguration Spannungen mit einer Auflösung // von 10 Bit lesen. Damit lassen sich Werte von // 0 - 1023, also 1024 Werte abbilden. // Als Referenzspannung wird in der // Standardkonfiguration dabei die Betriebsspannung // von 5V verwendet. Man erhält also Rohwerte // zwischen 0 (0V) und 1024 (5V). int sensorValue = analogRead(sensor); // Umrechnung des Rohwertes in Volt (s. oben) float volt = (sensorValue / 1024.0) * 5.0; // Umrechnung der Spannung in Temperatur. Die // verwendeten Werte ergeben sich aus dem Datenblatt // des Sensors. temperature = (volt - 0.5) * 100; // werte aufsummieren sum += temperature; // counter erhöhen counter++; // und etwas warten für die nächste Messung delay(sampleTime / num); }
Einige Erläuterungen:
// Anzahl der Summanden, Zähler und summe const int sampleTime = 1000; const int num = 100; int counter = 0; float sum = 0.0;
sampleTime : die Konstante ist die Zeitdauer zwischen zwei Messausgaben in Millisekunden
num : Anzahl der Messungen pro Sampletime. Die Werte bedeuten, dass alle 10 ms (1000 ms / 100) eine Messung durchgeführt und 100 Messungen aufaddiert und als Durchschnitt ausgegeben werden
counter : zählt von 0 - 99, also 100 Messungen
sum : die Summe der Messungen, sie wird nach 100 Messungen durch 100 dividiert. Die Variable muss float sein und wird deshalb mit 0,0 initialisiert.
float temperature; float avg;
temperature : enthält die aus den Spannungen am Sensor errechnete temperatur
avg : Durchschnittswert der Messungen, wird alle 1000 ms ausgegeben
Achtung: die beiden Variablen sind innerhalb der loop()-Funktion definiert und können nur dort verwendet werden und sie sind nicht initialisiert, haben also keinen Anfangswert. Das ist deshalb nicht notwendig, da die Variablen weiter unten in der loop()-Funktion einen definierten Wert erhalten.
// Anzahl der Summanden schon erreicht? if (counter >= num) { // durchschnitt errechnen avg = sum / num; // Ausgabe der Werte über die serielle Schnittstelle Serial.print("Grad Celsius: "); Serial.println(avg); // Zähler und summe zurücksetzen counter = 0; sum = 0.0; }
if (Ausdruck) { Anweisungen; ... } : wenn der Ausdruck "wahr" ist, werden alle Anweisungen zwischen den geschwungenen Klammer ausgeführt, sonst übersprungen
counter >= num : gibt "wahr" zurück, wenn der counter größer als oder gleich groß wie num ist
Konkret heißt das, dass die in den geschwungenen Klammern stehende Durchschnitssberechnung und die serielle Ausgabe 100 mal übersprungen werden, dann einmal gemacht, wieder 100 mal übersprungen usw. Wichtig: das Zurücksetzen des Zählers und Durchschnitts (counter, sum) darf nicht vergessen werden.
// werte aufsummieren sum += temperature; // counter erhöhen counter++;
sum += temperature : ist die kurze Form für sum = sum + temperature
counter++ : ist die kurze Form für counter = counter + 1, counter wir bei jedem Durchlauf um eins erhöht
Achtung: die Ausdrücke sind nicht als mathematische Formeln zu lesen, sonst würden sie im ersten Beispiel nur für einen speziellen Fall und im zweiten Beispiel überhaupt nie zutreffen. Das Programm macht vielmehr Folgendes: der rechts vom "ist gleich" (=) stehende Teil wird berechnet und danach in der Variablen links abgespeichert, wobei der alte Inhalt überschrieben wird. Beispiel: counter hat den Wert 4, der rechte Teil ist also 4 +1 ergibt 5, jetzt wird counter = 5 gesetzt, der alte Wert (4) ist verloren.
Kniffliges
1. Erweitere die Temperaturmessschaltung mit Hilfe der if-Anweisung und zusätzlicher Hardware so, das das Überschreiten oder Unterschreiten einer oderer mehrer Temperaturgrenzwerte mit Hilfe einer oder mehrer Leds signalisiert wird.
2. Erweitere die oben angeführte Schaltung so, dass eine angeschlossene Led mit zunehmender Temperatur schneller blinkt.
7. Experiment Albedo
Als Albedo wird das Maß für das Rückstrahlvermögen von Oberflächen beschrieben. Es spielt u. a. in der Glaziologie (Gletscherkunde) eine wichtige Rolle. Helle Oberflächen haben eine größere Albedo als dunkle Oberflächen. Das kann mit einem Experiment nachgewiesen werden, das sich des in "5. Arduino-Projekt: Temperaturmessung mit analogem Temperatursensor" vorgestellten Aufbaus und Programms bedient.
1. Platziere Arduino und Steckbrett mit Sensor auf weißem Grund und halte möglichst nahe eine Wärmequelle, z. B. Glüh- oder Halogenlampe und halte den Temperaturverlauf mit dem seriellen Monitor fest und kopiere zum Schluss die Daten in ein Tabellenkalkulationsarbeitsblatt.
2. Wiederhole die Messung mit einem schwarzen Untergrund. Idealerweise sollte auch das weiße Steckbrett bis auf eine Sensoröffnung mit schwarzem Papier abgedeckt werden. Zuvor allerdings muss die gesamte Schaltung samt Steckbrett wieder auf die Zimmertemperatur zurückgegangen sein.
3. Die Messreihen können nun mit Hilfe einer Tabellenkalkulation miteinander verglichen und als Liniengrafiken angezeigt werden.
6. Experiment Verdunstungskälte
Bei der Verdunstung von Flüssigkeiten (z. B. Wasser) entsteht Kälte. Das kann mit der in "5. Arduino-Projekt: Temperaturmessung mit analogem Temperatursensor" vorgestellten Schaltung experimentell überprüft werden.
1. Ein Wattebausch wird mit Alkohol getränkt, da Alkohol bei Zimmertemperatur viel schneller als z. B. Wasser verdunstet.
2. Den Wattebausch mit einem Stück Klarsichtfolie als Isolierung an den Temperatursensor der Schaltung geben und den seriellen Plotter oder den seriellen Monitor starten. Nicht vergessen, die Klarsichtfolie oben zu öffnen, damit die Verdunstung schneller vor sich geht.
Jetzt heißt es ein bisschen Geduld aufzubringen und die Temperaturverläufe zu beobachten.
Nach einiger Zeit (10 - 20 Minuten) zeigen sich um 2 - 3 Grad niedrigere Temperaturen:
5. Arduino-Projekt: Temperaturmessung mit analogem Temperatursensor
Der Sensor
Für dieses Projekt wird der dem Arduino-Kit beiligende Temperatursensor oder ein ähnlicher verwendet:
TMP36 - Temperatursensor
Der Sensor kann laut Datenblatt mit einer Spannung bis 7V betrieben werden und liefert am Ausgang eine Spannung von 100 mV bis 2000 mV:
Bei 25° C sind das 750 mV. Der Sensor liefert 10mV/1°C und hat einen Temperaturbereich von -40° C bis +125° C:
Das bedeutet, dass 0° C ein Spannungswert von 750mV - 25 * 10mV = 500mV anliegen.
Pin-Out des TMP36
Die Hardware
Der Anschluss des Temperatursensor TMP36 gestaltet sich sehr einfach, es wird der Ausgang des Sensors mit dem analogen Eingang A0 des Arduino verbunden:
Und so kann der Aufbau mit dem Steckbrett aussehen:
So könnte der Aufbau in echt aussehen:
Programmierung
/** * Temperaturmessung mit TMP36 */ // Pinbezeichnung des analogen Messeingang const byte sensor = A0; // In der setup()-Funktion wird die serielle // Schnittstell zum Anzeigen der Werte initialisiert // und der Pin "sensor" auf INPUT geschaltet (braucht es // eigentlich nicht, denn nach dem Reset sind alle // Pins auf INPUT. void setup() { Serial.begin(9600); pinMode(sensor,INPUT); } // Im loop() wird die Spannung am Pin "sensor" gemessen // und in eine Tempteraturangabe in °C umgerechnet und // über die serielle Anzeige (USB-Kabel übernimmt diese // Funktion) ausgegeben. void loop() { // sensorValue enthält den Rohwert der gemessenen // Spannung. Der Arduino kann in der Standard- // konfiguration Spannungen mit einer Auflösung // von 10 Bit lesen. Damit lassen sich Werte von // 0 - 1023, also 1024 Werte abbilden. // Als Referenzspannung wird in der // Standardkonfiguration dabei die Betriebsspannung // von 5V verwendet. Man erhält also Rohwerte // zwischen 0 (0V) und 1024 (5V). int sensorValue = analogRead(sensor); // Umrechnung des Rohwertes in Volt (s. oben) float volt = (sensorValue / 1024.0) * 5.0; // Umrechnung der Spannung in Temperatur. Die // verwendeten Werte ergeben sich aus dem Datenblatt // des Sensors. float temperature = (volt - 0.5) * 100; // Ausgabe der Werte über die serielle Schnittstelle Serial.print("Grad Celsius: "); Serial.println(temperature); // und etwas warten für die nächste Messung delay(1000); }
Was ist neu?
Serial.begin(9600);
Der Arduino bietet die Möglichkeit Werte und Texte über das USB-Kabel, das im Betrieb als serielle Datenverbindung zum PC fungiert, auszugeben. Die Werte werden mit dem "seriellen Monitor" angezeigt, der über das Symbol oben rechts in der Arduino-IDE geöffnet wird:
Serial : stellt den Bezug zu einer Klasse (oder einem Objekt) mit dem Namen "Serial" her, das fest in Arduino eingebaut ist. Die Klasse "Serial" besitzt eine Funktion "begin()".
Serial.begin() : ruft diese Funktion auf. Der Punkt (.) trennt den Klassen- vom Funktionsnamen.
9600 : gibt die sogenannte Baud-Rate an, das ist die Übertragungsgeschweindigkeit in Bits/s an. Der in der Serial.begin()-Funktion angegebene Wert muss mit den Einstellungen des seriellen Monitors oder des seriellen Plotters übereinstimmen.
int sensorValue = analogRead(sensor);
analogRead() : die Pins A0 bis A5 können Analoge Spannungswerte mit Hilfe dieser Funktion einlesen, standardmäßig in 10-Bit-Auflösung (0-1023)
int sensorValue : eine Variable vom Typ "integer" (ganzzahlig), die Werte von -32,768 to 32,767 erfassen (ein byte-Typ wäre hier zu klein gewählt)
float volt = (sensorValue / 1024.0) * 5.0;
float volt : definiert eine Varibale mit Namen "volt" vom Typ "float", hier können sogenannte Fließkommazahlen abgespeichert werden, das sind sehr große und sehr kleine Zahlen, auch mit Nachkommastellen
1024.0 und 5.0 : der Punkt ist ein (angelsächsisches) Komma und wird hier angegeben, damit der Compiler, der den Arduino-Sketch in Maschinensprache übersetzt, gezwungen wird, das Ergebnis mit Nachkommastellen zu berechnen (da sensorValue ja eine Ganzzahl ist)
Serial.print("Grad Celsius: "); Serial.println(temperature);
Serial.print : gibt die Zeichenkette "Grad Celsius: " über die serielle Verbindung am PC aus. Die Anführungszeichen markieren eine Zeichenkette, sonst würde der Compiler davon ausgehen, das "Grad" oder "Celsius" Variablen sind
Serial.println : dasselbe wie Serial.print, hängt aber zusätzlich einen Zeilenumbruch (ln) an, so dass die nächste Ausgabe in eine neue Zeile geschrieben wird. Hier wird der Inhalt der Variable "temperature" ausgegeben
Kniffliges
1. Den Aufbau und das Programm so erweitern, das bei jeder Messwertausgabe ein Led kurz blinkt.
2. Die Werte über den seriellen Plotter unter "Werkzeuge" im Arduino-Menu plotten lassen.
Screenshots wurden mit Fritzing erstellt, falls nicht anders angegeben (https://fritzing.org)