Commande automatique de groupe électrogène - machine à états et autres questions

Bonjour à tous

Je suis en train de réaliser(enfin, d'essayer, en tout cas) une gestion automatique de groupe électrogène, qui devra permettre un démarrage du moteur sur commande externe à contact sec, commande interne par poussoir, surveiller le moteur (charge batterie, pression d'huile, température) et le couper si nécessaire et surveiller le temps de fonctionnement pour afficher un témoin "maintenance" si le délai entre entretiens est dépassé.

Il y a un certain nombre d'entrées tout ou rien permettant de lire les capteurs du moteur (pression d'huile, charge batterie, etc) et de sorties commandant des relais pour l'alimentation générale, le contact, le démarreur et le préchauffage.

Le fonctionnement général est géré par une machine à états. Le programme se compose finalement d'une boucle infinie qui effectue :
-lecture entrées
-passage par la machine à états qui détermine les sorties
-écritures sorties

Pour le moment, le programme est le suivant : (voir fichier joint)

J'aurais concernant ce code quelques questions, en particulier sur le stockage de valeurs "word" en eeprom. J'utilise le code suivant, pour décomposer les word en deux bytes et les stocker/relire :

EEPROM.write(adr_tpstotLSB,(byte)tpstot);
EEPROM.write(adr_tpstotMSB,(byte)(tpstot>>8));

pour son écriture, est-ce que la conversion (byte)valeur_en_word retourne bien comme je l'espère le byte LSB ?
et pour la seconde partie, avec une variable de type unsigned int, il ne devrait pas y avoir de problème de "sign extension" lors de >>8 ? et même si le cast (byte) retourne le byte LSB, si il y a un problème de sign extension, ça ne devrait pas interférer ?

  tpstot = EEPROM.read(adr_tpstotMSB)<<8; 	//temps fonctionnement total depuis installation
  tpstot += EEPROM.read(adr_tpstotLSB);

ça, si j'ai bien compris le fonctionnement de <<, ça devrait marcher ?

le problème étant que pour le moment, le matériel n'est pas fabriqué, je peux donc difficilement tester le code en situation...

Concernant la machine à états, c'est la première fois que je me lance dans un codage de ce type, donc les remarques des gourous de la question (barbudor ? ) sont les bienvenues :slight_smile:

Je donnerai bien sûr ici toutes les informations concernant les évolutions du projet, les schémas électriques, etc.

groupe.ino (9.01 KB)

Oui, ton code de décalage devrait fonctionner.

Pour mémoire :

  • l'opérateur << décale les bits vers la gauche et insère à droite des 0
    Le comportement est le même que le nombre soit signé ou non signé : le bit de poids fort est perdu (il n'y a pas conservation du signe=
    Par exemple : 0x8001 << 1 = 0x0002

  • l'opérateur >> décale les bits vers la gauche mais le comportement est différent si le nombre est signé ou non :

i) Sur un nombre signé, le signe (bit de poids fort) est conservé (duppliqué)

   int a = -32765; // == 0x8003
   int b = a >> 1;
   Serial.println( b ); // renvoie -16383 == 0xC001

ii) sur un nombre non signé, un zéro est inséré sur le bit de poids fort

   unsigned int a = 32771; // == 0x8003
   unsigned int b = a >> 1;
   Serial.println( b ); // renvoie 16385 == 0x4001

Sinon, pour ce qui est des automates, bienvenue dans la secte.
Pour recevoir la bénédiction du Grand Maître, il faut donner la moitié de son stock de composants XD

Des questions ?

  • l'opérateur >> décale les bits vers la gauche mais le comportement est différent si le nombre est signé ou non

Le Grand Maître nous fait un petit coup de fatigue, il doit rendre 1/4 des composants qu'il a reçu

  • l'opérateur >> décale les bits vers la droite mais le comportement est différent si le nombre est signé ou non

Oui mais le Grand maïtre pourra peut etre doubler la donation initiale : une moitié pour Mealy, le reste pour Moore !! (sectes jumelles)

Oui, oui, c'est la CSG : Contribution de Solidarité des Gurus.
Chaque Grand Maître doit reverser à sa secte mère de manière hierarchique.
Denis Ritchie nous ayant quitté l'année dernière, ca en fait une de moins à payer.
J'ai jamais cotisé à la secte de La Pomme Croquée mais je ne renie pas mon affiliation à Bill Gates : mon premier programme était sur un TRS-80 en Basic ... Microsoft !

barbudor:
Oui, ton code de décalage devrait fonctionner.

ok pour les décalages. mais pour ce qui est du cast (byte)word, ça va bien me renvoyer l'octet de poids faible du word ?
ou faut-il employer byte(word) ? ça change quelquechose ?

Sinon, pour ce qui est des automates, bienvenue dans la secte.
Pour recevoir la bénédiction du Grand Maître, il faut donner la moitié de son stock de composants XD

Des questions ?

LOL

blague à part, tu cherches des composants en particulier ? j'ai un bon gros stock de trucs avec entre autre des 74LS***, des microcontroleurs anciens (Z80, 68000, etc) et pas mal de composants passifs qui ne me servent pas à grand chose, si ça peut servir avant que ça tombe en désuétude...

Oui le cast (byte)word ne va prendre que l'octet de poids fort. Par exemple, il n'est pas nécessaire de faire (byte)(word & 0xFF).

hu, poids fort ou faible ?

soyons clair, j'ai un doute sur ma compréhension des choses : si par exemple j'ai le word suivant :
word_A = b0011001100001111, j'ai bien 00110011 octet de poids fort, et 00001111 octet de poids faible, non ? ou c'est l'inverse ?

alors (byte)word_A == b00001111 ou b00110011 ?

je sais, j'ai du mal... et j'ai encore plus de mal avec l'hexa :stuck_out_tongue: mes cours d'infos sont loin, loinnnnn.....

bricofoy:
word_A = b0011001100001111, j'ai bien 00110011 octet de poids fort, et 00001111 octet de poids faible

Oui

alors (byte)word_A == b00001111 ou b00110011 ?

(byte)word_A == b00001111

ok, donc il y a une erreur dans ta précédente réponse :

barbudor:
Oui le cast (byte)word ne va prendre que l'octet de poids fort. Par exemple, il n'est pas nécessaire de faire (byte)(word & 0xFF).

il fallait lire, si j'ai bien compris, "ne va prendre que l'octet de poids faible"

j'ai bon ?

Oui, oui, tu as raison
Je suis trop fatigué
Je dis n'importe quoi

:(=>[]

pas de soucis :slight_smile:

je m'interroge sur l'opportunité de séparer ma machine à états en deux, une qui gère uniquement le fonctionnement du moteur, et une qui gère les entrées pour pouvoir simplifier les conditions de changement d'état.
Mais comme les deux seraient amenées à agir sur les mêmes sorties, je doute de la faisabilité de la chose. Ou alors je sépare mon registre de sorties en deux. Mais au final cela va t-il vraiment simplifier ? vu que du coup je vais me retrouver avec une machine dont les conditions de changement d'état dépendent de l'état de l'autre machine... oula non, en fait ça va pas le faire. Heu oui désolé je réfléchis en live, là

Nouvelle version du code (en pièce jointe), nettement plus complète. Normalement la machine à états est finie, à deux ou trois correction dans les conditions près.

groupe.ino (12.2 KB)

J'aime bien ton code : define (au lieu de variables inutiles comme dans beaucoup d'exemples Arduino), machine d'état.
C'est dommage que tu n'ai pas une syntaxe différente pour les variables, les états, .. qui rendrait le code plus lisible.
Pour la packing des bits dans le BYTE, j'aurais utilisé des décalages plutot qu'une multiplication.

Tu sautes 2 échelons direct dans la secte des Adorateurs de la Machine d'Etat :slight_smile:

barbudor:
J'aime bien ton code : define (au lieu de variables inutiles comme dans beaucoup d'exemples Arduino), machine d'état.

Merci :slight_smile:

C'est dommage que tu n'ai pas une syntaxe différente pour les variables, les états, .. qui rendrait le code plus lisible.

Ben pourtant, j'ai justement essayé de faire un truc où les nom indiquent ce que ça représente, comme vet_** pour "valeur état *", ve_ pour "valeur entrée **" etc etc

Tu verrais ça plutôt comment ?

Pour la packing des bits dans le BYTE, j'aurais utilisé des décalages plutot qu'une multiplication.

Heu, où donc ? J'utilise partout des additions, il me semble ?

Il n'y a que dans

void lecture_entrees() {
  // lecture des entrées, stockage dans une seule variable byte pour economiser de la ram et faciliter les manipulations
  entrees = ve_alim * digitalRead(e_alim);
  entrees += ve_ext * digitalRead(e_ext);
  entrees += ve_prh * digitalRead(e_prh);
  entrees += ve_run * digitalRead(e_run);
  entrees += ve_rst * digitalRead(e_rst);
  ubat = coef_ubat * analogRead(e_ubat);
  temps_courant = millis();
}

qu'il y a des multiplications, mais c'est une multiplication par 1 ou 0 de la constante à additionner ou non au registre. Donc au final, ça reste une addition :smiley:

Tu sautes 2 échelons direct dans la secte des Adorateurs de la Machine d'Etat :slight_smile:

Ciel ! 8)

Depuis que j'ai fait du grafcet au lycée et des réseaux de pétri en école d'ingé, j'aime bien les MET, au moins c'est pas (trop) le bordel dans les conditions.

Reste que pour la mise en oeuvre, j'ai comme d'habitude fait les choses à l'envers. En partant du codage "en live" au lieu de faire ma machine sur le papier avant.

Et du coup, j'ai une zone merdique autour des état et_decomp et et _demarreur, qui sont plus ou moins inter-dépendants, et mon état et_run qui regroupe en fait des sortes de sous-états puisque les sorties diffèrent selon certaines conditions...

bricofoy:

C'est dommage que tu n'ai pas une syntaxe différente pour les variables, les états, .. qui rendrait le code plus lisible.

Ben pourtant, j'ai justement essayé de faire un truc où les nom indiquent ce que ça représente, comme vet_** pour "valeur état *", ve_ pour "valeur entrée **" etc etc

Tu verrais ça plutôt comment ?

Voici en gros mes choix a titre purement personnel :

Variable globale ou fonction globale : NomDeLaVariable ou NomDeLaFonction (majuscule sur le début de chaque mot)

Variable locale d'une fonction ou fonction locale d'un module ou fonction/variable membres d'une classe : nomDeLaVariable nomDeLaFonction (minuscule au début, majuscule début des autres mots)

Paramètre d'une fonction : _nomDeParamètre (comme une variable locale mais avec _ devant)

Types : NomDuType_t
Enums (j'utilise systématiquement des enums pour mes états) :
typdef enum {
DEBUT,
EN_COURS,
ARRET
} EtatsMoteur_t;

EtatsMoteur_t EtatMoteur;

Ce sont des choix personnels, chacun fait fait fait c'qui lui plait plait plait XD

Il n'y a que dans

void lecture_entrees() {

// lecture des entrées, stockage dans une seule variable byte pour economiser de la ram et faciliter les manipulations
  entrees = ve_alim * digitalRead(e_alim);
  entrees += ve_ext * digitalRead(e_ext);
  entrees += ve_prh * digitalRead(e_prh);
  entrees += ve_run * digitalRead(e_run);
  entrees += ve_rst * digitalRead(e_rst);
  ubat = coef_ubat * analogRead(e_ubat);
  temps_courant = millis();
}




qu'il y a des multiplications, mais c'est une multiplication par 1 ou 0 de la constante à additionner ou non au registre. Donc au final, ça reste une addition :-D

J'aurais écris :

  entrees = digitalRead(e_alim) << BIT_ALIM
             | digitalRead(e_ext) << BIT_EXT
             | digitalRead(e_prh) << BIT_PRH
             | digitalRead(e_run) << BIT_RUN
             | digitalRead(e_rst) << BIT_RST;

Encore question de goût.

pour les variables : en effet, maintenant que tu le dis, j'ai quelques réminiscences de stage chez Motorola ou il y avait des convention de codage dans ce style... Pour une prochaine version je remettrai ça d'équerre.
Pour le type "enum" pour les états, j'avais prévu au départ de faire un truc comme ça, sauf que les cours d'infos sont loin, que j'ai pas trouvé rapidement d'exemples démonstratifs sur le net, et que c'est pas détaillé dans l'arduino référence.
Du coup, j'ai fait ce que je savais faire...

En plus là je stocke l'état dans un byte, soit me semble t'il la plus petite variable possible sur un arduino. Avec une enum, concrètement ça bouffe quoi comme place en mémoire ?
Bon ceci dit je suis je pense bien loin d'arriver au bout de la ram de la bestiole, mais bon...

pour la lecture des entrées, oui en effet maintenant que tu l'a écris, ça parait tout simple :grin: mais les décalages, c'est nouveau pour moi, et ça ne me vient pas naturellement. Je suppose qu'en plus d'être plus clair, ton code est nettement plus rapide à l'execution, non ?

Avec une enum, concrètement ça bouffe quoi comme place en mémoire ?

Normalement un compilo bien élevé implémente l'énum dans la plus petite variable possible.
Donc ca devrait être un byte aussi.

ton code est nettement plus rapide à l'execution, non ?

Pareil, un compilo bien optimisé devrait savoir traduire automatiquement une multiplication par une puissance en un décalage.
Mais au cas ou... mieux vaut l'écrire comme cela :wink:

Bon, ça progresse. Le code n'a guère évolué, mais il est nettement moins plein de bugs.
Et le hard a commencé à se profiler.

J'ai failli graver un premier PCB hier, et je me suis rendu compte in-extremis que mon design était trop grand et ne rentrait pas dans le boîtier... oui je sais, organisation, tout ça, blablabla.... :ouin:

Donc là je suis en train de rerouter la carte en plus petit...

Dommage, mon premier essai de transfert thermique d'impression laser sur le cuivre semblait bien réussi.
A ce sujet, pour ceux qui veulent se lancer dans l'aventure de la gravure low-cost, le papier glacé des pubs SNCF est PARFAIT pour faire ça :slight_smile: :slight_smile: c'est impressionnant, le toner se transfère intégralement sur la carte, et le papier se décolle de lui-même avec juste un peu d'eau bouillante :slight_smile:

groupe.zip (535 KB)

groupe.ino (20.8 KB)

ça progresse lentement, mais ça progresse !

un petit coup de gravure, avec un engin qui ne m'avait pas servi depuis 19ans !!

la carte finalement gravée, nouvelle version (la première ne rentrait pas dans le boîtier !! la honte )

un petit coup de sérigraphie, ça présente quand même mieux :

dessous aussi, et aussi étamage à froid :

des p'tits trous, encore des p'tits trous...

mise en place des composants :

le merdier avec enfin tous les composants à leur place :

et les premiers bugs physiques :

forcément, les MOS avec D et S inversés, ça se polarise pas trop bien... :confused:

d'ailleurs à ce sujet, existe-il plusieurs brochages des 2n2222 en TO92 ? ceux que j'ai sur la carte ont collecteur et émetteur inversés par rapport à toutes les datasheet que j'ai pu trouver, j'ai pas vraiment compris...