Bonjour à tous,
J'avais créer un système de régulation pour piloter le volet de chauffage de ma voiture. Le régulateur interne étant HS.
Le programme me permettait de configurer l'ouverture du volet de 0 à 100%, ajustement manuel selon la température ressentie.
Et puis, je me suis dit qu'une régulation PID ce serait quand même mieux. Donc je l'ai implémentée. J'ai aussi implémenté un système de menu avec les 2 boutons afin de pouvoir régler le PID à la volée. A cette fin, j'utilise la librairie Onebutton.
Tout cela fonctionne très bien. Du moins quand je fais les essais sur mon bureau. Je test le programme sur un autre arduino en désactivant les fonctions oled pour les tests car l'arduino qui me sert à tester le programme n'en possède pas.
Après injection du programme dans l'arduino de la voiture, les boutons réagissent avec 2 à 3 secondes de latence. Là où un simple clic d'1/2s me permet de changer des valeurs, il faut que je reste appuyé 3s et pour accéder au menu de réglage, appui réglé à 2,5s, je dois rester appuyé 5 à 6s.
Il y a quelque chose dans mon programme qui ralenti la boucle je pense, ou qui pompe de la ressource, ou alors j'ai mal organisé mon programme et il y a des sous-programmes qui se télescopent...
Je ne suis pas assez aguerri en arduino pour déterminer quel est le problème.
Voici mon programme. Est-ce que quelqu'un serait assez aimable pour regarder ce que je pourrais améliorer dedans ?
Toutes les explications quant à l'utilisation de ce programme se trouvent là : Régulation chauffage Hyundai Matrix
Et le programme en lui même est assez bien documenté pour s'y retrouver facilement je pense.
Merci beaucoup.
/**************************************************************************************************
VERSION 6 du 20 décembre 2024 - Version installée le 20/12/2024
Régulation du chauffage de la HyundaiMatrix
- Récupère la température interieure à partir d'un capteur de température OneWire DS18B20
- Récupère la position du papillon de chauffage à partir du potentiomètre interne du moteur
- Pilote le moteur de papillon par impulsions
- Consigne réglable par boutons +/-
- Affiche les informations imporantes sur un écran oled I2C
Version 6 - Ajout d'un menu permettant le réglage des parametres de consigne, Kp, Ki, Kd + sauvegarde en EEPROM
Ajout de la librairie OneButton
Ajout de 4 fonctions de gestion des boutons
Version 5 - Sauvegarde de la température en mémoire EEPROM
Version 4.1 - Ajout de :
Arrondi des mesures à 0,5 près - Fonction arrondir() - Ligne 144
Réglage du PID à 4, 0.2 et 1 - Ligne 50
Limitation du PID de 5 à 98 - Ligne 71
**************************************************************************************************/
// Inclusion des librairies nécessaires au programme
#include <EEPROM.h> // Librairie pour utiliser la mémoire EEPROM de l'arduino (Mémoire de 1ko non-volatile)
#include <PID_v2.h> // Librairie du régulateur PID
#include <OneWire.h> // Librairie OneWire pour le capteur de température
#include <OneButton.h> // Librairie des gestion des 2 boutons
#include <DallasTemperature.h> // Librairie pour le capteur DS18B20
#include "SSD1306AsciiAvrI2c.h" // Librairie de l'écran Oled en I2C
// Initialisation de l'écran
#define I2C_ADDRESS 0x3C // Définition de l'adresse de l'écran
SSD1306AsciiAvrI2c oled; // Initialisation de l'écran
// Initialisation des pins des boutons
#define PIN_BOUTON_PLUS 7
#define PIN_BOUTON_MOINS 12
// Crée un nouveau bouton sur la pin PIN_BOUTON_PLUS
OneButton boutonPlus(PIN_BOUTON_PLUS, true);
// Crée un nouveau bouton sur la pin PIN_BOUTON_MOINS.
OneButton boutonMoins(PIN_BOUTON_MOINS, true);
// Initialisation des pins arduino
const int pinCapteurTemp = 2; // Pin du capteur de température
const int pinOuvertureMoteur = 3; // Pin 1 de commande d'ouverture du moteur
const int pinFermetureMoteur = 4; // Pin 2 de commande de fermeture du moteur
const int pin_potar = A0; // Pin du potentiomètre de volet
// Entrées/Sorties
// Initialisation des variables et constantes
double positionAnaPotar = 0; // Valeur analogique du potentiomètre
double positionPourcentPotar = 0; // Position calculée en % du potentiomètre
double positionVoletPID; // Position du volet calculée par le PID
double echelle_mini = 305; // Valeur mini de l'échelle de mesure du potentiomètre
double echelle_maxi = 720; // Valeur maxi de l'échelle de mesure du potentiomètre
int impulsion = 100; // Durée des impulsions moteur en ms
int menu = 0; // Numéro du menu en cours : 0=consigne / 1=Kp / 2=Ki / 3=KD
int reglage = 0; // Validation du mode réglage 0=non / 1=oui (Byte ??)
// Variables PID
double consigneTemperature; // Consigne de température (°C) que l'on va aller chercher en mémoire EEPROM
double temperatureActuelle = 25; // Température mesurée (°C)
double sortiePID = 0; // Position volet calculée par le PID (0-100 %)
//EEPROM.put(0, 25); // A décommenter au 1er démarrage afin d'initialiser l'EEPROM pour la consigne
//EEPROM.put(1, 5); // A décommenter au 1er démarrage afin d'initialiser l'EEPROM pour le Kp
// EEPROM.put(2, 1); // A décommenter au 1er démarrage afin d'initialiser l'EEPROM pour le Kp
// EEPROM.put(3, 2); // A décommenter au 1er démarrage afin d'initialiser l'EEPROM pour le Kp
double Kp = EEPROM.read(1); //récupère la dernière valeur de Kp en EEPROM
double Ki = EEPROM.read(2); //récupère la dernière valeur de Ki en EEPROM
double Kd = EEPROM.read(3); //récupère la dernière valeur de Kd en EEPROM
// Initialisation du PID(kP, kI, kD, mode: DIRECT/REVERSE)
PID pid(&temperatureActuelle, &sortiePID, &consigneTemperature, Kp, Ki, Kd, DIRECT);
// Initialisation du capteur de température
OneWire oneWire(pinCapteurTemp);
DallasTemperature capteurTemp(&oneWire);
void setup() {
// Initialisation des périphériques
Serial.begin(115200); // Communication série pour débogage
capteurTemp.begin(); // Initialisation du capteur de température
oled.begin(&Adafruit128x64, I2C_ADDRESS); // Initialise l'écran Oled
consigneTemperature = EEPROM.read(0); //Récupère la dernière valeur de consigneTemperature en EEPROM
// Configuration du PID
pid.SetOutputLimits(5, 98); // Limite de la sortie : 0 à 100 %
pid.SetMode(AUTOMATIC); // Activer le PID en mode automatique
// Initialise les broches de commandes du moteur
pinMode(pinOuvertureMoteur, OUTPUT); // Pin d'ouverture en sortie
digitalWrite(pinOuvertureMoteur, LOW); // On met la sortie à 0
pinMode(pinFermetureMoteur, OUTPUT); // Pin de fermeture en sortie
digitalWrite(pinFermetureMoteur, LOW); // On met la sortie à 0
// lie les fonctions du bouton +
boutonPlus.attachClick(clickPlus);
//boutonPlus.attachDoubleClick(doubleclickPlus);
boutonPlus.attachLongPressStart(longPressStartPlus);
//boutonPlus.attachLongPressStop(longPressStopPlus);
//boutonPlus.attachDuringLongPress(longPressPlus);
boutonPlus.setPressMs(2500);
// lie les fonctions du bouton -
boutonMoins.attachClick(clickMoins);
//boutonMoins.attachDoubleClick(doubleclickMoins);
boutonMoins.attachLongPressStart(longPressStartMoins);
//boutonMoins.attachLongPressStop(longPressStopMoins);
//boutonMoins.attachDuringLongPress(longPressMoins);
boutonMoins.setPressMs(2500);
}
void loop() {
// Garde l'oeil sur les boutons:
boutonPlus.tick();
boutonMoins.tick();
positionPapillon(); // Appelle la fonction de lecture de la position du papillon (retourne la variable "positionPourcentPotar")
lectureTemperature(); // Appelle la fonction de lecture de la température (Retourne la variable "temperatureActuelle")
pid.Compute(); // Calcule la commande PID
sortiePID = arrondir(sortiePID);
actionMoteur(); // Fonction d'activation du moteur en fonction de la position du volet
affichageConsole(); // Affichage des valeur sur la console pour débuggage
affichageOled(); // Affichage des valeurs sur l'écran interne
}
// ************** FONCTIONS *************
// Fonction pour arrondit un décimal au multiple de 0,5 le plus proche
float arrondir(float number) {
return round(number / 0.5) * 0.5; // Diviser par 0.5, arrondir au plus proche entier, puis multiplier par 0.5
}
/**************************************************************************************/
// Fonction appellée lorsque l'on appuie sur le bouton + une fois
void clickPlus() {
//Serial.print("Button Plus click.");
// Cette partie permet de naviguer dans les menus
if (reglage == 0) { // Vérification que l'on ne soit pas en mode réglage
menu++; // On incrémente le menu de 1
if (menu == 4) menu = 0; // On bloque les menus à 3
}
affichageConsole(); // Affichage des donnée sur la console série
affichageOled(); // Affichage des valeurs sur l'écran interne
// Cette partie permet d'effectuer les réglages
if (reglage == 1) { // Vérification que l'on soit en mode réglage
if (menu == 0) { // Si on est dans le menu 0 (réglage de la consigne de température)
consigneTemperature += 0.5; // On peut incrémenter
if (consigneTemperature > 30.0) consigneTemperature = 30.0; // On limite à la valeur maxi
EEPROM.write(0, consigneTemperature); // On écrit la valeur en EEPROM
affichageConsole(); // Affichage des donnée sur la console série
affichageOled(); // Affichage des valeurs sur l'écran interne
} //Fin du if (menu == 0)
if (menu == 1) { // On est dans le menu 1 (réglage du Kp)
Kp += 0.5; // On incrémente le Kp
if (Kp < 0) Kp = 0; //On limite le mini à 0
EEPROM.write(1, Kp); // On écrit la valeur en EEPROM
affichageConsole(); // Affichage des donnée sur la console série
affichageOled(); // Affichage des valeurs sur l'écran interne
} // Fin du if (menu==1)
if (menu == 2) { // On est dans le menu 2 (réglage du Ki)
Ki += 0.1; // On incrémente le Ki
if (Ki < 0) Ki = 0; //On limite le mini à 0
EEPROM.write(2, Ki); // On écrit la valeur en EEPROM
affichageConsole(); // Affichage des donnée sur la console série
affichageOled(); // Affichage des valeurs sur l'écran interne
} // Fin du if (menu==2)
if (menu == 3) { // On est dans le menu 3 (réglage du Kd)
Kd += 0.5; // On incrémente le Kd
if (Kd < 0) Kd = 0; //On limite le mini à 0
EEPROM.write(3, Kd); // On écrit la valeur en EEPROM
affichageConsole(); // Affichage des donnée sur la console série
affichageOled(); // Affichage des valeurs sur l'écran interne
} // Fin du if (menu==3)
} // Fin du if (reglage == 1)
} // Fin de la fonction clickPlus
void longPressStartPlus() { // Fonction appellée lorsque l'on appuie longtemps sur le bouton +
//Serial.println("Button Plus longPress start");
reglage = 1;
affichageConsole(); // Affichage des donnée sur la console série
affichageOled(); // Affichage des valeurs sur l'écran interne
} // longPressStartPlus
void clickMoins() { // Fonction appellée lorsque l'on appuie sur le bouton - une fois
// Cette partie permet de naviguer dans les menus
if (reglage == 0) { // Vérification que l'on ne soit pas en mode réglage
menu--; // On incrémente le menu de 1
if (menu == -1) menu = 3; // On bloque les menus à 3
}
affichageConsole(); // Affichage des donnée sur la console série
affichageOled(); // Affichage des valeurs sur l'écran interne
// Cette partie permet d'effectuer les réglages
if (reglage == 1) { // Vérification que l'on soit en mode réglage
if (menu == 0) { // Si on est dans le menu 0 (réglage de la consigne de température)
consigneTemperature -= 0.5; // On peut décrémenter la consigne de température
if (consigneTemperature < 15.0) consigneTemperature = 15.0; // On limite à la valeur mini
EEPROM.write(0, consigneTemperature); // On écrit la valeur en EEPROM
affichageConsole(); // Affichage des donnée sur la console série
affichageOled(); // Affichage des valeurs sur l'écran interne
} //Fin du if (menu == 0)
if (menu == 1) { // On est dans le menu 1 (réglage du Kp)
Kp -= 0.5; // On décrémente le Kp
EEPROM.write(1, Kp); // On écrit la valeur en EEPROM
affichageConsole(); // Affichage des donnée sur la console série
affichageOled(); // Affichage des valeurs sur l'écran interne
} // Fin du if (menu==1)
if (menu == 2) { // On est dans le menu 2 (réglage du Ki)
Ki -= 0.1; // On décrémente le Ki
EEPROM.write(2, Ki); // On écrit la valeur en EEPROM
affichageConsole(); // Affichage des donnée sur la console série
affichageOled(); // Affichage des valeurs sur l'écran interne
} // Fin du if (menu==2)
if (menu == 3) { // On est dans le menu 3 (réglage du Kd)
Kd -= 0.5; // On décrémente le Kd
EEPROM.write(3, Kd); // On écrit la valeur en EEPROM
affichageConsole(); // Affichage des donnée sur la console série
affichageOled(); // Affichage des valeurs sur l'écran interne
} // Fin du if (menu==3)
} // Fin du if (reglage == 1)
} // Fin de la fonction clickMoins
void longPressStartMoins() { // Fonction appellée lorsque l'on appuie longtemps sur le bouton + et sert à sortir du mode réglage
//Serial.println("Button Moins longPress start");
reglage = 0;
affichageConsole(); // Affichage des donnée sur la console série
affichageOled(); // Affichage des valeurs sur l'écran interne
} // longPressStartMoins
/**************************************************************************************/
// Fonction d'acquisition de la position du papillon
void positionPapillon() {
positionAnaPotar = analogRead(pin_potar); // On lit la résistance du potentiomètre
positionPourcentPotar = ((positionAnaPotar - echelle_mini) / (echelle_maxi - echelle_mini)) * 100; // On transforme la valeur en ohm en 0 à 100% selon les échelles mini et maxi
positionPourcentPotar = arrondir(positionPourcentPotar); // On arrondit la valeur à 0,5 près
}
/**************************************************************************************/
// Fonction de lecture de la température
void lectureTemperature() {
capteurTemp.requestTemperatures(); // On emet une demande globale de lecture de température sur le bus
temperatureActuelle = capteurTemp.getTempCByIndex(0); // On récupère les valeurs sur le bus
temperatureActuelle = arrondir(temperatureActuelle); // On arrondit la valeur à 0,5 près
affichageOled(); // On appelle la fonction d'affichage global
}
/**************************************************************************************/
// Fonction de gestion du moteur **************/
void actionMoteur() {
if (positionPourcentPotar < sortiePID) { // Si le volet n'est pas assez ouvert par rapoort à la consigne, alors
if (positionPourcentPotar >= 99) stop(); // Si la papillon est en dessus de 99%, on bloque le moteur
else ouvre(); // sinon on l'ouvre
}
if (positionPourcentPotar > sortiePID + 1) { // Si le volet est trop ouvert par rapport à la consigne, alors
if (positionPourcentPotar <= 1) stop(); // si le papillon est en dessous de 1%, on bloque le moteur
else ferme(); // sinon on ferme le papillon
}
if ((positionPourcentPotar >= (sortiePID - 1)) && (positionPourcentPotar <= (sortiePID + 1))) { // Si le volet est ouvert entre consigne-1 et consigne+1, alors
stop(); // on stoppe le volet
}
}
/**************************************************************************************/
// Fonction d'ouverture du volet (commande du moteur)
void ouvre() {
// oled.setCursor(110, 20); // Place le curseur en bas à droite
// oled.print("+"); // Affiche les impulsions d'ouverture
digitalWrite(pinOuvertureMoteur, HIGH); // On met la pin 1 du moteur à l'état haut
digitalWrite(pinFermetureMoteur, LOW); // On met la pin 2 du moteur à l'état bas
delay(impulsion); // Cette tempo gère le temps de marche du moteur
stop(); // Appel de la fonction qui stoppe le moteur
}
/**************************************************************************************/
// Fonction de fermeture du volet (commande du moteur)
void ferme() {
digitalWrite(pinOuvertureMoteur, LOW); // On met la pin 1 du moteur à l'état bas
digitalWrite(pinFermetureMoteur, HIGH); // On met la pin 2 du moteur à l'état haut
delay(impulsion); // Cette tempo gère le temps de marche du moteur
stop(); // Appel de la fonction qui stoppe le moteur
}
/**************************************************************************************/
// Fonction qui stoppe le volet (commande du moteur)
void stop() {
digitalWrite(pinOuvertureMoteur, LOW); // On met la pin 1 du moteur à l'état bas
digitalWrite(pinFermetureMoteur, LOW); // On met la pin 2 du moteur à l'état bas
}
/**************************************************************************************/
// Fonction d'affichage sur la console série
void affichageConsole() {
//Serial.print("Température actuelle : ");
//Serial.print(temperatureActuelle, 1);
//Serial.print(" °C | Consigne : ");
//Serial.print(consigneTemperature, 1);
//Serial.print(" °C | Sortie PID : ");
//Serial.print(sortiePID, 0);
//Serial.print(" %");
//Serial.print("Papillon: ");
if (positionPourcentPotar <= 1) { // Si le papillon est à moins de 1% d'ouverture
//Serial.print("mini "); // On affiche "mini"
} else {
if (positionPourcentPotar >= 99) { // Sinon si le papillon est à plus de 99%
//Serial.print("maxi "); // on affiche "maxi"
} else {
//Serial.print(positionPourcentPotar, 1); // Dans tous les autres cas on affiche la position en %
//Serial.print(" % ");
}
}
if (positionPourcentPotar < (sortiePID - 1)) { // Si l'ouverture est inférieure à la consigne
//Serial.println("Ouvre "); // On affiche l'ouverture en cours
} else {
if (positionPourcentPotar > (sortiePID + 1)) { // Si l'ouverture est supérieure à la consigne
//Serial.println("Ferme "); // On affiche la fermeture en cours
} else {
//Serial.println("Stand by"); // Sinon on affiche l'attente.
}
}
//Serial.print(" | Réglage ");
//Serial.print(reglage);
//Serial.print(" | Menu ");
//Serial.println(menu);
switch (menu) {
case 0:
//Serial.print(" Consigne Température: ");
//Serial.println(consigneTemperature, 1);
break;
case 1:
//Serial.print(" Kp : ");
//Serial.println(Kp, 1);
break;
case 2:
//Serial.print(" Ki : ");
//Serial.println(Ki, 1);
break;
case 3:
//Serial.print("Kd : ");
//Serial.println(Kd, 1);
break;
}
}
// Fonction d'affichage sur l'écran Oled
void affichageOled() {
oled.setFont(System5x7);
oled.set2X();
oled.setCursor(0, 0);
oled.print("Temp ");
oled.print(temperatureActuelle, 1);
oled.println("c");
// Partie de l'affichage interactif !!
switch (menu) {
case 0: // Quand on est dans la consigne
switch (reglage) {
case 0: // Quand on n'est pas en mode réglage, pas de surbrillance
oled.setInvertMode(0);
break;
case 1: // Quand on est en mode réglage, activation de la surbrillance
oled.setInvertMode(1);
break;
}
oled.print("Cons: ");
oled.print(consigneTemperature, 1);
oled.println("c ");
oled.setInvertMode(0); //Toujours enlever la surbrillance ici !
break;
case 1: // Quand on est sur le Kp
switch (reglage) {
case 0: // Quand on n'est pas en mode réglage, pas de surbrillance
oled.setInvertMode(0);
break;
case 1: // Quand on est en mode réglage, activation de la surbrillance
oled.setInvertMode(1);
break;
}
oled.print("Kp : ");
oled.print(Kp, 1);
oled.println(" "); // Efface les caractère qui restent derrière
oled.setInvertMode(0); //Toujours enlever la surbrillance ici !
break;
case 2: // Quand on est sur le Ki
switch (reglage) {
case 0: // Quand on n'est pas en mode réglage, pas de surbrillance
oled.setInvertMode(0);
break;
case 1: // Quand on est en mode réglage, activation de la surbrillance
oled.setInvertMode(1);
break;
}
oled.print("Ki : ");
oled.print(Ki, 1);
oled.println(" "); // Efface les caractère qui restent derrière
oled.setInvertMode(0); //Toujours enlever la surbrillance ici !
break;
case 3: // Quand on est sur le Kd
switch (reglage) {
case 0: // Quand on n'est pas en mode réglage, pas de surbrillance
oled.setInvertMode(0);
break;
case 1: // Quand on est en mode réglage, activation de la surbrillance
oled.setInvertMode(1);
break;
}
oled.print("Kd : ");
oled.print(Kd, 1);
oled.println(" "); // Efface les caractère qui restent derrière
oled.setInvertMode(0); //Toujours enlever la surbrillance ici !
break;
}
// Fin de la partie interactive
oled.print("PID : ");
oled.println(sortiePID, 1);
oled.print("Volet ");
oled.println(positionPourcentPotar, 1);
}