Datalogger de température autonome avec une carte Arduino et un capteur LM35

Le mystère des chaussettes disparues devra attendre

Image d'entête

par skywodd | | Licence (voir pied de page)

Catégories : Projets | Mots clefs : Arduino Genuino LM35 Température Datalogger


Dans ce projet, nous allons voir ensemble comment fabriquer un datalogger de température autonome avec une carte Arduino, un capteur de température LM35 et quelques lignes de code. Pour ce projet, les mesures de température seront stockées en mémoire EEPROM interne pour une lecture ultérieure.

Sommaire

Bonjour à toutes et à tous !

Chers lecteurs, l'heure est grave. La machine à laver le linge de la maison familiale rétrécit tous les vêtements qu'on lui confie. À chaque lavage, les vêtements en coton perdent une ou deux tailles. La situation ne peut plus durer, il est grand temps d'agir. Les meilleurs enquêteurs du TamiaLab ont été dépêchés sur place pour mener l'enquête.

Le principal suspect dans cette sombre affaire de jeans rétrécis n'est autre que le capteur de température de la machine à laver. Plus le temps passe, plus les machines à laver deviennent complexes. C'est seulement quand on se retrouve à devoir chercher sur internet un code d'erreur en hexadécimal que l'on se rend compte que cela va un peu trop loin (histoire vraie).

Le seul moyen de prouver la culpabilité du capteur de température est de mesurer ladite température. J'ai donc entrepris la conception d'un système de mesure de température autonome.

Le but

Le but est simple : mesurer la température au sein de la machine à laver à intervalle régulier durant deux heures et stocker en mémoire les mesures pour une analyse ultérieure.

La machine doit laver les vêtements à 30°C. Si la température dépasse cette limite durant le cycle de lavage, c'est qu'il y a un problème.

Le montage

Le montage est simple, rapide à assembler et peu couteux. C'est un projet jetable (un "throwie" en langage DIY). Mon but n'est pas de faire un datalogger complet, mais de comprendre pourquoi je suis serré comme un saucisson dans mes jeans et mes chemises.

Photographie du montage de datalogging de température autonome

Le montage

Pour réaliser ce montage, il va falloir :

  • Une carte Arduino chinoise (la version la moins chère, 3€ sur ebay),

  • Un capteur de température LM35DZ (pas besoin de prendre la version qui mesure des températures négatives pour ce projet),

  • Un connecteur pour pile 9 volts (j'ai simplement utilisé le connecteur d'une ancienne pile 9 volts HS).

Le capteur de température est câblé sur la broche A5 de la carte Arduino. Je vous invite à lire mon tutoriel dédié sur le sujet pour plus d'informations.

Le connecteur d'alimentation est câblé sur les broches GND et VIN de la carte Arduino.

Pour finir, le montage est scellé dans un sachet plastique étanche pour survivre au cycle de lavage.

Le code

Le code doit permettre la mesure de la température à intervalles réguliers, de même que la récupération des données en fin de mesure.

1
#include <EEPROM.h>

Pour ce projet, je n'ai besoin de stocker les données que pendant deux heures (le temps d'un cycle de lavage). J'utilise donc la mémoire EEPROM interne de la carte Arduino.

Pour un "vrai" projet de datalogger, il serait plus intéressant d'utiliser une mémoire de grande capacité, comme une carte SD par exemple.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/* Broche du capteur de température LM35DZ */
const byte TEMP_SENSOR_PIN = A5;

/* Broche pour l'activation du logging instantané */
const byte LOG_NOW_PIN = 2;

/* Constantes pour le mode lecture */
const String HELP_CMD = "HELP";
const String READ_CMD = "READ";
const String LOG_CMD = "LOG";
const String ERASE_CMD = "ZERO";
const unsigned long READ_CMD_TIMEOUT = 10000;

/* Constante pour le datalogging */
const unsigned long DELAY_BETWEEN_SAMPLE = 30000;

Le code débute comme toujours avec un certain nombre de constantes. Les constantes TEMP_SENSOR_PIN et LOG_NOW_PIN permettent de définir la broche du capteur de température et de l'interrupteur de début automatique de mesure.

Les autres constantes définissent les commandes textuelles qu'il sera possible d'utiliser pour effectuer diverses actions, une fois les mesures faites.

La dernière constante permet de définir le temps entre deux mesures? J'ai choisi de faire une mesure toutes les trente secondes.

1
2
3
4
5
6
7
/* Buffer temporaire pour les données */
const unsigned int MAX_NB_SAMPLES = 256;
struct DataBuffer {
  unsigned int nb_samples;
  unsigned int samples[MAX_NB_SAMPLES];
};
DataBuffer data = { 0, { 0 } };

Vient ensuite la déclaration de la structure qui va contenir les données. J'utilise une structure pour faciliter la sauvegarde en mémoire EEPROM.

Au lieu de stocker indépendamment le nombre de mesures effectuées et les données de mesures, je stocke la structure toute entiére en mémoire, en une seule opération.

1
2
3
4
/** Fonction de conversion : valeur brute -> température */
float temperature(unsigned int value) {
  return value * 1.1 / 1023.0 * 100.0;
}

La fonction temperature() permet de convertir la sortie brute du capteur de température en une valeur en degré Celsius.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/** Terminal de commande */
void command() {
  
  /* Attends l'ordre de passage en mode lecture */
  Serial.setTimeout(READ_CMD_TIMEOUT);
  bool exit = false;
  while (!exit) {

    /* Lit la commande */
    Serial.print(F("CMD> "));
    String cmd = Serial.readStringUntil('\n');
    Serial.println(cmd);

    /* Interpréte la commande */
    if (cmd == HELP_CMD) {

      /* Menu aide */
      Serial.println(F("-- COMMAND LIST --"));
      Serial.println(F("HELP Display this help"));
      Serial.println(F("READ Read back data from memory"));
      Serial.println(F("LOG Starting logging data at last index"));
      Serial.println(F("ZERO Reset index and data values"));

    } else if (cmd == READ_CMD) {

      /* Affiche les données en mémoire */
      if (data.nb_samples) {
        Serial.println(F("Index; Data; Temperature"));
        for (unsigned int index = 0; index < data.nb_samples; ++index) {
          Serial.print(index);
          Serial.print(F("; "));
          Serial.print(data.samples[index]);
          Serial.print(F("; "));
          Serial.println(temperature(data.samples[index]), 2);
        }
      } else {
        Serial.println(F("No data in memory."));
      }

    } else if (cmd == LOG_CMD) { 

      /* Sortie du mode terminal */
      exit = true;

    } else if (cmd == ERASE_CMD) {  

      /* Vidage de la mémoire */
      Serial.print(F("ERASING ... "));
      data.nb_samples = 0;
      EEPROM.put(0, data);
      Serial.println(F("Memory erased."));        

    } else {
      
      /* Commande inconnue */
      Serial.println(F("ERROR (Bad command)"));
    }
  }
}

La fonction command() permet d'afficher sur le port série un terminal de commande rudimentaire.

Grâce à ce terminal, il est possible de récupérer les données de mesure, d'effacer les données en mémoire, ou de démarrer manuellement la prise de mesures.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/** Fonction setup() */
void setup() {

  /* Initialise le port série */
  Serial.begin(115200);
  
  /* Référence analogique à 1.1 volts pour une meilleure précision */
  analogReference(INTERNAL);
  
  /* Load previous memory data */
  EEPROM.get(0, data);

  /* Lance le terminal de commande si la broche n'est pas activée */
  pinMode(LOG_NOW_PIN, INPUT_PULLUP);
  if (digitalRead(LOG_NOW_PIN) == HIGH) {
    command();
  }
  Serial.println(F("START LOGGING ..."));
}

La fonction setup() initialise le port série pour le terminal de commande, active la référence de tension interne à 1.1 volt pour une meilleure précision de mesure (voir mon article sur le capteur LM35 pour plus d'informations) et charge les données en mémoire EEPROM.

Le fait de charger les données actuellement en mémoire EEPROM permet de reprendre la mesure au même endroit si la carte s'arrête temporairement pour une quelconque raison.

Pour finir, la fonction setup() regarde l'état de la broche de début automatique de mesure. Si la broche est active (état LOW), la mesure commence immédiatement, sinon, le code lance le terminal de commande.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/** Fonction loop() */
void loop() {
  static unsigned long previousMillis = 0;
  unsigned long currentMillis = millis();

  /* Mesure toutes les N millisecondes */
  if (currentMillis - previousMillis >= DELAY_BETWEEN_SAMPLE) {
    previousMillis = currentMillis;

    /* Arret en cas de fin de mémoire */
    if (data.nb_samples == MAX_NB_SAMPLES) {

      /* Empéche la surécriture des données */
      Serial.println(F("LOGGING STOPPED (Out of memory)"));
      for (;;);
    }

    /* Mesure la température */
    data.samples[data.nb_samples] = analogRead(TEMP_SENSOR_PIN);

    /* Debug */
    Serial.print(F("SAMPLE "));
    Serial.print(data.nb_samples);
    Serial.print(F(": "));
    Serial.print(data.samples[data.nb_samples]);
    Serial.print(F(" = "));
    Serial.println(temperature(data.samples[data.nb_samples]), 2);
    
    /* Sauvegarde */
    data.nb_samples += 1;
    EEPROM.put(0, data);
  }
  
  /* Active le mode commande si l'utilisateur passe le caractère '$' sur le port série */
  if (Serial.available() && Serial.read() == '$') {
    command();
    Serial.println(F("RESTART LOGGING ..."));
  } 
}

La fonction loop() s'occupe de faire les mesures. Le code utilise l'astuce vue dans un précédent article pour faire la mesure à un intervalle de temps régulier, tout en scrutant le port série à la recherche du caractère $ qui déclenche le lancement du terminal de commande. Il est ainsi possible de relire les données en mémoire sans avoir à redémarrer la carte Arduino et sans avoir à toucher au câblage.

On remarquera que je sauvegarde en mémoire les données après chaque mesure pour éviter des pertes de données en cas d'arrêt imprévu de la carte. De plus, un test permet d'éviter de dépasser la taille totale de la mémoire de stockage.

Le code complet avec commentaires :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/*
 * Datalogger de température minimaliste et autonome.
 */

#include <EEPROM.h>


/* Broche du capteur de température LM35DZ */
const byte TEMP_SENSOR_PIN = A5;

/* Broche pour l'activation du logging instantané */
const byte LOG_NOW_PIN = 2;

/* Constantes pour le mode lecture */
const String HELP_CMD = "HELP";
const String READ_CMD = "READ";
const String LOG_CMD = "LOG";
const String ERASE_CMD = "ZERO";
const unsigned long READ_CMD_TIMEOUT = 10000;

/* Constante pour le datalogging */
const unsigned long DELAY_BETWEEN_SAMPLE = 30000;


/* Buffer temporaire pour les données */
const unsigned int MAX_NB_SAMPLES = 256;
struct DataBuffer {
  unsigned int nb_samples;
  unsigned int samples[MAX_NB_SAMPLES];
};
DataBuffer data = { 0, { 0 } };


/** Fonction de conversion : valeur brute -> température */
float temperature(unsigned int value) {
  return value * 1.1 / 1023.0 * 100.0;
}


/** Terminal de commande */
void command() {
  
  /* Attends l'ordre de passage en mode lecture */
  Serial.setTimeout(READ_CMD_TIMEOUT);
  bool exit = false;
  while (!exit) {

    /* Lit la commande */
    Serial.print(F("CMD> "));
    String cmd = Serial.readStringUntil('\n');
    Serial.println(cmd);

    /* Interpréte la commande */
    if (cmd == HELP_CMD) {

      /* Menu aide */
      Serial.println(F("-- COMMAND LIST --"));
      Serial.println(F("HELP Display this help"));
      Serial.println(F("READ Read back data from memory"));
      Serial.println(F("LOG Starting logging data at last index"));
      Serial.println(F("ZERO Reset index and data values"));

    } else if (cmd == READ_CMD) {

      /* Affiche les données en mémoire */
      if (data.nb_samples) {
        Serial.println(F("Index; Data; Temperature"));
        for (unsigned int index = 0; index < data.nb_samples; ++index) {
          Serial.print(index);
          Serial.print(F("; "));
          Serial.print(data.samples[index]);
          Serial.print(F("; "));
          Serial.println(temperature(data.samples[index]), 2);
        }
      } else {
        Serial.println(F("No data in memory."));
      }

    } else if (cmd == LOG_CMD) { 

      /* Sortie du mode terminal */
      exit = true;

    } else if (cmd == ERASE_CMD) {  

      /* Vidage de la mémoire */
      Serial.print(F("ERASING ... "));
      data.nb_samples = 0;
      EEPROM.put(0, data);
      Serial.println(F("Memory erased."));        

    } else {
      
      /* Commande inconnue */
      Serial.println(F("ERROR (Bad command)"));
    }
  }
}

/** Fonction setup() */
void setup() {

  /* Initialise le port série */
  Serial.begin(115200);
  
  /* Référence analogique à 1.1 volts pour une meilleure précision */
  analogReference(INTERNAL);
  
  /* Load previous memory data */
  EEPROM.get(0, data);

  /* Lance le terminal de commande si la broche n'est pas activée */
  pinMode(LOG_NOW_PIN, INPUT_PULLUP);
  if (digitalRead(LOG_NOW_PIN) == HIGH) {
    command();
  }
  Serial.println(F("START LOGGING ..."));
}

/** Fonction loop() */
void loop() {
  static unsigned long previousMillis = 0;
  unsigned long currentMillis = millis();

  /* Mesure toutes les N millisecondes */
  if (currentMillis - previousMillis >= DELAY_BETWEEN_SAMPLE) {
    previousMillis = currentMillis;

    /* Arret en cas de fin de mémoire */
    if (data.nb_samples == MAX_NB_SAMPLES) {

      /* Empéche la surécriture des données */
      Serial.println(F("LOGGING STOPPED (Out of memory)"));
      for (;;);
    }

    /* Mesure la température */
    data.samples[data.nb_samples] = analogRead(TEMP_SENSOR_PIN);

    /* Debug */
    Serial.print(F("SAMPLE "));
    Serial.print(data.nb_samples);
    Serial.print(F(": "));
    Serial.print(data.samples[data.nb_samples]);
    Serial.print(F(" = "));
    Serial.println(temperature(data.samples[data.nb_samples]), 2);
    
    /* Sauvegarde */
    data.nb_samples += 1;
    EEPROM.put(0, data);
  }
  
  /* Active le mode commande si l'utilisateur passe le caractère '$' sur le port série */
  if (Serial.available() && Serial.read() == '$') {
    command();
    Serial.println(F("RESTART LOGGING ..."));
  } 
}

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Résultat

Le coupable a réussi à dissimuler son crime une fois de plus. Le sachet étanche s'est rempli d'eau et la lessive à temporaire mise hors service le capteur de température qui n'a pas pu livrer les preuves tant attendues.

Mais l'inspecteur Skywodd est coriace. La machine à laver a peut-être gagné cette bataille, mais pas la guerre. Un nouveau test est prévu et cette fois-ci, le coupable sera démasqué.

Conclusion

Ce projet est désormais terminé.

Si ce projet vous a plu, n'hésitez pas à le commenter sur le forum, à le partager sur les réseaux sociaux et à soutenir le site si cela vous fait plaisir.