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 ! ), 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 :
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 :
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é).
Le matériel utilisé ici est le suivant :
-
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 ?)
-
Trois boutons poussoir, une résistance de 220ohms avec une LED (jaune)
-
un ruban de 10 LEDs RGB type WS2812B
-
Un interrupteur 2 positions (ON/OFF)
-
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 !),
-
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...
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.
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) :
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... )
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.
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 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
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
Bonnes bidouilles à tous !
Cordialement
Rémi - R-P7
GO code !