Certaines applications pour un réseau de trains miniatures ne requièrent pas toute la puissance d’un module Arduino/Genuino Uno et on peut trouver dommage de monopoliser un tel module pour si peu. La solution est de les confier à un microcontrôleur moins puissant, donc moins coûteux. Cette série d’articles vous présente le microcontrôleur ATtiny45 du constructeur Atmel et ses possibilités dans le domaine du modélisme ferroviaire.
L’article d’aujourd’hui vous propose de construire un automatisme de passage à niveau gérant l’ouverture ou la fermeture des barrières et le clignotement de ses feux. Ce sera l’occasion d’apprendre à relier un servomoteur à un µC ATtiny45.
Le microcontrôleur ATtiny45
Le microcontrôleur ATtiny45 (7)
Automatisme de passage à niveau
.
Par :
DIFFICULTÉ :★★☆
La bibliothèque Servo
L’IDE d’Arduino est livré avec des bibliothèques préinstallées bien pratiques pour gérer certains périphériques ; c’est le cas de la bibliothèque Servo qui permet de relier des servomoteurs à des cartes Arduino. Cette bibliothèque est décrite dans l’article La bibliothèque Servo ou bien encore à cette page du site Arduino : https://www.arduino.cc/en/Reference... .
Cette bibliothèque utilise un timer de 16 bits (par exemple le timer1 d’une carte UNO) pour générer le signal de contrôle du servomoteur, comme ceci est rappelé dans l’article Les Timers (I). Pour cette raison, la PWM n’est plus utilisable sur les broches 9 et 10 d’une carte UNO puisque cette PWM a besoin du timer1 pour être générée.
Le microcontrôleur ATtiny45 ne dispose pas de timer de 16 bits (il a deux timers de 8 bits). La bibliothèque Servo ne peut donc pas être utilisée avec ce type de microcontrôleur. Pour s’en convaincre, on peut ouvrir le programme Sweep donné en exemple dans l’IDE (Fichier > Exemples > Servo > Sweep) et changer à la ligne 18 myservo.attach(9) ;
en myservo.attach(3);
puisque l’ATtiny n’a pas autant de broches. On exécute ensuite une compilation par l’option ‘Vérifier’ après avoir pris soin de définir comme type de carte l’ATtiny45 avec le menu ‘Outils’. On obtient un message d’erreur indiquant que la compilation est impossible pour cette carte, ce qui est normal puisque le microcontrôleur n’a pas de timer 16 bits et que la bibliothèque Servo n’a pas été écrite pour un µC ATtiny.
Une solution pour pallier ce problème serait de faire appel à une autre bibliothèque équivalente à Servo mais écrite pour les µC ATtiny, comme par exemple la bibliothèque Servo_ATTinyCore incluse dans le Board Package ATtiny de Spence Konde (voir l’article Le microcontrôleur ATtiny45 (2)). Cette bibliothèque spécifique est normalement choisie automatiquement si on compile un sketch utilisant Servo sur ATtiny. Mais si ce n’est pas le cas, il suffit de remplacer en début de programme #include <Servo.h>
par #include <Servo_ATTinyCore.h>
. Les exemples Knob et Sweep de la bibliothèque Servo sont donnés pour cette bibliothèque dans Fichier > Exemples > Servo_ATTinyCore. Il devient alors aussi facile d’utiliser un servomoteur avec un ATtiny qu’avec une carte Uno.
Mais ce serait aussi une solution de facilité qui n’a guère sa place dans une série d’articles consacrés à la micro-puce ATtiny45. Dans un but pédagogique, nous allons donc générer nous-mêmes le signal de contrôle d’un servomoteur sur une sortie numérique de l’ATtiny45.
Signal de contrôle d’un servomoteur
En général, un servomoteur tourne entre 0 et 180° (il existe des servomoteurs capables de faire un tour complet) et c’est ce genre de servomoteur que nous allons commander pour ouvrir ou fermer les barrières de notre passage à niveau (PN). La figure 1 rappelle comment doit se présenter le signal de contrôle du servomoteur : il s’agit d’un signal périodique qui se renouvelle indéfiniment et présente une impulsion dont la largeur code la position du servomoteur. Si cette impulsion dure 1 ms (milliseconde), le servo est à la position 0°, si elle dure 1,5 ms il est à la position 90°, si elle dure 2 ms il est à la position 180°.
Pour positionner un servomoteur à une certaine position entre 0 et 180°, il faut donc générer une impulsion comprise entre 1 et 2 ms, selon une certaine périodicité comprise entre 10 et 20 ms et pour que le servomoteur garde la position voulue, il faut envoyer ce signal en permanence ce qui peut se faire à l’intérieur d’une boucle de programmation. Certains servomoteurs ne respectent pas tout à fait ces spécificités et ont besoin, pour explorer la plage 0 à 180°, d’impulsions plus courtes que 1 ms (soit 1000 µS) ou plus longues que 2 ms (soit 2000 µS) ; c’est pourquoi la bibliothèque Servo permet des impulsions de 544 µs à 2400 µs comme c’est expliqué dans l’article La bibliothèque Servo.
Le problème est donc de générer des impulsions sur une sortie numérique du microcontrôleur de telle sorte que ces impulsions durent entre 1000 et 2000 µs et se répètent avec une période comprise entre 10000 et 20000 µs. On peut être tenté de faire appel à la fonction delayMicroseconds()
pour calibrer la durée de l’impulsion (signal à l’état HIGH) et la répétitivité des impulsions (signal à l’état LOW durant le reste du temps), mais cette solution est contraignante puisque la fonction delayMicroseconds()
est bloquante tout comme la fonction delay()
. Pour solutionner ce problème, nous allons nous inspirer du programme donné en exemple ‘BlinkWithoutDelay’ en utilisant la fonction micros()
au lieu de millis()
pour une meilleure précision. Et c’est la fonction digitalWrite
qui fait passer le signal de l’état LOW à l’état HIGH ou réciproquement. On mémorise dans la variable ‘previousMicros’ le moment où on met le signal à l’état HIGH (flèche rouge sur la figure 1) et au fur et à mesure, on regarde si on a atteint la durée d’impulsion. À ce moment, on fait basculer le signal à l’état LOW jusqu’à la fin de la période souhaitée, donc pour une durée égale à (période – durée de l’impulsion). Tout cela se trouve à l’intérieur d’une boucle while
pour que le signal se répète.
// constantes :
const int bpPin = 4; // branchement des ILS
const int ServoPin = 3; // servomoteur
const long period = 15000; // periode (en microsecondes; comprise entre 10 et 20 ms)
const long pulseMin = 1000; // logiquement >= 1000
// variables :
unsigned long previousMicros = 0; // memorise l instant du front montant
long pulse = 1000; // Impulsion en microsecondes du signal
while(digitalRead(bpPin)==HIGH){
// ILS non declenche
pulse = pulseMin; // pour positionner barrières ouvertes
unsigned long currentMicros = micros();
if(currentMicros - previousMicros >= pulse) {
digitalWrite(ServoPin, LOW);
}
if (currentMicros - previousMicros >= (period - pulse)) {
digitalWrite(ServoPin, HIGH);
previousMicros = currentMicros;
}
}
Automatisme de passage à niveau
Le servomoteur devra être réglé pour aller d’une position 0° (barrières ouvertes) à 90° (barrières fermées). En absence de train, les barrières sont ouvertes en permanence ; le signal est généré avec la durée d’impulsion minimum pour que sa position soit 0° à l’intérieur d’une boucle while
dont on ne devrait sortir qu’à l’approche d’un train. En fait, on en sort au bout de deux secondes (avec break
) pour limiter à cette valeur (’duree_signal’) la durée du signal généré, ceci pour éviter un léger frétillement du servomoteur. À ce moment-là, on entre à nouveau dans une boucle while
prévue pour attendre l’approche d’un train. Celui-ci est repéré par un ILS (Interrupteur à Lames Souples) situé en amont du PN, suffisamment loin pour avoir le temps de fermer les barrières avant l’arrivée du train. Lorsque l’ILS se déclenche, on sort de la boucle pour générer un signal de positionnement avec une durée d’impulsion maximum correspondant à une position de 90°, et ceci de façon continue grâce à une troisième boucle while
dont on ne devrait sortir que lorsqu’un deuxième ILS, situé suffisamment loin en aval du PN pour que tout le train soit passé, est déclenché par le train. Une fois de plus, on en sort avant pour limiter le signal généré à une durée de deux secondes. Cette troisième boucle, qui correspond aux barrières fermées, peut aussi commander le clignotement des feux de PN en s’inspirant du programme ‘BlinkWithoutDelay’ (avec la fonction millis()
) ; quand on en sort au bout de deux secondes, il faut continuer à faire clignoter les DEL jusqu’à recevoir le déclenchement du deuxième ILS indiquant que le train est passé.
Des essais sont à faire pour déterminer les bonnes durées d’impulsion pour que le servomoteur parcourt les 90° nécessaires à la manœuvre des barrières ; pour ma part, j’ai utilisé un servo Futaba S3001 avec des durées d’impulsion de 1000 µs et 2000 µs pour avoir 90° de débattement (au lieu des 180° prévues par les spécificités des servos pour ces valeurs d’impulsion). Le servo est relié sur la sortie 3 de l’ATtiny45 et c’est donc sur cette sortie qu’il faut générer le signal de contrôle.
Les deux ILS (montés en parallèle) sont reliés à l’entrée 4 de l’ATtiny ; le montage en parallèle permet de faire circuler des trains dans les deux sens. Un premier déclenchement (peu importe par quel ILS) indique qu’un train arrive (peu importe de quelle direction) et un deuxième déclenchement que ce train est passé.
Deux DEL rouges (une pour chaque sens de circulation routière) sont reliées à la sortie 0 de l’ATtiny45 et clignotent trois fois avant que les barrières se ferment et lorsqu’elles sont fermées. On peut augmenter la durée de clignotement des DEL avant la fermeture des barrières grâce à la variable ‘nbre_cligno_avant’ (ce qui implique des ILS situés encore plus loin du PN). Les DEL s’arrêtent de clignoter dès que les barrières se rouvrent comme dans la réalité. La résistance de 150 Ω limite le courant à une dizaine de mA dans les DEL.
La figure 2 montre le montage à réaliser pour avoir l’ensemble de l’automatisme.
Le programme
Le programme commence par définir les constantes (broches, clignotement des DEL avant fermeture des barrières, demi-période de clignotement des DEL, périodicité du signal de contrôle, largeurs extrêmes des impulsions de commandes) puis les variables (mémorisation des moments, largeur d’impulsion). Vous pouvez bien sûr modifier ces valeurs en fonction de votre réseau.
Le setup positionne les broches du µC ATtiny45 en entrée ou en sortie.
Dans la fonction loop, la première boucle while
(ligne 46) correspond au cas où le PN est ouvert. On en sort au bout de deux secondes, pour attendre l’approche d’un train (ligne 63). Lorsque celui-ci déclenche le premier ILS, on fait clignoter les DEL (ligne 68) puis on entre dans une troisième boucle while
(ligne 75) correspondant aux barrières fermées et aux DEL clignotantes. On en sort au bout de deux secondes en continuant le clignotement des DEL (ligne 99) qui s’arrête lorsque le train s’est éloigné pour revenir aux conditions initiales (barrières ouvertes, DEL éteintes).
/* Servo_PN_ATtiny.ino
* --------------------------------------------------------------------------------
* Servomoteur avec un ATtiny pour barrieres PN
* Fabrique sur la sortie ServoPin un signal de servomoteur avec une
* impulsion reglable de pulseMin a pulseMax (soit 1 a 2 ms) et
* avec une période de 15000 µS (soit 15 ms) qui est fixe.
* 1,0 ms ==> servo a -90°
* 1,5 ms ==> servo centré
* 2,0 ms ==> servo a +90°
* En fait, depend du modele de servo
* --------------------------------------------------------------------------------
* ILS non declenche ==> barrieres fixes (ouvertes ou fermees)
* ILS declenche ==> provoque mouvement des barrieres
* --------------------------------------------------------------------------------
* Introduit le clignotement des feux rouges de PN sur sortie ledPin
*/
// constantes :
const int bpPin = 4; // ILS
const int ServoPin = 3; // Servo
const int ledPin = 0; // DEL de feux rouges
const int nbre_cligno_avant = 3; // clignotement des DEL avant fermeture barrieres
const int duree_cligno = 500; // demi periode de clignotement des DEL en ms
const long period = 15000; // periode (en microsecondes; comprise entre 10 et 20 ms)
const long pulseMin = 1000; // logiquement >= 1000
const long pulseMax = 2000; // logiquement <= 2000
// Ces deux valeurs permettent un debattement de 90° du servo S3001 Futaba
// variables :
unsigned long debut_boucle = 0; // memorise le debut du signal Servo
unsigned long duree_signal = 2000; // signal limite en duree pour eviter fretillement
unsigned long previousMicros = 0; // memorise l instant du front montant
unsigned long previousMillis = 0; // memorise changement d etat de la DEL
unsigned long currentMicros;
unsigned long currentMillis;
long pulse = 1000; // Impulsion en microsecondes du signal, de pulseMin a pulseMax
void setup() {
pinMode(bpPin, INPUT_PULLUP);
pinMode(ServoPin, OUTPUT);
pinMode(ledPin, OUTPUT);
}
void loop() {
debut_boucle = millis();
while(digitalRead(bpPin)==HIGH){
// ILS non declenche
pulse = pulseMin;
currentMicros = micros();
if(currentMicros - previousMicros >= pulse) {
digitalWrite(ServoPin, LOW);
}
if (currentMicros - previousMicros >= (period - pulse)) {
digitalWrite(ServoPin, HIGH);
previousMicros = currentMicros;
}
if(millis() - debut_boucle >= duree_signal){
break; // on arrete le signal après 2 secondes
}
}
while(digitalRead(bpPin)==HIGH) {
// attendre
}
// Ici, ILS declenche : clignotement des DEL
for(int i = 1; i <= nbre_cligno_avant; i++) {
digitalWrite(ledPin, HIGH);
delay(duree_cligno);
digitalWrite(ledPin, LOW);
delay(duree_cligno);
}
debut_boucle = millis();
while(digitalRead(bpPin) == HIGH) {
currentMillis = millis();
pulse = pulseMax;
currentMicros = micros();
if(currentMicros - previousMicros >= pulse) {
digitalWrite(ServoPin, LOW);
}
if (currentMicros - previousMicros >= (period - pulse)) {
digitalWrite(ServoPin, HIGH);
previousMicros = currentMicros;
}
// Clignotement des DEL rouges
if (currentMillis - previousMillis >= duree_cligno) {
digitalWrite(ledPin, !digitalRead(ledPin));
previousMillis = currentMillis;
}
if(millis() - debut_boucle >= duree_signal) {
break; // on arrete le signal apres 2 secondes
}
}
while(digitalRead(bpPin)==HIGH) {
// DEL continuent a clignoter
currentMillis = millis();
if(currentMillis - previousMillis >= duree_cligno) {
digitalWrite(ledPin, !digitalRead(ledPin));
previousMillis = currentMillis;
}
}
// Declenchement ILS
delay(500); // anti-rebond
digitalWrite(ledPin, LOW); // Extinction des DEL rouges
// ouverture des barrières se fera en debut de fonction loop
}
La procédure de programmation de l’ATtiny45 est expliquée dans l’article Le microcontrôleur ATtiny45 (2)
Nota : En limitant à deux secondes la durée du signal généré sur la broche 3 pour contrôler le servomoteur, on évite un léger frétillement des barrières en position ouverte ou fermée. Ce laps de temps est réglable par l’intermédiaire de la variable ‘duree_signal’ pour que le mouvement des barrières soit terminé avant de couper le signal de contrôle (cas d’un servomoteur tournant plus lentement). Dans ce montage, il peut être nécessaire de prévoir une alimentation pour le servomoteur séparée de l’alimentation de l’électronique ; cela dépend de la consommation du servomoteur que vous utilisez. Dans ce cas, seules les masses des deux alimentations sont à relier ensemble (voir le cours d’électronique en téléchargement libre dans l’article Démarrer en électronique).
Conclusion
Le montage proposé ici nous a permis de connecter un servomoteur sur un simple ATtiny45 et de développer un automatisme de passage à niveau très réaliste puisque les feux clignotent avant la fermeture des barrières jusqu’à leur réouverture. La vidéo suivante vous montre l’effet obtenu :
On pourrait sans doute reprocher que le mouvement des barrières est un peu rapide ; ceci peut être réglé en provoquant un débattement de 180° du servomoteur et en adjoignant une démultiplication du mouvement par un facteur deux grâce à des engrenages ou de simples roues à gorge. C’est juste un problème mécanique qui peut facilement se résoudre. Il reste deux sorties de disponibles sur l’ATtiny45 dont une pourrait servir à déclencher un système reproduisant la sonnerie du PN ; ceux qui aiment les ambiances sonores ont donc encore du pain sur la planche…
Le passage à niveau décrit dans cet article est conçu pour une ligne à voie unique parcourue dans les deux sens. Les modélistes souhaitant un passage à niveau pour traverser plusieurs voies se reporteront aux articles Un automatisme de Passage à Niveau et Etude d’un passage à niveau universel qui décrivent une solution plus universelle.
On peut aussi remarquer que ce montage, qui positionne un servomoteur avec un débattement de 90°, peut aussi servir à ouvrir les portes d’une remise à l’approche de la locomotive, ou encore les grilles d’une propriété lorsque le maître des lieux arrive avec son cabriolet.
On a déjà fait beaucoup de choses avec ce simple petit microcontrôleur et d’autres restent encore à faire.