Problème programme capteur niveau d'eau US

bonjour, j'ai créé un niveau d'eau avec capteur US pour ma piscine.
avec arduino nano
et capteur A02YYUW
1 alimentation 12V pour alimenter 1 electrovanne + 5V pour la carte arduino
1 relais pour commander l4EV
3 leds : blanche sous tension / rouge relais off / vert relais on.

principe : calcul de la distance, si >12cm => relais on.
lecture distance avec le capteur US OK , cohérente de la valeur réelle
à la mise en marche OK
au bout de qqs temps, plus rien ne se passe.
si j'éloigne le capteur, la mesure augmente, pourtant le relais ne s'active, y compris la led verte.
je pense à 1 saturation de la mémoire de l'arduino

voici le programme (j'en ai 1 autre qui fait le test de distance toutes les 24h, celui ci est simplifié).

merci de votre aide


#include <Wire.h>
#include <SoftwareSerial.h>

// affectation bornes capteur US
#define RXpin 6
#define TXpin 5
SoftwareSerial mySerial(RXpin, TXpin);
unsigned char data[4] = {};
float distance = 0.00;

// affectation led et relais------------------------------------------------
int ledV = 2; // borne digitale led verte remplissage en cours
int ledR = 3; // borne digitable led rouge remplissage  l'arret
int ledB = 4; // borne digitable led blanche systeme en marche
int Relais = 8; // borne digitable relais EV

//paramètres distance et arrosage
const long temps_arrosage = 300000; // durée réglée à 5mn=300000
int HREF = 12; // cote de référence au delà de laquelle on commande le remplissage

void setup() {
  pinMode(RXpin, INPUT);
  pinMode(TXpin, OUTPUT);
  Wire.begin();
  Serial.begin(9600);
  mySerial.begin(9600);
  pinMode(ledV, OUTPUT);
  pinMode(ledR, OUTPUT);
  pinMode(ledB, OUTPUT);
  pinMode(Relais, OUTPUT);
  digitalWrite(ledB,HIGH);
  digitalWrite(ledR,HIGH);  
}

void loop() {
  // Resetting the RX pin
  digitalWrite(TXpin, HIGH);
  delay(30);

  delay(2); // Ensure the pulse is detected
  digitalWrite(TXpin, LOW);
  delay(30);

  // Send positive pulse to the sensor's RX pin to start measurement
  digitalWrite(TXpin, HIGH);

  // Wait for the measurement to complete
  delay(60); // Adjust delay based on sensor specifications
  
  // Gathering & calculating the data
  for(int i=0; i<4; i++) { data[i] = mySerial.read(); }
  distance = ((data[1]*256)+data[2] )/ 10;

  // Display the measurement
  Serial.print("Distance: ");
  Serial.print(distance);
  Serial.println(" cm");
    if (distance > HREF){
      digitalWrite(ledR,LOW);
      digitalWrite(ledV,HIGH);
      digitalWrite(Relais,HIGH);
      delay(temps_arrosage);
      digitalWrite(ledV,LOW);
      digitalWrite(ledR,HIGH);
      digitalWrite(Relais,LOW);}
     else { }
      }

Je pense que ça vient plutôt du fait que le programme lit toujours 4 octets bruts avec mySerial.read() sans vérifier que le premier est bien 0xFF (pas de synchronisation sur la trame).

Comme le capteur émet en continu, le buffer logiciel de SoftwareSerial, qui n'a que 64 octets max sur Nano, se remplit en permanence. Pendant le delay(temps_arrosage) de 5 minutes, aucune lecture n'a lieu, donc le buffer déborde et les octets se décalent.

Après ce délai, la lecture des 4 octets ne tombe plus sur le début d'une trame : les valeurs de distance calculées deviennent incohérentes, et comme le programme ne vérifie jamais que la trame est valide, il continue de fonctionner avec des données fausses, sans doute inférieures au seuil, donc le relais ne se réactive plus.

Si vous voulez bien écouter le port série (ou gérer un flux asynchrone) vous pouvez jeter un oeil à mon petit tuto sur le sujet

Le principe de la correction consisterait à réécrire la lecture pour chercher l'octet 0xFF avant de lire la trame, vérifier le checksum, voire si vous faites des lectures peu fréquentes, vider le buffer des octets en trop avant chaque lecture pour bien avoir la dernière valeur (ou Idéalement il faudrait aussi remplacer le delay bloquant de 5 minutes par une temporisation basée sur millis(), pour continuer à lire et vider le port série pendant l'arrosage mais prendre des décisions que de temps en temps)

———

au fait - pas la peine de faire

la bibliothèque SoftwareSerial fait ça pour vous et ce truc là me semble un peu une hérésie pour lire un capteur série...

vous êtes sûr que c'est comme cela que le capteur fonctionne ?? De mémoire je pense que le capteur A02YYUW envoie en continu des trames de 4 octets (0xFF, poids fort, poids faible, checksum) sur son UART, à peu près toutes les 100 ms, sans besoin de déclenchement.

j'ai essayé de faire une passe de nettoyage (j'ai changé quelques broches)

#include <SoftwareSerial.h>

// affectation bornes capteur US
// pins 2 et 3 choisies car elles disposent des interruptions matérielles les plus précises sur Arduino Nano,
// ce qui améliore la fiabilité de SoftwareSerial pour la réception des trames
#define RXpin 2
#define TXpin 3
SoftwareSerial capteurDistance(RXpin, TXpin);

// affectation led et relais
const byte pinLedB = 4;    // led blanche : système en marche
const byte pinLedV = 5;    // led verte : remplissage en cours
const byte pinLedR = 6;    // led rouge : remplissage à l'arrêt
const byte pinRelais = 8;  // relais EV

// paramètres distance (en mm) et remplissage
const unsigned long temps_remplissage = 5ul * 60ul * 1000ul;  // durée réglée à 5mn, exprimées en ms
const int seuilDemarrage = 120;                               // cote de référence en mm au delà de laquelle on commande le remplissage
const int seuilSecurite = 50;                                 // cote de sécurité en mm en dessous de laquelle on coupe immédiatement, trop plein

// machine à état du système
enum EtatSysteme { REPOS, REMPLISSAGE } etat = REPOS;
unsigned long tempsDebutRemplissage = 0;

void setup() {
  pinMode(pinLedV, OUTPUT);
  pinMode(pinLedR, OUTPUT);
  pinMode(pinLedB, OUTPUT);
  pinMode(pinRelais, OUTPUT);

  digitalWrite(pinLedB, HIGH);
  digitalWrite(pinLedR, HIGH);
  digitalWrite(pinLedV, LOW);
  digitalWrite(pinRelais, LOW);

  Serial.begin(9600);
  capteurDistance.begin(9600);
}

// avance la machine à état de lecture d'un octet à chaque appel si un octet est disponible
// retourne true si une trame complète et valide vient d'être décodée, distance renvoyée en mm
bool lireDistance(int &d) {
  // machine à état de la lecture trame par trame, octet par octet
  static enum EtatLecture { ATTENTE_ENTETE,LECTURE_MSB,LECTURE_LSB, LECTURE_CHECKSUM } etatLecture = ATTENTE_ENTETE;
  static byte octetMSB = 0;
  static byte octetLSB = 0;

  if (!capteurDistance.available()) return false;

  byte octet = capteurDistance.read();

  switch (etatLecture) {
    case ATTENTE_ENTETE:
      // tant qu'on n'a pas 0xFF, on ignore l'octet et on reste en attente
      if (octet == 0xFF) etatLecture = LECTURE_MSB;
      break;

    case LECTURE_MSB:
      octetMSB = octet;
      etatLecture = LECTURE_LSB;
      break;

    case LECTURE_LSB:
      octetLSB = octet;
      etatLecture = LECTURE_CHECKSUM;
      break;

    case LECTURE_CHECKSUM:
      {
        byte checksumCalcule = octetMSB + octetLSB - 1;
        etatLecture = ATTENTE_ENTETE;
        if (octet == checksumCalcule) {
          d = (octetMSB * 256) + octetLSB;  // distance en mm, telle qu'envoyée par le capteur
          return true;
        }
      }
      break;
  }

  return false;
}

void remplissage() {
  etat = REMPLISSAGE;
  tempsDebutRemplissage = millis();
  digitalWrite(pinLedR, LOW);
  digitalWrite(pinLedV, HIGH);
  digitalWrite(pinRelais, HIGH);
}

void repos() {
  etat = REPOS;
  digitalWrite(pinLedV, LOW);
  digitalWrite(pinLedR, HIGH);
  digitalWrite(pinRelais, LOW);
}

void loop() {
  static int distance = -1;  // -1 tant qu'aucune mesure valide n'a encore été reçue
  unsigned long maintenant = millis();

  // lecture du capteur, un octet par tour de boucle, sans jamais bloquer
  if (lireDistance(distance)) {
    Serial.print("Distance: ");
    Serial.print(distance);
    Serial.println(" mm");
  }

  if (distance < 0) return;  // pas encore de mesure valide, on ne pilote rien

  // machine à état pour le pilotage du remplissage
  switch (etat) {
    case REPOS:
      if (distance > seuilDemarrage) remplissage();
      break;

    case REMPLISSAGE:
      // sécurité trop plein ou fin de la durée programmée
      if (distance < seuilSecurite || maintenant - tempsDebutRemplissage >= temps_remplissage) repos();
      break;
  }
}

merci @J-M-L vraiment top de me répondre.
je teste ça très vite.
j'avoue que mon niveau de novicitude ne me permet pas de programmer ainsi.
à très vite

lisez éventuellement des trucs sur la programmation par machine à états (cf mon tuto éventuellement)

PS: le code ci dessus a été tapé ici sur le forum, donc pas sûr que ça fonctionne.

@J-M-L test effectué. bon ben ça ne fonctionne pas.
je vais poursuivre le travail

Est-ce que la distance s’affiche correctement ?
J’ai pris comme hypothèse que le capteur balançait l’info sans rien avoir à faire de particulier.

Pouvez vous décrire vos branchements ?

C'est le cas de ce capteur , voir cet exemple d'utilisation sur le site DFRobot

Oui mais j’ai des doutes car il disait que ça marchait pendant un moment avec son sketch et il faisait

"The sensor sends data permanently via Serial interface (with TTL voltage). One advantage in comparison with the cheaper ultrasonic sensor is, that the

will work autonomously and non blocking. You just have to read the Serial data."

source : A02YYUW Ultrasonic Sensor readout with Arduino (Serial)

les manipulations sur l'entrée RX sont peut être totalement ignorées , à moins que le capteur se base sur l'état haut (état de repos) de RX pour en déduire la présence d'un récepteur ?

c'est vraimant étonnant que cela ait fonctionné avec le code indiqué en #1

Oui - d’où mon questionnement

Bonjour, je confirme pas de remontées de distance mesurée dans le
Moniteur. Je vais tester @al1fch votre code voir si la mesure fonctionne.
N’étant pas à la maison. Je teste quand je peux. Bonne journée .

pouvez vous confirmer que le code posté initialement donnait des mesures cohérentes pendant un moment ?

pouvez vous mettre une photo du capteur / un lien vers là où vous l'avez acheté ?

Bonjour, je confirme que le code que j’ai posté donnait bien des mesures cohérentes. Il indiquait un niveau de 16cm pour à peu près 16 cm mesuré. Seulement il n’y a avait plus de déclenchement du relais au bout d’un temps.

Voici le lien pour le capteur.

Capteur Us

Bonne fin de journée

Le commentaire d'un utilisateur allemand informe que ce capteut a un fonctionnement 'en émission contrôlée' et n'émet pas en continu comme le fait le modèle A02YYUW de DFrobot

commentaire traduit :

Super capteur, mais malheureusement livré sans aucune documentation. Toutes les tentatives de persuader le capteur d'envoyer des données ont échoué. Après de nombreuses recherches sur Internet, il s'est avéré que le spicimen que je possède est un A02YYUW de la rare variante « contrôlée par UART ». Contrairement à la version courante « automatique UART », cela nécessite de déclencher un flanc gauche-h pour la mesure. Le capteur renvoie alors la distance une seule fois puis redevient silencieux. Le flanc L-H peut, par exemple, envoyer n'importe quel numéro au capteur. Une petite note avec cette note pourrait certainement être jointe, cela ferait gagner des heures à l'utilisateur de recherches et de tests... donc seulement 3 étoiles.

ça explique la réception de données cohérentes en utilsant le code indiqué en #1 .... et l'absence de la référence A02YYUW sur l'anonce.

j'ai mis ce capteur (ou un similaire) dans une cuve de récupération des eaux de pluie. Il n'y avait rien à faire si ce n'est écouter le port série pour recevoir les distances. J'avais utilisé un port matériel cela dit, pas SoftwareSerial.

Pouvez vous tester ce code avec votre capteur, connecté ainsi :

Module US Arduino
VCC 5V
GND GND
TX D2 (Rx du SoftwareSerial)
RX D3 (Tx du SoftwareSerial)
#include <SoftwareSerial.h>

constexpr byte RXpin = 2;
constexpr byte TXpin = 3;
SoftwareSerial capteurDistance(RXpin, TXpin);

bool lireDistance(int &d) {
  static enum { ATTENTE_ENTETE, LECTURE_MSB, LECTURE_LSB, LECTURE_CHECKSUM } etat = ATTENTE_ENTETE;
  static byte msb, lsb;

  if (!capteurDistance.available()) return false;

  byte octet = capteurDistance.read();

  switch (etat) {
    case ATTENTE_ENTETE:
      if (octet == 0xFF) etat = LECTURE_MSB;
      break;

    case LECTURE_MSB:
      msb = octet;
      etat = LECTURE_LSB;
      break;

    case LECTURE_LSB:
      lsb = octet;
      etat = LECTURE_CHECKSUM;
      break;

    case LECTURE_CHECKSUM:
      etat = ATTENTE_ENTETE;
      if (octet == msb + lsb - 1) {
        d = (msb << 8) | lsb;
        return true;
      } else Serial.println("erreur cksum");
      break;
  }

  return false;
}

void setup() {
  Serial.begin(115200);
  capteurDistance.begin(9600);
}

void loop() {
  static int distance = -1;
  static unsigned long dernierAffichage = 0;

  if (lireDistance(distance) && millis() - dernierAffichage >= 1000) {
    dernierAffichage = millis();
    Serial.print("Distance : ");
    Serial.print(distance);
    Serial.println(" mm");
  }
}

ouvrez le moniteur série à 115200 bauds et dites nous si vous voyez passer quelque chose.

@JML le capteur de stef_33 diffère d'un AO2YYUW par le besoin d'un front montant sur son entrée RX pour envoyer une mesure unique.

"Mode de sortie : contrôlé par UART" , est bien écrit dans l'annonce .....aux côtés "UART automatique' ambigü

L'annonce ne mentionne pas "AO2YYUW', il s'agirait en fait d'un A02YYTW, voir ci-dessous :

Perplexity m'ai aidé à localiser une info sur la famille de capteurs de distance à ultra sons AO2YY* du fabricant Dypsensor avec les variantes de comportement

ah, en lisant les commentaires je vois effectivement un allemand qui dit

Toller Sensor, aber leider ohne jegliche Dokumentation geliefert. Alle Versuche den Sensor zum Senden von Daten zu überreden schlugen fehl.

Nach ausgiebiger Recherche im Internet hat sich herausgestellt, dass es sich bei dem mir vorliegenden Exemplar um einen A02YYUW in der seltenen "UART controlled" Variante handelt. Diese benötigt im Gegensatz zur gängigen "UART automatik"-Version eine L-H Flanke um zur Messung angetriggert zu werden. Der Sensor sendet dann genau ein mal die Entfernung zurück und schweigt dann wieder.

Die L-H Flanke kann z.B. das Senden irgend einer Zahl an den Sensor sein.

Ein kleines Zettelchen mit diesem Hinweis könnte man doch bestimmt noch beifügen, das würde dem Nutzer stundenlanges Suchen und Testen ersparen... daher nur 3 Sterne.

ce qui se traduit par

Excellent capteur, mais malheureusement livré sans aucune documentation. Toutes mes tentatives pour convaincre le capteur d’envoyer des données ont échoué.

Après des recherches approfondies sur Internet, il s’est avéré que l’exemplaire que j’ai reçu est un A02YYUW dans la rare variante « UART controlled ». Contrairement à la version courante « UART automatic », celle-ci nécessite un front montant L-H pour déclencher une mesure. Le capteur renvoie alors exactement une fois la distance mesurée, puis reste silencieux.

Le front montant L-H peut par exemple être généré en envoyant n’importe quel nombre au capteur.

Un petit feuillet avec cette information pourrait certainement être ajouté au produit. Cela éviterait à l’utilisateur des heures de recherches et de tests… d’où seulement 3 étoiles.

donc peut être un truc comme cela va fonctionner :

#include <SoftwareSerial.h>

constexpr byte RXpin = 2;
constexpr byte TXpin = 3;

SoftwareSerial capteurDistance(RXpin, TXpin);

void declencherMesure() {
  capteurDistance.write(0x01);
}

bool lireDistance(int &d) {
  static enum { ATTENTE_ENTETE, LECTURE_MSB, LECTURE_LSB, LECTURE_CHECKSUM } etat = ATTENTE_ENTETE;
  static byte msb;
  static byte lsb;

  if (!capteurDistance.available()) return false;

  byte octet = capteurDistance.read();

  switch (etat) {
    case ATTENTE_ENTETE:
      if (octet == 0xFF)
        etat = LECTURE_MSB;
      break;

    case LECTURE_MSB:
      msb = octet;
      etat = LECTURE_LSB;
      break;

    case LECTURE_LSB:
      lsb = octet;
      etat = LECTURE_CHECKSUM;
      break;

    case LECTURE_CHECKSUM:
      etat = ATTENTE_ENTETE;

      if (octet == msb + lsb - 1) {
        d = (msb << 8) | lsb;
      } else {
        Serial.println("Erreur checksum");
      }

      declencherMesure();

      return octet == msb + lsb - 1;
  }

  return false;
}

void setup() {
  Serial.begin(115200);
  capteurDistance.begin(9600);

  declencherMesure();
}

void loop() {
  static int distance = -1;

  if (lireDistance(distance)) {
    Serial.print("Distance : ");
    Serial.print(distance);
    Serial.println(" mm");
  }
}

Édit - j’avais pas vu que vous aviez déjà posté la traduction du commentaire

Voici la documentation concernant l'interfaçage des divers capteurs de distance de la série AO2 de Dypsensor (sortie auto, sortie déclenchée...)

pour le capteur de stef_33 c'est en fait un front descendant de RX (=n'importe quel donnée reçue) qui opère pour déclencher un A02YYTW

Oui donc envoyer un octet devrait faire le job

Après c’est juste une question de savoir si le code doit être synchrone ou asynchrone pour lire la réponse et il ne faut pas renvoyer une demande à moins de 70ms - ce que mon code ne force pas donc à traiter par exemple avec une petite attente au cas où

void declencherMesure() {
  static unsigned long dernierDeclenchement = 0;
  unsigned long maintenant = millis();
  unsigned long ecoule = maintenant - dernierDeclenchement;
  if (ecoule < 70) delay(70 - ecoule);
  capteurDistance.write(0x01);
  dernierDeclenchement = millis();
}

(Mais on n’est plus totalement asynchrone)