Pour faire suite à la première partie de notre tutoriel sur la mesure de la température et de l’humidité avec la carte Arduino Uno, retrouvez la deuxième partie de l’expérience dans cet article.
[themen_sprungmarken]
Le projet
Niveau : Avancé.
Temps requis : environ 1,5 heures.
Budget : environ 30 euros.
Ce dont vous avez besoin : 1x Arduino – Grove real time clock – RTC, 1x Arduino Shield – SD card V4, 1x batterie pour Grove real time clock, 1x carte SD .
Eléments additionnels : Connexion Internet, alimentation électrique pour l’Arduino.
Dans les derniers conseils pratiques concernant Arduino, nous avons mis en place un affichage de la température et de l’humidité de l’air. Une extension est cette fois prévue pour enregistrer ces données sur une carte SD. Lors de chaque mesure, le moment correspondant est également conservé. On ajoute dans ce but une carte SD ainsi qu’une horloge temps réel. L’affichage peut cependant être conservé au niveau du module Grove-Shield, mais si l’on souhaite utiliser un suivi des données sans affichage, il est possible simplement de le retirer.
Le module SD-Shield
Description et mise en place
L’enregistreur des données conserve ses données sur une carte SD, SDHC ou sur une carte micro SD (dans ce cas avec un adaptateur), disponibles dans le commerce. Pour cela, un module Shield est placé entre la carte Arduino et le module Base-Shield – et pour une question de branchement, le module Base-Shield reste en haut.
Les bibliothèques indispensables au module SD-Shield sont déjà intégrées à Arduino Studio, et ne doivent donc pas être installées séparément. La documentation est disponible dans la section des références Arduino, à l’adresse suivante : https://www.arduino.cc/en/Reference/SD.
Enregistrement des données – Bonjour tout le monde
Les premières données sont écrites sur la carte grâce au programme suivant :
#include #include int SELECTED_CHIP = 4; int count = 0; void setup() { Serial.begin(9600); if (SD.begin(SELECTED_CHIP)) { Serial.println("SD-Card initialized."); } else { Serial.println("SD-Card failed or is missing"); } } void loop() { String line = String("Bonjour le monde #") + 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); // Enregistrer sur la carte dataFile.close(); // Fermer le fichier Serial.println(line); } else { Serial.println("Error opening datafile"); } }
Deux variables sont d’abord définies : SELECTED_CHIP indique quel matériel est utilisé au niveau du module SD-Shield, la valeur étant fournie par le fabricant. La variable count sert uniquement à comptabiliser en parallèle les procédures d’enregistrement en cours, de manière à enregistrer le nombre sur la carte.
Dans setup(), un lien séquentiel de résolution des anomalies est d’abord mis en place, puis un lien avec la carte SD. La méthode SD.begin() indique si le lien a pu être établi avec succès.
La ligne est intégrée dans loop(), pour être ensuite enregistrée avec writeToSD() sur la carte SD. Nous utilisons par ailleurs la classe String qui permet de travailler de façon souple avec les chaînes de caractères. L’opérateur + associe deux Strings, la variable count est donc tout d’abord convertie en un String unique par l’intermédiaire de String(count). La valeur de count est par ailleurs augmentée d’une unité, et il convient pour finir d’attendre une seconde.
L’enregistrement intervient au niveau de writeToSD(). À ce niveau, un fichier est d’abord ouvert avec SD.open() ou créé s’il n’existe pas déjà (important : le nom de fichier doit comprendre au maximum 8 caractères). FILE_WRITE indique que le fichier doit être ouvert en mode écriture. Si l’ouverture s’est bien déroulée, file contient un objet de la classe File, avec lequel nous écrivons les lignes associées dans loop(). Nous fermons ensuite à nouveau le fichier. Cette procédure permet de veiller à ce que le fichier ne soit pas endommagé au cas où la carte est retirée entre deux procédures d’enregistrement ou en cas de coupure de courant. Afin de résoudre les anomalies, nous réintroduisons également à nouveau la valeur de line au niveau de l’interface séquentielle.
Si toutefois l’ouverture ne s’est pas bien déroulée (par exemple, en raison de l’absence de carte SD), file est vide et un message d’erreur est émis au niveau de l’interface séquentielle.
L’horloge temps réel
Description et mise en place
Contrairement à un ordinateur de bureau, Arduino ne dispose pas d’une horloge intégrée, celle-ci n’étant pas nécessaire pour de nombreuses applications. Pour l’enregistreur de données, nous utilisons par conséquent le module Grove-RTC, qui contient un microprocesseur de cadencement DS1307.
L’horloge temps réel nécessite une source de tension supplémentaire sous la forme d’une pile bouton 3V (CR1225), afin que le temps réglé soit conservé, y compris lorsque l’Arduino n’a plus de courant. À l’inverse, cela a pour conséquence que l’horloge temps réel ne fonctionne pas correctement sans pile, on ne peut donc pas s’en passer, même lorsque l’Arduino dispose d’un raccordement à une source d’alimentation électrique. La communication circule par l’intermédiaire d’un système de bus I²C. Il est possible d’utiliser n’importe quel type de raccordement I²C.
L’horloge temps réel a besoin, comme pour l’affichage, de l’installation d’une bibliothèque :
Téléchargement : https://github.com/Seeed-Studio/RTC_DS1307/archive/master.zip
Documentation : http://wiki.seeed.cc/Grove-RTC/
Télécharger les bibliothèques, puis par l’intermédiaire du menu « Sketch » → installer « Lier la bibliothèque » → « Ajouter les bibliothèques .ZIP… ».
Pour l’ensemble des bibliothèques il existe des exemples de programmes sous « Fichier » → « Exemples ».
Réglage et lecture du temps
Pour régler le temps au niveau du programme final, nous utilisons un Sketch au lieu de la méthode setup(). Si cela n’était pas le cas, le temps serait établi à nouveau à chaque démarrage de l’Arduino :
#include <Wire.h> #include <DS1307.h> DS1307 clock; // création d’un objet temps void setup() { Serial.begin(9600); // établir le lien avec le temps clock.begin(); // régler le temps clock.fillByYMD(2017, 9, 24); clock.fillByHMS(18, 53, 15); clock.fillDayOfWeek(SAT); clock.setTime(); } void loop() { printTime(); delay(1000); } void printTime() { clock.getTime(); // rappeler le temps au niveau microprocesseur 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(); }
Dans setup(), nous ouvrons tout d’abord le lien avec le port séquentiel, de manière à transmettre l’heure à l’ordinateur de bureau pour permettre une vérification. Par l’intermédiaire de clock.begin(), le lien avec l’horloge est ensuite ouvert. La date, l’heure et le jour de la semaine sont ainsi préparés pour être finalement transmis à l’horloge temps réel par l’intermédiaire de clock.setTime() (remplacer au préalable les valeurs avec l’heure actuelle ;-)).
Dans loop(), le temps est tout d’abord indiqué, puis on laisse une seconde s’écouler. Avec Arduino Studio l’émission apparaît dans « Outils » → « Contrôle séquentiel ».
Important : la transmission du programme à l’Arduino dure quelques secondes. Il est donc difficile de régler précisément le temps. Pour cette application, cela ne pose pas de problème. En effet, lorsqu’une valeur mesurée apparaît toutes les quelques minutes pour indiquer l’évolution des valeurs mesurées pendant la journée, un écart de quelques secondes ne fait aucune différence.
Le programme final
Après avoir testé l’ensemble des parties une fois, nous pouvons étendre le programme à partir du dernier tutoriel :
#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 Millisecondes unsigned long WRITE_INTERVAL = 60000; // 1 Minute unsigned long lastDisplayUpdate = DISPLAY_UPDATE_INTERVAL; unsigned long lastWrite = WRITE_INTERVAL; void setup() { Serial.begin(9600); // Initialiser l’interface séquentielle // Initialiser la carte SD int selectedChip = 4; if (!SD.begin(selectedChip)) { Serial.println("SD-Card failed or is missing"); } else { Serial.println("SD-Card initialized."); } dht.begin(); // Établir le lien avec le capteur clock.begin(); // Établir le lien avec l’horloge temps réel lcd.begin(16, 2); // Établir le lien avec l’affichage et initialiser l’affichage - 2 lignes avec chacune 16 caractères lcd.createChar(DEGREE_SYMBOL, degree); // Enregistrer le nouveau symbole "°" au niveau de l’affichage } void loop() { float humidity = dht.readHumidity(); float temperature = dht.readTemperature(); if (isnan(temperature) || isnan(humidity)) { Serial.println("Failed to read from DHT"); return; // Aucune donnée --> loop() quitter à nouveau à ce stade } if (millis() - lastDisplayUpdate > DISPLAY_UPDATE_INTERVAL) { const float coldest = 18; const float hottest = 30; int red = limit(255 * (temperature - coldest) / (hottest - coldest)); // Couleurs de bleu à rouge, en passant par le violet 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(); // Interroger le temps à partir du microprocesseur 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); // Enregistrer sur la carte SD dataFile.close(); Serial.println(line); // Enregistrer également au niveau de l’interface séquentielle pour résoudre les anomalies } else { Serial.println("Error opening datafile"); } } 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) { // les valeurs de couleurs doivent être situées dans l’intervalle 0..255 if (color < 0) { return 0; } else if (color > 255) { return 255; } else { return color; } }
Deux fréquences d’horloge différentes
Une nouveauté de ce programme est la mise en œuvre de deux processus présentant deux fréquences d’horloge différentes. L’affichage doit être actualisé plusieurs fois par seconde, de manière à donner des informations actualisées et à assurer une évolution fluide des couleurs. Pour l’enregistrement des valeurs mesurées sur la carte, une fréquence d’une minute est cependant suffisante. Des pauses simples avec delay() ne sont pour cette raison plus possibles. La solution : grâce à la fonction millis(), il est possible de récupérer les millisecondes passées depuis le démarrage de l’Arduino. Au niveau de lastDisplayUpdate et de lastWrite, le dernier état de l’actualisation de l’affichage ou de l’enregistrement est ensuite enregistré sur la carte SD . À chaque exécution de loop(), une comparaison est alors effectuée pour vérifier que l’écart de ces valeurs avec millis() est plus important que les valeurs de DISPLAY_UPDATE_INTERVAL ou de WRITE_INTERVAL. Les actions correspondantes ne sont menées que si cette condition est vérifiée. Les variables lastWrite et lastDisplayUpdate sont initialisées à partir de la longueur des intervalles correspondants, de telle façon que les deux actions interviennent directement au moment du démarrage du programme.
Dans loop() les valeurs sont dans un premier temps récupérées et analysées par un capteur. Si un problème est survenu lors de la récupération des données, une erreur est signalée et l’exécution de loop() est interrompue à ce stade pour repartir du début (c’est-à-dire la récupération des valeurs par le capteur).
Vérification
Enfin, une vérification est menée pour savoir s’il est à nouveau temps d’actualiser l’affichage. C’est le cas lorsque la différence entre le moment correspondant et lastDisplayUpdate est supérieure à DISPLAY_UPDATE_INTERVAL. La couleur est ensuite de nouveau déterminée et les données sont transmises à l’affichage. Pour des raisons de lisibilité, la méthode updateDisplay() est maintenant utilisée. lastDisplayUpdate est ensuite réglé sur le moment correspondant.
La même chose intervient pour l’enregistrement des données sur la carte SD. Lorsque le moment de la dernière procédure d’enregistrement est plus ancien que WRITE_INTERVAL, le moment correspondant est récupéré au niveau de l’horloge temps réel, les données sont enregistrées sur la carte SD, et lastWrite prend la valeur du moment correspondant.
Le moment est identifié par la méthode getTime(), qui rapatrie le moment de l’horloge temps réel, et qui établit un String comprenant la date et l’heure. L’opérateur += associe la valeur des séries de droite à celles de gauche.
Analyse des données
Les données sont transcrites dans un fichier portant le nom datalog.csv, et peuvent être importées dans des feuilles de travail Excel, LibreOffice Calc ou Google pour être analysées.
Il convient simplement de retirer la carte SD de l’Arduino et de la lire sur un ordinateur de bureau.
Amusez-vous bien !