Archives pour la catégorie Réseaux des copains

Gare du réseau d’un ami: états et modes de fonctionnement

États et modes de fonctionnement

Les événements détectés par le protothread LireEntrees sont interprétés par le protothread GererEtats pour modifier l’état du système . Ensuite, en fonction de l’état, les relais seront activés par le protothread EtablirItineraires.

Le protothread GererEtats n’a aucune interaction directe avec les entrées-sorties du micro-contrôleur. Cela permet de se concentrer sur la gestion des états et des modes de fonctionnement, sans se préoccuper (du moins pour l’instant) des relais à activer.

Représentation de l’état du système

En vue de la traduction de l’état du système en activation de relais par le protothread EtablirItineraires, il a paru judicieux de choisir une représentation par voie de gare. Chaque voie est représentée par une structure et les structures sont regroupées dans un tableau global accessible par le protothread EtablirItineraires:

struct tEtatVoie {
    uint8_t    nZoneLsne;
    uint8_t    nZoneBern;
    uint8_t    eEtablirLsne;
    uint8_t    eEtablirBern;
};
...
#define	NB_VOIES  6
...
struct tEtatVoie agVoies[NB_VOIES];

Les champs de la structure tEtatVoie ont les significations suivantes:

  • nZoneLsne: numéro de la zone à laquelle la voie est connectée côté Lausanne.
  • nZoneBern: numéro de la zone à laquelle la voie est connectée côté Berne.

Les valeurs possibles pour les numéros de zones sont les mêmes que les numéros des poussoirs d’entrées correspondants. La valeur 255 (-1) indique que la voie n’est connectée à aucune zone.

#define	Z_LSNE1  BP_DIR_LSNE
#define	Z_LSNE2  BP_PROV_LSNE
#define	Z_BERN1A BP_DIR_BERN_A
#define	Z_BERN1B BP_DIR_BERN_B
#define	Z_BERN2  BP_PROV_BERN
...
#define	BLOQUEE  255
  • eEtablirLsne: état de l’établissement de l’itinéraire côté Lausanne.
  • eEtablirBern: état de l’établissement de l’itinéraire côté Berne.

Les valeurs possibles pour ces états sont:

  • ETAB_IDLE: pas d’itinéraire à établir.
  • ETAB_DEMANDE: l’établissement de l’itinéraire est demandé.
  • ETAB_EN_COURS: l’établissement de l’itinéraire est en cours (état géré dans EtablirItineraires).

Structure du protothread

Le protothread est structuré comme suit:

PT_THREAD(GererEtats(struct pt *ppt))
{
    PT_BEGIN(ppt);

    while(1)
    {
        GererTransition(B_SAUVER_EEPROM);
        PT_YIELD(ppt);
    } // while

    PT_END(ppt);
} // GererEtats

Il appelle la fonction GererTransition qui gère effectivement les changements d’état. Le paramètre B_SAUVER_EEPROM permet de choisir s’il faut sauver le nouvel état dans l’EEPROM ou pas, cette fonction étant appelée aussi au démarrage pour restaurer l’état à partir de ce qui est sauvé en EEPROM.

La fonction GererTransition permet de coordonner les appels aux fonctions de gestion des états et des modes.

void GererTransition(uint8_t bxSauverEEPROM)
{
    if(bgEvtReinit)
    {
        InitialiserRelais();
        InitModuleEtats();
    }
    else
    {
        bNouvelEtat = 0;

        GererModeManoeuvre(ngEvtPoussoirEntreeMan);

        if((ngEvtPoussoirVoie != PAS_DE_POUSSOIR) &&
           (ngEvtPoussoirEntreeMan != PAS_DE_POUSSOIR) &&
           (ngEvtPoussoirEntreeMan != BP_MANOEUVRE))
        {
            if((ngEvtPoussoirVoie == BP_VOIE_1) || (ngEvtPoussoirVoie == BP_VOIE_2))
                GererVoie1_2(ngEvtPoussoirVoie, ngEvtPoussoirEntreeMan, bgSortieLsneLibre);
            if((ngEvtPoussoirVoie >= BP_VOIE_3) && (ngEvtPoussoirVoie <= BP_VOIE_6))
                GererVoie3_6(ngEvtPoussoirVoie, ngEvtPoussoirEntreeMan, bgSortieLsneLibre, bgSortieBernLibre);
        } // if

        // Blocage des cantons d'entrée au passage de l'ILS.
        if(bgEvtTrainEntreeLsne)
        {
            bgCantonEntreeLsne = 0;
            bNouvelEtat = 1;
        } // if
        if(bgEvtTrainEntreeBern)
        {
            bgCantonEntreeBern = 0;
            bNouvelEtat = 1;
        } // if

        // En mode transit, on détecte la ré-ouverture des cantons de sortie pour libérer
        // les cantons d'entréee.
        if(bgTransitLsneBern && bgEvtSortieBernLibere && !bgManoeuvre)
        {
            bgCantonEntreeLsne = 1;
            bNouvelEtat = 1;
        } // if
        if(bgTransitBernLsne && bgEvtSortieLsneLibere && !bgManoeuvre)
        {
            bgCantonEntreeBern = 1;
            bNouvelEtat = 1;
        } // if

        if(bNouvelEtat && bxSauverEEPROM) SauverEtatCourant();
    } // if(bgEvtReinit)

    // Réinitialiser les variables d'événements.
    ngEvtPoussoirVoie       = PAS_DE_POUSSOIR;
    ngEvtPoussoirEntreeMan  = PAS_DE_POUSSOIR;
    bgEvtTrainEntreeLsne    = 0;
    bgEvtTrainEntreeBern    = 0;
    bgEvtSortieLsneLibere   = 0;
    bgEvtSortieBernLibere   = 0;
    bgEvtReinit             = 0;
} // GererTransition

Cette fonction commence par détecter si une ré-initialisation du système a été demandée. Sinon, elle gère le mode manoeuvre, puis détermine quelle sous-fonction appeler selon le poussoir de voie pressé. Cette décomposition a uniquement pour but de simplifier les fonctions qui sont déjà suffisamment complexes. Ensuite ce sont les entrées de trains qui sont gérées ainsi que le mode transit décrit plus tard.

La variable bNouvelEtat est mise à jour chaque fois qu’un changement d’état est effectué. Son but est de déterminer ensuite si l’état doit être sauvegardé en EEPROM. La description de la sauvegarde / restauration de l’état et le fonctionnement de l’EEPROM seront décrits dans un autre article.

Changements d’état

A priori, le changement d’état est une opération simple. Pour une voie côté Lausanne par exemple, il suffit de coder:

static void GererVoie3_6(uint8_t nxPoussoirVoie, uint8_t nxPoussoirEntreeMan, uint8_t bxSortieLsneLibre, uint8_t bxSortieBernLibre)
{
uint8_t nVoie;

    switch(nxPoussoirEntreeMan)
    {
        case BP_PROV_LSNE:
            agVoies[nxPoussoirVoie].nZoneLsne = Z_LSNE1;
            agVoies[nxPoussoirVoie].eEtablirLsne = ETAB_DEMANDE;
            bNouvelEtat = 1;
            ....
        break;

        case BP_DIR_LSNE:
            if(bxSortieLsneLibre || bgManoeuvre)
            {
                agVoies[nxPoussoirVoie].nZoneLsne = Z_LSNE2;
                agVoies[nxPoussoirVoie].eEtablirLsne = ETAB_DEMANDE;
                bNouvelEtat = 1;
	    } // if
        break;

        ...
        default:
        break;
    } // switch
} // GererVoie3_6

Mais ce n’est pas aussi simple car les états des voies ne sont pas indépendants. Plusieurs cas doivent être distingués:

Blocage des autres voies

Quelques exemples:

  • Lorsque l’on relie la voie 3 à la zone Lsne1, il faut bloquer les voies 4, 5 et 6-9.
  • Lorsque l’on relie la voie 4 à la zone Lsne2, il faut bloquer les voies 3, 5, 6-9 mais aussi les voies 1 et 2.

La programmation de ces blocages se fait après le changement d’état principal dans la fonction GererVoie1_2.

// Bloquer l'autre voie côté Lausanne.
if((nxPoussoirVoie == BP_VOIE_1) && (agVoies[nxPoussoirVoie].eEtablirLsne != ETAB_IDLE))
{
    agVoies[BP_VOIE_2].nZoneLsne = BLOQUEE;
    agVoies[BP_VOIE_2].eEtablirLsne = ETAB_IDLE;
} // if

if((nxPoussoirVoie == BP_VOIE_2) && (agVoies[nxPoussoirVoie].eEtablirLsne != ETAB_IDLE))
{
    agVoies[BP_VOIE_1].nZoneLsne = BLOQUEE;
    agVoies[BP_VOIE_1].eEtablirLsne = ETAB_IDLE;
} // if

// On doit aussi bloquer l'autre voie côté Berne même s'il n'y a pas de zone d'arrêt
// car cette info est utilisée pour la commande des alimentations traction.
if((nxPoussoirVoie == BP_VOIE_1) && (agVoies[nxPoussoirVoie].eEtablirBern != ETAB_IDLE))
{
    agVoies[BP_VOIE_2].nZoneBern = BLOQUEE;
    agVoies[BP_VOIE_2].eEtablirBern = ETAB_IDLE;
} // if

if((nxPoussoirVoie == BP_VOIE_2) && (agVoies[nxPoussoirVoie].eEtablirBern != ETAB_IDLE))
{
    agVoies[BP_VOIE_1].nZoneBern = BLOQUEE;
    agVoies[BP_VOIE_1].eEtablirBern = ETAB_IDLE;
} // if

// Bloquer les voies 3 à 6 qui pourraient être reliées à LSNE2.
if(agVoies[nxPoussoirVoie].eEtablirLsne != ETAB_IDLE)
{
    for(nVoie = BP_VOIE_3; nVoie <= BP_VOIE_6; nVoie++)
    {
        if(agVoies[nVoie].nZoneLsne == Z_LSNE2)
        {
            agVoies[nVoie].nZoneLsne = BLOQUEE;
            agVoies[nVoie].eEtablirLsne = ETAB_IDLE;
        } // if
    } // for
} // if

// Bloquer les voies 3 à 5 qui pourraient être reliées à BERN2.
if(agVoies[nxPoussoirVoie].eEtablirBern != ETAB_IDLE)
{
    for(nVoie = BP_VOIE_3; nVoie <= BP_VOIE_5; nVoie++)
    {
        if(agVoies[nVoie].nZoneBern == Z_BERN2)
        {
            agVoies[nVoie].nZoneBern = BLOQUEE;
            agVoies[nVoie].eEtablirBern = ETAB_IDLE;
        } // if
    } // for
} // if

Ainsi que dans la fonction GererVoie3_6.

// Bloquer les autre voies.
for(nVoie = BP_VOIE_3; nVoie <= BP_VOIE_6; nVoie++)
{
    if(nVoie != nxPoussoirVoie)
    {
        if(agVoies[nxPoussoirVoie].eEtablirLsne != ETAB_IDLE)
        {
            agVoies[nVoie].nZoneLsne = BLOQUEE;
            agVoies[nVoie].eEtablirLsne = ETAB_IDLE;
        } // if
        if(agVoies[nxPoussoirVoie].eEtablirBern != ETAB_IDLE)
        {
            agVoies[nVoie].nZoneBern = BLOQUEE;
            agVoies[nVoie].eEtablirBern = ETAB_IDLE;
        } // if
    } // if
} // for

if(agVoies[nxPoussoirVoie].nZoneLsne == Z_LSNE2)
{
    agVoies[BP_VOIE_1].nZoneLsne = BLOQUEE;
    agVoies[BP_VOIE_2].nZoneLsne = BLOQUEE;
    agVoies[BP_VOIE_1].eEtablirLsne = ETAB_IDLE;
    agVoies[BP_VOIE_2].eEtablirLsne = ETAB_IDLE;
    if((nxPoussoirVoie >= BP_VOIE_3) && (nxPoussoirVoie <= BP_VOIE_6) && !bgManoeuvre)
    {
        bgCantonEntreeLsne = 0;
        bNouvelEtat = 1;
    } // if
 } // if

Voie reliée des 2 côtés au même sens de circulation

Ce cas se produit par exemple lorsque la voie 5 est reliée à Lsne1, puis à Bern1A. Elle est alors ouverte des 2 côtés. Ce cas correspond à la condition de transit.

Dans la fonction GererVoie1_2, il suffit de détecter la condition de transit et la mémoriser dans la variable globale bgTransitBernLsne pour plus de commodité.

// Détecter la condition de transit Bern -> Lausanne.
bgTransitBernLsne = (agVoies[nxPoussoirVoie].nZoneLsne == Z_LSNE2) &&
                    (agVoies[nxPoussoirVoie].nZoneBern == Z_BERN2);

Dans la fonction GererVoie3_6 on doit aussi bloquer le côté opposé d’une voie s’il n’est pas relié au même sens de circulation.

// Cas des voies reliées des 2 côtés à la même direction de circulation.
if((nxPoussoirVoie >= BP_VOIE_3) && (nxPoussoirVoie <= BP_VOIE_5))
{
    if(agVoies[nxPoussoirVoie].eEtablirLsne != ETAB_IDLE)
    {
        if((agVoies[nxPoussoirVoie].nZoneLsne == Z_LSNE1) && !B_BERN1(agVoies[nxPoussoirVoie].nZoneBern))
        {
            agVoies[nxPoussoirVoie].nZoneBern = BLOQUEE;
            agVoies[nxPoussoirVoie].eEtablirBern = ETAB_IDLE;
        } // if

        if((agVoies[nxPoussoirVoie].nZoneLsne == Z_LSNE2) &&
        (agVoies[nxPoussoirVoie].nZoneBern != Z_BERN2))
        {
            agVoies[nxPoussoirVoie].nZoneBern = BLOQUEE;
            agVoies[nxPoussoirVoie].eEtablirBern = ETAB_IDLE;
        } // if
    } // if

    if(agVoies[nxPoussoirVoie].eEtablirBern != ETAB_IDLE)
    {
        if(B_BERN1(agVoies[nxPoussoirVoie].nZoneBern) && (agVoies[nxPoussoirVoie].nZoneLsne != Z_LSNE1))
        {
            agVoies[nxPoussoirVoie].nZoneLsne = BLOQUEE;
            agVoies[nxPoussoirVoie].eEtablirLsne = ETAB_IDLE;
        } // if

        if((agVoies[nxPoussoirVoie].nZoneBern == Z_BERN2) && (agVoies[nxPoussoirVoie].nZoneLsne != Z_LSNE2))
        {
            agVoies[nxPoussoirVoie].nZoneLsne = BLOQUEE;
            agVoies[nxPoussoirVoie].eEtablirLsne = ETAB_IDLE;
        } // if
    } // if
} // if

// Détection de la condition de transit.
bgTransitLsneBern = (agVoies[nxPoussoirVoie].nZoneLsne == Z_LSNE1) && B_BERN1(agVoies[nxPoussoirVoie].nZoneBern) ||
                    (agVoies[nxPoussoirVoie].nZoneLsne == Z_LSNE2) && (agVoies[nxPoussoirVoie].nZoneBern == Z_BERN2);

Mode manoeuvre

La fonction GererModeManoeuvre ne fait que changer l’état de la variable bgManoeuvre. Cette fonction est la seule de tout ce module à interagir avec le matériel, en l’occurence la led qui indique que le mode manoeuvre est actif !

static void GererModeManoeuvre(uint8_t nxPoussoirEntreeMan)
{
    if(nxPoussoirEntreeMan == BP_MANOEUVRE) bgManoeuvre = !bgManoeuvre;

    if(bgManoeuvre)
    {
        SET_LED(LED_MANOEUVRE);
        bgCantonEntreeLsne = 0;
        bgCantonEntreeBern = 0;
    }
    else
        CLR_LED(LED_MANOEUVRE);
} // GererModeManoeuvre

Le mode manoeuvre est utilisé pour déterminer si on doit tenir compte de l’état du canton de sortie, comme par exemple:

    case BP_DIR_LSNE:
        if(bxSortieLsneLibre || bgManoeuvre)
        {
            agVoies[nxPoussoirVoie].nZoneLsne = Z_LSNE2;
            agVoies[nxPoussoirVoie].eEtablirLsne = ETAB_DEMANDE;
            bNouvelEtat = 1;
        } // if

La condition booléenne est vraie si la sortie est libre ou si on est en mode manoeuvre, auquel cas l’état de la sortie n’importe pas.

Il est aussi utilisé pour autoriser les trains à quitter les cantons d’entrée, comme par exemple:

    case BP_PROV_BERN:
        agVoies[nxPoussoirVoie].nZoneBern = Z_BERN2;
        agVoies[nxPoussoirVoie].eEtablirBern = ETAB_DEMANDE;
        bNouvelEtat = 1;

        if(!bgManoeuvre) bgCantonEntreeBern = 1;

Si le mode manoeuvre est actif, le canton n’est pas libéré.

Mode transit

Le mode transit permet d’intégrer la gare au reste du réseau et de laisser les trains la traverser sans aucune intervention manuelle.

Le mode transit s’active indépendamment pour chaque direction de circulation (Lausanne – Berne ou vice-versa) lorsqu’une voie de gare est reliée à la même direction de circulation de chaque côté. Les possibilités sont (voir le code au chapitre Voie reliée des 2 côtés au même sens de circulation ci-dessus) :

  • Voies 1 et 2: provenance Berne / direction Lausanne.
  • Voies 3 à 5: provenance Lausanne / direction Berne ou provenance Berne / direction Lausanne.

C’est dans la fonction GererTransition que l’on gère l’automatisme.

// En mode transit, on détecte la ré-ouverture des cantons de sortie pour libérer
// les cantons d'entréee.
if(bgTransitLsneBern && bgEvtSortieBernLibere && !bgManoeuvre)
{
    bgCantonEntreeLsne = 1;
    bNouvelEtat = 1;
} // if
if(bgTransitBernLsne && bgEvtSortieLsneLibere && !bgManoeuvre)
{
    bgCantonEntreeBern = 1;
    bNouvelEtat = 1;
} // if

Cela peut fonctionner car les variables d’événements bgEvtSortieBernLibere et bgEvtSortieLsneLibere détectent les transitions canton fermé vers canton ouvert, soit la ré-ouverture !
 

Retour vers la table des matières

Publicités

Gare du réseau d’un ami: lecture des entrées

Lecture des entrées

Le protothread LireEntrees est en charge de la lecture:

  • du clavier analogique (cf. l’article lecture des entrées: clavier analogique),
  • de l’état des cantons de sortie côtés Lausanne et Berne,
  • des ILS détectant l’entrée des trains en provenance de Lausanne et Berne.

Lecture des boutons poussoirs

La lecture du bouton pressé, soit parmi les voies de gare, soit parmi les entrées et le mode manoeuvre est effectuée par la fonction nLirePoussoir. Pour s’affranchir des rebonds sur les poussoirs, la transition de l’état non pressé à l’état pressé est détectée. Pour cela, les déclarations suivantes sont nécessaires:

static uint8_t nPoussoirVoie, nPoussoirEntreeMan, nPoussoirVoieNouv, nPoussoirEntreeManNouv;

2 variables sont définies pour chaque chaînes de poussoirs. Par exemple, pour les poussoirs de choix de la voie: nPoussoirVoie, nPoussoirVoieNouv (pour le nouvel état du poussoir). Ces variables, toutes initialisées à la valeur PAS_DE_POUSSOIR, s’utilisent comme suit dans le corps principal du protothread LireEntrees:

while(1)
{
    nTimerRebond = DELAI_REBOND;
    PT_WAIT_UNTIL(ppt, nTimerRebond == 0);
    ...
    // Lire les poussoirs.
    nPoussoirVoieNouv = nLirePoussoir(BP_VOIES);
    nPoussoirEntreeManNouv = nLirePoussoir(BP_ENTREES_MAN);

    // Détection des transitions des poussoirs.
    if((nPoussoirVoieNouv != PAS_DE_POUSSOIR) && (nPoussoirVoie == PAS_DE_POUSSOIR))
        nPoussoirVoiePress = nPoussoirVoieNouv;
    else
        nPoussoirVoiePress = PAS_DE_POUSSOIR;

    if((nPoussoirEntreeManNouv != PAS_DE_POUSSOIR) && (nPoussoirEntreeMan == PAS_DE_POUSSOIR))
        nPoussoirEntreeManPress = nPoussoirEntreeManNouv;
    else
        nPoussoirEntreeManPress = PAS_DE_POUSSOIR;
    nPoussoirVoie = nPoussoirVoieNouv;
    nPoussoirEntreeMan = nPoussoirEntreeManNouv;
    ...

Le timer nTimerRebond est utilisé pour insérer un temps d’attente de 50 ms entre chaque lecture des poussoirs (cf. l’article programmation pour la description du fonctionnement des timers).

Lorsqu’une transition de l’état PAS_DE_POUSSOIR à une valeur correspondant à la pression d’un poussoir, la variable nPoussoirVoiePress, respectivement nPoussoirEntreeManPress est mise à jour avec la valeur du bouton pressé.

Il faut encore gérer les pressions sur les 2 poussoirs qui doivent être effectuées dans un délai de 5 secondes. À cet effet, les états suivants sont définis (les valeurs sont absolument arbitraires):

#define E_IDLE             0
#define E_BP_ENTREE_MAN    1
#define E_BP_VOIE          2
#define E_BP_COMPLET       3

Ces états ont les significations suivantes:

  • E_IDLE: état initial du système, en attente d’une pression sur un poussoir de gare ou d’entrée. Une pression sur un bouton d’entrée ou de manoeuvre passe dans l’état E_BP_ENTREE_MAN et enclenche le timer. De même une pression sur un bouton de voie passe dans l’état E_BP_VOIE et enclenche le timer.
  • E_BP_ENTREE_MAN: un bouton d’entrée ou le bouton de manoeuvre a été pressé. Une pression sur un bouton de voie passe dans l’état E_BP_COMPLET tandis que l’occurence du timeout repasse dans l’état E_IDLE.
  • E_BP_VOIE: un bouton de voie a été pressé. Une pression sur un bouton d’entrée ou le bouton de manoeuvre passe dans l’état E_BP_COMPLET tandis que l’occurence du timeout repasse dans l’état E_IDLE.
  • E_BP_COMPLET: les 2 poussoirs voie et entrée/manoeuvre ont été pressé dans le délai imparti. Après avoir recopié les valeurs des poussoirs dans les variables globales ngEvtPoussoirVoie et ngEvtPoussoirEntreeMan pour les rendre disponibles au reste du système, retour à l’état E_BP_IDLE.

Lecture des ILS: cantons d’entrée

Les 2 cantons d’entrée côtés Lausanne et Berne doivent être refermé une fois que le train correspondant est entré dans la gare. Pour cela, les trains doivent être détectés à l’entrée de la gare. Comme la détection des trains sur ce réseau est déjà basée sur des ILS, le matériel roulant est déjà équipé d’aimants. Il est donc naturel d’utiliser 2 ILS supplémentaires pour cette détection.

Ces 2 ILS sont câblés comme des poussoirs sur 2 entrées numériques du micro-contrôleur qu’ils tirent vers la masse lorsqu’ils sont activés. Tout comme les poussoirs, les ILS produisent des rebonds. À l’instar des poussoirs, on s’intéresse aux transitions de l’état non pressé vers l’état pressé. Le code suivant, appelé dans la boucle principale du protothread toutes les 40 ms, effectue la détection de transition.

#define	B_TRAIN_ENTREE_LSNE	(!(PIND & _BV(TRAIN_LSNE)))
#define	B_TRAIN_ENTREE_BERN	(!(PIND & _BV(TRAIN_BERN)))
...
bTrainEntreeLsneNouv	= B_TRAIN_ENTREE_LSNE;
if((bTrainEntreeLsneNouv == 1) && (bTrainEntreeLsne == 0))
    bgEvtTrainEntreeLsne = bTrainEntreeLsneNouv;
else
    bgEvtTrainEntreeLsne = 0;
bTrainEntreeLsne = bTrainEntreeLsneNouv;

bTrainEntreeBernNouv	= B_TRAIN_ENTREE_BERN;
if((bTrainEntreeBernNouv == 1) && (bTrainEntreeBern == 0))
    bgEvtTrainEntreeBern = bTrainEntreeBernNouv;
else
    bgEvtTrainEntreeBern = 0;
bTrainEntreeBern = bTrainEntreeBernNouv;

Les 2 macros B_TRAIN_ENTREE_LSNE et B_TRAIN_ENTREE_BERN effectuent la lecture des ports du micro-contrôleur. Ensuite, les variables bTrainEntreeLsneNouv, bTrainEntreeLsne, respectivement bTrainEntreeBernNouv, bTrainEntreeBern permettent de comparer l’état courant avec l’état précédent.

Ce sont ensuite les variables globales bgEvtTrainEntreeLsne et bgEvtTrainEntreeBern qui exportent les événements correspondant au reste du programme.

Aux noms de variables près, c’est exactement le même principe qui est utilisé pour lire l’état des cantons de sortie, information nécessaire pour déterminer si on peut laisser sortir un train.

#define B_SORTIE_LSNE_LIBRE	(!(PIND & _BV(CANTON_LSNE)))
#define	B_SORTIE_BERN_LIBRE	(!(PIND & _BV(CANTON_BERN)))
...
bSortieLsneLibreNouv	= B_SORTIE_LSNE_LIBRE;
if((bSortieLsneLibreNouv == 1) && (bgSortieLsneLibre == 0))
    bgEvtSortieLsneLibere = bSortieLsneLibreNouv;
else
    bgEvtSortieLsneLibere = 0;
bgSortieLsneLibre = bSortieLsneLibreNouv;

bSortieBernLibreNouv	= B_SORTIE_BERN_LIBRE;
if((bSortieBernLibreNouv == 1) && (bgSortieBernLibre == 0))
    bgEvtSortieBernLibere = bSortieBernLibre;
else
    bgEvtSortieBernLibere = 0;
bgSortieBernLibre = bSortieBernLibreNouv;

Retour vers la table des matières

Gare du réseau d’un ami: lecture des entrées: clavier analogique

Lecture des entrées: clavier analogique

Après la présentation de la structure générale du programme et du concept de protothread mis en oeuvre (cf. l’article programmation), passons à la description du protothread LireEntrees. Cet article décrit la lecture du clavier analogique, les autres tâches de ce protothread feront l’objet d’un autre article.

Lecture des boutons poussoir

La lecture d’un bouton poussoir d’une entrée ou d’une voie se fait à l’aide du convertisseur analogique-numérique intégré au micro-contrôleur. La chaîne de résistances correspondant aux boutons d’entrées (2 côté Lausanne, 3 côté Berne) ainsi qu’au bouton du mode manoeuvre est reliée à l’une des entrées du multiplexeur d’entrée du convertisseur analogique-numérique. La chaîne de résistances correspondant aux 6 boutons de voie est reliée à l’autre entrée.

Les 2 chaînes de résistances comportent les mêmes valeurs de résistances, calculées à l’aide de la feuille Excel décrite dans l’article sur la connexion de boutons poussoirs. .

Les déclarations nécessaires sont:

#define	PAS_DE_POUSSOIR	255
// Codes des touches pour les entrées en gare, convertisseur ADC0.
#define	NB_POUSSOIRS_ENTREES    6
#define BP_DIR_LSNE             0
#define BP_PROV_LSNE            1
#define	BP_DIR_BERN_A           2
#define	BP_DIR_BERN_B           3
#define	BP_PROV_BERN            4
#define	BP_MANOEUVRE            5

// Codes des touches pour les voies de gare, convertisseur ADC 1.
#define	NB_POUSSOIRS_VOIES      6
#define	BP_VOIE_1               0
#define	BP_VOIE_2               1
#define	BP_VOIE_3               2
#define	BP_VOIE_4               3
#define	BP_VOIE_5               4
#define	BP_VOIE_6               5  // Voies 6 à 9.

// Configuration du convertisseur ADC.
#define	CONFIG_ADMUX_AD_REF    (_BV(REFS0))
#define	SELECT_ADC0            (ADMUX &= ~(_BV(MUX0)|_BV(MUX1)|_BV(MUX2)))
#define	SELECT_ADC1            (ADMUX |= _BV(MUX0))
#define	CONFIG_ADCSRA          (_BV(ADEN)|_BV(ADPS1)|_BV(ADPS0))
#define	ENABLE_ADC             (ADCSRA |= _BV(ADEN))
#define	START_CONVERSION       (ADCSRA |= _BV(ADSC))
#define CONVERSION_RUNNING     (ADCSRA & _BV(ADSC))
#define	NB_POUSSOIRS	6

uint16_t aSeuilsTouches[NB_POUSSOIRS] PROGMEM = {
79, 248, 431, 606, 772, 939 };

#define	BP_ENTREES_MAN	0
#define	BP_VOIES	1

Le convertisseur analogique-numérique doit être initialisé pour utiliser la tension d’alimentation comme référence et prendre la valeur de l’entrée 0 ou 1 du multiplexeur d’entrée.

ADMUX  = CONFIG_ADMUX_AD_REF;
ADCSRA = CONFIG_ADCSRA;

Toutes ces déclarations permettent d’écrire la fonction de lecture d’un poussoir de l’une des 2 chaînes.

static uint8_t nLirePoussoir (uint8_t ePoussoir)
{
uint16_t n, seuil;
uint16_t nAdValeur;

    if(ePoussoir == BP_VOIES) SELECT_ADC1;
    else if(ePoussoir == BP_ENTREES_MAN) SELECT_ADC0;

    ENABLE_ADC;
    START_CONVERSION;
    while(CONVERSION_RUNNING);

    // Il est indispensable de lire ADCL avant ADCH, sinon la conversion ne peut pas être répétée !
    nAdValeur  = ADCL|(ADCH << 8);

    for (n = 0; n < NB_POUSSOIRS; n++)
    {
        seuil = (uint16_t)pgm_read_word(&(aSeuilsTouches[n]));

        if (nAdValeur < seuil)
        {
            return(n);
         } // if
    } // for
    return(PAS_DE_POUSSOIR);
} // nLirePoussoir

En fonction de la valeur du paramètre, on commence par sélectionner l’entrée du multiplexeur à lire. Ensuite, le convertisseur analogique-numérique est enclenché par l’appel ENABLE_ADC, la conversion lancée par l’appel START_CONVERSION. On attend ensuite la fin de la conversion par l’instruction while(CONVERSION_RUNNING).

La valeur, entre 0 et 1023 est lue par l’instruction nAdValeur = ADCL|(ADCH << 8);.

Ensuite, on parcourt les valeurs de seuils du tableau aSeuilsTouches pour déterminer quelle poussoir a été pressé. Les valeurs de ce tableau proviennent de la feuille Excel décrite dans l’article sur la connexion de plusieurs poussoirs.

Le tableau aSeuilsTouches est déclaré à l’aide de la directive PROGMEN pour qu’il soit stocké en mémoire programme plutôt qu’en RAM. Le principal avantage est l’économie de RAM qui en résulte. Pour lire une valeur de ce tableau, on procède comme suit:

  • Ecrire l’instruction d’accès au tableau: aSeuilsTouches[n];
  • Prendre son adresse: &(aSeuilsTouches[n]);
  • Passer le tout à une fonction d’accès à la mémoire programme: seuil = (uint16_t)pgm_read_word(&(aSeuilsTouches[n]));
  • D’autres fonctions permettant de lire des bytes ou des chaînes de caractères sont définies dans la librairie progmen.h.

Si la valeur lue est plus petite que le seuil courant, la position dans le tableau donne le numéro du poussoir pressé, d’où les définitions des constantes correspondant aux poussoirs ci-dessus. On peut alors sortir de la fonction.

Si aucun bouton n’est pressé la dernière instruction est exécutée:
return(PAS_DE_POUSSOIR);

Cette fonction s’inspire largement de l’exemple donné par C. Tavernier dans son livre: Arduino, maîtrisez sa programmation et ses cartes d’interfaces (shields). La principale différence ici est la déclaration du tableau de constantes en mémoire programme.

Retour vers la table des matières

Gare du réseau d’un ami: programmation

Structure générale

La technique mise en oeuvre est la programmation synchrone qui permet tout à la fois d’avoir plusieurs tâches donnant l’illusion de se dérouler en parallèle, d’autre part de se passer des interruptions. La structure du programme principal est simplement:

while(1)
{
   Tâche1;
   Tâche2;
   ...
   TâcheN;

   // Attendre que le timer0 atteigne la valeur correspondant à 10ms,
   // puis le réinitialiser.
   while(TCNT0 < MAX_TIMER0);
   TCNT0 = 0;
} // while

Les tâches s’exécutent l’une après l’autre, puis on attend la fin de la période de 10 ms. Pour compter 10 ms avec le timer0, les définitions suivantes sont nécessaires:

#define	CONFIG_TIMER0	(_BV(CS01)|_BV(CS00))
#define	MAX_TIMER0	156

...
    // À placer avant d'entrer dans la boucle principale.
    TCCR0 = CONFIG_TIMER0;

La valeur CONFIG_TIMER0, écrite dans le registre de contrôle TCCR0 du timer0 divise la fréquence d’horloge de 1 MHz par 64. Avec ce paramétrage du pre-scaler, la valeur MAX_TIMER0 de 156, utilisée dans la boucle principale compte 10 ms. Il aurait été possible d’utiliser une instruction _delay_ms() ou delay dans l’environnement Arduino, mais ce n’est applicable que lorsque le temps d’exécution des tâches est beaucoup plus petit que 10 ms car le temps d’exécution s’ajoute au temps d’attente, l’instruction _delay_ms() ne s’exécutant pas en parallèle. Il y a donc une dérive temporelle plus ou moins importante et plus ou moins gênante.

Le timer intégré au micro-contrôleur ne présente pas ce problème car il tourne parallèlement à l’exécution des instructions par le micro-contrôleur. Ainsi, les périodes font exactement 10 ms.

Ce principe impose toutefois 2 contraintes:

  • Les tâches ne doivent pas monopoliser le processeur et donc rendre la main rapidement. Nous verrons plus loin quelles techniques permettent de garantir cela.
  • Le temps d’exécution cumulé des N tâches ne doit pas excéder 10 ms. Si cette contrainte ne peut pas être satisfaite, on peut soit augmenter la période, soit configurer le processeur pour que son oscillateur interne tourne plus vite: 2, 4 et 8 MHz peuvent aussi être choisi en plus de 1 MHz qui est la valeur par défaut.

Utilisation des protothreads

Les tâches peuvent être implantées sous formes de machines à états finis ou, lorsque la séquence d’instructions est largement séquentielle, sous forme de protothreads.

Les protothreads, un concept proposé par Adam Dunkels (cf sa page: protothreads qui décrit le principe et propose la librairie en téléchargement), permettent un style de programmation quasi concurrente même sur des micro-contrôleurs 8 bits tels que les Atmega et Attiny. C’est aussi utilisable dans l’environnement Arduino ainsi que le montre le tutorial suivant: Threading in Arduino.

Un premier exemple de protothread

Ce premier exemple de protothread fait clignoter une LED. Rien de bien nouveau par rapport au premier programme que tout amateur de micro-contrôleur développe, si ce n’est que la LED pourra clignoter tandis que le micro-contrôleur déroulera d’autres tâches.

Le programme développé pour le contrôle de la gare a un protothread qui fait clignoter la LED présente sur la carte micro-contrôleur pour indiquer que tout se déroule bien et que le programme n’est pas parti dans une boucle infinie, auquel cas la LED ne clignoterait plus.

Après avoir téléchargé les fichiers permettant d’utiliser les protothreads (cf. le lien plus haut), ajouter l’instruction suivante dans le programme:

#include <pt.h>

Les protothreads sont implantés uniquement sous formes de macros du préprocesseur C, il n’y a donc que des fichiers .h à recopier dans le même répertoires que les fichiers .c et .h du programme.

Il faut ensuite écrire les déclarations suivantes:

#define	LED_CARTE	PB7
...
#define	SET_LED(led)	(PORTB |= _BV(led))
#define CLR_LED(led)	(PORTB &= ~_BV(led))
...
#define PERIODE	10
#define	DELAI_WATCHDOG	(500/PERIODE)
...
static struct pt ptWatchdogLed;
static uint16_t	nTimerWatchdog;
...
nTimerWatchdog = 0;

PT_THREAD(WatchdogLed(struct pt *ppt))
{
    if(nTimerWatchdog > 0) nTimerWatchdog--;

    PT_BEGIN(ppt);
    while(1)
    {
        SET_LED(LED_CARTE);

        nTimerWatchdog = DELAI_WATCHDOG;
        PT_WAIT_UNTIL(ppt, nTimerWatchdog == 0);

        CLR_LED(LED_CARTE);

        nTimerWatchdog = DELAI_WATCHDOG;
        PT_WAIT_UNTIL(ppt, nTimerWatchdog == 0);
    } // while
    PT_END(ppt);
} // WatchdogLed

Une petite explications quant au nom de cette fonction s’impose. Un watchdog ou chien de garde en français est un mécanisme surveillant le bon fonctionnement d’un système informatique.

Le paramètre passé à cette fonction permet de mémoriser l’endroit où reprendre lors du prochain appel, cette fonction est appelée dans la boucle principale toutes les 10 ms.

Le fonctionnement est le suivant:

  • Le code entre le début et l’instruction PT_BEGIN(ppt) est exécuté à chaque appel. Il s’agit ici de décrémenter le timer nTimerWatchdog qui est en fait un compteur du nombre d’appels de la fonction. Comme la fonction est appelée toutes les 10 ms, le compteur permet de mesurer le temps par tranches de 10 ms.
  • Lors du premier appel, on entre dans la fonction pour appeler SET_LED(LED_CARTE) une macro qui met à 1 la sortie correspondant à la LED de la carte. Cette macro a le même effet que l’appel digitalWrite(LED_CARTE, HIGH) de l’Arduino.
  • Le compteur nTimerWatchdog est ensuite initialisé avec la valeur DELAI_WATCHDOG, cette constante étant définie comme 500/PERIODE, soit 500 ms / 10 ms = 50 itérations.
  • L’instruction PT_WAIT_UNTIL(ppt, nTimerWatchdog == 0) est très intéressante. Son premier paramètre permet de mémoriser l’endroit courant du code. Son second paramètre est une condition booléenne.
  • Si la condition booléenne est vraie, on passe à l’instruction suivante.
  • Si elle est fausse, on saute directement à l’instruction PT_END(ppt).
  • À l’appel suivant de la fonction, le compteur nTimerWatchdog est décrémenté car le code avant l’instruction PT_BEGIN(ppt) est exécuté à tous les appels.
  • À partir de l’instruction PT_BEGIN(ppt), on saute directement à l’instruction PT_WAIT_UNTIL(ppt, nTimerWatchdog == 0) où on était resté lors du précédent appel.
  • À nouveau la condition est testée. Si maintenant le compteur nTimerWatchdog vaut zéro, cela signifie que 500 ms se sont écoulées depuis l’initialisation de nTimerWatchdog.
  • On passe à l’instruction suivante: CLR_LED(LED_CARTE) qui a pour effet d’éteindre la LED, ce qui correspond à digitalWrite(LED_CARTE, LOW) pour Arduino.
  • On arrive ensuite sur la seconde initialisation du compteur nTimerWatchdog pour attendre pendant que la LED est enclenchée.
  • L’instruction PT_WAIT_UNTIL(ppt, nTimerWatchdog == 0) qui suit a exactement le même comportement. Au bout de 50 appels de la fonction, on continue. Comme on est dans une boucle infinie, on revient au début du bloc while et on allume la LED.

La structure de la fonction WatchdogLed est très proche du programme de clignotement de LED que tout le monde connaît. Les principales différences sont la présence des instructions PT_BEGIN(ppt) et PT_END(ppt) pour délimiter la zone de code où on souhaite mémoriser la dernière instruction exécutée à chaque appels, ainsi que l’utilisation du bloc suivant à la place d’une instruction _delay_ms ou delay (Arduino):

nTimerWatchdog = DELAI_WATCHDOG;
PT_WAIT_UNTIL(ppt, nTimerWatchdog == 0);

Ce qui fait tout l’intérêt de ce mécanisme c’est que l’appel à PT_WAIT_UNTIL ne bloque pas, il retourne tout de suite à l’appelant si la condition est fausse. Ce qui n’est évidemment pas le cas avec les fonctions _delay_ms ou delay.

L’utilisation de cette fonction est très simple.

PT_INIT(&ptWatchdogLed);
...
while(1)
{
   WatchdogLed(&ptWatchdogLed);

   Tâche2;
   ...
   TâcheN;

   // Attendre que le timer0 atteigne la valeur correspondant à 10ms,
   // puis le réinitialiser.
   while(TCNT0 < MAX_TIMER0);
   TCNT0 = 0;
} // while

Ainsi, comme le protothread WatchdogLed ne bloque plus, il « rend la main » au micro-contrôleur ce qui lui permet d’appeler les autres tâches dans la boucle principale.

En plus de l’appel PT_WAIT_UNTIL, il existe aussi PT_WAIT_WHILE qui permet d’attendre (et donc de sauter au PT_END tant qu’une condition est vraie et poursuivre dès que la condition est fausse. Mentionnons aussi PT_YIELD qui permet de sauter au PT_END sans aucune condition, ce qui peut parfois s’avérer utile.

Remarques importantes

Quelques précautions sont indispensables pour bien utiliser les protothreads.

  • Ne jamais employer l’instruction switch dans un protothread. La raison est que l’instruction switch est utilisée par les macros implantant les protothreads pour permettre la mémorisation du dernier emplacement et les sauts.
  • Les variables déclarées localement dans la fonction sont perdues entre chaque appel. Si on a besoin de conserver une valeur entre 2 appels, il est impératif de déclarer une variable hors de la fonction du protothread. Il existe un cas particulier que nous verrons dans un autre article.
  • Il n’est pas possible d’appeler PT_WAIT_UNTIL, PT_WAIT_WHILE et PT_YIELD dans une fonction appelée depuis un protothread. Il faut utiliser la notion de sous-protothread, utilisée dans ce projet par le protothread EtablirItineraires.

Utilisation dans le cadre de ce projet

Pour le système gérant la gare, 4 protothreads ont été développés:

  • WatchdogLed qui a fait l’objet de l’explication ci-dessus.
  • LireEntrees pour lire les poussoirs ainsi que les autres entrées du système.
  • GererEtats pour gérer la machine à états finis de la gare.
  • EtablirItineraires pour commander les relais en fonction de l’état.

Le programme principal a donc la structure suivante:

...
PT_INIT(&ptWatchdogLed);
PT_INIT(&ptLireEntrees);
PT_INIT(&ptGererEtats);
PT_INIT(&ptEtablirItineraires);
...
while(1)
{
    // Ce protothread permet de voir qu'aucun autre protothread
    // ne monopolise le processeur.
    WatchdogLed(&ptWatchdogLed);

    LireEntrees(&ptLireEntrees);
    GererEtats(&ptGererEtats);
    EtablirItineraires(&ptEtablirItineraires);
    CommanderRelais();

    // Attendre la fin de la période et passer à la suivante.
    while(TCNT0 < MAX_TIMER0);
    TCNT0 = 0;
} // while

Les 4 protothreads communiquent entre eux par des variables globales. Selon les bonnes pratiques de développement logiciel, ce n’est pas ce qu’il y a de mieux, mais c’est plus optimal sur un micro-contrôleur 8 bits!

La fonction CommanderRelais est appelée dans la boucle principale pour rafraîchir le contenu des registres à décalage. Comme cette fonction n’a pas besoin de mémoriser son état, elle n’est pas implantée sous forme de protothread. Elle est par ailleurs aussi appelée dans le protothread EtablirItineraires.

Retour à la table des matières

Gare du réseau d’un ami: électronique

Réalisation électronique

Technologie

Même s’il devrait être possible de réaliser une matrice de diodes, je propose une solution à microcontrôleur (famille AVR) avec des cartes périphériques commandant des relais pour contrôler la distribution des alimentations tractions sur les zones d’aiguilles, les zones d’arrêt et les feux.

Pour la commande des aiguilles, il y aura certainement 2 sorties par aiguille avec relais qui produiront chacune une impulsion plutôt que de se fier aux contacts de fins de course. Le microcontrôleur enclenchera les aiguilles d’un itinéraire l’un après l’autre, ce qui réduira le courant à fournir par l’alimentation.

Le multiplexage des sorties se fera très certainement à l’aide de registres à décalage, pouvant être chaînés, dont le câblage est plus simple que le Charlieplexing.

Pour lire l’état des poussoirs, je pense utiliser l’astuce du clavier analogique. Les poussoirs changent la configuration d’un réseau de résistances pour varier le coefficient d’un diviseur de tension. La tension résultante est ensuite lue à l’aide d’un convertisseur A/D.

Les poussoirs des voies seront regroupés sur une entrée analogique, ceux des 2 extrémités sur une autre entrée analogique. Ainsi, il sera possible de presser 2 poussoirs à la fois, un poussoir voie, un poussoir extrémité (gauche ou droite).

Il me faudra donc un Atmega8 au moins car il dispose d’un convertisseur A/D et de suffisamment de mémoire programme (8 KB).

Le système comportera 4 cartes:

  • une carte processeur
  • 3 carte à 16 relais

Schémas des cartes relais

J’ai décidé d’apprendre à utiliser le logiciel Kicad pour ce projet. Voici les schémas d’une carte à 16 relais. Chaque carte comporte 2 registres à décalage 75HC595. Chaque sortie du registre décalage aboutit sur une résistance, une LED à faible consommation (2 mA) pour indiquer l’état de la sortie et aider à la mise au point; ainsi qu’un transistor. On n’oublie pas la diode de roue libre. Le schéma ne le montre pas, mais chaque registre à décalage comporte un condensateur de découplage de 100 nF en parallèle sur ses pattes d’alimentation !

GroupeRelais_SCH_Main

Le schéma principal se décompose en 2 sous-schémas.

GroupeRelais_SCH_LSB GroupeRelais_SCH_MSB

L’alimentation est 12V continu. Le 5V nécessaire pour les registres à décalage est produit à l’aide d’un régulateur 7805.

Les composants de la carte à 16 relais ont été placés et connectés avec Kicad. En vue de l’utilisation d’une carte à pastilles (permet une plus grande densité) j’ai détourné à cet effet l’éditeur de circuit imprimé de Kicad. Le 1er essai de placement montre que les 16 relais, les 2 registres à décalage et les autres composantes tiennent sur une carte de 160 * 100 mm, plutôt une bonne nouvelle. Pour gagner de la place, je conserve les connexions des contacts des relais de part et d’autre de chacun d’eux. Les connexions correspondront à des fils entre pastilles plutôt qu’à des pistes de cuivre sur un circuit imprimé. Une fois les connexions terminées, cela donne ceci.

GroupeRelais_carte

Réalisation des cartes relais

Avec Jappy (à gauche sur la photo), nous avons passé plusieurs soirées à la réalisation de ces cartes.

Soudures_cartes

Pour obtenir 3 cartes comme celle-ci:

GareJappy02

À l’aide d’un programme de test, on peut voir que la carte fonctionne bien.

Schéma de la carte microcontrôleur

GareJappy_Atmega8Schema

L’élément central de cette carte est le micro-contrôleur Atmega8. Comme il n’est pas nécessaire d’avoir une grande précision des temps, son oscillateur interne fera l’affaire, donc pas de quartz externe. En périphérie, l’Atmega8 comporte les éléments suivants:

  • Un régulateur 7805 accompagné des condensateurs habituels pour produire du 5 V à partir de l’alimentation externe 12 V.
  • Une résistance pull-up sur la patte reset pour le démarrage.
  • Un bouton poussoir, initialement prévu pour ré-initialiser l’état, il a finalement été reconverti en aide à la mise au point du programme.
  • Une LED dont le clignotement montre que le programme tourne correctement et ne reste pas bloqué dans une boucle infinie.
  • Un connecteur ISP 6 broches pour relier la carte à un programmateur in situ.
  • 2 chaînes de résistances permettant de relier 2 * 6 poussoirs au microcontrôleur sur 2 entrées du multiplexeur vers le convertisseur analogique-numérique. Le principe du clavier analogique est très bien décrit par Jean-Luc dans l’article suivant de son blog: Plusieurs boutons poussoirs sur une entrée analogique. Le calcul des résistances est décrit dans cet article: Connecter plusieurs boutons poussoir à un microcontrôleur.
  • 3 sorties pour commander les registres à décalage des cartes à relais.
  • 2 sorties pour contrôler les cantons d’entrée côtés Lausanne et Berne.
  • 2 entrées pour tester l’état des cantons de sortie côtés Lausanne et Berne.
  • 3 LEDS pour afficher les informations suivantes au TCO: mode manoeuvre, itinéraire en cours d’établissement, attente de la pression sur le second poussoir.

Réalisation de la carte micro-contrôleur

Toujours à l’aide de KiCad, les composants sont « implantés » sur une carte à pastilles.

CarteAtmega8Brd

Une fois réalisée, cette carte donne ceci.

dsc_2411

Pour la mise au point du programme et le test, d’anciens blocs de poussoirs Märklin remplacent les poussoirs du TCO.

dsc_2410

Il ne reste plus qu’à développer le programme…

Retour vers la table des matières

Gare du réseau d’un ami: principes

Introduction

Cet article, repris du forum du N, décrit la réalisation du système de contrôle de la gare du réseau N d’un ami.

Plan de voies et principes généraux

GareJappySpec

  • La gare s’inspire de la celle de Palézieux sur la ligne Lausanne – Berne. Lausanne est à gauche, Berne à droite.
  • Les voies 1 et 2 ne peuvent être parcourues que par les trains venant de Berne direction Lausanne.
  • Les voies 3, 4, 5 peuvent être parcourues par les trains circulant dans les 2 sens grâce aux 2 bretelles aux 2 extrémités de la gare.
  • Les voies 1, 2, 3, 4, 5 disposent chacune d’une alimentation. Les voies 6, 7, 8, 9 ont une alimentation unique.
  • Les aiguilles de la partie gauche de la gare sont alimentées par l’alimentation de la voie accessible par l’itinéraire choisi. Il en est de même pour la partie droite de la gare.
  • Les voies 1, 2 ont une zone d’arrêt à leurs extrémités gauche. Chaque zone d’arrêt dispose d’un feu.
  • Les voies 3, 4, 5 ont une zone d’arrêt à leurs 2 extrémités. Chaque zone d’arrêt dispose d’un feu.
  • Les zones d’arrêt ne fonctionnent qu’avec des rames classiques, avec une loco en tête. Attention aux rames réversibles ou aux autorails.
  • Venant de Lausanne et venant de Berne, il y a un canton. Il est libre si le train peut accéder à une voie de gare et il est automatiquement bloqué après le franchissement.
  • Si les cantons direction Lausanne et direction Berne sont bloqués, la zone d’arrêt de la voie choisie pour sortir de la gare reste bloquée.
  • Il n’y a pas de détection des trains en voies de gare. Par contre, le franchissement des feux à l’entrée de la gare doit être détecté. Des ILS seront placés de chaque côté.
  • Lorsque l’une des voies 1 ou 2 est reliée direction Lausanne et provenance Berne, elle est en mode transit. Le canton à l’entrée côté Berne est automatiquement réactivé lorsque le canton de sortie côté Lausanne passe de l’état occupé à l’état libre. Le même comportement s’applique aux voies 3 à 5 reliées dans le sens Berne – Lausanne ou Lausanne – Berne. Dans ce dernier cas, le canton d’entrée côté Lausanne est automatiquement réactivé lorsque le canton de sortie direction Berne passe de l’état occupé à l’état libre.
  • Un poussoir supplémentaire permet d’enclencher et déclencher le mode manoeuvre. Dans ce mode, on ne tient pas compte des cantons de sortie et les cantons d’entrée restent bloqués. Le mode manoeuvre est visualisé par une LED au TCO.
  • Le dernier état de la gare est sauvegardé afin d’être repris au prochain démarrage. Il est en outre possible de tout réinitialiser en pressant un bouton côté Lausanne suivi d’un bouton côté Berne.
  • L’établissement d’un itinéraire se fait par 2 pressions successives sur un bouton d’entrée suivi d’un bouton de voie ou vice-versa. Le délai maximum entre ces 2 pressions est de 5 secondes, visualisé par l’allumage d’une LED au TCO.
  • Les aiguilles de voies de garage ainsi que la rotonde sont commandés à part.
  • Pas de retour au TCO, les feux donneront l’information et ils sont très proches du TCO !
  • Les points noirs représentent les boutons poussoirs de commande des itinéraires selon le principe: origine – destination.
  • Il peut y avoir jusqu’à 4 mouvements de trains simultanés, par exemple:
    • 1 entrée venant de Berne sur voie 1.
    • 1 sortie direction Lausanne sur voie 2.
    • 1 entrée venant de Lausanne sur voie 3.
    • 1 sortie direction Berne sur voie 4.

Alimentations traction des voies de gare

Les voies 1 et 2 sont câblées comme suit. Chacune d’elle a, à son extrémité gauche, une zone d’arrêt alimentée par son transfo, via un relais. Le relais commande aussi le feu. L’alimentation du feu est ici montrée via la sortie accessoire du transfo, mais toutes les autres variantes sont possibles: courant continu, leds, masse commune, etc. Le schéma du relais est tiré du site: www.sonelec-musique.com.

GareJappyVoies12

Les voies 3, 4 et 5 disposent d’une zone d’arrêt à chaque extrémité car, contrairement aux voies 1 et 2, elles peuvent être parcourues dans les 2 sens.

GareJappyVoies345

Alimentations traction des zones d’aiguilles

Les éclisses isolantes sur les 2 voies délimitent, outre les voies de gare, 4 zones d’aiguilles nommées: Lsne 1, Lsne 2, Berne 1, Berne 2. En fonction de l’itinéraire choisi, ces zones doivent être alimentées par l’une ou l’autre des alimentations de voies de gare.

GareJappyZonesAiguillages

La zone Lsne 1 peut être alimentée par les voies 3, 4, 5, ainsi que par les voies 6, 7, 8 et 9 qui ont une alimentation commune. On remarque que chaque relais commute les 2 fils des alimentations traction. Cela évite tout risque de court-circuit si on remplace les transfos traditionnels par des montages à LM317 suivis d’un inverseur, tous reliés au même transfo global.

GareJappyLsne1

La zone Lsne 2 peut recevoir l’alimentation attribuée à Lsne 1 ou l’une des alimentations des voies 1 et 2.

GareJappyLsne2

La zone Berne 1 est un peu plus simple que Lsne 1 car elle ne peut être alimentée que les par les voies 3, 4 et 5.

GareJappyBerne1

La zone Berne 2 peut recevoir l’alimentation attribuée à Berne 1 ou l’une des alimentations des voies 1 et 2.

GareJappyBerne2

Il est ainsi possible de déterminer le nombre de relais nécessaires pour commuter les alimentations tractions:

  • Voies 1, 2: 2 relais.
  • Voies, 3, 4, 5: 3 * 2 = 6 relais.
  • Lsne 1: 3 relais.
  • Lsne 2: 2 relais.
  • Berne 1: 2 relais.
  • Berne 2: 2 relais.

Soit un total de 17 relais et autant de sorties digitales. En ajoutant les relais de commande des aiguilles, on atteint une quarantaine de relais.

Retour vers la table des matières