OvenOpenMoniteur: Enregistreur de températures pour fours à pain OpenSource avec ESP-WROOM-32, DHT22, MAX6675 et Carte SD
15 juin 2026Nous souhaitons créer ici un enregistreur de températures, avec quatre thermosondes de type K et un DHT22. Ce miniprojet s’inscrit dans un travail de thèse sur la comparaison énergétique des fours à pain en boulangerie.
Nous souhaitons comparer le cycle entier de trois fours à pain, de la chauffe et à la déchauffe. Il s’agit de trois fours aux vecteurs énergétiques différents : électrique, bois-énergie, et concentration solaire.
L’objectif de cette installation est de comparer le temps de montée en température de chaque four, le temps de cuisson d’un pain, le plateau de température (saturation), la descente de température pendant l’enfournement, le temps de déchauffe, etc. De même, nous souhaitons corréler le cycle de température avec l’activité des boulanger·ères.
L’installation composée de 4 thermocouples et d’un capteur mesurant la température et l’humidité ambiante. Elle peut être utilisée pour un four, mais aussi pour prendre la mesure d’autres équipements (réfrigérateur e.g.).
Nous proposons ici de partager la construction de notre installation sous forme de tutoriel.
Le code source et le typon pour le matériel final seront disponibles à la fin de cet article ainsi qu’à ce lien.
https://gricad-gitlab.univ-grenoble-alpes.fr/ferrarijprojects/ovenopenmonitor
Si vous faites un copier/coller du code présent dans cette page, les sources sont sous licence CC-BY-NC-SA https://creativecommons.org/licenses/by-nc-sa/3.0/fr/

Partie 1 : Cahier des charges
- Avoir 4 thermocouples pour la mesure de température, résistant à plus de 300°C, mesurant au moins 2 m de long (2 sondes placées à l’avant et le fond du four ; 2 sondes placées dans 2 pains différents)
- Avoir 1 capteur DHT22 pour la prise de température et d’humidité ambiante (connaissance de l’environnement extérieur au four pour une meilleure comparaison)
- Pouvoir enregistrer automatiquement les données sur une période de 24h, à des pas de temps de 30s à 1min -> Horodatage des données et sauvegarde de celles-ci sur une carte SD
- Avoir une installation qui prenne peu de place, et qui soit protégée de la poussière
- Ne pas dépendre du Wifi
- Etre OpenSource et anti-obsolescence programmée (chaque module de la carte peut-être remplacé ou upgradé indépendamment tout en étant rétrocompatible)
Partie 2 : Matériel choisi
L’installation est composée du matériel suivant :
- 4 thermocouples type K, allant de -50°C à +600°C

- 4 modules MAX6675

Un MAX6675 pour connecter chaque thermocouple de type K.
Les thermocouples sont constitués de « deux fils de matériaux différents qui génèrent une force électromotrice (FEM) en fonction de la différence de température entre les deux extrémités. Le MAX6675 utilise une interface SPI pour communiquer avec le microcontrôleur et transférer les lectures de température en sortie. » (https://www.moussasoft.com/max6675-module-thermocouple-avec-arduino/#1-11-description-du-module-max6675)
On pourra retrouver toute la documentation pour la réalisation du câblage ici : https://www.moussasoft.com/max6675-module-thermocouple-avec-arduino/#1-11-description-du-module-max6675.
- Wemos D1 Mini et un shield combinant une RTC (Real Time Clocl) et un lecteur de carte SD

- DHT22 : capteur de température et d’humidité ambiantes

- Carte ESP-WROOM-32

Carte de développement avec interfaces WiFi et Bluetooth
Permet de connecter les capteurs de température, le DHT22 et la carte SD.
- Pile pour l’horloge RTC
Utile pour avoir la date (jour et heure) exacte.
Pile bouton RS PRO, 3 V Lithium Manganèse Dioxyde, 30 mm CR3032 500 mAh

- Logiciel IDE Arduino

Partie 3 : Réalisation finale
Schéma du câblage

Schéma du circuit imprimé

Circuit imprimé

Figure 7 : Installation du circuit imprimé de l’enregistreur autonome final
Code sur IDE Arduino
//Bibliothèques nécessaires pour le fonctionnement du programme
#include "FS.h" //File System : permet de gérer des fichiers, utile pour la carte SD
#include "SD.h" //Bibliothèque pour utiliser une carte SD : permet de lire les fichiers et écrire des données dans la carte SD
#include "SPI.h" //Active la communication SPI (Serial Peripherical Interface). Protocole pour communiquer avec la carte SD et les capteurs
#include "max6675.h" //Bibliothèque pour le module MAX6675 et thermocouple K
#include "RTClib.h" //Bibliothèque pour les modules d’horloge temps réel (RTC) et permettre d’obtenir la date et l’heure en temps réel
#include "DHT.h" //Bibliothèque pour le capteur DHT22 (températures et humidité ambiantes)
RTC_DS1307 rtc;
char daysOfTheWeek[7][12] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
SPIClass spiSD(HSPI);
#define SD_CS 5 // MAX6675 CS@27
//Définition du câblage pour relier les thermocouples au module ESP-WROOM-32
int thermoDO = 19;
int thermoCLK = 18;
int thermoCS1 = 25;
int thermoCS2 = 26;
int thermoCS3 = 27;
int thermoCS4 = 33;
//Connexion des thermocouples aux modules MAX6675
MAX6675 thermocouple1(thermoCLK, thermoCS1, thermoDO);
MAX6675 thermocouple2(thermoCLK, thermoCS2, thermoDO);
MAX6675 thermocouple3(thermoCLK, thermoCS3, thermoDO);
MAX6675 thermocouple4(thermoCLK, thermoCS4, thermoDO);
File myFile;
#define DHTPIN 32
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
/*
Uncomment and set up if you want to use custom pins for the SPI communication
#define REASSIGN_PINS
int sck = -1;
int miso = -1;
int mosi = -1;
int cs = -1;
*/
//Définition d’une série de fonctions pour traiter les dossiers/fichiers sur la carte SD, qu’on appelera dans notre programme
void listDir(fs::FS &fs, const char *dirname, uint8_t levels) {
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if (!root) {
Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory()) {
Serial.println("Not a directory");
return;
}
//Permet d’afficher le dossier demandé, de l’ouvrir et de vérifier s’il existe
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
Serial.print(" DIR : ");
Serial.println(file.name());
if (levels) {
listDir(fs, file.path(), levels - 1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
//Permet d’explorer le fichier : ouvrir les dossiers et sous-dossiers, parcourt le contenu, sa taille
void createDir(fs::FS &fs, const char *path) {
Serial.printf("Creating Dir: %s\n", path);
if (fs.mkdir(path)) {
Serial.println("Dir created");
} else {
Serial.println("mkdir failed");
}
}
//Permet de créer un dossier sur la carte SD
void removeDir(fs::FS &fs, const char *path) {
Serial.printf("Removing Dir: %s\n", path);
if (fs.rmdir(path)) {
Serial.println("Dir removed");
} else {
Serial.println("rmdir failed");
}
}
//Permet de supprimer un dossier sur la carte SD
void readFile(fs::FS &fs, const char *path) {
Serial.printf("Reading file: %s\n", path);
File file = fs.open(path);
if (!file) {
Serial.println("Failed to open file for reading");
return;
}
Serial.print("Read from file: ");
while (file.available()) {
Serial.write(file.read());
}
file.close();
}
//Ouvre un fichier, tente de le lire et vérifie s’il existe, lit son contenu, affiche le contenu dans le moniteur série et le ferme
void writeFile(fs::FS &fs, const char *path, const char *message) {
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if (!file) {
Serial.println("Failed to open file for writing");
return;
}
if (file.print(message)) {
Serial.println("File written");
} else {
Serial.println("Write failed");
}
file.close();
}
//Permet de créer un fichier, d’écrire du texte
void appendFile(fs::FS &fs, const char *path, const char *message) {
Serial.printf("Appending to file: %s\n", path);
File file = fs.open(path, FILE_APPEND);
if (!file) {
Serial.println("Failed to open file for appending");
return;
}
if (file.print(message)) {
Serial.println("Message appended");
} else {
Serial.println("Append failed");
}
file.close();
}
//Permet d’enregistrer des mesures et de garder un historique (ouverture fichier, ajout d’un texte, fermeture du fichier)
void renameFile(fs::FS &fs, const char *path1, const char *path2) {
Serial.printf("Renaming file %s to %s\n", path1, path2);
if (fs.rename(path1, path2)) {
Serial.println("File renamed");
} else {
Serial.println("Rename failed");
}
}
//Permet de renommer ou déplacer un fichier sur la carte SD
void deleteFile(fs::FS &fs, const char *path) {
Serial.printf("Deleting file: %s\n", path);
if (fs.remove(path)) {
Serial.println("File deleted");
} else {
Serial.println("Delete failed");
}
}
//Permet de supprimer un fichier sur la carte SD
void testFileIO(fs::FS &fs, const char *path) {
File file = fs.open(path);
static uint8_t buf[512];
size_t len = 0;
uint32_t start = millis();
uint32_t end = start;
if (file) {
len = file.size();
size_t flen = len;
start = millis();
while (len) {
size_t toRead = len;
if (toRead > 512) {
toRead = 512;
}
file.read(buf, toRead);
len -= toRead;
}
end = millis() - start;
Serial.printf("%u bytes read for %lu ms\n", flen, end);
file.close();
} else {
Serial.println("Failed to open file for reading");
}
file = fs.open(path, FILE_WRITE);
if (!file) {
Serial.println("Failed to open file for writing");
return;
}
size_t i;
start = millis();
for (i = 0; i < 2048; i++) {
file.write(buf, 512);
}
end = millis() - start;
Serial.printf("%u bytes written for %lu ms\n", 2048 * 512, end);
file.close();
}
//Permet de mesurer la vitesse de lecture/écriture sur la carte SD
//Initialisation du programme
void setup() {
Serial.begin(115200);
while (!Serial) { delay(10); }
//Initialisation du port série
#ifdef REASSIGN_PINS
SPI.begin(sck, miso, mosi, cs);
#endif
//Configuration SPI
spiSD.begin(14, 12, 13, 5); //CLK=14,MISO=12,MOSI=13,CS=5
//Configuration carte SD
//if(!SD.begin(cs)){ //Change to this function to manually change CS pin
if (!SD.begin(5, spiSD)) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
Serial.print("SD Card Type: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
} else if (cardType == CARD_SD) {
Serial.println("SDSC");
} else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize);
/*
listDir(SD, "/", 0);
createDir(SD, "/mydir");
listDir(SD, "/", 0);
removeDir(SD, "/mydir");
listDir(SD, "/", 2);
writeFile(SD, "/hello.txt", "Hello ");
appendFile(SD, "/hello.txt", "World!\n");
readFile(SD, "/hello.txt");
deleteFile(SD, "/foo.txt");
renameFile(SD, "/hello.txt", "/foo.txt");
readFile(SD, "/foo.txt");
testFileIO(SD, "/test.txt");
Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
*/
//Initialisation RTC
if (!rtc.begin()) {
Serial.println("Couldn't find RTC");
Serial.flush();
while (1) delay(10);
}
//vérification fonctionnement de l’horloge, réglage de l’heure et initialisation DHT22
if (!rtc.isrunning()) {
Serial.println("RTC is NOT running, let's set the time!");
// When time needs to be set on a new device, or after a power loss, the
// following line sets the RTC to the date & time this sketch was compiled
//rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
rtc.adjust(DateTime(2026, 5, 01, 6, 35, 00));
}
// When time needs to be re-set on a previously configured device, the
// following line sets the RTC to the date & time this sketch was compiled
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
// rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
dht.begin();
}
//Partie du programme qui tourne en boucle
void loop() {
DateTime now = rtc.now(); //Récupération date et heure actuelle
Serial.print(now.year(), DEC);
Serial.print('/');
Serial.print(now.month(), DEC);
Serial.print('/');
Serial.print(now.day(), DEC);
Serial.print(" (");
Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
Serial.print(") ");
Serial.print(now.hour(), DEC);
Serial.print(':');
Serial.print(now.minute(), DEC);
Serial.print(':');
Serial.print(now.second(), DEC);
Serial.println();
//Affichage date et heure
float h = dht.readHumidity();
// Read temperature as Celsius (the default)
float t = dht.readTemperature();
// Read temperature as Fahrenheit (isFahrenheit = true)
// float f = dht.readTemperature(true);
if (isnan(h) || isnan(t)) {
Serial.println(F("Failed to read from DHT sensor!"));
return;
}
Serial.println(t);
Serial.println(h);
//Affichage humidité (%) et température (°C)
float t1 = thermocouple1.readCelsius();
float t2 = thermocouple2.readCelsius();
float t3 = thermocouple3.readCelsius();
float t4 = thermocouple4.readCelsius();
Serial.print("C1 = ");
Serial.println(t1);
Serial.print("C2 = ");
Serial.println(t2);
Serial.print("C3 = ");
Serial.println(t3);
Serial.print("C4 = ");
Serial.println(t4);
myFile = SD.open("/records.txt", FILE_APPEND);
//Ouverture du fichier « records »
if (myFile) {
Serial.print("Writing to records.txt...");
myFile.println((String(now.year())+"/"+String(now.month())+"/"+String(now.day())+" "+String(now.hour())+":"+String(now.minute())+":"+String(now.second())+ " " +String(h) + "," +String(t) + "," +String(t1) + "," + String(t2) + "," + String(t3) + "," + String(t4)).c_str());
//Ecriture : date heure humidité h, température t, t1, t2, t3, t4
myFile.close(); // Sauvegarde les données sur la carte SD
Serial.println("done.");
}
// Fermeture du fichier
else {
// if the file didn't open, print an error:
Serial.println("error opening hello.txt");
}
// For the MAX6675 to update, you must delay AT LEAST 250ms between reads!
delay(30000); // Délai de 30 secondes entre les mesures
}
Partie 4 : Connecter le dispositif à IDE Arduino
- Brancher l’installation via un câble USB-B Micro
- Vérifier que le port USB soit bien connecté à l’IDE Arduino


- Téléverser le code
- Ouvrir le Moniteur série
- Vérifier que le nombre de baud soit bien le même que celui du programme (ici : 115200)
- Vérifier que les mesures s’affichent correctement et que les valeurs sont cohérentes

Remarque : Pour le 1er essai, nous n’avons pas mis la pile du RTC en place. Nous avons uniquement modifié la date et l’heure dans le programme.
- Vérifier ensuite la création du fichier « records » et l’enregistrement des données

Partie 5 : Protocole
Hypothèses
- Compte tenu de l’activité des boulangers, il nous paraît important de modéliser le cycle entier de la chauffe et de la déchauffe du four. Selon le four utilisé, la déchauffe permet de cuire certains produits (cuisson des brioches après la cuisson du pain pour le four à gueulards (chaleur descendante), et avant la cuisson du pain pour le four électrique) ;
- Nous pouvons modéliser le cycle de chauffe selon 3 phases : la chauffe du four, un plateau correspondant à l’inertie du four et à la cuisson des pains, la déchauffe du four ;
- Nous pensons qu’il est important de caractériser la puissance du four en prenant en compte sa masse et la masse de pain cuite, plutôt que la puissance du four à vide ;
- De même, il est préférable de comparer le rendement de la puissance de chauffe de la masse mise dans le four, plutôt que le rendement de la puissance du four à vide.
Critères de caractérisation quantitatifs que l’on souhaite mesurer
A partir des critères de caractérisation du four sélectionnés, de la définition des 3 systèmes et les données manquantes, nous souhaitons mesurer :
– La température du four à tout instant à 2 endroits différents T_A et T_B (°C)
– La température extérieure T_ext (°C) et celle du fournil T_fou (°C)
– Le temps de chauffe du four 〖Δt〗_0 (min)
– Le temps de cuisson 〖Δt〗_1 (min)
– Le temps de déchauffe du four 〖Δt〗_2 (min)
– La masse de pains dans le four m_pain (kg)
– La capacité calorifique C_p du système (J.K^(-1))
– La masse de bois utilisé m_bois (kg), la teneur en humidité H (%)
– L’irradiation I (W.m^(-2))
En entrée, nous avons :
– La puissance nominale du four électrique P_n (W)
– La puissance théorique du four solaire P_th (W)
A partir de ces valeurs, nous calculerons et analyserons :
– Le pouvoir calorifique intérieur du bois PCI (J.〖kg〗^(-1))
– La puissance de chauffe P_c et de déchauffe P_d du four
– Le rendement de la puissance de chauffe η_c et le rendement de la puissance de déchauffe η_d du four ??
– Les variations de températures (°C) pendant :
– la phase de chauffe 〖ΔT〗_0
– l’enfournement 〖ΔT〗_1
– la cuisson 〖ΔT〗_2
– le défournement 〖ΔT〗_3
– la phase de déchauffe〖 ΔT〗_4
– Les variations de température des 2 étages T_A et T_B pour les fours électrique et solaire (indicateurs d’homogénéité/hétérogénéité de la température du four)
Matériel utilisé
- 2 sondes de température placées dans le four
- 2 sondes dans 2 pains différents (??)
- 1 thermomètre extérieur
- 1 thermomètre dans le fournir
- 1 pyromètre pour mesurer l’irradiation
- 1 humidimètre pour mesurer la teneur en humidité TH du bois
- 1 balance
- 1 montre
Description du protocole de mesures pour les 3 fours
→ Avant le démarrage du four :
- Placer les 2 thermosondes dans le four et relever les températures
- Relever la température extérieure et/ou du fournil
- Déterminer l’essence et la teneur en humidité TH du bois
- Mesurer la masse du bois
- Peser la masse des pains/brioches/biscuits et la masse des moules utilisés (noter le matériau)
- Noter l’heure de démarrage du four
- Noter l’heure du nettoyage du four à gueulard
→ Pendant le cycle de chauffe/cuisson/déchauffe du four :
- Relever l’irradiance, la puissance électrique sur le tableau de commande, les températures des 2 thermosondes toutes les 2 min
- Placer les 2 sondes dans 2 pains (de chaque étage ou endroit différents)
- Relever l’heure d’enfournement
- Continuer de noter les températures des 2 thermosondes dans le four et les 2 dans chaque pain
- Relever l’heure de défournement et continuer à noter les températures des 2 sondes
- Continuer les mesures pour les autres enfournements
- Mesurer la masse de bois restante pour calculer la masse de bois consommée
En parallèle : mesure de la consommation énergétique, l’énergie entrante dans le système de chauffe
Essai 1 : Cuisson d’un Banana Bread au four électrique particulier
Le 1er essai a été fait avec la cuisson d’un Banana Bread dans un four électrique particulier. Nous avons réalisé le graphique (figure).
Pour cela, nous avons disposé 2 thermocouples de type K dans le four, un dans le fond (courbe grise) et un près de la porte (courbe jaune).
Une fois le four chauffé, nous avons disposé une sonde dans le gâteau (courbe bleue claire) et l’avons mis dans le four.
Un dernier capteur est laissé sur le plan de travail, à température ambiante de la cuisine (courbe bleue foncée).
Après la cuisson faite, je récupère la carte SD, je la mets dans l’ordinateur et récupère les données dans le dossier « records ».
Je copie-colle les données sur Excel (format texte CSV) puis je construits les courbes.

Figure 8 : Cycle de chauffe et de déchauffe du four pour la cuisson d’un Banana Bread.

