Girouette électronique - ESP32 - serveur WiFi -code gray
Girouette basée sur une roue code gray imprimée 3D. Comprenant un serveur WiFi elle est destinée à ma station météo à ESP32.
1 en vidéo :
|
L'angle est capté par 4 phototransistors qui attaquent directement un ESP32. Ce dernier programmé en tant que serveur WiFi, décode cette information et la transmet par WiFi à tout client qui se connecte.
C'est le cas ici ; le petit afficheur LCD 3.5" est piloté par un autre ESP32 programmé en tant que client WiFi, et affiche l'angle en temps (presque) réel (2 connexions par secondes, paramétrable...)
Nous allons maintenant démonter tout ça et étudier chaque pièce en détail.
|
|
2 Vue d'ensemble
|
L'électronique est protégée de la pluie par un "gobelet doseur" de 160ml fournis avec une lessive liquide dont je ne me rappelle plus la marque.
|
|
3 Détail de la partie tournante
.
|
|
L'axe est un arbre rectifié de 3mm récupéré sur un ancien lecteur de disquettes 3"1/2. Les deux éléments de dominos d'électricien sont soudés sur un petit morceau de circuit imprimé en "L". La tige horizontale est un jonc de carbone. L’empennage est une carte d'alu teinté, très fine (0.2mm) au format carte de visite, découpée en biais. L'étanchéité est assurée par un petit cône en plastique qui tourne avec l'axe et englobe (sans le toucher) l'extrémité d'un tube fixé au gobelet.
|
4 La queue en alu léger
|
Je vous montre d'où provient la plaque en alu, moi je ne vends rien. Ces plaques très fines (0.2mm) sont destinées à être gravées au laser. J'ai fixé la plaque par des petites agrafes utilisées en couture 😉️
|
|
5 Le petit tube de sortie de l'axe
C'est un tube en laiton (diam 5.5mm ext, 3.5mm int) soudé sur une rondelle métallique elle même collée sur le fond du gobelet préalablement percé.
|
|
|
6 Le mécanisme
|
|
La roue codeuse est enfermée dans un boîtier en PLA noir imprimé en 3D.
|
7 Le boitier
|
|
|
8 La roue codeuse et le capteur à phototransisors
|
|
La plaquette de circuit imprimé sur laquelle sont soudés les phototransistors en boîtier smd 0603 est fixée à la partie plastique par deux vis de 2mm. Les trous de fixation sont oblongs ce qui permet de positionner les phototransistors exactement à l'aplomb des pistes perforées de la roue.
Comme on peut le voir, l'axe principal est monté sur roulements à billes.
|
9 Positionnement précis des phototransistors
Il faut pour cela desserrer très légèrement les deux écrous de M2 qui fixent la plaquette.
|
Côté ESP32, j'ai soudé des picots sur le bord de la platine afin de pouvoir connecter un oscilloscope à tour de rôle sur chaque capteur.
|
|
10 Test à l'oscilloscope
Pour desserrer les écrous de 2 facilement (les doigts c'est un peu trop gros, une pince plate c'est pas pratique) on peut utiliser la tête d'une vis BTR.
|
Il ne reste plus qu'à régler le positionnement de la platine pour obtenir des niveaux de tension bien francs vus à l'oscilloscope, lorsqu'on fait tourner manuellement le disque, et ce pour chaque capteur. On lit ici une amplitude de 3V. C'est normal, les phototransistors sont alimentés par les R de pull-up internes à l'ESP32 qui fonctionne, lui, sous 3V3. Voir schéma plus bas. Le +5V présent sur le pin "Vin" n'est utilisé (en ce qui nous concerne) que pour alimenter les LEDS d'illumination (au travers de résistances, voir schéma).
|
|
11 Test par liaison série USB
Une fois que les signaux vus à l'oscilloscope (ou mesurés avec un multimètre, si vous préférez) sont corrects, il reste à vérifier leur bonne interprétation logique. Pour cela j'ai ajouté quelques lignes au programme (voir les "Serial.println()") qui sortent l'information par la liaison série USB. Voici ci-contre ce qu'il faut obtenir (dans un moniteur série comme par exemple celui inclus dans l'IDE Arduino) lorsqu'on fait tourner lentement la girouette dans le sens horaire. Au passage on vérifie qu'il n'y a qu'un bit qui change à la fois conformément au code gray du disque. Pour connaître l'angle réel, il faut multiplier par 22.5° (=360°/16).
Je dois ici préciser que l'informaion angulaire étant également transmise par WiFi, il n'est nullement obligatoire de connecter l'ESP par le port USB, il suffit en fait de l'alimenter, par exemple par une tension de 5V appliquée entre un pin "GND" et le pin "Vin". Deux fils donc, ne véhiculant aucune information. La carte "ESP32 dev module 30 pins" comporte en outre un régulateur 3V3 pour alimenter la puce ESP32.
|
|
|
12 Les phototransistors
|
|
Vous avez vu la taille de ces bestioles ? 0,8mm ! Autant dire que la soudure est légèrement délicate à réaliser. J'ai gravé la plaquette directement avec une CNC.
Je vous fournis d'ailleurs le g-code qui va bien.
|
13 Taille du phototransistor PT1921C
|
|
Le voici vu au microscope, posé sur une pièce de 1€, puis sur l'ongle de l'annulaire de ma main gauche.
Il faut en souder quatre, plus quatre LED de la même taille plus quatre résistances d'alim pour les led. Et le tout bien aligné, bien espacé, au poil quoi.
|
14 Vue au microscope des P1921 soudés
Une machine peut faire mieux, c'est sûr.
|
|
|
15 La roue codeuse code gray
|
|
J'ai imprimé la roue codeuse en PLA noir avec une imprimante 3D (une Creality Ender-3 Pro)
Pourquoi recourir au code gray plutôt qu'a un code binaire "ordinaire" ?
Parce que dans le cas du code gray, lors de la rotation, il n'y a jamais plusieurs bits qui changent d'état simultanément contrairement au code binaire (ex: passage de 01 à 10 ou pire de 0000 à 1111).
l'avantage ? Pour le binaire, le passage de l'état 01 à 10 peut se faire de deux façons :
01 -> 11 -> 10
ou bien :
01 -> 00 -> 10
suivant que c'est le premier ou le second bit le plus prompt à réagir (le "en même temps" est illusoire si on regarde à l'échelle de la nanoseconde)
et pour 4 bits, il y a 4! (factorielle 4) façons de passer de 1111 à 0000 soit 24 états intermédiaires différents possibles (permutations de l'ordre dans lequel les 4 bits commutent), ce qui constitue un aléas total.
Je me rappelle avoir trouvé la cause du déclenchement intempestif d'un système d'alarme (du commerce), due à ce type d'aléa (la transmission de la tension de la pile des détecteurs IR se faisait par radio, sur le même canal logique que celui servant à déclencher l'alarme. Et cette dernière se déclenchait donc de façon apparemment aléatoire lorsque l'information binaire codant la tension passait par un état imprévu mais correspondant à l'alarme. Je vous laisse compter les failles de conception de ce système ! Rassurez-vous, dans le domaine aéronautique la chose est connue et bien maîtrisée!)
Oui mais alors les ordinateurs qui utilisent le code binaire sont plein d'aléas ? En fait non, parce qu'en logique séquentielle synchrone on utilise un chef d'orchestre (une horloge à quartz) qui cadence les circuits de façon à ce que les états logiques soient pris en compte lorsque les niveaux électriques sont bien établis (sur les paliers et pas lors des commutations). Mais dans le cas qui nous occupe, il s'agit de logique combinatoire, sans horloge, les niveaux changent à des instants indéterminés, inconnus (à l'avance) du microcontrôleur. D'où la nécessité d'utiliser ce code gray.
Voici les 24 façons de passer de 1111 à 0000 :
Appelons ABCD les 4 bits représentant ce nombre; Il peuvent basculer dans cet ordre A puis B puis c puis D... mais aussi A puis C puis...
Voici les combinaisons possibles :
ABCD, ABDC, ACBD, ACDB, ADBC, ADCB,
BACD, BADC, BCAD, BCDA, BDAC, BDCA,
CABD, CADB, CBAD, CBDA, CDAB, CDBA,
DABC, DACB, DBAC, DBCA, DCAB, DCBA,
soit 24 possibilités différentes passant par des états qui n'ont pas grand chose à voir avec la valeur finale !
Je vous fournis le fichier stl et même les sources pour FreeCAD qui vous permettent d'imprimer cette roue codeuse.
|
16 Dimensions du disque
|
|
Sur cette image le quadrillage est millimétrique.
- Le trait bleu c'est le bord du disque, on voit qu'il est à 13mm du centre. Son diamètre fait donc 26mm.
- Les encoches font 1mm de large et 1mm de matière les séparent. Leurs entraxes sont donc de 2mm, c'est ce qu'il faut retenir pour la disposition des phototransistors.
- Le trou central fait 3mm de diamètre
- La première encoche débute à 5mm du centre de rotation, et donc à 3,5mm du bord du trou.
J'ai utilisé une grille en mm, j'aurais pu prendre une grille en 20eme de pouces pour simplifier le placement des phototransistors avec un logiciel de CAO comme Kicad. Toutefois j'ai gravé le minuscule circuit imprimé supportant les phototransistors directement avec ma CNC (Alfawise C10 Pro) et une fraise en V (voir image ci-dessus).
|
17 Gravure du cuivre à la CNC
|
Il faudra que je vous présente l'application que j'ai programmée sous Qt5 pour générer un g-code pour graver des formes simples, traits, rectangles, cercles, facilement, sans utiliser Inkscape (dessin vectoriel, conversion en chemins, export g-code... avec plus ou moins de bonheur...)
|
|
18 Les leds d'illumination sous la roue codeuse
Ces led, ainsi que leurs résistances d'alimentation, sont elles aussi en boîtiers smd 0603.
On voit au passage que l'axe principal est monté sur roulements à billes.
|
|
|
19 La conception de la roue codeuse
|
|
J'ai dessiné la pièce avec le logiciel FreeCAD sous linux.
|
20 Dessin du boitier
Lui aussi réalisé avec FreeCAD, de même que les autres pièces, supports etc... Je vous fournis tous les fichier stl.
|
|
|
21 Le schéma
|
|
|
22 Le code source de la girouette en C++
|
|
|
23 Le code source du client WiFi (pour test affichage)
CODE SOURCE en C++
/* Horloge_TFT () + client girouette pour ESP32 Wroom + afficheur 3.5 TFT 480x320 par Silicium628 */ /*===================================================================================================== CONCERNANT L'AFFICHAGE TFT: connexion: (Pensez à configurer le fichier User_Setup.h de la bibliothèque ~/Arduino/libraries/TFT_eSPI/ ) les lignes qui suivent ne sont qu'un commentaire pour vous indiquer la config à utiliser placée ici, elle ne sont pas fonctionnelles Il FAUT modifier le fichier User_Setup.h installé par le système Arduino dans ~/Arduino/libraries/TFT_eSPI/ // ESP32 pins used for the parallel interface TFT #define TFT_CS 27 // Chip select control pin #define TFT_DC 14 // Data Command control pin - must use a pin in the range 0-31 #define TFT_RST 26 // Reset pin #define TFT_WR 12 // Write strobe control pin - must use a pin in the range 0-31 #define TFT_RD 13 #define TFT_D0 16 // Must use pins in the range 0-31 for the data bus #define TFT_D1 4 // so a single register write sets/clears all bits #define TFT_D2 2 // 23 #define TFT_D3 22 #define TFT_D4 21 #define TFT_D5 15 // 19 #define TFT_D6 25 // 18 #define TFT_D7 17 =====================================================================================================*/ String version="1.0"; #include <stdint.h> #include <TFT_eSPI.h> // Hardware-specific library #include "Free_Fonts.h" TFT_eSPI TFT480 = TFT_eSPI(); // Configurer le fichier User_Setup.h de la bibliothèque TFT480_eSPI au préalable #include <WiFi.h> #include <HTTPClient.h> const char* ssid2 = "WIND_srv"; const char* password2 = "62z4exW58"; //IP address with URL path const char* srvName_ANGLE = "http://192.168.4.1/AGL"; //.4.2 ? const uint32_t connectTimeoutMs = 10000; uint8_t mode = 0; // 0 ou 1 String recp_HR = "{}"; String recp_ANGLE = "{}"; uint8_t WiFi_status=0; uint32_t memoMillis = 0; uint32_t currentMillis; uint16_t compte1=1; float angle1=0; #define NOIR 0x0000 #define MARRON 0x9240 #define ROUGE 0xF800 #define ROSE 0xFBDD #define ORANGE 0xFBC0 #define JAUNE 0xFFE0 #define JAUNE_PALE 0xF7F4 #define VERT 0x07E0 #define VERT_FONCE 0x02E2 #define OLIVE 0x05A3 #define CYAN 0x07FF #define BLEU_CLAIR 0x455F #define AZUR 0x1BF9 #define BLEU 0x001F #define MAGENTA 0xF81F #define VIOLET1 0x781A #define VIOLET2 0xECBE #define GRIS_TRES_CLAIR 0xDEFB #define GRIS_CLAIR 0xA534 #define GRIS 0x8410 #define GRIS_FONCE 0x5ACB #define GRIS_TRES_FONCE 0x2124 #define BLANC 0xFFFF static void smartdelay(unsigned long ms) { unsigned long start = millis(); while (millis() - start < ms) {;} } void httpGetAngle() { // Serial.println("envoi req HR"); TFT480.fillCircle(470, 20, 5,BLEU ); delay(200); HTTPClient http2; http2.begin(srvName_ANGLE); int httpResponseCode = http2.GET(); if (httpResponseCode>0) { recp_ANGLE = http2.getString(); TFT480.fillCircle(470, 20, 5,VERT ); } http2.end(); } void affi_pointe(uint16_t x0, uint16_t y0, uint16_t r, double angle_i, float taille, uint16_t couleur_i) { // trace une pointe de flèche sur un cercle de rayon r // angle_i en degrés décimaux - sens trigo float angle =angle_i/57.3; // (57.3 ~ 180/pi) int16_t x1, x2, x3; int16_t y1, y2, y3; x1=x0+r* cos(angle); // pointe y1=y0-r* sin(angle); // pointe x2=x0+(r-7)* cos(angle-taille); // base A y2=y0-(r-7)* sin(angle-taille); // base A x3=x0+(r-7)* cos(angle+taille); // base B y3=y0-(r-7)* sin(angle+taille); // base B TFT480.fillTriangle(x1, y1, x2, y2, x3, y3, couleur_i); } void affi_angle() { String s1, s2; //s1=""; //if(angle_i<10){s1+="0";} //s1 += String(angle_i); s1 = String(angle1); s2= recp_ANGLE; TFT480.setTextColor(NOIR, BLANC); TFT480.setFreeFont(FF5); TFT480.setTextSize(1); TFT480.drawString(" ANGLE > ",0,90); TFT480.fillRect(120, 90, 90, 14, NOIR); // efface TFT480.setTextColor(VERT); TFT480.drawString(s1, 120, 90); } void affi_boussole(int x0, int y0, int dx, int dy, float angle_i) { int a1; TFT480.drawRect(x0, y0, dx, dy, BLANC); TFT480.fillRect(x0+1, y0+1, dx-2, dy-2, NOIR); // efface TFT480.setFreeFont(FF0); TFT480.setTextSize(1); TFT480.drawString("N", x0+35, y0+5); TFT480.setTextColor(BLANC); TFT480.setFreeFont(FF5); TFT480.setTextSize(1); TFT480.fillRect(x0+15, y0+1, 50, 53, NOIR); // efface affi_pointe(x0+dx/2, y0+dy/2, (dy/2)-5, 90-angle_i, 2.5, CYAN); TFT480.drawCircle(x0+dx/2, y0+dy/2, (dy/2)-2, BLANC); TFT480.setTextColor(ROUGE); TFT480.drawString("N", -6+x0+dx/2, y0+5); TFT480.setTextColor(JAUNE); TFT480.drawString("W", x0+6, -8+y0+dy/2); TFT480.setTextColor(JAUNE); TFT480.drawString("E", x0+dx-14, -8+y0+dy/2); TFT480.setTextColor(BLEU); TFT480.drawString("S", -6+x0+dx/2, y0+dy-20); TFT480.setFreeFont(FF0); TFT480.setTextSize(1); TFT480.setTextColor(BLANC); a1=(int)angle_i; String s1 =String(a1); TFT480.fillRect(x0+60, y0+5, 25, 8, NOIR); // efface TFT480.drawString(s1, x0+65, y0+5); } void connexion_serveur_ANGLE() { //TFT480.fillScreen(NOIR); TFT480.fillRect(0, 0, 480, 75, GRIS_TRES_FONCE); // efface TFT480.setCursor(0, 20, 4); TFT480.setFreeFont(FF5); TFT480.setTextSize(1); TFT480.setTextColor(JAUNE, NOIR); TFT480.print("Connexion au serveur ANGLE :"); WiFi.persistent(false); WiFi.begin(ssid2, password2); delay(100); TFT480.setTextColor(BLANC, NOIR); TFT480.setCursor(0, 40, 1); while(WiFi.status() != WL_CONNECTED) { delay(100); TFT480.print("."); } TFT480.setCursor(0, 60, 1); TFT480.print(WiFi.localIP()); } void affi_accueil() { TFT480.fillScreen(NOIR); TFT480.setTextColor(BLANC, NOIR); TFT480.setFreeFont(FF1); uint16_t y=0; TFT480.drawString("TEST AFFI", 0, y); y+=20; String s1="version " + version; TFT480.drawString(s1, 0, y); y+=20; TFT480.setTextColor(JAUNE, NOIR); TFT480.drawString("Client WiFi", 0, y); y+=40; } void setup() { Serial.begin(115200); Serial.println("Setup"); TFT480.init(); TFT480.setRotation(3); // 0..3 à voir, suivant disposition de l'afficheur et sa disposition affi_accueil(); delay (2000); TFT480.fillScreen(NOIR); TFT480.setCursor(130, 0, 2); // Set "cursor" at top left corner of display (0,0) and select font 4 TFT480.setTextColor(TFT_BLUE, TFT_BLACK); TFT480.println("Client WiFi"); delay(500); TFT480.fillScreen(NOIR); } int angl_1 =0; void loop() { //if ((compte1 % 5)==0) // toutes les 5s //if(0) // POUR TEST { WiFi.disconnect(); connexion_serveur_ANGLE(); recp_ANGLE = "{}"; if(WiFi.status()== WL_CONNECTED ) { httpGetAngle(); } } Serial.println(recp_ANGLE); String s1=recp_ANGLE.substring(0, 6); Serial.println(s1); if(recp_ANGLE.substring(0, 6)=="angle=") { Serial.println("ok"); String s2=recp_ANGLE.substring(6, 8); Serial.println(s2); angl_1 = s2.toInt(); } angle1 = 22.5*angl_1; //angle1=22.5 * compte1; // POUR TEST affi_angle(); //affi_boussole(10, 10, 15*compte1, 10*compte1, angle1); // POUR TEST de la taille affi_boussole(10, 150, 150, 100, angle1); //delay(500); //angle1+=22.5; //if (angle1>359) {angle1=0;} compte1++; if (compte1>30) { compte1=1; TFT480.fillScreen(NOIR); } smartdelay(300); }
|
|
|
24 Documents
Vous trouverez ici tous les codes sources, les fichiers stl et bien plus encore...
|
|
|
|