[Projet fini] Le dentomatique (enfin !) v3.3.3

Bonjour,
Depuis le temps que je travaille dessus, je pense que plusieurs d'entre vous ici en ont entendus parler. Voici une brève description de l'objet (c'est juste une petite victoire cet appareil, rien de bien utile... mais c'est incroyable pour moi d'enfin pouvoir le poster dans Réalisations et Projets Finis ) :

(EDIT : petit ajout de dernière minute : Ça fait environ 6 mois que ce truc traine dans mon tiroir. Merci à tous ceux qui m’ont aidés ici !!!)

Le principe du "Dentomatique" :


j'en avais marre de perdre le fil en comptant les secondes quand je me lavais les dents le matin quand je suis pas réveillé (et le soir aussi, un, pas d’inquiétude !). Du coup, j'ai pensé à un système me permettant de rêver tranquillement pendant qu'il compte à ma place 20 ou 30 secondes pour chaque côté de la bouche (en haut à droite, en haut à gauche, en bas à droite, etc...).

Je principe est simple. Un ruban de 10 LEDs WS2812B sur la face avant, un câble USB derrière, raccordé au connecteur USB de la carte NANO, et sur le dessus, une LED, le bouton ON/OFF (pourquoi pas ! :wink:), et 3 boutons poussoirs :

  • un bouton pause avec un timeout de 1 minute
  • un bouton lançant un cycle de 7 fois 20 secondes quand je suis pressé
  • et enfin un bouton lançant un cycle de 7 fois 30 secondes quand j'ai le temps et que je veux faire ça bien !

Voici un schéma pour mieux comprendre pourquoi 7 cycles de tant de temps :

schéma du principe des cycles

Réalisé grâce à l'IA génératrice d'image gratuite Craiyon et à LibreOffice Draw.

Lorsqu'on lance un cycle de, disons par exemple, 20 secondes, une LED s'allume toutes les 2 secondes (20÷10=2), et une fois atteint les 20 secondes, on éteint tout le bandeau, on attend 500 ms, puis on repart pour un cycle de 20 secondes, et ce jusqu'à atteindre les 7 cycles.

Le code, ses versions et son fonctionnement


J'ai fais plusieurs versions. La première version avait le code directement dans des ISR (interruptions), avec une demi-tonne de `while` à la ligne. Autant dire que c'était pas près de marcher.

La deuxième version, mieux construite, utilisait comme ici une machine à état. Je me rapprochais du but, mais comme j'étais resté sur le système de while, le code était bloquant, et la fonction pause ne fonctionnait pas. logique.

Là, j'ai changé radicalement de technique. J'ai repris la machine à état de la V2, mais j'ai viré les while pour les remplacer par un système de test avec des sauvegardes dans des fonctions de type etat_t (un typedef enum). De là j'ai corrigé deux bugs, et ça marche !

Voici le code, sur lequel j'ai bien galéré, merci à @petitours et @pandar09 pour leur aide !

/*
Name:		 Dentomatique_devellopement_V3.cpp
Created: 17.09.2023
Author:	 Rémi Arduino/Pandar09
version: 3.3.3

Architecture :
  - fonctionne sur la base d'une machine à état

Bibliothèque :
  - OneButton => https://github.com/mathertel/OneButton
  - FastLED => https://github.com/FastLED/FastLED

Liens utiles :
  le wokwi de JML : https://wokwi.com/projects/368412397088476161
  la réponse de JML : https://forum.arduino.cc/t/discussion-programmation-ir-pull-up-pull-down-rebonds-et-interruptions/1132027/315
  le tuto de JML : https://forum.arduino.cc/t/programmation-automate-fini-machine-a-etat/452532/

Ça fait beaucoup de JML, non ? :)
--------------------------------------------------------------------------------------------------------------------
RAM:   [=======   ]  74.3% (used 761 bytes from 1024 bytes)
Flash: [=====     ]  48.9% (used 7008 bytes from 14336 bytes)
> lignes : 231
*/

// déclaration des bibliothèques utilisées ------------------------------------------------
#include <Arduino.h>
#include <OneButton.h>  // bibliothèque pour gérer les boutons
#include <FastLED.h>    // bibliothèque pour gérer les LEDs RGB

// Prototype fonctions locales
void allumeLeds(uint8_t nbLed);
void initAnimation(void);

//# paramétrage app
#define NB_LEDS 10 //Nombre de leds sur le bandeau
#define NB_TOUR_BOUCHE  7 // Nombre d'etapes dans le lavage de dent
#define TEMPS_CLIGNOTEMENT_LED_VERTE 500 
#define TEMPO_MAX_INIT_ANIMATION 5000  // durée max entre 2 changement d'etat de led
const uint32_t dureeMaxPause = 60000ul; // on indique le temps max de la pause

const uint8_t DataPinLEDrgb = 5;
const uint8_t pinBPpause = 10;
const uint8_t pinBP20s = 11;
const uint8_t pinBP30s = 12;

#ifdef LED_BUILTIN
const byte pinLedVerte = LED_BUILTIN;
#else
const byte pinLedVerte = 13;
#endif

//# var globales
CRGB leds[NB_LEDS];

OneButton buttonPause(pinBPpause);
OneButton button20s(pinBP20s);
OneButton button30s(pinBP30s);

uint32_t Millis = 0; // temps en cours
uint32_t lastMillis= 0;      // temps dernier changement led
uint32_t lastMillisPause= 0; // temps activation pause
uint8_t nbToursBouche = 0;     // variable conteur de tours dans la bouche
uint8_t nbLedsAllumees = 0;    // variable conteur de LEDs allumées

typedef enum 
{
  STOP,
  PAUSE,
  MODE20s = 2000,
  MODE30s = 3000
}etat_t ;

etat_t etat = STOP;
etat_t etatEnPause= STOP; // etat actif quand on se met en pause
etat_t etatLast= PAUSE;  // etat actif au dernier passage dans l'animation

void eteindre() {
  // dans cette fonction, on éteint toutes les LEDs
  // et on remet le conteur de LEDs allumées à 0.
  fill_solid(leds, NB_LEDS, CRGB::Black);
  FastLED.show();
  Serial.println("Extinction des LEDs");
}

void callback20s() {
  // ici, c'est la fonction de callback qui met l'état de la machine
  // à état en mode 20 secondes.
  eteindre();
  initAnimation();
  etat = MODE20s;
  Serial.println("Passage en mode 20s");
}

void callback30s() {
  // et ici, c'est la fonction de callback qui met l'état de
  // la machine à état en mode 30 secondes.
  eteindre();
  initAnimation();
  etat = MODE30s;
  Serial.println("Passage en mode 30s");
}

// Met en pause le mode, on repart au meme endroit dans le mode si on reclique sur pause
void callbackPause() {

  if( etat == PAUSE) {
    // sortie de la pause
    switch (etatEnPause)
    {
      case MODE20s : etat = MODE20s ; break;
      case MODE30s : etat = MODE30s ; break;
      default : etat = STOP; break;
    }
    Serial.println("Sortie de pause");
  }
  else if (etat == MODE20s || etat == MODE30s) {
    // Entréee en pause
    etatEnPause = etat ;
    etat = PAUSE ;
    lastMillisPause = millis() ; // lancement tempo timeout
    Serial.println("Passage en pause");
  }
  else {    
    Serial.println("Pourquoi que tu appuis alors que tu es en STOP ? :)");
  }
}

void readButtons() {
  // fonction qui gère (lit) tous les boutons
  buttonPause.tick();
  button20s.tick();
  button30s.tick();
}

bool gestionGreenLED() {
  static bool LEDstate = 0;
  static uint32_t ancienTempsClignotmentLedVerte = 0;
  if ((millis() - ancienTempsClignotmentLedVerte) >= TEMPS_CLIGNOTEMENT_LED_VERTE) {
    LEDstate = !LEDstate;
    digitalWrite(pinLedVerte, LEDstate);
    ancienTempsClignotmentLedVerte = millis();
  }
  return (LEDstate);
}

// Gère les animations de LEDs en fonction de l'état de la machine à état (modifiés via les fonctions de callback)
void gestionAnimation() {
etat_t candidat = etat;

  if(etat == STOP) {
    if(etatLast != etat) eteindre();
  }
  else if(etat== PAUSE) {
    if (millis() - lastMillisPause >= dureeMaxPause) {
      candidat = STOP ; //le traitement du stop (eteindre) se fera au prochain passage dans la fonction, trés bientôt

      Serial.print("Timout pause, passage en STOP");
    }
  }
  else if(etat== MODE20s || etat== MODE30s) {
    // Attente de la bonne période pour faire ce qui est à faire
    if (millis() - lastMillis >= etat) {
      lastMillis = millis();
      allumeLeds(nbLedsAllumees++);      

      if (nbLedsAllumees >= NB_LEDS) {
        nbLedsAllumees = 0;
        nbToursBouche++;
      }

      if (nbToursBouche >= NB_TOUR_BOUCHE ){
        nbToursBouche = 0;
        candidat = STOP; //le traitement du stop (eteindre) se fera au prochain passage dans la fonction, trés bientôt
      }

      Serial.print("Changement : ");  Serial.print(nbToursBouche);
      Serial.print("tours ");  Serial.print(nbLedsAllumees);
      Serial.println("leds");
    }
  }
  else { 
    Serial.println("Etat non gere ! passage en STOP");
    candidat = STOP ; //le traitement du stop (eteindre) se fera au prochain passage dans la fonction, trés bientôt
  }

  etatLast = etat ;
  etat = candidat ;
}

void allumeLeds(uint8_t nbLed) {
  Serial.print("Allumage de ");
  Serial.print(nbLed);
  Serial.println("leds");

  eteindre();

  for(uint8_t i = 0; i < nbLed; i++) {
    leds[i] = CRGB::Green;
    FastLED.show();
  }
}

void setup() {  // setup
  // -------------------serial monitor--------------
  Serial.begin(115200);
  Serial.println("Démarrage...");
  // -------------------attach buttons--------------
  button20s.attachClick(callback20s);
  button30s.attachClick(callback30s);
  buttonPause.attachClick(callbackPause);
  // -------------------attach RGB LEDs-------------
  FastLED.addLeds<WS2812B, DataPinLEDrgb>(leds, NB_LEDS);
  FastLED.setBrightness(30);
  eteindre();
  // -------------------attach green LED------------
  pinMode(pinLedVerte, OUTPUT);
}

void loop() {
  readButtons();
  gestionAnimation();
  gestionGreenLED();
}

void initAnimation()
{
  lastMillis = millis() - TEMPO_MAX_INIT_ANIMATION; // TEMPO_MAX_EINIT_ANIMATION permet qu'une première led s'allume immédiatement
  nbToursBouche = 0;
  nbLedsAllumees = 0;
}

Un petit zip du projet platformIO VScode, parce que je n'utilise plus l'IDE depuis belle lurette, et que je sais qu'ils y en a ici qui sont de mon avis qui ne veulent pas se faire c***r à créer un nouveau projet :
Dentomatique developpement version V3 finie - version PlatFormIO.zip (1,5 Mo)


La version pour l'IDE Arduino en .ino (beurk...)
Dentomatique_developpement_version_V3_finie_pour_IDE_Arduino.ino (6,6 Ko)


Les versions :
Versions.mkd.zip (1,3 Ko)


Comme vous pouvez le voir dans le code, j'ai utilisé la bibliothèque Arduino OneButton et la bibliothèque FastLED :

Le câblage et le matériel :

:bulb: Le câblage est simple, en voici le schéma (fritzing) :

Un bouton pour mettre en pause, un pour les 7 cycles de 20s et le dernier pour ceux de 30s. La LED rouge indique que le programme tourne bien, et que le dentomatique est allumé. La bande de LEDs RGB type WS2812B affiche le temps restant avant de changer de cycle (une LED s'allume toutes les 2 ou 3 secondes selon le mode sélectionné).

:electric_plug: Le matériel utilisé ici est le suivant :

  1. Une carte Arduino NANO, attention avec un ATmega168p (je l'avais en stocke et le programme rentre, je me suis dit pourquoi ne pas m'en débarrasser comme ça ?)

  2. Trois boutons poussoir, une résistance de 220ohms avec une LED (jaune)

  3. un ruban de 10 LEDs RGB type WS2812B

  4. Un interrupteur 2 positions (ON/OFF)

  5. Plaque à pastilles pour faire les circuits, fer à souder, étain 2% plomb (je crois - en tout cas c'est une merveille, ça fond comme du beurre !),

  6. Colle chaude et carton... Bah oui, un, on fait avec ce qu'on peut, et le rendu n'est pas mal du tout, même comme ça...

:zap: l'alimentation :
L'alimentation est effectuée via un vieux câble USB type A vers USB MICRO, sectionné à environ 3 cm du Micro, puis soudé au pin de l'interrupteur ON/OFF à bascule.

Câble d'alimentation

Le design externe :


Pour ce qui est du DESIGN, je me suis inspiré de ce boîtier de Abrège (YouTube), dans cette vidéo : https://www.youtube.com/watch?v=xw8iRNxt-VU

Voici le dentomatique tel que je me l'imaginais au départ (dessin 3D sur Autodesk Tinkercad - lien du projet : Login | Tinkercad) :

design v1 du dentomatique

J'ai ensuite pensé à faire des bords ronds, mais le reste est, dans la version réelle, presque exactement identique. J'ai utilisé pour faire la coque une plaque de carton peu épaisse, que j'ai mise à la bonne forme puis collé. J'ai ensuite collé le font, le tout à la colle chaude (avec soudure + colle chaude dans ma chambre, je vais pas vivre vieux, mais bon... :innocent:)

J'ai créé la face avant du projet en fabriquant une copie de la face arrière, puis en faisant une rainure de la largeur du ruban de LEDs. une fois ceci fait, j'ai collé une feuille de papier cartonné blanche, puis encore une face pour créer une double épaisseur.

Photos des faces du boîtier

Les circuits définitifs :


Enfin, une fois le squelette du boîtier fini, je suis passé aux deux circuits : le module de commande, sur lequel va être enfichée l'Arduino NANO 168p, et le module comportant les trois boutons (je rappelle, pause, 30s et 20s...) et la LED qui clignote. Après de très longs moments passés à souder des fils sur des plaquettes à pastilles, j'ai fini par créer deux circuits qui ressemblent un peu à quelque chose :

(sachant que je câblage est exactement le même que pour la version breadboard, on a juste :arrow_up: upgrade la version pour passer à celle définitive sur plaquettes à pastilles soudées)

Photos (faces avant et arrière) de la carte de contrôle

Cette carte permettra de contrôler le ruban de 10 LED RGB et d'alimenter le système via le port micro-USB de la NANO

Photos (faces avant et arrière) de la carte boutons/LED

Cette carte sera branchée sur la carte de contrôle via des câbles dupont soudés sur les pins de la carte

Schéma fait sur fritzing des deux cartes (ça sert franchement à rien, mais c'est toujours ça de gagné...)

La finition, et le résultat :


Une fois le boîtier dessiné, monté, collé, que le code fût fini, et que les soudures furent sécurisées à la colle chaude, voici une photo du projet enfin terminé.

Je l'utilise maintenant presque à chaque fois que je me lave les dents :wink:

Photo du projet enfin terminé !

Les améliorations futurs :


Je pense intégrer bientôt une fonction pour savoir quand ouvrir la fenêtre de la salle de bain quand il y a trop d'humidité dans l'air (après un bain, par exemple) - utilisation probable d'un DHT11. J'ai déjà commencé à prévoir cette option en ajoutant un connecteur mâle à trois broches sur le PCB, qu'il faudra ensuite souder aux pins sous la carte.

Si vous avez bien regardé la photo de l'arrière du boîtier, vous avez certainement remarqué la petite grille découpée dans le carton. Elle est là pour faire jolie pour l'instant, mais elle servira plus tard à laisser passer l'air vers le capteur, qui sera quand à lui fixé derrière.

Voili voilou, très heureux d'avoir enfin fini ce projet, le petit panda roux va aller dormir :wink:
Bonnes bidouilles à tous !

Cordialement
Rémi - R-P7 :fox_face:
GO code !

2 Likes

Un grand bravo ! Je comprend que ça soit une grande satisfaction de finir un projet et de pouvoir se retourner et constater le chemin parcouru...
:+1:

Bonjour @ProfesseurMephisto et merci !
(c'est sur que là, le chemin parcouru fut bien long :wink:)

Bonne journée
Cordialement
Pandaroux007

Bravo ! C'est un long projet qui a demandé beaucoup de temps et qui va t'en faire gagner pas mal !

Je te conseille d'utiliser Fusion 360 pour la modélisation 3D, c'est vraiment incroyable et il existe une version d'essai de durée illimitée gratuite (il y a aussi une version pro avec un peu plus d'options mais inutile pour les amateurs)

Tu es sûre, il me semble que les dernières versions gratuites, sont des version d'essais limitées dans le temps.
Sketchup(Free) lui est gratuit, mais uniquement en version WEB, je crois.

Moi j'utilise un modeleur paramétrique complétement gratuit et open source, FreeCAD, beaucoup moins intuitif que les deux précédents, mais on s'y fait à la longue :slight_smile:

Merci pour le partage de ton projet @pandaroux007

Ce qui fait 3 minutes 30, ce qui est bien.
Pourquoi 3 minutes de brossage au minimum ?
Parce que 3 minutes est la durée de survie d'une bactérie dans le dentifrice. Et peu de gens le savent.
Bravo pour ce projet.

Félicitations pour ce projet!

Effectivement, je ne savais pas! Je fais la guerre à mes enfants pour ça mais j’avais la croyance que statistiquement c’est le temps qu’il fallait pour être sûr d’avoir brossé la totalité de la surface des dents. Comme quoi

Bonjour @techvij et merci beaucoup !

Merci du conseil, malheureusement ce logiciel n'est pas (encore) disponible pour Linux, je ne peux donc pas l'utiliser...
En revanche j'ai le même que @terwal

Même si je ne l'ai encore jamais utilisé pour créer une pièce, et que je ne sais pas l'utiliser (pas encore !) - j'utilise TinkerCAD pour l'instant...

Pareillement - Merci pour l'info @hbachetti :wink:

Merci !

Bonne fin de journée
Cordialement
Un petit panda roux très fier d'avoir fini ce truc :wink::fox_face:

Bravo Rémi :wink:
Très belle réalisation.
Tu peux être fier de toi.

Bonne soirée :wink:

Bonsoir @philippe86220
Merci !

C'était plus un défi que quelque chose de vraiment utile :wink:

Bonne fin de journée
Amitiés
Pandaroux007

Peu importe, tu progresses et c’est l’essentiel :wink:

Vrai ! C'est comme ça qu'on apprend, en créant !
(ma devise depuis... 5 minutes :innocent: : GO code !)

On en remet une petite couche ... d'émail :wink:
Après un repas, surtout s'il comporte des aliments acides, légumes, fruits, l'émail des dents se ramollit. Il ne faut pas brosser immédiatement, sinon le dentifrice, qui est abrasif, provoque une usure importante de l'émail.
Deuxièmement, une phase de re-minéralisation a lieu après un repas, et si l'on brosse immédiatement, cette re-minéralisation est stoppée.

Cela fait deux raisons donc pour ne pas se brosser les dents juste après un repas. On attend une heure.
Bien entendu, plus on est jeune, plus cette précaution a du sens.

Ces informations proviennent d'une de mes dentistes qui avait assisté à une conférence sur le sujet, alors qu'elle réparait souvent les angles des dents d'une collègue, présente également à cette conférence.
La cause : je jus d'orange, fortement acide, en fin de petit déjeuner, suivi d'un brossage pratiquement immédiat. Une vraie catastrophe ...

Et ça a un goût bizarre aussi si l'on se brosse les dents juste après avoir mangé !

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.