liberer des pin E/S en accentuant l'usage de variables

Mon projet de répondeur fonctionne très bien avec une Nano V3/ ch340 , toutefois en ayant utilisé toutes les pins existantes ! J'aimerais modifier mon code, en utilisant plus à profit, les variables. Notamment, pour le système de contrôle de fin d'appel monopolisant à lui seul plusieurs E/S.
Le principe actuel étant la détection tonalité 440hz , reprise en mode appui long bouton de 490ms, rejetant toute impulsion plus courte, avec pose imposée de 500ms entre 2 détections, (le standard étant un signal carré 500/500ms) le tout suivi d'un compteur 7 impulsions, afin d'éviter toute commande intempestive. OUi, ça fonctionne efficacement. Au prix d'un certain nombre de tours de passe-passe en E/S.
Mon intention, à partir du résultat actuel du détecteur 440 hz basé sur Groetzel/h, soit une variable que j'ai nommé "Tonalité", serait de pouvoir gérer les fonctions ci-dessus uniquement par des variables.
Depuis deux jours, je tourne dans tous les sens quelques formules, sans résultats. Ayant du mal à adapter l'usage de temporisation avec les variables.

Je ne vois pas le lien entre pins et variables.
De combien de pins avez vous RÉELLEMENT besoin pour vous interfaces au monde extérieur ?
Pouvez vous nous en dire plus sur chacune d’entre elles et votre montage ?

Sinon pour la gestion "mémoire" du processus c’est typiquement une définition de programme qui se prête bien à la programmation par machine à états (cf mon tuto éventuellement)

Bonjour J-M-L;
j'utilise actuellement 4 pins pour cette tache.
En D11, la sortie passe à l'état haut en présence tonalité 440HZ et d'impulsions parasites incessantes( voix) .
Reprise en entrée D5, où je gère la mesure largeur impulsion >490ms, en appliquant la librairie Bouton de Bricoleau.
Sortent en D8,les impulsions confirmées de plus de 490ms, ré-introduites en A2 elles sont à la fois comptées et espacées de 500ms afin de coller au plus près du signal normalisé.
Enfin, le résultat compteur active dès la 7ème impulsion, une sortie n'entrant pas dans celles que je souhaite remplacer. ça fonctionne à merveille ainsi.
A la sortie détecteur basé sur Groetzel/h , je souhaite déclarer la variable "tonalité = true " au lieu de (11,HIGH) tel qu'actuellement. Et appliquer le même traitement ensuite que décrit ci-dessus, en interne, par usages de timer appliqués à des valeurs et non des états E/S.
j'ai consulté ton tuto sur la machine à états. Parfait, mais mon niveau actuel est loin de me permettre de comprendre et d'appliquer ce type de code.

A titre d'étude de code, voici ce que je pensais pouvoir faire pour la première étape de mon processus.
Bien évidemment, ça coince quelque part.
Le code suivant est un test de simulation

int temps_defini =490;                                       // durée minimum d'une impulsion pour être active
int ledpin = 10;                                             // led témoin activée par cette impulsion ( pour le test uniquement)
boolean tonalite = false;                                    // variable issue du détecteur 440hz basé sur Groetzel/h

unsigned long debut_creneau;                                 // variable comptage durée
boolean variable = false;
int etat_tonalite= LOW;
int etat_led = LOW;
int dernier_etat_tonalite =LOW;



void setup() {
  pinMode(3,INPUT_PULLUP);                                   // le detecteur est simulé par un bouton en D3
  pinMode(4,OUTPUT);                                         // led témoin prise en compte action sur le bouton
  pinMode(10,OUTPUT);                                      // led témoin impulsion validée faisant plus de   
                                                                       //   490ms
                                                                                         
}
void loop() {

  

  if(digitalRead(3)==LOW)digitalWrite (4,HIGH);
  tonalite= true;                                                  // création d'une impulsion tonalité par appui long sur bouton, la led s'allume instantanément
                                                                    
   if ((tonalite) == true)
   etat_tonalite =HIGH;                                            // condition de changement d'état 
   else
   etat_tonalite = LOW ;

  

  if( etat_tonalite == HIGH && dernier_etat_tonalite == LOW){
    debut_creneau = millis();                                                 // démarrage comptage durée
    variable = true;
  }
  {
    if(variable == true && etat_tonalite == HIGH && dernier_etat_tonalite == HIGH){                // maintien de l'impulsion tonalite
      if ((millis ()-debut_creneau)>= temps_defini){                                         
        etat_led = HIGH;                                                                          // led en d10 s'allume si l'impulsion dépasse 490ms
      }
      if ( etat_tonalite == LOW){  
        etat_led = LOW;                                                                          // extinction led en absence tonalité
      }
      dernier_etat_tonalite = etat_tonalite;
      digitalWrite(ledpin,etat_led);
    }
  }
}

vangel:
j'utilise actuellement 4 pins pour cette tache.
En D11, la sortie passe à l'état haut en présence tonalité 440HZ et d'impulsions parasites incessantes( voix) .
Reprise en entrée D5, où je gère la mesure largeur impulsion >490ms, en appliquant la librairie Bouton de Bricoleau.
Sortent en D8,les impulsions confirmées de plus de 490ms, ré-introduites en A2 elles sont à la fois comptées et espacées de 500ms afin de coller au plus près du signal normalisé.

Le signal qui sort en D11 et celui qui sort en D8 sont nécessaire à l'extérieur de la carte Arduino?

Pouvez vous reexpliquer en français ce que le code t’oit faire ? (Cahier des charges)

Non fdufnews, les signaux en D11 et D8 ne sont pas nécessaires en extérieur, le seul résultat final étant d'obtenir une commande externe déclenchée par 7 tonalités calibrées et confirmées imposant la seule sortie active.
J-M-L, le code doit à partir d'une variable vraie , résultat d'un détecteur de fréquence, mesurer la durée durant laquelle elle reste vraie , soit 490ms maintenues sans aucune coupure.
Si cette durée est obtenue,sans aucune coupure, une seconde variable disons A mémorise cette impulsion comme validée.
Cependant,Un temps de pause de 500 ms entre deux validations de la variable A, correspondant à l'image du signal normalisé des télécom de fin d'appel (440hz durant 500ms suivi de 500ms de pause) doit les espacer les unes des autres.
Un compteur ensuite, comptabilisera 7 impulsions( variable A vraie) pour activer une sortie où figure la seule pin à conserver.

Ok donc vous utilisez des pins comme mémoire au lieu de simplement conserver cela dans des variables ?

La programmation par machine à états permet de traiter assez simplement votre besoin. Avez vous essayé de dessiner un graphe d’états ?

je pense que le graphe pourrait correspondre à ceci.

Suspens intenable :slight_smile:

(Pas de PJ)

vangel:
je pense que le graphe pourrait correspondre à ceci.

Après divers recherches, j'ai compilé un nouveau code adapté d'un code machine d'état existant, du moins annoncé pour tel, modifié à ma sauce .
Il ne concerne pour l'heure, que l'ébauche du chrono 490ms de validation calibrage de l'impulsion tonalité.
Seule la led activée par le bouton s'allume. Ce qui revient à dire que le chrono 490ms ne fonctionne pas.
Est-ce que ce code s'approche de la voie vers laquelle m'orienter?

const unsigned long Delai = 490;
boolean tonalite = false;
boolean var2 = false;

void validation() {
static unsigned long dernierChangement =0;
unsigned long date = millis();
while (tonalite ==true) // tandis que la variable tonalite est vraie
{ date = millis(); // début chrono
if(date - dernierChangement>= Delai) // si chrono - 0 dépasse 490ms , la condition devient vraie
{ digitalWrite(10,HIGH); // led témoin impulsion bien calibrée
var2 =true; // memoire impulsion validée
dernierChangement = date;
}
}
}

void setup(){
pinMode(3,INPUT_PULLUP); // bouton de création variable tonalite ( pour la simulation)
pinMode(4,OUTPUT); // led témoin tonalite = true
pinMode(10,OUTPUT); // led témoin impulsion calibrée à 490ms validée
}

void loop () {

if(digitalRead (3) == LOW)digitalWrite (4,HIGH); // condition création tonalite virtuelle
tonalite=true;
validation(); // lancement de l'action
}

donc si je comprends bien, on part d'un état REPOS et lors de la détection d'une impulsion de tonalité sur une pin on passe en mode appel ENTRANT. Si l'on enchaîne 7 séquences consécutives [HIGH 490ms + LOW 500ms] alors on passe dans un mode signal VALIDE et on active une pin de sortie sinon si un des timings et erroné ou que l'on n'a pas les 7 séquences, on retourne au REPOS.

éventuellement (comme il faut bien revenir en attente), on peut prévoir qu'une fois le signal activé pendant un certain temps, il se désactive et on retourne au REPOS.

la machine à état pourrait alors ressembler à cela:

Merci J-M-L d'accorder intérêt à ma requête.
Oui, actuellement une pin entrante récupère un état 0 issu de la sortie du détecteur Groetzel , lorsque celui-ci détecte un signal 440hz.
Ce signal n'est pas un appel entrant, mais une tonalité de coupure d'appel. Le retour à l'état inverse de cette même entrée se fait de lui-même, en l'absence de tonalité correspondante.
j'envisage donc qu'une variable supplantera cette pin. Affectée True en présence signal, sinon false.

erreur

Sinon, le graphe correspond sensiblement au projet,si toutefois je l’interprète bien.
Ce que tu appelles repos , étant ce que je nommais moi" tonalite".
Détail qui m’interpelle , > à 501 ms provoquerait une fausse alerte, alors qu'en théorie, les telecom envoient bien un signal calibré de 500ms. J'ai considéré > à 490 ms par sécurité, afin de différencier une tonalité des parasites plus courts, considérant que tout ce qui est supérieur à 490 ms vaut une tonalité, tout ce qui est inférieur vaut parasite à ne pas prendre en compte.
Si je comprends également, c'est le compteur que tu as nommé" entrant" qui devient la charnière autour de laquelle les actions s'enchaînent.

Si l'on enchaîne 7 séquences consécutives [HIGH 490ms + LOW 500ms] alors
 on passe dans un mode signal VALIDE et on active une pin de sortie 
sinon si un des timings est erroné ou que l'on n'a pas les 7 séquences, 
on retourne au REPOS.

Cela résume bien l'objectif recherché à partir d'une variable d'entrée , et aboutissant sur une pin de sortie.
Ne conservant que la pin A0 pour l 'entrée du détecteur Groetzel, et D8 pour la pin de sortie. Soit une économie de 4 pin sur les 6 au total sur ma réalisation actuelle.
Le retour en attente peut s'avérer utile effectivement, pour de futures évolutions, car actuellement, si le montage interprète bien une vraie coupure d'appel confirmée par 7 tonalités ( nombre choisi, considérant qu'une erreur puisse malgré tout s'être immiscer ), outre la sortie finalisant l'enregistrement, la séquence prévoit déjà un retour à l'état de veille.

REPOS est mon état initial, en absence de toute "commande". Si je comprends ce que vous dites vous avez un appareil externe sur lequel tourne un Algorithme de Goertzel pour détecter un signal 440hz et cet appareil externe met une pin à LOW lorsque cela se produit sinon la pin reste à HIGH.

=> est-ce bien cela ?

Dans ce cas cela inverserait dans mon schéma les LOW et HIGH.

La boucle principale (déclenchement correct) de la machine à état serait celle ci

On est au REPOS, on reçoit sur la broche de signal Sig un LOW qui indique que l'appareil externe a détecté 440hz. C'est le démarrage potentiel d'un cycle où l'on va attendre 7 [LOW - HIGH] successifs calibrés à ~500ms.

On se met alors en mode comptage du temps avec les 2 états bleus à droite: si on reçoit un HIGH au bout d'environ 500ms ça correspond à la spec, on attend le retour du LOW au bout aussi de 500ms.

Si ce cycle se répète 7 fois alors il y a validation du protocole, on sort du cycle 'bleu" et on passe en état signal VALIDE.

Détail qui m'interpelle , > à 501 ms provoquerait une fausse alerte, alors qu'en théorie, les telecom envoient bien un signal calibré de 500ms. J'ai considéré > à 490 ms par sécurité, afin de différencier une tonalité des parasites plus courts, considérant que tout ce qui est supérieur à 490 ms vaut une tonalité, tout ce qui est inférieur vaut parasite à ne pas prendre en compte.

Si la norme c'est 500ms, un signal plu long serait sans doute synonyme d'erreur non? j'ai pris 501 mais on peut prendre ce que vous voulez comme timeout, 600ms 700ms etc... il vous faut sans doute un truc qui dit que si la broche ne change pas d'état comme attendu au bout d'environ 500ms alors c'est une fausse alerte et on retourne à l'état initial. C'est donc l'objectif des transitions rouges:

Le décodeur Groetzel est intégré à la même carte Nano qui doit gérer le processus de filtrage à l'étude ensuite.
Il n' y a pas d'appareil externe sur cette partie propre au traitement du signal.
Le résultat d'une détection sort actuellement sur une pin, mon intention est de le traiter depuis un changement de valeur d'une variable " tonalite " ou nommée autrement, afin de libérer entre autres cette pin.
Le signal 440hz analogique rentre en A0, depuis le transfo d'isolement réseau téléphonique,via une préamplification, cela je ne peux et ne veux le modifier.

On est au REPOS, on reçoit sur la broche de signal Sig
 un LOW qui indique que l'appareil externe a détecté 440hz. C'est le 
démarrage potentiel d'un cycle où l'on va attendre 7 [LOW - HIGH] 
successifs calibrés à ~500ms.

On se met alors en mode comptage du
 temps avec les 2 états bleus à droite: si on reçoit un HIGH au bout 
d'environ 500ms ça correspond à la spec, on attend le retour du LOW au 
bout aussi de 500ms.

Si ce cycle se répète 7 fois alors il y a validation du protocole, on sort du cycle 'bleu" et on 
passe en état signal VALIDE.

Ce schéma colle bien , si ce n'est qu'on part d'une variable et non d'une broche, pouvant donner à la variable la valeur vraie ou fausse au départ suivant ce qui convient le mieux pour la suite du traitement, à priori sans importance si je ne me trompe. Par exemple, tonalite = true.

Le décodeur Goertzel est intégré à la même carte Nano qui doit gérer le processus de filtrage à l'étude ensuite. Il n' y a pas d'appareil externe sur cette partie propre au traitement du signal.

OK, donc l'entrée Sig se résume simplement à la sortie de l'algo pas besoin d'une pin pour cela.

la machine à état ne consommera quasiment aucun temps CPU significatif donc les 2 processus (Goertzel + machine à état) peuvent tourner sans souci en même temps si l'algo Goertzel donnait déjà satisfaction.

Pourriez vous poster le code actuel avec votre analyse Goertzel sur A0 ?

PS: je crois qu'on dit Goertzel et non pas Groetzel

Bien sûr J-M-L, voici un partiel du code centré sur la détection uniquement, afin qu'il ne soit pas noyé dans un amas de lignes superflues de l'ensemble du répondeur. Il est vrai qu'avec la librairie, le code du décodeur est relativement court.

#include<Goertzel.h>
const int tonalite = 11;
const int sensorPin = A0;
const float TARGET_FREQUENCY=440;
const int N= 100;
const float THRESHOLD = 4000;
const float SAMPLING_FREQUENCY = 8900;
Goertzel goertzel= Goertzel(TARGET_FREQUENCY,N,SAMPLING_FREQUENCY);

void setup() {
pinMode(tonalite,OUTPUT);
pinMode(sensorPin,INPUT);  

}

void loop() {
 goertzel.sample(sensorPin);
 float magnitude = goertzel.detect();
 if(magnitude>THRESHOLD)
 digitalWrite(tonalite,LOW);                                 // Sortie actuelle du signal
}

Autant pour moi pour l' orthographe de Goertzel.

OK - donc la machine à état pourrait ressembler à cela en simplifiant et oubliant le cas ou on resterait coincé dans un état:

et le code (tapé ici, absolument non testé) pourrait ressembler à cela:

#include<Goertzel.h>
const byte sensorPin = A0;
const float TARGET_FREQUENCY = 440;
const float NB_SAMPLES = 100;
const float THRESHOLD = 4000;
const float SAMPLING_FREQUENCY = 8900;
Goertzel goertzel = Goertzel(TARGET_FREQUENCY, NB_SAMPLES, SAMPLING_FREQUENCY);


// ----- LA MACHINE A ETATS -----
enum : byte {REPOS, TONALITE_ON, TONALITE_OFF, VALIDE} etat = REPOS;

const uint32_t dureeAttendue  = 500000ul;   // en µs -> 500ms pour la durée normée
const uint32_t approximation  = 10000ul;    // en µs -> on accepte ±10ms d'approximation sur les tonalités
const uint32_t dureeMin       = dureeAttendue - approximation;
const uint32_t dureeMax       = dureeAttendue + approximation;

const uint32_t dureeValide    = 1000000ul;  // en µs -> on reste en état validé pour 1s

void traitement(bool tonalite)
{
  static uint32_t t0 = 0;
  static byte compte = 0;
  uint32_t deltaT;

  switch (etat) {
    case REPOS:
      if (tonalite) {
        t0 = micros();
        compte = 0;
        etat = TONALITE_ON;
      }
      break;

    case TONALITE_ON:
      if (compte >= 7) { // au bout de 7 cycle on valide
        t0 = micros();
        Serial.println(F("BINGO !!!!"));
        etat = VALIDE;
      } else if (!tonalite) {
        deltaT = micros() - t0;
        if ((deltaT >= dureeMin) && (deltaT <= dureeMax)) { // tonalité pendant ~500ms
          t0 = micros();
          etat = TONALITE_OFF;
        } else {
          etat = REPOS; // fausse alerte
        }
      }
      break;

    case TONALITE_OFF:
      if (tonalite) {
        deltaT = micros() - t0;
        if ((deltaT >= dureeMin) && (deltaT <= dureeMax)) { // tonalité pendant ~500ms
          t0 = micros();
          compte++;
          etat = TONALITE_ON;
        } else {
          etat = REPOS; // fausse alerte
        }
      }
      break;

    case VALIDE:
      if (micros() - t0 >= dureeValide) etat = REPOS;
      break;
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(sensorPin, INPUT);
}

void loop() {
  goertzel.sample(sensorPin); // récupère N échantillons
  traitement(goertzel.detect() > THRESHOLD); // on passe en paramètre l'état de la détection (vrai ou faux)
}

la fonction traitement() est toute simple à lire, elle ne fait qu'implémenter les transitions décrites par les flèches sur le graphe d'états. On lui passe vrai ou faux en paramètre en fonction de la présence de la tonalité ou pas.

Bien sûr cela va être OK si goertzel.sample() ne prend pas trop longtemps


PS:

Autant pour moi pour l' orthographe de Goertzel.

on écrirait sans doute (lit-on ici et là) "au temps pour moi" :grin: :smiling_imp:

Magnifique contribution à ce projet, merci énormément d'y avoir consacré temps et énergie, J-M-L.
J'attends la réception d' un nouveau préampli d'adaptation niveau signal entre la source et la carte Nano pour faire un test sur un nouveau projet. En effet,le signal étant davantage calibré par ce système qu'auparavant, à + ou - , 10ms près du signal normalisé telecom, je me dois de faire le test connecté au réseau.
Ma première idée était de passer par un générateur via mon smartphone, mais j'ai une chance sur 1000, d'envoyer le bon timing, et ce 7 fois, pour vérification. c'est l'affaire de quelques jours seulement, et bien évidemment, je reviendrai vous entretenir du résultat.
j'ai tenté de décortiquer le code,pour le comprendre, étonné de retrouver le compteur en tête de case tonalite-on, alors que pour le néophyte que je suis, le compteur est en phase terminale. Toutefois, s'il est là, c'est qu'il doit l'être, je n'en doute pas un seul instant, forcément avant case valide.
A fin de voir ce qui cloche dans mon raisonnement, voici un code test comparant la durée de vérité d'une variable à une référence temps d'une seconde, s'il elle est égale ou supérieure, une led témoin s'allume.(12)
Seule la led témoin bouton fonctionne, HIGH et LOW suivant appui ou non, lequel engendre la variable "entree" vraie ou fausse. En aucun cas, la led en 12 s'allume,si j'ai maintenu la variable vraie au moins une seconde.
Assimiler ce code simplifié, m'aiderait je pense, à mieux travailler autour d'une variable, notamment avec des notions de durée. Merci encore d'y porter intérêt et de ta fructueuse et amicale collaboration.

bool entree = false;
int temps=1000;
unsigned long debut;
int etat= false;
int dernier_etat = false; 
int variable= false; 

void setup ()
{
  pinMode(3,INPUT_PULLUP);
  pinMode(10,OUTPUT);
  pinMode(4,OUTPUT);
  pinMode(12,OUTPUT);
}

void loop()
{    
    if(digitalRead(3)== LOW)  
   entree = true; 
   else 
   entree= false;  
   
      if (entree) {
        digitalWrite (4,HIGH);
        etat = true;
        
      if  (!entree){
          digitalWrite(4,LOW);
        etat= false; 

        if((etat == true) && (dernier_etat== false)){
          debut=millis();
          variable = true;
        }
        if ((variable == true) && (etat == true)&& (dernier_etat = true)){
          if(millis()-debut>= temps){
            digitalWrite(12,HIGH);
          }
        }
        }}}