La carte Satellite V1 - Un Configurateur-Testeur

. Par : Dominique. URL : https://www.locoduino.org/spip.php?article249

Après la présentation des principes fondateurs des Satellites Locoduino La carte Satellite V1, la réalisation matérielle La carte Satellite V1, les logiciels La carte Satellite V1 et l’interface SAM La carte Satellite V1, 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

La carte Satellite V1
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
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é
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
Un prototype de configurateur-testeur

Voici le schéma de l’engin (cliquer pour agrandir l’image) :

Le schéma de câblage
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
affectations de pattes

Et un petit réseau CAN constitué de 2 satellites et du configurateur-testeur :

Une séance de configuration et tests
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" :

Configurateur de Satellite

Ce sketch contient les fichiers suivants :

Liste des fichiers du programme
Liste des fichiers du programme

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 :

  1. #define VERSION F("Satellites Test & Config") // pour le moniteur de l'IDE
  2. #define SHORTVERSION F("TC_Satellites 0.5") // pour l'ecran LCD
  3. #include <SPI.h>
  4. #include <mcp_can.h>
  5. #include <mcp_can_dfs.h>
  6. #include <Wire.h>
  7. #include <LiquidCrystal_I2C.h>
  8. #include <Encoder.h>
  9. #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.

  1. MCP_CAN canController(9); // Set CS to pin 9 for CAN
  2. volatile bool Flag_Recv = false; // pour IRQ CAN
  3. volatile boolean CANOK = false;
  4. unsigned int messagesRecus = 0; //nb de message reçus pour affichage
  5.  
  6. unsigned long IdR;
  7. unsigned char lenR = 0;
  8. unsigned char bufR[8]; // buffer reception
  9.  
  10. unsigned long IdS;
  11. unsigned char lenS = 0;
  12. 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 :

  1. LiquidCrystal_I2C lcd(0x27,20,4); // LCD address = 0x27 pour 4 lignes de 20 cars
  2. Encoder Encodeur(3, 5); // encodeur
  3. long oldPosition = -999;
  4. const int SWencPin = 4; // bouton encodeur
  5. Bounce SWenc = Bounce();
  6. bool gSWencState = false;
  7. const int SWmodePin = 7; // inverseur test/config
  8. Bounce SWmode = Bounce();
  9. bool gSWmodeState = false;
  10.  
  11. bool _debug = true; // pour valider les "Serial.print" vers la Console
  12.  
  13. /* le principe est de savoir s'il y a rotation ou non, et dans quel sens
  14.  * peu importe le nombre de crans.
  15.  * On obtient +1 = incrément, 0 = pas de rotation, -1 = décrement
  16.  */
  17.  
  18. int lireEncodeur()
  19. {
  20. int retour = 0;
  21. long newPosition = Encodeur.read()/4;
  22. if (newPosition != oldPosition) {
  23. if (newPosition > oldPosition) {
  24. retour = 1;
  25. } else {
  26. retour = -1;
  27. }
  28. oldPosition = newPosition;
  29. }
  30. return (retour);
  31. }

Maintenant on introduit l’interface SAM décrite dans l’article La carte Satellite V1 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 :

  1. ////////////////// L'Interface SAM /////////////////////
  2. #include "SatelliteWrapper.h" // gere les aiguilles (point) et les signaux
  3. #include "Feux.h" // défini les types de feux
  4.  
  5. ///////////////// les programmes de tests ont besoin de la variable lcd et de l'interface SAM //////
  6. #include "LCD.h" // gestion de l'afficheur LCD
  7. #include "A_Tests.h" // programmes de test
  8. #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.

  1. /*
  2.  * Les aiguillages
  3.  */
  4. PointWrapper pointWrapper0(0, 2); /* aiguillage 0 du gestionnaire sur satellite 2 */
  5. PointWrapper pointWrapper1(1, 6); /* aiguillage 1 du gestionnaire sur satellite 6 */
  6.  
  7. /*
  8.  * Les signaux
  9.  */
  10. SemaphoreSignalWrapper S1wrapper(0, 4, SIGNAL_0); /* signal 0 du gestionnaire sur satellite 4, slot 0 */
  11. SemaphoreSignalWrapper S2wrapper(1, 3, SIGNAL_0); /* signal 1 du gestionnaire sur satellite 3, slot 0 */
  12. CarreSignalWrapper C3wrapper(4, 0, SIGNAL_0); /* signal 4 du gestionnaire sur satellite 0, slot 0 */
  13. CarreSignalWrapper C4wrapper(5, 1, SIGNAL_0); /* signal 5 du gestionnaire sur satellite 1, slot 0 */
  14. CarreSignalWrapper C5wrapper(6, 6, SIGNAL_0); /* signal 6 du gestionnaire sur satellite 6, slot 0 */
  15. CarreSignalWrapper C6wrapper(7, 2, SIGNAL_0); /* signal 7 du gestionnaire sur satellite 2, slot 0 */
  16. CarreRappelRalentissementSignalWrapper C1wrapper(2, 7, SIGNAL_0); /* signal 2 du gestionnaire sur satellite 7, slot 0 */
  17. CarreRappelRalentissementSignalWrapper C2wrapper(3, 5, SIGNAL_0); /* signal 3 du gestionnaire sur satellite 5, slot 0 */
  18. SemaphoreRalentissementSignalWrapper S3wrapper(8, 3, SIGNAL_1); /* signal 8 du gestionnaire sur satellite 3, slot 1 */
  19. 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.

  1. // le flag IRQ monte quand au moins un message est recu
  2. // le flag IRQ ne retombe QUE si tous les messages sont lus
  3.  
  4. 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.

  1. void sendTest(byte tId)
  2. {
  3. uint32_t frameId = 0x20 + tId;
  4. canController.sendMsgBuf(frameId, 0, 3, bufS);
  5. }
  6.  
  7. void sendAiguille(int a, bool i)
  8. {
  9. PointWrapper::setPointPosition(a,i);
  10. }
  11.  
  12. void sendSignal(int sig, unsigned int f)
  13. {
  14. SignalWrapper::setSignalState(sig, f);
  15. }
  16.  
  17. void sendConfig(byte cId, byte cLg)
  18. {
  19. unsigned long longId = 0x1FFFFF20 + cId;
  20. canController.sendMsgBuf(longId, 1, cLg, bufS);
  21. }

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
  1. void setup()
  2. {
  3. Serial.begin(115200);
  4. Serial.flush();
  5. Serial.println(SHORTVERSION);
  6. lcd.init(); // initialise le lcd
  7. lcd.backlight();
  8. lcd.setCursor(0, 0); // LCD 1e ligne
  9. lcd.print(SHORTVERSION);
  10.  
  11. oldPosition = Encodeur.read()/4; // initialise encodeur et boutons
  12. pinMode(SWencPin,INPUT_PULLUP);
  13. SWenc.attach(SWencPin);
  14. SWenc.interval(100);
  15. pinMode(SWmodePin,INPUT_PULLUP);
  16. SWmode.attach(SWmodePin);
  17. SWmode.interval(100);
  18.  
  19. const int baudrate = CAN_500KBPS; // initialise le Can à 500 Kb/s
  20. canController.start();
  21. int repeat = 10; // open the CAN
  22. while (repeat > 0) {
  23. if (CAN_OK == canController.begin(baudrate)) {CANOK=true;break;}
  24. else {repeat--;}
  25. delay(200);
  26. }
  27. lcd.setCursor(0, 1); // LCD 2e ligne
  28. if (CANOK) {lcd.print(F("Can OK"));}else{lcd.print(F("Can NOK!"));}
  29. attachInterrupt(0, MCP2515_ISR, FALLING); // start interrupt from pin 2
  30.  
  31. /*
  32.   * Pas de filtres Can
  33.   */
  34.  
  35. PointWrapper::begin(); // initialise SAM
  36. SignalWrapper::begin();
  37.  
  38. Serial.println("Etat Initial");
  39. printOutBuffers(); // etat initial des satellites
  40.  
  41. sVit.f = 2.0; // vitesse des servos par défaut
  42.  
  43. delay(2000); // le temps de lire le LCD
  44.  
  45. gModeConfig = digitalRead(SWmodePin);
  46. if (gModeConfig) { // clé T/C mode config ou test
  47. initConfig(); // voir Configuration.h
  48. } else {
  49. initTest(); // voir Tests.h
  50. }
  51. } // 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.
Test de l'aiguille 0 - position déviée
Test de l’aiguille 0 - position déviée
  1. void loop()
  2. {
  3. if (Flag_Recv) { // reception et traitement des messages Can
  4. while (CAN_MSGAVAIL == canController.checkReceive()) {
  5. canController.readMsgBuf(&lenR, bufR); // read data, lenR: data length, bufR: data buf
  6. IdR = canController.getCanId();
  7. if (_debug) {
  8. Serial.print(" id:0x");Serial.print(IdR, HEX);Serial.print(" Data");
  9. for (int k = 0; k < lenR; k++) {
  10. Serial.print(' ');Serial.print("0x");Serial.print(bufR[k], HEX);
  11. }
  12. Serial.println();
  13. }
  14. messagesRecus++;
  15. if (!gModeConfig) { // en mode test on affiche l'état des capteurs lignes 3 et 4
  16. displayCapteurs(IdR, bufR); // gestion des messages CAN reçus : affichage LCD (dans LCD.h)
  17. }
  18. }
  19. Flag_Recv = false; // Flag MCP2515 pret pour un nouvel IRQ
  20. }
  21.  
  22. if ( SWmode.update() ) { // test de la clé T/C
  23. if ( SWmode.fell() ) {
  24. gModeConfig = false;
  25. initTest();
  26. } else {
  27. if ( SWmode.rose() ) {
  28. gModeConfig = true;
  29. initConfig();
  30. }
  31. }
  32. }
  33.  
  34. if (gModeConfig) { // mode config ou test ?
  35. doConfig();
  36. } else {
  37. doTest();
  38. }
  39.  
  40. if (gModeConfig == false) // uniquement en mode test
  41. {
  42. sendSatelliteMessage(); // emission periodique définie dans SatelliteConfig.h OUT_MESSAGE_PERIOD = 100;
  43. }
  44.  
  45. if (_debug) {
  46. if (gModeConfig == false) {lcd.setCursor(16, 0);} else {lcd.setCursor(16, 3);}
  47. if (messagesRecus > 9999) messagesRecus = messagesRecus - 10000;
  48. if (messagesRecus < 1000) lcd.print(' ');
  49. if (messagesRecus < 100) lcd.print(' ');
  50. if (messagesRecus < 10) lcd.print(' ');
  51. lcd.print(messagesRecus);
  52. }
  53. } // 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.

  1. //-----------------------------------------------
  2. void initDisplayCapteurs() {
  3. // lignes 2 et 3 affichent l'état des capteurs satellites
  4. lcd.setCursor(0, 2); // 3e ligne
  5. lcd.print("Zones : ");
  6. lcd.setCursor(0, 3); // 4e ligne
  7. lcd.print("Detec : ");
  8. }
  9.  
  10. //-------------------------------------------------------------------
  11. void displayCapteurs(unsigned long canId, unsigned char *buf)
  12. {
  13. /*
  14.   * Ligne x : 01234567890123456789
  15.   * Ligne 2 : Test courant
  16.   * Ligne 2 : Zones :
  17.   * Ligne 3 : Detec :
  18.   */
  19. switch (canId)
  20. {
  21. case 0x10: // Z0,P2,P4
  22. lcd.setCursor(8, 2); // 3e ligne col 8+0
  23. lcd.print(bitRead(buf[0],2) ? '0' : '.');
  24. lcd.setCursor(10, 3); // 4e ligne col 8+2
  25. lcd.print(bitRead(buf[0],1) ? '2' : '.');
  26. lcd.setCursor(12, 3); // 4e ligne col 8+4
  27. lcd.print(bitRead(buf[0],0) ? '4' : '.');
  28. break;
  29. case 0x11: // Z1,P3,P5
  30. lcd.setCursor(9, 2); // 3e ligne col 8+1
  31. lcd.print(bitRead(buf[0],2) ? '1' : '.');
  32. lcd.setCursor(11, 3); // 4e ligne col 8+3
  33. lcd.print(bitRead(buf[0],1) ? '3' : '.');
  34. lcd.setCursor(13, 3); // 4e ligne col 8+5
  35. lcd.print(bitRead(buf[0],0) ? '5' : '.');
  36. break;
  37. case 0x12: // Z2
  38. lcd.setCursor(10, 2); // 3e ligne col 8+2
  39. lcd.print(bitRead(buf[0],2) ? '2' : '.');
  40. break;
  41. case 0x13: // Z3,P7,P8
  42. lcd.setCursor(11, 2); // 3e ligne col 8+3
  43. lcd.print(bitRead(buf[0],2) ? '3' : '.');
  44. lcd.setCursor(15, 3);// 4e ligne col 8+7
  45. lcd.print(bitRead(buf[0],1) ? '7' : '.');
  46. lcd.setCursor(16, 3); // 4e ligne col 8+8
  47. lcd.print(bitRead(buf[0],0) ? '8' : '.');
  48. break;
  49. case 0x14: // Z6,P6,P9
  50. lcd.setCursor(14, 2); // 3e ligne col 8+6
  51. lcd.print(bitRead(buf[0],2) ? '6' : '.');
  52. lcd.setCursor(14, 3); // 4e ligne col 8+6
  53. lcd.print(bitRead(buf[0],1) ? '6' : '.');
  54. lcd.setCursor(17, 3); // 4e ligne col 8+9
  55. lcd.print(bitRead(buf[0],0) ? '9' : '.');
  56. break;
  57. case 0x15: // Z4,P1
  58. lcd.setCursor(12, 2); // 3e ligne col 8+4
  59. lcd.print(bitRead(buf[0],2) ? '4' : '.');
  60. lcd.setCursor(9, 3); // 4e ligne col 8+1
  61. lcd.print(bitRead(buf[0],1) ? '1' : '.');
  62. break;
  63. case 0x16: // Z5
  64. lcd.setCursor(13, 2); // 3e ligne col 8+5
  65. lcd.print(bitRead(buf[0],2) ? '5' : '.');
  66. break;
  67. case 0x17: // P0
  68. lcd.setCursor(8, 3); // 4e ligne col 8+0
  69. lcd.print(bitRead(buf[0],1) ? '0' : '.');
  70. break;
  71. default:
  72. lcd.setCursor(19, 2);
  73. lcd.print('?'); // Id reçu inconnu
  74. break;
  75. }
  76. }

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.

  1. ////////////// DoTest //////////
  2. const String titreTest[]={
  3. "Aiguille 0 ", //0 sous-menus Min, Max
  4. "Aiguille 1 ", //1 sous-menus Min, Max
  5. "Feu # Sat #", //2 sous-menus 0 à 9 (10 signaux)
  6. };
  7. const int maxMenu = 3;
  8.  
  9. const String nomFeu[16]= {"Vl", "A", "S", "C", "R", "RR", "M", "Cv", "Vlc", "Ac", "Sc", "Rc", "RRc", "Mc", "D", "E"};
  10. int indexFeu = 0;
  11. unsigned int typeFeu[16] = {Vl, A, S, C, R, RR, M, Cv, Vlc, Ac, Sc, Rc, RRc, Mc, D, E};
  12. int FeuSat[10] = {4,3,7,5,0,1,6,2,3,4}; // n° de satellite en fonction du N° de feu
  13.  
  14. int menuTest = 0;
  15. int sousmenuTest = 0;
  16. int etatTest = 0; // gestion encodeur
  17. int noSat = 0;
  18. int noAig = 0; // numero d'aiguille 0..1
  19. int noSig = 0; // numero signal 0..9
  20. 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.

  1. void initTest()
  2. {
  3. menuTest = 0;
  4. sousmenuTest = 0;
  5. etatTest = 0;
  6. lcd.clear();
  7. displayTitre();
  8. initDisplayCapteurs(); // onglet LCD.h
  9. testAiguille(sousmenuTest);
  10. }

Cette fonction displayTitre() sert à rafraîchir la 1ère ligne de l’écran du LCD chaque fois que nécessaire.

  1. void displayTitre() {
  2. lcd.setCursor(0, 0);
  3. lcd.print(titreTest[menuTest]);
  4. switch (menuTest) {
  5. case 0:
  6. case 1:
  7. // aiguilles 0 et 1 sous-menus Min, Max
  8. switch (sousmenuTest) {
  9. case 0:
  10. lcd.print("Min");
  11. break;
  12. case 1:
  13. lcd.print("Max");
  14. break;
  15. }
  16. break;
  17. case 2:
  18. //"Feu # Sat # ", //2 sous-menus 0 à 9 (10 signaux)
  19. lcd.print(" ");
  20. lcd.setCursor(4, 0);
  21. noSig = sousmenuTest;
  22. lcd.print(noSig);
  23. lcd.setCursor(10, 0); //
  24. noSat = FeuSat[noSig];
  25. lcd.print(noSat);
  26. lcd.setCursor(0, 1); ; // 2 eme ligne
  27. lcd.print("Type ");
  28. break;
  29. }
  30. }

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.

  1. //-----------------------------------------------
  2. void testAiguille(boolean s) {
  3. sendAiguille(menuTest, s);
  4. lcd.setCursor(0, 1); // 2 eme ligne
  5. if (s) {
  6. lcd.print("Droit ");
  7. } else {
  8. lcd.print("Devie ");
  9. }
  10. }
  11.  
  12. //-----------------------------------------------
  13. void testFeu() {
  14. lcd.setCursor(0, 1); ; // 2 eme ligne
  15. lcd.print("Type ");
  16. lcd.print(nomFeu[indexFeu]);
  17. lcd.print(" ");
  18. SignalWrapper::setSignalState(noSig, typeFeu[indexFeu]);
  19. }

La fonction doTest()

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
Organigramme du programme de tests
  1. void doTest() {
  2. // positionne menuTest et sousmenuTest et fait le test correspondant à chaque changement
  3. // menuTest et sousmenuTest définissent les tests elementaires
  4. // etatTest définit l'emploi de l'encodeur
  5. // etatTest = 0 : juste la selection de test par l'encodeur
  6. int u = lireEncodeur();
  7. // rotation de l'encodeur pour choix du test
  8. if ((u != 0) && (etatTest == 0 )) {
  9. if (u > 0) { // +1 encodeur
  10. if (menuTest == 2) { // choix parmi 9 signaux
  11. if (sousmenuTest < 9) {
  12. sousmenuTest++;
  13. }
  14. }
  15. if (menuTest <= 1) { // aiguilles 0 et 1
  16. sousmenuTest++;
  17. if (sousmenuTest > 1) {
  18. menuTest++; // passage au test signaux
  19. sousmenuTest = 0;
  20. }
  21. if (menuTest <= 1) {
  22. testAiguille(sousmenuTest);
  23. }
  24. }
  25. }
  26. if (u < 0) { // -1 encodeur
  27. if (menuTest <= 1) { // aiguille
  28. if (sousmenuTest > 0) {
  29. sousmenuTest--;
  30. } else {
  31. if (menuTest > 0) {
  32. menuTest--;
  33. sousmenuTest = 1;
  34. }
  35. }
  36. testAiguille(sousmenuTest);
  37. }
  38. if (menuTest == 2) { // feux
  39. if (sousmenuTest > 0) {
  40. sousmenuTest--;
  41. } else {
  42. menuTest = 1; // passage en test aiguille
  43. sousmenuTest = 0;
  44. testAiguille(sousmenuTest);
  45. }
  46. }
  47. }
  48. displayTitre(); // met à jour noSig pour testFeu()
  49. }
  50. if ((u != 0) && (etatTest == 2)) { // choix type de feu
  51. if (u > 0) { // augmenter
  52. if (indexFeu < 15) indexFeu++;
  53. } else {
  54. if (indexFeu > 0) indexFeu--;
  55. }
  56. testFeu();
  57. }
  58. // un appui sur le bouton de l'encodeur change etatTest en fonction du contexte défini par etatTest et menuTest
  59. if (SWenc.update()) {
  60. if ( SWenc.fell() ) { // appui sur bouton encodeur
  61. switch (etatTest) {
  62. case 0:
  63. if (menuTest == 2) { // passage en choix type de feu
  64. etatTest = 2;
  65. indexFeu=0; // Vl
  66. displayTitre();
  67. testFeu();
  68. }
  69. break;
  70. case 2: // fin du choix type de feux
  71. etatTest = 0;
  72. lcd.clear();
  73. displayTitre(); // retour a l'etat précédent du choix test
  74. initDisplayCapteurs(); // rafraichissement LCD
  75. break;
  76. }
  77. }
  78. }
  79. }

Voici quelques exemples :

Test du signal 9 sur le satellite 4 : voie libre
Test du signal 9 sur le satellite 4 : voie libre
Test du signal 9 sur le satellite 4 : ralentissement clignotant
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 : voie libre
Test du signal 7 sur le satellite 2 : avertissement
Test du signal 7 sur le satellite 2 : avertissement
Test du signal 7 sur le satellite 2 : carré
Test du signal 7 sur le satellite 2 : carré
Test de l'aiguille 0 - position droite
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.

  1. ////////////// DoConfig //////////
  2.  
  3. bool gModeConfig = false; // mode test
  4. int valeurEncodeur = 0; // valeur absolue (virtuelle) de l'encodeur
  5. int gnumConfig = 0; // numero de config
  6. bool gModeSWConfig = false; // choix ou true : execution
  7. int satConfig = 0; // choix du satellite (0..7)
  8. int ledConfig = 0; // choix de la led (0..9)
  9.  
  10. int sMin = 1300; // servo min
  11. int sMax = 1700; // servo max
  12. // pour les conversions entre 1 float et 4 bytes
  13. union sVitType {float f; unsigned char b[sizeof(float)];} ;
  14. sVitType sVit; // servo vitesse
  15. uint8_t sLum = 255; // intensité
  16. uint8_t sAlt = 255; // temps d'allumage
  17. uint8_t sFad = 255; // temps d'extinction
  18. uint8_t sOnt = 255; // temps allumé
  19. uint8_t sPer = 255; // periode
  20.  
  21.  
  22. const String titreConfig[]={
  23. "Servo Max ", //0
  24. "Servo Min ", //1
  25. "Servo Vitesse", //2
  26. "Choix Led ", //3
  27. "Led Maxi ", //4
  28. "Led T. allume", //5
  29. "Led T. eteint", //6
  30. "Led T. on ", //7
  31. "Led Periode ", //8
  32. "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.

  1. ///////////////// initialisations /////////////////
  2.  
  3. void initConfig() // menu principal
  4. {
  5. lcd.clear(); // 1e ligne
  6. lcd.print("Config Satellite ");
  7. lcd.print(satConfig); // satellite en cours de configuration
  8. while (1) {
  9. int enc = lireEncodeur();
  10. if (enc > 0) satConfig++;
  11. if (enc < 0) satConfig--;
  12. if (satConfig < 0) {satConfig = 0;}
  13. if (satConfig > 7) {satConfig = 7;}
  14. lcd.setCursor(17, 0);
  15. lcd.print(satConfig); // satellite en cours de configuration
  16. SWenc.update();
  17. if (SWenc.fell()) break;
  18. if ( SWmode.update() ) { // test de la clé T/C
  19. if ( SWmode.fell() ) {
  20. gModeConfig = false;
  21. initTest();
  22. return;
  23. }
  24. }
  25. }
  26. gModeSWConfig = false;// mode choix fonction
  27. lcd.setCursor(0, 1); // 2e ligne
  28. lcd.print(titreConfig[gnumConfig]);
  29. }

Ensuite l’ordonnancement des étapes de configuration est représenté sur cet organigramme :

Organigramme du programme de configuration
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.

  1. ///////////////// configuration /////////////////
  2. /*
  3.  * Mode opératoire
  4.  * Choisir un paramètre de configuration en tournant le bouton de l'encodeur (9 cas possibles).
  5.  * Appuyer sur le bouton de l'encodeur pour passer en mode modification du paramètre sélectionné.
  6.  * Tourner le bouton de l'encodeur pour changer la valeur du paramètre.
  7.  * 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.
  8.  * Appuyer sur le bouton de l'encodeur pour terminer le mode modification du paramètre sélectionné.
  9.  * Une message Can de fin de configuration est envoyé au satellite
  10.  */
  11. void doConfig()
  12. {
  13. int u = lireEncodeur();
  14. if (u == 0) // pas de changement
  15. {
  16. SWenc.update();
  17. if ( SWenc.fell() )
  18. {
  19. gModeSWConfig = !gModeSWConfig; // inversion du mode CONFIG -> ENREGISTREMENT et retour MENU
  20. if (gModeSWConfig == false) // fin du mode CONFIG -> ENREGISTREMENT
  21. {
  22. lcd.setCursor(14, 1); // 2e ligne
  23. lcd.print(" "); // effacement
  24. switch (gnumConfig)
  25. {
  26. case 0: // Servo Max
  27. bufS[0]=0x80; // perma servo
  28. bufS[1]=1; // aig max
  29. bufS[2]=(byte)(sMax>>8);
  30. bufS[3]=(byte)(sMax);
  31. sendConfig(satConfig, 4);
  32. break;
  33. case 1: // Servo Min
  34. bufS[0]=0x80; // perma servo
  35. bufS[1]=0; // aig min
  36. bufS[2]=(byte)(sMin>>8);
  37. bufS[3]=(byte)(sMin);
  38. sendConfig(satConfig, 4);
  39. break;
  40. case 2: // Servo Vit
  41. bufS[0]=0x80; // perma servo
  42. bufS[1]=2; // aig vit
  43. bufS[2]=sVit.b[3];
  44. bufS[3]=sVit.b[2];
  45. bufS[4]=sVit.b[1];
  46. bufS[5]=sVit.b[0];
  47. sendConfig(satConfig, 6);
  48. break;
  49. case 3: // choix de la led
  50. break;
  51. case 4: // Led Max
  52. bufS[0]=0x81; // perma led
  53. sendConfig(satConfig, 3);
  54. break;
  55. case 5: // Led T. allume
  56. bufS[0]=0x81; // perma led
  57. sendConfig(satConfig, 3);
  58. break;
  59. case 6: // Led T. eteint
  60. bufS[0]=0x81; // perma led
  61. sendConfig(satConfig, 3);
  62. break;
  63. case 7: // Led T. On
  64. bufS[0]=0x81; // perma led
  65. sendConfig(satConfig, 3);
  66. break;
  67. case 8: // Led Period
  68. bufS[0]=0x81; // perma led
  69. sendConfig(satConfig, 3);
  70. break;
  71. }
  72. } else // passage en mode modif (gModeSWConfig = true) -> SELECTION du TYPE
  73. {
  74. lcd.setCursor(13, 1); // 2e ligne
  75. lcd.print('>');
  76. switch (gnumConfig)
  77. {
  78. case 0:
  79. lcd.print(sMax);
  80. break;
  81. case 1:
  82. lcd.print(sMin);
  83. break;
  84. case 2:
  85. lcd.print(sVit.f, 2);
  86. break;
  87. case 3: // choix de la led
  88. lcd.print(ledConfig);
  89. break;
  90. case 4:
  91. lcd.print(sLum); // Max
  92. break;
  93. case 5:
  94. lcd.print(sAlt); // T. allume
  95. break;
  96. case 6:
  97. lcd.print(sFad); // T. eteint
  98. break;
  99. case 7:
  100. lcd.print(sOnt); // T. on
  101. break;
  102. case 8:
  103. lcd.print(sPer); // Period
  104. break;
  105. case 9:
  106. gnumConfig = 0;
  107. initConfig(); // fin pour ce satellite
  108. break;
  109. }
  110. lcd.print(" ");
  111. }
  112. }
  113. return;
  114. }
  115. // cas de la rotation encodeur
  116. if (u > 0) valeurEncodeur++;
  117. if (u < 0) valeurEncodeur--;
  118. if (gModeSWConfig == false) // mode CHOIX CONFIG
  119. {
  120. gnumConfig = valeurEncodeur;
  121. if (gnumConfig < 0) {valeurEncodeur = gnumConfig = 0;}
  122. if (gnumConfig > 9) {valeurEncodeur = gnumConfig = 9;}
  123. lcd.setCursor(0, 1); // 2e ligne
  124. lcd.print(titreConfig[gnumConfig]);
  125. } else { // mode MODIFICATION
  126. lcd.setCursor(14, 1); // 2e ligne
  127. switch (gnumConfig)
  128. {
  129. case 0: // Servo Max
  130. if (u > 0) sMax++;
  131. if (u < 0) sMax--;
  132. lcd.print(sMax);lcd.print(" ");
  133. bufS[0]=0; // tempo servo
  134. bufS[1]=1; // a0 max
  135. bufS[2]=(byte)(sMax>>8);
  136. bufS[3]=(byte)(sMax);
  137. sendConfig(satConfig, 4);
  138. break;
  139.  
  140. case 1: // Servo Min
  141. if (u > 0) sMin++;
  142. if (u < 0) sMin--;
  143. lcd.print(sMin);lcd.print(" ");
  144. bufS[0]=0; // tempo servo
  145. bufS[1]=1; // a0 min
  146. bufS[2]=(byte)(sMin>>8);
  147. bufS[3]=(byte)(sMin);
  148. sendConfig(satConfig, 4);
  149. break;
  150.  
  151. case 2: // Servo Vit
  152. if (u > 0) {sVit.f = sVit.f + 0.1;}
  153. if (u < 0) {sVit.f = sVit.f - 0.1;}
  154. lcd.print(sVit.f, 2);lcd.print(" ");
  155. bufS[0]=0; // tempo servo
  156. bufS[1]=2; // a0 vitesse
  157. bufS[2]=sVit.b[3];
  158. bufS[3]=sVit.b[2];
  159. bufS[4]=sVit.b[1];
  160. bufS[5]=sVit.b[0];
  161. sendConfig(satConfig, 6);
  162. break;
  163.  
  164. case 3: // choix de la Led
  165. if ((u > 0) && (ledConfig < 9)) ledConfig++;
  166. if ((u < 0) && (ledConfig > 0)) ledConfig--;
  167. lcd.print(ledConfig);lcd.print(" ");
  168. break;
  169.  
  170. case 4: // Led Max luminosité
  171. if ((u > 0) && (sLum < 255)) sLum++;
  172. if ((u < 0) && (sLum > 0)) sLum--;
  173. lcd.print(sLum);lcd.print(" ");
  174. bufS[0]=1; // reglage led
  175. bufS[1]= (ledConfig << 3) + 0; // numero de led + intensité
  176. bufS[2]=sLum;
  177. sendConfig(satConfig, 3);
  178. break;
  179.  
  180. case 5: // Led allumage Time
  181. if ((u > 0) && (sAlt < 255)) sAlt++;
  182. if ((u < 0) && (sAlt > 0)) sAlt--;
  183. lcd.print(sAlt);lcd.print(" ");
  184. bufS[0]=1; // reglage led
  185. bufS[1]= (ledConfig << 3) + 1; // numero de led + intensité
  186. bufS[2]=sAlt;
  187. sendConfig(satConfig, 3);
  188. break;
  189.  
  190. case 6: // Led extinction Time
  191. if ((u > 0) && (sFad < 255)) sFad++;
  192. if ((u < 0) && (sFad > 0)) sFad--;
  193. lcd.print(sFad);lcd.print(" ");
  194. bufS[0]=1; // reglage led
  195. bufS[1]= (ledConfig << 3) + 2; // numero de led + intensité
  196. bufS[2]=sFad;
  197. sendConfig(satConfig, 3);
  198. break;
  199.  
  200. case 7: // Led On Time
  201. if ((u > 0) && (sOnt < 255)) sOnt++;
  202. if ((u < 0) && (sOnt > 0)) sOnt--;
  203. lcd.print(sOnt);lcd.print(" ");
  204. bufS[0]=1; // reglage led
  205. bufS[1]= (ledConfig << 3) + 3; // numero de led + intensité
  206. bufS[2]=sOnt;
  207. sendConfig(satConfig, 3);
  208. break;
  209.  
  210. case 8: // Led Period
  211. if ((u > 0) && (sPer < 255)) sPer++;
  212. if ((u < 0) && (sPer > 0)) sPer--;
  213. lcd.print(sPer);lcd.print(" ");
  214. bufS[0]=1; // reglage led
  215. bufS[1]= (ledConfig << 3) + 4; // numero de led + intensité
  216. bufS[2]=sPer;
  217. sendConfig(satConfig, 3);
  218. break;
  219.  
  220. case 9: // fin pour ce satellite
  221. break;
  222. }
  223. }
  224. }

L’interface utilisateur se présente comme suit :

Choix d'un satellite à configurer
Choix d’un satellite à configurer
Choix du paramètre "Servo Max"
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"
Valeur par défaut du paramètre "Servo Max"
Réglage du paramètre "Servo Max"
Réglage du paramètre "Servo Max"
Validation du paramètre "Servo Max"
Validation du paramètre "Servo Max"
Reglage de la vitesse du servo d'aiguille
Reglage de la vitesse du servo d’aiguille
Choix d'une des Leds du satellite
Choix d’une des Leds du satellite
Réglage luminosité maximum
Réglage luminosité maximum
Réglage de la période de clignotement
Réglage de la période de clignotement
Réglage du temps d'allumage (rise time)
Réglage du temps d’allumage (rise time)
Réglage du temps d'extinction (fall time)
Réglage du temps d’extinction (fall time)
Réglage de la durée du temps "allumé"
Réglage de la durée du temps "allumé"
Fin de configuration
Fin de configuration

Le programme complet peut être téléchargé ici :

Télécharger le programme complet

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").