[conseil] Réalisation d'un anémomètre/girouette pour un voilier

Bonjour à tous,

J’ai récupérer des capteurs d’une station météo Campbell, et j’ai voulu réuitliser l’anémo et la giroutte pour les monter sur un voilier afin d’avoir un retour de vent instantaté (rafale si affinité) et de direction du vent par rapport au bateau.

L’anémomètre fonctionne comme un potar, et d’après sa feuille technique, la vitesse du vent est proportionnelle à la fréquence émise, et non à la tension. J’ai ainsi réalisé une petite plaque de prototype avec un filtre passe-bas, un comparateur LM358, des diodes de protections etc. et après moult bidouillage et essai, j’ai réussi à lire la fréquence avec la librairie FreqMeasure

Anémomètre OK.

Pour la girouette, même histoire, et là j’ai trouvé un code de quelqu’un que j’ai réutilisé/bidouillé/adapté, et avec le code indépendant de l’anémo, la girouette fonctionne et me renvoie la valeur entre 0-360 Deg.

Girouette OK

Là ou cela ce complique, c’est pour unifier les codes en un seul. Je me perds entre les délais de mesure de l’anémo, de calcul, de renvoie, d’affichage des deux capteurs etc. J’ai essayé de retirer les delay, d’utiliser une interruption mais sans succès. J’arrive à afficher les valeurs de la giroutte sans problème, mais pour l’anémo, il y a comme un énorme “lag” entre la réalité et l’affichage des valeurs.
Si l’anémomètre s’arrête, il n’affiche pas “0” mais la dernière mesure prise…

Dans l’utilisation finale, j’aimerai afficher (en serial au début puis après sur un LCD) :

  • vitesse instantané relevée (mesure à 0,5 secondes)
  • vitesse moyenne du vent ( sur une plage de x minutes)
  • vitesse des rafales (moyenne des valeurs les plus élevés dépassant de 18km/h la vitesse moyenne sur x minutes)
  • direction du vent

J’en appelle donc à vos bons conseils sur le code ci-dessous afin de m’orienter dans mon codage, j’aimerai bien savoir ce qui est de travers et des pistes comment le corriger !

/* Anemomètre et Girouette NRG40C& NRG200P (récup d'une station météo Campbell)
   Version 1.0
*/
#include <FreqMeasure.h>
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Hardware pin definitions
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

//PIN Signal de l'anémomètre à brancher sur le PIN(8)
const int analogInPin = A0;  // Pin A0 du signal de la girouette NRG 200P


//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//Définitions des constantes & variables
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

int period = 500 ;  //Période de mesure de l'anémomètre en millisecondes
double sum = 0;
int count = 0;
float vit_ms = 0;
float vit_km = 0;
float vit_kn = 0;


float outputValue = 0;
float rawDirection = 0;
float maxSensorValue = 1023;
float minSensorValue = 0;

int sensorValue = 1;
int finalDirection = 0;
int biggestAddingDirection = 0;
int z = 0;
int sensorValue2 = 0;
int n = 0;
int modeSize = 0;
int c = 0;
int degree = 0;
int addingDirection[] =
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
  40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
  80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
  120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
  160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199,
  200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
  240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279,
  280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319,
  320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362
};

volatile unsigned long millis_display = 0; // tempo affichage des infos



//========================================================================================================
// Routine d'intialisation
//========================================================================================================
void setup()
{

  Serial.begin(57600);
  FreqMeasure.begin();           // Initialise la lecture de l'anémomètre

  while (degree < 362)           // Set all 362 values to zero.
  {
    addingDirection[degree] = 0;
    degree++;
  }
  maxSensorValue = 984.9999;


}

//========================================================================================================
// Loop
//========================================================================================================

void loop()
{
  //Toutes les x secondes, afficher les infos
  if (millis() - millis_display > 1000)
  {
    anemometre();
    girouette() ;
    afficherInfos();

    millis_display = millis();
  }



}
//========================================================================================================
// Calcul de l'anémomètre
//========================================================================================================
void anemometre () {


  if (FreqMeasure.available()) {
    // Moyenne de plusieurs lecture de données
    sum = sum + FreqMeasure.read();
    count = count + 1;

    if (count > 6) {
      float frequency = FreqMeasure.countToFrequency(sum / count);
      sum = 0;
      count = 0;
      vit_ms = (frequency * 0.75) + 0.35;
      vit_km = (vit_ms * 3.6);
      vit_kn = (vit_ms * 1.944);

    }
  }
  else {
    vit_ms = 0;
  }
}


//========================================================================================================
// Calcul de la girouette
//========================================================================================================

void girouette () {
  resetValues();
  z = 0;
  n++;
  sensorValue2 = 0;

  while (z < 10)                           // Get 10 quick readings.
  {
    z++;
    //digitalWrite(12, HIGH);
    //delay(5);
    // read the analog in value:
    sensorValue = analogRead(analogInPin);
    sensorValue2 = sensorValue2 + sensorValue;
    //digitalWrite(12, LOW);
    //delay(45);
  }

  delay(500);                            // set this to 500 so total delay = 1 second.
  sensorValue = (sensorValue2 / z);



  // assume that dead band starts at 356 and ends at 0
  // and values of zero correspond to 360 or 0 degrees:
  // The maximum analogue reading I am getting on the wiper is 981
  // out of a possible range of 1024 (10 bits)
  // MAJ_Fred : la girouette NRG200P a une bande morte entre 4 et 8°, prenons 5° !
  if (sensorValue == 0)
  {
    outputValue = 0;
  }

  outputValue = ((sensorValue - minSensorValue) * 355 / maxSensorValue) + 1.75;

  selfCalibrate();                         // Checks the maximum range of the analoue readings when sensor comes out of dead band.

  ////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Non linearity calculations:
  // Now assume that max non linearity is at 240 degrees and is +0.53
  // Also, assume non linearity itself is linear, not a curve:

  if (outputValue < 240 || outputValue == 240)
  {
    rawDirection = outputValue * 0.53 / 240 + outputValue;
  }
  if (outputValue > 240)
  {
    rawDirection = 0.53 * (358 - outputValue) / 118 + outputValue;
  }
  if (sensorValue == minSensorValue)
  {
    rawDirection = 360;
  }

  degree = (int)rawDirection;

  ///////////////////////////////////////////////////////////////////////////////////
  // Special case for rawDirection = 360:
  if (rawDirection == 360)
  {
    degree = 359 + c;
    c++;
  }
  if (c > 2)
  {
    c = 0;
  }
  if (degree == 361)
  {
    degree = 1;
  }
  /////////////////////////////////////////////////////////////////////////////////////
  // Calculate the running mode (Some of these variables will need to be reset by a call back from main processor).

  addingDirection[degree] = addingDirection[degree] + 1;
  if (addingDirection[degree] > modeSize)
  {
    modeSize = modeSize + 1;
    finalDirection = degree;
  }

  //////////////////////////////////////////////////////////////////////////////////////
  if (finalDirection == 359 || finalDirection == 360 || finalDirection == 1)
  {
    finalDirection = 360;
  }

}
// 30 days = 2592000 seconds
// Each loop of n x 10 is ten seconds
// Every ten seconds max sensor value is reduced by 0.0001
// Every 30 days max sensor values reduced by 2592000 / 10 * 0.00001 = 25.92 degrees
// In 3 days of northerly winds max sensor value will adjust by as much as 2.592 degrees.
// During northerly winds the sensor will self calibrate:

void selfCalibrate()
{
  if (sensorValue > maxSensorValue)
  {
    maxSensorValue = sensorValue;
    tone(11, 1000, 500);
  }
  if (sensorValue < minSensorValue)
  {
    minSensorValue = sensorValue;
    tone(11, 2000, 500);
  }

  if (  ((n > 10) && (sensorValue > 900)) || ((n > 10) && (sensorValue < 100))   ) // Only adjust min and max sensor readings in a northerly wind.
  {
    maxSensorValue = maxSensorValue - 0.001;                                       // Slowly pulls back max sensor value.
    minSensorValue = minSensorValue + 0.001;                                       // Slowly pushes forwards min sensor value.
    n = 0;
  }
}


// Requires a call back pulse of 5 seconds to reset key values.
void resetValues()
{
  int callBack = digitalRead(2);
  if (callBack == LOW)
  {
    digitalWrite(13, HIGH);
    modeSize = 0;
    tone(11, 2500, 500);
    while (degree < 362)
    {
      addingDirection[degree] = 0;
      degree++;
    }
  }
  else
  {
    digitalWrite(13, LOW);
  }
}

void afficherInfos()
{
  //uniquement des Serial.print


  Serial.print(vit_km);
  Serial.print(" km/h :");
  Serial.print("  ||  ");
  Serial.print(vit_kn);
  Serial.print(" kn :");
  Serial.print("\t Degree = ");
  Serial.print(degree);
  Serial.println();


}

Bonjour et bienvenue

J'en appelle donc à vos bons conseils sur le code ci-dessous afin de m'orienter dans mon codage, j'aimerai bien savoir ce qui est de travers et des pistes comment le corriger !

lis bien la Règle du Forum !!

Le sous-forum ou tu poses ta demande n'est pas approprié, il est EXCLUSIVEMENT dédié à la présentation des choses FINIES C'est en quelque sorte la 'Vitrine d'Exposition" !

le lien 'Report to Moderator' permet de demander le déplacement à l'endroit ou l'on demande de l'aide, des conseils.......

Ok merci, j'ai pourtant lu les régles !

Topic déplacé...

Tu as un delay (500) dans la fonction girouette qui n'aide pas pour la fluidité du code. Enlève le. Si ça ne suffit, essaie de commenter les tone, qui sont peut-être bloquants (je n'ai jamais testé) et regarde si ça a un impact.

Oui j'ai déjà essayé et je viens de recommencer, ca ne change rien, j'ai toujours un lag énorme entre le moment ou je commence à faire tourner l'anémo et ou la valeur s'affiche dans le serial...Même chose pour le ralentissement, si l'anémo s'arrête, il se passe environ 10 secondes avant que la valeur ne chute puis reste bloqué à la dernière valeur (mais jamais 0 km/h).

C'est ok pour la girouette.

Sayber:
j'ai toujours un lag énorme entre le moment ou je commence à faire tourner l'anémo et ou la valeur s'affiche dans le serial...Même chose pour le ralentissement, si l'anémo s'arrête, il se passe environ 10 secondes avant que la valeur ne chute puis reste bloqué à la dernière valeur (mais jamais 0 km/h).

Tu fais six mesures avant d'afficher un résultat si l'anémomètre tourne lentement cela peut-être long.
Concernant l'arrêt de l'anémomètre, il y une remarque sur le site de pjrc qui explique que la librairie ne gère pas ce cas et qu'il faut que l'utilisateur implémente un système de hors-temps.

Essaye de déplacer les appels des fonctions anemometre et girouette hors du bloc if de la loop
Pour l'instant ton code dans la loop est équivalent à mettre un delay (1000) puis faire les mesures et afficher les résultats. Or je suppose que ce que tu veux faire c'est mesurer en continu, puisque tu veux une vitesse instantanée, et afficher toutes les secondes ?

Edit : il est peut-être possible de se passer de cette bibliothèque et de faire les mesures directement sur les capteurs avec des interruptions. Peux-tu fournir les datasheets de ton anémomètre et de la girouette ?

Alors c'est beaucoup mieux en sortant les appels de fonctions en dehors de la boucle if de la loop. Et effectivement c'est ce que je veux, continuer a prendre des mesures en background, et afficher un résultat des deux capteurs une fois par seconde.

Pour la datasheet https://www.nrgsystems.com/products/met-sensors/anemometers/detail/40c-anemometer
https://www.nrgsystems.com/products/met-sensors/wind-direction-vanes/detail/200p-wind-vane

l'anémo envoi un courant AC sinusoidale, le montage avec le filtre passe-bas et le LM358 permet de transformer le signal en carré, puis je le lis avec la lib Freqmeasure...et je converti ensuite les fréquences en vitesse.

@fdufnews oui j'ai vu, mais je n'arrive pas à implémenter un systeme hors-temps justement ! peut-on ajouter une condition de type:

  • si FreqMeasure.count.available est faux, alors la vitesse=0 ?

Sayber:
Alors c'est beaucoup mieux en sortant les appels de fonctions en dehors de la boucle if de la loop.

Donc c'est réglé ou pas encore ?

Sayber:
@fdufnews oui j'ai vu, mais je n'arrive pas à implémenter un systeme hors-temps justement ! peut-on ajouter une condition de type:

  • si FreqMeasure.count.available est faux, alors la vitesse=0 ?

Bonjour
il te faut determiner déjà la capacité max de ton anémo (nb impulsions max par unité de temps)
une bon approche est d'étudier/experimenter avec la fonction PulseIn
et son parametre timeout

lesept:
Donc c'est réglé ou pas encore ?

Non pas encore, mais c'est beaucoup mieux concernant le vent instantané (j'ai baissé le nombre de lecture à 4) En cas de ralentissement puis d'arrêt, la dernière valeur reste indéfiniment et ne passe jamais à 0.

L'autre point, c'est le calcul des rafales et du vent moyen qui se fait sur un temps plus long. Comment faire pour faire une moyenne sur 10min par exemple ? ou stocker les données et comment faire ?

@Artouste tout cela je l'ai fait et j'y ai passé un temps fou ! le programme de l'anémo est opérationnel tout seul, je l'ai même étalonner en voiture sur plusieurs vitesse, et c'est plutôt très précis ! J

Pour calculer une moyenne, tout dépend de ce que tu veux faire. Si c'est une moyenne glissante, tu dois mémoriser toutes les données mesurées servant au calcul de ladite moyenne. Le plus simple est de les stocker dans un tableau (array en anglais) et de remplacer la plus ancienne par la dernier mesure pour recalculer ensuite la moyenne.
Sur une durée de dix minutes, il faut que tu choisisses toi même le nombre de données à conserver, ça dépend de la dynamique de ton problème.

Les rafales : tu veux garder la vitesse maximale mesurée ? Que faut-il faire pour les rafales ?

Comment stocker dans un tableau ?

Sur 10 minutes car on peut :

  • faire une moyenne des valeurs (vent moyen)
    -les rafales : valeur max de vent sur 10min dépassant de 10nds le vent moyen

Sayber:
Comment stocker dans un tableau ?

On définit un tableau comme on définit une variable, en ajoutant une dimension, une taille. Par exemple, voici une variable :
int var = 0;et voici un tableau :

int tablo[10];

Pour initialiser c'est un peu plus compliqué :
int tablo[10] = {0};met tout à 0.
int tablo[10] = {0,1,2,3,4,5,6,7,8,9};fait une autre initialisation. Tu peux affecter une valeur à un élément du tableau comme ceci :
tablo[5] = 4;ou lire un élément comme cela

var = tablo[5];

Une dernière chose savoir : le premier élément d'un tableau porte l'indice 0.

Sayber:
Sur 10 minutes car on peut :

  • faire une moyenne des valeurs (vent moyen)
    -les rafales : valeur max de vent sur 10min dépassant de 10nds le vent moyen

Il faut donc faire une moyenne glissante pour vérifier à chaque mesure si elle dépasse le max + 10 kt sur les 10 minutes précédentes. Il te reste à définir combien de mesures tu stockeras sur ta période de 10 minutes, ce sera la taille de ton tableau.

Je suis en train de me perdre complètement dans le code, j’ai l’impression de partir dans tous les sens :confused:

Alors j’ai essayé PulseIn() à la place de FreqMeasure, et ca a l’air de faire à peu près la même chose

void anemo() {
  if ((millis() - timeold) > 1000) { //Timer 1 secondes

    // Mesure la durée de l'impulsion haute
    //noInterrupts();
    unsigned long etat_haut = pulseIn(signal_anemo, HIGH);
    //interrupts();

    // Mesure la durée de l'impulsion basse
    //noInterrupts();
    unsigned long etat_bas = pulseIn(signal_anemo, LOW);
    //interrupts();

    // Calcul de la periode = etat haut + etat bas
    long periode = (etat_bas + etat_haut);
    // Calcul de la frequence = 1 / periode
    frequence = (1 / (periode * 0.000001));
    vit_kmh = (((frequence * 0.75) + 0.35) * 3.6);
     
    if (periode <= 0 ) {
      frequence = 0;
      vit_kmh = 0;
    }
   
    z = 0;
    n++;
    sensorValue2 = 0 ;

    while (z < 1000){
      z++;
      sensorValue = vit_kmh ;
      sensorValue2 = sensorValue2 + sensorValue ;
    }
    sensorValue = (sensorValue2/ z);
    
    timeold = millis();
  }
}

J’ai ajouté la calculatrice à la fin, mais évidemment il me calcule la moyenne sur la loop apparement (donc 1 seconde), et non pas sur une durée de x secondes.

J’ai commencé à mettre les mains dans les tableaux (array) avec l’exemple smoothing, mais j’ai les neurones qui fument entre tous les codes que j’essaie à taton…

Comment définit-on la période de mesure de 10min, avec mettons 60 mesures par minute (donc 600 mesures) ?
int tablo [600] pour 601 mesures (de 0 à 600)
Et surtout comment exprimer :

  • Arduino tu affiches la vitesse instantané
  • Pendant ce temps là, tu récupères une mesure de vitesse instantané/seconde pendant 10mn et tu me sors la moyenne

je rame grave !

int tablo [600];

définit un tableau de 600 valeurs, indexées de 0 à 599.

Il faut que tu fasses des fonctions simples, quitte à en écrire plus. L'avantage, c'est que tu peux valider plus facilement une fonction simple et passer à autre chose sereinement, plutôt que chercher dans les multiples lignes commentées ou modifiées l'endroit où se trouve une erreur. Ensuite, c'est la loop qui va orchestrer le déroulement de ton code.

Par exemple, tu fais une fonction qui renvoie la direction indiquée par la girouette. Dès qu'elle fonctionne correctement, tu n'y touches plus. Une autre fonction renvoie la vitesse instantanée mesurée par l'anémomètre. Idem, une fois que ça marche, tu n'y touches plus. Une autre fonction va faire une moyenne sur un tableau de données.

Ensuite, tu écris la loop pour faire ce que tu veux : mesures de direction et de vitesse toutes les X millisecondes et stockage de la vitesse dans le tableau, calcul de la moyenne toutes les Y millisecondes, affichage toutes les secondes, etc.

Pour faire une action toutes les X millisecondes, tu sais faire il me semble :

if (millis()_chronoAction > X) {
  chronoAction = millis();
// Ici appel de la fonction qui lance l'action
}

Certains mettent le bloc if dans la fonction appelée, d'autres dans la loop, à toi de voir ce que tu préfères. L'important est de simplifier au maximum afin de valider sereinement et ne plus toucher à ce qui est validé...

Bonjour.
Je lis ce topic un peu tardivement ... mais je rentre de croisière !
Amateur de DIY et de navigation, je transfère progressivement toute l'électronique du bord sur des montages perso.
Je suis en phase de développement de l'anémo-girouette en utilisant un capteur Advansea ( moderne) envoyant ses données sur un répétiteur Autohelm ( très vieux) et un module perso portable à base de MKRZ.

L'anémomètre émet 8 impulsions par tour. Il suffit de compter le nombre d'impulsions durant une durée précise pour obtenir directement la vitesse du vent, sans calculs. Utilisation d'une interruption pour capture des impulsions :
attachInterrupt(digitalPinToInterrupt(ANEMO_IN), INT_Anemo, RISING);
Contenu de l'interruption -> void INT_Anemo() { ComptAnemo ++;}

Pour une vitesse en noeuds, je compte les impulsions durant 280ms. Les mesures sont affichées toutes les 0,5s.
La vitesse est aussi intégrée ( moyennée) sur 2s selon la méthode du tableau ( post n°13 de lesept). Ca fonctionne parfaitement.

La girouette ne fonctionne pas comme la tienne ( la mienne fait varier le rapport cyclique d'un signal de 1KHz). Je ne surcharge donc pas ce topic. Par contre là se pose un réel problème d'intégration ( moyenne) : Lorsque l'angle indiqué oscille entre 350 et 10 degrés, nous sommes face au vent, soit au 0. Or la moyenne de (350+10) / 2 = 180, soit plein vent arrière !!!

Pour la moyenne de la direction, il faut convertir en x et y, faire la moyenne sur x et y puis retourner à la direction avec un arctangente (voir un atan2).