sequence sur le portd

KdH76:
bonsoir

oui, mais cela affecte uniquement le tableau et le masque ?

cordialement
kdh

Oui.. affichez l’index de la séquence lors de l’erreur ça vous dira où vous en étiez et ce qu’il attendait

Tenez en prenant une autre approche avec une mini machine à états, on pourrait procéder comme cela:
(non testé):

// définir les pins qui vous interessent dans le masque - elles doivent être contigües
const byte masquePinActives = B01111000; // ici on prend donc D3, D4, D5, D6 (D0 à droite en bit de poids faible et D7 à gauche en bit de poids fort)

// définir la séquence exacte à attendre, y compris les passage à zéro.
// Pas besoin de s'embêter à aligner sur les pins, listez juste les valeurs attendues des pins contigües
const byte sequenceAttendue[] = {B1000, B0000, B0001, B0000, B1001, B0000, B1000, B0000, B1110};

// le reste se configure tout seul
const byte decalageMasque = __builtin_ctz(masquePinActives); /// le nombre de 0 à droite du masque
const byte maxIndexSequence = sizeof(sequenceAttendue) / sizeof(sequenceAttendue[0]) - 1;

boolean sequenceRecue(boolean forceReset = false)
{
  static enum : byte {ATTENTE_DEBUT, SEQUENCE_EN_COURS, ERREUR_SEQUENCE, SEQUENCE_OK} etat = ATTENTE_DEBUT;
  static boolean sequenceRecue = false;
  static byte indexSequence = 0;

  if (forceReset) {
    etat = ATTENTE_DEBUT;
    indexSequence = 0;
    sequenceRecue = false;
  }

  if (!sequenceRecue) {
    byte valeurCourante = (PIND & masquePinActives) >> decalageMasque;
    switch (etat) {

      case ATTENTE_DEBUT:
        if (valeurCourante == sequenceAttendue[0]) etat = SEQUENCE_EN_COURS;
        break;

      case SEQUENCE_EN_COURS:
        if (valeurCourante != sequenceAttendue[indexSequence]) { // on a recu une nouvelle valeur, est-ce la suivante dans la séquence
          indexSequence++;
          if (valeurCourante == sequenceAttendue[indexSequence]) { // si c'est la bonne on regarde si on est à la fin
            if (indexSequence == maxIndexSequence) etat = SEQUENCE_OK;
          } else {
            etat = ERREUR_SEQUENCE;
          }
        }
        break;

      case ERREUR_SEQUENCE:
        Serial.print(F("Etape: "));  Serial.print(indexSequence);
        Serial.print(F(" -> Recu: B"));  Serial.print(valeurCourante, BIN);
        Serial.print(F(" au lieu de: B"));  Serial.println(sequenceAttendue[indexSequence], BIN);
        indexSequence = 0;
        etat = ATTENTE_DEBUT;
        break;

      case SEQUENCE_OK:
        Serial.println(F("Bingo!"));
        sequenceRecue = true;
        break;
    }
  }
  return sequenceRecue;
}

void setup() {
  Serial.begin(115200);
  DDRD &= (byte) (~masquePinActives); // on met en entrée les bits qui nous interessent dans le masque
}

void loop() {
  if (sequenceRecue()) {
    // cette partie du code ne s'exécutera qu'une fois que la séquence a été reçue correctement
    // si vous voulez remettre la séquence en mode attente, appelez sequenceRecue(true);
  }
}

il faut définir masquePinActives pour savoir quelles sont les pins à écouter. Elles doivent être contigües telle que c'est codé et ensuite vous définissez la séquence exacte à attendre, y compris les passage à 0.

la machine a état doit se lire assez simplement.

l'idée c'est que la loop() appelle sans cesse la machine à état. Si la séquence a été bien reçue une fois alors on peut faire des choses dans la boucle. sinon cet appel se charge de gérer le parcours des états jusqu'à bonne réception de la séquence attendue. L'avantage de cette approche par machine à état c'est qu'à aucun moment la machine n'est bloquante en attente d'un événement externe, la loop() peut donc tourner pour faire autre chose en attendant la séquence par exemple.

à voir car tapé ici et non testé.

Bonsoir

Vous introduisez le décalage de masque, en plus du masque si j'ai bien compris ?

la première monture est plus lisible pour un novice comme moi :wink:

toujours sur la première monture , une fois le masque déclaré , nous n'avons plus besoin de nous occuper de l'état des autres pins peu importe leurs valeurs ?

peut-on copier l'état de certaines pins sur le portB , cela me permettrait de supprimer la fonction masque .

Cordialement
KDH

Vous introduisez le décalage de masque, en plus du masque si j'ai bien compris ?

oui j'applique le masque (donc ça ne conserve que les valeurs que l'on veut) et je décale de 3 bits vers la droite, comme ça on peut comparer vraiment avec les valeurs des 4 pins que vous déclarez

Les 3 bits c'est parce que les 3 premiers zéro du masque à droite disent qu'on ne veut pas ces pins. Pour trouver ce nombre 3, j'ai utilisé la fonction __builtin_ctz(), elle compte les zéro à droite

Ensuite la machine à état n'est pas trop compliquée à comprendre

On démarre en disant qu'on est en attente d'un signal compatible avec la première valeur du tableau

donc je dis que mon état initial (variable état) vaut ATTENTE_DEBUT

dans ce cas quand on appelle la fonction, on lit les 4 pins et on compare avec la primère valeur du tableau et si c'est bon alors c'est que la séquence est commencée, je change l'état en SEQUENCE_EN_COURS

      case ATTENTE_DEBUT:
        if (valeurCourante == sequenceAttendue[0]) etat = SEQUENCE_EN_COURS;
        break;

la prochaine fois qu'on revient dans la fonction, l'état est SEQUENCE_EN_COURS donc on exécute

      case SEQUENCE_EN_COURS:
        if (valeurCourante != sequenceAttendue[indexSequence]) { // on a recu une nouvelle valeur, est-ce la suivante dans la séquence
          indexSequence++;
          if (valeurCourante == sequenceAttendue[indexSequence]) { // si c'est la bonne on regarde si on est à la fin
            if (indexSequence == maxIndexSequence) etat = SEQUENCE_OK;
          } else {
            etat = ERREUR_SEQUENCE;
          }
        }
        break;

Ce code dit en gros:

Si je lis toujours la valeur d'avant sur les pins, c'est que c'est l'ancien signal, je ne fais rien

Mais si j'ai reçu une nouvelle valeur (le premier if teste si la nouvelle valeur ne correspond pas à l’entree Qu’on vient d’avoir dans la séquence) alors il faut tester si on a bien reçu la valeur de la séquence suivante.

Donc on augmente indexSequence et on compare avec la valeur reçue.

Si c'est pas la bonne valeur c'est que la séquence est rompue on va à l'état ERREUR_SEQUENCE, sinon c'est bon, on vient de valider une nouvelle étape de la séquence

Dans ce cas deux choses peuvent se produire:

  • soit on est au bout de la séquence (on le sait en testant si (indexSequence == maxIndexSequence) et dans ce cas on a bien reçu tout et on va à l'état SEQUENCE_OK

  • soit on a juste validé un des éléments intermédiaires - dans ce cas il suffit juste de continuer à attendre et on reste dans cet état. Comme on augmenté (comme dans l'autre code) indexSequence au prochain tour d'appel de cette fonction on testera l'attente de l'élément de séquence suivant.

c'est une programmation par machine à états (si vous voulez mieux comprendre comment ça marchecf mon tuto éventuellement)


Sinon pour la question

peut-on copier l'état de certaines pins sur le portB , cela me permettrait de supprimer la fonction masque .

ben c'est ce que l'on fait avec
    byte valeurCourante = (PIND & masquePinActives);On a mis dans valeurCourante d'un seul coup tous les états des pins (et le masque a dégagé les pins qui ne nous intéressent pas)

ensuite si vous voulez voir combien vaut un bit particulier vous pouvez utilisez la fonction bitRead() par exemple bitRead(valeurCourante, 3) va lire le 4ème bit de poids faible (on commence à compter à 0) donc la valeur de la pin D4

Bonsoir

je procedes à des tests petit à petit, toujours sur la premiere monture.

j'ai un souci j'affiche bien sur le moniteur serie la premiere sequence
puis
j'envoie la seconde
puis, plus rien sur le moniteur serie , celui ci reste à la premiere
pour afficher l'état , je ferme et j'ouvre à nouveau le moniteur serie. et la il m'affiche le seconde sequence

je comprends pas pourquoi

merci
KDH

Bonsoir KdH76

Remets ton code sur le forum afin de pouvoir étudier ton problème.

Cordialement
jpbbricole

void setup() {
 Serial.begin(115200);
 DDRD = B0000000;                                                                                                  // Port B en entrée
 sequEnCours = 0;
 sequAtteinte = false;
}

void loop() 
{
 byte pindVal = PIND & B01111000;                                                                                  // Pour cacher les bits inutiles
 if ((pindVal == sequValeurs[sequEnCours]) && !sequAtteinte)                                                       // On met le masque sequValeurs[n] sur la valeur lue pour comparer
 {
   Serial.println("Sequ. attendue :\t" + String(sequValeurs[sequEnCours], BIN) + "\t "  + String(sequValeurs[sequEnCours], BIN)+ "\t "  + String(sequEnCours));
   sequEnCours ++;   
   //Serial.println(String(sequEnCours));
   //Serial.println("SUIVANT");                                                                                            
                                                                                                                   // On passe à la séquence suivante, les autres sont ignorées

             if (sequEnCours >= sequNombre)                                                                                  // Si toutes les séquences sont passées.
              {
               sequAtteinte = true;                                                                                          // Mettre cette ligne en remarque pour un cycle continuel
               sequEnCours = 0;                                                                                              // Compteur de séquences à 0
                Serial.println("\nBingo !!!");
               }
 }
 
 else if(pindVal != 0 && !sequAtteinte)                                                                            // Si pas dans l'ordre ou pas dans les séquences surveillées et pas 0
     {
       sequEnCours = 0;
       Serial.println("Erreur FREQ" + String(pindVal, BIN));
         }

 while (pindVal != 0)                                                                                               // Tant que pas 0
 {pindVal = PIND & B01111000;}

 if (sequAtteinte)                                                                                                 // Si toutes les séquences sont passées.
 {
   Serial.println("ALARME PTI");
   // Mettre, ici, l'action à exécuter
   // Faire un reset pour recommencer ou enlever la remarque de la ligne ci-dessous pour un cycle continuel
   //sequAtteinte = false;                                                                                          // Enlever la remarque pour un cycle continuel
 }
}

Je te redis ça demain matin, pour mettre un code en ligne il faut le mettre entre les balises

 et

Bonne soirée
jpbbricole

ok , merci

bonne soirée et bonne nuit.

KDH

À titre de curiosité vous avez essayé l’autre code?

(Quand vous postez du code mettez tout -> Dans celui ci-dessus vous avez oublié la définition des variables et ça peut jouer bien sûr pour aider à trouver les soucis)

Bonjour KdH76

KdH76:
je procedes à des tests petit à petit, toujours sur la premiere monture.
j'ai un souci j'affiche bien sur le moniteur serie la premiere sequence
puis
j'envoie la seconde
puis, plus rien sur le moniteur serie , celui ci reste à la premiere

Voilà, comme il y avait, pour moi, des confusions quand aux bits utilisés, j'ai réécri, en partie le premier programme, en apportant certaines améliorations et surtout en partant de l'utilisation des bits de 4-7 du PORTD, voire l'entête du programme ARDFR_KdH76_PortdRecepteur.ino pour des informations supplémentaires.

Afin de me rapprocher de la réalité, j'ai également écrit un "émetteur" de bits afin de simuler l'arrivée des tonalités. Il suffit de connecter les pins de ports de l'émetteur, sur le récepteur.

Sur l'émetteur, qui peut être à peu près n'importe quel Arduino, les ports, nombre et positions sont définis dans la variable emulateurPinNbr et le tableau emulateurPins[] dans l'ordre LSB vers MSB. Ainsi pour faire les connexions entre l'émetteur et le récepteur si:
dans le programme de l'émetteur

int emulateurPins[] = {7, 8, 9, 10}; // Pins de l'émulateur de LSB à MSB
Le câblage avec le récepteur se fera
7 -> 4
8 -> 5
9 -> 6
10 -> 7
GND -> GND

Et pour envoyer une séquence de "tonalités", dans le moniteur de l'IDE de l'émetteur il suffit de taper
1234 et enter ce qui corespond à la séquence attendue:
byte sequValeurs[] = {B00010000, B00100000, B00110000, B01000000}; // Valeurs des séquences, sur les bits de poids fort, 4-7, du registre D
L'entrée de la séquence est codée hexadécimale donc accepte 0123456789ABCDEF.

J'ai ajouté un timer afin de surveiller les séquences qui ne se terminent pas.

Les 2 programmes sont dans la pièce jointe.

A ta disposition pour toutes autres explications.

Cordialement
jpbbricole

ARDFR_KdH76_PortdEmRec.zip (3.04 KB)

@JP bravo pour l'émulateur, ça permettra de tester plus simplement des scénari (avez vous poussé la sophistication jusqu’à traiter de A à F pour envoyer des configurations 4 bits arbitraires ?)

sur ce point:

jpbbricole:
Le câblage avec le récepteur se fera
7 -> 4
8 -> 5
9 -> 6
10 -> 7

Je suis en vadrouille donc sur mon smartphone et donc j'ai pas facilement accès au contenu du zip mais je suppose qu'il faut aussi relier les GND des 2 cartes pour que ça fonctionne. peut-être bon de le rajouter dans les recommandations pour le prochain testeur

J-M-L:
(avez vous poussé la sophistication jusqu’à traiter de A à F pour envoyer des configurations 4 bits arbitraires ?)
je suppose qu'il faut aussi relier les GND des 2 cartes pour que ça fonctionne. peut-être bon de le rajouter dans les recommandations pour le prochain testeur

Pour le GND c'est fait (Ah la force de l'habitude)
Pour de A à F, la honte je n'y avait même pas pensé (Là c'est plus la force de l'habitude mais l'âge :confused: )

Quand je te dis que grâce à toi je vais friser la perfection :slight_smile:

Cordialement
jpbbricole

jpbbricole:
Quand je te dis que grâce à toi je vais friser la perfection

LOL

J'ai l'impression qu'on ne sera jamais d'accord sur l'usage de le classe String dans des codes de démos pour des débutants ni sur le manque du break; quand vous recevez le '\n' dans votre écoute du port série...

mais bon - c'est un monde libre - l'important c'est de se faire plaisir

Je jetterai un oeil au code ce soir - si ça vous intéresse je pourrais partager mes impressions, mais j'ai l'impression que ça vous fâche (alors que c'est vraiment constructif et orienté débat d'idées).

Bonsoir

la question va paraitre bete, mais pourquoi vous declarez

byte masqueNettoyage = B11110000;

et aussi
void loop()
{
byte pindVal = PIND & B11110000;

c' est pas suffisant le masque dans la loop ?

merci encore pour votre aide , je teste , je teste

KdH76:
la question va paraitre bete, mais pourquoi vous declarez

Non c'est le programmeur qui est bête!!!

byte pindVal = PIND & masqueNettoyage;                                           // Pour masquer les bits inutiles
 if (pindVal > 0)
 {
 delay(5);                                                              // Attente que tout les bits soient positinnés (sorte d'antirebonds)
 pindVal = PIND & masqueNettoyage;                                            // Pour masquer les bits inutiles

Il faut corriger cette ligne aux 2 places
byte pindVal = PIND & masqueNettoyage;

le programme tourne quand même tant que l'on ne change pas l'emplacement, j'ai ajouter cette variable masqueNettoyage après-coup pour rendre le programme plus souple à gérer.

Cordialement
jpbbricole

je testerais demain soir

merci jpb

Allez quelques commentaires promis - qui valent ce qu'ils valent puisque je pense que votre code est fonctionnel pour l'objectif du test

Dans l'émetteur:

  • bon y'a des Strings... :slight_smile: mais quitte à les utiliser autant ne pas non plus doubler l'usage mémoire.
    au lieu de faire commandeRecue.toCharArray(commandeRecueChar, commandeLongueure);utilisez juste un const char * commandeRecueChar = commandeRecue.c_str(); (cf la doc de c_str() ) pour accéder au buffer en lecture

vous pourrez toujours lire avec un int freqIndex = (int)commandeRecueChar[i];le cast en (int)n'est pas nécessaire puisqu'un char est un entier signé sur 8 bits, il migre donc automatiquement dans un entier signé 16 bits sans soucis.

  • vous transformez tout en String rien que pour m'embêter je suis sûr... :slight_smile:
    Serial.print(String(bitRead(freqIndex, i)));pourquoi ne pas faire tout simplement Serial.print(bitRead(freqIndex, i));pas besoin d'instancier une chaîne et de stocker la conversion dedans pour ensuite juste imprimer et détruire la chaîne...

  • pour la lisibilité au lieu d'utiliser les code ASCII vous pouvez directement utiliser un caractère

if (freqIndex > 64)                          // Si caractere >= A
		{freqIndex -= 7;}
		freqIndex -= 48;

--> utiliser 'A' ou '0'

  • comme déjà dit, je préconise l'ajout du break après avoir reçu le '\n'
		char monChar = (char)Serial.read();                          // Lecture caracteres
		if (monChar == '\n')                                         // Si caractere Nouvelle Ligne (Lf 0xA ou 10) donc fin de ligne
		{
			monCommandNew  = true;                                   // Flag de nouvelle chaine recue
			break; // <<<==== ICI

		}

de manière générale ça permet d'avoir dans le buffer de lecture une ligne de commande à traiter alors que sinon, si vous avez déjà entamé la réception de la ligne suivante, vous allez la rajouter dans cette ligne. (bien sûr je ne recommande pas la classe String non plus, un bon buffer qu'on remplit suffit au lieu de créer des petites Strings en faisant monCommandRx += monChar; (quand la reserve est dépassée).

  • toujours sur les Strings pour éviter d'en créer plus que de nécessaire et remplir la mémoire, quand vous en passez une en paramètre essayez de la passer par référence quand c'est possible --> au lieu devoid monitCmdRecue(String commandeRecue)faitesvoid monitCmdRecue(String& commandeRecue)

voilà. mais comme dit plus haut - c'est fonctionnel pour l'objectif fixé, c'est ce qui compte. Ensuite ce sont des choix personnels ou style de programmation.

J'avais codé un truc similaire au votre pour l'émission, mais j'étais allé à l'essentiel et m'attachant à basculer toutes les pins en même temps donc par manipulation des ports.

Si ça vous tente de commenter en retour et me dire ce que vous auriez fait différemment. c'est fonctionnel pour un UNO et assez condensé.

// Emetteur pour Arduino UNO
// définir les pins qui vous interessent dans le masque 
// elles doivent être contigües dans le PORT B (digital pin 8 to 13)

const byte masquePinActives = B011110; // ici on prend donc  D9, D10, D11, D12 (et pas D8 ni D13)

const unsigned long  longueurImpulsion = 100ul; // durée de l'impulsion en Millisecondes
const unsigned long  longueurPause = 135ul;     // durée de pause en Millisecondes

const byte decalageMasque = __builtin_ctz(masquePinActives); // le nombre de 0 à droite du masque

void setup() {
  Serial.begin(115200);
  DDRB |= masquePinActives; // on met en sortie les bits qui nous interessent dans le masque et ne touche pas les autres
  PORTB &= (byte) (~masquePinActives); // on met les sorties choisies à 0
}

void loop() {
  if (Serial.available()) {
    char c = Serial.read();
    byte elementSequence = 0xFF;

    if ((c >= '0') && (c <= '9')) elementSequence = c - '0';
    else if ((c >= 'a') && (c <= 'f')) elementSequence = c - 'a' + 10;
    else if ((c >= 'A') && (c <= 'F')) elementSequence = c - 'A' + 10;

    if (elementSequence != 0xFF) {
      PORTB |= (elementSequence << decalageMasque);
      delay(longueurImpulsion);
      PORTB &= (byte) (~masquePinActives); // on met les sorties choisies à 0
      delay(longueurPause);
    }
  }
}

si ça vous tente je vous donnerai mon avis sur le récepteur, si ça sert à quelque chose (mais globalement il fait je job aussi je pense)

La seule chose que je dirais cependant pour KdH c'est de changer le

	while (tonValeur != 0)                                                     // Attente fin tonalité
	{tonValeur = PIND & B11110000; delay(100);}

en

	while (tonValeur != 0)                                                     // Attente fin tonalité
	{tonValeur = PIND & B11110000; }

ou de mettre un délai plus petit pour éviter tout risque sur le timing (et noter que le timeout n'arrivera pas si on ne reçoit pas la trame nulle, c’est une partie bloquante du code que l’approche par machine à état réglait)

J-M-L:
voilà. mais comme dit plus haut - c'est fonctionnel pour l'objectif fixé, c'est ce qui compte. Ensuite ce sont des choix personnels ou style de programmation.

Allez quelques commentaires promis - qui valent ce qu'ils valent puisque je pense que votre code est fonctionnel pour l'objectif du test

Voilà à peu près tout ce qu'il y a retenir, c'est du pure J-M-L.
Ne perds pas ton précieux temps à faire le même exercice avec le programme récepteur....

Bonne journée
Cordialement
jpbbricole

jpbbricole:
Voilà à peu près tout ce qu'il y a retenir, c'est du pur J-M-L.

Chacun sa façon de voir - je pense qu'il y a bcp à retenir. L'étude de cas, la confrontation des idées dans un respect mutuel fait partie des meilleurs moyens d'enrichir sa pratique.

J'ai l'impression que vous prenez les commentaires constructifs sur votre code ou les rappels à la théorie sous-jacente comme des critiques personnelles ou je ne sais quel manque de respect Ce n'est pas le cas.

Et pourtant vous réagissez à chaque fois soit avec agressivité ou sarcasme.

Voir qu'il y a plusieurs chemins menant à des codes fonctionnels, comprendre que deux solutions vraiment différentes peuvent répondre à un même besoin exprimé, évaluer les bienfaits de chacune des deux approches est quelque chose de positif.

Le commentaire sur la gestion du 'A' à 'F' par exemple (quelle que soit l'approche) a permis d'améliorer la fonctionnalité de votre simulateur.

Mon bout de code avec des opération d'arithmétique booléenne (les & et |) sur les ports montre comment arriver à mettre les pins rapidement en conformité plutôt qu'en passant par plusieurs digitalWrite() (que vous traitez dans le récepteur avec un petit delay() pour attendre la stabilisation - donc c'est fonctionnel comme je dis) mais permet de se rapprocher de la specification du système de @KdH76 qui fait tout en 1 cycle.

Votre code d'écoute du port série sans le break; après réception du marqueur de fin de ligne de commande fonctionnera dans 90% des cas - mais comme on l'a vu dans une autre discussion ne fonctionnait pas si le rythme d'arrivée des données est trop rapide... c'est bien de le comprendre...

La notion de passage par référence au lieu de créer des copies d'objets, c'est important pour la mémoire et la performance. c'est bien de savoir que ça existe...

bref l'objectif est de faire progresser chacun des lecteurs du forum qui se plonge un peu dans un fil de discussion, y compris moi même. Ne ramenez donc pas tout à votre personne.

Ma contribution ici est dans ce but - aider à faire progresser toute la communauté en apportant mon experience et mes idées, les confronter, développer des passions et peut-être pour les jeunes leur donner une ambition pour une future carrière dans l'ingénierie (on en manque tant en France). Et j'en retire bcp de l'expérience des autres aussi, bref "bien vieillir" en se challengeant les neurones..