PID autotuning code, fuzzy logic, Datalogger system heater chauffant 4-20mA

Pour faire comprendre la régulation PID industrielle à des étudiants, des asservissements de chauffages sont utilisés avec un gradateur 4 20mA et lampe, une PT 100 avec transmetteur 4 20mA.

Ce système permet aussi aux étudiants de rechercher des défaillances.

Le schéma électrique et le TP est téléchargeable sur le lien suivant

Mais le système précèdent n’avait pas d’enregistreur pour connaitre l’influence du paramétrage du PID.
Or, les dataloggers industrielles ont des prix abordables, sauf si l’on prend beaucoup de voies.


Le paramétrage de ce datalogger à 2 entrées est assez facile pour avoir la commande et la mesure de la sortie pour vérifier l’optimisation de la commande du PID.

Mais en attendant que le datalogger industriel precedent arrivent, j’ai dû faire l’acquisition des mesures 4 -20mA avec un TTGO.
Les exemples d’Arduino pour lire le 4-20mA ne manquent pas sur le net.

Voici le programme qui permet de tracer les 2 voies sur l’ecran du TTGO et de récuperer les données via la liaison serie par un fichier .csv via le terminal toutes les secondes.
le TTGO peut rajouter de nombreux autres capteurs.

/TTGO graphic 4_20mA  temperature et out
#include <TFT_eSPI.h>
#include <SPI.h>
#include <OneWire.h>
#include <DallasTemperature.h>

TFT_eSPI tft = TFT_eSPI();  //library   1,4" color 135, 240

//OneWire oneWire(25);       //pin sensor DS18B20     
//DallasTemperature sensors(&oneWire);

float Dec=0;
float T=0;
float DS18;
float temps=0;
float outdec;
float pourcentage;

byte x;   
byte i;


 #define Sleft            0    //switch left
 #define Sright          35   //switch right

   

void trace() {
  tft.drawLine(0,134,240,134,TFT_RED);      // axe Horizontal
  tft.drawLine(0,130,0,0, TFT_CYAN);        // axe vertical
  tft.drawLine(120,134,120,110, TFT_CYAN);  //ligne curseur du milieu

  tft.setCursor(110, 120, 2);// (x,y,taille)
  tft.print("120s");
  tft.setCursor(210, 120, 2);// (x,y,taille)
  tft.print("240");
  
  tft.setCursor(0, 67, 2);// (x,y,taille)
  tft.print("50");
  tft.print("C");

  tft.setCursor(0, 0, 2);// (x,y,taille)
  tft.print("100");
  tft.print("C");
}

void setup() {
  pinMode(33, OUTPUT);
  pinMode(2, INPUT);   //entree analog 220ohms PT100 4 20mA
  pinMode(36, INPUT);   //out  4 20mA gradateur
  pinMode(35, INPUT_PULLUP);       //bouton droit
  
 
 Serial.begin(115200); 
   
  tft.init();
  tft.setRotation(1); //format horizontal (X=0,Y=0) en haut à gauche
  tft.fillScreen(TFT_BLACK);
  

//sensors.begin();   //DS18B40
analogReadResolution(12);   //12 bits 
  
 tft.setTextColor(TFT_WHITE,TFT_BLACK);   //https://wiki.microduinoinc.com/Tft.drawLine()
trace();
}




void loop() {
//  if (digitalRead(17)==1)   {digitalWrite(17,LOW);}  else {digitalWrite(17,1);} // Le temps du programme sans le delay est de 700 micro seconde.

for (i=0; i<240; i++)  { x++;
temps++;
Dec=analogRead(2);  //volage image intensity   220ohms    mA=(0.02-0.004)*T/100+0.004
T=(Dec-1092)/43.44;     //V=220*mA     V=0.035*T+0.88    Dec=43.44*T+1092 
if (T>130) {T=130;}

outdec=analogRead(36);    //9.7V  à 100%   4.1V à 0%   pont diviseur par 3   
pourcentage=(outdec-1590)/21.7;           //out(V)=((9.7-4.1)*pourcentage+4.1)/3    outdec=21.7*pourcentage+1590
if (pourcentage>99) {pourcentage=99;}
if (pourcentage<=0) {pourcentage=0;}

//   sensors.requestTemperatures();  //DS18B20  
//   DS18=sensors.getTempCByIndex(0);  //en °C

tft.drawPixel(x,134-T,TFT_GREEN);  
//tft.drawPixel(x,134-DS18,TFT_RED);   
tft.drawPixel(x,134-pourcentage,TFT_RED);  

tft.setCursor(50, 0, 4);// (x,y,taille) 
tft.setTextColor(TFT_GREEN,TFT_BLACK);    
tft.print(T,1);tft.print("`"); 
tft.print("C  ");
          

tft.setCursor(150, 0, 4);// (x,y,taille)
tft.setTextColor(TFT_RED,TFT_BLACK);  
tft.print(pourcentage,1); 
tft.print("% ");

Serial.print(temps,0);Serial.print(";");     //terminal recuperer les données dans un terminal serie en fichier CSV
Serial.print(T,1);Serial.print(";");      //tracer les courbes dans excel
Serial.print(pourcentage,1);Serial.println(";");
//Serial.print(DS18,1);Serial.println(";");

delay(999);             //base de temps mais routine d'interruption serait mieux
                          
if(x>=239){                           //rafraichit tout l'ecran
  tft.fillScreen(TFT_BLACK);         //effacement de tout l'ecran
trace();
  x=0;}
   }     //fin tracage

   }  //end loop

Malgré que la temperature est très stable, l’acquisition de la temperature du TTGO a une fluctuation comme on peut l’observer sur la figure suivante.

Évidemment, le TTGO pourrait tout faire le datalogger et - remplacer le régulateur PID Anly AT403

L'ESP32 n'est pas réputé pour la qualité de son ADC qui présente quelques non linéarités.

Il est préférable d'utiliser analogReadMilliVolts() plutôt que analogRead().
analogReadMilliVolts() retourne une valeur calibrée qui intègre une correction de la non linéarité.


et vous prévoyez d'ajouter un filtrage pour éviter les réactions assez fortes de l'asservissement?

1 Like

Effectivement, un filtrage serait bienvenu pour éliminer le bruit sur la température alors qu’il n’y a pas de bruit sur la mesure de la commande.

Pour l’instant, j’ai raccourci les fils sans résultat et sans rajouter de condensateur à la résistance de 220Ω qui permet de lire le courant 4-20mA

On peut observer ce bruit aussi sur la figure suivante


Voici le nouveau programme, ou le TTGO qui mesure juste la température 4-20mA avec filtrage du transmetteur et la sortie du régulateur industriel qui est aussi en 4-20mA.
Un ecreteur de mesure aurait pu etre ajouté pour minimiser le bruit dans ce programme sachant que l’increment de temperature max correspond à cette équation connaissant la puissance et la capacité thermique

//TTGO graphic 4_20mA  temperature et out filtre
#include <TFT_eSPI.h>
#include <SPI.h>
//#include <OneWire.h>
//#include <DallasTemperature.h>

hw_timer_t * timer = NULL;
TFT_eSPI tft = TFT_eSPI();  //library   1,4" color 135, 240

//OneWire oneWire(25);       //pin sensor DS18B20     
//DallasTemperature sensors(&oneWire);

bool flag;
float Dec=0;
float DS18;
float temps=0;
float outdec;
float pourcentage;

byte x=0;   


float T;
 float Te1;  
 float Te2;

 float sortie;
 float sortie1;     //  
 float sortie2;
 float Tfiltre;      //
/*           
     const float a1 =-1.1429;   //2eme ordre  Fc=0.1Hz   10s
     const float a2 =0.41280;   //mesure temp
     const float Gain =14.82; 
     */

     const float a1 =-1.5610;   //2eme ordre  Fc=0.05Hz   20s
     const float a2 =0.64135;   //mesure temp
     const float Gain =49.78; 


 #define Sleft            0    //switch left
 #define Sright          35   //switch right


void IRAM_ATTR timer_isr() {    //routine interruption 1 seconde
  temps++;       
  flag=1;
  }//fin isr

   

void trace() {
  tft.drawLine(0,134,240,134,TFT_RED);      // axe Horizontal
  tft.drawLine(0,130,0,0, TFT_CYAN);        // axe vertical
  tft.drawLine(120,134,120,110, TFT_CYAN);  //ligne curseur du milieu

  tft.setCursor(110, 120, 2);// (x,y,taille)
  tft.print("120s");
  tft.setCursor(210, 120, 2);// (x,y,taille)
  tft.print("240");
  
  tft.setCursor(0, 67, 2);// (x,y,taille)
  tft.print("50");
  tft.print("C");

  tft.setCursor(0, 0, 2);// (x,y,taille)
  tft.print("100");
  tft.print("C");
}

void setup() {
  pinMode(32, OUTPUT);
  pinMode(33, INPUT);   //entree analog 220ohms PT100 4 20mA
  pinMode(36, INPUT);   //out  4 20mA gradateur
  pinMode(35, INPUT_PULLUP);       //bouton droit
//  pinMode(25, INPUT);    //DS18B20
 
 Serial.begin(115200); 
   
  tft.init();
  tft.setRotation(1); //format horizontal (X=0,Y=0) en haut à gauche
  tft.fillScreen(TFT_BLACK);
  

//sensors.begin();   //DS18B40
analogReadResolution(12);   //12 bits 
analogSetAttenuation(ADC_11db);     //defaut
  
 tft.setTextColor(TFT_WHITE,TFT_BLACK);   //https://wiki.microduinoinc.com/Tft.drawLine()
trace();

 uint8_t timer_id = 1;     
  uint16_t prescaler = 80; // Between 0 and 65 535
  int threshold = 1000000; //   1s

  
  timer = timerBegin(timer_id, prescaler, true);
  timerAttachInterrupt(timer, &timer_isr, true);
  timerAlarmWrite(timer, threshold, true);
  timerAlarmEnable(timer);

Dec=analogRead(33);  //initialisation du filtre
T=(Dec-1092)/43.44;
Te1=T;
Te2=T;
sortie=T*Gain;
sortie1=T*Gain;
}




void loop() {
//  if (digitalRead(17)==1)   {digitalWrite(17,LOW);}  else {digitalWrite(17,1);} // Le temps du programme sans le delay est de 700 micro seconde.

if (flag==1)    {
  x++;  //incremente l'abscisse courbe
Dec=analogRead(33);  //volage image intensity   220ohms    mA=(0.02-0.004)*T/100+0.004
//Dec=analogReadMilliVolts(2); 
  Te2=Te1;      //entree(n-2)
  Te1=T;       //entree(n-1)
T=(Dec-1092)/43.44;     //V=220*mA     V=0.035*T+0.88    Dec=43.44*T+1092 
if (T>100) {T=100;}
//if (T>(Te1+1)) {T=(Te1+0.1);}  //limitation de l'increment de temperature en 1 seconde
//if (T<(Te1-1)) {T=(Te1-0.1);}   //ecretteur pour minimiser les bruit

  sortie2=sortie1;      //sortie(n-2)
  sortie1=sortie;       //sortie(n-1)
  sortie=(T+Te1*2+Te2-sortie1*a1-sortie2*a2) ;         //filtre passe pas recursif ordre 2
  Tfiltre=(sortie/Gain);    //gain du filtre    Temperature filtrée

outdec=analogRead(36);    //9.7V  à 100%   4.1V à 0%   pont diviseur par 3  
//outdec=analogReadMilliVolts(36); 
pourcentage=(outdec-1590)/21.7;           //out(V)=((9.7-4.1)*pourcentage+4.1)/3    outdec=21.7*pourcentage+1590
if (pourcentage>99) {pourcentage=99;}
if (pourcentage<=0) {pourcentage=0;}

//   sensors.requestTemperatures();  //DS18B20  
//   DS18=sensors.getTempCByIndex(0);  //en °C

    

tft.drawPixel(x,134-Tfiltre,TFT_GREEN);  
//tft.drawPixel(x,134-DS18,TFT_RED);   
tft.drawPixel(x,134-pourcentage,TFT_RED);  

tft.setCursor(50, 0, 4);// (x,y,taille) 
tft.setTextColor(TFT_GREEN,TFT_BLACK);    
tft.print(Tfiltre,1);tft.print("`"); 
tft.print("C  ");        

tft.setCursor(150, 0, 4);// (x,y,taille)
tft.setTextColor(TFT_RED,TFT_BLACK);  
tft.print(pourcentage,1); 
tft.print("% ");

Serial.print(T,1);Serial.print(";");     //terminal recuperer les données dans un terminal serie en fichier CSV
Serial.print(Tfiltre,1);Serial.print(";");      //tracer les courbes dans excel
Serial.print(pourcentage,1);Serial.println(";");
//Serial.print(DS18,1);Serial.println(";");
                          
if(x>=239){                           //rafraichit tout l'ecran
  tft.fillScreen(TFT_BLACK);         //effacement de tout l'ecran
trace();
  x=0;}

     flag=0;}  //fin flag
 
   }  //end loop

Donc le résultat avec filtre numérique passe bas pour une fréquence de coupure de 20Hz sans l’excréteur.
L’écrêteur permet de minimiser encore mieux ces fluctuations.


Voici une photo de ce qu’il y a l’écran du TTGO avant le filtrage ou l'on voit quelques pixels tres loin de la mesure.

La commande de ce régulateur PID AT403 est très bizarre avec ces sauts de commandes !

Le régulateur AT403 a un autotunnig pour rechercher les valeurs des coefficients du PID.

Il y a différentes manières pour faire cet auto-tunning comme la méthode du « régleur »

http://w3.cran.univ-lorraine.fr/perso/hugues.garnier/Enseignement/Auto/F-Auto-Correcteurs.pdf

Au départ, la commande est tout ou rien avec la mesure qui oscille autour de la consigne avec un coefficient proportionnel relativement grand et qui va diminuer jusqu’à ce que la sortie devient linéaire dans une zone prédéfinie autour de la consigne.

Puis, le coefficient intégral augmente en douceur pour que la température atteigne la consigne sans saturer la sortie du PID. Enfin, le coefficient dérivé augmentera pour anticiper un changement de consigne à la limite de la saturation de la commande mais avec les saturations de la commande cette action dérivée est inutile dans notre process.

Grace au filtre, le bruit de la mesure température est atténué comme on peut l’observer sur la figure suivante

Pour faire un régulateur sur un système de chauffage :

Pour les 2 dernières régulations précédentes tout ou rien, il y aura une légère fluctuation de la sortie de la température en fonction de la période de commutation.
Pour la régulation PWM, la fluctuation de la température maximale correspondra à l’équation suivante dépendant de la capacité thermique sachant que cette fluctuation sera le plus défavorable pour le rapport cyclique de 0.5.


C’est un peu idiot d’utiliser un PID industriel ou l’on a du mal à comprendre de nombreux paramètres par manque d’explication et parce que on ne sait pas comment est le code interne pour savoir parametrer les coefficients.

Les anti windup peuvent être que sur l’intégrateur ou sur la sortie

Certains regulateurs industriels utilisent le coefficient integral en ki (s^-1), d’autres utilisent son inverse le temps integral Ti (s)….

Bref, autant faire le PID avec l’ESP32 !

Avec une période secteur de 20ms, voulant une précision de PWM de 1% sachant que le gradateur a un zéro crossing donc réalisant des périodes secteurs complètent, alors la période de la PWM sera de 2 secondes.

Mais, le choix de l’échantillonnage de la mesure peut toujours de faire toutes les 1 secondes.

Donc, la routine d’interruption timer se fera tous les 20ms et créera une PWM comme sur le programme suivant


void IRAM_ATTR timer_isr() {    //routine interruption 20mseconde
compt++; 
       if (compt==50)   {flag=1;}   //1s  echantillonnage 
       if  (compt>100) {flag=1;compt=0;}  //2s  periode PWM
  }//fin isr

Dans la boule loop
tempson=PWM ;      //tempson=PWM*periode/(timerISR*100)=PWM*2s/(0.02*100)=PWM
if (compt<tempson)  {digitalWrite(32,1) ;}
if (compt>tempson)  {digitalWrite(32,0) ;}

Le proportionnel integrale dérivée discrétisé peut s’écrire de cette façon en saturant l’integral et la sortie.

 erreur1=erreur;
 erreur=consigne-Tfiltre;
 integral=ki*erreur+integral;
 derive=kd*(erreur-erreur1);    //
if (integral>=90) {integral=90;}   // 
if (integral<-90) {integral=-90;}

PWM=kp*erreur+integral+derive;
if (PWM>=100) {PWM=100;}   // PWM en %
if (PWM<=0) {PWM=0;}

Voici le programme complet pour ESP32

//TTGO graphic 4_20mA  temperature filtre et out PID
#include <TFT_eSPI.h>
#include <SPI.h>
//#include <OneWire.h>
//#include <DallasTemperature.h>

hw_timer_t * timer = NULL;
TFT_eSPI tft = TFT_eSPI();  //library   1,4" color 135, 240

//OneWire oneWire(25);       //pin sensor DS18B20     
//DallasTemperature sensors(&oneWire);

bool flag;
float Dec=0;
float DS18;
float compt=0;
float PWM;
float tempson;
float outdec;
float pourcentage;

byte x=0;   


float T;
 float Te1;  
 float Te2;

 float sortie;
 float sortie1;     //n-1  
 float sortie2;
 float Tfiltre;      //

  float consigne=55;
  float erreur1;   
  float erreur;
  float integral;
  float derive;

const float kp=0.2;  
const float ki=0.001;
const float kd=1;
   
/*           
     const float a1 =-1.1429;   //2eme ordre  Fc=0.1Hz   10s
     const float a2 =0.41280;   //mesure temp
     const float Gain =14.82; 
     */

     const float a1 =-1.5610;   //2eme ordre  Fc=0.05Hz   20s
     const float a2 =0.64135;   //mesure temp
     const float Gain =49.78; 


 #define Sleft            0    //switch left
 #define Sright          35   //switch right


void IRAM_ATTR timer_isr() {    //routine interruption 20mseconde
 // if (digitalRead(17)==1)   {digitalWrite(17,LOW);}  else {digitalWrite(17,1);} // 
 compt++; 
       if (compt>=50)   {flag=1;}   //1s  echantillonage 
       if  (compt>100) {flag=1;compt=0;}  //2s  periode PWM
  }//fin isr

   

void trace() {
  tft.drawLine(0,134,240,134,TFT_RED);      // axe Horizontal
  tft.drawLine(0,130,0,0, TFT_CYAN);        // axe vertical
  tft.drawLine(120,134,120,110, TFT_CYAN);  //ligne curseur du milieu

  tft.setCursor(110, 120, 2);// (x,y,taille)
  tft.print("120s");
  tft.setCursor(210, 120, 2);// (x,y,taille)
  tft.print("240");
  
  tft.setCursor(0, 67, 2);// (x,y,taille)
  tft.print("50");
  tft.print("C");

  tft.setCursor(0, 0, 2);// (x,y,taille)
  tft.print("100");
  tft.print("C");
}

void setup() {
  pinMode(32, OUTPUT);   //out PWM
  pinMode(17, OUTPUT);   //
  pinMode(33, INPUT);   //entree analog 220ohms PT100 4 20mA
  pinMode(36, INPUT);   //out  4 20mA gradateur
  pinMode(35, INPUT_PULLUP);       //bouton droit
//  pinMode(25, INPUT);    //DS18B20
 
 Serial.begin(115200); 
   
  tft.init();
  tft.setRotation(1); //format horizontal (X=0,Y=0) en haut à gauche
  tft.fillScreen(TFT_BLACK);
  

//sensors.begin();   //DS18B40
analogReadResolution(12);   //12 bits 
analogSetAttenuation(ADC_11db);     //defaut
  
 tft.setTextColor(TFT_WHITE,TFT_BLACK);   //https://wiki.microduinoinc.com/Tft.drawLine()
trace();

 uint8_t timer_id = 1;     
  uint16_t prescaler = 80; // Between 0 and 65 535
//  int threshold = 1000000; //   1s
  int threshold = 20000; //   20ms
  
  timer = timerBegin(timer_id, prescaler, true);
  timerAttachInterrupt(timer, &timer_isr, true);
  timerAlarmWrite(timer, threshold, true);
  timerAlarmEnable(timer);

Dec=analogRead(33);  //initialisation du filtre
T=(Dec-1092)/43.44;
Te1=T;
Te2=T;
sortie=T*Gain;
sortie1=T*Gain;

erreur=consigne-T;  //initialise la derivée

}




void loop() {
  

tempson=PWM ;      //tempson=PWM*periode/(timerISR*100)=PWM*2s/(0.02*100)
if (compt<tempson)  {digitalWrite(32,1) ;}
if (compt>tempson)  {digitalWrite(32,0) ;}


if (flag==1)    {   //toutes les secondes
  x++;  //incremente l'abscisse courbe t
Dec=analogRead(33);  //volage image intensity   220ohms    mA=(0.02-0.004)*T/100+0.004
//Dec=analogReadMilliVolts(2); 
  Te2=Te1;      //entree(n-2)
  Te1=T;       //entree(n-1)
T=(Dec-1092)/43.44;     //V=220*mA     V=0.035*T+0.88    Dec=43.44*T+1092 
if (T>100) {T=100;}
//if (T>(Te1+1)) {T=(Te1+0.1);}  //limitation de l'increment de temperature en 1 seconde
//if (T<(Te1-1)) {T=(Te1-0.1);}   //ecretteur pour minimiser les bruit

  sortie2=sortie1;      //sortie(n-2)
  sortie1=sortie;       //sortie(n-1)
  sortie=(T+Te1*2+Te2-sortie1*a1-sortie2*a2) ;         //filtre passe pas recursif ordre 2
  Tfiltre=(sortie/Gain);    //gain du filtre    Temperature filtrée
//correcteur Proportionel integral derivee discretisée
 erreur1=erreur;
 erreur=consigne-Tfiltre;
 integral=ki*erreur+integral;
 derive=kd*(erreur-erreur1);    //
if (integral>=90) {integral=90;}   // 
if (integral<-90) {integral=-90;}

PWM=kp*erreur+integral+derive;
if (PWM>=100) {PWM=100;}   // PWM en %
if (PWM<=0) {PWM=0;}

outdec=analogRead(36);    //9.7V  à 100%   4.1V à 0%   pont diviseur par 3  
//outdec=analogReadMilliVolts(36); 
pourcentage=(outdec-1590)/21.7;           //out(V)=((9.7-4.1)*pourcentage+4.1)/3    outdec=21.7*pourcentage+1590
if (pourcentage>99) {pourcentage=99;}
if (pourcentage<=0) {pourcentage=0;}

//   sensors.requestTemperatures();  //DS18B20  
//   DS18=sensors.getTempCByIndex(0);  //en °C

    

tft.drawPixel(x,134-Tfiltre,TFT_GREEN);  
//tft.drawPixel(x,134-DS18,TFT_RED);   
tft.drawPixel(x,134-pourcentage,TFT_RED);  

tft.setCursor(50, 0, 4);// (x,y,taille) 
tft.setTextColor(TFT_GREEN,TFT_BLACK);    
tft.print(Tfiltre,1);tft.print("`"); 
tft.print("C  ");        

tft.setCursor(150, 0, 4);// (x,y,taille)
tft.setTextColor(TFT_RED,TFT_BLACK);  
tft.print(pourcentage,1); 
tft.print("% ");

Serial.print(T,1);Serial.print(";");     //terminal recuperer les données dans un terminal serie en fichier CSV
Serial.print(Tfiltre,1);Serial.print(";");      //tracer les courbes dans excel
Serial.print(pourcentage,1);Serial.println(";");
//Serial.print(DS18,1);Serial.println(";");
                          
if(x>=239){                           //rafraichit tout l'ecran
  tft.fillScreen(TFT_BLACK);         //effacement de tout l'ecran
trace();
  x=0;}

     flag=0;}  //fin flag
 
   }  //end loop

Il est possible de vérifier le fonctionnement du programme de la régulation PWM et le système sous Proteus. Mais, il faudra changer de microcontrôleur.

1 Like

Le zéro crossing de l’optocoupleur permet même de faire une commande toutes les 10ms donc pour une PWM de 100%, une période de la PWM de 1s=100%*0.01s est possible.

Les temps de simulation sur les systèmes chauffant sont long.

Donc pour tester, le programme PID, il a été simulé dans Proteus avec un atmega328 comme on peut l’observer sur la figure suivante pour une consigne de 55°C, un coefficient proportionnel de 0.2 pour avoir une zone linéaire de ±11°C et un coefficient intégral de 0.1.

Il y a un léger dépassement de la température désirée pour ce choix de coefficient. La fluctuation provoquée par de la période de la PWM de 1s est filtré par l’inertie du système chauffant n’est pas observable tellement elle est petite.

En réel, l’utilisation d’une lampe halogène qui a 95% de pertes ou d’une led qui a 30% de pertes, avec une période de 1s de la PWM fait un effet stroboscopique mais qui permet aux étudiants d’observer les dynamiques de la commande (Sinon une résistance).

En pratique, avec l’ESP32 on a utilisé le même PCB que pour la commande du radiateur électrique utilisant un triac (relais statique) donc le nombre de commutation n’est pas problématique comme pour un relais.

En pratique, il y a un léger retard de la mesure de la température qui provoque une oscillation autour de la consigne. De même, le filtre passe bas qui annule le bruit sur la mesure de la température provoque un léger retard.

Voici le programme du PID pour l’Atmega 328

//PID simulation atmega328
//#include <LiquidCrystal.h>
#include <TimerOne.h>
//#include <OneWire.h>
//#include <DallasTemperature.h>


//LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

bool flag;
float Dec=0;
float DS18;
float compt=0;
float PWM;
float tempson;
float outdec;
float pourcentage;

byte x=0;   


float T;
 float Te1;  
 float Te2;

 float sortie;
 float sortie1;     //n-1  
 float sortie2;
 float Tfiltre;      //

  float consigne=55;
  float erreur1;   
  float erreur;
  float integral;
  float derive;

const float kp=5;  
const float ki=0.01;
const float kd=0;
   
           
     const float a1 =-1.1429;   //2eme ordre  Fc=0.1Hz   10s
     const float a2 =0.41280;   //mesure temp
     const float Gain =14.82; 
     
/*
     const float a1 =-1.5610;   //2eme ordre  Fc=0.05Hz   20s
     const float a2 =0.64135;   //mesure temp
     const float Gain =49.78; 
*/


void callback()  {    //interruption toutes les 10m secondes
   // if (digitalRead(17)==1)   {digitalWrite(17,LOW);}  else {digitalWrite(17,1);} // 
 compt++; 
       
       if  (compt>100) {flag=1;compt=0;}  //1s  periode PWM
  
 
 }  //fin callback


void setup() 
{
pinMode(13,OUTPUT); //Set the Heater/Relay Pin as OUTPUT
  pinMode(A0,INPUT);   //mesure LM35
 // pinMode(A2,INPUT);   //consigne variable

  Serial.begin(57600);
  
  //lcd.begin(16, 2);    // Initialisation du LCD
  
 Timer1.initialize(10000);           // initialize timer1 1seconde, and set a 0,1 second period =>  100 000  pour 0.01s  10 000
 Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt

// analogReference( DEFAULT );


//initilisation du filtre

Dec=analogRead(A0);  //initialisation du filtre
T=Dec/2.048; 
Te1=T;
Te2=T;
sortie=T*Gain;
sortie1=T*Gain;

erreur=consigne-T;  //initialise la derivée

}

void loop()  {
tempson=PWM ;      //tempson=PWM*periode/(timerISR*100)=PWM*1s/(0.01*100)
if (compt<tempson)  {digitalWrite(13,1) ;}
if (compt>tempson)  {digitalWrite(13,0) ;}


if (flag==1)    {   //toutes les secondes

Dec=analogRead(A0);  //mesure temperature
  Te2=Te1;      //entree(n-2)
  Te1=T;       //entree(n-1)
T=Dec/2.048;     //V=10mV/°C     Dec=0.01*1024*T/5=2.048*T 
if (T>100) {T=100;}


  sortie2=sortie1;      //sortie(n-2)
  sortie1=sortie;       //sortie(n-1)
  sortie=(T+Te1*2+Te2-sortie1*a1-sortie2*a2) ;         //filtre passe pas recursif ordre 2
  Tfiltre=(sortie/Gain);    //gain du filtre    Temperature filtrée

//correcteur Proportionel integral derivee discretisée
 erreur1=erreur;
 erreur=consigne-Tfiltre;
 integral=ki*erreur+integral;
 derive=kd*(erreur-erreur1);    //
if (integral>=90) {integral=90;}   // 
if (integral<-90) {integral=-90;}

PWM=kp*erreur+integral+derive;
if (PWM>=100) {PWM=100;}   // PWM en %
if (PWM<=0) {PWM=0;}



Serial.print(T,1);Serial.print(";");     //terminal recuperer les données dans un terminal serie en fichier CSV
Serial.print(Tfiltre,1);Serial.print(";");      //tracer les courbes dans excel
Serial.print(PWM,1);Serial.println(";");
//Serial.print(DS18,1);Serial.println(";");
                          


     flag=0;}  //fin flag
 
   }  //end loop



control-heater-PID atmega.zip (215.6 KB)

Si l’animation de la simulation dans Proteus est en temps réel alors le temps de la simulation est aussi long que dans la réalité.

On peut remarquer que la puissance moyenne de la sortie correspond

Evidement dans scilab, il est possible de modéliser tout le système et d’avoir les dynamiques très rapidement.

Il y a un livre qui démocratise assez bien la régulation PID avec la programmation arduino et rasberry. Mais les présentations de la régulation ne prennent pas en compte les saturations, ni les perturbations des systèmes.

Il y a un extrait de ce livre en français ici

https://issuu.com/eimworld/docs/regulation-pid-par-la-pratique-extrait

ou ici en anglais

https://api.pageplace.de/preview/DT0400.9783895765209_A45232942/preview-9783895765209_A45232942.pdf

Le programme d’un PID est sur la page 135 et utilisant une routine interruption à la page 152

Avec le nombres d’objets domestiques qui nous entourent, ce livre pourrait etre un peu plus pragmatique.

Aujourd’hui, les régulateurs industriels font de l’auto tunning pour faire le réglage des coefficient du PID mais cela sera une autre histoire

1 Like

Les systèmes non linéaires sont nombreux avec des retards de mesures, des saturations, des zones mortes, des exposants, du bruit donc chacun ont leurs particularités…

Don, le paramétrage du PID va avoir des grosses différences en fonction de ces différentes non linéarités.

Les méthodes anto-tunning de PID sont aussi très nombreuses. Une des plus simples est par la méthode d’identification de savoir comment réagit le système à un échelon en boucle ouverte ou de façon fréquentiel.

Pour une réponse à un échelon, les systèmes chauffant peuvent se mettre en général sous la forme du modèle du premier ordre avec un retard correspondant aux équations suivantes laplace et en fonction du temps :

Les mesures pour faire l’identification ont maintes possibilités en fonction de leur dynamiques de sortie.

La méthode de Broida avec l’identification à un échelon demande d’avoir le gain statique RTH avec une mesure de régime établi qui est long à faire par le micro car celui-ci doit attendre que la pente de la sortie soit presque nulle. L’indentification de Strejc est valable pour les modèles du second ordre avec une pente nulle lors du démarrage.

https://freddy.mudry.org/public/NotesApplications/NAPidAj_06.pdf

Mais pour les systèmes chauffants, il y a une relation entre le gain statique et la constante de temps correspondant à la résistance thermique RTH.

Le micro peut donc connaitre rapidement la capacité thermique avec la mesure de l’inverse de pente à l’origine du système. Par conséquent, l’identification peut se faire avec les équations suivantes puis déterminer les coefficients du PID avec la méthode ziegler.

La méthode ziegler est indépendante du gain statique en boucle ouverte.

Par contre, la méthode ziegler demande à la régulation une grande puissance d’entrée et va provoquer une régulation avec une zone linéaire faible et un coefficient intégral provoquant un dépassement et une oscillation faible autour de la consigne.

La précision de l’identification par le micro est relativement bonne et donne des coefficients qui optimise les dynamiques du système chauffant.

Le temps de l’identification peut correspondre à 4 fois le retard mesuré donc dure 10% de la constante de temps du système.

Si l’on desire l’indentification du gain statique, le temps va demander la constante de temps du système.

Voici le programme ou il faut activer par un bouton poussoir pour faire pour faire l’autotunning de l’identification et

L’indentification se fera avec une température plus faible de 15°C du régime établi.

Voici la partie de l’autotunning qui détermine étape par étape via des flags pour faire l’identification

if (flagautotunning==1) {  //
  if (timeauto==0) {Tamb=Tfiltre; PWM=100; }
  timeauto++;
  if( (Tfiltre>(Tamb+1))  && flagretard==1 )  {retard=timeauto; flagretard=0; flagCTH=1; }
  if( (Tfiltre>=(Tamb+10)) && flagCTH==1  )  {CTH=(power*timeauto)/10  ;  flagCTH=0; flagRTH=1;}  
Tpente=(power*timeauto/CTH)+Tamb;
  if( (Tpente>(Tfiltre+18)) && flagRTH==1 )  {RTH=(18/(power*0.36))  ;flagRTH=0;flagPID=1; }
  if (flagPID==1) {kp=(1.2*CTH)/(retard);ki=kp/(2*retard);kd=(kp*retard)/2;flagPID=0; flagautotunning=0; }


Le programme complet avec la regulation PID

/PID simulation atmega328 indentification ziegler
//#include <LiquidCrystal.h>
#include <TimerOne.h>
//#include <OneWire.h>
//#include <DallasTemperature.h>


//LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

bool flag;   //echantillonage 1s
bool flagautotunning;
bool flagCTH;
bool flagRTH;
bool flagretard;
bool flagPID;

  float timeauto=0;
  float Tamb;
  float retard;
  float CTH=999;
  float RTH=2;
  float Tpente;
const float power=70;

float Dec=0;
float DS18;
float compt=0;
float PWM;
float tempson;
float outdec;
float pourcentage;

byte x=0;   


float T;
 float Te1;  
 float Te2;

 float sortie;
 float sortie1;     //n-1  
 float sortie2;
 float Tfiltre;      //

  float consigne=55;
  float erreur1;   
  float erreur;
  float integral;
  float derive;

 float kp=5;  
 float ki=0.01;
 float kd=0;
 float lambdaCstBF;         // λ constante de temps desirée en boucle fermée  
   
           
     const float a1 =-1.1429;   //2eme ordre  Fc=0.1Hz   10s
     const float a2 =0.41280;   //mesure temp
     const float Gain =14.82; 
     
/*
     const float a1 =-1.5610;   //2eme ordre  Fc=0.05Hz   20s
     const float a2 =0.64135;   //mesure temp
     const float Gain =49.78; 
*/


void callback()  {    //interruption toutes les 10m secondes
   // if (digitalRead(17)==1)   {digitalWrite(17,LOW);}  else {digitalWrite(17,1);} // 
 compt++; 
       
       if  (compt>100) {flag=1;compt=0;}  //1s  periode PWM
  
 
 }  //fin callback


void setup() 
{
pinMode(13,OUTPUT); //Set the Heater/Relay Pin as OUTPUT
  pinMode(A0,INPUT);   //mesure LM35
pinMode(12,INPUT_PULLUP); //switch autotunning 
pinMode(11,INPUT_PULLUP); //send coefficient et identification 
 // pinMode(A2,INPUT);   //consigne variable

  Serial.begin(57600);
  
  //lcd.begin(16, 2);    // Initialisation du LCD
  
 Timer1.initialize(10000);           // initialize timer1 1seconde, and set a 0,1 second period =>  100 000  pour 0.01s  10 000
 Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt

// analogReference( DEFAULT );


//initilisation du filtre

Dec=analogRead(A0);  //initialisation du filtre
T=Dec/2.048; 
Te1=T;
Te2=T;
sortie=T*Gain;
sortie1=T*Gain;

erreur=consigne-T;  //initialise la derivée

}



void loop()  {
tempson=PWM ;      //temps On=PWM*periode/(timerISR*100)=PWM*1s/(0.01*100)
if (compt<tempson)  {digitalWrite(13,1) ;}
if (compt>tempson)  {digitalWrite(13,0) ;}

if (digitalRead(12)==0 && (T<40)) {flagautotunning=1; timeauto=0;flagretard=1; }  //lancement autotunning



if (flag==1)    {   //toutes les secondes

Dec=analogRead(A0);  //mesure temperature
  Te2=Te1;      //entree(n-2)
  Te1=T;       //entree(n-1)
T=Dec/2.048;     //V=10mV/°C     Dec=0.01*1024*T/5=2.048*T 
if (T>100) {T=100;}


  sortie2=sortie1;      //sortie(n-2)
  sortie1=sortie;       //sortie(n-1)
  sortie=(T+Te1*2+Te2-sortie1*a1-sortie2*a2) ;         //filtre passe pas recursif ordre 2
  Tfiltre=(sortie/Gain);    //gain du filtre    Temperature filtrée


if (flagautotunning==1) {  //
  if (timeauto==0) {Tamb=Tfiltre; PWM=100; }
  timeauto++;
  if( (Tfiltre>(Tamb+1))  && flagretard==1 )  {retard=timeauto; flagretard=0; flagCTH=1; }
  if( (Tfiltre>=(Tamb+10)) && flagCTH==1  )  {CTH=(power*timeauto)/10  ;  flagCTH=0; flagRTH=1;}  
Tpente=(power*timeauto/CTH)+Tamb;
  if( (Tpente>(Tfiltre+18)) && flagRTH==1 )  {RTH=(18/(power*0.36))  ;flagRTH=0;flagPID=1; }
  if (flagPID==1) {kp=(1.2*CTH)/(retard);ki=kp/(2*retard);kd=(kp*retard)/2;flagPID=0; flagautotunning=0; }  //methode ziegler
/*
  if (flagPID==1) { lambdaCstBF=(RTH*CTH)/2;       //methode SMIC
        kp=(CTH)/(retard+lambdaCstBF);   
        ki=kp/(RTH*CTH);
        kd=(CTH*T)/(2*lambdaCstBF);    flagPID=0; flagautotunning=0; }    
*/

Serial.print("Auto");Serial.print(";");  
Serial.print(timeauto,0);Serial.println(";");

} //fin tunning


if (flagautotunning==0) {   //correcteur Proportionel integral derivee discretisée
 erreur1=erreur;
 erreur=consigne-Tfiltre;
 integral=ki*erreur+integral;
 derive=kd*(erreur-erreur1);    //
if (integral>=90) {integral=90;}   // 
if (integral<-90) {integral=-90;}


PWM=kp*erreur+integral+derive;
if (PWM>=100) {PWM=100;}   // PWM en %
if (PWM<=0) {PWM=0;}

Serial.print("regule");Serial.print(";");  
Serial.print(T,1);Serial.print(";");     //terminal recuperer les données dans un terminal serie en fichier CSV
Serial.print(Tfiltre,1);Serial.print(";");      //tracer les courbes dans excel
Serial.print(PWM,1);Serial.println(";");
//Serial.print(DS18,1);Serial.println(";");

        }  // fin PID

      if (digitalRead(11)==0 ) {
Serial.print("PID");Serial.print(";"); 
Serial.print(retard,0);Serial.print(";");
Serial.print(CTH,0);Serial.print(";");
Serial.print(RTH,1);Serial.print(";");
Serial.print(kp,1);Serial.print(";");
Serial.print(ki,3);Serial.print(";");
Serial.print(kd,1);Serial.println(";");

 }                   


     flag=0;}  //fin flag 1seconde
 
   }  //end loop

Pour vérifier l’indentification et la détermination des coefficients, une simulation sous Proteus a déjà été effectué, comme on peut l’observer sur la figure suivante. Ou il faut appuyer sur le poussoir 11 pour avoir les données.

si l’indentification est correcte, la regulation est trop agressive avec le calcul des coefficients par ziegler.

La méthode Smooth PID Controller Tuning avec le choix λ de la constante de temps souhaité en boucle fermée sera bien mieux

1 Like

La méthode Smooth PID Controller Tuning donne de bien meilleur résultat avec le choix λ de la constante de temps souhaité en boucle fermé que la méthode ziegler précédente

https://skoge.folk.ntnu.no/publications/2012/skogestad-improved-simc-pid/PIDbook-chapter5.pdf

Pour une constante de temps identique en boucle fermée et ouverte alors, le coefficient proportionnel donne l’inverse de la résistance thermique et c’est normal. La constante de temps intégral correspond à la constante de tems désiré et la constante de temps de la dérivée correspond au retard divisé par 2.

Voici les équations de la méthode SIMC pour determiner les coeficients du PID

Remarque :

si l’on désire une constante de temps désirée en boucle fermée 2 fois plus rapide qu’en boucle ouverte, le coefficient kp devra être doublé et la puissance de sortie devra aussi doubler.

Evidement dans un environnent pragmatique, la puissance sera saturée donc ce n’est pas possible.

Donc dans le programme précédent, il faut juste changer la ligne du calcul du PID par la ligne suivante et faire un choix de la constante de temps desirée lambda

  if (flagPID==1) { lambdaCstBF=300;       //methode SMIC
        kp=(CTH)/(retard+lambdaCstBF);   
        ki=kp/(RTH*CTH);
        kd=(CTH*T)/(2*lambdaCstBF);    flagPID=0; flagautotunning=0; }  

Évidemment, on a testé le programme en simulation.

Dans Proteus, on a voulu rajouter un retard de 5 s à la mesure pour mieux s’approcher du système réel mais cela ne fonctionne pas.

Pour faire l’identification et l’autotunning, il y a aussi la méthode « Relay control » qui est très utilisé dans les régulateurs industriels.

1 Like

Bonjour,

Sauf que dans la vrai vie le code suivant est carrément non déterministe; à savoir:

float compt = 0;   // 0.0 aurait été plus rigoureux
float tempson;
...

void callback()    // interruption toutes les 10m secondes
{
       compt++;

       if (compt>100) { flag = 1; compt = 0; }  // 1s  periode PWM
      ...
}
...
void setup() 
{
...
       Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
...
}

void loop() 
{
...
       tempson = PWM;      // temps On=PWM*periode/(timerISR*100)=PWM*1s/(0.01*100)
       if (compt < tempson) { digitalWrite(13, 1); }
       if (compt > tempson) { digitalWrite(13, 0); }    // 'else if' aurait été le bienvenu
...
}

Ayant soulever maintes fois le danger de la mise à jour de variables non atomiques dans une interruption et lues en fond de tâche, le résultat n'est absolument non garanti, qui plus est, lorsque ladite variable est un float, c.a.d. définie sur 32 bits ;-)

Je laisse l'auteur du programme rechercher la correction dans les fils de discussions du forum

A suivre...

Vous voulez que je le déclare en byte, compt, Puisque cette variable ne depasse pas 100 ?????

merci de corriger mes coquilles.

Voici méthode d’autotunning PID « relay control » qui est décrit dans le lien précèdent mais aussi dans ceux-ci mais sans etre trop vulgarisé et comment c’est appliqué

https://slunik.slu.se/kursfiler/TE0010/10095.1213/Reg1TuneReview.pdfhttps://www.mdpi.com/2227-7390/11/21/4548

Lorsqu’il y a une commande d’un système de première ordre avec une commande en tout ou rien à période fixe (signal carré) alors la réponse de la sortie sera la suivante.

On peut observer les pentes montantes et descendante de la sortie en fonction de l’entrée, l’amplitude des oscillations en régime établi à chaque période.

En régime établi, la température moyenne sera égale au gain du système multiplié par la puissance moyenne.

La période du « relay command » doit être bien plus grande que celle de la PWM et bien inférieur à la constante de temps du système (30 s dans l’exemple suivant).

Cette période peut être choisi pour une certaine valeur de la sortie.

Voici les équations des incréments de température pour chaque demi période avec le gain critique en boucle fermé qui permet de déterminer les coefficient ziegler.

Sauf erreur de ma part, les coefficients du PID avec cette méthode vont dépendre de la période choisie par faire « control relay » comme on peut l’observer avec les équations suivantes.

Donc, c’est bizarre…et pas très cohérent pourtant, les unités du coefficient proportionnel sont bien en en W/°C.

Le temps ton pour une certaine puissance provoque une certaine amplitude de la température qui peut être mesurée pendant un certain temps exemple pour un incrément de 10°C

Le temps toff, avec une puissance nulle, donc lorsque la température diminue vers la température ambiante peut être aussi mesurée.

Le temps toff permet de déterminer la constante de temps mais il faudra connaitre la température ambiante.

Le temps ton permettre comme on peut l’observer dans les équations suivantes

Cette dernière identification est donc bien plus rapide que celle de l’échelon.

Voici le nouveau code de l’identification relay control pour avoir une augmentation de 10°C et une diminution de 2°C choisi arbitrairement pour que l’identification soient pertinentes.

On a préféré séparer la mesure de la température de la temperature initiale et celle ambiante pour que l’indentification puisse se faire à tout moment.

if (flagautotunning==1) {  
  //methode relay control roue libre
  if (timeauto==0) {Tinit=Tfiltre; PWM=100; flagretard=1; }   
  Tamb=analogRead(A2);
  Tamb=Tamb/2.048;
  timeauto++;
  if( (Tfiltre>(Tinit+0.5))  && flagretard==1 )  {retard=timeauto; flagretard=0; flagton=1; }
  if( (Tfiltre>=(Tinit+10)) && flagton==1  )  {ton=timeauto-retard  ;  flagton=0; flagtoff=1; PWM=0; }  
  if( (Tfiltre<=(Tinit+10-2)) && flagtoff==1 )   {toff=timeauto-ton-retard  ; flagtoff=0;   flagPID=1; }

  if (flagPID==1) { 
    csttemps=toff/(log(Tinit+10-Tamb)/(Tinit+10-2-Tamb));          //identification pour -2°C
    CTH= power/((10/ton)+(Tamb/csttemps))   ;
    RTH=csttemps/CTH;

    lambdaCstBF=(RTH*CTH)/2;       //methode SIMC choix de la constante de temps en BF
        kp=(CTH)/(retard+lambdaCstBF);   
        ki=(kp)/csttemps;
        kd=(CTH*T)/(2*lambdaCstBF);    flagPID=0; flagautotunning=0; }  

Voici Le programme complet

//PID simulation atmega328 indentification relay control SiMC
//#include <LiquidCrystal.h>
#include <TimerOne.h>
//#include <OneWire.h>
//#include <DallasTemperature.h>


//LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

bool flag;   //echantillonage 1s
bool flagautotunning=1  ;
bool flagton;
bool flagtoff;
bool flagretard;
bool flagPID;


  float timeauto=0;
  float Tamb;
  float Tinit;
  float retard;
  float CTH=999;
  float RTH=9;
  float csttemps;
const float power=70;

float Dec=0;
float DS18;
byte compt=0;
float PWM;
float tempson;
float outdec;
float pourcentage;

byte x=0;   


float T;
 float Te1;  
 float Te2;

 float sortie;
 float sortie1;     //n-1  
 float sortie2;
 float Tfiltre;      //

  float consigne=55;
  float erreur1;   
  float erreur;
  float integral;
  float derive;

 float kp=2.6 ; 
 float ki=0.009;
 float kd=10;
 float lambdaCstBF;         // λ constante de temps desirée en boucle fermée     
 float ton;              //temps ou la power est mise
 float toff;              //temps ou la power est 0
           
     const float a1 =-1.1429;   //2eme ordre  Fc=0.1Hz   10s
     const float a2 =0.41280;   //mesure temp
     const float Gain =14.82; 
     
/*
     const float a1 =-1.5610;   //2eme ordre  Fc=0.05Hz   20s
     const float a2 =0.64135;   //mesure temp
     const float Gain =49.78; 
*/


void callback()  {    //interruption toutes les 10m secondes
   // if (digitalRead(17)==1)   {digitalWrite(17,LOW);}  else {digitalWrite(17,1);} // 
 compt++; 
       
       if  (compt>100) {flag=1;compt=0;}  //1s  periode PWM
  
 
 }  //fin callback


void setup() 
{
pinMode(13,OUTPUT); //Set the Heater/Relay Pin as OUTPUT
  pinMode(A0,INPUT);   //mesure LM35
pinMode(12,INPUT_PULLUP); //switch autotunning 
pinMode(11,INPUT_PULLUP); //send coefficient et identification 
 // pinMode(A2,INPUT);   //consigne variable

  Serial.begin(57600);
  
  //lcd.begin(16, 2);    // Initialisation du LCD
  
 Timer1.initialize(10000);           // initialize timer1 1seconde, and set a 0,1 second period =>  100 000  pour 0.01s  10 000
 Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt

// analogReference( DEFAULT );


//initilisation du filtre

Dec=analogRead(A0);  //initialisation du filtre
T=Dec/2.048; 
Te1=T;
Te2=T;
sortie=T*Gain;
sortie1=T*Gain;

erreur=consigne-T;  //initialise la derivée
}



void loop()  {
tempson=PWM ;      //temps On=PWM*periode/(timerISR*100)=PWM*1s/(0.01*100)
if (compt<tempson)  {digitalWrite(13,1) ;}
if (compt>tempson)  {digitalWrite(13,0) ;}

if (digitalRead(12)==0 && (T<40)) {flagautotunning=1; timeauto=0; }  //lancement autotunning si temperature systeme << Temperature regime etbli max



if (flag==1)    {   //toutes les secondes

Dec=analogRead(A0);  //mesure temperature
  Te2=Te1;      //entree(n-2)
  Te1=T;       //entree(n-1)
T=Dec/2.048;     //V=10mV/°C     Dec=0.01*1024*T/5=2.048*T 
if (T>100) {T=100;}


  sortie2=sortie1;      //sortie(n-2)
  sortie1=sortie;       //sortie(n-1)
  sortie=(T+Te1*2+Te2-sortie1*a1-sortie2*a2) ;         //filtre passe pas recursif ordre 2
  Tfiltre=(sortie/Gain);    //gain du filtre    Temperature filtrée


if (flagautotunning==1) {  
  //methode relay control roue libre
  if (timeauto==0) {Tinit=Tfiltre; PWM=100; flagretard=1; }   
  Tamb=analogRead(A2);
  Tamb=Tamb/2.048;
  timeauto++;
  if( (Tfiltre>(Tinit+0.5))  && flagretard==1 )  {retard=timeauto; flagretard=0; flagton=1; }
  if( (Tfiltre>=(Tinit+10)) && flagton==1  )  {ton=timeauto-retard  ;  flagton=0; flagtoff=1; PWM=0; }  
  if( (Tfiltre<=(Tinit+10-2)) && flagtoff==1 )   {toff=timeauto-ton-retard  ; flagtoff=0;   flagPID=1; }

  if (flagPID==1) { 
    csttemps=toff/(log(Tinit+10-Tamb)/(Tinit+10-2-Tamb));          //identification pour -2°C
    CTH= power/((10/ton)+(Tamb/csttemps))   ;
    RTH=csttemps/CTH;

    lambdaCstBF=(RTH*CTH)/2;       //methode SIMC choix de la constante de temps en BF
        kp=(CTH)/(retard+lambdaCstBF);   
        ki=(kp)/csttemps;
        kd=(CTH*T)/(2*lambdaCstBF);    flagPID=0; flagautotunning=0; }     

Serial.print("Auto");Serial.print(";");  
Serial.print(Tfiltre,1);Serial.print(";");
Serial.print(timeauto,0);Serial.print(";");
Serial.print(retard,0);Serial.print(";");
Serial.print(ton,0);Serial.print(";");
Serial.print(toff,0);Serial.println(";");


} //fin tunning


if (flagautotunning==0) {   //correcteur Proportionel integral derivee discretisée
 erreur1=erreur;
 erreur=consigne-Tfiltre;
 integral=ki*erreur+integral;
 derive=kd*(erreur-erreur1);    //
if (integral>=90) {integral=90;}   // 
if (integral<-90) {integral=-90;}


PWM=kp*erreur+integral+derive;
if (PWM>=100) {PWM=100;}   // PWM en %
if (PWM<=0) {PWM=0;}

Serial.print("regule");Serial.print(";");  
Serial.print(T,1);Serial.print(";");     //terminal recuperer les données dans un terminal serie en fichier CSV
Serial.print(Tfiltre,1);Serial.print(";");      //tracer les courbes dans excel
Serial.print(PWM,1);Serial.println(";");
//Serial.print(DS18,1);Serial.println(";");

        }  // fin PID

      if (digitalRead(11)==0 ) {
Serial.print("PID");Serial.print(";"); 
Serial.print(retard,0);Serial.print(";");
Serial.print(CTH,0);Serial.print(";");
Serial.print(RTH,3);Serial.print(";");
Serial.print(kp,1);Serial.print(";");
Serial.print(ki,3);Serial.print(";");
Serial.print(kd,1);Serial.println(";");

 }                   


     flag=0;}  //fin flag 1seconde
 
   }  //end loop

Pour valider le code facilement, une simulation sous Proteus du programme, ou l’on peut observer l’indentification au démarrage puis la régulation pour avoir une consigne de 55°C a été effectuée.

bref, chaque système est différent, donc son identification sera légèrement différente.Exemple : pour la régulation de la vitesse d’un moteur, la vitesse initiale et en régime établie en roue libre sera de 0.Alors que pour uns système chauffant, la température ambiante joue les troubles fêtes.

1 Like

Bonjour,

C'est effectivement une solution dans le cas présent car la lecture/écriture d'un byte est atomique et bien plus économique que l'utilisation d'un float

A suivre...

Pendant que l’on y est,

Autant présenter une commande par logique floue pour ce système de chauffage car nombreux régulateurs industriels le proposent

Mais la littérature sur la logique floue n’est pas facile à comprendre avec ces règles d’appartenances de fuzzyfication, l’explication dans wikipedia est une vrai catastrophe de meme pour l’IA

https://fr.wikipedia.org/wiki/Logique_floue

La logique floue permet de :

- tolérer l’imprécision, d’où son terme flou

- travailler avec des règles linguistiques avec des systèmes mal connus et non linéaire.

https://fr.wikipedia.org/wiki/Logique_floue

Exemple de Règles linguistique pour des process de chauffage

- Si l’erreur est grande positive >12.5°C → chauffage fort 100%

- Si l’erreur est petite positive <6.25°C → chauffage moyen 50%

- Si l’erreur est égale à 0°C → chauffage éteint 0%

- Si l’erreur est négative <0°C → chauffage éteint 0%

Donc, la sortie du régulateur peut s’écrire tout simplement Out(%)=k1. e avec k1=100%/12.5°C meme si la précision de la mesure de la température est de 0.5°C car le filtre du second degré nivelle cette imprécision.

Voici 2 articles qui expliquent simplement la logique floue, lorsque la sortie n’est pas trop floue.

https://www.e-kart.fr/images/stories/news/2011/ever/ever11-paper-spbl2.pdf

https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=6350006

Pour une implémentation aisée sur un microcontrôleur, le contrôleur flou est basé sur une table utilisant l’erreur et sa dérivée, puis il y a une intégration de la sortie des valeurs de cette table pour annuler les perturbations

Avec la defuzzification, la sortie avec l’intégration du régulateur flou est linéaire et revient à un PI (proportionnel intégral d’où son nom fuzzy logique PI) expliqué sur le lien suivant

https://www.espacetechnologue.com/wp-content/uploads/2019/01/TP_Fuzzy.pdf

La commande du régulateur out est directement déduite de cette table ou il faut définir les facteurs d’échelles (k1. e, k2.D e ). Dans cette table, il y a une ligne de 0 surnommé la ligne de commutation ou a trajectoire du système contrôlé dans le plan de phase glisse autour de cette ligne de 0 dont l’équation est la suivante

Ligne commutation = k1.e + k2.e.s = 0

Par conséquent, la sortie du régulateur va agir comme un proportionnel intégral correspondant à l’équation suivante

Out= e.(k1 + k2.s).k3/s = e.(k1/s + k2).k3= e.(ki/s + kp)

Le facteur d'échelle k1 influe sur la saturation de l'erreur de la zone linéaire autour de 0.

En effet, si k1=0.1 avec une erreur de 10°C, la sortie s’incrémentera de 1% avec une coefficient k3 de 1.

Les facteurs d'échelle k1 et k2 déterminent la dynamique de sortie du contrôleur flou correspondant à l’équation suivante si cela suit la ligne de commutation

image

Pour avoir une constante de temps de 70s= k2/k1 donc k2=7 pour k1=0.1

Donc, le choix des coefficients n’a rien à voir avec la méthode SIMC même si on retrouve les mêmes dynamiques.

Le codage en C du fuzzy logic, n’est pas le même que pour le PID

erreur1=erreur;
 erreur=consigne-Tfiltre;
 derreur=(erreur-erreur1);
fuzzy=k1*erreur+k2*(erreur-erreur1);
if (fuzzy>=100) {fuzzy=100;}   // 
if (fuzzy<-100) {fuzzy=-100;}

integral=k3*fuzzy+integral;

if (integral>=100) {integral=100;}   // 
if (integral<0) {integral=0;}

PWM=integral;

Voici la simulation avec les choix de facteur d’échelle précèdent qui a un léger dépassement

En comparaison, avec un régulateur PID

image

Pour gagner du temps, la modélisation de la simulation a été faite en discrétisant le regulateur flou dans Scilab avec une période d’échantillonnage de 1s.

Il y a des petites différences entre la réponse dans Proteus et dans Scilab

il va falloir faire un synthese de tout cela

1 Like

Les regulateurs industriels PID proposent un parametrage séquentiel de differents valeurs de consignes ou de rampes et meme que certains permettent un fonctionnement d’étapes via un grafcet.

Les machines d’etat avec grafcet permettent d’avoir une programmation simple et tres robute en process (peu d’erreur de programme).

https://fr.wikipedia.org/wiki/Grafcet

En effet, un automate programmable n’est qu’un micro controleur ou il est possible de rajouter des racks specifiques avec un multiplexage des entrées et sorties.

Certains utilisent des cartes arduinos avec un soft qui permet de passer de la programmation grafcet, ladder directement en C.

Pourtant, la retranscription d’un algo grafcet direcment en langage structuré C est facile à faire.

Bref, Les rampes sont tres faciles à programmer en C avec des fonction récurentes.

Mais malgré le régulateur PI, avec une rampe, il y aura une « erreur de trainage » tracking errors qui sera egale à la consigne multiplié par la constante de temps en boucle fermée comme démontrée sur ce lien

https://public.iutenligne.net/automatique-et-automatismes-industriels/verbeken/cours_au_mv/chapitre3/chap34.html

erreur suivie (°C)=pente rampe (°C/s)*constante dominante en boucle fermée (s)

D’ailleurs utiliser une rampe permet de determiner la constante de temps dominante de la regulation en boucle fermée avec le regulateur à cause de cette erreur de suivie dans la zone de la regulation lineaire car il y a la saturation de la sortie du regulateur.

Donc, il faut que la rampe soit inferieur aux temps de montée de la temper ure.

Evidement, cette erreur s’annulera lorsque la consigne sera constante.

Exemple pour la variation de consigne suivante

if (digitalRead(10)==0) {   //rampe cycle process
       if (seconde==0 )  {consigne=20;}
      if (seconde<200 ) {consigne=0.1+consigne;}          //rampe integration recurente discretisée jusqu'a 40°C en 200s    ((40-20)/200)=0.1
       if (seconde>200 && seconde<300) {consigne=40;}       //consigne constante pendant 50s
       if (seconde>=300 && seconde<400) {consigne=(0.05+0.025)+consigne;}   //consigne=((45-40)/100)+consigne + erreur de suivie
       if (seconde>=400 && seconde<500) {consigne=45;}
      if (seconde>=500 && seconde<cycle) {consigne=55;}
     
       if (seconde>cycle) {seconde=0;}   //faire un nouveau cycle
}
seconde++;

Sur la figure suivante, on peut observer que pour les 200 premiéres secondes, cette erreur de suivie pour la rampe de 0.1°C/s, d’ailleurs, il y a 7°C d’erreur à t=200s

=7°C/0.1°C/s=70s

Etant donné que l’on connait cette erreur de suivie, alors il est possible de le rajouter sur la consigne pour minimiser l’erreur.

Exemple pour une pente désirée de 0.05°C/s, pour aller de 40°C à 45°C en 100s, provoquant une erreur de 3.5°C, la rampe de la consigne devrait etre de 0.05°C/s+3.5°C/100s=0.085°C/s , mais il y un risque d’un petit depassement.

D’ailleurs, on peut observer que l’erreur de suivie entre 200 et 300s est presque nulle, ainsi que l’erreur statique pour la consigne de 45°C.

Puis, il y a la reponse à un echellon pour atteindre les 55°C

pour Kp=20 ki=0.05 kd =0

Evidement, si la regulation du PID n’a pas une bonne reponse à un echelon avec de bonne dynamique, il n’y aura pas une bonne réponse pour une rampe.

Voici le programme en entier

/PID simulation atmega328 indentification relay control SiMC
//#include <LiquidCrystal.h>
#include <TimerOne.h>
//#include <OneWire.h>
//#include <DallasTemperature.h>


//LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

bool flag;   //echantillonage 1s
bool flagautotunning=0  ;
bool flagton;
bool flagtoff;
bool flagretard;
bool flagPID;


  float timeauto=0;
  float Tamb;
  float Tinit;
  float retard;
  float CTH=999;
  float RTH=9;
  float csttemps;
const float power=70;

float Dec=0;
float DS18;
byte compt=0;
float PWM;
float tempson;
float outdec;
float pourcentage;

float seconde;
float cycle=1000;
int rampe;

float T;
 float Te1;  
 float Te2;

 float sortie;
 float sortie1;     //n-1  
 float sortie2;
 float Tfiltre;      //

  float consigne=55;
  float erreur1;   
  float erreur;
  float integral=0;
  float derive;

 float kp=20 ;     //100%/20   lineariation=5
 float ki=0.05;
 float kd=0;
 float lambdaCstBF;         // λ constante de temps desirée en boucle fermée     
 float ton;              //temps ou la power est mise
 float toff;              //temps ou la power est 0
           
     const float a1 =-1.1429;   //2eme ordre  Fc=0.1Hz   10s
     const float a2 =0.41280;   //mesure temp
     const float Gain =14.82; 
     
/*
     const float a1 =-1.5610;   //2eme ordre  Fc=0.05Hz   20s
     const float a2 =0.64135;   //mesure temp
     const float Gain =49.78; 
*/


void callback()  {    //interruption toutes les 10m secondes
   // if (digitalRead(17)==1)   {digitalWrite(17,LOW);}  else {digitalWrite(17,1);} // 
 compt++; 
       
       if  (compt>100) {flag=1;compt=0;}  //1s  periode PWM
 
 }  //fin callback


void setup() 
{
pinMode(13,OUTPUT); //Set the Heater/Relay Pin as OUTPUT
  pinMode(A0,INPUT);   //mesure LM35
pinMode(12,INPUT_PULLUP); //switch autotunning 
pinMode(11,INPUT_PULLUP); //valeur PID 
pinMode(10,INPUT_PULLUP); //consigne rampe 
 // pinMode(A2,INPUT);   //consigne variable

  Serial.begin(57600);
  
  //lcd.begin(16, 2);    // Initialisation du LCD
  
 Timer1.initialize(10000);           // initialize timer1 1seconde, and set a 0,1 second period =>  100 000  pour 0.01s  10 000
 Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt

// analogReference( DEFAULT );


//initilisation du filtre

Dec=analogRead(A0);  //initialisation du filtre
T=Dec/2.048; 
Te1=T;
Te2=T;
sortie=T*Gain;
sortie1=T*Gain;

erreur=consigne-T;  //initialise la derivée
}



void loop()  {
tempson=PWM ;      //temps On=PWM*periode/(timerISR*100)=PWM*1s/(0.01*100)
if (compt<tempson)  {digitalWrite(13,1) ;}
if (compt>tempson)  {digitalWrite(13,0) ;}

if (digitalRead(12)==0 && (T<40)) {flagautotunning=1; timeauto=0; }  //lancement autotunning si temperature systeme << Temperature regime etabli max


if (flag==1)    {   //toutes les secondes

if (digitalRead(10)==0) {   //rampe cycle process
       if (seconde==0 )  {consigne=20;}
      if (seconde<200 ) {consigne=0.1+consigne;}          //rampe integration recurente discretisé jusqu'a 40°C en 200s    ((40-20)/200)=0.1
       if (seconde>200 && seconde<300) {consigne=40;}       //consigne constante pendant 50s
       if (seconde>=300 && seconde<400) {consigne=0.075+consigne;}   //consigne=((45-40)/100)+consigne;
       if (seconde>=400 && seconde<500) {consigne=45;}
      if (seconde>=500 && seconde<cycle) {consigne=55;}
     
       if (seconde>cycle) {seconde=0;}   //faire un nouveau cycle
                        } 

seconde++;
Dec=analogRead(A0);  //mesure temperature
  Te2=Te1;      //entree(n-2)
  Te1=T;       //entree(n-1)
T=Dec/2.048;     //V=10mV/°C     Dec=0.01*1024*T/5=2.048*T 
if (T>100) {T=100;}


  sortie2=sortie1;      //sortie(n-2)
  sortie1=sortie;       //sortie(n-1)
  sortie=(T+Te1*2+Te2-sortie1*a1-sortie2*a2) ;         //filtre passe pas recursif ordre 2
  Tfiltre=(sortie/Gain);    //gain du filtre    Temperature filtrée


if (flagautotunning==1) {  
  //methode relay control roue libre
  if (timeauto==0) {Tinit=Tfiltre; PWM=100; flagretard=1; }   
  Tamb=analogRead(A2);
  Tamb=Tamb/2.048;
  timeauto++;
  if( (Tfiltre>(Tinit+0.5))  && flagretard==1 )  {retard=timeauto; flagretard=0; flagton=1; }
  if( (Tfiltre>=(Tinit+10)) && flagton==1  )  {ton=timeauto-retard  ;  flagton=0; flagtoff=1; PWM=0; }  
  if( (Tfiltre<=(Tinit+10-2)) && flagtoff==1 )   {toff=timeauto-ton-retard  ; flagtoff=0;   flagPID=1; }

  if (flagPID==1) { 
    csttemps=toff/(log(Tinit+10-Tamb)/(Tinit+10-2-Tamb));          //identification pour -2°C
    CTH= power/((10/ton)+(Tamb/csttemps))   ;
    RTH=csttemps/CTH;

    lambdaCstBF=(RTH*CTH)/2;       //methode SIMC choix de la constante de temps en BF
        kp=(CTH)/(retard+lambdaCstBF);   
        ki=(kp)/csttemps;
        kd=(CTH*T)/(2*lambdaCstBF);    flagPID=0; flagautotunning=0; }     

Serial.print("Auto");Serial.print(";");  
Serial.print(Tfiltre,1);Serial.print(";");
Serial.print(timeauto,0);Serial.print(";");
Serial.print(retard,0);Serial.print(";");
Serial.print(ton,0);Serial.print(";");
Serial.print(toff,0);Serial.println(";");


} //fin tunning


if (flagautotunning==0) {   //correcteur Proportionel integral derivee discretisée
 erreur1=erreur;
 erreur=consigne-Tfiltre;
 integral=ki*erreur+integral;
 derive=kd*(erreur-erreur1);    //
if (integral>=99) {integral=99;}   // 
if (integral<-99) {integral=-99;}

PWM=kp*erreur+integral+derive;
if (PWM>=100) {PWM=100;}   // PWM en %
if (PWM<=0) {PWM=0;}

Serial.print("regule");Serial.print(";");  
//Serial.print(T,1);Serial.print(";");     //terminal recuperer les données dans un terminal serie en fichier CSV
Serial.print(integral,1);Serial.print(";");
//Serial.print( derive,1);Serial.print(";");
Serial.print(consigne,1);Serial.print(";");
Serial.print(Tfiltre,1);Serial.print(";");      //tracer les courbes dans excel
Serial.print(PWM,1);Serial.println(";");
//Serial.print(DS18,1);Serial.println(";");

        }  // fin PID

      if (digitalRead(11)==0 ) {
Serial.print("PID");Serial.print(";"); 
Serial.print(retard,0);Serial.print(";");
Serial.print(CTH,0);Serial.print(";");
Serial.print(RTH,3);Serial.print(";");
Serial.print(kp,1);Serial.print(";");
Serial.print(ki,3);Serial.print(";");
Serial.print(kd,1);Serial.println(";");

 }                   


     flag=0;}  //fin flag 1seconde
 
   }  //end loop
1 Like

Voici un teste des rampes avec la regulation flou pour k1=0.1 k2=7; k3=1; et en compensant l’erreur de trainage.

La programmation a été modifié pour que l’erreur de trainage soit calculée directement par le programme mais il faut que la commande ne soit pas saturé évidement pour que le suivi soit effectif.

La rampe possible va dépendre de la température de chauffe comme le montre les équations suivantes

voici la codage de la consigne

if (digitalRead(10)==0) {   //rampe cycle process
       if (seconde==0 )  {consigne=20;}
      if (seconde<200 ) {rampe=(40-20) ; rampe=rampe/200;  erreurTrainage=(rampe*lambdaCstBF)/200; consigne=(rampe+erreurTrainage)+consigne;}        
       if (seconde>200 && seconde<300) {consigne=40;}       //consigne constante pendant 50s
       if (seconde>=300 && seconde<400) {rampe=(45-40) ; rampe=rampe/(400-300); erreurTrainage=(rampe*lambdaCstBF)/(400-300); consigne=(rampe+erreurTrainage)+consigne;}   //consigne=((45-40)/100)    0.05+erreur=0.05+0.05*70/100=3.5/100=0.035;
       if (seconde>=400 && seconde<600) {consigne=45;}
      if (seconde>=600 && seconde<650) {rampe=(55-45) ; rampe=rampe/(650-600); erreurTrainage=(rampe*lambdaCstBF)/(650-600); consigne=(rampe+erreurTrainage)+consigne;}   //(55-45)/50=0.2
      if (seconde>=650 && seconde<cycle) {consigne=55;}

       if (seconde>cycle) {seconde=0;}   //faire un nouveau cycle


On peut observer sur la réponse du process va bien atteindre 40°C en temps voulu en minimisant l’erreur de trainage. Puis il y a un petit dépassement pour la consigne constante à 40°C. l’erreur de suivi est quasi nulle atteindre 45°C.

Par contre, il y a une erreur de suivi de 45 à 55°C, car la rampe de la consigne 0.2°C/s dépasse ce que peut faire le process peut faire 0.066°C/s.

Lorsque la consigne passe à une constante de 55°C, la réponse de la boucle fermée est assez bizarre.

Donc si on change la rampe de la consigne entre 900 et 600 secondes pour passer de 45 à 55°C avec 0.033°C/s, le suivie est correcte comme on peut l’observer sur la figure suivante

//PID simulation atmega328 fuzzy PI rampe
//#include <LiquidCrystal.h>
#include <TimerOne.h>
//#include <OneWire.h>
//#include <DallasTemperature.h>


//LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

bool flag;   //echantillonage 1s
bool flagautotunning=0 ;
bool flagton;
bool flagtoff;
bool flagretard;
bool flagPID;


  float timeauto=0;
  float Tamb;
  float Tinit;
  float retard;
  float CTH=999;
  float RTH=9;
  float csttemps;
const float power=70;

float Dec=0;
float DS18;
byte compt=0;
float PWM;
float tempson;
float outdec;
float pourcentage;

float seconde;
float cycle=1000;
float rampe=0.1;
float erreurTrainage=0.1;

byte x=0;   


float T;
 float Te1;  
 float Te2;

 float sortie;
 float sortie1;     //n-1  
 float sortie2;
 float Tfiltre;      //

  float consigne=55;
  float erreur1;   
  float erreur;
  float derreur;
  float integral=50;

float fuzzy ;
 float k1=0.1 ;      //
 float k2=7;    //
 float k3=1;
 float lambdaCstBF=70;         // λ constante de temps desirée en boucle fermée     
 float ton;              //temps ou la power est mise
 float toff;              //temps ou la power est 0
           
     const float a1 =-1.1429;   //2eme ordre  Fc=0.1Hz   10s
     const float a2 =0.41280;   //mesure temp
     const float Gain =14.82; 
     
/*
     const float a1 =-1.5610;   //2eme ordre  Fc=0.05Hz   20s
     const float a2 =0.64135;   //mesure temp
     const float Gain =49.78; 
*/


void callback()  {    //interruption toutes les 10m secondes
   // if (digitalRead(17)==1)   {digitalWrite(17,LOW);}  else {digitalWrite(17,1);} // 
 compt++; 
       
       if  (compt>100) {flag=1;compt=0;}  //1s  periode PWM
  
 
 }  //fin callback


void setup() 
{
pinMode(13,OUTPUT); //Set the Heater/Relay Pin as OUTPUT
  pinMode(A0,INPUT);   //mesure LM35
pinMode(12,INPUT_PULLUP); //switch autotunning 
pinMode(11,INPUT_PULLUP); //send coefficient et identification 
 // pinMode(A2,INPUT);   //consigne variable

  Serial.begin(57600);
  
  //lcd.begin(16, 2);    // Initialisation du LCD
  
 Timer1.initialize(10000);           // initialize timer1 1seconde, and set a 0,1 second period =>  100 000  pour 0.01s  10 000
 Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt

// analogReference( DEFAULT );


//initilisation du filtre

Dec=analogRead(A0);  //initialisation du filtre
T=Dec/2.048; 
Te1=T;
Te2=T;
sortie=T*Gain;
sortie1=T*Gain;

erreur=consigne-T;  //initialise la derivée
}



void loop()  {
tempson=PWM ;      //temps On=PWM*periode/(timerISR*100)=PWM*1s/(0.01*100)
if (compt<tempson)  {digitalWrite(13,1) ;}
if (compt>tempson)  {digitalWrite(13,0) ;}

if (digitalRead(12)==0 && (T<40)) {flagautotunning=1; timeauto=0; }  //lancement autotunning si temperature systeme << Temperature regime etbli max



if (flag==1)    {   //toutes les secondes

if (digitalRead(10)==0) {   //rampe cycle process
       if (seconde==0 )  {consigne=20;}
      if (seconde<200 ) {rampe=(40-20) ; rampe=rampe/200;  erreurTrainage=(rampe*lambdaCstBF)/200; consigne=(rampe+erreurTrainage)+consigne;}        
       if (seconde>200 && seconde<300) {consigne=40;}       //consigne constante pendant 50s
       if (seconde>=300 && seconde<400) {rampe=(45-40) ; rampe=rampe/(400-300); erreurTrainage=(rampe*lambdaCstBF)/(400-300); consigne=(rampe+erreurTrainage)+consigne;}   
       if (seconde>=400 && seconde<600) {consigne=45;}
      if (seconde>=600 && seconde<900) {rampe=(55-45) ; rampe=rampe/(900-600); erreurTrainage=(rampe*lambdaCstBF)/(900-600); consigne=(rampe+erreurTrainage)+consigne;}   //
      if (seconde>=900 && seconde<cycle) {consigne=55;}
       if (seconde>cycle) {seconde=0;}   //faire un nouveau cycle
                        } 
seconde++; 


Dec=analogRead(A0);  //mesure temperature
  Te2=Te1;      //entree(n-2)
  Te1=T;       //entree(n-1)
T=Dec/2.048;     //V=10mV/°C     Dec=0.01*1024*T/5=2.048*T 
if (T>100) {T=100;}


  sortie2=sortie1;      //sortie(n-2)
  sortie1=sortie;       //sortie(n-1)
  sortie=(T+Te1*2+Te2-sortie1*a1-sortie2*a2) ;         //filtre passe pas recursif ordre 2
  Tfiltre=(sortie/Gain);    //gain du filtre    Temperature filtrée


if (flagautotunning==1) {  
  //methode relay control roue libre
  if (timeauto==0) {Tinit=Tfiltre; PWM=100; flagretard=1; }   
  Tamb=analogRead(A2);
  Tamb=Tamb/2.048;
  timeauto++;
  if( (Tfiltre>(Tinit+0.5))  && flagretard==1 )  {retard=timeauto; flagretard=0; flagton=1; }
  if( (Tfiltre>=(Tinit+10)) && flagton==1  )  {ton=timeauto-retard  ;  flagton=0; flagtoff=1; PWM=0; }  
  if( (Tfiltre<=(Tinit+10-2)) && flagtoff==1 )   {toff=timeauto-ton-retard  ; flagtoff=0;   flagPID=1; }

  if (flagPID==1) { 
    csttemps=toff/(log(Tinit+10-Tamb)/(Tinit+10-2-Tamb));          //identification pour -2°C
    CTH= power/((10/ton)+(Tamb/csttemps))   ;
    RTH=csttemps/CTH;

    lambdaCstBF=(RTH*CTH)/4;       //methode  
        k1=0.1;   
        k2=lambdaCstBF/k1;
        k3=1;    flagPID=0; flagautotunning=0;      
                  }//fin flag pid
Serial.print("Auto");Serial.print(";");  
Serial.print(Tfiltre,1);Serial.print(";");
Serial.print(timeauto,0);Serial.print(";");
Serial.print(retard,0);Serial.print(";");
Serial.print(ton,0);Serial.print(";");
Serial.print(toff,0);Serial.println(";");


} //fin tunning


if (flagautotunning==0) {   //correcteur PI fuzzy
 erreur1=erreur;
 erreur=consigne-Tfiltre;
 derreur=(erreur-erreur1);
fuzzy=k1*erreur+k2*(erreur-erreur1);
if (fuzzy>=100) {fuzzy=100;}   // 
if (fuzzy<-100) {fuzzy=-100;}

integral=k3*fuzzy+integral;

if (integral>=100) {integral=100;}   // 
if (integral<0) {integral=0;}

PWM=integral;


//Serial.print("regule");Serial.print(";");  
Serial.print(consigne,1);Serial.print(";");  
Serial.print(Tfiltre,1);Serial.print(";");
Serial.print(derreur,1);Serial.print(";");
Serial.print(fuzzy,1);Serial.print(";");
Serial.print(PWM,1);Serial.println(";");


        }  // fin PID

      if (digitalRead(11)==0 ) {
Serial.print("fuzzy");Serial.print(";"); 
Serial.print(retard,0);Serial.print(";");
Serial.print(CTH,0);Serial.print(";");
Serial.print(RTH,3);Serial.print(";");


 }                   


     flag=0;}  //fin flag 1seconde
 
   }  //end loop

1 Like