Utiliser un capteur de température et d'humidité DHT11 / DHT22 avec une carte Arduino / Genuino

Et non, une humidité relative de 100% ne signifie pas que l'on nage dans une piscine

Image d'entête

par skywodd | | Licence (voir pied de page)

Catégories : Tutoriels Arduino | Mots clefs : Arduino Genuino Humidité DHT11 DHT22 DHT21 AM2302 Capteur Température


Dans ce tutoriel, nous allons apprendre ensemble à communiquer avec un capteur de température et d'humidité, le DHT22 et son petit frère, le DHT11. Nous verrons les caractéristiques de ses capteurs, leurs fonctionnements et comment les utiliser. En bonus, nous verrons comment calculer le point de rosée, pour les amateurs de météo.

Sommaire

Bonjour à toutes et à tous !

Si vous lisez cet article en été, vous êtes sûrement en train de travailler. Il fait chaud, l'air est lourd et vous avez l'impression de vous liquéfier sur votre siège. Voilà une situation des plus classique et des plus désagréable.

Dans ce genre de situation, trois questions vous traverse normalement l'esprit : "Quelle température fait-il ?", "Pourquoi fait-il aussi lourd !?" et "Que ce passerait-il si j'essayé de dormir dans ma baignoire ?".

Savoir mesurer la température et l'humidité d'une zone est une information très utile, en particulier pour prédire la météo ou pour stocker du matériel et des aliments dans de bonnes conditions.

Se pose donc la question suivante : comment mesurer (simplement) à la fois la température et l'humidité d'une pièce ou d'un contenant avec une carte Arduino / Genuino ? C'est le sujet de l'article d'aujourd'hui.

Le capteur DHT22 et son petit frère le DHT11

Photographie d'un capteur DHT11 et DHT22

Capteur DHT22 versus capteur DHT11

Le capteur DHT22 (aussi connu sous la référence AM2302) et son petit frère le DHT11 sont des capteurs de température et d'humidité "deux en un".

Le capteur DHT22 / AM2302 est capable de mesurer des températures de -40 à +125°C avec une précision de +/- 0.5°C et des taux d'humidité relative de 0 à 100% avec une précision de +/- 2% (+/- 5% aux extrêmes, à 10% et 90%). Une mesure peut être réalisée toutes les 500 millisecondes (soit deux fois par seconde).

Le capteur DHT11 est lui capable de mesurer des températures de 0 à +50°C avec une précision de +/- 2°C et des taux d'humidité relative de 20 à 80% avec une précision de +/- 5%. Une mesure peut être réalisée toutes les secondes.

Le DHT22 et le DHT11 sont tous les deux compatibles 3.3 volts et 5 volts (le fabricant recommande cependant de toujours alimenter le capteur en 5 volts pour avoir des mesures précises). Ils ont aussi le même câblage et le même protocole de communication.

Pour résumer, voici les caractéristiques des deux capteurs sous forme de tableau comparatif :

DHT22 / AM2302

DHT11

Humidité (relative %)

0 ~ 100 %

20 ~ 80%

Précision (humidité)

+/- 2% (+/- 5% aux extrêmes)

+/- 5%

Température

-40 ~ +150°C

0 ~ +50°C

Précision (température)

+/- 0.5°C

+/- 2°C

Fréquence mesure max

2Hz (2 mesures par seconde)

1Hz (1 mesure par seconde)

Tension d'alimentation

3 ~ 5 volts

3 ~ 5 volts

Stabilité à long terme

+/- 0.5% par an

+/- 1% par an

Le DHT22 est clairement beaucoup plus précis et stable que le DHT11. Mais il est aussi deux fois plus cher. Le choix se résume donc à une question de balance entre budget, précision et rapidité de mesure.

En toute franchise, si vous voulez des mesures à peu près correctes, utilisez un DHT22, ou mieux, des capteurs d'humidité et de température spécialisés. Les capteurs DHTxx sont assez imprécis en général et tout particulièrement les DHT11 qui sont pour ainsi dire inutilisables en capteur d'humidité tellement la mesure est imprécise.

Le DHT11 ne peut pas mesurer (et supporter) des températures négatives ou des températures supérieures à 50°C. Attention donc en cas d'utilisation en extérieur !

N.B. Les capteurs DHT11 et DHT22 sont disponibles seuls ou précablés sur un petit circuit. C'est le cas par exemple du capteur DHT11 visible sur la photographie ci-dessus (le capteur bleu).

Câblage d'un capteur DHTxx

Diagramme de câblage d'une capteur DHTxx

Diagramme de câblage

Quel que soit votre choix de capteur DHTxx, le câblage est le même.

Les capteurs DHTxx communiquent avec le microcontrôleur via une unique broche d'entrée / sortie, dont on verra ensemble le protocole de communication dans le chapitre suivant.

Le brochage du capteur est le suivant :

  • La broche n°1 est la broche d'alimentation (5 volts ou 3.3 volts).

  • La broche n°2 est la broche de communication. Celle-ci doit impérativement être reliée à l'alimentation via une résistance de tirage de 4.7K ohms (il s'agit d'une sortie à collecteur ouvert).

  • La broche n°3 n'est pas utilisée et ne doit pas être câblée.

  • La broche n°4 est la masse du capteur (GND).

N.B. Un condensateur de 100nF est requis entre les broches n°1 et n°4 pour que le capteur fonctionne correctement. On verra cela lors de la réalisation du montage de démonstration.

Le protocole de communication

Comme précisé précédemment, les capteurs DHTxx ont la particularité de communiquer avec le microcontrôleur via une unique broche d'entrée / sortie.

Bien que cela soit marqué "One Wire" un peu partout sur le document constructeur du capteur, il ne s'agit pas d'un véritable bus de communication 1-Wire. Il s'agit simplement d'un protocole de communication propriétaire, utilisant un seul fil et nécessitant des timings très précis.

Le déroulement d'une communication

Format d'une trame de communication d'un capteur DHTxx

Format d'une trame de communication

Détails du format d'une trame de communication d'un capteur DHT22

Détails du format d'une trame de communication (capteur DHT22)

La communication avec un capteur DHTxx se fait en 3 étapes :

  • Tout d'abord, le microcontrôleur maître (la carte Arduino dans notre cas) réveille le capteur en plaçant la ligne de données à LOW pendant au moins 800µs (au moins 18ms pour le DHT11). Durant ce laps de temps, le capteur va se réveiller et préparer une mesure de température et d'humidité. Une fois le temps écoulé, le maître va libérer la ligne de données et passer en écoute.

  • Une fois la ligne de données libérée, le capteur répond au maître (pour montrer qu'il est bien réveillé) en maintenant la ligne de données à LOW pendant 80µs puis à HIGH pendant 80µs.

  • Le capteur va ensuite transmettre une série de 40 bits (5 octets). Les deux premiers octets contiennent la mesure de l'humidité. Les deux octets suivants contiennent la mesure de la température et le cinquième octet contient une somme de contrôle qui permet de vérifier que les données lues sont correctes.

N.B. La broche de communication des capteurs DHTxx est de type "collecteur ouvert". La sortie du capteur ne génère pas de tension. Elle ne fait que commuter (via un transistor) la tension au niveau de la résistance de tirage sur la ligne de données. Dans ce contexte, HIGH est la tension de la résistance de tirage et LOW la tension à la masse (0 volt). Cela peut paraitre complexe, mais sans cette sortie à collecteur ouvert, il ne serait pas possible d'alimenter le capteur en 5 volts tout en ayant une résistance de tirage à 3.3 volts (dans le cas d'un montage avec une carte Arduino en 3.3 volts derrière).

Détails du format d'une trame de communication d'un capteur DHT11

Détails du format d'une trame de communication (capteur DHT11)

Il est bon de noter que même si le DHT11 et DHT22 partagent le même protocole de communication, la façon dont les valeurs sont transmises est différente.

Le DHT11 ne peut mesurer que des températures supérieures à zéro et à une précision faible qui rend inutile de transmettre des valeurs à virgules. Au contraire, le DHT22 peut mesurer des températures négatives et des valeurs avec une précision à 0.1 unité près.

On verra dans le chapitre "code" la façon dont sont codées les valeurs dans les deux cas.

Le montage de démonstration

Afin de comprendre comment utiliser un capteur DHT22 ou DHT11 avec une carte Arduino, nous allons faire un petit montage de démonstration très simple.

Dans notre montage, la carte Arduino va lire la température et l'humidité ambiante et envoyer les valeurs mesurées à l'ordinateur via le câble USB.

Photographie du matériel nécessaire à la réalisation du montage de démonstration pour un capteur DHT11 ou DHT22

Matériel nécessaire

Pour réaliser ce montage, il va nous falloir :

  • Une carte Arduino UNO (et son câble USB),

  • Un capteur DHT11 ou DHT22 (ou AM2302),

  • Une résistance de 4.7K ohms – code couleur jaune / violet / rouge,

  • Un condensateur de 100nF,

  • Une plaque d'essai et des fils pour câbler notre montage.

N.B. Si vous disposez d'un module précâblé, la résistance et le condensateur doivent normalement déjà être câblés et sont donc inutiles dans ce cas.

Vue prototypage du montage de démonstration pour un capteur DHT11 ou DHT22

Vue prototypage du montage

Vue schématique du montage de démonstration pour un capteur DHT11 ou DHT22

Vue schématique du montage

Pour commencer notre montage, nous allons câbler les deux broches d'alimentation du capteur (broche n°1 et broche n°4) respectivement aux broches 5V et GND de la carte Arduino.

Dans la foulée, on câblera le condensateur de 100nF entre les broches n°1 et n°4 du capteur, en parallèle des broches d'alimentation. Les condensateurs de 100nF ne sont pas polarisés, ils peuvent donc être câblés dans n'importe quel sens.

Photographie du montage de démonstration pour un capteur DHT11 ou DHT22

Le montage fini

On achève ensuite le circuit en reliant la résistance de 4.7K ohms d'un côté à la broche n°1 du capteur (+5v) et de l'autre à la broche n°2 du capteur (sortie du signal). Pour finir, on câble la broche n°2 du capteur à la broche A0 de la carte Arduino avec un fil.

Dans le cas d'une utilisation avec une carte Arduino 3.3 volts, il faudra relier l'alimentation du capteur à la broche 5V de la carte Arduino et la résistance de tirage à la broche 3.3 volts.

Si vous utilisez une carte Arduino 3.3 volts, ne câblez pas la résistance de tirage au 5 volts, sinon vous allez détruire votre carte !

N.B. La broche n°3 du capteur doit rester non connectée. C'est normal.

Le code de démonstration

Le code de démonstration de ce chapitre mesure la température et l'humidité ambiante et affiche le résultat sur le port série. Ce code est conçu pour fonctionner avec des capteurs DHT11 et DHT22.

Je préviens de suite, le code est complexe, très complexe même. Cela est dû aux timings très précis nécessaires pour la communication avec le capteur. Je vais essayer d'expliquer le code le plus simplement possible, mais un cachet d'aspirine risque d'être nécessaire pour finir la lecture.

Si vous voulez simplement utiliser le code sans le comprendre, regardez les commentaires dans le code d'exemple complet un peu plus bas ;)

1
2
3
4
5
6
7
/** Broche "DATA" du capteur */
const byte BROCHE_CAPTEUR = 5;

/* Code d'erreur de la fonction readDHT11() et readDHT22() */
const byte DHT_SUCCESS = 0;        // Pas d'erreur
const byte DHT_TIMEOUT_ERROR = 1;  // Temps d'attente dépassé
const byte DHT_CHECKSUM_ERROR = 2; // Données reçues erronées

Le code commence comme d'habitude avec la déclaration des constantes pour le programme.

Ici on déclare plusieurs constantes : une constante pour la broche du capteur et trois constantes pour les codes d'erreurs de la fonction qui va lire le capteur. Ces trois constantes nous permettront plus tard de détecter et gérer les erreurs de lecture du capteur.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/** Fonction setup() */
void setup() {
 
  /* Initialisation du port série */
  Serial.begin(115200);
  Serial.println(F("Demo DHT11 et DHT22"));
  
  /* Place la broche du capteur en entrée avec pull-up */
  pinMode(BROCHE_CAPTEUR, INPUT_PULLUP);
}

La fonction setup() fait deux choses dans cet exemple : initialiser le port série à une vitesse de 115200 bauds pour l'affichage des valeurs mesurées et mettre la broche du capteur en entrée avec résistance de tirage.

La mise en entrée de la broche n'est pas strictement nécessaire en soi. C'est juste une sécurité pour éviter de réveiller prématurément le capteur lors du démarrage de la carte Arduino.

 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
/** Fonction loop() */
void loop() {
  float temperature, humidity;
 
  /* Lecture de la température et de l'humidité, avec gestion des erreurs */
  // N.B. Remplacer readDHT11 par readDHT22 en fonction du capteur utilisé !
  switch (readDHT22(BROCHE_CAPTEUR, &temperature, &humidity)) {
  case DHT_SUCCESS: 
     
    /* Affichage de la température et du taux d'humidité */
    Serial.print(F("Humidite (%): "));
    Serial.println(humidity, 2);
    Serial.print(F("Temperature (^C): "));
    Serial.println(temperature, 2);
    break;
 
  case DHT_TIMEOUT_ERROR: 
    Serial.println(F("Pas de reponse !")); 
    break;
 
  case DHT_CHECKSUM_ERROR: 
    Serial.println(F("Pb de communication !")); 
    break;
  }
  
  /* Pas plus d'une mesure par seconde */
  // N.B. Avec le DHT22 il est possible de réaliser deux mesures par seconde
  delay(1000);
}

La fonction loop() fait trois choses : elle lit le capteur, affiche le résultat de la lecture et attend une seconde avant de recommencer.

La lecture du capteur se fait via la fonction readDHT22() que l'on étudiera tout à l'heure (une fonction semblable nommée readDHT11() sera aussi présentée pour les utilisateurs de DHT11).

Cette fonction prend en argument le numéro de la broche du capteur à lire et deux pointeurs vers des variables de type float qui contiendront le résultat des mesures.

PS Le & devant les deux noms de variables est une syntaxe qui permet d'obtenir un pointeur sur la variable en question.

Cette fonction retourne un code d'erreur : DHT_SUCCESS si la lecture c'est bien passée, DHT_TIMEOUT_ERROR si le capteur n'a pas répondu et DHT_CHECKSUM_ERROR si le capteur a répondu, mais que les données sont corrompues.

On pourrait utiliser une cascade de if / else pour tester le code de retour, mais cela ne serait pas très pratique à lire par la suite. J'utilise donc un switch / case pour gérer les valeurs de retour possibles.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
byte readDHT11(byte pin, float* temperature, float* humidity) {
  
  /* Lit le capteur */
  byte data[5];
  byte ret = readDHTxx(pin, data, 18, 1000);
  
  /* Détecte et retourne les erreurs de communication */
  if (ret != DHT_SUCCESS) 
    return ret;
    
  /* Calcul la vraie valeur de la température et de l'humidité */
  *humidity = data[0];
  *temperature = data[2];

  /* Ok */
  return DHT_SUCCESS;
}
 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
byte readDHT22(byte pin, float* temperature, float* humidity) {
  
  /* Lit le capteur */
  byte data[5];
  byte ret = readDHTxx(pin, data, 1, 1000);
  
  /* Détecte et retourne les erreurs de communication */
  if (ret != DHT_SUCCESS) 
    return ret;
    
  /* Calcul la vraie valeur de la température et de l'humidité */
  float fh = data[0];
  fh *= 256;
  fh += data[1];
  fh *= 0.1;
  *humidity = fh;
 
  float ft = data[2] & 0x7f;
  ft *= 256;
  ft += data[3];
  ft *= 0.1;
  if (data[2] & 0x80) {
    ft *= -1;
  }
  *temperature = ft;

  /* Ok */
  return DHT_SUCCESS;
}

La fonction readDHT11() permet de lire un capteur DHT11. La fonction readDHT22() permet de lire un capteur DHT22. Jusque là, rien de bien méchant. C'est après que ça se complique.

N.B. J'ai choisi de découper le code en plusieurs fonctions pour limiter le copier-coller ou le code inutile.

Ces deux fonctions commencent de la même façon : on déclare un tableau de 5 octets pour stocker les données du capteur et on appelle la fonction readDHTxx() qui permet de lire un capteur DHT de manière brute, sans convertir les octets lus en valeurs utilisables.

Dans le cas du DHT11, la durée du signal de start est fixée à 18 millisecondes, contre 1 milliseconde pour le DHT22. Dans les deux cas, le timeout est de 1000 µs, soit une milliseconde.

Une fois le capteur lu, les deux fonctions suivent une logique similaire. Premièrement, elles vérifient qu'il n'y a pas eu d'erreur de communication. Si tout va bien, elles transforment les données brutes en valeurs utilisables, puis renvoient le code d'erreur DHT_SUCCESS qui signifie que tout va bien.

Dans le cas du DHT11, la méthode de calcul de la température et de l'humidité est triviale. Dans le cas du DHT22, c'est beaucoup plus complexe. Il faut gérer la partie entière, la partie décimale et bien prendre en compte le signe pour les températures négatives. Les plus curieux pourront se reporter à la capture d'écran de la documentation constructeur quelques chapitres plus haut ;)

1
2
3
4
5
6
7
/**
 * Fonction bas niveau permettant de lire la température et le taux d'humidité (en valeurs brutes) mesuré par un capteur DHTxx.
 */
byte readDHTxx(byte pin, byte* data, unsigned long start_time, unsigned long timeout) {
  data[0] = data[1] = data[2] = data[3] = data[4] = 0;
  // start_time est en millisecondes
  // timeout est en microsecondes

Accrochez-vous, ça va secouer ;)

La fonction readDHTxx() s'occupe de la communication avec les capteurs. C'est une fonction générique qui se contente de suivre le protocole de communication pour lire les cinq octets de données du capteur, quelle que soit sa référence.

La fonction commence par mettre des zéros dans les cinq octets du tableau de sortie. Cela est nécessaire pour la suite du programme.

1
2
3
4
5
6
7
8
9
  /* Conversion du numéro de broche Arduino en ports / masque binaire "bas niveau" */
  uint8_t bit = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  volatile uint8_t *ddr = portModeRegister(port);   // Registre MODE (INPUT / OUTPUT)
  volatile uint8_t *out = portOutputRegister(port); // Registre OUT (écriture)
  volatile uint8_t *in = portInputRegister(port);   // Registre IN (lecture)
  
  /* Conversion du temps de timeout en nombre de cycles processeur */
  unsigned long max_cycles = microsecondsToClockCycles(timeout);

Ensuite, le code appelle différentes fonctions bas niveau du framework Arduino pour faire correspondre le numéro de broche à divers registres du processeur et masques binaires. Grâce à ces registres et masques, il est possible de manipuler la broche d'entrée sortie, sans avoir à passer par le framework Arduino.

Cela est nécessaire, car les timings requis pour une bonne communication avec un capteur DHT sont si précis que perdre quelques microsecondes avec pinMode(), digitalRead() ou digitalWrite() n'est pas envisageable.

 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
  /* Evite les problèmes de pull-up */
  *out |= bit;  // PULLUP
  *ddr &= ~bit; // INPUT
  delay(100);   // Laisse le temps à la résistance de pullup de mettre la ligne de données à HIGH
 
  /* Réveil du capteur */
  *ddr |= bit;  // OUTPUT
  *out &= ~bit; // LOW
  delay(start_time); // Temps d'attente à LOW causant le réveil du capteur
  // N.B. Il est impossible d'utilise delayMicroseconds() ici car un délai
  // de plus de 16 millisecondes ne donne pas un timing assez précis.
  
  /* Portion de code critique - pas d'interruptions possibles */
  noInterrupts();
  
  /* Passage en écoute */
  *out |= bit;  // PULLUP
  delayMicroseconds(40);
  *ddr &= ~bit; // INPUT
 
  /* Attente de la réponse du capteur */
  timeout = 0;
  while(!(*in & bit)) { /* Attente d'un état LOW */
    if (++timeout == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
  }
    
  timeout = 0;
  while(*in & bit) { /* Attente d'un état HIGH */
    if (++timeout == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
  }

  /* Lecture des données du capteur (40 bits) */
  for (byte i = 0; i < 40; ++i) {
 
    /* Attente d'un état LOW */
    unsigned long cycles_low = 0;
    while(!(*in & bit)) {
      if (++cycles_low == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
    }

    /* Attente d'un état HIGH */
    unsigned long cycles_high = 0;
    while(*in & bit) {
      if (++cycles_high == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
    }
    
    /* Si le temps haut est supérieur au temps bas c'est un "1", sinon c'est un "0" */
    data[i / 8] <<= 1;
    if (cycles_high > cycles_low) {
      data[i / 8] |= 1;
    }
  }
  
  /* Fin de la portion de code critique */
  interrupts();
 
  /*
   * Format des données :
   * [1, 0] = humidité en %
   * [3, 2] = température en degrés Celsius
   * [4] = checksum (humidité + température)
   */
   
  /* Vérifie la checksum */
  byte checksum = (data[0] + data[1] + data[2] + data[3]) & 0xff;
  if (data[4] != checksum)
    return DHT_CHECKSUM_ERROR; /* Erreur de checksum */
  else
    return DHT_SUCCESS; /* Pas d'erreur */
}

Dans l'ordre :

  • le code place la broche en entrée avec résistance de tirage (au cas où cela ne serait pas déjà le cas),

  • le code passe la broche en sortie et envoi le signal de start pour réveiller le capteur,

  • le code désactive temporairement les interruptions pour avoir des timings précis,

  • le code passe la broche en entrée et attend une transition de LOW à HIGH (avec timeout).

  • Pour chacun des 40 bits de données :

    • le code mesure la durée de l'état bas du signal,

    • le code mesure la durée de l'état haut du signal,

    • si l'état haut est plus long que l'état bas, c'est un 1,

  • une fois les 40 bits lus, le code réactive les interruptions,

  • et pour finir, vérifie que la somme de contrôle est valide afin de s'assurer de la bonne réception des données.

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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/**
 * Exemple de code pour la lecture d'un capteur DHT11 ou DHT22.
 */

/** Broche "DATA" du capteur */
const byte BROCHE_CAPTEUR = 5;

/* Code d'erreur de la fonction readDHT11() et readDHT22() */
const byte DHT_SUCCESS = 0;        // Pas d'erreur
const byte DHT_TIMEOUT_ERROR = 1;  // Temps d'attente dépassé
const byte DHT_CHECKSUM_ERROR = 2; // Données reçues erronées

/** Fonction setup() */
void setup() {
 
  /* Initialisation du port série */
  Serial.begin(115200);
  Serial.println(F("Demo DHT11 et DHT22"));
  
  /* Place la broche du capteur en entrée avec pull-up */
  pinMode(BROCHE_CAPTEUR, INPUT_PULLUP);
}

/** Fonction loop() */
void loop() {
  float temperature, humidity;
 
  /* Lecture de la température et de l'humidité, avec gestion des erreurs */
  // N.B. Remplacer readDHT11 par readDHT22 en fonction du capteur utilisé !
  switch (readDHT22(BROCHE_CAPTEUR, &temperature, &humidity)) {
  case DHT_SUCCESS: 
     
    /* Affichage de la température et du taux d'humidité */
    Serial.print(F("Humidite (%): "));
    Serial.println(humidity, 2);
    Serial.print(F("Temperature (^C): "));
    Serial.println(temperature, 2);
    break;
 
  case DHT_TIMEOUT_ERROR: 
    Serial.println(F("Pas de reponse !")); 
    break;
 
  case DHT_CHECKSUM_ERROR: 
    Serial.println(F("Pb de communication !")); 
    break;
  }
  
  /* Pas plus d'une mesure par seconde */
  // N.B. Avec le DHT22 il est possible de réaliser deux mesures par seconde
  delay(1000);
}

/**
 * Lit la température et le taux d'humidité mesuré par un capteur DHT11.
 *
 * @param pin Broche sur laquelle est câblée le capteur.
 * @param temperature Pointeur vers la variable stockant la température.
 * @param humidity Pointeur vers la variable stockant le taux d'humidité.
 * @return DHT_SUCCESS si aucune erreur, DHT_TIMEOUT_ERROR en cas de timeout, ou DHT_CHECKSUM_ERROR en cas d'erreur de checksum.
 */
byte readDHT11(byte pin, float* temperature, float* humidity) {
  
  /* Lit le capteur */
  byte data[5];
  byte ret = readDHTxx(pin, data, 18, 1000);
  
  /* Détecte et retourne les erreurs de communication */
  if (ret != DHT_SUCCESS) 
    return ret;
    
  /* Calcul la vraie valeur de la température et de l'humidité */
  *humidity = data[0];
  *temperature = data[2];

  /* Ok */
  return DHT_SUCCESS;
}

/**
 * Lit la température et le taux d'humidité mesuré par un capteur DHT22.
 *
 * @param pin Broche sur laquelle est câblée le capteur.
 * @param temperature Pointeur vers la variable stockant la température.
 * @param humidity Pointeur vers la variable stockant le taux d'humidité.
 * @return DHT_SUCCESS si aucune erreur, DHT_TIMEOUT_ERROR en cas de timeout, ou DHT_CHECKSUM_ERROR en cas d'erreur de checksum.
 */
byte readDHT22(byte pin, float* temperature, float* humidity) {
  
  /* Lit le capteur */
  byte data[5];
  byte ret = readDHTxx(pin, data, 1, 1000);
  
  /* Détecte et retourne les erreurs de communication */
  if (ret != DHT_SUCCESS) 
    return ret;
    
  /* Calcul la vraie valeur de la température et de l'humidité */
  float fh = data[0];
  fh *= 256;
  fh += data[1];
  fh *= 0.1;
  *humidity = fh;
 
  float ft = data[2] & 0x7f;
  ft *= 256;
  ft += data[3];
  ft *= 0.1;
  if (data[2] & 0x80) {
    ft *= -1;
  }
  *temperature = ft;

  /* Ok */
  return DHT_SUCCESS;
}

/**
 * Fonction bas niveau permettant de lire la température et le taux d'humidité (en valeurs brutes) mesuré par un capteur DHTxx.
 */
byte readDHTxx(byte pin, byte* data, unsigned long start_time, unsigned long timeout) {
  data[0] = data[1] = data[2] = data[3] = data[4] = 0;
  // start_time est en millisecondes
  // timeout est en microsecondes
 
  /* Conversion du numéro de broche Arduino en ports / masque binaire "bas niveau" */
  uint8_t bit = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  volatile uint8_t *ddr = portModeRegister(port);   // Registre MODE (INPUT / OUTPUT)
  volatile uint8_t *out = portOutputRegister(port); // Registre OUT (écriture)
  volatile uint8_t *in = portInputRegister(port);   // Registre IN (lecture)
  
  /* Conversion du temps de timeout en nombre de cycles processeur */
  unsigned long max_cycles = microsecondsToClockCycles(timeout);
 
  /* Evite les problèmes de pull-up */
  *out |= bit;  // PULLUP
  *ddr &= ~bit; // INPUT
  delay(100);   // Laisse le temps à la résistance de pullup de mettre la ligne de données à HIGH
 
  /* Réveil du capteur */
  *ddr |= bit;  // OUTPUT
  *out &= ~bit; // LOW
  delay(start_time); // Temps d'attente à LOW causant le réveil du capteur
  // N.B. Il est impossible d'utilise delayMicroseconds() ici car un délai
  // de plus de 16 millisecondes ne donne pas un timing assez précis.
  
  /* Portion de code critique - pas d'interruptions possibles */
  noInterrupts();
  
  /* Passage en écoute */
  *out |= bit;  // PULLUP
  delayMicroseconds(40);
  *ddr &= ~bit; // INPUT
 
  /* Attente de la réponse du capteur */
  timeout = 0;
  while(!(*in & bit)) { /* Attente d'un état LOW */
    if (++timeout == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
  }
    
  timeout = 0;
  while(*in & bit) { /* Attente d'un état HIGH */
    if (++timeout == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
  }

  /* Lecture des données du capteur (40 bits) */
  for (byte i = 0; i < 40; ++i) {
 
    /* Attente d'un état LOW */
    unsigned long cycles_low = 0;
    while(!(*in & bit)) {
      if (++cycles_low == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
    }

    /* Attente d'un état HIGH */
    unsigned long cycles_high = 0;
    while(*in & bit) {
      if (++cycles_high == max_cycles) {
        interrupts();
        return DHT_TIMEOUT_ERROR;
      }
    }
    
    /* Si le temps haut est supérieur au temps bas c'est un "1", sinon c'est un "0" */
    data[i / 8] <<= 1;
    if (cycles_high > cycles_low) {
      data[i / 8] |= 1;
    }
  }
  
  /* Fin de la portion de code critique */
  interrupts();
 
  /*
   * Format des données :
   * [1, 0] = humidité en %
   * [3, 2] = température en degrés Celsius
   * [4] = checksum (humidité + température)
   */
   
  /* Vérifie la checksum */
  byte checksum = (data[0] + data[1] + data[2] + data[3]) & 0xff;
  if (data[4] != checksum)
    return DHT_CHECKSUM_ERROR; /* Erreur de checksum */
  else
    return DHT_SUCCESS; /* Pas d'erreur */
}

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).

Bonus : calcule du point de rosée ("dew point")

Le code présenté ci-dessous est en grande partie copier-coller de la bibliothèque DHT11Lib. Seules quelques petites modifications ont été apportées pour rendre le code plus lisible.

Le code ci-dessous permet de calculer une approximation rapide du point de rosée en fonction de la température et de l'humidité ambiante. Le point de rosée est la température à laquelle les gouttelettes d'eau (la rosée) se forment sur les surfaces (comme sur les herbes en extérieur).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/** Fonction de calcul rapide du point de rosée en fonction de la température et de l'humidité ambiante */
double dewPoint(double celsius, double humidity) {

  // Constantes d'approximation
  // Voir http://en.wikipedia.org/wiki/Dew_point pour plus de constantes
  const double a = 17.27;
  const double b = 237.7;

  // Calcul (approximation)
  double temp = (a * celsius) / (b + celsius) + log(humidity * 0.01);
  return (b * temp) / (a - temp);
}

N.B. La page Wikipedia sur le calcul du point de rosée dispose d'un certain nombre de constantes pour l'approximation de résultat en fonction de la plage de température et de la pression atmosphérique. N'hésitez pas à aller y faire un tour pour voir.

Bonus : conversion degrés Celsius vers Fahrenheit ou Kelvin

Pour les amateurs d'unités de mesure anglaises, voici la classique formule de conversion de degrés Celsius vers degrés Fahrenheit :

1
2
3
double celsiusToFahrenheit(double celsius) {
  return 1.8 * celsius + 32;
}

Et pour les amateurs de science, voici la formule de conversion de degrés Celsius vers degrés Kelvin :

1
2
3
double celsiusToKelvin(double celsius) {
  return celsius + 273.15;
}

Conclusion

Ce tutoriel est désormais terminé.

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