OvenOpenMoniteur: Enregistreur de température pour four à 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 : Enregistrer les données avec une carte SD
Schéma du câblage
Code
Partie 4 : Enregistrer les données du DHT22 (température et humidité ambiantes)
Partie 5 : Intégrer les 4 thermocouples pour l’enregistrement des 4 températures
Partie 6 : Réalisation finale
Câblage :

Schéma du circuit imprimé :

Circuit imprimé :

Figure 7 : Installation du circuit imprimé de l’enregistreur autonome final
Code :
//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 : Essais
Protocole :
- 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

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.
Essai 2 : Cuisson de pains au four électrique particulier
Le 2ème essai est celui de la cuisson de 2 pains dans un lèchefrite.
