Bonsoir,
Je m'arrache les cheveux qui me restent en tentant de modifier un croquis qui tourne depuis des années sur un 2560.
Dans l'application (brûleur à pellets) j'ai écrit un switch dont une étape traite de l'extinction en fin de chauffe.
Je souhaite ajouter une condition de contrôle de temps de soufflage avant extinction de flamme.
Le cycle est simple : arrêt de chauffe --> ventilation à fond --> attente de flamme éteinte (LDR) --> cycle de ventilation supplémentaire (post-extiction) --> arrêt.
Le test in situ en hiver étant compliqué (chaque session de chauffe dure environ 1h), j'ai écrit un programme de test sur un autre 2560 sur lequel est raccordé un potentiomètre en entrée. Le moniteur série me renseigne sur les actions.
Le code :
unsigned long previousMillis;
unsigned long currentMillis0;
unsigned long previousMillis0;
int etat = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
switch (etat) { // étape d'extinction
case 0:
if (analogRead(A0) <= 512) // si flamme éteinte, on passe à l'étape suivante
{Serial.println("fin d'extinction");
etat = 1;}
currentMillis = millis();
if (currentMillis - previousMillis >= 20000) // si le délai de 20s est dépassé, on passe à l'étape suivante
{previousMillis = currentMillis;
Serial.println("Delai d'extinction depasse");
etat = 1; }
break;
case 1: // étape de post-extinction
currentMillis0 = millis();
if (currentMillis0 - previousMillis0 >= 5000) // on continue de ventiler pendant 5s
{previousMillis0 = currentMillis0;
Serial.println("fin de post-extinction");
etat = 0; }
break; }
}
Le résultat est inattendu : le passage de l'étape 0 à 1 fonctionne soit par valeur analogique, soit par temps atteint.
Par contre l'étape 1 ne prend pas en compte le délai de 5s.
De plus, le message de dépassement de durée à l'étape 0 apparait de manière impromptue.... Quelque chose m'échappe de le traitement des millis()...
Une piste ?
D'où l’intérêt de toujours mettre un default dans le switch/case
...
default:
Serial.printf("Etat non attendu: etat [%d]\n", etat);
break;
}
NB: @fdufnews: alors que la variable previousMillis0 n'est pas initialisée. Et au passage tu as la même chose dans le case 0:
Les variables globales sont initialisées à 0 par le compilateur
bon j'ai pris le temps de coller le truc dans wokwi et l'indenter
unsigned long previousMillis;
unsigned long currentMillis0;
unsigned long previousMillis0;
int etat = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
switch (etat) { // étape d'extinction
case 0:
if (analogRead(A0) <= 512) // si flamme éteinte, on passe à l'étape suivante
{
Serial.println("fin d'extinction");
etat = 1;
}
currentMillis = millis();
if (currentMillis - previousMillis >= 20000) // si le délai de 20s est dépassé, on passe à l'étape suivante
{
previousMillis = currentMillis;
Serial.println("Delai d'extinction depasse");
etat = 1;
}
break;
case 1: // étape de post-extinction
currentMillis0 = millis();
if (currentMillis0 - previousMillis0 >= 5000) // on continue de ventiler pendant 5s
{
previousMillis0 = currentMillis0;
Serial.println("fin de post-extinction");
etat = 0;
}
break;
}
}
le break est bien en dehors du if donc ce n'est pas ça.
je vois que dans le premier if du case 0 on change l'état mais on laisse le code se poursuivre , ce n'est pas toujours une bonne chose. Si on a décidé de changer d'état, on règle ce qu'il faut et on fait break . Ici on dirait qu'il manque une gestion de previousMillis0 quand on sort du case 0.
La machine à états me semble aussi très incomplète et un enum pour décrire les états serait sans doute le bienvenu
Oui mais pour un fonctionnement comme attendu ça ne peut pas fonctionner comme ça.
Il faudrait à minima faire un previousMillis0 = millis(); lors du passage de l'état 0 à l'état 1.
En quasi profane, je vois à quel point un simple ajout de condition peut faire réfléchir... D'abord merci pour vos contributions.
Mes insomnies m'ont conduit ce matin à écrire le code du simulateur autrement (pardon d'avance pour le non respect des règles d'indentation).
J'ai rajouté une étape initiale pour écrire la première variable et n'ai utilisé que 2 variables :
unsigned long tpExtinct;
unsigned long tpPostExt;
int etat = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
switch (etat) {
case 0: // étape de fin de chauffe
if (analogRead(A0) > 512)
{Serial.println("chauffe en cours");
tpExtinct = millis();
etat = 1; }
break;
case 1: // étape d'extinction
if (analogRead(A0) <= 512) // si flamme éteinte, on passe à l'étape suivante
{Serial.println("fin d'extinction");
tpPostExt = millis();
etat = 2;}
if (millis() - tpExtinct >= 20000) // si le délai de 2s est dépassé, on passe à l'étape suivante
{tpExtinct = 0;
Serial.println("Delai d'extinction depasse");
tpPostExt = millis();
etat = 2; }
break;
case 2: // étape de post-extinction
if (millis() - tpPostExt >= 5000) // on continue de ventiler pendant 5s
{ tpPostExt = 0;
Serial.println("fin de post-extinction");
etat = 0; }
break; } // fin du switch
} // fin du loop
La mise à 0 des variables tpPostExt et tpPostExt n'est probablement pas nécessaire.
Ce code fonctionne en simulation.
Je vais l'implémenter en soirée sur la chaudière pour valider.
1 - L'indentation est absolument absconse (non, cela n'est pas un gros mot ... Donc code illisible) - Cf. Style d'indentation couramment pratiqué pour les programmes écrits en Langage C et C++
2 - Je ne vois toujours pas de default: dan le bloc switch/case
3 - Effectivement, rien ne sert de mettre à 0 une variable qui sera plut tard initialisée à autre chose (l'optimiseur, suivant son niveau d'optimisation, ne généra d'ailleurs pas cette remise à 0)
NB:
if (millis() - tpExtinct >= 20000) // si le délai de 2s est dépassé, on passe à l'étape suivante
Je ne pense pas que le commentaire corresponde au code écrit (et inversement)
=> Le Langage C propose la notion de #define
=> Au lieu d'écrire un commentaire erroné, définir une constante explicite comme:
Bonjour @J-M-L,
Si j'ai bien compris, les define sont, avant la compilation et l'édition de lien, interprétés par le préprocesseur, qui remplace partout dans le code où il rencontre le nom défini par la valeur associée... Mais cela ne défini pas de variables, et n'occupe donc pas de place en mémoire ?
Manifestement la bonne pratique en C++ est de typer ses constantes, donc de déclarer des variables immuables pour stocker ces valeurs.
Est-ce que cela veut dire que si on utilise const on utilise de la place en mémoire alors que constexpr est l'équivalent du define en C ?
J'ai un doute quand à mon interprétation...
Qu'est ce que rollower ?
En espérant ne pas avoir posé de questions stupide... ,
Cordialement,
Pandaroux007
Merci pour le conseil, je prends.
Le principe du code fonctionne en réel sur la chaudière.
Il ne me reste plus qu'à le parfaire, ne serait-ce que par respect pour les contributeurs, et aussi pour les ménager pour le jour où j'aurai encore besoin de support.
En théorie ce que vous dites et juste mais l’optimiseur sait que ce sont des constantes et donc il injecte les valeurs comme si c’était un define, mais en tenant compte du type.. d’autre part l’injection brute de la valeur dans le code ne veut pas dire que le compilateur ne sera pas obligé de gérer de la mémoire temporaire pour effectuer les opérations.
Et l’absence de type est un piège dans lequel les débutants tombent souvent
EDIT : c'est une autre bonne pratique d'utiliser des parenthèses sur les valeurs des define même quand il n'y a pas de calcul - (par exemple #define foobar (12345) ?
oui le calcul 60 * uneSeconde se fait entre 2 int et donc sur 16 bits et 60,000 ne rentre pas (max 32767) et donc on passe en négatif.
le rollover c'est quand vous basculez depuis le haut pour revenir en bas.
un autre exemple : un byte va de -0 à 255, si vous avez
byte x = 255;
x = x + 1; // <=== rollover, x passe de 255 à 0
➜ Le rollover fait passer la valeur maximale à zéro pour un entier non signé, et à la valeur minimale pour un entier signé lors d'une incrémentation de 1.
un rollover c'est un dépassement de capacité, dans le cas d’espèce, tu dois essayer d'aborder le problème en binaire :
Lorsque tu dépasses la capacité d'un int avec l'opération 60 X 1000, pour le résultat, une troncature à 16 bits est immédiatement effectuée et du coup le résultat de l'opération te donne 0b1110101001100000
==> soit un nombre négatif que tu obtiens avec un complément à deux : 0b1110101001100000 complément à deux - Wokwi ESP32, STM32, Arduino Simulator
PS : j’ai fait plusieurs choses en même temps et j’ai mis très longtemps à répondre. Du coup je n’avais pas vu la réponse de @J-M-L
Plus 1 bien sûr
Voilà maintenant j'ai un peu plus de temps. Voici un exemple plus significatif :
#define uneSeconde (1000)
#define uneUnite (70 * uneSeconde)
void setup()
{
Serial.begin(115200);
Serial.print ("uneUnite = "); Serial.println(uneUnite);
Serial.print ("70 000, 17 bits en binaire : 0b10001000101110000 correspond bien à : "); Serial.println(0b10001000101110000);
Serial.print ("70 000 après troncature à 16 bits : 0b0001000101110000 correspond bien à : ");Serial.println(0b0001000101110000);
}
void loop() {}
70 000 est un résultat sur 17 bits : 0b10001000101110000
Si on effectue une troncature à 16 bits (int sur UNO), on obtient : 0b0001000101110000 ce qui correspond bien à 4464 en décimal. Ici le MSB n'est pas égal à 1, le chiffre résultant du dépassement est cette fois positif.
Je pense que cet exemple est plus explicite pour expliquer le phénomène de troncature puisqu'on est sur 17 bits - 1 pour rester dans la norme des 16 bits valable pour un int (-32768 à 32767) sur UNO par exemple.
Dans tous les cas on reste sur les 16 bits de poids faibles en enlevant les bits de poids fort !
Voilà, j'espère m'être bien fait comprendre. Pour moi c'est clair mais je ne suis pas certain d'avoir fait une bonne explication ...
Bonne soirée à tous.
PS : En fait on enlève tous les bits de poids fort supérieurs à 16 bits et si le résultat est négatif (MSB = 1) on effectue un complément à deux. on se situe plutôt dans le cas d’un overflow plus que d’un rollover, d’ailleurs @J-M-L fait bien la distinction dans sa réponse