Après la présentation des principes fondateurs des Satellites Locoduino La carte Satellite V1 (1), la réalisation matérielle La carte Satellite V1 (2), les logiciels La carte Satellite V1 (4) et l’interface SAM La carte Satellite V1 (3), quand il s’agit de mettre en service plusieurs satellites dans lesquels un même logiciel a été gravé mais qui reste à être personnalisé (on dit "configuré"), on est bien obligé d’avoir recours à un appareil spécial pour faire ce travail.
C’est l’objet de cet article.
Quand, de plus, les organes essentiels de notre réseau ferré sont, eux-même, connectés sur un réseau CAN, l’affaire devient bigrement intéressante, car cet "appareil spécial" peut servir à des quantités de tâches : configurer les satellites, bien-sûr, mais aussi les tester, tester la centrale en envoyant des ordres de conduite, tester le TCO en simulant des événements sur le réseau, etc...
On va d’abord, ici, se limiter à la configuration et aux tests des satellites, mais ceux qui voudront aller plus loin auront une base qu’ils pourront étendre. Il y aura probablement, d’autres réalisations de configurateurs, pour ceux qui souhaiteront utiliser différents organes d’interface utilisateur.
Cet exemple de configurateur est construit sur un UNO avec carte CAN, écran LCD et juste un encodeur quadratique pour sélectionner et exécuter les opérations. On ne peut pas faire plus simple ! Nous prendrons l’exemple du Locoduinodrome comme alibi.
Dans le projet Locoduinodrome, nous avions besoin de 8 satellites identiques comme celui-là : La carte Satellite V1 (3)
La carte Satellite V1
Pour rappel, ces satellites sont connectés sur un bus CAN, au plus près des appareils de voie qu’ils commandent ou surveillent et sont alimentés par une ligne leur délivrant 9V. Certains d’entre-eux alimentent les rails en DCC pour détecter la présence d’un convoi.
Nous avons donc un schéma de branchement avec, au centre, les 3 réseaux qui relient les satellites (9V, CAN et DCC) et, à la périphérie, les connexions particulières de chaque satellite :
L’alimentation DCC dont sera déduite l’occupation de voie.
Une ou deux détections ponctuelles (zones d’arrêt).
Les commandes des DEL d’un ou deux signaux.
Une ou zéro commande de servo d’aiguille.
Représentation du Locoduinodrome
Comme les logiciels des satellites sont tous les mêmes (à l’exception du numéro d’identification), il va falloir paramétrer chacun d’eux pour qu’il fasse ce qu’on lui demande. On a regroupé ici l’ensemble des logiciels des satellites
Ce tableau montre ce que chaque satellite a en charge :
Table des satellites
Id Satellite
Aiguille
Signaux
Détecteurs
DCC
0
C3
P3, P5
Z0
1
C4
P4, P6
Z1
2
a0
C5
Z2
3
S2, S3
P8, P9
Z3
4
S1, S4
P7, P10
Z6
5
C2
P2
Z4
6
a1
C5
Z5
7
C1
P1
Le configurateur doit donc être capable de tester et de modifier les paramètres de tous ces éléments, à savoir :
afficher les détections (zones et ponctuelles)
régler les butées des débattements des servos correspondant aux positions droite et déviée des aiguilles
régler la vitesse de rotation des servos pour un effet réaliste
régler la luminosité de chaque led
régler les temps d’allumage et d’extinction des leds (inertie)
régler les périodes des clignotements des leds
On a donc à faire un appareil dont l’interface utilisateur va tenir la plus grande place dans le logiciel.
J’ai choisi un UNO auquel j’ai adjoint les périphériques suivants :
Une interface CAN (ici c’est la carte Can Locoduino mais d’autres cartes Can équivalentes existent comme celle qui est montrée dans l’article La bibliothèque ACAN (1)).
Un écran LCD de 4 lignes de 20 caractères.
Un encodeur quadratique (avec son bouton qui comporte un contact interrupteur).
Un inverseur unipolaire pour le choix test ou configuration.
Le tout tient sur une petite plaquette de plexi avec des entretoises :
Vue de coté
A gauche le UNO recouvert par une plaque supportant les boutons et le plan du réseau tel que représenté plus haut (très pratique pour savoir ce que l’on fait), et à droite la carte CAN située sous l’écran LCD (sans oublier les pieds en feutre - une carte SD se glisse aussi sur le UNO sur cette photo, mais c’est seulement pour d’autres projets dont on ne parlera pas ici).
Un prototype de configurateur-testeur
Voici le schéma de l’engin (cliquer pour agrandir l’image) :
Le schéma de câblage
et une affectation des pattes des composants qui peut faciliter les connexions et les vérifications après câblage.
affectations de pattes
Et un petit réseau CAN constitué de 2 satellites et du configurateur-testeur :
Une séance de configuration et tests
Ici on voit les satellites 2 et 4 choisis parce que j’ai pu brancher 3 signaux (un semaphore, 2 carrés) et un servo d’aiguille.
J’ai branché un fil entre le GND et une entrée capteur (ce qui fait apparaître le chiffre "6" sur cette photos et les photos d’écran suivantes. Ce chiffre "6" signifie qu’une détection a lieu sur le satellite N°6.
Les satellites et le configurateur-testeur sont reliés par un câble de type téléphonique pour établir la liaison CAN.
Il ne faut pas oublier les straps d’adaptation d’impédance (résistance de 120Ω intégrée à la carte CAN) aux 2 extrémités du bus CAN.
Voici le logiciel complet du configurateur de satellites.
Il se trouve sur le git Locoduino, dans le dossier "Locoduinodrome" :
A coté du programme "Configurateur_de_satellites.ino", on trouve :
Le programme de test (A_Tests.h)
Le programme de configuration (B_Configuration.h)
Un fichier d’utilitaires pour affichage LCD
Et l’interface SAM (tous les autres fichiers)
Ce programme a été écrit en langage Arduino (donc du C simple, sans programmation objet). Son but est de configurer et de tester les 8 satellites installés pour la démonstration du Locoduinodrome à l’expo d’Orléans, et rien de plus.
Un inverseur permet de passer du mode test au mode configuration et vice versa.
Tout le reste est piloté par un encodeur quadratique avec son bouton intégré :
Un appui sur le bouton permet d’exécuter une action sélectionnée par rotation du bouton
Commençons par définir les ingrédients dont le logiciel a besoin :
Les PINS utilisées :
0,1 = Tx, Rx (ne servent que pour le debug sur moniteur),
2 = Interruption des réceptions sur le bus CAN,
3,5 = L’encodeur, permet de connaitre le sens de rotation,
4 = Le bouton de l’encoder (un clic !),
7 = L’inverseur config/test,
9,11,12,13, RST = Le bus SPI pour le CAN
A4 et A5 = Le bus I2C, SDA et SCL respectivement.
Les bibliothèques générales :
#define VERSION F("Satellites Test & Config") // pour le moniteur de l'IDE
#define SHORTVERSION F("TC_Satellites 0.5") // pour l'ecran LCD
#include <SPI.h>
#include <mcp_can.h>
#include <mcp_can_dfs.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Encoder.h>
#include <Bounce2.h>
On y trouve d’abord la version du logiciel à mettre à jour à chaque modification.
Puis les bibliothèques suivantes :
SPI pour le bus SPI
La bibliothèque Can à prendre sur le dépôt Git Locoduino (Bibliothèque Can)
La bibliothèque Wire pour l’I2C
LiquiCrystal_I2C pour l’afficheur LCD
Bounce2 pour calmer les boutons et inverseurs
On déclare dans la foulée l’objet canController qui communique avec la carte CAN via le SPI, et les variables globales nécessaires.
MCP_CAN canController(9); // Set CS to pin 9 for CAN
volatile bool Flag_Recv = false; // pour IRQ CAN
volatile boolean CANOK = false;
unsigned int messagesRecus = 0; //nb de message reçus pour affichage
unsigned long IdR;
unsigned char lenR = 0;
unsigned char bufR[8]; // buffer reception
unsigned long IdS;
unsigned char lenS = 0;
unsigned char bufS[8]; // buffer emission
Puis les objets lcd et Encodeur ainsi que les 2 boutons. On en profite pour caser ici la routine de lecture de l’encodeur :
LiquidCrystal_I2C lcd(0x27,20,4); // LCD address = 0x27 pour 4 lignes de 20 cars
Encoder Encodeur(3, 5); // encodeur
long oldPosition = -999;
const int SWencPin = 4; // bouton encodeur
Bounce SWenc = Bounce();
bool gSWencState = false;
const int SWmodePin = 7; // inverseur test/config
Bounce SWmode = Bounce();
bool gSWmodeState = false;
bool _debug = true; // pour valider les "Serial.print" vers la Console
/* le principe est de savoir s'il y a rotation ou non, et dans quel sens
* peu importe le nombre de crans.
* On obtient +1 = incrément, 0 = pas de rotation, -1 = décrement
*/
int lireEncodeur()
{
int retour = 0;
long newPosition = Encodeur.read()/4;
if (newPosition != oldPosition) {
if (newPosition > oldPosition) {
retour = 1;
} else {
retour = -1;
}
oldPosition = newPosition;
}
return (retour);
}
Maintenant on introduit l’interface SAM décrite dans l’article La carte Satellite V1 (3) et les programmes de tests et de configuration qui sont dans deux onglets (fichiers dans le dossier du programme) différents, avec un onglet "LCD.h" regroupant les routines d’affichage sur LCD :
////////////////// L'Interface SAM /////////////////////
#include "SatelliteWrapper.h" // gere les aiguilles (point) et les signaux
#include "Feux.h" // défini les types de feux
///////////////// les programmes de tests ont besoin de la variable lcd et de l'interface SAM //////
#include "LCD.h" // gestion de l'afficheur LCD
#include "A_Tests.h" // programmes de test
#include "B_Configuration.h" // programmes de configuration
Les déclarations suivantes décrivent la topologie du réseau, à savoir quels capteurs (présence zone et ponctuels-zone d’arrêt) et quels actionneurs (servo d’aiguille et signaux) sont connectés à chaque satellite.
C’est un moment très important dans la définition du réseau et de ses ingrédients.
/*
* Les aiguillages
*/
PointWrapper pointWrapper0(0, 2); /* aiguillage 0 du gestionnaire sur satellite 2 */
PointWrapper pointWrapper1(1, 6); /* aiguillage 1 du gestionnaire sur satellite 6 */
/*
* Les signaux
*/
SemaphoreSignalWrapper S1wrapper(0, 4, SIGNAL_0); /* signal 0 du gestionnaire sur satellite 4, slot 0 */
SemaphoreSignalWrapper S2wrapper(1, 3, SIGNAL_0); /* signal 1 du gestionnaire sur satellite 3, slot 0 */
CarreSignalWrapper C3wrapper(4, 0, SIGNAL_0); /* signal 4 du gestionnaire sur satellite 0, slot 0 */
CarreSignalWrapper C4wrapper(5, 1, SIGNAL_0); /* signal 5 du gestionnaire sur satellite 1, slot 0 */
CarreSignalWrapper C5wrapper(6, 6, SIGNAL_0); /* signal 6 du gestionnaire sur satellite 6, slot 0 */
CarreSignalWrapper C6wrapper(7, 2, SIGNAL_0); /* signal 7 du gestionnaire sur satellite 2, slot 0 */
CarreRappelRalentissementSignalWrapper C1wrapper(2, 7, SIGNAL_0); /* signal 2 du gestionnaire sur satellite 7, slot 0 */
CarreRappelRalentissementSignalWrapper C2wrapper(3, 5, SIGNAL_0); /* signal 3 du gestionnaire sur satellite 5, slot 0 */
SemaphoreRalentissementSignalWrapper S3wrapper(8, 3, SIGNAL_1); /* signal 8 du gestionnaire sur satellite 3, slot 1 */
SemaphoreRalentissementSignalWrapper S4wrapper(9, 4, SIGNAL_1); /* signal 9 du gestionnaire sur satellite 4, slot 1 */
Les utilitaires de réception et d’envoi de messages Can :
On y trouve d’abord la routine d’interruption qui s’exécute à chaque réception de message Can. Elle est très courte comme il se doit. Elle fait simplement monter un flag (Flag_Recv) qui est testé à chaque boucle de la loop.
// le flag IRQ monte quand au moins un message est recu
// le flag IRQ ne retombe QUE si tous les messages sont lus
void MCP2515_ISR() { Flag_Recv = true; } // surveillance de l'IRQ
Ensuite nous trouvons des utilitaires faits pour simplifier l’interface avec le gestionnaire qui ne connait (dans le cas de la démo du Locoduinodrome) que des numéros d’appareil.
Cette dernière routine sert à envoyer un message de configuration contenu dans bufS de taille cLg au satellite cId.
Le SETUP du programme
Ici, il n’y a rien que du classique :
initialisation du port série, du LCD, affichage de la version du logiciel ;
initialisation de l’encodeur ;
initialisation du réseau CAN, sans filtre car tous les messages peuvent concerner le configurateur ;
initialisation de SAM
test de la clé CONFIG/TEST dans la variable gModeConfig
void setup()
{
Serial.begin(115200);
Serial.flush();
Serial.println(SHORTVERSION);
lcd.init(); // initialise le lcd
lcd.backlight();
lcd.setCursor(0, 0); // LCD 1e ligne
lcd.print(SHORTVERSION);
oldPosition = Encodeur.read()/4; // initialise encodeur et boutons
pinMode(SWencPin,INPUT_PULLUP);
SWenc.attach(SWencPin);
SWenc.interval(100);
pinMode(SWmodePin,INPUT_PULLUP);
SWmode.attach(SWmodePin);
SWmode.interval(100);
const int baudrate = CAN_500KBPS; // initialise le Can à 500 Kb/s
canController.start();
int repeat = 10; // open the CAN
while (repeat > 0) {
if (CAN_OK == canController.begin(baudrate)) {CANOK=true;break;}
else {repeat--;}
delay(200);
}
lcd.setCursor(0, 1); // LCD 2e ligne
if (CANOK) {lcd.print(F("Can OK"));}else{lcd.print(F("Can NOK!"));}
attachInterrupt(0, MCP2515_ISR, FALLING); // start interrupt from pin 2
/*
* Pas de filtres Can
*/
PointWrapper::begin(); // initialise SAM
SignalWrapper::begin();
Serial.println("Etat Initial");
printOutBuffers(); // etat initial des satellites
sVit.f = 2.0; // vitesse des servos par défaut
delay(2000); // le temps de lire le LCD
gModeConfig = digitalRead(SWmodePin);
if (gModeConfig) { // clé T/C mode config ou test
initConfig(); // voir Configuration.h
} else {
initTest(); // voir Tests.h
}
} // fin de Setup
La LOOP du programme
Comme d’habitude, c’est une fonction qui se répète indéfiniment et qui va exécuter plusieurs tâches, les unes après les autres :
Tester la réception d’un message CAN et afficher ce qu’il signifie, à savoir une occupation ou une détection ponctuelle, ainsi qu’un compteur de messages géré à la fin de la loop.
Tester le changement d’état de la clé TEST/CONFIG pour mettre à jour la variable gModeConfig et initialiser le mode correspondant.
Réaliser la tache doConfig() ou doTest(), selon le mode.
Et enfin, envoyer une interrogation aux satellites avec une récurrence que l’on peut définir.
Sur cette image et sur les copies d’écran suivantes on voit toutes les informations correspondant à chaque contexte :
Le compteur en haut à droite montre la circulation des messages Can sur le réseau Can.
Le texte en première ligne est le titre du test ou de la configuration particulière (aiguille avec son numéro en position Min ou Max, ou feu avec son numéro et le numéro de satellite sur lequel il est branché).
La deuxième ligne désigne l’état de l’élément testé ou le paramètre en cours de configuration.
La troisième ligne présente les détections d’occupation en mode test.
La quatrième ligne présente les détections ponctuelles en mode test.
Dans les 2 lignes du bas, la présence du satellite est représentée par un point, lorsqu’il n’y a pas de détection, et par son numéro lorsqu’il y a une détection.
void loop()
{
if (Flag_Recv) { // reception et traitement des messages Can
while (CAN_MSGAVAIL == canController.checkReceive()) {
canController.readMsgBuf(&lenR, bufR); // read data, lenR: data length, bufR: data buf
IdR = canController.getCanId();
if (_debug) {
Serial.print(" id:0x");Serial.print(IdR, HEX);Serial.print(" Data");
for (int k = 0; k < lenR; k++) {
Serial.print(' ');Serial.print("0x");Serial.print(bufR[k], HEX);
}
Serial.println();
}
messagesRecus++;
if (!gModeConfig) { // en mode test on affiche l'état des capteurs lignes 3 et 4
displayCapteurs(IdR, bufR); // gestion des messages CAN reçus : affichage LCD (dans LCD.h)
}
}
Flag_Recv = false; // Flag MCP2515 pret pour un nouvel IRQ
}
if ( SWmode.update() ) { // test de la clé T/C
if ( SWmode.fell() ) {
gModeConfig = false;
initTest();
} else {
if ( SWmode.rose() ) {
gModeConfig = true;
initConfig();
}
}
}
if (gModeConfig) { // mode config ou test ?
doConfig();
} else {
doTest();
}
if (gModeConfig == false) // uniquement en mode test
{
sendSatelliteMessage(); // emission periodique définie dans SatelliteConfig.h OUT_MESSAGE_PERIOD = 100;
}
if (_debug) {
if (gModeConfig == false) {lcd.setCursor(16, 0);} else {lcd.setCursor(16, 3);}
if (messagesRecus > 9999) messagesRecus = messagesRecus - 10000;
if (messagesRecus < 1000) lcd.print(' ');
if (messagesRecus < 100) lcd.print(' ');
if (messagesRecus < 10) lcd.print(' ');
lcd.print(messagesRecus);
}
} // fin de Loop
Voici les routines d’affichage des états des capteurs sur les 2 lignes du bas du LCD. Evidemment ces routines sont adaptées à la configuration choisie de notre réseau.
//-----------------------------------------------
void initDisplayCapteurs() {
// lignes 2 et 3 affichent l'état des capteurs satellites
lcd.setCursor(0, 2); // 3e ligne
lcd.print("Zones : ");
lcd.setCursor(0, 3); // 4e ligne
lcd.print("Detec : ");
}
//-------------------------------------------------------------------
void displayCapteurs(unsigned long canId, unsigned char *buf)
{
/*
* Ligne x : 01234567890123456789
* Ligne 2 : Test courant
* Ligne 2 : Zones :
* Ligne 3 : Detec :
*/
switch (canId)
{
case 0x10: // Z0,P2,P4
lcd.setCursor(8, 2); // 3e ligne col 8+0
lcd.print(bitRead(buf[0],2) ? '0' : '.');
lcd.setCursor(10, 3); // 4e ligne col 8+2
lcd.print(bitRead(buf[0],1) ? '2' : '.');
lcd.setCursor(12, 3); // 4e ligne col 8+4
lcd.print(bitRead(buf[0],0) ? '4' : '.');
break;
case 0x11: // Z1,P3,P5
lcd.setCursor(9, 2); // 3e ligne col 8+1
lcd.print(bitRead(buf[0],2) ? '1' : '.');
lcd.setCursor(11, 3); // 4e ligne col 8+3
lcd.print(bitRead(buf[0],1) ? '3' : '.');
lcd.setCursor(13, 3); // 4e ligne col 8+5
lcd.print(bitRead(buf[0],0) ? '5' : '.');
break;
case 0x12: // Z2
lcd.setCursor(10, 2); // 3e ligne col 8+2
lcd.print(bitRead(buf[0],2) ? '2' : '.');
break;
case 0x13: // Z3,P7,P8
lcd.setCursor(11, 2); // 3e ligne col 8+3
lcd.print(bitRead(buf[0],2) ? '3' : '.');
lcd.setCursor(15, 3);// 4e ligne col 8+7
lcd.print(bitRead(buf[0],1) ? '7' : '.');
lcd.setCursor(16, 3); // 4e ligne col 8+8
lcd.print(bitRead(buf[0],0) ? '8' : '.');
break;
case 0x14: // Z6,P6,P9
lcd.setCursor(14, 2); // 3e ligne col 8+6
lcd.print(bitRead(buf[0],2) ? '6' : '.');
lcd.setCursor(14, 3); // 4e ligne col 8+6
lcd.print(bitRead(buf[0],1) ? '6' : '.');
lcd.setCursor(17, 3); // 4e ligne col 8+9
lcd.print(bitRead(buf[0],0) ? '9' : '.');
break;
case 0x15: // Z4,P1
lcd.setCursor(12, 2); // 3e ligne col 8+4
lcd.print(bitRead(buf[0],2) ? '4' : '.');
lcd.setCursor(9, 3); // 4e ligne col 8+1
lcd.print(bitRead(buf[0],1) ? '1' : '.');
break;
case 0x16: // Z5
lcd.setCursor(13, 2); // 3e ligne col 8+5
lcd.print(bitRead(buf[0],2) ? '5' : '.');
break;
case 0x17: // P0
lcd.setCursor(8, 3); // 4e ligne col 8+0
lcd.print(bitRead(buf[0],1) ? '0' : '.');
break;
default:
lcd.setCursor(19, 2);
lcd.print('?'); // Id reçu inconnu
break;
}
}
Le programme de TEST
Comme je l’ai indiqué au début de cet article, toute l’interface utilisateur se résume à tourner le bouton de l’encodeur pour sélectionner un type de test puis d’appuyer sur le bouton pour le réaliser ou choisir dans une sous-sélection. Un dernier appui permet de revenir au départ.
Donc nous avons les notions de menu et sous-menu.
J’ai choisi (arbitrairement) de ne retenir que 3 menus : 1 pour chaque aiguille (2 cas : droit ou dévié) et 1 pour l’ensemble des feux (160 cas : 16 type de feux pour 10 signaux). C’est le choix que j’ai fait et qui m’a semblé le plus pertinent.
Voici donc les variables déclarées en commençant par les chaines de caractères à afficher sur le LCD.
Cela se traduit par quelques tableaux dont l’un (FeuSat[10]) permet de faire la correspondance entre un numéro de signal et le satellite qui le porte.
Les variables sont évidentes.
////////////// DoTest //////////
const String titreTest[]={
"Aiguille 0 ", //0 sous-menus Min, Max
"Aiguille 1 ", //1 sous-menus Min, Max
"Feu # Sat #", //2 sous-menus 0 à 9 (10 signaux)
};
const int maxMenu = 3;
const String nomFeu[16]= {"Vl", "A", "S", "C", "R", "RR", "M", "Cv", "Vlc", "Ac", "Sc", "Rc", "RRc", "Mc", "D", "E"};
int indexFeu = 0;
unsigned int typeFeu[16] = {Vl, A, S, C, R, RR, M, Cv, Vlc, Ac, Sc, Rc, RRc, Mc, D, E};
int FeuSat[10] = {4,3,7,5,0,1,6,2,3,4}; // n° de satellite en fonction du N° de feu
int menuTest = 0;
int sousmenuTest = 0;
int etatTest = 0; // gestion encodeur
int noSat = 0;
int noAig = 0; // numero d'aiguille 0..1
int noSig = 0; // numero signal 0..9
const int maxSignaux = 9;
Cette fonction initialise les variables des tests. Elle est appelée chaque fois que la clé TEST/CONFIG est positionnée sur TEST.
Cette fonction displayTitre() sert à rafraîchir la 1ère ligne de l’écran du LCD chaque fois que nécessaire.
void displayTitre() {
lcd.setCursor(0, 0);
lcd.print(titreTest[menuTest]);
switch (menuTest) {
case 0:
case 1:
// aiguilles 0 et 1 sous-menus Min, Max
switch (sousmenuTest) {
case 0:
lcd.print("Min");
break;
case 1:
lcd.print("Max");
break;
}
break;
case 2:
//"Feu # Sat # ", //2 sous-menus 0 à 9 (10 signaux)
lcd.print(" ");
lcd.setCursor(4, 0);
noSig = sousmenuTest;
lcd.print(noSig);
lcd.setCursor(10, 0); //
noSat = FeuSat[noSig];
lcd.print(noSat);
lcd.setCursor(0, 1); ; // 2 eme ligne
lcd.print("Type ");
break;
}
}
Les 2 fonctions suivantes envoient une commande par message CAN au satellite sélectionné dans les menu et sous-menu pour positionner une aiguille ou afficher un type de signal typeFeu.
Cette fonction teste la rotation de l’encodeur et l’appui sur son bouton.
Dans le cas des tests, comme ceux-ci ne provoquent pas de modification de la configuration des satellites, j’ai choisi d’exécuter un test à chaque rotation de l’encodeur.
Le bouton de l’encodeur ne sert qu’à passer en mode de choix de feu après la sélection du signal et du satellite.
La variable u indique si une rotation a lieu dans le sens horaire (u=1) ou anti-horaire (u=-1).
La fonction permet de balayer tous les cas possibles à l’intérieur des limites.
A chaque cas, outre la mise à jour des variables d’état et de l’afficheur LCD, elle appelle les fonctions testAiguille() et testFeu().
Organigramme du programme de tests
void doTest() {
// positionne menuTest et sousmenuTest et fait le test correspondant à chaque changement
// menuTest et sousmenuTest définissent les tests elementaires
// etatTest définit l'emploi de l'encodeur
// etatTest = 0 : juste la selection de test par l'encodeur
int u = lireEncodeur();
// rotation de l'encodeur pour choix du test
if ((u != 0) && (etatTest == 0 )) {
if (u > 0) { // +1 encodeur
if (menuTest == 2) { // choix parmi 9 signaux
if (sousmenuTest < 9) {
sousmenuTest++;
}
}
if (menuTest <= 1) { // aiguilles 0 et 1
sousmenuTest++;
if (sousmenuTest > 1) {
menuTest++; // passage au test signaux
sousmenuTest = 0;
}
if (menuTest <= 1) {
testAiguille(sousmenuTest);
}
}
}
if (u < 0) { // -1 encodeur
if (menuTest <= 1) { // aiguille
if (sousmenuTest > 0) {
sousmenuTest--;
} else {
if (menuTest > 0) {
menuTest--;
sousmenuTest = 1;
}
}
testAiguille(sousmenuTest);
}
if (menuTest == 2) { // feux
if (sousmenuTest > 0) {
sousmenuTest--;
} else {
menuTest = 1; // passage en test aiguille
sousmenuTest = 0;
testAiguille(sousmenuTest);
}
}
}
displayTitre(); // met à jour noSig pour testFeu()
}
if ((u != 0) && (etatTest == 2)) { // choix type de feu
if (u > 0) { // augmenter
if (indexFeu < 15) indexFeu++;
} else {
if (indexFeu > 0) indexFeu--;
}
testFeu();
}
// un appui sur le bouton de l'encodeur change etatTest en fonction du contexte défini par etatTest et menuTest
if (SWenc.update()) {
if ( SWenc.fell() ) { // appui sur bouton encodeur
switch (etatTest) {
case 0:
if (menuTest == 2) { // passage en choix type de feu
etatTest = 2;
indexFeu=0; // Vl
displayTitre();
testFeu();
}
break;
case 2: // fin du choix type de feux
etatTest = 0;
lcd.clear();
displayTitre(); // retour a l'etat précédent du choix test
initDisplayCapteurs(); // rafraichissement LCD
break;
}
}
}
}
Voici quelques exemples :
Test du signal 9 sur le satellite 4 : voie libre
Test du signal 9 sur le satellite 4 : ralentissement clignotant
Test du signal 7 sur le satellite 2 : voie libre
Test du signal 7 sur le satellite 2 : avertissement
Test du signal 7 sur le satellite 2 : carré
Test de l’aiguille 0 - position droite
Le programme de configuration
On commence par déclarer les constantes et variables nécessaires :
celles qui servent à la navigation dans les menus ;
les valeurs par défaut des paramètres de configuration
les chaines qui s’afficheront sur l’écran LCD : on voit qu’il y a 3 paramètres de configuration par servo et 5 par Led, avec 10 Leds, ce qui fait 56 paramètres au total.
le menu comporte une position "Choix Led" qui permet de sélectionner une Led parmi les 10 possibles et une position "Fin config" qui permet de revenir au menu principal après être entré dans les configurations des paramètres des Leds.
A ce stade du développement des satellites, ceux-ci ne remontent pas (encore) la valeur courante de leurs paramètres. Par conséquent le programme de configuration nécessite de refaire le réglage d’un paramètre dès lors qu’on le sélectionne pour le modifier.
On remarque en passant que les valeurs des butées initiales des servos sont choisies dans une plage très étroite près du milieu de l’amplitude totale du servo. Ceci a pour but d’éviter, au moment du lancement de la configuration, de forcer l’aiguille vers des positions dangereuses pour sa mécanique. Le réglage des butées d’aiguille consistera donc à élargir cette plage et dépassant légèrement (mais pas trop !) le point de contact des lames pour donner un léger effet ressort.
////////////// DoConfig //////////
bool gModeConfig = false; // mode test
int valeurEncodeur = 0; // valeur absolue (virtuelle) de l'encodeur
int gnumConfig = 0; // numero de config
bool gModeSWConfig = false; // choix ou true : execution
int satConfig = 0; // choix du satellite (0..7)
int ledConfig = 0; // choix de la led (0..9)
int sMin = 1300; // servo min
int sMax = 1700; // servo max
// pour les conversions entre 1 float et 4 bytes
union sVitType {float f; unsigned char b[sizeof(float)];} ;
sVitType sVit; // servo vitesse
uint8_t sLum = 255; // intensité
uint8_t sAlt = 255; // temps d'allumage
uint8_t sFad = 255; // temps d'extinction
uint8_t sOnt = 255; // temps allumé
uint8_t sPer = 255; // periode
const String titreConfig[]={
"Servo Max ", //0
"Servo Min ", //1
"Servo Vitesse", //2
"Choix Led ", //3
"Led Maxi ", //4
"Led T. allume", //5
"Led T. eteint", //6
"Led T. on ", //7
"Led Periode ", //8
"Fin config "}; //9
Ensuite nous avons une fonction initConfig() qui sert à initialiser toutes les variables d’état du mode configuration.
Elle intègre une boucle infinie nécessaire au choix d’un satellite parmi les 8 possibles car on ne peut configurer qu’un seul satellite à la fois.
///////////////// initialisations /////////////////
void initConfig() // menu principal
{
lcd.clear(); // 1e ligne
lcd.print("Config Satellite ");
lcd.print(satConfig); // satellite en cours de configuration
while (1) {
int enc = lireEncodeur();
if (enc > 0) satConfig++;
if (enc < 0) satConfig--;
if (satConfig < 0) {satConfig = 0;}
if (satConfig > 7) {satConfig = 7;}
lcd.setCursor(17, 0);
lcd.print(satConfig); // satellite en cours de configuration
SWenc.update();
if (SWenc.fell()) break;
if ( SWmode.update() ) { // test de la clé T/C
if ( SWmode.fell() ) {
gModeConfig = false;
initTest();
return;
}
}
}
gModeSWConfig = false;// mode choix fonction
lcd.setCursor(0, 1); // 2e ligne
lcd.print(titreConfig[gnumConfig]);
}
Ensuite l’ordonnancement des étapes de configuration est représenté sur cet organigramme :
Organigramme du programme de configuration
La rotation de l’encodeur se traduit, en fonction de la variable booléenne gModeSWConfig par :
soit la navigation dans les menus de configuration ;
soit la variation d’un paramètre de configuration.
L’appui sur le bouton de l’encodeur inverse la variable gModeSWConfig.
///////////////// configuration /////////////////
/*
* Mode opératoire
* Choisir un paramètre de configuration en tournant le bouton de l'encodeur (9 cas possibles).
* Appuyer sur le bouton de l'encodeur pour passer en mode modification du paramètre sélectionné.
* Tourner le bouton de l'encodeur pour changer la valeur du paramètre.
* Un message Can de configuration est envoyé au satellite à chaque cran de l'encodeur. Ceci a pour but de voir l’effet immédiat sur l’objet configuré : le servo bouge dans un sens ou l’autre, la led éclaire plus ou moins. On s’arrête quand le résultat est satisfaisant.
* Appuyer sur le bouton de l'encodeur pour terminer le mode modification du paramètre sélectionné.
* Une message Can de fin de configuration est envoyé au satellite
*/
void doConfig()
{
int u = lireEncodeur();
if (u == 0) // pas de changement
{
SWenc.update();
if ( SWenc.fell() )
{
gModeSWConfig = !gModeSWConfig; // inversion du mode CONFIG -> ENREGISTREMENT et retour MENU
if (gModeSWConfig == false) // fin du mode CONFIG -> ENREGISTREMENT
{
lcd.setCursor(14, 1); // 2e ligne
lcd.print(" "); // effacement
switch (gnumConfig)
{
case 0: // Servo Max
bufS[0]=0x80; // perma servo
bufS[1]=1; // aig max
bufS[2]=(byte)(sMax>>8);
bufS[3]=(byte)(sMax);
sendConfig(satConfig, 4);
break;
case 1: // Servo Min
bufS[0]=0x80; // perma servo
bufS[1]=0; // aig min
bufS[2]=(byte)(sMin>>8);
bufS[3]=(byte)(sMin);
sendConfig(satConfig, 4);
break;
case 2: // Servo Vit
bufS[0]=0x80; // perma servo
bufS[1]=2; // aig vit
bufS[2]=sVit.b[3];
bufS[3]=sVit.b[2];
bufS[4]=sVit.b[1];
bufS[5]=sVit.b[0];
sendConfig(satConfig, 6);
break;
case 3: // choix de la led
break;
case 4: // Led Max
bufS[0]=0x81; // perma led
sendConfig(satConfig, 3);
break;
case 5: // Led T. allume
bufS[0]=0x81; // perma led
sendConfig(satConfig, 3);
break;
case 6: // Led T. eteint
bufS[0]=0x81; // perma led
sendConfig(satConfig, 3);
break;
case 7: // Led T. On
bufS[0]=0x81; // perma led
sendConfig(satConfig, 3);
break;
case 8: // Led Period
bufS[0]=0x81; // perma led
sendConfig(satConfig, 3);
break;
}
} else // passage en mode modif (gModeSWConfig = true) -> SELECTION du TYPE
{
lcd.setCursor(13, 1); // 2e ligne
lcd.print('>');
switch (gnumConfig)
{
case 0:
lcd.print(sMax);
break;
case 1:
lcd.print(sMin);
break;
case 2:
lcd.print(sVit.f, 2);
break;
case 3: // choix de la led
lcd.print(ledConfig);
break;
case 4:
lcd.print(sLum); // Max
break;
case 5:
lcd.print(sAlt); // T. allume
break;
case 6:
lcd.print(sFad); // T. eteint
break;
case 7:
lcd.print(sOnt); // T. on
break;
case 8:
lcd.print(sPer); // Period
break;
case 9:
gnumConfig = 0;
initConfig(); // fin pour ce satellite
break;
}
lcd.print(" ");
}
}
return;
}
// cas de la rotation encodeur
if (u > 0) valeurEncodeur++;
if (u < 0) valeurEncodeur--;
if (gModeSWConfig == false) // mode CHOIX CONFIG
{
gnumConfig = valeurEncodeur;
if (gnumConfig < 0) {valeurEncodeur = gnumConfig = 0;}
if (gnumConfig > 9) {valeurEncodeur = gnumConfig = 9;}
lcd.setCursor(0, 1); // 2e ligne
lcd.print(titreConfig[gnumConfig]);
} else { // mode MODIFICATION
lcd.setCursor(14, 1); // 2e ligne
switch (gnumConfig)
{
case 0: // Servo Max
if (u > 0) sMax++;
if (u < 0) sMax--;
lcd.print(sMax);lcd.print(" ");
bufS[0]=0; // tempo servo
bufS[1]=1; // a0 max
bufS[2]=(byte)(sMax>>8);
bufS[3]=(byte)(sMax);
sendConfig(satConfig, 4);
break;
case 1: // Servo Min
if (u > 0) sMin++;
if (u < 0) sMin--;
lcd.print(sMin);lcd.print(" ");
bufS[0]=0; // tempo servo
bufS[1]=1; // a0 min
bufS[2]=(byte)(sMin>>8);
bufS[3]=(byte)(sMin);
sendConfig(satConfig, 4);
break;
case 2: // Servo Vit
if (u > 0) {sVit.f = sVit.f + 0.1;}
if (u < 0) {sVit.f = sVit.f - 0.1;}
lcd.print(sVit.f, 2);lcd.print(" ");
bufS[0]=0; // tempo servo
bufS[1]=2; // a0 vitesse
bufS[2]=sVit.b[3];
bufS[3]=sVit.b[2];
bufS[4]=sVit.b[1];
bufS[5]=sVit.b[0];
sendConfig(satConfig, 6);
break;
case 3: // choix de la Led
if ((u > 0) && (ledConfig < 9)) ledConfig++;
if ((u < 0) && (ledConfig > 0)) ledConfig--;
lcd.print(ledConfig);lcd.print(" ");
break;
case 4: // Led Max luminosité
if ((u > 0) && (sLum < 255)) sLum++;
if ((u < 0) && (sLum > 0)) sLum--;
lcd.print(sLum);lcd.print(" ");
bufS[0]=1; // reglage led
bufS[1]= (ledConfig << 3) + 0; // numero de led + intensité
bufS[2]=sLum;
sendConfig(satConfig, 3);
break;
case 5: // Led allumage Time
if ((u > 0) && (sAlt < 255)) sAlt++;
if ((u < 0) && (sAlt > 0)) sAlt--;
lcd.print(sAlt);lcd.print(" ");
bufS[0]=1; // reglage led
bufS[1]= (ledConfig << 3) + 1; // numero de led + intensité
bufS[2]=sAlt;
sendConfig(satConfig, 3);
break;
case 6: // Led extinction Time
if ((u > 0) && (sFad < 255)) sFad++;
if ((u < 0) && (sFad > 0)) sFad--;
lcd.print(sFad);lcd.print(" ");
bufS[0]=1; // reglage led
bufS[1]= (ledConfig << 3) + 2; // numero de led + intensité
bufS[2]=sFad;
sendConfig(satConfig, 3);
break;
case 7: // Led On Time
if ((u > 0) && (sOnt < 255)) sOnt++;
if ((u < 0) && (sOnt > 0)) sOnt--;
lcd.print(sOnt);lcd.print(" ");
bufS[0]=1; // reglage led
bufS[1]= (ledConfig << 3) + 3; // numero de led + intensité
bufS[2]=sOnt;
sendConfig(satConfig, 3);
break;
case 8: // Led Period
if ((u > 0) && (sPer < 255)) sPer++;
if ((u < 0) && (sPer > 0)) sPer--;
lcd.print(sPer);lcd.print(" ");
bufS[0]=1; // reglage led
bufS[1]= (ledConfig << 3) + 4; // numero de led + intensité
bufS[2]=sPer;
sendConfig(satConfig, 3);
break;
case 9: // fin pour ce satellite
break;
}
}
}
L’interface utilisateur se présente comme suit :
Choix d’un satellite à configurer
Choix du paramètre "Servo Max"
Le nombre 31 en bas à droite affiche un compteur de messages CAN en mode configuration.
Valeur par défaut du paramètre "Servo Max"
Réglage du paramètre "Servo Max"
Validation du paramètre "Servo Max"
Reglage de la vitesse du servo d’aiguille
Choix d’une des Leds du satellite
Réglage luminosité maximum
Réglage de la période de clignotement
Réglage du temps d’allumage (rise time)
Réglage du temps d’extinction (fall time)
Réglage de la durée du temps "allumé"
Fin de configuration
Le programme complet peut être téléchargé ici :
Conclusions
Avec un seul bouton (encodeur quadratique), on peut faire des tas de choses, mais le programme peut sembler complexe justement pour faire toutes ces choses. Ceux qui sont un peu experts arriveront à l’adapter à leurs besoins spécifiques. Pour les autres, il y a là un exemple d’utilisation de ce type d’encodeur quadratique, comment y rattacher de multiples traitements.
Le logiciel et ses futures mises à jour sont mises à disposition sur le git : Configurateur de Satellite.
Nous vous invitons vivement à faire part de vos réalisations personnelles et vos remarques constructives sur le Forum (dossier "Vos projets/Satellite").