Réalisation d’un wagon de mesure (distance et vitesse)

et transmission Bluetooth

. Par : Serge. URL : https://www.locoduino.org/spip.php?article259

Il est très utile de pouvoir mesurer la vitesse (à l’échelle) d’un convoi, pour étalonner vos locomotives, ainsi que des distances entre deux points de votre réseau (pour déterminer une distance de ralentissement par exemple). Cet article décrit la réalisation d’un wagon échelle HO chargé de mesurer, afficher et transmettre la distance parcourue et la vitesse de déplacement. Il décrit aussi la réalisation d’une application pour affichage des mesures sur un équipement Android (application développée sous Processing). Le cœur du système est un Arduino Nano et fonctionne aussi bien en analogique DC qu’en DCC.

JPEG - 56.6 kio

Description :

Le système est construit autour d’un Arduino Nano dont le rôle est de :

  • mesurer les tensions analogiques de sortie des capteurs linéaires à effet Hall
  • extraire la position angulaire de l’essieu
  • calculer la distance parcourue
  • dériver la distance parcourue pour obtenir la vitesse
  • mettre à l’échelle les valeurs obtenues en fonction du choix des unités d’affichage
  • afficher sur l’écran du wagon les résultats
  • transmettre ces résultats via une liaison série (connectée par Bluetooth)
  • recevoir et décoder des commandes reçues par la liaison série.

Une application sur smartphone (Android) est chargée de recevoir et d’afficher les données Bluetooth.

Geoff Bonza a déjà décrit une réalisation similaire dans la revue MRH incluant un capteur d’effort à jauges de contrainte et transmettant les données vers un récepteur spécifique.

La réalisation décrite ci-après a de moins grandes ambitions et se limite à la mesure de la distance parcourue et à l’évaluation de la vitesse. Cela simplifie les usinages mécaniques du wagon et la réalisation de pièces spécifiques. La réception se fait par Bluetooth vers smartphone ou tablette Android ou vers PC.

Principes de fonctionnement :

Côté matériel, les composants du système sont les suivants :

Figure 1 : synoptique matériel complet
Figure 1 : synoptique matériel complet

Choix du wagon :

Afin de simplifier le captage du courant et le câblage des capteurs, j’ai choisi d’utiliser un wagon de marchandise à 2 essieux fixes. Pour faciliter l’installation de l’électronique, j’ai voulu un toit démontable et j’ai opté pour des portes coulissantes pour pouvoir voir l’affichage des données.
Mon choix s’est porté sur un wagon de marchandise LIMA DB Hbis292 acheté d’occasion dont j’ai retiré le lest pour faciliter les perçages dans le plancher.

Alimentation électrique :

La tension des rails est récupérée par deux fois deux lames ressorts frottant sur les essieux du wagon montés tète bêche.

Figure 2 : implantation du captage du courant
Figure 2 : implantation du captage du courant

Après redressement et léger filtrage la tension rail alimente un régulateur de tension à découpage de type mini DC-DC réglé pour 10V DC.
Cette tension est utilisée pour charger deux batteries Lithium-Ion montées en série via un régulateur de charge.

Une diode avec une résistance en série sont installées à la sortie positive du régulateur de tension. La diode évite la décharge de la batterie dans le régulateur de tension quand le système est hors rails, et la résistance limite le courant de charge des batteries.
La tension de sortie des batteries est de 7,4 V, ce qui convient très bien pour alimenter l’Arduino Nano via la broche Vin. Leur capacité (200mAh) a été dictée par l’encombrement.

Capteur de position essieu :

Le capteur de position angulaire de l’essieu est composé de 2 capteurs à effet Hall analogiques fournissant une tension continue proportionnelle au champ magnétique, devant lesquels passent 4 aimants néodyme fer bore (h=1mm, Ø=2mm) collés sur l’essieu du wagon :

Figure 3 : schéma d'installation des capteurs
Figure 3 : schéma d’installation des capteurs

Les capteurs à effet Hall proportionnels que j’avais à ma disposition, sont des Allegro A1302 maintenant remplacés par des A1308 ou des AH3503 (AH electronics).
Les aimants sont collés sur l’essieu avec leurs pôles alternés N-S-N-S en laissant, à coté, de la place pour les lamelles de captage de courant. Pour repérer les pôles de chaque aimant, je les ai laissés s’attirer les uns les autres en une pile, et j’ai coloré au marqueur les faces supérieures de chacun des aimants de la pile.

Figure 4 : capteurs montés sur le wagon
Figure 4 : capteurs montés sur le wagon

Le champ magnétique généré par les aimants est de forme sensiblement sinusoïdale et tourne avec l’essieu tandis que les capteurs A et B sont fixes, solidaires du plancher du wagon.
Répartition du champ magnétique :

Figure 5 : répartition théorique du champ magnétique
Figure 5 : répartition théorique du champ magnétique

Les deux capteurs A et B sont décalés angulairement de + ou -90° pour fournir un sinus et un cosinus sur une période magnétique. Ici j’ai utilisé deux paires de pôles (4 aimants ==> 2 N et 2 S) et donc les signaux des capteurs auront une période d’un demi-tour d’essieu. Sur le dessin et dans la réalité les capteurs sont décalés de 270° électriques soit -90° modulo 360.

Si \alpha est la position angulaire de l’essieu, et si on considère que le capteur A fournit cos(2\alpha), alors le capteur B fournit cos(2\alpha-270), soit – sin(2\alpha), soit sin(-2\alpha). Comme cos(2\alpha) = cos(-2\alpha), on peut considérer que le capteur A mesure cos(-2\alpha) et le capteur B mesure sin(-2\alpha). Par un calcul arc tangente on obtiendra –2\alpha, soit au signe près la position angulaire de l’essieu à l’intérieur du demi-tour. L’inversion de signe est due au décalage de 270° des capteurs au lieu de 90°

La tension en sortie des capteurs est presque sinusoïdale et présente deux périodes sur un tour d’essieu :

Figure 6 : tensions théoriques en sortie des capteurs
Figure 6 : tensions théoriques en sortie des capteurs

Il s’agit là de valeurs théoriques.
Les aimants n’ont pas tous les quatre les mêmes caractéristiques, et ne passent pas forcément à la même distance des capteurs entraînant des amplitudes de sinusoïdes différentes, et donc des erreurs de mesure de position. La mesure est aussi sensible au positionnement des capteurs, l’un par rapport à l’autre. Il faut trouver une position telle que les deux signaux soient en quadrature de phase (déphasés de ±90°) et de mêmes amplitudes.
Dans mon cas, voici les mesures effectuées en utilisant le lien série de l’Arduino et l’enregistrement des valeurs sur le PC de développement.
J’ai fait tourner l’essieu du wagon à vitesse constante à l’aide de la roue d’une motrice et j’ai obtenu ces courbes :

Figure 7 : mesure des tensions réelles et extraction de la position essieu
Figure 7 : mesure des tensions réelles et extraction de la position essieu

Après 0,05 s, on constate sur ces courbes que les tensions des deux capteurs n’ont pas la même amplitude. Ces différences entraînent des ondulations sur la courbe de position qui aurait du être une droite. Si l’on considère que le mouvement, après démarrage ( 0,05 s ), se fait à vitesse constante, il est possible de quantifier ces erreurs de mesure en utilisant la meilleure droite (régression linéaire) passant par les mesures au-delà de 0,05 s.

Figure 8 : Estimation de l'erreur de mesure (droite des moindres carrés)
Figure 8 : Estimation de l’erreur de mesure (droite des moindres carrés)

L’écart maximal entre les mesures et la droite est de l’ordre de 10°. Compte tenu d’un rayon de roue de 11mm, l’erreur de mesure de la position sera de 11 . \pi. 10 / 360 ≈ 1 mm. Cette valeur est acceptable compte tenu de l’utilisation de l’application.
L’erreur de position étant systématique car due aux imprécisions de positionnement des capteurs, il est possible d’améliorer la précision en utilisant une table de calibration.

Calcul de la distance parcourue :

Si \alpha est l’angle de l’essieu, comme il y a 2 paires de pôles (4 aimants), les capteurs de position donnent comme tension de sortie : VA= 2,5 + 2,5*cos(-2\alpha) VB=2,5 + 2,5*sin(-2\alpha).
Pour le calcul de l’angle on utilise la fonction atan2(y,x) qui fournit l’angle correspondant aux deux coordonnées x et y obtenues et qui gère les signes des arguments, contrairement à la fonction atan. Donc \alpha = atan2((VB- 2,5),(VA- 2,5))/2. La fonction atan2 renvoyant un angle entre -\pi et +\pi, l’angle \alpha sera compris entre -90° et +90°. Dans le cas de l’exemple précédent, on obtient la valeur suivante de \alpha :

Figure 9 : Résultat de l'arc-tangente
Figure 9 : Résultat de l’arc-tangente

Lorsque la valeur de l’angle essieu calculée atteint -90°, elle passe à +90°. Pour obtenir l’angle essieu cumulé, il faut retrancher 180° chaque fois que l’on détecte une différence > 90° entre deux points successifs et ajouter 180° si la différence entre deux points successifs est < -90°. Cela revient à compter le nombre de demi-tours réalisés par l’essieu. On obtient alors la courbe présentée Figure 7.
Connaissant l’angle essieu cumulé en radians, il suffit de le multiplier par le rayon de roue pour obtenir la distance parcourue depuis la dernière remise à zéro. Comme l’angle d’essieu est positif ou négatif, le système gère le sens de marche du wagon, et décompte les marches arrières.

Calcul de la vitesse :

A partir de la distance parcourue, la vitesse s’obtient par dérivation. Si Dn est la distance actuelle, et Dn-1 la distance calculée au pas précédent, la vitesse V peut se calculer par V=(Dn- Dn-1)/(tn-tn-1). Ici tn-tn-1 vaut 0,002 s. La différence est très sensible aux erreurs de mesure et il convient de filtrer le résultat.

Pour calculer une vitesse filtrée, j’ai implanté le calcul en temps réel d’un filtre du 2nd ordre constitué d’un filtre du 1er ordre rebouclé via un gain et un intégrateur. Le diagramme de la Figure 10 représente un synoptique du filtre et les équations d’état correspondantes. On retrouve les deux variables d’état X1 et X2 et leurs équations dans la routine interruption() cadencée à 2 ms.

Figure 10 : Filtre 2nd ordre et équations d'état
Figure 10 : Filtre 2nd ordre et équations d’état

Le filtre du 2nd ordre ayant un gain de 1, la variable X2 est l’image filtrée de l’entrée E. La sortie S étant prise en amont de l’intégrateur de X2, S sera l’image de la dérivée de X2 qui est l’image de l’entrée filtrée, S est donc la dérivée filtrée de l’entrée E.

Processeur et périphériques :

Le processeur utilisé est un Arduino Nano qui pilote :

  • un écran SSD1306 pour afficher les données mesurées
  • un module Bluetooth HC06 pour transmettre, sans fil, les mesures via la liaison série vers un équipement portable.
  • les LEDs des feux de fin de convoi.
    J’ai tenté d’utiliser une émulation logicielle de la liaison série (soft serial) pour communiquer avec le module Bluetooth HC06 et conserver la liaison série pour le débug avec le PC. Malheureusement, l’émulation logicielle de la liaison série consomme du temps CPU, alors que le périphérique série (UART) travaille en parallèle à la CPU sans consommer de temps CPU. Pour garder de la marge, j’ai fini par utiliser la liaison série standard du processeur connectée au module HC06. Mais, comme cette liaison sert également au téléchargement du logiciel, pour ne pas perturber le téléchargement, il faut déconnecter le module HC06 pendant le télé-versement du code dans le microcontrôleur.

Implantation de l’afficheur :

Afin de diminuer le nombre de fils de câblage, j’ai utilisé un afficheur OLED avec un bus I2C. Voulant avoir des caractères suffisamment lisibles, j’ai choisi un écran de de 0,96ˮ de type SSD1306.

Figure 11 : Afficheur I2C OLED 0,96" SSD1306
Figure 11 : Afficheur I2C OLED 0,96" SSD1306

La hauteur utile de l’afficheur correspond assez bien à la hauteur du wagon, mais son encombrement mécanique total m’a obligé à fraiser le plancher du wagon pour loger la partie inférieure de l’afficheur. On aperçoit l’ouverture du plancher du wagon sur la figure 4.

Module Bluetooth HC06 :

Figure 12 : Connexion module Bluetooth HC06
Figure 12 : Connexion module Bluetooth HC06

La liaison série de l’Arduino est reliée à un module Bluetooth de type HC06. Le module HC06 est paramétrable (nom, vitesse …) via des commandes AT (voir les nombreux tutoriels).

Attention, les données échangées par le HC06 sont en 3,3V alors que l’Arduino est en 5V. Il faut prévoir un pont diviseur entre la sortie Tx de l’Arduino et l’entrée Rx du HC06. Par contre, le HC06 accepte une alimentation de 3,3V à 6V.

Feux de fin de convoi :

Figure 13 : Implantation des feux de fin de convoi
Figure 13 : Implantation des feux de fin de convoi

Deux LEDs rouges de 3 mm sont collées dans des perçages à l’arrière du wagon. Deux petits manchons de laiton de 3 mm intérieur viennent compléter la décoration. Les LEDs sont alimentées par une sortie de l’Arduino et commandées par la liaison série.

Implantation globale :

L’ensemble des modules tient facilement à l’intérieur du wagon. Afin de minimiser un peu le nombre de fils, l’Arduino est monté sur une carte prototype pastillée, sur laquelle viennent se raccorder l’alimentation, les 4 fils des capteurs, le bus I2C vers l’afficheur, le module Bluetooth HC06 et les diodes des feux de fin de convoi. Pour la programmation de l’Arduino, le module support se soulève et donne accès au connecteur mini-USB, en n’oubliant pas de déconnecter le module HC06.

Figure 14 : Implantation des éléments dans le wagon (1/2)
Figure 14 : Implantation des éléments dans le wagon (1/2)
Figure 15 : Implantation des éléments dans le wagon (2/2)
Figure 15 : Implantation des éléments dans le wagon (2/2)

Schéma de câblage :

Figure 16 : Schéma de câblage du wagon
Figure 16 : Schéma de câblage du wagon

Logiciel Arduino :

Utilisation d’une maquette de mise au point :

Pour la mise au point du logiciel, sur table, j’ai utilisé un Arduino Uno sur lequel était installé un shield de développement équipé d’une mini bread board, le tout connecté au PC via la liaison série :

Figure 17 : Maquette de développement à base d'Arduino Uno
Figure 17 : Maquette de développement à base d’Arduino Uno

Les capteurs du wagon sont connectés aux 2 entrées analogiques de l’Arduino par des fils de circonstance. En utilisant le terminal intégré à l’IDE Arduino, il est facile de valider les trames de données sur la liaison série.
Sur la photo (Figure 16), on remarquera le fil orange débranché, ce qui déconnecte le signal TX du module HC06 du RX de l’Arduino, afin de ne pas perturber la connexion série vers le PC. On notera également le pont diviseur d’une valeur de 2/3 (résistances de 3,6 kOhm et 6,8 kOhm) pour connecter le TX de l’Arduino (en 5V) au RX du module HC06 (en 3,3V).
La maquette permet également de valider les échanges Bluetooth en reconnectant le HC06 une fois le logiciel téléchargé dans l’Arduino.

Architecture logicielle :

Pour faciliter la gestion du temps réel, la tache de calcul est gérée par une interruption récurrente toutes les 2 ms. Pour simplifier la configuration des interruptions, j’ai utilisé la bibliothèque ’FlexiTimer2’ qui propose toutes les routines nécessaires.

Figure 18 : Architecture logicielle pour l'Arduino
Figure 18 : Architecture logicielle pour l’Arduino

Déclarations et initialisations :
Les instructions #include permettent d’ajouter les fichiers des déclarations des bibliothèques. Les bibliothèques utilisées sont :

  • FlexiTimer2 gère la configuration des registres pour la gestion des tâches répétitives sous interruption timer.
  • SPI requise pour la bibliothèque graphique Adafruit qui gère l’afficheur. (bibliothèque intégrée à l’IDE Arduino)
  • Wire bibliothèque I2C pour communiquer avec l’afficheur. (bibliothèque intégrée à l’IDE Arduino : voir article Bibliothèque Wire : I2C)
  • Adafruit_GFX bibliothèque générale graphique Adafruit pour écrire sur l’afficheur.
  • Adafruit_SSD1306 bibliothèque spécifique à l’afficheur utilisé.

Les instructions #define définissent des mnémoniques pour divers paramètres utilisés dans le programme.
Un objet de la classe Adafruit_SSD1306 s’appelant display est instancié en variable globale pour manipuler l’afficheur.
Diverses variables globales sont créées pour permettre les échanges de données entre les diverses routines du programme. Il s’agit principalement de variables pour communiquer avec la routine d’interruption.

// Programme pour wagon odometre,tachymetre 
//
// V1.0 	S.G.	création du logiciel
// 		Calcul distance, vitesse
// 		Affichage OLED, serial BlueTooth
//        r reset distance et vitesse
//        a allume led feux arrieres
//        e eteint led feux arrieres
//        u changement d'unite pour l'affichage
// 		Clignotement led temoin de presence d'IT
//
#include <FlexiTimer2.h>      // configuration automatisee des IT du timer 2
                
// Declarations pour affichage OLED SSD1306
#include <SPI.h>        // utilisee par la bibliotheque Adafruit pilotant l'afficheur
#include <Wire.h>             // bibliotheque I2C
#include <Adafruit_GFX.h>     // bibliotheque graphique Adafruit
#include <Adafruit_SSD1306.h> // bibliotheque afficheur
// Declarations limitees car l'afficheur OLED SSD1306 est en I2C (SDA, SCL pins)
#define SCREEN_WIDTH 128      // OLED largeur de l'afficheur en pixels
#define SCREEN_HEIGHT 64      // OLED hauteur de l'afficheur en pixels
#define OLED_RESET     -1     // n° broche reset afficheur (-1 si reset commun arduino)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); 
                                                                                 // instanciation de l'afficheur

#define PIN_X 0               // analog pin pour mesurer x (0 à 1023 pour 0 à 5V)
#define PIN_Y 1               // analog pin pour mesurer y (0 à 1023 pour 0 à 5V)
#define X_OFFSET 512          // valeur du 0 pour le capteur x  (2,5 V)
#define Y_OFFSET 512          // valeur du 0 pour le capteur y  (2,5 V)

const double _2PI = 2 * 3.1415926535;   // 2 Pi pour les calculs en radians
const double CSTE_ROUE = (11./4.);      // (mm/rad) 
                     // diamètre roue/2 -> rayon  puis /2 car 2 paires d'aimants par tour de roue
const double _echantil = 0.002;    // periode d'echantillonnage pour le filtre
const double _Wn = 2.*3.1415926525 * 3.;  // rad/s   ici Fc = 3Hz pour le filtre de calcul de la vitesse
const double _2Ksi = 2. * 1.;     // ksi =1  (filtre super amorti).
const double COEF_DIST=87./1000.;     // passage de mm à km avec echelle HO
const double COEF_VIT=3.6/1000.*87.; // passage de mm/s à km/h avec echelle HO

// les variables suivantes sont globales car l'interruption en a besoin a chaque calcul
double X1 = 0, X2 = 0;   // variables d'etat pour le filtre de calcul de la vitesse
bool init_dist = true;       // remet à 0 la distance affichée quand vrai
double offset_dist = 0.;    // decalage de zero appliqué a la distance affichée (raz)
double coef_dist = 1.;      // coef pour le changement d'unite de distance
double coef_vit = 1.;       // coef pour le changement d'unite de vitesse
float angle_1 = 0;          // sauvegarde du calcul d'angle precedent pour detection des passages +/- pi
float tours = 0;              // nombre de tours deja faits pour calcul de la distance 
double distance;            // distance parcourue (accessible partout)
float vitesse;                 // vitesse apres filtrage (accessible partout)
int compteur=0;            // compte les IT, fait clignoter la led témoin de "vie" du soft et des IT
                                   // repasse à zero tout seul au bout de 65536 IT (131s)
										
// variables globales pour générer les trames à envoyer
char  dist[11]="        mm";  // chaines utilisées pour formater l'affichage
char   vit[11]="      mm/s";  // 10 caracteres en taille 2 + le caractere de fin '\0'
int  unit_mm = 0;               // memorise la selection du type d'unites

// donnees globales pour la gestion de la periode d'envoi
long aff_date;                    // date du dernier raffraichissement de l'ecran OLED
#define AFF_UPDATE  100  // periode de rafraichissement de l'affichage en ms

#define led_pin 13             // led à faire clignoter (temoin de "vie" des IT)
#define lanterne_pin 5       // feux arrière du wagon
#define test_IT_pin 6        // sortie digitale pour chronometrer les IT avec un scope
#define test_Form_pin 7    // sortie digitale pour chronometrer le formatage
#define test_Com_pin 8     // sortie digitale pour chronometrer l'affichage
#define test_Aff_pin 9       // sortie digitale pour chronometrer la liaison PC

// variables intermédiaires du calcul declarees globales pour pouvoir les utiliser en debug via la liaison série
int x, y;                            // mesures lues (cos et sin)
double angle;                   // angle magnetique de l'essieu (2 paires de poles)
double dx1_dt, dx2_dt;     // variables intermédiaires de calcul de la vitesse

setup() :
La routine setup() réalise toutes les initialisations de la liaison série (vitesse de transmission), de l’afficheur (type et adresse I2C), des interruptions (nom de la routine d’interruption, période d’interruptions, autorisation des interruptions) et des sens de fonctionnement des broches de l’Arduino.

void setup()  {
  Serial.begin(9600);      // initialize serial connection to PC
  while (!Serial) { ; }     // attente de connexion du port serie USB
  
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
                    // Initialisation de l'objet afficheur a faire en premier car il a
                    // besoin de memoire contigue pour l'allocation dynamique
                    // du buffer d'affichage de 1024 octets
    Serial.println(F("SSD1306 allocation failed"));   // Init ratee
    for (;;);                    // on s'arrete, boucle infinie!
  }

  // affichages varies
  display.display();  // affichage du logo de la librairie (splash.h) mis dans le buffer par l'init
  delay(2000);                          // pause de 2 seconde pour admirer le logo ;-)
  display.clearDisplay();               // effacement du buffer de l'afficheur
  display.setTextSize(2);               // taille 2 pour message lisible
  display.setTextWrap(true);            // autorise le retour à la ligne automatique
  display.setTextColor(WHITE);          // dessine le texte en blanc
  display.setCursor(0, 0);              // ecriture a partir du coin en haut à gauche
  display.println(F("Setup OK!" ));     // message de bienvenue sur l'ecran
  display.display();                    // affichage du buffer
  Serial.println(F("Setup OK" ));       // message de bienvenue sur la com serie
  delay(1000);                          // pause pour voir le message
  display.clearDisplay();               // effacement du buffer
  display.drawPixel(0, 0, WHITE);       // affiche un pixel en haut à gauche pour ne pas laisser l'ecran vide (aide au debug)
  display.display();                    // affichage du buffer
  display.clearDisplay();               // efface le buffer pour le trace suivant
  display.setTextSize(1);               // remplit le buffer 
  display.setCursor(0, 0);              // en haut à gauche
  display.print(F("distance"));
  display.setCursor(0, 32);             // au milieu à gauche
  display.print(F("vitesse"));
  display.setTextSize(2);               // gros texte pour les valeurs mesurées

  aff_date = millis();                  // init date de ref. pour les affichages OLED, PC

  // initialisation interruptions cadencees
  FlexiTimer2::set(2, 0.001, interruption);    // un tic = 2*1ms pour lancer interruption()
  FlexiTimer2::start();                 // on demarre les IT

  // initialisation des broches de sortie
  pinMode(led_pin, OUTPUT);             // led clignotante
  pinMode(lanterne_pin, OUTPUT);        // led feu arriere wagon
  pinMode(test_IT_pin, OUTPUT);         // sortie chronometrage IT
  pinMode(test_Form_pin, OUTPUT);       // sortie chronometrage Formatage
  pinMode(test_Aff_pin, OUTPUT);        // sortie chronometrage Affichage
  pinMode(test_Com_pin, OUTPUT);        // sortie chronometrage Communication serie
}

loop() :
C’est la routine principale, appelée automatiquement en boucle par le système d’exploitation de l’Arduino. Ici elle se contente de vérifier que le temps écoulé depuis le dernier envoi et affichage est inférieur au temps de répétition (100 ms). Si ce n’est pas le cas, les routines envoi() et affichage() sont de nouveau appelées.

void loop() {  // la seule action de loop est de cadencer l'affichage des resultats
  if (millis() > aff_date)  {           // il est temps d'afficher
    aff_date += AFF_UPDATE;             // date de la prochaine action
    mise_en_forme();                    // mise en forme
    envoi();	                        // envoi sur la liaison serie
    affichage();                        // affichage 
  }
}

serialEvent() :
Cette routine est appelée par le système d’exploitation de l’Arduino entre chaque appel à la routine loop() lorsque au moins un caractère a été reçu par la liaison série.
Ici, lorsqu’elle est appelée, on commence par vérifier qu’il y a bien au moins un caractère dans le buffer de réception. Puis, après lecture du premier caractère du buffer, on exécute la commande correspondant à ce caractère. S’il y a d’autres caractères dans le buffer, ils seront lus au prochain appel de serialEvent().
Les commandes acceptées sont :

  • r pour remise à zéro de l’odomètre. Un flag est positionné à 1, et la remise à zéro sera effectuée par le prochain passage dans la routine interruption().
  • a allumage des feux de fin de convoi. La sortie logique de l’Arduino correspondante est positionnée à 1.
  • e extinction des feux de fin de convoi. La sortie logique de l’Arduino correspondante est positionnée à 0.
  • u changement d’unité d’affichage. Le choix des unités se fait par une liste cyclique de trois combinaisons : mm et mm/s, ou mm et km/h ramenés de l’échelle HO à l’échelle 1, ou m et km/h à l’échelle 1. Le changement d’unité se fait en changeant les coefficients pour calculer la valeur à afficher et en corrigeant le nom de l’unité en fin des chaines de caractères destinées à l’afficheur.
void serialEvent() {     // fonction appelee par le systeme si il y a quelquechose de recu sur
                         // la liaison serie (ici on ne fait que quelques commutations de flags).
  char c;                        // caractere intermediaire recu
  
  if (Serial.available()>0)      // y a-t-il qquechose recue venant de la liaison serie
        c=Serial.read();         // lecture du 1er caractere du buffer de reception
  switch(c) {                    // aiguillage en fonction du caractere recu
    case 'r' :                   // raz distance a la prochaine IT
      init_dist = true;                 
      break;
    case 'A' :                   // allumage feu arriere
    case 'a' :
      digitalWrite(lanterne_pin, HIGH); 
      break;
    case 'e' :                   // extinction feu arriere
      digitalWrite(lanterne_pin, LOW);
      break;
    case 'u' :                   // changement d'unite d'affichage.
                                 // les calculs sont toujours en mm et mm/s  
      switch (unit_mm++) {  // phases du cycle des unites d'affichage avec incrementation
        case 0:                         // on passe de mm/s a km/h on reste en m
          coef_vit = COEF_VIT;          // mise à jour du coef d'affichage de la vitesse
          vit[6]='k';            // on remplace le m et le s de 'mm/s' pour obtenir 'km/h'
          vit[9]='h';
          break;
        case 1:                  // on passe de mm a m
          coef_dist=COEF_DIST;   // mise à jour du coef d'affichage de la distance
          dist[9]=' ';           // on efface un m pour passer de 'mm' a 'm '
          break;
        case 2:                  // on revient aux unites de calcul
          coef_dist = 1.;        // les coefs reviennent à 1.
          coef_vit = 1.;
          dist[9]='m';           // et les chaines de caracteres sont retablies            
          vit[6]='m';
          vit[9]='s';
          break;                
       }
      unit_mm%=3;         // seulement 3 cas d'affichage (si unit_mm vaut 3 on revient a 0)
      //     init_dist = true;    // raz distance a la prochaine IT (desactivee)  
      break;   
  }   // fin du swich(c) 
}   // fin de serialEvent

interruption() :
Suite à l’initialisation des registres du processeur par setup() via la bibliothèque FlexiTimer2, cette routine est appelée toutes les 2 ms. Les traitements réalisés sont :

  • lecture des mesures des capteurs à effet Hall et suppression de l’offset.
  • calcul de l’angle de l’essieu par la fonction mathématique atan2().
  • calcul du nombre total de demi-tours de l’essieu en incrémentant ou décrémentant le compteur de demi-tours en fonction de la différence de la position angulaire de l’essieu entre deux calculs.
  • calcul de la distance parcourue en mm par multiplication de l’angle total (1/2 angle essieu + nombre de demi-tours) par le rayon de la roue.
  • calcul de la vitesse par implémentation des équations d’état du 2nd ordre.
  • clignotement de la LED embarquée sur la carte Arduino pour indiquer que la routine d’interruption est toujours appelée : un compteur est incrémenté à chaque appel de la routine interruption(). Le bit 8 du compteur est utilisé pour piloter la broche de sortie liée à la LED. Ce bit change d’état tous les 256 appels de la routine interruption(). La LED clignote donc à une période de 256*2*2ms = 1,024 s soit presque 1Hz.
void interruption()  {
    digitalWrite(test_IT_pin, HIGH); // positionnement sortie de chronometrage interruption
// mesure de l'angle "magnetique" = angle_essieu * 2 (car 2 paires de poles)
    x=analogRead(PIN_X)-X_OFFSET; // mesure cos et sin de l'angle magnetique
    y=analogRead(PIN_Y)-Y_OFFSET; // peut importe le gradian de mesure, tant qu'il est identique sur X et Y
    angle = atan2(y, x);           // extraction de l'angle (calcul ratiometrique)
// decompte du nombre de 1/2 tours essieu ou de tours magnétiques
    if ((angle - angle_1) > 4 ) tours -= _2PI;    // l'angle vient de faire un saut a -Pi
    if ((angle - angle_1) < -4 ) tours += _2PI;   // l'angle vient de faire un saut a +Pi
    angle_1 = angle;                              // memorisation de l'angle pour IT suivante
    distance = (angle + tours) * CSTE_ROUE; // distance = angle * Rayon / nbre paires de poles
    if (init_dist) {                              // raz distance
      X1 = 0; X2 = 0;                             // raz variables d'etat du filtre
      tours=0;                                    // raz nbre de tours
      offset_dist = angle_1*CSTE_ROUE;  // reglage de l'offset pour avoir distance = 0
      init_dist = false;                          // raz flag pour ne pas repasser ici
    }
    distance -= offset_dist;   // compensation de la position initiale de l'essieu

// filtre derivee (2eme ordre)
    dx1_dt = (distance - X2 - _2Ksi * X1) * _Wn;  // equation d'evolution de l'etat X1
    dx2_dt = X1 * _Wn;                            // equation d'evolution de l'etat X2
    X1 += dx1_dt * _echantil;                     // integration X1
    X2 += dx2_dt * _echantil;                     // integration X2
    vitesse = dx2_dt;                   // X2 recopie la distance à travers le filtre
                                        //     ==> sa derivee est la vitesse filtree
// pour faire clignoter la led temoin de "vie" du systeme                                                                                                                  
    digitalWrite(led_pin, !(compteur++ & 0x100)); 
                                  // le bit 2^9 du compteur change toutes les 256 IT 
                                  // soit 256*2ms = 512ms la led clignote a 1,024 Hz
    digitalWrite(test_IT_pin, LOW);  // positionnement sortie de chronometrage de l'IT
}

mise_en_forme() :
Cette routine appelée périodiquement par la routine loop() (période en ms définie par le paramètre AFF_UPDATE) est chargée de la mise en forme des chaines de caractères envoyées à l’afficheur via le bus I2C et envoyées aussi sur la liaison série. Les traitements réalisés sont :

  • extraction du signe et des digits en base 10 de la variable distance de type flottant double et remplissage caractère par caractère de la chaîne de caractères dist, avec gestion de dépassement de capacité de l’afficheur.
  • même opération pour la variable vitesse de type flottant vers la chaîne de caractères vit.
void mise_en_forme() {// mise en forme des resultats dans dist et vit
  int i;                  // indice pour les boucles
  long data;              // pour stocker la valeur a afficher
  char *p;                // pointeur sur la chaine resultat du formatage
  char c;                 // caractere intermediaire (recu ou calcule pour le formatage)
digitalWrite(test_Form_pin, HIGH); // mise a 1 de la sortie de chronometrage du formatage
  // on commence par la distance
     // on remplit dist avec les 7 chiffres, sans toucher au nom de l'unite à la fin de dist
        data = long(distance*coef_dist + 0.5);    // arrondi à l'unite
        if (data<0) {                   // si la valeur est négative :
          c='-';                        //      - on stocke le caractere du signe 
          data=-data;                   //      - on travaille sur la valeur absolue
        }
        else c=' ';                     // sinon, le caractere de signe est ' '
        p=dist;                         // p pointe sur la chaine resultat du format
        for (i=0;i<7;i++) *p++=' ';     // effacement, a la fin p pointe sur la fin de dist
        for (i=0;i<7;i++) {   // on remplit la chaine à partir de la fin (il y a toujours au moins 1 caractere)
          *p--=(data%10)+48;   // avec le reste de la division par 10 + 48 pour obtenir des caracteres ASCII
          data=data/10;                 // et on se prepare pour le chiffre suivant
          if (data==0) {                // il n'y a plus de chiffres a afficher
            *p=c;                       // on place le signe
            break;              // et on sort de la boucle en laissant les espaces avant le signe
          }
        }
        if (data!=0) { // data etait superieur à 10^7 (il reste qquechose apres 7 divisions par 10)
          p=dist;                       // il y a debordement :
          *p++=c;                       // on garde le signe
          *p++=' ';
          for (i=0;i<4;i++) *p++='X';   // et on met quelques X (4)
          *p++=' ';
          *p++=' ';
        }    
  // idem pour la vitesse (2 digits en moins)
        // on remplit la chaine vit avec les 5 chiffres, sans toucher au nom de l'unite deja à la fin de vit
        data = long(vitesse * coef_vit + 0.5);     // la valeur de la vitesse est arrondie à l'unite
        if (data<0) {                   // si la valeur est négative :
          c='-';                        //      - on prepare le caractere du signe 
          data=-data;                   //      - on travaille sur la valeur absolue
        }
        else c=' ';                     // sinon, le caractere de signe est ' '
        p=vit;                          // p pointe sur le debut de la chaine resultat du format
        for (i=0;i<5;i++) *p++=' ';     // effacement de toute la chaine, a la fin p pointe sur la fin de 
        for (i=0;i<5;i++) {             // on remplit la chaine à partir de la fin
          *p--=(data%10)+48;            // avec le reste de la division par 10 + 48 pour obtenir des caracteres
          data=data/10;                 // et on se prepare pour le chiffre suivant
          if (data==0) {                // il n'y a plus de chiffres a afficher
            *p=c;                       // on place le signe
            break;                      // et on sort de la boucle
          }
        }
        if (data!=0) {                  // data etait superieur à 10^5 (il reste qquechose apres 5 divisions par 10)
          p=vit;                        // il y a debordement :
          *p++=c;                       // on garde le signe
          *p++=' ';
          for (i=0;i<4;i++) *p++='X';   // et on met quelques X
          *p++=' ';
        }
digitalWrite(test_Form_pin, LOW);   // mise à 0 de la sortie de chronometrage de la mise en forme
}  // fin de mise_en_forme()

affichage() :

  • effacement des nombres précédemment affichés par dessin d’un rectangle plein, noir et envoi à l’afficheur des 2 nouvelles chaines à afficher.
  • à la fin des deux chaines de caractères dist et vit, on trouve les noms des unités qui ne sont pas effacés si ce n’est lors d’un changement d’unité.
void affichage() {    // pilotage de l'afficheur AFF_OLED:

digitalWrite(test_Aff_pin, HIGH);   // mise à 1 de la sortie de chronometrage du pilotage de l'afficheur
        // toutes les actions se font dans le buffer avant de declencher l'affichage du buffer
        display.fillRect(0, 10, 128, 20,BLACK);   // on efface la valeur precedente dist par un rectangle noir 
        display.fillRect(0, 42, 128, 20,BLACK);   // on efface la valeur precedente vit par un rectangle noir
        display.setCursor(0, 10);       // on se positionne début 2eme ligne
        display.print(dist);            // on ecrit la valeur dans le buffer
        display.setCursor(0, 42);       // on se positionne début 4eme ligne
        display.print(vit);             // on ecrit la valeur dans le buffer
        display.display();              // affichage du buffer
digitalWrite(test_Aff_pin, LOW);   // mise à 0 de la sortie de chronometrage du pilotage de l'afficheur
}

envoi() :
Cette routine transmet les deux chaines de caractères dist et vit séparées, par une virgule, via la liaison série vers le PC via l’USB ou vers le module Bluetooth HC06. La routine envoi() peut également servir en debug pour faire afficher un état ou une variable du logiciel.

void envoi() {                     // communication via la liaison serie
// COMM_Serie:                          
digitalWrite(test_Com_pin, HIGH);  // mise à 1 de la sortie de chronometrage du pilotage de la liaison serie
        Serial.write(dist,10);          // envoi des 10 caracteres de dist  (write() est plus rapide que print)
        Serial.write(",",1);            // envoi du separateur 
        Serial.write(vit,10);           // envoi des 10 caracteres de vit
        Serial.write(10);               // envoi de NewLine  \n
digitalWrite(test_Com_pin, LOW);  // mise à 0 de la sortie de chronometrage du pilotage de la liaison serie
}

Vérification du timing :

Pour s’assurer qu’il y a une marge sur le temps d’occupation de la CPU par les différentes tâches du logiciel, j’ai utilisé une sortie logique de l’Arduino par tâche. La sortie était mise à 1 en entrée de tache et remise à 0 en sortie. Le basculement de la sortie consomme peu de temps (de l’ordre de la µs) et n’a pas d’influence notoire sur les mesures de timing qui sont de l’ordre de grandeur de la milliseconde.
Dans le logiciel, les sorties logiques suivantes sont affectées aux tâches suivantes :

  • D6 routine interruption()
  • D7 routine mise en forme() des chaines de caractères de la trame à envoyer
  • D8 routine envoi() : remplissage du buffer pour envoi par l’UART
  • D9 routine afficheur() : écriture du buffer de l’afficheur via la bibliothèque graphique

Chacune de ces sorties étant reliée à une voie de l’oscilloscope, on obtient les résultats suivants :

Figure 19 : durée et période de récurrence des interruptions
Figure 19 : durée et période de récurrence des interruptions

La routine d’interruption (lecture des capteurs et calculs) s’exécute bien toutes les 2 ms et dure 0,55 ms (D6). Elle consomme 25% du temps CPU.

Figure 20 : Durée des taches
Figure 20 : Durée des taches

La mise en forme des chaines de caractères à partir des valeurs numériques de la distance et la vitesse prend 0,13 ms (D7). Le remplissage des buffers de la liaison série prend 0,16 ms (D8).

Figure 21 : Influence d'une interruption sur la tache de mise en forme
Figure 21 : Influence d’une interruption sur la tache de mise en forme

Lorsque une interruption tombe pendant l’exécution de la tache de mise en forme, celle-ci dure 0,68 ms.

Figure 22 : Chronogramme global des taches du logiciel Arduino
Figure 22 : Chronogramme global des taches du logiciel Arduino

Tandis que les interruptions tombent toutes les 2 ms (D6), toutes les 100 ms les chaines de caractères sont mises en forme (D7), puis le buffer de la liaison série est rempli (D8), puis les écritures sur l’affichage sont effectuées (D9). L’envoi des trames (Tx), déclenché par l’écriture du buffer, se fait en parallèle avec l’écriture sur l’afficheur grâce à l’UART qui les émet en temps masqué. L’envoi dure 23 ms.
L’écriture dans les buffers de l’affichage prend 26,5 ms dans le timing. La durée réelle n’est que de 19,5 ms en temps CPU, mais l’écriture est interrompue 14 fois par l’interruption de calcul.

Figure 23 : Enchaînement des taches
Figure 23 : Enchaînement des taches

Sur ce zoom, le cadencement des taches de l’Arduino est mis en évidence.
1 - D7 mise_en_forme()
2 - D8 envoi()
3 - D9 affichage()
La liaison série (Tx) est au repos à l’état 1 et commence à émettre dès que le premier octet est écrit dans le buffer.

Ces mesures indiquent que c’est la gestion du buffer de l’afficheur qui prend le plus de temps. Le programme n’utilise environ que 50% du temps CPU. On pourrait diminuer la période d’envoi et d’affichage des trames ou ajouter des capteurs et traitements supplémentaires.

Réception des données

Terminal Bluetooth :
Dans un premier temps, la réception des données Bluetooth peut se faire sur un smartphone Android en utilisant une application de terminal Bluetooth telle que celle-ci qui va se comporter comme le terminal de l’IDE Arduino.

Figure 24 : Réception des données sur Android Terminal Bluetooth
Figure 24 : Réception des données sur Android Terminal Bluetooth

Une fois l’appareil Android appairé avec le module HC-06, l’application affiche :

  • une fenêtre de réception sur laquelle s’inscrivent les trames reçues
  • une ligne d’émission qui permet d’envoyer des commandes vers le wagon
  • des boutons logiciels auxquels on peut attribuer des commandes à envoyer.

Ceci correspond à la solution la plus facile à mettre en œuvre, mais pas la plus ergonomique.


Autres applications possibles :

JPEG - 17.7 kio

Il existe une application appelée « Bluetooth Electronics » qui permet de générer des écrans de gestion et d’affichage de données transmises par Bluetooth. Il suffit de configurer graphiquement les boutons, voltmètres ou autres équipements que l’on veut voir apparaître sur le panneau de contrôle du smartphone, de leur donner un nom et de paramétrer leur comportement.
Par contre, il faudra modifier les données envoyées et reçues par l’Arduino pour qu’elles correspondent à la syntaxe attendue par « Bluetooth Electronics ».

Une autre application possible est « MIT App Inventor 2 » pour ceux qui aiment la programmation graphique.

JPEG - 19.2 kio

Ce logiciel permettra de développer une application graphique pour recevoir et afficher les données transmises par le logiciel Arduino du wagon. « MIT App Inventor 2 » édite et compile l’application Android sur un serveur WEB via un navigateur, puis la télécharge sur le smartphone.
Par contre, il faut un compte Gmail pour accéder à cet outil de développement en ligne créé par Google.

Création en Processing d’une application Bluetooth dédiée :

Pour ceux ont la volonté de développer une application Android personnelle, il existe entre autres un outil de développement d’applications graphiques : « Processing ». La version Android s’installe facilement sur PC et permet de créer une application Android dédiée au wagon.

JPEG - 33.2 kio

Installation de Processing Android
Il faut commencer par installer Processing sur votre ordinateur PC, Mac ou Linux en téléchargeant le programme correspondant à votre ordinateur à partir du site Processing et en suivant les conseils donnés de l’article Démarrer en Processing (1).
Une fois Processing démarré, dans la fenêtre de l’IDE cliquer en haut à droite sur la case affichant ‘mode Java’ et sélectionner « Ajouter un mode »
Choisir ‘Android Mode’ dans la liste déroulante qui apparaît alors et confirmer en cliquant sur le bouton ‘Installer’.
Après installation, vous pouvez passer en mode Android par le menu déroulant en haut à gauche de la fenêtre.
La première fois que vous passez en mode Android, le système vous demandera de télécharger le SDK (Software Development Kit) Android en affichant la fenêtre suivante :

JPEG - 33 kio

Cliquez sur ‘Download SDK automaticaly’. Attendre la fin de l’installation qui peut prendre un peu de temps car le SDK Android fait un peu plus de 1Go.
Lorsque l’installation est terminée, pour tester votre logiciel, il faut connecter votre smartphone ou tablette. Pour cela :
• Connecter l’appareil PC (si on vous le demande, choisir le mode périphérique multimédia, MTP) ;
• Maintenant il faut activer le mode Débogage USB pour pouvoir télécharger une application par l’entrée USB.

Attention, depuis la version 4.2 d’Android, le menu ’Options de développement’ n’apparaît plus par défaut dans les options de paramétrage, pour faire apparaître cette option :
Sur votre téléphone, dans les paramètres, choisissez ’Général → À propos du téléphone’. Touchez sept fois la zone ’Numéro de version’ : un message vous indique que vous êtes développeur !
L’option ’Options de développement’ ou ’Options pour les développeurs’ apparaît maintenant dans les paramètres de votre appareil.

• Activer alors le Débogage USB dans les options de développement de votre téléphone.
Dans la fenêtre Processing de votre ordinateur cliquez sur Android → Select device : la référence de votre téléphone doit apparaître.
Sous Windows il faudra éventuellement installer un driver USB pour le téléphone en suivant la procédure Google.
Vous pouvez maintenant charger vos programmes en mode Android.
Mis à part les spécificités de chaque appareil, le même sketch processing fonctionnera sur votre ordinateur en mode Java ou en mode Android sur l’émulateur ou sur votre téléphone.
Je vous propose un programme de test simple qui colore en noir le demi-écran sur lequel vous cliquez.
Il suffit de copier le code suivant dans la fenêtre de l’IDE Processing :

void setup() {
  fullScreen();
  noStroke();
  fill(0);
}

void draw() {
  background(204);
  if (mousePressed) {
    if (mouseX < width/2) {
      rect(0, 0, width/2, height); // Left
    } else {
      rect(width/2, 0, width/2, height); // Right
    }
  }
}

Si vous êtes en mode Java, la compilation et l’exécution se feront sur votre ordinateur. Vous pouvez tester en cliquant sur la fenêtre graphique.
Si vous êtes en mode Android, vous pouvez exécuter le sketch dans l’émulateur Android en cliquant Sketch → Run in Emulator. Vous pouvez aussi l’exécuter sur le téléphone ou la tablette connecté via l’USB, en cliquant Sketch → Run on Device.
Cela vous permettra de faire vos tests de mise au point des graphismes sur votre ordinateur en compilant en mode Java, puis ultérieurement rajouter les spécificités du Bluetooth sur votre téléphone en compilant en mode Android.

Application Android "Wagon.apk"

Fonctionnalités de l’application
L’application doit :

  • recevoir les trames Bluetooth
  • afficher éventuellement les trames reçues pour un diagnostic plus facile
  • afficher la distance et la vitesse
  • afficher les unités de mesure de la distance et de la vitesse
  • proposer des boutons logiciels pour les commandes à envoyer au wagon
  • envoyer les commandes au wagon.
  • s’afficher avec une icône significative dans le tableau des applications Android.

    Figure 25 : Ecran application Android 'Wagon'
    Figure 25 : Ecran application Android ’Wagon’

    Pour le graphisme de l’application, j’ai opté pour un compteur de vitesse affichant, à l’aide d’un cadran et d’une aiguille, la vitesse et, à l’aide d’un affichage de style « 7 segments », la distance parcourue. L’aiguille change de couleur en fonction du sens de déplacement du wagon. Le cadran comporte l’indication des unités utilisées. Les trames reçues s’affichent en bas de l’écran et les boutons de commande sont répartis autour du cadran.
    Sur la capture d’écran jointe, l’aiguille est verte donc marche avant, le wagon a parcouru 224mm et se déplace à une vitesse équivalente de 37km/h.
    L’icone rouge indique que les feux de fin de convoi sont allumés, le bouton jaune sert à la remise à zéro de la distance et le bouton bleu permet de changer les unités pour l’affichage. En bas de l’écran on retrouve la trame transmise par le module Bluetooth du wagon.
    Architecture de l’application :
    Le logiciel de l’application que j’ai développée est divisé en trois fichiers :

  • Wagon.pde fichier principal
  • BT_android.pde fichier qui contient les procédures liées au Bluetooth
  • Boutons.pde fichier contenant les routines de gestion des boutons
Figure 26 : Architecture logicielle de l'application Android
Figure 26 : Architecture logicielle de l’application Android

Cette application fait appel à la partie net/Bluetooth de la librairie Ketai. Pour l’installer, il faut aller dans la fenêtre de l’IDE Processing et dérouler les menus suivants :

  1. ’sketch’ dans la barre de menu
  2. ’importer une librairie’
  3. ’ajouter une librairie’
  4. sélectionner ’Ketai’ dans la liste déroulante
  5. cliquer sur ’Install’

Wagon.pde
On trouve dans ce fichier les déclarations de variables globales propres à l’affichage sur l’écran du périphérique Android.
Vient ensuite la routine settings() qui déclare les dimensions de l’écran.
La routine setup() charge les diverses images nécessaires pour l’application, les polices de caractères requises et appelle les routines d’initialisation de la connexion Bluetooth et d’initialisation de la gestion des boutons logiciels.
La routine principale draw() est exécutée de façon répétitive. Après s’être assuré de la connexion du Bluetooth, son rôle consiste à placer et dessiner toutes les entités graphiques sur l’écran, en fonction des variables globales gérées par les routines Bluetooth ou les routines de gestion des boutons logiciels.
Après la mise en place de la couleur de fond, du titre et de l’icône de l’application, l’image du cadran du compteur de vitesse est dessinée sur l’écran, puis on lui superpose :

  • un masque noir rectangulaire pour préparer le fond de l’afficheur 7 segments simulé.
  • une image sombre de tous les segments pour simuler les segments éteints.
  • le texte correspondant à la valeur de la distance avec la police 7 segments en jaune vif
  • l’image de l’aiguille tournée d’un angle proportionnel à la vitesse. L’image de l’aiguille utilisée dépend du signe de la vitesse (aiguille verte si vitesse vers l’avant ou positive, aiguille rouge en cas contraire).
  • la chaîne de caractères correspondant à la trame reçue.
    La routine draw() appelle alors la routine draw_boutons() du fichier ‘Boutons.pde’ qui dessine les boutons logiciels et gère les actions sur ces boutons.

BT_android.pde
Ce fichier contient :

  • une série de déclarations de variables globales, ainsi que la déclaration globale des objets :
    • Bluetooth bt qui permet de gérer la connexion Bluetooth.
    • Ketailist klist qui gère la liste des appareils Bluetooth appairés et permet de sélectionner la connexion au module Bluetooth HC06 du wagon.
  • des routines de CallBack en fonction des événements déclenchés par l’application, ou la bibliothèque Bluetooth Ketai.
  • la routine setup_BT() qui initialise l’objet bt et le flag isConfiguring indiquant que le Bluetooth est en cours d’initialisation et de configuration.
  • la routine draw_BT() qui crée la liste des appareils appairés et qui, via le CallBack onKetaiListSelection(), déclenche la sélection et la connexion au périphérique choisi (de préférence le module HC06 du wagon). Cette routine ne sera appelée qu’au lancement de l’application grâce au mécanisme du flag isConfiguring qui est remis à zéro, et donc le draw() principal ne rappellera plus cette routine ultérieurement.
  • la routine onBluetoothDataEvent(String who, byte[] data) appelée par la bibliothèque Ketai lorsque le buffer de réception Bluetooth n’est pas vide. Cette routine vérifie le contenu du buffer, et lorsqu’il contient une trame complète (présence d’un NewLine ‘\n’), elle vérifie la validité de trame, l’analyse et extrait les entiers distance et vitesse ainsi que les chaines de caractères des unité Ud et Uv, pour affichage par le draw() principal.
  • la routine envoi_octet(byte x) dont le rôle est d’envoyer l’octet x vers le périphérique connecté.

Boutons.pde
Ce fichier assure la gestion de l’affichage des 3 boutons logiciels et la gestion des actions correspondantes. Il contient :

  • les déclarations globales nécessaires telles qu’emplacements, images, tailles et états des boutons.
  • la routine setup_boutons() dont le rôle est de charger les images des boutons.
  • La routine draw_boutons() qui gère les boutons logiciels en deux étapes :
    -** si l’utilisateur appuie sur l’écran (mousePressed), la routine recherche quel est le bouton concerné, puis positionne les flags correspondants et envoie le caractère adéquat, sinon on se contente de laisser les boutons dans leur état actuel.
    -** en fonction des flags positionnés précédemment, la routine sélectionne et affiche l’image de chacun des trois boutons.
    Quand un bouton logiciel est activé, on se contente de dessiner à sa place un carré gris pour simuler l’état appuyé, à l’exception du bouton d’éclairage des feux de fin de convoi qui affiche 2 images différentes suivant l’allumage ou l’extinction des feux de fin de convoi.

Configuration pour Android :
Le répertoire de développement de l’application se présente alors sous la forme suivante :

Pour que l’application soit accessible sur le smartphone par une icone différente de l’icone par défaut de Processing, on lui attribue une icone qui sera affichée par le système Android dans la page des applications.

Figure 27 : Exemple d'icone pour l'application
Figure 27 : Exemple d’icone pour l’application

Pour cela, une image est créée et dupliquée dans plusieurs fichiers de type .png avec des résolutions différentes. : icon_32x32.png, icon_48x48.png, icon_72x72.png, icon_96x96.png, icon_144x144.png et icon_192x192.png. Un éditeur graphique permet de préparer ces fichiers facilement à partir d’une image de plus grande résolution. Ces fichiers seront placés dans le même répertoire que les fichiers source.
Les fichiers contenant les images nécessaires à l’application seront placés dans le sous-répertoire data.
Le sous-répertoire code ne contient qu’un seul fichier définissant des paramètres de compilation de l’application.

Mise au point :
Pour la mise au point des routines n’utilisant pas le Bluetooth (position des images, rotations, boutons …) il est possible d’utiliser Processing dans la version pour PC en utilisant la liaison USB série pour valider le décodage des trames et l’envoi de commandes.
L’étape suivante de validation sur PC consiste à connecter le Bluetooth de votre PC sur le Bluetooth du wagon. Repérer ensuite le n° du port COM attribué à la liaison Bluetooth dans le PC, et utiliser la librairie liaison série de Processing en connectant le sketch sur le port COM Bluetooth en relation avec l’Arduino du wagon.
Pour valider les graphiques sur smartphone deux solutions :

  • utiliser l’émulateur Android sur PC.
    Cette solution est un peu lourde et ne reflète pas forcément ce qui se passera exactement sur votre smartphone.
  • utiliser un smartphone connecté par une connexion USB.
    Après compilation, Processing télécharge l’application dans le smartphone et démarre son exécution. Il est alors possible de voir l’effet final et d’apporter les corrections de positionnement des éléments graphiques ou autres.
    Ne pas oublier de donner à l’application l’autorisation d’accéder au Bluetooth de smartphone : dans l’IDE menu Android → Sketch Permissions → BLUETOOTH.

Une fois l’application validée, vous pouvez en faire une application certifiée Google et téléchargeable via le Play Store Google. Mais face à la complexité de cette procédure, je ne m’y suis pas aventuré et je ne vous le conseille pas.
Par contre il y a deux possibilités de récupérer le fichier .apk qui vous permettra de partager votre réalisation :

  • rechercher le fichier *.apk dans les sous répertoires de votre répertoire de fichiers temporaires :
    C :\Users{votre_user_name\AppData\Local\Temp\...
    Le fichier porte un nom avec ’_debug’ à l’intérieur pour montrer qu’il n’est pas enregistré dans le Play Store. Il vous suffit de le renommer à votre guise.
  • utiliser sur votre smartphone l’application ‘Apk Extractor’ (à télécharger sur la Play Store Google) qui se chargera de créer le fichier .apk correspondant à l’application créée et vous permettra de la partager.

Autre application développée pour Android : Flaman.apk

Avec les mêmes fonctionnalités et sur la base de l’application « Wagon », j’ai écrit un sketch Processing affichant les données venant du wagon sur une image d’un tachymètre Flaman.
Voici comment se présente cette nouvelle application :

Figure 28 : Ecran de l'application 'Flaman'
Figure 28 : Ecran de l’application ’Flaman’

La distance parcourue s’affiche en haut à droite avec l’unité en dessous. Le cadran du tachymètre Flaman affiche l’unité de vitesse et montre une aiguille bleue ou noire (suivant le sens du déplacement) indiquant la vitesse reçue ainsi qu’une aiguille rouge affichant la vitesse maximale atteinte. Pour ramener l’aiguille rouge sur la valeur de la vitesse reçue, il faut cliquer sur le bout du petit levier proche du centre de rotation des aiguilles. Tout comme pour un tachymètre réel, la petite aiguille noire affiche les unités du nombre de minutes de l’heure actuelle. En dessous de l’image du tachymètre on trouve les trois interrupteurs correspondant au changement d’unité, à la remise à zéro de la distance cumulée et à l’allumage des feux de fin de convoi. La trame de data reçue du wagon est affichée en dessous des interrupteurs.
Lors de cette capture d’écran, il était 15h24, la distance parcourue est de 4563 mm, la vitesse actuelle est de 75 km/h en marche arrière. La vitesse maximale atteinte est de 115 km/h. Les feux de fin de convoi sont allumés.

Le sketch de l’application Flaman est divisé en quatre fichiers :

    • Flaman.pde fichier principal
    • BT_android.pde fichier qui contient les procédures liées au Bluetooth
    • Fond.pde fichier contenant les routines d’affichage de l’image dynamique du Flaman
    • Switch.pde fichier de définition de la classe Switch pour la gestion des interrupteurs
      Figure 29 : Architecture logicielle sketch 'Flaman'
      Figure 29 : Architecture logicielle sketch ’Flaman’

Flaman.pde
On trouve dans ce fichier les déclarations de variables globales propres à l’affichage sur l’écran du périphérique Android.
Vient ensuite la routine settings() qui déclare les dimensions de l’écran.
La routine setup() appelle la routine d’initialisation de la connexion Bluetooth, charge les diverses images nécessaires pour l’application, les polices de caractères requises, initialise les variables globales de positionnement des éléments graphiques et instancie les trois interrupteurs de la classe Switch. Toutes les tailles et positions des éléments graphiques sont proportionnelles à la taille de l’écran du smartphone via l’utilisation des variables WinLarg et Agrandissement.
La routine principale draw() est exécutée de façon répétitive. Après s’être assurée de la connexion du Bluetooth, son rôle consiste calculer les angles des aiguilles de vitesse, à placer le repère graphique initial, appeler la routine de dessin draw_fond() et appliquer la méthode Draw() à chacune des instances des trois interrupteurs.

BT_android.pde
Ce fichier fait appel à la librairie Ketai Bluetooth. Il contient :

  • une série de déclarations de variables globales, ainsi que la déclaration globale des objets :
    • Bluetooth bt qui permet de gérer la connexion Bluetooth.
    • Ketailist klist qui gère la liste des appareils Bluetooth appairés et permet de sélectionner la connexion au module Bluetooth HC06 du wagon.
  • des routines de CallBack en fonction des évènements déclenchés par l’application, ou la bibliothèque Bluetooth Ketai.
  • la routine setup_BT() qui initialise l’objet bt et le flag isConfiguring indiquant que le Bluetooth est en cours d’initialisation et de configuration.
  • la routine draw_BT() qui crée la liste des appareils appairés et qui via le CallBack onKetaiListSelection() déclenche la sélection et la connexion au périphérique choisi (de préférence le module HC06 du wagon). Cette routine ne sera appelée qu’au lancement de l’application grâce au mécanisme du flag isConfiguring qui est remis à zéro, et donc le draw() principal ne rappellera plus cette routine ultérieurement.
  • la routine onBluetoothDataEvent(String who, byte[] data) appelée par la bibliothèque Ketai lorsque le buffer de réception Bluetooth n’est pas vide. Cette routine vérifie le contenu du buffer, et lorsqu’il contient une trame complète (présence d’un NewLine ‘\n’), elle vérifie la validité de trame, l’analyse et extrait les entiers distance et vitesse ainsi que les chaines de caractères des unités Ud et Uv, pour affichage par le draw() principal.
  • la routine envoi_octet(byte x) dont le rôle est d’envoyer l’octet x vers le périphérique connecté.

Fond.pde
Ce fichier dessine toute la partie graphique de l’application en suivant les étapes :

  • Dessin de l’image de fond
  • Dessin des étiquettes des interrupteurs (dessin d’un rectangle gris clair et écriture avec la police Dymo)
  • Mise en place de l’image des graduations
  • Ecriture des noms des unités
  • Ecriture de la distance (police ‘7 segments’ jaune sur un rectangle sombre)
  • Dessin de l’aiguille des minutes en fonction de l’heure réelle dans le petit cadran.
  • Dessin des aiguilles de vitesse (noire si la vitesse est positive, bleue quand la vitesse est négative, rouge pour la vitesse maximale enregistrée).
  • Mise en place de l’image du premier plan et écriture du titre avec l’icone
  • Affichage de la trame reçue (pour faciliter le débug).

Switch.pde
Ce fichier définit la classe Switch qui permet de créer des interrupteurs. Cette classe possède une méthode qui gère l’interrupteur en fonction des clics de l’utilisateur et appelle la routine pour envoyer la commande correspondante vers le wagon. Il contient :

  • les déclarations globales (emplacements, taille, images et états des boutons.
  • Le constructeur de la classe
  • La méthode Draw() qui gère les boutons logiciels en deux étapes :
    • gestion de la position des clics qui activent les interrupteurs et mises à jour des états des interrupteurs suivant que l’interrupteur est bistable ou à impulsion.
    • mise en place de l’image de l’interrupteur en fonction de son état.
    • en fonction des flags positionnés précédemment, la routine sélectionne et affiche l’image de chacun des trois boutons.

Enregistrement et analyse des données :
En connectant le PC et l’Arduino du wagon via le Bluetooth, il est possible d’utiliser le port COM du PC correspondant au Bluetooth du wagon pour recevoir les données :

  • soit via un programme « terminal » qui pourra les enregistrer dans un fichier log pour être traitées ultérieurement,
  • soit via Excel qui exécutera des macros de réception et d’analyse des données.

Téléchargements

Le programme pour l’Arduino du wagon
Le programme Android "wagon"
Le programme Android "Flaman"

Extensions possibles :
Le projet peut surement être amélioré. Il serait possible de lui adjoindre un capteur de mouvement trois axes pour mesurer le pourcentage d’une rampe, ou comparer la vitesse de rotation d’un gyromètre de lacet à la vitesse de déplacement pour évaluer les rayons de courbure des voies.…

Comme d’habitude, n’hésitez pas à poser toutes vos questions auxquelles je m’efforcerai de répondre dans les meilleurs délais. Pour cela, utilisez de préférence le fil du forum correspondant à cette réalisation :
LOCODUINO »Parlons Arduino »Vos projets »Wagon de mesure distance et vitesse