Inleiding
In de laatste ‘Hoe doe je dat?’ over de Arduino hebben we een module gebouwd voor het weergeven van de temperatuur en luchtvochtigheid. Deze wordt nu uitgebreid om gegevens op te kunnen slaan op een SD-kaart. Bovendien wordt voor elke meting het desbetreffende tijdstip geregistreerd. Nu worden daar een shield voor een SD-kaart en een realtimeklok aan toegevoegd. Het display kan op het Grove-shield blijven zitten. Als de gegevenslogger zonder display gebruikt gaat worden, kan deze eenvoudig worden verwijderd.
Het SD-shield
Omschrijving en inbouwen
De gegevens van de gegevenslogger worden opgeslagen op een SD-, SDHC- of MicroSD-kaart (in het laatste geval met een adapter). Hiervoor is een shield beschikbaar dat tussen de Arduino en het base-shield kan worden geplaatst; het base-shield wordt in verband met de stekker aan de bovenkant bevestigd.
De bibliotheken, die nodig zijn voor het SD-shield, zijn al aanwezig in Arduino Studio en hoeven dus niet afzonderlijk geïnstalleerd te worden. Meer informatie is te vinden in de Arduino-documentatie:
https://www.arduino.cc/en/Reference/SD
Gegevens schrijven: Hallo wereld
De eerste gegevens worden met het volgende programma naar de kaart geschreven:
#include <SPI.h>
#include <SD.h>
int SELECTED_CHIP = 4;
int count = 0;
void setup() {
Serial.begin(9600);
if (SD.begin(SELECTED_CHIP)) {
Serial.println(“SD-kaart geïnitialiseerd.”);
} else {
Serial.println(“SD-kaart kan niet worden geladen of ontbreekt”);
}
}
void loop() {
String line = String(“Hallo wereld #”) + String(count);
writeToSD(line);
count = count + 1;
delay(1000);
}
void writeToSD(String line) {
File dataFile = SD.open(“test.csv”, FILE_WRITE);
if (dataFile) {
dataFile.println(line); // Naar de SD-kaart schrijven
dataFile.close(); // Bestand sluiten
Serial.println(line);
} else {
Serial.println(“Fout bij openen van gegevensbestand”);
}
}
Vervolgens worden er twee variabelen gedefinieerd: Met SELECTED_CHIP wordt aangegeven welke hardware wordt gebruikt bij het SD-shield. De waarde wordt opgegeven door de fabrikant van het SD-shield. De variabele count dient er alleen toe om de lopende schrijfprocessen mee te tellen en het cijfer ook naar de kaart te schrijven.
In setup() wordt eerst een seriële verbinding gemaakt voor het debuggen; vervolgens wordt een verbinding met de SD-kaart gemaakt. De methode SD.begin() geeft aan of de verbinding succesvol kon worden gemaakt.
In loop() wordt de regel opgebouwd die aansluitend op writeToSD() naar de SD-kaart wordt geschreven. Daarvoor gebruiken we de stringklasse waarmee een flexibele werking met tekenreeksen mogelijk is. De operator + verbindt twee strings; de variabele count wordt daarom met String(count) eerst omgezet naar een string. De waarde van count wordt nog met één verhoogd en tot slot wordt er één seconde gewacht.
Er wordt geschreven via writeToSD(). Daar wordt vervolgens met SD.open() een bestand geopend of nieuw aangemaakt als het nog niet bestaat (belangrijk: de bestandsnaam mag maximaal 8 tekens lang zijn). Met FILE_WRITE wordt aangegeven dat het bestand in schrijfmodus moet worden geopend. Als het openen is gelukt, bevat file een object van de klasse File waarmee we de regel schrijven die in loop() is samengesteld. Daarna wordt het bestand weer gesloten. Dit zorgt ervoor dat het bestand niet beschadigd raakt als de kaart tussen twee schrijfprocessen wordt verwijderd of als de stroom uitvalt. Voor het debuggen geven we de waarde van line ook nog op de seriële interface weer.
Als het openen niet is gelukt (bijvoorbeeld omdat de SD-kaart ontbreekt), is file leeg en wordt er een foutmelding weergegeven op de seriële interface.
De realtimeklok
Omschrijving en inbouwen
In tegenstelling tot een pc beschikt een Arduino niet over een ingebouwde klok, omdat deze voor de meeste toepassingen niet nodig is. Voor de gegevenslogger gebruiken we daarom de Grove RTC-module, die beschikt over een DS1307 klokgeneratorchip.
Voor de realtimeklok is een extra spanningsbron nodig in de vorm van een 3V knoopcelbatterij (CR1225), zodat de ingestelde tijd ook behouden blijft als de Arduino zelf niet op het stroomnet is aangesloten. Dat heeft wel tot gevolg dat de realtimeklok zonder batterij niet correct functioneert; men kan er derhalve niet van afzien, zelfs als de Arduino vast op een stroombron aangesloten blijft. De communicatie verloopt via het I²C-bussysteem; er kan een willekeurige I²C-aansluiting worden gebruikt.
Evenals voor het display moet voor de realtimeklok een bibliotheek worden geïnstalleerd:
Download: https://github.com/Seeed-Studio/RTC_DS1307/archive/master.zip
Documentatie: http://wiki.seeed.cc/Grove-RTC/
Download de bibliotheken en installeer deze vervolgens via het menu ‘Sketch’ → ‘Bibliotheek koppelen’ → ‘ZIP-bibliotheken toevoegen…’.
In het menu ‘Bestand’ → ‘Voorbeelden’ zijn voor alle bibliotheken voorbeeldprogramma’s beschikbaar.
Tijd instellen en uitlezen
Om de tijd in te stellen, gebruiken we een eigen sketch in plaats van de setup()-methode in het definitieve programma. Anders zou de tijd elke keer weer worden teruggezet dat de Arduino wordt gestart:
#include <Wire.h>
#include <DS1307.h>
DS1307 clock; // een tijdobject aanmaken
void setup() {
Serial.begin(9600);
// De verbinding met de klok maken
clock.begin();
// De tijd instellen
clock.fillByYMD(2017, 9, 24);
clock.fillByHMS(18, 53, 15);
clock.fillDayOfWeek(SAT);
clock.setTime();
}
void loop() {
printTime();
delay(1000);
}
void printTime() {
clock.getTime(); // Tijd van de chip opvragen
Serial.print(clock.hour, DEC);
Serial.print(“:”);
Serial.print(clock.minute, DEC);
Serial.print(“:”);
Serial.print(clock.second, DEC);
Serial.print(” | “);
Serial.print(clock.dayOfMonth, DEC);
Serial.print(“.”);
Serial.print(clock.month, DEC);
Serial.print(“.”);
Serial.print(clock.year, DEC);
Serial.println();
}
In setup() openen we eerst de verbinding met een seriële poort, zodat de tijd ter controle naar de pc kan worden verzonden. Met clock.begin() wordt vervolgens de verbinding met de klok geopend, worden datum, tijd en weekdag voorbereid en ten slotte via clock.setTime() naar de realtimeklok verzonden (vervang de waarden eerst door de huidige tijd ;-))
In loop() wordt eerst de tijd uitgegeven en vervolgens een seconde gewacht. In Arduino Studio wordt de uitvoer weergegeven onder ‘Tools’ → ‘Seriële monitor’.
Belangrijk: het duurt enkele seconden om het programma naar de Arduino te verzenden; het is daarom moeilijk om de tijd exact in te stellen. Voor deze toepassing is dat geen probleem: als er om de paar minuten een meetwaarde wordt geregistreerd om het verloop van de meetwaarden gedurende de dag te registreren, dan is een afwijking van enkele seconden geen probleem.
Het definitieve programma
Nadat we alle onderdelen hebben getest, kan het programma uit de vorige tutorial worden uitgebreid:
#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include <DS1307.h>
#include <DHT.h>
#include <rgb_lcd.h>
DS1307 clock;
DHT dht(A0, DHT22);
rgb_lcd lcd;
byte DEGREE_SYMBOL = 0;
byte degree[8] = {
0b00000,
0b00010,
0b00101,
0b00010,
0b00000,
0b00000,
0b00000,
0b00000
};
unsigned long DISPLAY_UPDATE_INTERVAL = 200; // 200 milliseconden
unsigned long WRITE_INTERVAL = 60000; // 1 minuut
unsigned long lastDisplayUpdate = DISPLAY_UPDATE_INTERVAL;
unsigned long lastWrite = WRITE_INTERVAL;
void setup() {
Serial.begin(9600); // Seriële interface initialiseren
// SD-kaart initialiseren
int selectedChip = 4;
if (!SD.begin(selectedChip)) {
Serial.println(“SD-kaart kan niet worden geladen of ontbreekt”);
} else {
Serial.println(“SD-kaart geïnitialiseerd.”);
}
dht.begin(); // Verbinding maken met de sensor
clock.begin(); // Verbinding maken met de realtimeklok
lcd.begin(16, 2); // Verbinding maken met het display en het display initialiseren: 2 regels van elk 16 tekens
lcd.createChar(DEGREE_SYMBOL, degree); // Het nieuwe “°”-symbool registreren bij het display
}
void loop() {
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
if (isnan(temperature) || isnan(humidity)) {
Serial.println(“DHT lezen is niet mogelijk”);
return; // Geen gegevens –> loop() weer verlaten op dit punt
}
if (millis() – lastDisplayUpdate > DISPLAY_UPDATE_INTERVAL) {
const float coldest = 18;
const float hottest = 30;
int red = limit(255 * (temperature – coldest) / (hottest – coldest));
// Kleur van blauw via paars naar rood
int green = 0;
int blue = limit(255 * (hottest – temperature) / (hottest – coldest));
updateDisplay(red, green, blue, temperature, humidity);
lastDisplayUpdate = millis();
}
if (millis() – lastWrite > WRITE_INTERVAL) {
String line = String(getTime()) + “;” + String(temperature) + “;” + String(humidity);
writeToSD(line);
lastWrite = millis();
}
}
String getTime() {
clock.getTime(); // Tijd van de chip opvragen
String t = String(clock.dayOfMonth, DEC);
t += String(“.”);
t += String(clock.month);
t += String(“.”);
t += String(clock.year);
t += String(” “);
t += String(clock.hour);
t += “:”;
t += String(clock.minute);
t += “:”;
t += String(clock.second);
return t;
}
void writeToSD(String line) {
File dataFile = SD.open(“datalog.csv”, FILE_WRITE);
if (dataFile) {
dataFile.println(line); // Naar de SD-kaart schrijven
dataFile.close();
Serial.println(line); // Aanvullend schrijven naar de seriële interface voor debuggen
}
else {
Serial.println(“Fout bij openen van gegevensbestand”);
}
}
void updateDisplay(int red, int green, int blue, float temperature, float humidity) {
lcd.setRGB(red, green, blue);
lcd.setCursor(0, 0);
lcd.print(“T: “);
lcd.print(temperature);
lcd.write(DEGREE_SYMBOL);
lcd.print(“C”);
lcd.setCursor(0, 1);
lcd.print(“H: “);
lcd.print(humidity);
lcd.print(“%”);
}
float limit(float color) { // Kleurwaarden moeten binnen het bereik 0..255 liggen
if (color < 0) {
return 0;
}
else if (color > 255) {
return 255;
}
else {
return color;
}
}
Nieuw bij dit programma zijn de twee processen met verschillende klokfrequenties. Het display moet voor een actuele weergave en vloeiende kleurovergangen meerdere keren per seconde worden bijgewerkt. Voor het schrijven van de meetwaarden naar de kaart is echter een minutenpuls voldoende. Eenvoudige pauzes met delay() zijn daarom niet meer mogelijk. De oplossing: Met de functie millis() kan het aantal voorbije milliseconden vanaf het starten van de Arduino worden opgevraagd. In lastDisplayUpdate en lastWrite worden dan telkens de laatste tijd van het bijwerken van het display en het schrijven naar de SD-kaart opgeslagen. Elke keer dat loop() wordt uitgevoerd, wordt dan vergeleken of het verschil van deze waarden voor millis() groter is dan de waarden van DISPLAY_UPDATE_INTERVAL en WRITE_INTERVAL en alleen in dat geval worden de desbetreffende acties uitgevoerd. De variabelen lastWrite en lastDisplayUpdate worden met de lengte van het desbetreffende interval geïnitialiseerd, zodat beide acties direct volgen op de start van het programma.
In loop() worden ten slotte eerst de waarden van de sensor opgehaald en gecontroleerd. Als er tijdens het ophalen van de waarden problemen zijn opgetreden, wordt een foutmelding geretourneerd en wordt de uitvoering van loop() op deze plek beëindigd en weer van voren af aan gestart (d.w.z. dat de waarden weer worden opgehaald uit de sensor).
Vervolgens wordt gecontroleerd of het weer tijd is om het display bij te werken. Dat is het geval als het verschil tussen het huidige tijdstip en lastDisplayUpdate groter is dan DISPLAY_UPDATE_INTERVAL. Daarna wordt wederom de kleur bepaald en worden de gegevens naar het display verzonden. Voor de leesbaarheid is daarvoor de methode updateDisplay() beschikbaar. Aansluitend wordt lastDisplayUpdate ingesteld op de actuele tijd.
Dat gebeurt ook voor het schrijven van de gegevens naar de SD-kaart. Als het tijdstip van het laatste schrijfproces langer geleden is dan WRITE_INTERVAL, wordt de actuele tijd bepaald door de realtimeklok, worden de gegevens naar de SD-kaart geschreven en wordt lastWrite ingesteld op de huidige tijd.
De tijd wordt bepaald via de methode getTime(), die de tijd opvraagt van de realtimeklok en een string genereert met datum en tijd. Met de operator += wordt de waarde van de rechterstring gekoppeld aan de linkerstring.
Gegevens evalueren
De gegevens worden naar een bestand met de naam datalog.csv geschreven en kunnen voor de evaluatie worden geïmporteerd in Excel, LibreOffice Calc of Google Spreadsheets.
Verwijder de SD-kaart uit de Arduino en lees deze uit op de pc!
Veel plezier!
Stuklijst
1x Arduino – Grove realtimeklok – RTC
1x Arduino Shield – SD-kaart V4
1x Batterij voor Grove realtimeklok
1x SD-kaart