Lorsqu’on est amateur de voie très étroite, et qu’on se régale à faire circuler des matériels pittoresques sur des voies herbeuses, à demi enterrées et savamment délabrées, on est assez vite confronté au problème de la mauvaise captation.
On peut certes multiplier les prises de courant, ou digitaliser le matériel en ajoutant un condensateur de maintien, mais la circulation au ralenti reste un problème permanent.
D’où l’idée d’alimenter les engins sur batterie, en commençant par ce petit véhicule articulé bien connu des HOe-istes: l’automotrice Egger-Bahn 1010.
Pour la commande, il est fait appel à un module de la famille des ESP8266, l’ESP12s, qui assure les communications en WiFi et gère aussi le fonctionnement de l’engin, à la façon d’un décodeur digital.
Schéma de principe :
Le montage (cliquez sur l’image pour l’agrandir) est fait autour du module ESP12s.
La tension de la batterie est appliquée d’une part à un régulateur LDO pour alimenter l’ESP12s en 3,3V, et d’autre part à un convertisseur élévateur (step-up) pour alimenter le moteur en 10V.
Un « pont en H » DRV8833 permet d’appliquer au moteur une tension hachée (vitesse) et de polarité (sens) ad-hoc.
Notes :
• les différentes LEDs sont connectées directement à la batterie pour soulager le régulateur 3,3V.
• le moteur est alimenté en seulement 10V à cause de la limitation du pont DRV8833. Mais ce pont a été choisi pour sa petite taille, et 10V suffisent pour assurer un fonctionnement réaliste.
• la batterie est rechargée par un chargeur externe.
Réalisation :
L’ESP12s est monté sur un petit circuit imprimé dessiné spécialement. Toutes les résistances sont au dos.
Le pont en H DRV8833 est connecté dans le prolongement du circuit.
Comme le circuit se loge dans le toit, deux LEDs blanches font office de plafonniers.
Notes :
• le circuit a été dessiné à l’origine pour un régulateur HT7333, mais suite à des problèmes avec ce composant, il a été remplacé par un TC1262, dont malheureusement l’empreinte est différente.
• il y a six points de connexion (à gauche de l’ESP) qui permettent de charger le programme au départ, et de le recharger en cas de problème avec l’OTA.
Pour dégager de la place pour l’aménagement intérieur, l’électronique est logée sous le toit. Malgré tout il n’a pas été possible d’ajouter un avertisseur. Nous en cherchons un tout petit.
Le convertisseur « step-up » est intégré dans le plancher :
Vue de l’intérieur :
L’aménagement intérieur est réduit à quelques bustes de voyageurs.
Une fois peints, les composants qui dépassent sous le châssis évoquent assez bien les réservoirs et autres organes qu’on trouve là en réalité :
Au final (cliquez sur les images)…
Partie logicielle :
On trouve sur internet de nombreuses pages exposant de façon détaillée la mise en oeuvre des ESP8266, aussi nous n’y reviendrons pas.
La plupart de ces pages décrivent comment implémenter un serveur HTML dans le module, qui peut alors être commandé depuis un ordiphone, une tablette ou un ordinateur par le biais d’un simple navigateur.
Nous avons procédé différemment, en utilisant plutôt le protocole UDP, qui est très simple et très rapide, mais nécessite une « appli » dans l’ordiphone. Mais cette application existe: c’est l’appli ROCO Z21. Le protocole en est décrit sur le net, et l’utilisation que nous en faisons est extrêmement simplifiée: seuls quelques messages sont traités, et nombre de paramètres sont limités à un seul octet.
Note : en l’état, ce programme n’analyse pas l’adresse DCC contenue dans le paquet, pas plus qu’il ne vérifie la checksum figurant en fin de message.
Commande de plusieurs engins :
Comme d’autres réalisations de ce type sont en projet, on peut se demander comment contrôler simultanément plusieurs engins moteurs.
Cela peut se faire assez simplement un réalisant un « re-routeur » de paquets UDP.
Les paquets émis par l’application Z21 ont des paramètres fixes: adresse IP et port du destinataire. Mais ils contiennent l’adresse DCC de l’engin à commander.
Le fonctionnement du re-routeur consiste à récupérer tous les paquets (il est vu comme une centrale par les ordiphones), en extraire l’adresse DCC, et renvoyer chaque paquet à l’adresse IP de l’engin.
Par exemple, et pour simplifier nos tests, les paquets contenant l’adresse DCC X sont réacheminés à l’adresse IP 192.168.1.X.
En pratique, le re-routeur a été réalisé par programme à l’intérieur du PC qui gère le réseau, et également à titre de test par un ESP8266 indépendant.
Intégration à un système existant :
On peut mélanger les engins WiFi et les engins DCC. Pour cela il suffit que le re-routeur transmette directement à la centrale DCC les paquets qui ne sont pas destinés à un engin WiFi.
Améliorations possibles :
Cette réalisation était un prototype. On pourrait lui ajouter une mesure de la tension de la batterie, un avertisseur sonore, etc.
Remerciements :
• à JC pour sa réalisation Car-System,
• à mon ami Marc, pour m’avoir procuré une caisse martyre,
• aux membres de l’excellent forum Locoduino, qui m’ont donné des tuyaux intéressants.
// ================================================
// commande de l’automotrice Egger-Bahn
// version 5, adresse MAC d’origine, IP 70
// accélérations et ralentis progressifs, mode manoeuvre
// ================================================
// https://www.tala-informatique.fr/wiki/index.php/Esp8266_udp_server
// https://projetsdiy.fr/attribuer-ip-fixe-projet-arduino-esp32-esp8266-esp01/
#include <ESP8266WiFi.h>
#include <WiFiUDP.h>
#include <ArduinoOTA.h> // pour OTA
// informations remontées au PC pour affichage
#define sp Serial.print
#define spl Serial.println
const byte Moteur1 = 5;
const byte Moteur2 = 4;
const byte G0 = 16;
const byte G1 = 2; // LED interne pour test
const byte G2 = 0;
const byte G3 = 13;
const byte G4 = 14;
const byte EEP = 12;
const byte LED = 2; // LED interne
char NbEssais = 10; // 10 essais à 500ms de connexion au réseau domestique
bool EtatLed = false;
unsigned long previousMillis = 0;
const long interval = 200;
const char Increment = 2, Decrement = 2;
char sens, sensinst; int Vinst, Vobj, PWM;
bool AccRal = false; // démarrage sans inertie
// pour récupération des paquets UDP
const uint16_t PORT = 21105; // Port d’écoute UDP Z21
const uint16_t BUFFER_SIZE = 512; // Taille du tampon de réception
char buffer[BUFFER_SIZE]; // Tampon de réception
uint16_t len = 0;// Taille du paquet reçu;
// réponse quelconque (loco 3, checksum incluse) juste pour confirmer la liaison
const char reponse[15] = {0x0F,0x00, 0x40,0x00, 0xEF,0x00, 0x03,0x00, 0x03, 0x00,0x00,0x00,0x00,0x00, 0xA0};
// instanciation du serveur UDP
WiFiUDP udp;
// ==================================================================================================== SETUP
void setup() {
char I;
pinMode (Moteur1, OUTPUT);
pinMode (Moteur2, OUTPUT);
pinMode (G0, OUTPUT);
pinMode (G1, OUTPUT);
pinMode (G2, OUTPUT);
pinMode (G3, OUTPUT);
pinMode (G4, OUTPUT);
pinMode (EEP, OUTPUT);
digitalWrite(G0, HIGH); // Leds connectées au plus
digitalWrite(G1, HIGH); // Leds connectées au plus
digitalWrite(G2, HIGH); // Leds connectées au plus
digitalWrite(G3, LOW); // sortie auxiliaire au moins
digitalWrite(G4, HIGH); // Leds connectées au plus
digitalWrite(EEP, LOW); // ENABLE
digitalWrite (LED, HIGH); // Led interne connectée au plus
Serial.begin(115200); // on démarre le port série
delay(10); // On attend « un peu » que le buffer soit prêt
// mode de fonctionnement: point d’accès ou station:
setup_sta(); // essai en mode station sur réseau domestique
if (NbEssais == 0) {setup_ap();} // sinon mode point d’accès
// Démarrage de l’écoute
udp.begin(PORT);
sp(« Démarrage de l’écoute sur le port « ); spl(PORT);
for (I=1; I<=3; I++){digitalWrite(LED,LOW); delay(200); digitalWrite(LED,HIGH); delay(200);} // clign
}
// ============================================================================================ fin de setup
void setup_sta(){ // ============================================= SETUP EN MODE STATION (connexion à la box)
const char sta_ssid[] = « SSID_box »;
const char sta_password[] = « mot_de_passe »;
//const char sta_address[12] = « 192,168,1,70 »; // adresse demandée
const uint8_t mac[6] = {0x2C, 0x3A, 0xE8, 0x26, 0xA3, 0x54}; // « 2c:3a:e8:26:a3:54 »
//WiFi.disconnect(true); // On efface la configuration précédente MAIS ÇA EMPÊCHE DE MODIFIER l’ADRESSE MAC
// changement de l’adresse MAC cf: https://circuits4you.com/2017/12/31/how-to-change-esp8266-mac-address/
// pas de changement si on opère plusieurs engins
spl(« »);
//sp(« OLD ESP8266 MAC: « );
//spl(WiFi.macAddress()); // This will read MAC Address of ESP
//wifi_set_macaddr(0, const_cast<uint8*>(mac)); //changement MAC address pour Livebox
sp(« final ESP Board MAC Address: « ); spl(WiFi.macAddress());
// demande d’une adresse IP précise
WiFi.config({192,168,1,70},{192,168,1,1},{255,255,255,0},{192,168,1,1}); // IP, gateway, subnet, DNS
// Initialisation de la connection WiFi
WiFi.begin(sta_ssid, sta_password);
spl(« attente de la connexion »);
// attente de connexion
while (WiFi.status() != WL_CONNECTED && NbEssais >0 ){ delay(500); NbEssais–;}
spl(NbEssais,DEC); // test
if(WiFi.status()==WL_CONNECTED){
// pour OTA
ArduinoOTA.setHostname(« monEsp »); // on donne une petit nom a notre module
ArduinoOTA.begin(); // initialisation de l’OTA
// Affichage des informations
sp(« Connecté à « ); sp(sta_ssid); // <<<<<<<<<< prendre la vraie valeur
sp( » avec l’ip « ); spl(WiFi.localIP());
}
}
void setup_ap(){ // ============================================================= SETUP EN MODE POINT D’ACCÈS
//const char *ap_ssid = « JPM-8266 »;
//const char *ap_password = « 12345678 »;
const char ap_ssid[] = « JPM-8266 »;
const char ap_password[] = « 12345678 »;
spl(« »);
sp(« Setting soft-AP configuration … « );
//spl(WiFi.softAPConfig(local_IP, gateway, subnet) ? « Ready » : « Failed! »);
spl(WiFi.softAPConfig({192,168,1,70},{192,168,1,50},{255,255,255,0}) ? « Ready » : « Failed! »);
sp(« Starting soft-AP … « );
spl(WiFi.softAP(ap_ssid, ap_password) ? « Ready » : « Failed! »);
sp(« Soft-AP SSID = « ); spl(ap_ssid); // <<<<<<<<<< prendre la vraie valeur
sp(« Soft-AP IP address = « ); spl(WiFi.softAPIP());
}
// ===================================================================================================== LOOP
void loop() {
unsigned long currentMillis = millis();
ArduinoOTA.handle(); // gestion OTA
if (udp.parsePacket() > 0) { readPacket(); } // réception des paquets
if (currentMillis – previousMillis >= interval) { // toutes les interval ms
previousMillis = currentMillis;
if (AccRal) { // gestion des changements de vitesse
if (Vinst != Vobj) { // si vitesse de croisière, rien à faire
if (Vobj > Vinst) { // accélérer
Vinst = Vinst + Increment;
if (Vinst > Vobj) { Vinst = Vobj;}
}
else { // ralentir
Vinst = Vinst – Decrement;
if (Vinst < Vobj) { Vinst = Vobj;}
}
ExecVitesse();
}
} // end AccRal
} // end if
} // end loop
// ==================================================================== récupération et affichage d’un paquet
void readPacket() {
// Mise en tampon du paquet
len = udp.available();
udp.read(buffer, len);
TraitementMessage(); // traitement du paquet
}
// ==================================================================================== envoi d’un paquet UDP
void sendPacket(const char content[], IPAddress ip, uint16_t port) {
udp.beginPacket(ip, port);
udp.write(content);
udp.endPacket();
}
// =================================================================================== traitement des messages
void TraitementMessage(){ // décodage des messages Z21
char GPIOx ;
bool Commande ; // commande des fonctions
// ———————————————- message F1 0A = get firmware version (initial)
// send firmware version p.ex.: F3.0A.01.23.DB
// ———————————- message 0x00 = mode commande des locos, renvoyer loco info
if(buffer[4]==0x00) {
spl(« passage en mode commande des locos, réponse: send loco info… »);
sendPacket( reponse, udp.remoteIP(), udp.remotePort() );
}
// —————————————————– message 21 24 = get status (périodique)
if (buffer[4]==0x21 && buffer[5]==0x24 ) {
//spl( » get status (périodique) »);
}
// ————————————————– message E3 F0 = get loco info (périodique)
if (buffer[4]==0xE3 && buffer[5]==0xF0 ) {
// spl( » get loco info (périodique). Répondre… »);
}
// —————————————— message E4 13 = commande de vitesse 128 pas loco X
if (buffer[4]==0xE4 && buffer[5]==0x13 ) {
sp( » commande de vitesse 128 pas loco « ); sp(buffer[7], DEC);
Vobj = (buffer[8] & 127); sp( » Vobj: « ); sp(Vobj);
sens = (buffer[8] & 128);
if (sensinst != sens) {Vinst = 0; Vobj = 0; ExecVitesse();} //arrêt
if (sens==0) { spl( » sens 0″); }
else { spl( » sens 1″); }
sensinst = sens;
analogWriteFreq(100); // PWM à 100Hz à 40000Hz
if (!AccRal) { Vinst = Vobj; ExecVitesse();}
}
// ——————————————————– message 00 80 = set track power off
if (buffer[3]==0x00 && buffer[4]==0x80 ) {
sp( » set track power OFF »);
Vinst = 0; Vobj = 0; ExecVitesse(); // arrêt instantané
}
// ———————————————————- message 21 81 = set track power on
if (buffer[4]==0x21 && buffer[5]==0x81 ) {
Serial.println( » set track power ON »); // pas d’action
}
// ——————————————— message E4 F8 = commande des fonctions loco X
if (buffer[4]==0xE4 && buffer[5]==0xF8 ) {
sp(« loco « ); sp(buffer[7], DEC);
sp( » fonction « ); sp(buffer[8] & 31);
Commande = ((buffer[8] & 192)!=0);
if(Commande) {spl( » ON »);}
else{spl( » OFF »);}
// commande des ports GPIO 14, 16, 13, 13, 2 par les fonctions 0,1,2,3,4
// filtrage des autres fonctions
switch (buffer[8] & 31) { // fonction DCC
case 0: { GPIOx = 14; // fanaux AV et AR
if (Commande) { digitalWrite(GPIOx, LOW);} // connectés au plus
else { digitalWrite(GPIOx, HIGH);}
}
break;
case 1: { GPIOx = 16; // éclairage intérieur
if (Commande) { digitalWrite(GPIOx, LOW);} // connecté au plus
else { digitalWrite(GPIOx, HIGH);}
}
break;
case 2: { GPIOx = 13; // klaxon 1
if (Commande) { digitalWrite(GPIOx, HIGH);} // le klaxon est connecté au moins
else { digitalWrite(GPIOx, LOW);}
}
break;
case 3: { GPIOx = 13; // klaxon 2 commandé en PWM pour essai
if (Commande) { analogWrite(GPIOx, 80); } // le klaxon est connecté au moins
else { digitalWrite(GPIOx, LOW);}
}
break;
case 4: { GPIOx = 2; // LED du module pour tests
if (Commande) { digitalWrite(GPIOx, LOW);} // connectée au plus
else { digitalWrite(GPIOx, HIGH);}
}
break;
case 6: { // mode avec/sans changement de vitesse progressif
if (Commande) { AccRal = true;}
else { AccRal = false; Vinst = Vobj; ExecVitesse();}
}
break;
default: { // futures commandes de réglage
sp(« fonction « ); sp(buffer[8] & 31); spl( » non traitée »);
}
break;
} // end switch
} // fin de traitement des fonctions
} // fin de traitement des messages
// ================================================================== commande effective de la vitesse
void ExecVitesse() { // prise de la vitesse instantanée Vinst
PWM = map(Vinst,0,127,0,1023);
sp(« Vobj: « ); sp(Vobj,DEC); sp( » PWM: « ); spl(PWM,DEC);
// commande de la vitesse par enable (EEP) sur GPIO 12
if (sens==0) { digitalWrite(Moteur1, HIGH); digitalWrite(Moteur2, LOW); }
else { digitalWrite(Moteur1, LOW); digitalWrite(Moteur2, HIGH); }
analogWrite(EEP,PWM);
}
// ================================================================================== fin du programme
0 commentaires