Arrêt chrono avec GY-61

Bonjour, j'ai besoin d'aide. Je vais faire 3 lâchers de ballon type météo avec des élèves et je souhaiterai chronométrer le temps de vol. Mon cahier des charges : Matériel : capteur "GY-61 ADXL335 à 3 Axes", un bouton poussoir pour la mise en marche, un I2C oled 0.91 ou 1306 de 0.96, et un arduino nano. Fonctionnement souhaité : Démarrage par appuis sur BP, affichage du temps, arrêt automatique par détection de l'inactivité de l'accéléromètre pendant 5 minutes, et arrêt du chrono + affichage figé. Quelqu'un serait-il en mesure de me faire le programme ? Merci beaucoup ! ;o)

:warning:

Post mis dans la mauvaise section, on parle anglais dans les forums généraux. ➜ je l'ai déplacé vers le forum francophone pour vous pour cette fois...

Merci de prendre en compte les recommandations listées dans "Les bonnes pratiques du Forum Francophone”

pourquoi écrire en gras ??

comment voyez vous cela mathématiquement parlant ?

vous lisez l'accélération sur les 3 axes, mais même au sol le ballon peut être balloté par le vent non ?

c'est typiquement une définition de programme qui se prête bien à la programmation par machine à états (cf mon tuto éventuellement)

la boucle principale ferait un truc du genre

void loop() {
  switch (etatActuel) {
    case ATTENTE:
      if (digitalRead(boutonPin) == LOW) {
        demarrerChronometre();
        etatActuel = EN_VOL;
      }
      break;

    case EN_VOL:
      tempsEcoule = millis() - tempsDebut;
      afficherTempsEcoule();
      if (estInactif()) {
        tempsInactivite = millis(); // Enregistrer le temps du début d'inactivité
        etatActuel = PEUT_ETRE_A_TERRE;
      }
      break;

    case PEUT_ETRE_A_TERRE:
      if (millis() - tempsInactivite >= delaiInactivite) etatActuel = A_TERRE; // état confirmé
      if (! estInactif()) etatActuel = EN_VOL; // Revenir à l'état "EN_VOL" si l'accéléromètre est à nouveau actif
      break;

    case A_TERRE:
      arreterChronometre();
      etatActuel = FIN_CYCLE;
      break;

    case FIN_CYCLE: break; // on ne fait plus rien, faudra faire reset
  }
}

les fonctions ont un nom assez parlant , i faudra les écrire :slight_smile:

Merci d'avoir mis le post dans la bonne section, c'est sympa. Je me suis fait la réflexion, pourquoi en gras, peut-être parce qu'en ce moment ma balance me fait la gueule, ou alors je n'ai pas fait attention, c'est curieux en effet ! N'y voyez rien dans cela...

LOL :slight_smile:

Merci pour ce code, je vais l'étudier. Quant à la question sur ma vision mathématique de la chose, je vous remercie de me l'avoir posée, je ne comprends même pas votre question, étant complètement néophyte et béotien à la fois en matière de code. Sauf à ce que la nacelle se balance au bout d'une branche, statistiquement plausible, cela reste rare, elle tombe à terre dans la plupart du temps. Elle reste donc inerte. Votre question est intéressante, car j'ai omis un détail, il serait bien que le chrono ne redémarre pas lorsque la nacelle est prise en main. Fonction à ajouter au cahier des charges.

:slight_smile:

Ma question posait sur quels critères utiliser pour dire que la nacelle est posée au sol.

vous avez choisis un composant qui donne une accélération sur 3 axes.

Physiquement / mathématiquement parlant, un observateur ne peut pas dire s'il est en mouvement à vitesse constante en raison du principe de relativité de Galilée. Selon ce principe, les lois de la physique sont les mêmes dans tous les référentiels inertiels (ceux qui ne sont pas soumis à une accélération). Cela signifie que, sans un point de référence extérieur, un observateur ne peut pas distinguer entre un état de repos et un mouvement rectiligne uniforme.

➜ si la nacelle se déplace à vitesse constante en ligne droite dans le vent, l'accélération sera nulle, sauf les composantes de la gravité qui seraient les mêmes que si la nacelle était posée au sol un peu penchée éventuellement mais peut être ça n'arrive jamais...

Si la nacelle au sol ne se balance pas au bout d'une branche, alors on peut sans doute dire qu'elle est posée si la seule accélération à laquelle elle est soumise c'est la gravité


le code que j'ai posté fait déjà ça


Ok, merci des éclaircissements, en effet en choisissant un 3 axes je pensais que dans le programme il suffirait que les trois données se stabilisent, avec un temps de latence de 5 minutes, pour arrêter le chrono...

A l'horizontale, le déplacement n'est pas à vitesse constante, il suit la vitesse du vent qui varie en fonction de l'altitude, de plus il y a parfois des changements de direction, ensuite à la verticale, après éclatement du ballon, la nacelle est en chute libre sur 30 km environ à 180/200 km/h, puis le parachute s'ouvre et la chute est à 20 km/h avec là également un déplacement latéral. Finalement, sauf à se balancer au bout d'une branche, au sol, en effet, il ne reste plus que la gravité...

Ok donc la stabilité des mesures pendant assez longtemps pourrait suffire

Oui, 5 minutes me semblait suffisantes, pourriez-vous m'aider ?

Je n'ai pas de ADXL335 donc je ne peux rien tester mais votre projet a l'air un peu fun donc je veux bien essayer de vous aider

Pouvez vous préciser la résolution de votre I2C oled 0.91? (128x32 pixels ?)

Tenez voici un bout de code qui pourrait marcher

Dans le simulateur (avec des potentiomètres à la place de votre ADXL335)

  • vous cliquez sur le bouton pour démarrer le décompte
  • pour simuler le mouvement de la nacelle, vous déplacez les curseurs
  • si vous ne touchez plus aux curseurs pendant 5 minutes la durée du vol sera affichée, sinon le décompte reprend.

Dans le code:

  • on lit l'accéléromètre une fois par seconde (periodeEntreMesures)
  • on décide qu'il n'y a pas d'activité si la différence entre la dernière mesure en mouvement et la lecture en cours est inférieur à toleranceCapteur
  • les 5 minutes à attendre sont dans delaiInactivite ➜ pour tester dans wokwi sans avoir à attendre 5 minutes, mettez 5000 à la place de 300000, ça fera 5 secondes pour décider du retour au sol.

à tester à affiner en fonction de vos besoins - dans les idées à travailler

  • sauvegarde en EEPROM de la durée au cas où la batterie se vide avant qu'on ne retrouve le ballon. au boot l'arduino pourrait afficher cette dernière valeur enregistrée.

  • est-ce bien nécessaire d'afficher la durée pendant le vol sur l'écran, ça consomme du courant et personne ne regarde ?

  • on pourrait mettre en sommeil le système entre 2 mesures de l'accéléromètre pour économiser la batterie.

  • rajouter des petites icônes jolies pour dire de presser sur le bouton, afficher qu'on est en vol ou à terre etc...

le code
/* ============================================
  code is placed under the MIT license
  Copyright (c) 2024 J-M-L
  For the Arduino Forum : https://forum.arduino.cc/u/j-m-l

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  ===============================================
*/



#define DEBUG 1    // SET TO 0 OUT TO REMOVE TRACES

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#if DEBUG
#define D_SerialBegin(...) Serial.begin(__VA_ARGS__);
#define D_print(...)       Serial.print(__VA_ARGS__)
#define D_write(...)       Serial.write(__VA_ARGS__)
#define D_println(...)     Serial.println(__VA_ARGS__)
#else
#define D_SerialBegin(...)
#define D_print(...)
#define D_write(...)
#define D_println(...)
#endif


const byte boutonPin = 3;
const byte xPin = A0; // Broche de l'axe X de l'accéléromètre
const byte yPin = A1; // Broche de l'axe Y de l'accéléromètre
const byte zPin = A2; // Broche de l'axe Z de l'accéléromètre

Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &Wire); // écran de 128x64 pixels

enum {ATTENTE, EN_VOL, PEUT_ETRE_A_TERRE, A_TERRE} etatActuel = ATTENTE;

unsigned long tempsDebut = 0;       // Variable pour stocker le temps au lancement
unsigned long tempsEcoule = 0;      // Variable pour stocker le temps écoulé
unsigned long tempsInactivite = 0;  // Variable pour mesurer la durée d'inactivité (début d'inactivité)
const unsigned long delaiInactivite = 300000ul; // 5 minutes en millisecondes
const int toleranceCapteur = 20;    // Tolérance pour considérer un changement significatif ~2%

inline void afficheTemps(const char * txt, unsigned long h, unsigned long m, unsigned long s) {
  display.clearDisplay();
  display.setCursor(0, 0);
  display.print(txt);     D_print(txt);
  display.print(h);       D_print(h);
  display.print(':');     D_write(':');
  if (m < 10) {
    display.print("0");   D_write('0');
  }
  display.print(m);       D_print(m);
  display.print(':');     D_write(':');
  if (s < 10) {
    display.print("0");   D_write('0');
  }
  display.print(s);       D_println(s);
  display.display();
}

void afficherTempsEcoule() {
  static unsigned long heurePrecedente = -1, minPrecedente = -1, secPrecedente = -1;
  tempsEcoule = millis() - tempsDebut;
  unsigned long secondes = tempsEcoule / 1000ul;
  unsigned long minutes = secondes / 60ul;
  unsigned long heures = minutes / 60ul;
  minutes  %= 60ul;
  secondes %= 60ul;

  if (secondes != secPrecedente || minutes != minPrecedente || heures != heurePrecedente) { // on affiche que si nécessaire
    afficheTemps("Vol ", heures, minutes, secondes);
    secPrecedente = secondes;
    minPrecedente = minutes;
    heurePrecedente = heures;
  }
}

void afficherDureeVol() {
  unsigned long duree = tempsInactivite - tempsDebut; // la durée effective
  unsigned long secondes = duree / 1000ul;
  unsigned long minutes = secondes / 60ul;
  unsigned long heures = minutes / 60ul;
  minutes  %= 60ul;
  secondes %= 60ul;
  afficheTemps("Duree ", heures, minutes, secondes);
}


void demarrerChronometre() {
  tempsDebut = millis();
  display.clearDisplay();
  display.setCursor(0, 0);
  display.println("EN VOL");
  display.display();
}

bool ballonEnVol() {
  static const unsigned long periodeEntreMesures = 1000; // on lit l'accéléromètre de temps en temps

  static unsigned long derniereMesure = -1000;
  static bool enVol = true;
  static int derniereLectureX = -1000;
  static int derniereLectureY = -1000;
  static int derniereLectureZ = -1000;

  if (millis() - derniereMesure >= periodeEntreMesures) { // est-ce le moment de faire une lecture ?
    // double lecture pour plus de stabilité
    analogRead(xPin);
    int x = analogRead(xPin);

    analogRead(yPin);
    int y = analogRead(yPin);

    analogRead(zPin);
    int z = analogRead(zPin);

    // Vérifier si les valeurs de l'accéléromètre ont changé de manière significative
    if (abs(x - derniereLectureX) < toleranceCapteur && abs(y - derniereLectureY) < toleranceCapteur && abs(z - derniereLectureZ) < toleranceCapteur) {
      enVol = false; // L'accéléromètre est inactif, on n'est plus en vol
    } else {
      // Mettre à jour les dernières lectures si le capteur est actif
      derniereLectureX = x;
      derniereLectureY = y;
      derniereLectureZ = z;
      enVol = true; // L'accéléromètre est actif, on est en vol
    }
    derniereMesure = millis();
  }
  return enVol;
}

void setup() {
  pinMode(boutonPin, INPUT_PULLUP);
  D_SerialBegin(115200);
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3D)) { // Addresse 0x3D pour 128x64, et 0x3C pour 128x32
    for (;;); // on s'arrête ici
  }
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.clearDisplay();

  display.setCursor(0, 0);
  display.print("PRET"); D_println("PRET");
  display.display();
}

void loop() {
  switch (etatActuel) {
    case ATTENTE:
      if (digitalRead(boutonPin) == LOW) {
        demarrerChronometre();
        etatActuel = EN_VOL;
      }
      break;

    case EN_VOL:
      if (! ballonEnVol()) {                // si le ballon n'est plus en vol
        D_println("INACTIF");
        tempsInactivite = millis();         // Enregistrer le temps du début d'inactivité
        etatActuel = PEUT_ETRE_A_TERRE;
      } else afficherTempsEcoule();
      break;

    case PEUT_ETRE_A_TERRE:
      if (millis() - tempsInactivite >= delaiInactivite) {
        afficherDureeVol();
        etatActuel = A_TERRE; // état confirmé
      }
      else if (ballonEnVol()) {
        D_println("ACTIF A NOUVEAU");
        etatActuel = EN_VOL; // Revenir à l'état "EN_VOL" si l'accéléromètre est à nouveau actif
      }
      break;

    case A_TERRE:
      yield(); // on en fait plus rien
      break;
  }
}

Dans un premier temps, je vous remercie de votre aide et de bien vouloir y passer un peu de temps, c'est très sympa. La raison de ma demande : d'habitude un élève appuyait sur un chrono (d'EPS) dès que le ballon était lâché, ensuite il le donnait à ceux qui suivent le ballon, en général des radioamateurs (avec parfois des élèves), ou moi-même, et arrivés sur le lieu de chute nous arrêtions le chrono, mais le temps de récupération varie beaucoup, et nous n'avions pas le temps de vol réel. Nous avons également la réception des données par radio, mais parfois ça ne fonctionne pas, d'où cette idée d'un chrono automatique. Il y a toujours quelque chose qui ne fonctionne pas, cela rend humble... Vous avez raison, l'affichage n'est intéressant que les premières minutes du lâcher et lorsque la nacelle est récupérée. Cela permettrait en effet d'économiser la pile (pas de batterie car elle se coupe avec le froid). Je pense que l'on pourrait mettre en sommeil de la 10ème minute (car parfois nous avons du retard dans le gonflage) jusqu'à ce la nacelle soit immobile. Le vol est d'en moyenne 3h, 5h au maximum (jusqu'à aujourd'hui). Votre idée de sauvegarde est très intéressante. Je regarde votre code et je vais faire les essais. Merci encore.

Le sommeil serait partiel, on réveillerait le module toutes les x secondes pour vérifier qu'on est encore en vol , puis on se rendort.

si vous voulez allez plus loin, vous pouvez rajouter d'autres capteurs sur le module (GPS pour avoir la trace du ballon, température etc...) et rajouter une carte SD pour l'enregistrement le temps du vol.

Quelle durée de vie a votre pile ? Quelle est la durée habituelle d'un vol ?

PS/ notez que vous pourriez prendre un Arduino Nano 33 BLE Sense qui a l'avantage d'incorporer directement en hardware un capteur inertiel à 9 degrés de liberté, la mesure de la température, humidité, pression atmosphérique et avec 1 Mega de mémoire flash on peut envisager de stocker les mesures pendant le vol directement dans la puce aussi. Pour rajouter le GPS vous pouvez regarder du côté du Arduino MKR GPS Shield.

La résolution de notre I2C oled 0.91 est bien de 128x32 pixels. Ok pour le sommeil partiel. Concernant les capteurs, nous avons un émetteur (Kikiwi -869.450MHz fourni par le CNES/Planète Sciences, il enregistre les données sur une SD quand ça fonctionne, cet émetteur n'est pas fiable) avec 8 capteurs (temp int/ext, Lum, Hygro, Pression, UV, IR, Son) l'émetteur nous envoi les trames, ayant au préalable étalonné les capteurs les élèves décodent les données qui arrivent sous forme de tension, la position GPS est jointe, elle est donc envoyée par radio, mais également par GPRS dans la mesure où la nacelle est en-deça de 3 km. Nous avons un système de secours avec un arduino mega mini pro et les mêmes capteurs. Nous avons également la possibilité d'y joindre un autre émetteur (Kiwi toujours fourni par le CNES/PS) celui-ci est plus fiable mais moins performant, toujours 8 capteurs mais pas d'enregistrement sur l'émetteur, il est en 138.500MHz et nous envoi donc les trames par radio également. Je vais m'intéresser également à l'Arduino Nano 33 BLE Sens, mais comme dit plus haut, je suis nul en code, je travaille au collège et nous programmons par blocs, il faut que je m'y mette...

oui :slight_smile:

sinon je vois que vous êtes bien équipé !

Avec le temps, je me suis équipé... Je viens de faire le montage, le programme se téléverse sans problème, mais rien ne se passe, pas d'affichage...

Enfin, si, il se passe quelque chose, quand je tourne les pots une del rouge se manifeste brièvement sur le nano...

Il faut modifier cette ligne pour mettre 128,32 puisque c’est la résolution de votre écran

Et dans le setup changer l’adresse de l’écran en 0x3C si c’est la bonne adresse pour votre module.

On pourrait mettre des constantes en début de code pour que ce soit plus simple à trouver