Gestion encodeur pour modification consigne

Bonjour à tous,

après avoir suivi pas mal de tutos, de fils de forum, j’arrive enfin à une solution qui marche, mais qui marche mal.

J’ai un programme de gestion d’un chauffage avec cascade PID qui lui fonctionne très bien, et je souhaite ajouter une encodeur rotatif avec bouton centrale afin de permettre à l’utilisateur de modifier la consigne (mes dames ont toujours froid).

j’ai donc mis dans le loop le code suivant :

SW_State = digitalRead(SW_Pin);

  //vérif état bouton
  if (SW_State == LOW){
    if(Menu == 0){
      Menu++;
    }
    else if(Menu == 1){
      Menu--;
    }
  }

  //Définition menu
  if (Menu == 0){
    Menu_P();
  }
    else if (Menu == 1){
    Menu_Consigne();
    }
    else {
        Display.clearDisplay();
        Display.setTextSize(2);
        Display.setTextColor(WHITE);
        Display.setCursor(10,10);
        Display.println("Erreur de menu");
        Display.display();
    }

lorsque j’appuie sur le bouton de l’encodeur, j’arrive bien sur la boucle Menu_Consigne()

  while (SW_State == HIGH)
    {
      SW_State = digitalRead(SW_Pin);
      Actual_State_DT = digitalRead(DT_Pin);
      Actual_State_CLK = digitalRead(CLK_Pin);
      if (Actual_State_DT != Last_State_DT) {
        Last_State_DT = Actual_State_DT;
        if (Actual_State_CLK != Actual_State_DT){
          Room_Target = Room_Target + 0.1;
        }
        else {
          Room_Target = Room_Target - 0.1;
        }
      }
    Display.clearDisplay();
    Display.setTextSize(2);
    Display.setTextColor(WHITE);
    Display.setCursor(10,10);
    Display.println("CONSIGNE");
    Display.setTextSize(3);
    Display.setTextColor(WHITE);
    Display.setCursor(10,30);
    Display.println(Room_Target);
    Display.display();
    delay(50);
    }
  }

alors j’ai principalement 3 soucis, le premier, c’est que la boucle loop tourne mais ne semble pas toujours capter l’appui sur le bouton, il faut appuyer 2 fois.

lorsqu’il est capté, j’arrive bien sur le menu consigne, mais par contre, même si l’encodeur est correctement détecté, je n’arrive pas à faire varier la consigne, ou au mieux de 0.1 par tour entier de l’encodeur ce qui est pas terrible.

enfin pour sortir du menu, encore une fois, il faut appuyer 2 fois sur le bouton.

Sauriez vous me dire quelles erreurs j’ai pu faire dans le code et comment l’améliorer ?

merci d’avance

Votre boucle Menu_Consigne() est bloquante et lente. Lla combinaison du delay(50) et des appels fréquents à Display.clearDisplay() /Display.display() , qui ralentit la boucle vous fait sans doute rater des appuis ou des mouvements d’encodeur.

Vous devriez utiliser des bibliothèques pour les boutons et l'encodeur et envisager un code non bloquant et optimiser les affichages (rafraîchissez que la zone qui a changé).

J'ai partagé deux codes qui embarquent une gestion de consigne avec un potentiomètre pour le réglage des consignes . Peut-être cela pourrait vous donner des idées:

Bonjour,

On présent dans le sujet l'interêt d'utiliser les intéruptions pour détecter les appuis et top encoder...

Bonjour à tous,

Déjà, un grand merci à vous pour votre retour,

j’ai commencé à regarder ton code J-M-L, j’avoue que j’étais à des années lumières en terme d’organisation de programme. (pour les interruptions, j’avoue que je regarderais plus tard)

j’ai donc repris mon code déjà en réarrangeant les blocs avec toutes les données au même endroit, (include et déclaration).

j’ai aussi du coup retravailler le loop pour n’avoir que des appels de fonctions et pas les fonctions en elle même, enfin j’ai essayé d’exploiter les biblio existantes correctement (par exemple, le <encoder.h>, j’avais pas du tout maitrisé l’utilisation et sur un code test, j’ai eu un résultat super stable..

J’ai aussi testé l’utilisation de la biblio onebutton pour la gestion du bouton de l’encodeur.

Pour le One button, j’ai donc

const byte SW_Pin = 7;
#include <OneButton.h>

OneButton Encod_Btn(SW_Pin, true);

void Button_Survey(){

  Encod_Btn.tick();

}

//dans le setup() je défini le Callback de la fonction
Encod_Btn.attachClick(Control_Menu);

//et dans le loop
void loop() {

  Get_Data();
  Button_Survey();

  PID_Calculation();
  Button_Survey();

  Display_MenuP();
  Button_Survey();

  delay(300);
}

Sur cette partie, je ne semble pas m’être trompé.

Par contre, j’ai un souci plus dérangeant, lorsque je upload le programme, la boucle setup() contient un texte à afficher, mais je ne le vois même pas, et le code ne semble même pas s’executer.

auriez vous des idées sur ce qui pourraient déclencher ce comportement ? (je joins le .ino en pièce jointe

Draft_Cascade_PID.ino (8,7 Ko)

Pouvez vous poster le code avec les balises de code - difficile de lire une pièce jointe .ino sur les smartphone

Bien sur, pardon..

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

//define sensor pins
#define DHTPIN A1
#define DHTTYPE DHT22

//define screen
#define Screen_Width 128
#define Screen_Heigth 64
#define Screen_Rst -1

//define Encoder
const byte SW_Pin = 7;
//const byte CLK_Pin = 5;
//const byte DT_Pin = 6;

//define PWM Output
const byte PWM_PIN = 8;

//****************************************************************************************************
//  BITMAP FIRE
//****************************************************************************************************
const unsigned char feu [] PROGMEM = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x03, 0xc0, 0x00, 
	0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x67, 0xf0, 0x00, 
	0x00, 0xef, 0xf0, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xfe, 0x00, 
	0x01, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xc0, 
	0x04, 0xff, 0x9f, 0xc0, 0x06, 0xff, 0x5f, 0xe0, 0x06, 0xfe, 0xff, 0xe0, 0x07, 0xfe, 0xef, 0xf0, 
	0x0f, 0xfe, 0xef, 0xf0, 0x0f, 0xed, 0xf7, 0xf0, 0x0f, 0xcd, 0xfb, 0xf0, 0x07, 0xd5, 0xfb, 0xe0, 
	0x07, 0xb9, 0xfd, 0xe0, 0x07, 0xbf, 0xfd, 0xe0, 0x03, 0xbf, 0xfd, 0xc0, 0x01, 0xbf, 0xfd, 0x80, 
	0x00, 0xbf, 0xfd, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00
};


Adafruit_SSD1306 Display(Screen_Width, Screen_Heigth, &Wire, Screen_Rst);

//define variable for temperature
float Temp_Correct = -2;
float Room_Hum = 0;
float Room_Temp = 0;
float Room_Temp_Corrected = 0;
float Heater_Temp = 0;
float Room_Target = 23;
const float Heat_Temp_Max = 40;
float Room_Hum_D = 0;

// define Variable for encoder and menu
int SW_State;
long old_Pos_Enc = -999;
int Menu;

//****************************************************************************************************
//  CASCADE PID
//****************************************************************************************************

#include <PID_v1.h>

//External PID
double Input_Room, Output_Room, Setpoint_Room;
double Kp_room = 2, Ki_room= 0.5, Kd_room = 1; // to be adjusted

//Interal PID
double Input_Heater, Output_Heater, Setpoint_Heater;
double Kp_heater = 5, Ki_heater= 0.8, Kd_heater = 2; // to be adjusted

//define function for PID
PID pidRoom(&Input_Room, &Output_Room, &Setpoint_Room, Kp_room, Ki_room, Kd_room, DIRECT);
PID pidHeater(&Input_Heater, &Output_Heater, &Setpoint_Heater, Kp_heater, Ki_heater, Kd_heater, DIRECT);

void PID_Calculation(){
    // Boucle extérieure : régul pièce
  Input_Room = Room_Temp;
  Setpoint_Room = Room_Target;
  pidRoom.Compute();
  Setpoint_Heater = Output_Room; // La consigne pour la boucle intérieure

  // Sécurité : jamais chauffer > max !
  if (Setpoint_Heater > Heat_Temp_Max) Setpoint_Heater = Heat_Temp_Max;

  // Boucle intérieure : régul chauffe
  Input_Heater = Heater_Temp;
  //pidHeater.SetSetpoint(Setpoint_Heater);
  pidHeater.Compute();
}

//****************************************************************************************************
//  ENCODEUR
//****************************************************************************************************
#include <Encoder.h>

Encoder Cons_Encoder(6,5);

//****************************************************************************************************
//  Gestion bouton encodeur
//****************************************************************************************************
#include <OneButton.h>

OneButton Encod_Btn(SW_Pin, true);

void Button_Survey(){
  Encod_Btn.tick();
}


//****************************************************************************************************
//  Affichage menu principal
//****************************************************************************************************

void Display_MenuP() {
  Display.clearDisplay();
  //température pièce
  Display.setTextSize(2,4);
  Display.setTextColor(WHITE);
  Display.setCursor(5,5);
  Display.println(Room_Temp_Corrected);
  Display.drawCircle(72, 5, 4, WHITE);
  Display.setCursor(80,5);
  Display.println("C");
  //Humidité pièce
  Display.setTextSize(2);
  Display.setCursor(0,45);
  Display.println(Room_Hum);
  Display.setTextSize(1);
  Display.setCursor(60,52);
  Display.println("%HR");
  //PID Power
  Display.setCursor(5, 36);
  Display.println(Heater_Temp);
  Display.setCursor(38, 36);
  /*Display.println("Heater:");
  Display.setCursor(85, 36);
  Display.println(Output_Heater/255*100);
  //Output_Heater=0;*/
  if (Output_Heater>0) {
    Display.drawBitmap(95,28,feu,32,32,WHITE);
  } 
  Display.display();
}

//****************************************************************************************************
//  Menu de geston de la consigne
//  Amélioration à faire sur la gestion de l'encodeur
//****************************************************************************************************

void Display_Menu_Consigne(){
  SW_State = digitalRead(SW_Pin);

  //gestion de l'encodeur et variation de la valeur
  while (SW_State == HIGH)
    {
      SW_State = digitalRead(SW_Pin);
      long New_Pos_Enc = Cons_Encoder.read();
      //le /8 permet de compenser les crans (4 pulse par cran) et d'avoir un gradient de 0.5°C pour la consigne
      if (New_Pos_Enc != old_Pos_Enc){
        Room_Target = Room_Target + ((New_Pos_Enc - old_Pos_Enc)/8);
        old_Pos_Enc = New_Pos_Enc;
      }
    }
  Display.clearDisplay();
  Display.setTextSize(2);
  Display.setTextColor(WHITE);
  Display.setCursor(10,10);
  Display.println("CONSIGNE");
  Display.setTextSize(3);
  Display.setTextColor(WHITE);
  Display.setCursor(10,30);
  Display.println(Room_Target);
  Display.display();
}

//****************************************************************************************************
//  Pilotage du choix du menu, 
//****************************************************************************************************

void Control_Menu(){
    if(Menu == 0){
      Menu++;
      Display_MenuP();
    }
    else if(Menu == 1){
      Menu--;
      Display_Menu_Consigne();
    }

  else {
    Display.clearDisplay();
    Display.setTextSize(2);
    Display.setTextColor(WHITE);
    Display.setCursor(10,10);
    Display.println("Erreur de menu");
    Display.display();
    }
}

//****************************************************************************************************
//  Recupération données Capteurs
//****************************************************************************************************
#include <DHT.h>
#include <DHT_U.h>
#include <OneWire.h>
#include <DallasTemperature.h>

//define function for DHT
DHT dht(DHTPIN, DHTTYPE);

//define function for DS18B20
OneWire OneWire(A3);
DallasTemperature ds(&OneWire);

void Get_Data(){
    //recup des datas du DHT22
  Room_Hum=dht.readHumidity();
  Room_Temp=dht.readTemperature();
  Room_Hum_D = round(Room_Temp);
  //Correction des températures lues sur DHT22
  Room_Temp_Corrected = Room_Temp + Temp_Correct;
  //recup des datas du DS18B20
  ds.requestTemperatures();
  Heater_Temp = ds.getTempCByIndex(0);

  //contrôle des valeurs du DHT22
  if (isnan(Room_Hum) || isnan(Room_Temp)){
    Display.clearDisplay();
    Display.setTextSize(1);
    Display.setTextColor(WHITE);
    Display.setCursor(10,25);
    Display.println("Probléme de lecture");
    return;
    }
}

//****************************************************************************************************
//  Bloc de SETUP
//****************************************************************************************************


void setup() {

  //demarrage lecture sondes
  dht.begin();
  ds.begin();
  
  //initialisation écran et LoadScreen
  Display.begin(SSD1306_SWITCHCAPVCC, 0X3C);
  Display.clearDisplay();
  Display.setTextSize(2);
  Display.setTextColor(WHITE);
  Display.setCursor(10,10);
  Display.println("HELLO");
  Display.display();

  //Définition Pin diverses
  //pinMode (LED_BUILTIN, OUTPUT);

  //définition pin puissance
  pinMode(PWM_PIN, OUTPUT);

  Setpoint_Room = Room_Target;
  Setpoint_Heater = 0;
  Menu = 0;

  //définition PID
  pidRoom.SetMode(AUTOMATIC);
  pidHeater.SetMode(AUTOMATIC);
  pidRoom.SetOutputLimits(0, Heat_Temp_Max); // La consigne interne varie jusque température sécu résistance
  pidHeater.SetOutputLimits(0, 255);           // PWM output

  //définition comportement bouton encodeur avec biblio onebutton
  Encod_Btn.attachClick(Control_Menu);

  delay(600);
}

//****************************************************************************************************
//  Boucle principale
//****************************************************************************************************

void loop() {

  Get_Data();
  Button_Survey();

  PID_Calculation();
  Button_Survey();

  Display_MenuP();
  Button_Survey();

  delay(300);
}

Ce n’est pas une boucle mais une fonction :slight_smile:

Est-ce que ensuite le code fonctionne et vous voyez des choses sur l’écran ?

Quel type d’arduino ? Comment c’est alimenté et câblé ?

Sans rien changer au câblage si vous chargez ce code

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define screenWidth 128
#define screenHeight 64
#define screenRst -1

Adafruit_SSD1306 ecran(screenWidth, screenHeight, &Wire, screenRst);

void setup() {
  Serial.begin(115200);
  Serial.println("Initialisation...");

  bool ok = ecran.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  Serial.print("ecran.begin: ");
  Serial.println(ok ? "OK" : "Echec");

  if (!ok) return;

  ecran.clearDisplay();
  ecran.setTextSize(2);
  ecran.setTextColor(WHITE);
  ecran.setCursor(10, 10);
  ecran.println("HELLO");
  ecran.display();
}

void loop() {}

Et ouvrez la console série à 115200 bauds - ça fait quoi ?

Bonjour,

non il n’y a plus rien qui s’affiche, enfin il garde l’affichage précédent.

il s’agit d’un arduino nano alimenté par l’USB.

en chargeant votre code, le code fonctionne correctement à 9600 baud dans le moniteur série, par contre à 115200, j’ai 2 losange avec des points d’interrogations.

mon code, lorsque compilé prend 81% de l’espace, est ce que fonction des puces on pourrait manquer de place ?

Une nano 328P « classique » ?

Vous ne seriez pas un peu juste en RAM avec ce grand écran ?

Surprenant car mon code met bien le port série à 115200 bauds

C’est un 328P usbC de chez aliexpress donc clairement pas le plus puissant. pour la RAM, Arduino IDE me donne une occupation de 45% de la mémoire dynamique, il reste donc 1120 Octets pour l’écran et les calculs.

concernant le moniteur série, je viens de réessayer, à 115200, ps de souci, j’ai bien les données.

Après si je suis trop border line par rapport aux capacités du nano, je passerais au dessus.. genre un 32U4 ou un 2560 fonction de..

la bibliothèque Adafruit_SSD1306 alloue la mémoire pour l'écran de manière dynamique dans son constructeur, ce n'est donc pas reporté par le compilateur.

il essaye d'allouer WIDTH * ((HEIGHT + 7) / 8 octet, soit dans votre cas 128 * 71/8 = 1024 octets ➜ il vous reste moins de 100 octets de RAM pour la pile et le tas, ce n'est pas assez...

➜ il faudra effectivement penser à passer sur une MEGA par exemple. On trouve des clones de la Mega sous le nom de "Mega 2560 PRO MINI" avec une taille raisonnable.


(source)

Hello..

En effet c'est clairement insuffisant..

Je suis en train de refaire le câblage sur un méga2560 de chez elegoo que j’ai en stock, je recharge le programme et vous donne le résultat.

OK


au fait — ce delay() en fin de loop n'est pas bon il risque de vous faire rater des appuis sur le bouton ➜ virez le.

Je suis en déplacement cette semaine, je retente ce week-end..

Le delay était plus pour les mesures sachant que le dht22 n'est pas le plus rapide.. Va falloir que je traite différemment ce point

Si vous utilisez la bibliothèque classique (DHT.h, Adafruit) la commande pour aller lire les données est bloquante le temps qu'il faut pour obtenir l'info.

Ok, donc pas besoin de delay dans ce cas. De plus, avec millis, je pourrai regarder à faire une mesure toutes les n secondes ce qui serait surement mieux en terme de consommation de cylces.

merci de ton retour

Hello à tous,

Bon déjà je viens de finir la migration sur mega2560, et donc comme évalué, ça a démarré direct. Je continue sur le programme maintenant, j’ai enlevé le delay et repris la période de mesure comme suit :

void Get_Data(){
  if (millis() > Last_Measure_Time + 4000){ 
//Last_Measure_Time est initialisé dans le setup avec :
// Last_Measure_Time = millis();

      //recup des datas du DHT22
    Room_Hum=dht.readHumidity();
    Room_Temp=dht.readTemperature();
    Room_Hum_D = round(Room_Temp);
    //Correction des températures lues sur DHT22
    Room_Temp_Corrected = Room_Temp + Temp_Correct;
    //recup des datas du DS18B20
    ds.requestTemperatures();
    Heater_Temp = ds.getTempCByIndex(0);

    //contrôle des valeurs du DHT22
    if (isnan(Room_Hum) || isnan(Room_Temp)){
      Display.clearDisplay();
      Display.setTextSize(1);
      Display.setTextColor(WHITE);
      Display.setCursor(10,25);
      Display.println("Probléme de lecture");
      return;
    }
  }
}

toute cette partie semble fonctionner, le point a travaillé et la gestion de l’encodeur qui ne semble pas fonctionné, j’y retourne.

EDIT

Bon, j’ai corrigé un peu le code pour améliorer les choses, mais j’ai l’impression d’avoir un souci de bounce, quand j’appuie sur le bouton de l’encodeur, le menu de réglage consigne apparait mais seulement une 1/2 secondes et disparait.

donc au niveau de ma fonction Display_Menu_Consigne(), je comprends que je dois faire une boucle qui doit rester active tant que je n’appuies pas sur le bouton de l’encodeur, mais je ne vois pas comment le faire proprement.

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

//define sensor pins
#define DHTPIN A1
#define DHTTYPE DHT22

//define screen
#define Screen_Width 128
#define Screen_Heigth 64
#define Screen_Rst -1

//define Encoder
const byte SW_Pin = 7;
//const byte CLK_Pin = 5;
//const byte DT_Pin = 6;

//define PWM Output
const byte PWM_PIN = 8;

//****************************************************************************************************
//  BITMAP FIRE
//****************************************************************************************************
const unsigned char feu [] PROGMEM = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x03, 0xc0, 0x00, 
	0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x67, 0xf0, 0x00, 
	0x00, 0xef, 0xf0, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xfe, 0x00, 
	0x01, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xc0, 
	0x04, 0xff, 0x9f, 0xc0, 0x06, 0xff, 0x5f, 0xe0, 0x06, 0xfe, 0xff, 0xe0, 0x07, 0xfe, 0xef, 0xf0, 
	0x0f, 0xfe, 0xef, 0xf0, 0x0f, 0xed, 0xf7, 0xf0, 0x0f, 0xcd, 0xfb, 0xf0, 0x07, 0xd5, 0xfb, 0xe0, 
	0x07, 0xb9, 0xfd, 0xe0, 0x07, 0xbf, 0xfd, 0xe0, 0x03, 0xbf, 0xfd, 0xc0, 0x01, 0xbf, 0xfd, 0x80, 
	0x00, 0xbf, 0xfd, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00
};


Adafruit_SSD1306 Display(Screen_Width, Screen_Heigth, &Wire, Screen_Rst);

//define variable for temperature
float Temp_Correct = -2;
float Room_Hum = 0;
float Room_Temp = 0;
float Room_Temp_Corrected = 0;
float Heater_Temp = 0;
float Room_Target = 23;
const float Heat_Temp_Max = 40;
float Room_Hum_D = 0;

// define Variable for encoder and menu
int SW_State;
long old_Pos_Enc = -999;
int Menu;

//define timer interval for Temp measure
unsigned long Last_Measure_Time;

//****************************************************************************************************
//  CASCADE PID
//****************************************************************************************************

#include <PID_v1.h>

//External PID
double Input_Room, Output_Room, Setpoint_Room;
double Kp_room = 2, Ki_room= 0.5, Kd_room = 1; // to be adjusted

//Interal PID
double Input_Heater, Output_Heater, Setpoint_Heater;
double Kp_heater = 5, Ki_heater= 0.8, Kd_heater = 2; // to be adjusted

//define function for PID
PID pidRoom(&Input_Room, &Output_Room, &Setpoint_Room, Kp_room, Ki_room, Kd_room, DIRECT);
PID pidHeater(&Input_Heater, &Output_Heater, &Setpoint_Heater, Kp_heater, Ki_heater, Kd_heater, DIRECT);

void PID_Calculation(){
    // Boucle extérieure : régul pièce
  Input_Room = Room_Temp;
  Setpoint_Room = Room_Target;
  pidRoom.Compute();
  Setpoint_Heater = Output_Room; // La consigne pour la boucle intérieure

  // Sécurité : jamais chauffer > max !
  if (Setpoint_Heater > Heat_Temp_Max) Setpoint_Heater = Heat_Temp_Max;

  // Boucle intérieure : régul chauffe
  Input_Heater = Heater_Temp;
  //pidHeater.SetSetpoint(Setpoint_Heater);
  pidHeater.Compute();
}

//****************************************************************************************************
//  ENCODEUR
//****************************************************************************************************
#include <Encoder.h>

Encoder Cons_Encoder(6,5);

//****************************************************************************************************
//  Gestion bouton encodeur
//****************************************************************************************************
#include <OneButton.h>

OneButton Encod_Btn(SW_Pin, true);

void Button_Survey(){
  Encod_Btn.tick();
}


//****************************************************************************************************
//  Affichage menu principal
//****************************************************************************************************
void Display_MenuP() {
  Display.clearDisplay();
  //température pièce
  Display.setTextSize(2,4);
  Display.setTextColor(WHITE);
  Display.setCursor(5,5);
  Display.println(Room_Temp_Corrected);
  Display.drawCircle(72, 5, 4, WHITE);
  Display.setCursor(80,5);
  Display.println("C");
  //Humidité pièce
  Display.setTextSize(2);
  Display.setCursor(0,45);
  Display.println(Room_Hum);
  Display.setTextSize(1);
  Display.setCursor(60,52);
  Display.println("%HR");
  //PID Power
  Display.setCursor(5, 36);
  Display.println(Heater_Temp);
  Display.setCursor(38, 36);
  /*Display.println("Heater:");
  Display.setCursor(85, 36);
  Display.println(Output_Heater/255*100);
  //Output_Heater=0;*/
  if (Output_Heater>0) {
    Display.drawBitmap(95,28,feu,32,32,WHITE);
  } 
  Display.display();
}

//****************************************************************************************************
//  Menu de geston de la consigne
//  Amélioration à faire sur la gestion de l'encodeur
//****************************************************************************************************
void Display_Menu_Consigne(){
  SW_State = digitalRead(SW_Pin);

  //gestion de l'encodeur et variation de la valeur
  while (SW_State == HIGH)
    {
      SW_State = digitalRead(SW_Pin);
      long New_Pos_Enc = Cons_Encoder.read();
      //le /8 permet de compenser les crans (4 pulse par cran) et d'avoir un gradient de 0.5°C pour la consigne
      if (New_Pos_Enc != old_Pos_Enc){
        Room_Target = Room_Target + ((New_Pos_Enc - old_Pos_Enc)/8);
        old_Pos_Enc = New_Pos_Enc;
      }
    }
  Display.clearDisplay();
  Display.setTextSize(2);
  Display.setTextColor(WHITE);
  Display.setCursor(10,10);
  Display.println("CONSIGNE");
  Display.setTextSize(3);
  Display.setTextColor(WHITE);
  Display.setCursor(10,30);
  Display.println(Room_Target);
  Display.display();
}

//****************************************************************************************************
//  Pilotage du choix du menu, 
//****************************************************************************************************
void Control_Menu(){
    if(Menu == 0){
      Menu++;
      Display_MenuP();
    }
    else if(Menu == 1){
      Menu--;
      Display_Menu_Consigne();
    }

  else {
    Display.clearDisplay();
    Display.setTextSize(2);
    Display.setTextColor(WHITE);
    Display.setCursor(10,10);
    Display.println("Erreur de menu");
    Display.display();
    }
}

//****************************************************************************************************
//  Recupération données Capteurs
//****************************************************************************************************
#include <DHT.h>
#include <DHT_U.h>
#include <OneWire.h>
#include <DallasTemperature.h>

//define function for DHT
DHT dht(DHTPIN, DHTTYPE);

//define function for DS18B20
OneWire OneWire(A3);
DallasTemperature ds(&OneWire);

void Get_Data(){
  if (millis() > Last_Measure_Time + 4000){
      //recup des datas du DHT22
    Room_Hum=dht.readHumidity();
    Room_Temp=dht.readTemperature();
    Room_Hum_D = round(Room_Temp);
    //Correction des températures lues sur DHT22
    Room_Temp_Corrected = Room_Temp + Temp_Correct;
    //recup des datas du DS18B20
    ds.requestTemperatures();
    Heater_Temp = ds.getTempCByIndex(0);

    //contrôle des valeurs du DHT22
    if (isnan(Room_Hum) || isnan(Room_Temp)){
      Display.clearDisplay();
      Display.setTextSize(1);
      Display.setTextColor(WHITE);
      Display.setCursor(10,25);
      Display.println("Probléme de lecture");
      return;
    }
  }
}

//****************************************************************************************************
//  Bloc de SETUP
//****************************************************************************************************
void setup() {

  //demarrage lecture sondes
  dht.begin();
  ds.begin();
  
  //initialisation écran et LoadScreen
  Display.begin(SSD1306_SWITCHCAPVCC, 0X3C);
  Display.clearDisplay();
  Display.setTextSize(2);
  Display.setTextColor(WHITE);
  Display.setCursor(10,10);
  Display.println("HELLO");
  Display.display();

  //Définition Pin diverses
  //pinMode (LED_BUILTIN, OUTPUT);

  //définition pin puissance
  pinMode(PWM_PIN, OUTPUT);

  Setpoint_Room = Room_Target;
  Setpoint_Heater = 0;
  Menu = 0;

  //définition PID
  pidRoom.SetMode(AUTOMATIC);
  pidHeater.SetMode(AUTOMATIC);
  pidRoom.SetOutputLimits(0, Heat_Temp_Max); // La consigne interne varie jusque température sécu résistance
  pidHeater.SetOutputLimits(0, 255);           // PWM output

  //définition comportement bouton encodeur avec biblio onebutton
  Encod_Btn.attachClick(Display_Menu_Consigne);
  Last_Measure_Time = millis();
  delay(4000);

}

//****************************************************************************************************
//  Boucle principale
//****************************************************************************************************

void loop() {

  Get_Data();
  Button_Survey();

  PID_Calculation();
  Button_Survey();

  Display_MenuP();
  Button_Survey();
}

Une machine à état serait bien utile plutôt que de bloquer la loop dans un while().

Quand vous enfoncez un bouton - vous passeriez en mode édition de consigne - ça devrait affecter l’affichage et quand vous relâchez le bouton vous sortez de ce mode et l’affichage redevient normal.

regardez cet exemple

où j'utilise la bibliothèque Toggle au lieu de OneButton

Bon j'avais un peu de temps pendant que le poulet cuisait :slight_smile:

j'ai viré OneButton pour mettre Toggle et j'ai un peu nettoyé votre code et sa structure, changé les noms pour passer en camelCase et en Français, mis une petite machine à état qui gère l'appui sur le bouton de l'encodeur pour passer en mode consigne ou en sortir.

Autre point important. Votre code est lent à cause de la lecture des capteurs et du PID ce qui fait que l'on peut rater des interruptions de l'encodeur si on ne dépend que du polling. Dans votre cas il faut vraiment passer avec les interruptions. Il se trouve que la bibliothèque Encoder sait faire cela si les 2 pins que vous utilisez pour CLK et DT sont des pins qui supportent les interruptions ➜ c'est pour cela que j'ai déplacé vos pins vers les broches 2 et 3 et que j'ai rajouté avant d'inclure la bibliothèque une directive spéciale pour cette bibliothèque qui lui dit d'utiliser les interruptions si possible.

// When used on Teensy and Arduino, Encoder uses very optimized interrupt routines written in assembly language. Normally, Encoder uses attachInterrupt(), which allows dynamically attaching functions to each interrupt. The dynamic function call adds slight overhead. To eliminate this extra overhead, you can use this option.
// This optional setting causes Encoder to use more optimized code,
// It must be defined before Encoder.h is included.
#define ENCODER_OPTIMIZE_INTERRUPTS
#include <Encoder.h>

(c'est décrit dans la doc de cette bibliothèque sur le wiki de l'auteur)

Enfin j'ai optimisé un peu l'affichage pour ne les faire que quand quelque chose change, sinon c'est pas beau, ça peut clignoter.

vous pouvez tester cela ici:

le code / branchements
/* ============================================
  code is placed under the MIT license
  Copyright (c) 2023 J-M-L
  For the Arduino Forum : https://forum.arduino.cc/u/j-m-l

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  ===============================================
*/



// When used on Teensy and Arduino, Encoder uses very optimized interrupt routines written in assembly language. Normally, Encoder uses attachInterrupt(), which allows dynamically attaching functions to each interrupt. The dynamic function call adds slight overhead. To eliminate this extra overhead, you can use this option.
// This optional setting causes Encoder to use more optimized code,
// It must be defined before Encoder.h is included.
#define ENCODER_OPTIMIZE_INTERRUPTS
#include <Encoder.h>

#include <Toggle.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
#include <DallasTemperature.h>
#include <OneWire.h>
#include <PID_v1.h>

//********************** CONFIGURATION  ******************************

constexpr byte pinClk = 2;
constexpr byte pinDt = 3;
constexpr byte pinSw = 7;
constexpr byte pinPwm = 8;

constexpr byte pinDht = A1;
constexpr byte pinDs = A3;
constexpr byte dhtType = DHT22;

constexpr int screenRst = -1;
constexpr byte screenWidth = 128;
constexpr byte screenHeight = 64;

//********************** VARIABLES ******************************

OneWire oneWire(pinDs);
DallasTemperature ds(&oneWire);
DHT dht(pinDht, dhtType);

constexpr unsigned char feu[] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x03, 0xc0, 0x00,
  0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x67, 0xf0, 0x00,
  0x00, 0xef, 0xf0, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xfe, 0x00,
  0x01, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xc0,
  0x04, 0xff, 0x9f, 0xc0, 0x06, 0xff, 0x5f, 0xe0, 0x06, 0xfe, 0xff, 0xe0, 0x07, 0xfe, 0xef, 0xf0,
  0x0f, 0xfe, 0xef, 0xf0, 0x0f, 0xed, 0xf7, 0xf0, 0x0f, 0xcd, 0xfb, 0xf0, 0x07, 0xd5, 0xfb, 0xe0,
  0x07, 0xb9, 0xfd, 0xe0, 0x07, 0xbf, 0xfd, 0xe0, 0x03, 0xbf, 0xfd, 0xc0, 0x01, 0xbf, 0xfd, 0x80,
  0x00, 0xbf, 0xfd, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00
};

float correctionTemperature = -2;
float humiditePiece = 0;
float temperaturePiece = 0;
float temperaturePieceCorrigee = 0;
float temperatureChauffe = 0;
constexpr float pasDeLaConsigne = 0.50; // 0.5°C par cran
float consignePiece = 23;
constexpr float temperatureMaxChauffe = 40;

// PID
double entreePiece, sortiePiece, consignePIDPiece;
double kpPiece = 2, kiPiece = 0.5, kdPiece = 1;

double entreeChauffe, sortieChauffe, consignePIDChauffe;
double kpChauffe = 5, kiChauffe = 0.8, kdChauffe = 2;

PID pidPiece(&entreePiece, &sortiePiece, &consignePIDPiece, kpPiece, kiPiece, kdPiece, DIRECT);
PID pidChauffe(&entreeChauffe, &sortieChauffe, &consignePIDChauffe, kpChauffe, kiChauffe, kdChauffe, DIRECT);

// Encodeur et bouton
Encoder encodeurConsigne(pinClk, pinDt);
Toggle boutonEncodeur;
long anciennePositionEnc = 0;

// Gestion des mesures
constexpr unsigned long periodeMesures = 4000;
unsigned long dernierMesure = - periodeMesures; // pour déclencher immédiatement

// Ecran OLED
Adafruit_SSD1306 ecran(screenWidth, screenHeight, &Wire, screenRst);

// Etat du système
enum EtatSysteme {NORMAL, CONSIGNE} etatActuel = NORMAL;

//********************** FONCTIONS ******************************

void mesurerCapteurs() {
  if (millis() - dernierMesure >= periodeMesures) {
    dernierMesure = millis();

    humiditePiece = dht.readHumidity();
    temperaturePiece = dht.readTemperature();
    temperaturePieceCorrigee = temperaturePiece + correctionTemperature;

    ds.requestTemperatures();
    temperatureChauffe = ds.getTempCByIndex(0);
  }
}

void calculPID() {
  entreePiece = temperaturePiece;
  consignePIDPiece = consignePiece;
  pidPiece.Compute();
  consignePIDChauffe = sortiePiece;
  if (consignePIDChauffe > temperatureMaxChauffe) consignePIDChauffe = temperatureMaxChauffe;

  entreeChauffe = temperatureChauffe;
  pidChauffe.Compute();
}

void afficherNormal(bool forceAffichage = false) {
  static float derniereTemperaturePiece = -999;
  static float derniereHumiditePiece = -999;
  static float derniereTemperatureChauffe = -999;
  static int derniereSortieChauffe = -1;

  bool aChanger = forceAffichage
                  || (temperaturePieceCorrigee != derniereTemperaturePiece)
                  || (humiditePiece != derniereHumiditePiece)
                  || (temperatureChauffe != derniereTemperatureChauffe)
                  || (sortieChauffe != derniereSortieChauffe);

  if (!aChanger) return;

  derniereTemperaturePiece = temperaturePieceCorrigee;
  derniereHumiditePiece = humiditePiece;
  derniereTemperatureChauffe = temperatureChauffe;
  derniereSortieChauffe = sortieChauffe;

  ecran.clearDisplay();
  ecran.setTextSize(2);
  ecran.setCursor(5, 5);
  ecran.setTextColor(WHITE);
  ecran.println(temperaturePieceCorrigee);
  ecran.drawCircle(72, 5, 4, WHITE);
  ecran.setCursor(80, 5);
  ecran.println("C");

  ecran.setTextSize(2);
  ecran.setCursor(0, 45);
  ecran.println(humiditePiece);
  ecran.setTextSize(1);
  ecran.setCursor(60, 52);
  ecran.println("%HR");

  ecran.setCursor(5, 36);
  ecran.println(temperatureChauffe);

  if (sortieChauffe > 0) {
    ecran.drawBitmap(95, 28, feu, 32, 32, WHITE);
  }

  ecran.display();
}


void gererConsigne(bool configuration = false) {
  static long anciennePosition = 0;
  if (configuration) anciennePosition = encodeurConsigne.read() >> 2;
  long nouvellePosition = encodeurConsigne.read() >> 2; // on divise par 4 car on a 4 tick pour un click
  long delta = nouvellePosition - anciennePosition;
  if (configuration || delta != 0 ) {
    consignePiece += delta * pasDeLaConsigne;  

    anciennePosition = nouvellePosition;
    ecran.clearDisplay();
    ecran.setTextSize(2);
    ecran.setCursor(10, 10);
    ecran.println("CONSIGNE");
    ecran.setTextSize(3);
    ecran.setCursor(10, 30);
    ecran.println(consignePiece);
    ecran.display();
  }
}


void gestionAffichage() {
  switch (etatActuel) {
    case NORMAL:
      afficherNormal();
      break;
    case CONSIGNE:
      gererConsigne();
      break;
  }
}

void gestionEncodeur() {
  boutonEncodeur.poll();
  if (boutonEncodeur.onPress()) {
    if (etatActuel == NORMAL) {
      etatActuel = CONSIGNE;
      gererConsigne(true); // on force l'affichage et initialise l'encodeur
    } else {
      etatActuel = NORMAL;
      afficherNormal(true); // on force l'affichage
    }
  }
}

//********************** SETUP ******************************

void setup() {
  pinMode(pinPwm, OUTPUT);
  ecran.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  dht.begin();
  ds.begin();
  boutonEncodeur.begin(pinSw);
  Serial.begin(115200);
  pidPiece.SetMode(AUTOMATIC);
  pidChauffe.SetMode(AUTOMATIC);
  pidPiece.SetOutputLimits(0, temperatureMaxChauffe);
  pidChauffe.SetOutputLimits(0, 255);
}

//********************** LOOP ******************************

void loop() {
  mesurerCapteurs();
  calculPID();
  gestionEncodeur();
  gestionAffichage();
}

PS/ je n'ai pas vérifié les tensions pour les composants, j'ai tout branché en 5V sur wokwi (qui s'en fiche) et bien sûr je n'ai pas vérifié du tout votre logique de PID.

Mode normal:

Mode Consigne:

la bascule entre les modes se fait en cliquant sur le bouton de l'encodeur.

1 Like

Bon, je suis sur le postérieur..

Ce code est à des années lumières de ce que j’avais pu faire, j’ai clairement pas tout compris mais va falloir..

Un grand Merci JML, va falloir que je me penche sur 2 points en particulier, le premier c’est les interruptions sur les pins de rotation de l’encodeur et pas sur le switch, faut que je comprenne et en second c’est la gestion de l’état du changement qui me chiffonne, je connaissais pas du tout l’opérateur OR (||)..

merci beaucoup