Arduino : Un datalogger environnemental

Les Arduinos et Raspberry ont permis aux développeurs informatiques de s’intéresser aux petits automatismes généralement réservés aux purs électroniciens.

Je me suis intéressé par curiosité, comme beaucoup, aux Arduinos : petites cartes facilement programmables et particulièrement adaptées aux actions « temps réels ».

Présentation du projet

Voici un petit projet qui regroupe quelques un de mes 1ers essais :
Il s’agit de réaliser une station de mesure de :

  • La température ambiante
  • L’humidité ambiante
  • La concentration de l’air en CO2

Le montage permet :

  • Un affichage des mesures sur un écran à cristaux liquides
  • L’enregistrement de chaque valeurs horodatées sur une carte SD dans l’idée de pouvoir observer l’évolution au cours de la journée.

Voici une liste des différents composants utilisés :

  • Un Arduino Nano (mais ce peut-être un autre type d’Arduino)
  • Un détecteur d’humidité et de température DHT11
  • Un détecteur de CO2 MHZ14A
  • Un écran à cristaux liquides HD44780
  • Un contrôleur I2C PCF8574 pour piloter l’écran LCD
  • Une module d’horloge RTC 1302 (ou 1307)
  • Un lecteur de carte SD DS3231

Voici une photo du montage terminé et une photo de l’affichage sur l’écran LCD

Entrées  et sorties utilisées :

Les Arduinos possèdes plusieurs types d’entrées-sorties :

  • Des entrées analogiques
  • Des entrées-sorties numériques

Il n’y a pas de sorties analogiques car on peut les émuler à l’aide d’une sortie numérique facilement.

Les entrées sorties numériques sont de différents types :

  • Les entrées classiques (0 ou 1)
  • Les bus

Les Arduino possèdent 2 types de bus :

  • Un bus I2C
  • Un bus SPI

L’avantage du bus I2C est qu’on peut y connecter en parallèle plusieurs périphériques I2C du moment qu’ils n’ont pas la même adresse : on communique en effet avec l’un ou l’autre via leur adresse unique.

Dans notre montage on va communiquer avec :

  • En entrée : le détecteur de CO2 via un port digital qui sera paramétré en PWM (modulation de largeur d’impulsion)
  • En entrée : une entrée numérique pour le détecteur de température et d’humidité
  • En entrée / sortie : un bus SPI pour le lecteur de carte SD
  • En entrée / sortie : Une horloge interne (RTC) via le bus I2C
  • En sortie : l’écran LCD via le bus I2C

L’horloge RTC et l’écran LCD seront en parallèle sur l’unique bus I2C de l’Arduino.

Pour ceux qui commencent à prendre peur, rassurez-vous : la programmation restera simple : on va utiliser des bibliothèques disponibles sur internet pour exploiter l’horloge RTC, le lecteur de carte SD et l’écran LCD, notre job sera limité à paramétrer ces interfaces et à choisir quand lire et/ou écrire des données.

L’environnement de développement, les bibliothèques :

Vous pouvez déjà lancer le logiciel « Arduino IDE » et installer les bibliothèques suivantes :

  • LiquidCrystal I2C
  • Timekeeping functionnality for Arduino
  • DS1307rtc
  • SimpleDHT

Identifier les adresses des périphériques I2C

Nous allons utilisé un contrôleur I2C pour l’afficheur à cristaux liquides ainsi qu’une horloge RTC également sur port I2C. Pour y accéder, il nous faut leurs adresses. Il arrive que d’un modèle à l’autre, l’adresse ne soit pas la même, aussi, il existe un code Arduino pour afficher les adresses des périphériques I2C : lorsqu’un tel périphérique est connecté via les boches SDA et SCL, le code suivant affiche dans le moniteur série, l’adresse du périphérique :


i2c_scanner.ino    Télécharger

// ————————————–
// i2c_scanner
//
// Version 1
// This program (or code that looks like it)
// can be found in many places.
// For example on the Arduino.cc forum.
// The original author is not know.
// Version 2, Juni 2012, Using Arduino 1.0.1
// Adapted to be as simple as possible by Arduino.cc user Krodal
// Version 3, Feb 26 2013
// V3 by louarnold
// Version 4, March 3, 2013, Using Arduino 1.0.3
// by Arduino.cc user Krodal.
// Changes by louarnold removed.
// Scanning addresses changed from 0…127 to 1…119,
// according to the i2c scanner by Nick Gammon
// https://www.gammon.com.au/forum/?id=10896
// Version 5, March 28, 2013
// As version 4, but address scans now to 127.
// A sensor seems to use address 120.
// Version 6, November 27, 2015.
// Added waiting for the Leonardo serial communication.
//
//
// This sketch tests the standard 7-bit addresses
// Devices with higher bit address might not be seen properly.
//

#include <Wire.h>

void setup()
{
Wire.begin();

Serial.begin(9600);
while (!Serial); // Leonardo: wait for serial monitor
Serial.println(« \nI2C Scanner »);
}

void loop()
{
byte error, address;
int nDevices;

Serial.println(« Scanning… »);

nDevices = 0;
for(address = 1; address < 127; address++ )
{
// The i2c_scanner uses the return value of
// the Write.endTransmisstion to see if
// a device did acknowledge to the address.
Wire.beginTransmission(address);
error = Wire.endTransmission();

if (error == 0)
{
Serial.print(« I2C device found at address 0x »);
if (address<16)
Serial.print(« 0″);
Serial.print(address,HEX);
Serial.println( » ! »);

nDevices++;
}
else if (error==4)
{
Serial.print(« Unknown error at address 0x »);
if (address<16)
Serial.print(« 0 »);
Serial.println(address,HEX);
}
}
if (nDevices == 0)
Serial.println(« No I2C devices found\n »);
else
Serial.println(« done\n »);

delay(5000); // wait 5 seconds for next scan
}

L’utilisation de ce programme m’a permis de constaté que mon module I2C que je compte utiliser pour connecter l’écran LCD est à l’adresse 0x27. Certains modèles de la même marque sont à l’adresse 0x3F Ce n’est pas un soucis du moment qu’on le sait pour adapter le code source final en conséquence.

Le schéma du montage

Connexion du lecteur de carte SD

Il utilise le port SPI (bornes MOSI, MISO et CLK). Ce port est accessible via les broches D11,D12 et D13 du Arduino mais aussi via le bloc de 6 pins en entête du Arduino nano.
Le câble CS doit être connecté à une entrée/sortie digitale du Arduino de votre choix. Il faut juste se souvenir du numéro de cette broche car il faut l’indiquer dans le code.
Vous noterez que j’ai choisis la borne D10.

Connexion du détecteur d’humidité et de température DHT11

Elle est simplissime : il suffit de connecter l’alimentation et le fil de signal à une broche d’entrée digitale de l’Arduino. J’ai choisi la broche D9.

Connexion de l’horloge RTC

Elle est simplissime : il suffit de connecter l’alimentation et les fils SDA et SCL du port I2C respectivement aux ports A4 et A5 de l’Arduino.
A noter, le contrôleur I2C de l’écran LCD sera lui-aussi connecté en I2C aux mêmes broches de l’Arduino (A4 et A5)

Connexion de l’écran LCD en I2C

Comme l’horloge RTC, il est connecté en I2C aux ports A4 et A5 de l’Arduino. L’adresse de mon contrôleur I2C est 0x27

Connexion du MHZ14A : la mesure de la concentration en CO2

Cette sonde offre des possibilités multiples de connexion, elle dispose en effet :

  • d’un port série
  • d’une sortie analogique
  • d’une sortie PWM digitale

J’ai choisis d’exploiter la sortie PWM en la connectant à une entrée/sortie digitale de l’Arduino : la D8

Les spécification techniques détaillées sur programme :

Utilisation du module Horloge RTC

L’objectif de l’utilisation de l’horloge est de connaître, pour chaque valeur de température, humidité et concentration en CO2, la date et l’heure de la mesure puisque je souhaite enregistrer ces valeurs dans un fichier.

Mais cette horloge est comme une montre, elle n’est pas nécessairement à l’heure lorsqu’on l’allume pour la première fois. Et notre montage ne prévoit aucun bouton pour mettre l’horloge à l’heure ! Comment faire alors ?

Heureusement, il y a une astuce : lorsque l’on compile puis téléverse un programme au Arduino, ce fichier possède une date et heure de compilation et Arduino sait lire ces informations.

On peut donc au démarrage du programme :

  • Consulter la date et heure de la compilation du programme
  • Consulter la date et j’heure stockée dans l’horloge
  • Comparer les deux et :
    • Si l’horloge donne une date-heure inférieure à la date-heure de compilation c’est que l’horloge doit-être réglée.
    • Sinon, on peut estimer que l’horloge est à l’heure.

Il nous faudra donc :

  • Une fonction pour extraire la date et l’heure du fichier binaire compilé du programme
  • Une fonction pour lire la date et l’heure de l’horloge RTC
  • Une fonction pour mettre à jour cette date et heure.

Utilisation du  module SDCARD :

A chaque démarrage du programme on doit vérifier que :

  • le module SDCARD est bien présent et connecté
  • Une carte SD est bien présente
  • Ce module n’acceptant que les partitions de type FAT16 et FAT32, on vérifiera que la carte SD est bien de ce format.

A chaque tentative d’écriture de valeurs sur la carte on vérifiera :

  • le module SDCARD est bien présent et connecté
  • Une carte SD est bien présente
  • On choisira un nom de fichier (j’ai choisis un nom du type SSAAMMJJ.CSV, ainsi le programme ne va créer qu’un fichier par jour sans écraser le précédant à chaque redémarrage)
  • Si le fichier n’existe pas : le créer
  • Si le fichier existe : le compléter de la nouvelle valeur.

Enfin, il ne s’agit pas de remplir des fichiers avec des milliers d’informations répétitives : dans un datalogger on a deux choix possibles :

  • Enregistrer des données lorsque celles-ci changent
  • ou Enregistrer systématiquement des données de manière systématique au bout d’un certain temps précis.

J’ai chois cette dernière option :

  • Mesurer les valeurs toutes les 10 secondes
  • Ne les enregistrer systématiquement que toutes les 5 minutes de sorte qu’il soit aisé ensuite, sous Excel de produire une courbe.

A noter qu’à chaque écriture du fichier, il faut se demander s’il faut passer à un nouveau fichier (on peut franchir le cap des minuit).

Utilisation du  module DHT11 et du module MHZ14A :

Là c’est très simple : Il suffit de lire les données régulièrement.

Attention cependant : Si le module de température et d’humidité DHT11 est immédiatement exploitable, ce n’est pas le cas du module de mesure du CO2 (MHZ14A).

Ce dernier a besoin d’un temps de chauffe avant de fournir des valeurs exploitables.  Du coup, j’ai fais en sorte que le programme teste la probabilité que la mesure soit réelle. Si ce n’est pas le cas, j’affiche « Wait »

Utilisation de l’écran LCD et de la sortie série :

La sortie série est très pratique pour débugger un programme, je l’alimente régulièrement lorsque le programme fait quelque chose.

Concernant l’écran LCD , j’ai choisis de :

  1. Détailler les vérifications que le programme réalise au démarrage et y afficher les problèmes éventuellement rencontrés.
  2. Afficher : la date, l’heure, la température, l’humidité et la concentration en CO2

La sortie du port série du programme :

20:46:39.324 -> Starting …
20:46:39.371 -> Checking clock
20:46:43.451 -> Init SDCard
20:46:43.497 -> SDCARD OK
20:46:50.769 -> Temp:28C Hd:53
20:47:00.795 -> Temp:28C Hd:53
20:47:10.785 -> Temp:28C Hd:53
20:47:20.808 -> Temp:28C Hd:53
20:47:30.821 -> Temp:29C Hd:53
20:47:40.846 -> Temp:29C Hd:53
20:47:51.063 -> CO2 :545
20:47:51.110 -> Filename : 20210614.CSV
20:47:51.157 -> Temp:29C Hd:53
20:48:01.150 -> Temp:29C Hd:53
20:48:11.169 -> Temp:29C Hd:53
20:48:21.200 -> Temp:29C Hd:53
20:48:31.221 -> Temp:29C Hd:53
20:48:41.208 -> Temp:28C Hd:53
20:48:51.891 -> CO2 :535
20:48:51.938 -> Filename : 20210614.CSV

Le code source

Je l’ai volontairement très documenté. Au cours de la mise au moins, j’ai mis de nombreux affichages sur le port série que j’ai mis ensuite en commentaire une fois le programme au point.

A noter : dans l’état actuel, le programme va occuper 75% de la mémoire ce qui va provoquer un Warning lors du téléversement que vous pouvez ignorer.

Vous pouvez grandement diminuer la quantité de mémoire nécessaire en supprimer des sorties d’affichage sur le port série.

i2c_scanner.ino    Télécharger

/**************************************************************************/
/* BIBLIOTHEQUES*/
/**************************************************************************/
/*I2C*/
#include <Wire.h>
/**************************************************************************/
/*Horloge RTC DS3231-DS1307-DS1302*/
#include <DS1307RTC.h>
#include <Time.h>
/**************************************************************************/
/*Eran LCD 1602 en I2C*/
#include <LiquidCrystal_I2C.h>
/**************************************************************************/
/*Détecteur de temérature / humidité DHT11*/
#include <SimpleDHT.h>
/**************************************************************************/
/* Librairie carte SD*/
#include <SPI.h>
#include <SD.h>

/**************************************************************************/
/*Déclaration écran LCD I2C */
#define LCDADDR 0x27
#define NBCOLS 16
#define NBLIGS 2
LiquidCrystal_I2C lcd(LCDADDR,NBCOLS,NBLIGS);

/**************************************************************************/
/*Variables SDCARD */
Sd2Card card;
SdVolume volume;
SdFile root;
/*Détection pour permettre le déroulement du programme sans SDCARD */
bool SDCARD_present=true;
/*PIN de connexion CS */
const byte chipSelect = 10;

/**************************************************************************/
/** Broche « DATA » du capteur DHT11 */
const byte DHT_PIN = 9;
SimpleDHT11 dht11(DHT_PIN); //Déclaration object sond eDHT 11 avec sa broche

/**************************************************************************/
/*Ligne a afficher à l’écran*/
char myStg[16];

/**************************************************************************/
/*Broche MHZ14A, OFFSET ET VARIABLES DE TRAVAIL*/
const byte MHZ14A_PWM_PIN = 8;
const byte MHZ14A_ANA_PIN = 3;
const int CO2_OFFSET = 0;

/**************************************************************************/
/* Periode de mesure*/
const long samplePeriodCO2 = 60000L;
const long samplePeriodDHT = 10000L;
long lastSampleTimeCO2 = 0;
long lastSampleTimeDHT = 0;

/*Initialisation*/
byte temp = 0;
byte humid = 0;
unsigned int co2 = 0;

byte old_temp = -1;
byte old_humid = -1;
unsigned int old_co2 = -1;

/**************************************************************************/
/**Structure des variables date / heure **/
tmElements_t tm; /*date-heure retournée par l’horloge RTC*/
tmElements_t tm_compil; /*date-heure du programme exécutable */

/**************************************************************************/
/*Nombre de valeurs de co2 à prendre ,numero de la valeur prise et valeur actuelle*/
#define PRE_HEAT_TIME 60000

/***************************************************************************/
/* Lors de la création d’un fichier sur la carte, faire en sorte qu’il ait */
/* la bonne date-heure de création */
/***************************************************************************/
void dateTime(uint16_t* date, uint16_t* time) {
char timestamp[30];
int yy,mm,dd,hh,mn,ss;
tmElements_t dateheure;
RTC.read(dateheure);
yy= tmYearToCalendar(dateheure.Year);
mm= dateheure.Month;
dd= dateheure.Day;
hh= dateheure.Hour;
mn= dateheure.Minute;
ss= dateheure.Second;
sprintf(timestamp, « %02d:%02d:%02d %2d/%2d/%2d \n », hh, mn, ss,dd, mm,yy);
*date = FAT_DATE(yy, mm, dd);
*time = FAT_TIME(hh, mn, ss);
}

/***************************************************************************/
/*Créer / ajouter une ligne au fichier sur la SDCARD */
/***************************************************************************/
bool EcrireLigne(tmElements_t dateheure, int valco2,byte vtemp,byte vhumid)
{
/*Ligne a écrire sur la sdcard*/
char myLine[32];
char filename[32];
if (!SDCARD_present)
{
Serial.println(F(« SDCard absente »));
/*true = volontaire*/
return true;
}
if (!SD.begin(chipSelect))
{
Serial.println(F(« Couldn’t open SDCARD »));
return false;
}
sprintf(filename, « %04d%02d%02d.CSV », tmYearToCalendar(dateheure.Year), dateheure.Month, dateheure.Day);
Serial.print(« Filename : « );
Serial.println(filename);

/*Ecriture réelle toutes les 5 minutes*/
if (tm.Minute%5==0)
{
File logFile = SD.open(filename, FILE_WRITE);
delay(100);
if (!logFile)
{
Serial.println(F(« Couldn’t open log file »));
lcd.setCursor(0,1);
lcd.print(« SDCARD LOG ERROR »);
delay(1000);
return false;
}
else
{
Serial.println(F(« Writing to « ));
/*Serial.print(« Filename : « );*/
Serial.println(filename);
sprintf(myLine, « %04d-%02d-%02d %02d:%02d:%02d;%04d;%03d;%02d », tmYearToCalendar(dateheure.Year), dateheure.Month, dateheure.Day, dateheure.Hour, dateheure.Minute, dateheure.Second, valco2, vtemp, vhumid);
/*Serial.println(myLine);*/
logFile.println(myLine);
logFile.close();
}
}
return true;
}

/***********************************************************************************/
/*Initialisation et vérification SDCARD, fournit SDCARD_present : variable globale */
/***********************************************************************************/
bool InitSDCard()
{
Serial.println(F(« Init SDCard »));
// we’ll use the initialization code from the utility libraries
// since we’re just testing if the card is working!
clearLCD(1,0,15);
lcd.setCursor (0, 1);
lcd.print(F(« Init SDCard »));
if (!card.init(SPI_HALF_SPEED, chipSelect))
{
Serial.println(F(« No Card. »));
SDCARD_present = false;
}
else
{
Serial.println(F(« SDCARD OK »));
//Vérification du format: accepté : FAT16 / FAT 32
if (!volume.init(card))
{
Serial.println(F(« Bad format »));
SDCARD_present = false;
}
}
if (!SDCARD_present)
{
lcd.setCursor (12, 1);
lcd.print(F(« Fail »));
}
else
{
lcd.setCursor (14, 1);
lcd.print(F(« Ok »));
// set date time callback function
SdFile::dateTimeCallback(dateTime);
}
}

/***************************************************************************/
/*Lire l’heure de création du programme */
/***************************************************************************/
bool getTime(const char *str)
{
int Hour, Min, Sec;

if (sscanf(str, « %d:%d:%d », &Hour, &Min, &Sec) != 3) return false;
tm_compil.Hour = Hour;
tm_compil.Minute = Min;
tm_compil.Second = Sec;
return true;
}
/***************************************************************************/
/*Lire la date de création du programme */
bool getDate(const char *str)
{
char Month[12];
int Day, Year;
uint8_t monthIndex;
const char *monthName[12] = {
« Jan », « Feb », « Mar », « Apr », « May », « Jun »,
« Jul », « Aug », « Sep », « Oct », « Nov », « Dec »
};

if (sscanf(str, « %s %d %d », Month, &Day, &Year) != 3) return false;
for (monthIndex = 0; monthIndex < 12; monthIndex++) {
if (strcmp(Month, monthName[monthIndex]) == 0) break;
}
if (monthIndex >= 12) return false;
tm_compil.Day = Day;
tm_compil.Month = monthIndex + 1;
tm_compil.Year = CalendarYrToTm(Year);
return true;
}

/*************************************************************************/
/*Lecture unitaire sonde CO2 en Analogique*/
/*Filtrage des valeurs incohérentes */
int read_ANA_MHZ14A(byte sensorPin,byte nbeval, byte delais)
{
int ppm = 10000;
int v0;
int millivolt=10000;
/*char tbs[16];*/
for (byte i=0;i<nbeval;i++)
{
v0 = map(analogRead(sensorPin),0,1023,0,5000);
if (v0<millivolt) millivolt=v0;
if (delais>0) delay(delais);
}
/*
sprintf(tbs, »%2d mV « ,millivolt);
Serial.print(tbs);
*/
ppm = CO2_OFFSET + int(millivolt * 2.925 – 1201);
return ppm;
}

/*************************************************************************/
/*Lecture unitaire sonde CO2 en PWM*/
int Read_PWM_MHZ14A(byte sensorpin)
{
byte tentative=0;
unsigned long th, tl, ppm_pwm = 0;
do
{
th = pulseIn(sensorpin, HIGH, 1004000) / 1000;
tl = 1004 – th;
ppm_pwm = CO2_OFFSET + 5000 * (th – 2) / (th + tl – 4);
tentative++;
}
while (th == 0 && tentative<100);

return ppm_pwm;
}

/**************************************************************************/
/*Voir s’il faut mettre à jour l’horloge RTC et si oui le faire*/
bool UpdateRTC()
{
/**************************************************************************/
/* Variables locales de date-heure de compilation et d’horloge RTC */
char stcompil[15];
char strtc[15];
/*Variabes indiquant si on a du remettre à jour l’horloge RTC*/
bool parse = false;
bool config = false;
/*Obtenir la date et l’heure auxquelles ce programme a été compilé*/
if (getDate(__DATE__) && getTime(__TIME__) && RTC.read(tm))
{
parse = true;
/*et mettre à jour le module RTC avec cette date/heure si celle-ci est > la date du RTC*/
sprintf(strtc, « %04d%02d%02d%02d%02d », tmYearToCalendar(tm.Year), tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
sprintf(stcompil, « %04d%02d%02d%02d%02d », tmYearToCalendar(tm_compil.Year), tm_compil.Month, tm_compil.Day, tm_compil.Hour, tm_compil.Minute, tm_compil.Second);
/*
Serial.println(F(« Heure RTC actuelle : « ));
sprintf(myStg, « %02d/%02d/%04d %02d:%02d », tm.Day, tm.Month, tmYearToCalendar(tm.Year), tm.Hour, tm.Minute, tm.Second);
Serial.println(myStg);
Serial.println(F(« Heure de compilation : « ));
sprintf(myStg, « %02d/%02d/%04d %02d:%02d », tm_compil.Day, tm_compil.Month, tmYearToCalendar(tm_compil.Year), tm_compil.Hour, tm_compil.Minute, tm_compil.Second);
Serial.println(myStg);

Serial.print(« Compil : « );
Serial.println(stcompil);
Serial.print(« RTC : « );
Serial.println(strtc);
*/

/*Serial.println(strcmp(stcompil,strtc),DEC);*/
if (strcmp(stcompil,strtc)>0)
{
/*Serial.println(F(« Mise à l’heure nécessaire »));*/

if (RTC.write(tm_compil))
{
Serial.println(F(« Mise à l’heure réussie »));
config = true;
}
else
{
Serial.println(F(« Echec de la mise à l’heure »));
}

}
else
{
config = true;
}
}
return config;
}

/**************************************************************************/
/*Affiche la date et l’heure*/
/**************************************************************************/
void print_dateheure()
{
clearLCD(0,0,15);
lcd.setCursor(0, 0);
if (RTC.read(tm))
{
sprintf(myStg, « %02d/%02d/%04d %02d:%02d », tm.Day, tm.Month, tmYearToCalendar(tm.Year), tm.Hour, tm.Minute, tm.Second);
/*Serial.print(myStg);*/
/*Serial.print( » « );*/
lcd.print(myStg);
clearLCD(1,0,15);
lcd.setCursor(0, 1);
lcd.print(« Please wait »);
}
else
{
if (RTC.chipPresent())
{
Serial.println(F(« RTC is stopped »));
lcd.print(F(« RTC is stopped »));
}
else
{
Serial.println(F(« RTC read error! »));
lcd.print(F(« RTC error »));
}
}
}

/****************************************************************************/
/*Afficher la partie heure */
/****************************************************************************/
void print_heure()
{
if (tm.Day<=31)
{
sprintf(myStg, « %02d:%02d », tm.Hour, tm.Minute);
}
else
{
sprintf(myStg, « %s », « Error »);
}
lcd.setCursor(11, 1);
lcd.print(myStg);
}

/**************************************************************************/
/*Effacer une ligne de l’écran LCD de la position deb à la position fin */
/**************************************************************************/
void clearLCD(byte line,byte deb,byte fin)
{
lcd.setCursor(deb ,line);
for(int n = deb; n <= fin; n++) // 20 indicates symbols in line. For 2×16 LCD write – 16
{
lcd.print( » « );
}
}

/**************************************************************************/
/*Affiche les valeurs (Port série + LCD*/
/**************************************************************************/
void Affiche_DHT(byte temp, byte humid)
{
clearLCD(0,0,15);
/*Affiche Température*/
sprintf(myStg, « Temp:%dC », temp);
Serial.print(myStg);
Serial.print( » « );
lcd.setCursor(0, 0);
lcd.print(myStg);
/*Affiche Humidite*/
sprintf(myStg, « Hd:%d », humid);
Serial.println(myStg);
lcd.setCursor(11, 0);
lcd.print(myStg);
}

/**************************************************************************/
/*Affiche les valeurs (Port série + LCD*/
/**************************************************************************/
void Affiche_MHZ(int co2l)
{
clearLCD(1,0,10);
/*Affiche CO2*/
lcd.setCursor(0, 1);
sprintf(myStg, « CO2 :%d », co2);
Serial.println(myStg);
lcd.print(myStg);
}

/**************************************************************************/
/*Initialisation générale */
/**************************************************************************/
void setup()
{
/**************************************************************************/
/* Initialisation du port série */
Serial.begin(9600);
while (!Serial) ; // wait for serial
delay(200);
/**************************************************************************/
/* Initialisation des ports du DHT11*/
pinMode(DHT_PIN, INPUT_PULLUP);
/**************************************************************************/
/* Initialisation des ports du MHZ14A*/
pinMode(MHZ14A_PWM_PIN, INPUT);
/**************************************************************************/
/* initialise l’écran LCD */
/**************************************************************************/
lcd.init();
lcd.backlight();
/**************************************************************************/
Serial.println(F(« Starting … »));
clearLCD(0,0,15);
lcd.setCursor(0, 0);
lcd.print(F(« Starting… »));
Serial.println(F(« Checking clock »));
clearLCD(1,0,15);
lcd.setCursor(0, 1);
lcd.print(F(« Checking clock »));
delay(2000);
UpdateRTC();
delay(2000);
/**************************************************************************/
InitSDCard();
delay(2000);
/**************************************************************************/
print_dateheure();
delay(5000);
/**************************************************************************/
clearLCD(0,0,15);
lcd.setCursor(0, 0);
lcd.print(F(« Temp:Wait »));
clearLCD(1,0,15);
lcd.setCursor(0, 1);
lcd.print(F(« CO2 :Wait »));
/**************************************************************************/
}

 

/**************************************************************************/
/* Boucle du programme */
/**************************************************************************/
void loop()
{
long maintenant = millis();
if ((maintenant > lastSampleTimeDHT + samplePeriodDHT) || (lastSampleTimeDHT == 0))
{
lastSampleTimeDHT = maintenant;
/*Lire l’heure sur le module RTC*/
if (!RTC.read(tm))
{
Serial.print(F(« RTC Clock failed check connexions »));
tm.Day=100;
}
print_heure();
int err = SimpleDHTErrSuccess;
if ((err = dht11.read(&temp, &humid, NULL)) != SimpleDHTErrSuccess)
{
humid = old_humid;
temp = old_temp;
Serial.print(F(« DHT11 failed check connexions or sample time »));
}
else
{
old_temp = temp;
old_humid = humid;
Affiche_DHT(temp,humid);
}
}
if ((maintenant > lastSampleTimeCO2 + samplePeriodCO2) or (lastSampleTimeCO2 == 0))
{
lastSampleTimeCO2 = maintenant;
//Pour utiliser la méthode analogique
// unsigned int vco2=read_ANA_MHZ14A(MHZ14A_ANA_PIN,10, 10);
//Pour utiliser la méthode PWM
unsigned int vco2=Read_PWM_MHZ14A(MHZ14A_PWM_PIN);
if (vco2>=0)
{
co2=vco2;
}
//Pré chauffage : co2 qui indique le max et démarrage il y a moins de 3 minutes, on met le message d’attente
if (maintenant <=PRE_HEAT_TIME)
{
clearLCD(1,0,10);
lcd.setCursor(0, 1);
lcd.print(« CO2 :Wait »);
}
//Sinon : on traite et affiche les données
else
{
if (co2 < 0)
{
co2 = old_co2;
}
else
{
old_co2 = co2;
}
//Affiche les données (Serial et LCD
Affiche_MHZ(co2);
EcrireLigne(tm,co2,temp,humid);
}
}
}

Courbe de concentration en CO2 sur une après-midi

La concentration moyenne était de 400 PPM en 1950, vous noterez l’augmentation. Je vis en pleine campagne, ce qui explique que la concentration reste très raisonnable.
Enfin, la mesure ayant été fait par très beau temps, fenêtres ouvertes, on remarque que la concentration baisse lorsque les ouvrant sont ouverts.