Je dispose d’une cuve chauffante Brewferm de 27 litres avec un robinet inox. Une bonne vieille cuve… Le défaut de ce type de cuve vient de son imprécision au niveau des températures. Le thermostat est un simple potentiomètre gradué de 10 en 10. Lorsque la cuve passe en chauffe, elle chauffe à fond et comme elle a une très forte inertie, généralement, la température est dépassée de 5 à 10 degrés. Même en essayant de regraduer à force d’essais, même en se disant que l’on chauffe 10°C en dessous histoire de pas dépasser, même avec toutes les astuces possibles, disons-le, ça reste une bonne bouilloire de luxe pour grand buveur de thé
Mon projet était de parvenir à gommer ces défauts par une solution simple, pas chère et réutilisable.
Partant du principe que si je pouvais avoir une température précise et piloter plus finement la chauffe que le simple on off du thermostat de la cuve Brewferm, la solution serait plus proche de mes besoins, à savoir, brasser avec… Je suis parti sur l’idée de programmer mon propre programme de contrôle de chauffe.
Ayant déjà une bonne expérience sur l’Arduino Uno et ayant un peu de matériel disponible et quelques composants d’avance à sacrifier pour l’exercice, je suis parti sur cette solution. J’ai cherché le seul composant qu’il me manquait : une sonde précise étanche dans les plages de température.
Ensuite pour afficher la température précise à atteindre et la dernière température lue, il me fallait un afficheur. J’ai opté pour un afficheur 1602 A 2x16 digits que j’avais en stock. Le relai 220V/10A est assuré par un SRD-05-VDC-SL qui du coup trouve sa première et dernière application.
Pour éviter de gérer des entrées clavier, le réglage de la température de chauffe se fait par un potentiomètre dont la valeur va pouvoir être lue par la carte Arduino (valeur 0 à 1023).
S’agissant d’un pilotage d’un courant relativement puissant pour alimenter la cuve, le relai est l’élément qui va le plus être sollicité. J’ai fait un rapide examen des consommations prévues sur le circuit :
- afficheur 1602A : 1.2mA
- sonde DS18B20 : 1.5mA
- relais SRD-05-VDC-SL : 360mW sous 5V -> 72mA, donc au max 80mA
- 2x potentiomètres : 2x 0.5mA
- résistance : 0.25mA
La grosse contrainte sur ce type de projet sur Arduino, c’est la puissance disponible pour piloter les composants du circuit les plus gourmands. La carte Arduino Uno Rev 3 que j’ai utilisée permet de délivrer 40mA par port/plot et 240mA sur le circuit complet. Certains experts indiquent que la carte peut supporter beaucoup plus si la surconsommation est extrêmement ponctuelle et instantanée. Dans mon cas, c'est l’activation du relai pour mettre en chauffe surtout et arrêter la cuve, qui va générer le gros de la charge, qui d’après le datasheet du composant ne devrait pas dépasser 10ms. Un laps de temps assez réduit finalement qui devrait permettre au circuit d'évacuer la chaleur produit, s'il y en a. Il restera donc cependant à surveiller aussi la température du circuit...
Il restait à mettre en musique tout cela sur une plaque d’essai, avec quelques composants périphériques et quelques paquets de nouilles comme il est d’usage
Le schéma du circuit :
Voir la photo dans la galerie
La partie basse tension en détail :
1x Carte Arduino Uno (25€)
1x afficheur 1602 A ou équivalent
1x relai SRD-05-VDC-SL
1x sonde DB18S20 (13€ht)
2x potentiomètre 4,7kohms (valeurs lues 0 à 1024)
1x résistance de 220ohms
Détail de la partie secteur :
Voir la photo dans la galerie
Programme Arduino version test chauffe eau
Pour ma version de programmation d’essai, je suis parti en mode DEBUG (avec du println) et j’ai évité les tableaux et préféré consommer de la variable, plus simple de mon point de vue pour la mise au point, et ça me permet de mieux ajuster les différents niveaux de chauffe.
A force d’essais, je suis arrivé à la conclusion que pour dompter l’inertie de la cuve, il me fallait découper la chauffe en plusieurs phases et qu’il fallait des cycles de chauffe pas trop long, pour le moment, je suis descendu à 30s par cycle, ce qui semble un bon compromis entre vitesse de chauffe et maitrise de la montée en température :
- prg100 : une première phase où la température est loin d’être atteinte (écart > 15°C) et où on peut s’autoriser à libérer les chevaux caloriques de la cuve à 100%, soit 30s de chauffe sur 30s.
- prg1 : une deuxième phase où la température est loin d’être atteinte (écart > 10°C) et où on l’on doit commencer à couper un peu la chauffe pour incurver la pente de montée en température en coupant la chauffe 5s après 25s d’allumage
- prg2 : une troisième phase où la température est plus proche (écart >5°C) et où il faut commencer à couper un peu la chauffe 20s de chauffe, puis 10s de coupure
-prg3 : une quatrième phase où on est à moins de 2 degrés de la température à atteindre et où il faut fournir un effort de chauffe mais beaucoup plus modéré pour arriver en plateau à la bonne température, et là on allume 10s puis on coupe 20s
- prg4 : une cinquième phase où on est à moins de 1°C ou même à température, et là, on va juste devoir maintenir un effort pour se maintenir à la bonne température. Mes tests ont montré que prg4 devait être adapté en fonction de la température à atteindre : pour 63°C, 2s de chauffe/30s suffisent. Mais il faut compter ensuite pour monter à 75°C, 5s de chauffe et 25s de coupure. Puis pour monter à 100°C, il faut compter 10s de chauffe et 20s de coupure.
- sinon, dans les autres cas, on dépasse et on doit couper (et il y a peut-être quelque chose à revoir donc)
Mes premiers essais pour passer de 10°C à 60°C sont concluants, mais un premier essai de montée à 75°C a plafonné à 73°C. J’ai revu le programme 4 comme suit pour augmenter le temps de chauffe / 30s en conséquence : 5s pour monter à 75°C et 10s pour monter à 100°C.
On peut résumer les programmes comme suit :
chauffe t cible < 64 t cible <76 t cible 100
prg100 30 30 30
prg1 25 25 25
prg2 20 20 20
prg3 10 10 10
prg4 2 5 10
Mes tests à partir de là avec de l’eau ont été concluants. Il restera à voir si cela suffit pour un brassin un peu plus dense.
Il reste à voir comment le pilotage va fonctionner pour différents paliers et comment ça peut tenir dans la durée, car le relai peut engendrer de la surchauffe sur la carte. Sur quelques minutes, ça n’est pas possible à voir, mais en testant des maintient en température 1h, je devrais avoir une idée précise d’un éventuel problème ou de la validité de la solution. J’aviserai ensuite. Il existe des solutions type shield de puissance (https://www.manomano.fr/p/motor-power-s ... d=50562460), mais ça a un coup. Si nécessaire, j’y passerai
Résumé de mes tests :
Graphe montée en température de 9°C à 63°C :
Voir la photo dans la galerie
Graphe montée en température de 63°C à 75°C :
Voir la photo dans la galerie
Graphe montée en température de 75°C à 100°C :
Voir la photo dans la galerie
Programme Arduino
Code : Tout sélectionner
#include "TimerOne.h"
#include <LiquidCrystal.h>
#include <OneWire.h>
//SDHT dht;
OneWire ds(2); // Déclarez un objet OneWire lié à la broche 2
int valeur;
int potentiometre=A0;
double valeur_maxi_potentiometre=1024;
double valeur_mini_potentiometre=3;
double temperature_maxi_cible=100; // 100% est pas mal comme maxi !
double temperature_cible_courante=0;
double t1 = 0;
double tprec=0;
double tccprec=0;
//
// tableaux de données pour la sonde DS18B20
//
byte data[12]; // Tableau de données pour stocker les données du capteur
byte addr[8]; // Tableau d'adresses pour stocker l'adresse unique du capteur
float TemperatureSum=0;
//
// Initilisation du temps
//
unsigned long time; // lecture de l'incdice de temps du début d'action
unsigned long time1; // lecture du temps écoulé depuis début d'action
int chauffe=0; // 0=chauffe pas activée, 1=chauffe activée
//
// type_chauffe : plus on est proche de la température à atteindre, moins le temps de chauffe sera long, afin d'atténuer l'inertie de la cuve
//
// si t cible <65°C
//
// à + de 15°C sous la température, on active la prise 30s et on coupe 0s
// à + de 10°C sous la température, on active la prise 25s et on coupe 5s
// à + de 5°C sous la température, on active la prise 20s et on coupe 10s
// à + de 2°C sous la température, on active la prise 10s et on coupe 20s
// à + de 1°C sous la température, on active la prise 2s et on coupe 28s
// sinon, si la temp est > l'objectif, on coupe la prise sinon on l'allume
//
// si t cible <80°C
//
// à + de 15°C sous la température, on active la prise 30s et on coupe 0s
// à + de 10°C sous la température, on active la prise 25s et on coupe 5s
// à + de 5°C sous la température, on active la prise 20s et on coupe 10s
// à + de 2°C sous la température, on active la prise 10s et on coupe 20s
// à + de 1°C sous la température, on active la prise 5s et on coupe 25s
// sinon, si la temp est > l'objectif, on coupe la prise sinon on l'allume
//
//
// si t cible <100°C
//
// à + de 15°C sous la température, on active la prise 30s et on coupe 0s
// à + de 10°C sous la température, on active la prise 25s et on coupe 5s
// à + de 5°C sous la température, on active la prise 20s et on coupe 10s
// à + de 2°C sous la température, on active la prise 10s et on coupe 20s
// à + de 1°C sous la température, on active la prise 10s et on coupe 20s
// sinon, si la temp est > l'objectif, on coupe la prise sinon on l'allume
//
int type_chauffe=0;
int action_chauffe_terminee=0; // 0=non, 1=oui -> on peut repartir sur un type de chauffe différent selon la temp
// initialisation de l'afficheur LCD
// dans l'ordre RS(12), E(11) puis les 4 bits de données D4 à D7
LiquidCrystal lcd(12, 11, 7, 6, 5, 4);
// symbole °
byte degre[8] = {
B00110,
B01001,
B00110,
B00000,
B00000,
B00000,
B00000,
};
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(3, OUTPUT);
pinMode(A0, INPUT);
time=millis();
chauffe=0;
type_chauffe=0;
action_chauffe_terminee=0;
//
// Afficheur 2x16 digits
//
// définition de la dimension de l'afficheur (colonnes, lignes)
lcd.begin(16, 2); // ATTENTION : malgré les apparences, le modèle 161A possède 2 lignes de 8 caractères ... alignées !
// lcd.begin(10, 1) // pour un afficheur à 2 lignes de 16 caractères
// Affichage du message :
lcd.print("Controle de temp."); // les 8 premiers caractères ...
lcd.setCursor(0, 1); // changement de "ligne"
lcd.print("Lancement..."); // les 8 derniers caractères
lcd.createChar(0,degre);
delay(1000);
time=millis();
}
//
// BOUCLE PRINCIPAL
//
void loop() {
valeur = analogRead(potentiometre);
Serial.print("----->Valeur Potentiometre = ");
Serial.println(valeur);
//
// lecture de la température mesurée par la sonde
//
lecture_DS18B20();
t1=trunc(TemperatureSum);
//
// calcul de la température cible lue
//
temperature_cible_courante=trunc(double(valeur*temperature_maxi_cible/(valeur_maxi_potentiometre-valeur_mini_potentiometre)));
Serial.print("***********>t1=");
Serial.print(t1);
Serial.print(", tprec=");
Serial.println(tprec);
Serial.print("***********>temperature_cible_courante=");
Serial.print(temperature_cible_courante);
Serial.print(", tccprec=");
Serial.println(tccprec);
//
// dans ce cas actualiser l'affichage LCD
//
lcd.clear();
lcd.print("Temp.:"); // les 8 premiers caractères ...
lcd.print(int(trunc(t1))); // les 8 derniers caractères
lcd.write(byte(0)); // les 8 premiers caractères ...
lcd.print("C"); // les 8 premiers caractères ...
if (type_chauffe>0) {
if (type_chauffe==100) lcd.print(">*"); else {lcd.print(">");lcd.print(type_chauffe); }
lcd.print(">");
int sec=trunc((time1-time)/1000);
lcd.print(sec);
lcd.print("/30");
}
lcd.setCursor(0, 1); // changement de "ligne"
lcd.print("Cible:"); // les 8 premiers caractères ...
lcd.print(int(trunc(temperature_cible_courante))); // les 8 derniers caractères
lcd.write(byte(0)); // les 8 premiers caractères ...
lcd.print("C"); // les 8 premiers caractères ...
delay(500);
//
// actualiser si une différence est relevée sur une température
//
if(t1!=tprec || temperature_cible_courante!=trunc(double(tccprec))) {
if (temperature_cible_courante!=tccprec) {
//
// la température cible a changée -> la prendre en compte
//
Serial.print("----->Réglage température cuve sur ");
Serial.println(temperature_cible_courante);
tccprec=temperature_cible_courante;
} else {
//
// la température lue sur la sonde a changé -> la prendre en compte
//
Serial.print("----->température cuve sur ");
Serial.println(t1);
tprec=t1;
}
//
// on coupe la chauffe et on arrête l'action en cours pour se caler sur la nouvelle température
//
chauffe=0;
type_chauffe=0;
action_chauffe_terminee=0;
tccprec=temperature_cible_courante;
Serial.println("MODIFICATION D'UNE TEMPERATURE LUE -> ACTION DE CHAUFFE TERMINEE. On coupe !");
Serial.println("ACTION DE CHAUFFE A DETERMINER !");
digitalWrite(3, LOW); // normalement le port est déjà LOW
}
//
// DANS TOUS LES CAS : CONTROLE DE LA CHAUFFE
//
// si une action de chauffe est en cours, on la laisse aller au bout
//
Serial.print("action_chauffe_terminee=");
Serial.print(action_chauffe_terminee);
Serial.print(",chauffe=");
Serial.print(chauffe);
Serial.print(",type_chauffe=");
Serial.print(type_chauffe);
Serial.print(",time=");
Serial.print(time);
Serial.print(",time1=");
Serial.println(time1);
if (action_chauffe_terminee==0 && chauffe==1) {
time1=millis();
Serial.print("Lecture time1=");
Serial.print(float(time1/1000));
Serial.print(", nb secondes écoulées depuis denier millis() :");
unsigned long ecoulees=(time1-time)/1000;
Serial.println(ecoulees);
// dans tous les cas, si une 30s sont écoulées, on termine l'action en cours, on arrête la prise
if (time1>=time+30000) {
action_chauffe_terminee=1;
chauffe=0;
type_chauffe=0;
Serial.println("ACTION DE CHAUFFE TERMINEE. On coupe !");
digitalWrite(3, LOW); // normalement le port est déjà LOW
} else { // s'il y a une action de chauffe en cours et elle n'est pas terminée
// on controle l'action en cours selon son type de chauffe attendu
switch (type_chauffe) {
case 0: // on vérifie juste que les 30s ne sont pas passées
break;
case 1: // on vérifie que le temps de chauffe n'est pas dépassé
if (time1>=time+25000) {
// on coupe la chauffe
Serial.println("ACTION DE CHAUFFE 1 (25s/5s) TERMINEE. On coupe jusqu'à la fin des 30s !");
digitalWrite(3, LOW); // normalement le port est déjà LOW
}
break;
case 2: // on vérifie que le temps de chauffe n'est pas dépassé
if (time1>=time+20000) {
// on coupe la chauffe
Serial.println("ACTION DE CHAUFFE 2 (20s/10s) TERMINEE. On coupe jusqu'à la fin des 30s !");
digitalWrite(3, LOW); // normalement le port est déjà LOW
}
break;
case 3 : // on vérifie que le temps de chauffe n'est pas dépassé (rappel 5s, puis 55s de coupure)
if (time1>=time+10000) {
// on coupe la chauffe
Serial.println("ACTION DE CHAUFFE 3 (10s/20s) TERMINEE. On coupe jusqu'à la fin des 30s !");
digitalWrite(3, LOW); // normalement le port est déjà LOW
}
break;
case 4 : // on vérifie que le temps de chauffe n'est pas dépassé (rappel 2s, puis 28s de coupure)
int a=2000;
if (temperature_cible_courante<65) { a=2000;}
else if (temperature_cible_courante<80) { a=5000;}
else if (temperature_cible_courante<101) { a=70000;}
else { a=10000;}
if (time1>=time+a) {
// on coupe la chauffe
Serial.println("ACTION DE CHAUFFE 4 (2s/28s) TERMINEE. On coupe jusqu'à la fin des 30s !");
digitalWrite(3, LOW); // normalement le port est déjà LOW
}
case 100 : // on vérifie que le temps de chauffe n'est pas dépassé (rappel 30s, puis 0s de coupure)
if (time1>=time+30000) {
// on coupe la chauffe
Serial.println("ACTION DE CHAUFFE 100 (30s/0s) TERMINEE. On coupe jusqu'à la fin des 30s !");
digitalWrite(3, LOW); // normalement le port est déjà LOW
}
break;
default: // coupé
// on coupe la chauffe
Serial.println("ACTION DE CHAUFFE défaut (0s/30s) TERMINEE. On coupe jusqu'à la fin de la minute !");
digitalWrite(3, LOW); // normalement le port est déjà LOW
break;
}
}
} else if (chauffe==0 && type_chauffe==0) {
//
// si pas d'action en cours ou si action en cours et cette action est terminée, alors on repart sur une nouvelle action
//
chauffe=0;
action_chauffe_terminee=0;
// on repart pour 1 minute, reste à définir le programme
time=millis();
//
// plus on est proche de la température à atteindre, moins le temps de chauffe sera long, afin d'atténuer l'inertie de la cuve
// 100 : à + de 15°C sous la température, on active la prise 30s et on coupe 0s
// 1: à + de 10°C sous la température, on active la prise 25s et on coupe 5s
// 2: à + de 5°C sous la température, on active la prise 20s et on coupe 10s
// 3: à + de 2°C sous la température, on active la prise 10s et on coupe 20s
// 4: à + de 1°C sous la température, on active la prise 2s et on coupe 28s
// sinon, si la temp est > l'objectif, on coupe la prise sinon on l'allume
if (t1+15<temperature_cible_courante) {
//
// type_chauffe 100 on allume 30s puis on coupe 0s
//
type_chauffe=100;
chauffe=1;
action_chauffe_terminee=0;
digitalWrite(3, HIGH);
Serial.println("CHOIX TYPE CHAUFFE 100 (30s/0s). On allume !");
} else if (t1+10<temperature_cible_courante) {
//
// type_chauffe 1 on allume 25s puis on coupe 5s
//
type_chauffe=1;
chauffe=1;
action_chauffe_terminee=0;
digitalWrite(3, HIGH);
Serial.println("CHOIX TYPE CHAUFFE 1 (25s/5s). On allume !");
} else if (t1+5<temperature_cible_courante) {
//
// type_chauffe 2 on allume 20s puis on coupe 10s
//
type_chauffe=2;
chauffe=1;
action_chauffe_terminee=0;
digitalWrite(3, HIGH);
Serial.println("CHOIX TYPE CHAUFFE 2 (20s/10s). On allume !");
} else if (t1+2<temperature_cible_courante) {
//
// type_chauffe 3 on allume 10s puis on coupe 20s
//
type_chauffe=3;
chauffe=1;
action_chauffe_terminee=0;
digitalWrite(3, HIGH);
Serial.println("CHOIX TYPE CHAUFFE 3 (10s/20s). On allume !");
} else if (t1==temperature_cible_courante || t1+1<=temperature_cible_courante ) {
//
// type chauffe 4 on allume 2s et on coupe 28s :maintient en température
//
type_chauffe=4;
chauffe=1;
action_chauffe_terminee=0;
digitalWrite(3, HIGH);
Serial.println("CHOIX TYPE CHAUFFE 4 (2s/28s). On allume !");
} else if (t1>temperature_cible_courante) {
Serial.println("=======> Surchauffe !!!!!!!!! On coupe !");
digitalWrite(3, LOW);
chauffe=0;
time=millis();
}
}
tprec=t1;
tccprec=temperature_cible_courante;
}
void lecture_DS18B20() {
if ( !ds.search(addr)) {
Serial.println("Pas de capteur trouvé");
ds.reset_search();
delay(250);
return;
}
if ( OneWire::crc8( addr, 7) != addr[7]) {
Serial.println("CRC incorrect");
return;
}
ds.reset();
ds.select(addr);
ds.write(0x44, 1); // Lancez une conversion de température
delay(1000); // Attendez la fin de la conversion
byte present = ds.reset();
ds.select(addr);
ds.write(0xBE); // Demandez les données de température
for (int i = 0; i < 9; i++) { // Boucle pour lire les données
data[i] = ds.read();
}
ds.reset_search();
byte MSB = data[1];
byte LSB = data[0];
float tempRead = ((MSB << 8) | LSB); // Concaténez les octets pour obtenir la température
TemperatureSum = tempRead / 16;
Serial.print("Température : ");
Serial.print(TemperatureSum);
Serial.println("°C");
}
Il me reste pas mal à faire.
J’ai planqué le tout dans une boite de chaine neige en plastique dur noir, pour évite r l’achat d’un boitier dont le prix aurait multiplié par 10 le cout du prototype.
Mes tests m’ont amené à conserver la partie courant secteur hors de la boite contenant le circuit courant faible, afin de limiter les perturbations du courant secteur sur l’électronique.
Il me restera à fixer tout cela en plus dur soudé sur une plaque quand ce sera au point, et à protéger la partie électronique de l’Arduino des courant forts qui vont circuler dans la partie secteur…
Quelques photos …


