LOCODUINO

Aide
Forum de discussion
Dépôt GIT Locoduino
Flux RSS

mardi 19 mars 2024

Visiteurs connectés : 47

Le Locoduinodrome

La carte Satellite V1 (3)

La messagerie et l’interface SAM V1

.
Par : bobyAndCo, Dominique, Jean-Luc, Thierry

DIFFICULTÉ :

Après les principes fondateurs de la carte Satellite V1 et la description du matériel, nous abordons maintenant les aspects logiciels externes au Satellite, juste avant de décrire le logiciel du Satellite lui-même. S’agissant plus d’un middleware que d’une API, nous avons décidé de nommer cette couche logicielle : le Satellite Abstraction Middleware qui doit être appelé par les applications.

... SAM pour les intimes !

<

Cet article comporte 3 parties :

  • Les formats des messages CAN pour le projet Locoduinodrome
  • Le SAM
  • Quelques exemples d’utilisation.

Les formats des messages CAN

Nous avons expliqué dans « La carte Satellite V1 - Principes fondateurs » que les cartes satellites sont vues comme des entrées-sorties déportées.

Chaque satellite a la possibilité de gérer :

  • un servomoteur pour manœuvrer une aiguille
  • 2 capteurs de position « tout ou rien » (IR, ILS, capteurs à effet Hall,...)
  • 1 détecteur de présence par consommation (intégré à la carte)
  • 9 DEL

Le mode de communication choisi consiste à échanger des messages périodiquement entre le SAM et les satellites, chaque message émis (sortant) par un satellite regroupant l’ensemble des états des capteurs sous son contrôle et chaque message reçu (entrant) par un satellite contenant l’ensemble des commandes : la position souhaitée de l’aiguille et l’état des LED qu’il pilote. Ceci évite de multiplier les messages qu’échangent les satellites avec le système de gestion et diminue le trafic sur le réseau CAN.

Les explications qui suivent supposent un minimum de connaissance des transmissions CAN, que l’on trouvera aisément dans les articles Mise en oeuvre du Bus CAN entre modules Arduino (1) et Mise en oeuvre du Bus CAN entre modules Arduino (2).

Le format des messages émis par un satellite

Les Satellites V1 émettent donc un seul message CAN de type standard (identifiant de 11 bits) pour communiquer les états du détecteur de présence et des 2 capteurs de position. L’identifiant de ce message est égal à 0x10 + le numéro de Satellite émetteur. Il vaut donc 0x10 pour le Satellite 0, 0x11 pour le 1, et ainsi de suite jusqu’à 0x1F pour le Satellite 15 (voir figure 1).

Figure 1 : Identifiant des messages CAN émis par les satellites.
Figure 1 : Identifiant des messages CAN émis par les satellites.
Cet identifiant est montré ici en binaire. La partie grisée, sur 7 bits, est fixe et qualifie le type du message. La partie blanche, sur 4 bits, reçoit le numéro du satellite de 0 à 15 dans cette version.

Vient ensuite un seul octet de données dont 3 bits sont significatifs : 1 bit pour l’état de la zone (occupée/libre), 1 bit pour le détecteur ponctuel 0 (faisceaux IR obstrué ou libre, capteur à effet Hall déclenché ou pas, etc) et 1 bit pour le détecteur ponctuel 1 comme montré à la figure 2.

Figure 2 : Unique octet de donnée des messages émis par le satellite
Figure 2 : Unique octet de donnée des messages émis par le satellite
Seuls 3 bits de poids faible sont utilisés. Lorsqu’un bit est à ’-’, cela signifie que sa valeur est indifférente.

Ce message est envoyé automatiquement par le Satellite dès qu’un changement se produit afin de minimiser la latence entre une détection et la notification du changement au système de gestion. Il est également envoyé à chaque fois qu’un message d’état est reçu du système de gestion. Ceci permet, d’une part, de synchroniser ces messages périodiques [1] sur le système de gestion pour avoir une charge réseau homogène et prédictible et, d’autre part, de savoir si un Satellite est vivant et répond normalement.

Exemple : Le Satellite 6 détecte un véhicule dans la zone dont il est responsable mais les deux détecteurs ponctuels ne détectent rien. Le message qu’il envoie a le format montré à la figure 3.

Figure 3 : exemple de message envoyé par le Satellite 6
Figure 3 : exemple de message envoyé par le Satellite 6
Le numéro du Satellite émetteur est dans les 4 bits de poids faible de l’identifiant. 0110 en binaire correspond à la valeur 6 en décimal. Les 5 bits de poids fort de l’octet 0 étant indifférents, ils sont mis à 0. Le bit 2 à 1 signifie que la zone est occupée. Les bits 1 et 0 à 0 indiquent qu’aucun des capteurs ponctuels ne détecte un véhicule.

Soit en binaire : 00000100

Le format des messages reçus par un satellite

Les Satellites V1 reçoivent un seul message spécifiant les états des actionneurs que la carte contrôle. L’identifiant de ce message standard est égal à 0x20 + le numéro du satellite destinataire. Il vaut donc 0x20 pour le Satellite 0, 0x21 pour le 1, et ainsi de suite jusqu’à 0x2F pour le Satellite 15 (voir figure 4).

Figure 4 : Identifiant des messages CAN reçus par les satellites.
Figure 4 : Identifiant des messages CAN reçus par les satellites.
Egalement en binaire, l’identifiant est formé d’une partie fixe grisée qui qualifie le type de message et d’une partie variable en blanc qui donne le numéro du Satellite destinataire.

La consigne des actionneurs comprend 3 octets formés de 9 fois 2 bits d’état des DEL plus 1 bit de position pour le servo. L’état de chaque DEL est décrit par 2 bits pour permettre les 3 états : éteint, allumé, clignotant. L’état du servo est décrit par un seul bit : position horaire ou trigonométrique qui correspondent à des positions maximales (à régler pour chaque satellite) quand le sens de déplacement est sens horaire ou sens trigonométrique. Le détail est présenté à la figure 5. Comme la position de l’aiguille dépend du montage mécanique du servo, savoir quelle position du servo correspond à quelle position de l’aiguille relève du système de gestion.

Figure 5 : Format des messages de consigne reçus par un Satellite
Figure 5 : Format des messages de consigne reçus par un Satellite
L’octet 0 donne l’état des LEDs 0 à 3, l’octet 1 l’état des LEDs 4 à 7 et l’octet 3 l’état de la LED 8 et du servo.

Pour les raisons exposées dans « La carte Satellite V1 - Principes fondateurs », ce message est envoyé périodiquement par le système de gestion, toutes les 100ms par exemple.

Exemple : la consigne pour le Satellite 7 est de mettre le servomoteur à la position horaire et d’avoir toutes les DEL éteintes sauf la 0 qui clignote et la 6 qui est allumée. Le message de consigne correspondant est présenté figure 6.

Figure 6 : message de consigne envoyé au Satellite 7
Figure 6 : message de consigne envoyé au Satellite 7
Dans ses 4 bits de poids faible, l’identifiant contient le numéro du Satellite (0111 en binaire représente 7 en décimal). Les 3 octets de données spécifient les états des LEDs et du servo. Les bits indifférents sont ici mis à 0.

Le Satellite Abstraction Middleware (SAM)

Le SAM est l’interface entre les satellites et un unique gestionnaire.

Ce qu’on appelle « gestionnaire » ici, est le système de gestion des circulations du réseau, le cerveau du réseau en quelque sorte. Il peut prendre différentes formes dont celle d’une carte Arduino avec interface CAN (ce qui est notre but ici) :

Figure 7 : Le gestionnaire sur Arduino
Figure 7 : Le gestionnaire sur Arduino
L’Arduino contient à la fois le gestionnaire, le middleware SAM et l’interface CAN

Il peut aussi prendre la forme d’un logiciel gestionnaire sur ordinateur (tel que JMRI par exemple), pourvu qu’il puisse émettre et recevoir les messages CAN par l’intermédiaire d’une passerelle (gateway) comme une passerelle CAN/WiFI-Ethernet ou CAN/Bluetooth :

Figure 8 : JMRI comme système de gestion des circulations
Figure 8 : JMRI comme système de gestion des circulations
Le middleware SAM en C++ ne peut pas être intégré à JMRI en Java. Une passerelle est nécessaire.

Une telle passerelle n’est pas décrite dans cet article.

Ce gestionnaire pourra également prendre la forme d’un TCO pourvu qu’il n’interfère pas avec un autre gestionnaire car les doubles commandes des appareils de voie et des signaux ne sont pas envisageables.

Ce gestionnaire est donc un système généralement centralisé qui, à partir des informations sur les états occupés/libres des cantons, sur la position des appareils de voie et sur une représentation informatique de la structure du réseau, va déterminer l’état des signaux, les positions des aiguilles, donc recevoir les états des satellites et leur envoyer des commandes.

Il pourra évidemment faire bien plus, comme le suivi des trains, le bloc système, les itinéraires, etc.. en envoyant des commandes aux trains, mais c’est un autre domaine qui sort de cet article.

Le SAM est donc du code qui s’interface à votre gestionnaire pour lui donner accès aux satellites de façon simple.

On l’a vu au premier article de la série (La carte Satellite V1 (1)), comme les satellites ne sont pas spécialisés, ni sur le plan matériel, ni sur le plan logiciel, mais que leurs fonctions sont hétérogènes, leur spécialisation est faite par le gestionnaire, via le bus CAN. Car ces satellites sont des entrées/sorties déportées.

La correspondance entre les entrées-sorties des satellites et les équipements de voie est totalement libre : c’est à dire, par exemple, qu’un feu à 9 ampoules pourrait être géré par 2 Satellites, l’un pour 4 ampoules, l’autre pour 5. C’est une hypothèse d’école, bien sûr, mais ça peut arriver sur un réseau réel pour éviter d’ajouter un satellite quand il reste de la place dans un autre à proximité. Le fait de le voir comme une entité unique est de nature logicielle dans SAM, mais non matérielle. Il n’y a pas de configuration dans les satellites, ce qui élimine une grande partie de la complexité.

Pour gérer cette architecture matérielle extensible à volonté, le SAM définit un ensemble d’objets. Ces objets représentent les objets physiques du réseau (zones, aiguillages, feux, comme des proxy). La façon dont ces objets communiquent avec les nœuds sur le réseau CAN n’est pas du ressort du gestionnaire qui ne voit que le SAM. 

SAM contient donc des classes d’interfaçage pour chaque type d’objet :

  • Une classe de base PointWrapper dont une instance est affectée à chaque commande d’aiguille.
  • Une classe de base SignalWrapper pour chaque signal.

Comme il existe sur notre réseau plusieurs types de signaux n’ayant pas tous le même nombre de DEL et le même comportement (par exemple dans le cas d’un œilleton), SAM définit un jeu de classes dérivées dont une instance est affectée à chaque signal. nous en avons défini 4 pour les besoins du Locoduinodrome :

  • Une classe SemaphoreSignalWrapper
  • Une classe CarreSignalWrapper
  • Une classe SemaphoreRalentissementSignalWrapper
  • Une classe CarreRappelRalentissementSignalWrapper

Nous allons regarder de plus près chacune des classes.

La commande d’aiguillage

SAM définit la classe PointWrapper qui possède une méthode begin() avec comme arguments le numéro d’aiguille du gestionnaire et le numéro de satellite (puisqu’on a une seule aiguille par satellite). Une table statique indexée par le numéro d’aiguille du gestionnaire permet de retrouver l’objet PointWrapper correspondant et ce dernier connaît le message qu’il doit envoyer.

Ensuite la méthode statique setPointPosition utilise le numéro d’aiguille du gestionnaire pour indexer la table, récupérer l’objet et appeler le setPosition de l’objet qui écrira dans le message.

Exemple d’utilisation pour une commande d’aiguillage :

setPointPosition(pointId, pointPos);

pointId est le numéro d’aiguillage vu du gestionnaire et pointPos la position demandée (droit ou dévié).

Voici la déclaration de la classe PointWrapper que l’on trouve dans le fichier SatelliteWrapper.h :

class PointWrapper
{
private:
  static PointWrapper *sPointList;
  static PointWrapper **sPointTable;
  static int16_t sHigherPointNumber;

  int16_t mPointNumber;    /* numéro de l'aiguillage dans le gestionnaire */
  uint8_t mSatelliteId;    /* indentifiant du satellite                   */
  uint8_t mSatelliteIndex; /* index du satellite dans la table            */
  PointWrapper *mNext;

  void setPosition(const bool inPosition);
  void lookupMessage();

public:
  /*
   * begin construit la table indexée par le numéro d'aiguillage à partir de
   * la liste. La liste est construite par le constructeur.
   * begin demande également à chaque aiguillage de chercher leur
   * message CAN en fonction de l'identifiant du satellite.
   * À appeler dans setup.
   */
  static void begin();

  /*
   * setPointPosition positionne l'aiguillage inPointNumber à la position
   * inPosition
   */
  static void setPointPosition(const int16_t inPointNumber, const bool inPosition);

  /*
   * constructeur
   */
  PointWrapper(const uint16_t inPointNumber, const uint8_t inSatelliteId);

};

Et voici le code de la classe PointWrapper que l’on trouve dans le fichier SatelliteWrapper.cpp. Les explications sont dans les commentaires :

/*-------------------------------------------------------------
 * PointWrapper class
 *-------------------------------------------------------------
 */
PointWrapper *PointWrapper::sPointList = NULL;
PointWrapper **PointWrapper::sPointTable = NULL;
int16_t PointWrapper::sHigherPointNumber = -1;

/*
 * Constructeur.
 *
 * Chaque objet PointWrapper établit une correspondance entre l'identifiant
 * de l'aiguillage dans le gestionnaire et le numéro du satellite qui
 * contrôle cet aiguillage. Le constructeur constitue également une liste de
 * ces objets qui est ensuite exploité dans begin.
 */
PointWrapper::PointWrapper(
  const uint16_t inPointNumber,
  const uint8_t inSatelliteId
) :
mPointNumber(inPointNumber),
mSatelliteId(inSatelliteId),
mSatelliteIndex(NO_SATELLITE_INDEX)
{
  /* Ajoute l'aiguillage dans la liste */
  mNext = sPointList;
  sPointList = this;
  /* Note le max des identifiants d'aiguillage dans le gestionnaire */
  if (mPointNumber > sHigherPointNumber) sHigherPointNumber = mPointNumber;
}

/*
 * begin construit la table indexée par l'identifiant d'aiguillage dans le
 * gestionnaire et l'objet PointWrapper qui établit la correspondance
 * entre l'identifiant d'aiguillage et l'identifiant du satellite qui le
 * commande. Il n'est pas nécessaire que les identifiants soient de un en un
 * mais la consommation mémoire dépend du max des identifiants.
 */
void PointWrapper::begin()
{
  if (sHigherPointNumber != -1) {
    sPointTable = new PointWrapper*[sHigherPointNumber + 1];
    if (sPointTable != NULL) {
      for (int16_t i = 0; i <= sHigherPointNumber; i++) {
        sPointTable[i] = NULL;
      }
      PointWrapper *currentPoint = sPointList;
      while (currentPoint != NULL) {
        sPointTable[currentPoint->mPointNumber] = currentPoint;
        /* inscrit l'aiguillage dans la messagerie CAN */
        currentPoint->lookupMessage();
        currentPoint = currentPoint->mNext;
      }
    }
  }
}

void PointWrapper::setPointPosition(
  const int16_t inPointNumber,
  const bool inPosition
)
{
  if (inPointNumber >= 0 && inPointNumber <= sHigherPointNumber) {
    if (sPointTable[inPointNumber] != NULL) {
      sPointTable[inPointNumber]->setPosition(inPosition);
    }
  }
}

void PointWrapper::setPosition(const bool inPosition)
{
  if (mSatelliteIndex != NO_MESSAGE_INDEX) {
    outSatellitesMessages[mSatelliteIndex].setPointPosition(inPosition);
  }
}

void PointWrapper::lookupMessage()
{
  mSatelliteIndex = lookupMessageForId(mSatelliteId);
  if (mSatelliteIndex != NO_MESSAGE_INDEX) {
    outSatellitesMessages[mSatelliteIndex].reserve(mSatelliteId);
  }
}

Les commandes de signaux

SAM définit la classe SignalWrapper qui possède une méthode begin() avec comme arguments le numéro de signal du gestionnaire, le numéro de satellite et une série de DEL (3, 6 ou 9) affectées à ce signal qui correspond au connecteur réel sur la carte Satellite comme représenté sur figure 9.

Figure 9 : Les connecteurs pour les signaux
Figure 9 : Les connecteurs pour les signaux
3 connecteurs pour les signaux sont présents sur la carte. La broche la plus à droite peut être à 5V ou GND selon la position d’un strap pour choisir des LED à anode commune ou à cathode commune. On peut connecter 3 signaux à 3 feux ou bien un signal à 3 feux sur S1 et un à 6 sur S2 ou encore un signal à 9 feux sur S1.

Une table statique indexée par le numéro de signal du gestionnaire permet de retrouver l’objet SignalWrapper correspondant et ce dernier connaît le message où il doit écrire.

Exemple d’utilisation pour une commande de signal :

setSignalState(signalId, signalState);

signalId est le numéro de feu et signalState son état dans le gestionnaire.

Voici la déclaration de la classe SignalWrapper que l’on trouve dans le fichier SatelliteWrapper.h :

/*
 * La classe SignalWrapper est une classe abtraite représentant
 * la correspondance entre un signal du gestionnaire et le signal matériel
 * sur le satellite.
 */

/* cet enum definit le connecteur */
enum { SIGNAL_0 = 0, SIGNAL_1 = 3, SIGNAL_2 = 6 };

class SignalWrapper
{
private:
  static SignalWrapper *sSignalList;
  static SignalWrapper **sSignalTable;
  static int16_t sHigherSignalNumber;

  int16_t mSignalNumber;    /* Numéro du signal dans le gestionnaire */
  uint8_t mSatelliteId;     /* Identifiant du satellite sur lequel le signal est implanté */
  SignalWrapper *mNext;

  virtual void setState(const uint16_t inState) = 0;
  void lookupMessage();

protected:
  uint8_t mSatelliteIndex;  /* Index du satellite dans la table */
  uint8_t mSlot;            /* Slot / connecteur sur lequel le signal est connecté sur le satellite */

public:
  static void begin();
  static void setSignalState(const int16_t inSignalNumber, const uint16_t inState);

  SignalWrapper(const uint16_t inSignalNumber, const uint8_t inSatelliteId, const uint8_t inSlot);
};

Et voici le code de la classe SignalWrapper que l’on trouve dans le fichier SatelliteWrapper.cpp :

/*------------------------------------------------------------
 * SignalWrapper class
 *-------------------------------------------------------------
 */
SignalWrapper *SignalWrapper::sSignalList = NULL;
SignalWrapper **SignalWrapper::sSignalTable = NULL;
int16_t SignalWrapper::sHigherSignalNumber = -1;

void SignalWrapper::lookupMessage()
{
  mSatelliteIndex = lookupMessageForId(mSatelliteId);
  if (mSatelliteIndex != NO_MESSAGE_INDEX) {
    outSatellitesMessages[mSatelliteIndex].reserve(mSatelliteId);
  }
}

void SignalWrapper::setSignalState(
  const int16_t inSignalNumber,
  const uint16_t inState
)
{
  if (inSignalNumber >= 0 && inSignalNumber <= sHigherSignalNumber) {
    if (sSignalTable[inSignalNumber] != NULL) {
      sSignalTable[inSignalNumber]->setState(inState);
    }
  }
}

void SignalWrapper::begin()
{
  if (sHigherSignalNumber != -1) {
    sSignalTable = new SignalWrapper*[sHigherSignalNumber + 1];
    if (sSignalTable != NULL) {
      for (int16_t i = 0; i <= sHigherSignalNumber; i++) {
        sSignalTable[i] = NULL;
      }
      SignalWrapper *currentSignal = sSignalList;
      while (currentSignal != NULL) {
        sSignalTable[currentSignal->mSignalNumber] = currentSignal;
        /* inscrit l'aiguillage dans la messagerie CAN */
        currentSignal->lookupMessage();
        currentSignal = currentSignal->mNext;
      }
    }
  }
}

SignalWrapper::SignalWrapper(
  const uint16_t inSignalNumber,
  const uint8_t inSatelliteId,
  const uint8_t inSlot
) :
mSignalNumber(inSignalNumber),
mSatelliteId(inSatelliteId),
mSlot(inSlot)
{
  /* Ajoute l'aiguillage dans la liste */
  mNext = sSignalList;
  sSignalList = this;
  /* Note le max des identifiants d'aiguillage dans le gestionnaire */
  if (mSignalNumber > sHigherSignalNumber) sHigherSignalNumber = mSignalNumber;
}

Ensuite, il faut définir les classes dérivées de la classe de base SignalWrapper pour les signaux réels utilisés sur notre réseau Locoduinodrome : Sémaphore, Carré ; Sémaphore ralentissement ; Carré rappel de ralentissement.

Au préalable, les 15 types de signaux possibles sont d’abord définis dans un enum dans le fichier Feux.h :

// les feux possibles
enum {
  E   = 0,        // eteint
  Vl  = bit(0),   // 1 ou 0b0000000000000001
  A   = bit(1),   // 2 ou 0b0000000000000010
  S   = bit(2),   // 4 ou 0b0000000000000100
  C   = bit(3),   // 8 ou 0b0000000000001000
  R   = bit(4),   // 16 ou 0b0000000000010000
  RR  = bit(5),   // 32 ou 0b0000000000100000
  M   = bit(6),   // 64 ou 0b0000000001000000
  Cv  = bit(7),   // 128 ou 0b0000000010000000
  Vlc = bit(8),   // 256 ou 0b0000000100000000
  Ac  = bit(9),   // 512 ou 0b0000001000000000
  Sc  = bit(10),  // 1024 ou 0b0000010000000000
  Rc  = bit(11),  // 2048 ou 0b0000100000000000
  RRc = bit(12),  // 4096 ou 0b0001000000000000
  Mc  = bit(13),  // 8192 ou 0b0010000000000000
  D   = bit(14),  // 16384 ou 0b0100000000000000
  X   = bit(15)   // 32768 ou 0b1000000000000000
};

typedef uint16_t typeFeux; // 16 cas possibles

La signification des valeurs de feux est indiquée dans la figure suivante, l’indice "c" signifie "clignotant" et le valeur X est réservée pour un usage futur :

Figure 10 : Les différents types de signaux dans l'enum
Figure 10 : Les différents types de signaux dans l’enum
L’indice "c" signifie "clignotant"

Les classes dérivées suivantes sont codées dans le fichier SatelliteWrapper.cpp.

Classe dérivée pour les sémaphores :

/*
 * Wrapper pour les sémaphores : 3 feux, jaune, rouge, vert
 */
class SemaphoreSignalWrapper : public SignalWrapper
{
private:
  virtual void setState(const uint16_t inState);

public:
  SemaphoreSignalWrapper(const uint16_t inSignalNumber, const uint8_t inSatelliteId, const uint8_t inSlot) :
    SignalWrapper(inSignalNumber, inSatelliteId, inSlot) {}
};

/*-----------------------------------------------------------------
 * Wrapper pour les sémaphores : 3 feux, jaune, rouge, vert
 *
 * L'ordre des LED sur le satellite est :
 * - jaune
 * - rouge
 * - vert
 */
void SemaphoreSignalWrapper::setState(const uint16_t inState)
{
  if (mSatelliteIndex != NO_MESSAGE_INDEX) {
    AbstractCANOutSatelliteMessage &message = outSatellitesMessages[mSatelliteIndex];
    message.setLED(mSlot, inState & A ? LED_ON : LED_OFF);      /* jaune */
    message.setLED(mSlot + 1, inState & S ? LED_ON : LED_OFF);  /* rouge */
    message.setLED(mSlot + 2, inState & Vl ? LED_ON : LED_OFF); /* vert  */
  }
}

Classe dérivée pour les carrés :

/*-----------------------------------------------------------------
 * Wrapper pour les carrés
 */
class CarreSignalWrapper : public SignalWrapper
{
private:
  virtual void setState(const uint16_t inState);

public:
  CarreSignalWrapper(const uint16_t inSignalNumber, const uint8_t inSatelliteId, const uint8_t inSlot) :
    SignalWrapper(inSignalNumber, inSatelliteId, inSlot) {}
};

/*-----------------------------------------------------------------
 * Wrapper pour les carrés
 *
 * L'ordre des LED sur le satellite est :
 * - jaune
 * - rouge
 * - vert
 * - rouge2
 * - oeilleton (blanc)
 */
void CarreSignalWrapper::setState(const uint16_t inState)
{
  if (mSatelliteIndex != NO_MESSAGE_INDEX) {
    AbstractCANOutSatelliteMessage &message = outSatellitesMessages[mSatelliteIndex];
    message.setLED(mSlot, inState & A ? LED_ON : LED_OFF);        /* jaune */
    message.setLED(mSlot + 1, inState & S ? LED_ON : inState & C ? LED_ON : LED_OFF);    /* rouge */
    message.setLED(mSlot + 2, inState & Vl ? LED_ON : LED_OFF);   /* vert  */
    message.setLED(mSlot + 3, inState & C ? LED_ON : LED_OFF);    /* rouge2 */
    message.setLED(mSlot + 4, inState & C ? LED_OFF : inState ? LED_ON : LED_OFF); /* oeilleton */
  }
}

Classe dérivée pour les sémaphores avec ralentissement :

/*-----------------------------------------------------------------
 * Wrapper pour les semaphores avec ralentissement
 */
class SemaphoreRalentissementSignalWrapper : public SignalWrapper
{
private:
  virtual void setState(const uint16_t inState);

public:
  SemaphoreRalentissementSignalWrapper(const uint16_t inSignalNumber, const uint8_t inSatelliteId, const uint8_t inSlot) :
    SignalWrapper(inSignalNumber, inSatelliteId, inSlot) {}
};

/*-----------------------------------------------------------------
 * Wrapper pour les semaphores avec ralentissement
 *
 * L'ordre des LED sur le satellite est :
 * - jaune
 * - rouge
 * - vert
 * - jaune2
 * - jaune3
 */
void SemaphoreRalentissementSignalWrapper::setState(const uint16_t inState)
{
  if (mSatelliteIndex != NO_MESSAGE_INDEX) {
    AbstractCANOutSatelliteMessage &message = outSatellitesMessages[mSatelliteIndex];
    message.setLED(mSlot, inState & A ? LED_ON : LED_OFF);      /* jaune */
    message.setLED(mSlot + 1, inState & S ? LED_ON : LED_OFF);  /* rouge */
    message.setLED(mSlot + 2, inState & Vl ? LED_ON : LED_OFF); /* vert  */
    message.setLED(mSlot + 3, inState & R ? LED_ON : inState & Rc ? LED_BLINK : LED_OFF);  /* jaune2 */
    message.setLED(mSlot + 4, inState & R ? LED_ON : inState & Rc ? LED_BLINK : LED_OFF);  /* jaune3 */
  }
}

Classe dérivée pour les carrés avec rappel de ralentissement :

/*-----------------------------------------------------------------
 * Wrapper pour les carres avec rappel ralentissement
 */
class CarreRappelRalentissementSignalWrapper : public SignalWrapper
{
private:
  virtual void setState(const uint16_t inState);

public:
  CarreRappelRalentissementSignalWrapper(const uint16_t inSignalNumber, const uint8_t inSatelliteId, const uint8_t inSlot) :
    SignalWrapper(inSignalNumber, inSatelliteId, inSlot) {}
};

/*-----------------------------------------------------------------
 * Wrapper pour les carrés avec rappel ralentissement
 *
 * L'ordre des LED sur le satellite est :
 * - jaune
 * - rouge
 * - vert
 * - rouge2
 * - jaune2
 * - jaune3
 * - oeilleton
 */
void CarreRappelRalentissementSignalWrapper::setState(const uint16_t inState)
{
  if (mSatelliteIndex != NO_MESSAGE_INDEX) {
    AbstractCANOutSatelliteMessage &message = outSatellitesMessages[mSatelliteIndex];
    message.setLED(mSlot, inState & A ? LED_ON : LED_OFF);      /* jaune */
    message.setLED(mSlot + 1, inState & C ? LED_ON : LED_OFF);  /* rouge */
    message.setLED(mSlot + 2, inState & Vl ? LED_ON : LED_OFF); /* vert  */
    message.setLED(mSlot + 3, inState & C ? LED_ON : LED_OFF);  /* rouge2 */
    message.setLED(mSlot + 4, inState & RR ? LED_ON : inState & RRc ? LED_BLINK : LED_OFF);  /* jaune2 */
    message.setLED(mSlot + 5, inState & RR ? LED_ON : inState & RRc ? LED_BLINK : LED_OFF);  /* jaune3 */
    message.setLED(mSlot + 6, inState & C ? LED_OFF : inState ? LED_ON : LED_OFF); /* oeilleton */
  }
}

Deux fichiers SatelliteConfig.h et Messaging.h contiennent les constantes de configuration utilisées dans ces classes pointWrapper et signalWrapper, ainsi que dans la classe des messages CAN qui suit :

const uint8_t NO_SATELLITE_ID = 255;
static const uint32_t NUMBER_OF_SATELLITES = 8;
static const uint32_t OUT_MESSAGE_PERIOD = 100;
static const uint8_t NUMBER_OF_LED = 9;
static const uint32_t OUT_SATELLITE_MESSAGE_TYPE = 4;
static const uint32_t NUMBER_OF_BITS_FOR_SATELLITE_ID = 3;

static const uint8_t LED_OFF = 0x0;
static const uint8_t LED_BLINK = 0x1;
static const uint8_t LED_ON = 0x2;

La gestion des messages CAN

Une classe AbstractCANOutSatelliteMessage sert à cette gestion. Elle est déclarée dans le fichier CANMessage.h.

class AbstractCANOutSatelliteMessage
{
private:
  uint8_t mSatelliteId;
  uint8_t mData[3];

public:
  AbstractCANOutSatelliteMessage();

  void setPointPosition(const bool inPosition);
  void setLED(const uint8_t inLED, const uint8_t inState);
  uint8_t satelliteId() { return mSatelliteId; }
  void reserve(const uint8_t inSatelliteId) { mSatelliteId = inSatelliteId; }
  void print();
  void println() { print(); Serial.println(); }
  void send();
};
/*
 * Lookup the message table for a satellite Id
 */

static uint8_t lookupMessageForId(const uint8_t inSatelliteId)
{
  for (uint8_t messIdx = 0; messIdx < NUMBER_OF_SATELLITES; messIdx++) {
    if (outSatellitesMessages[messIdx].satelliteId() == inSatelliteId ||
        outSatellitesMessages[messIdx].satelliteId() == NO_SATELLITE_ID)
    {
      return messIdx;
    }
  }
  return NO_MESSAGE_INDEX; /* overflow */
}
extern MCP_CAN canController;

Le constructeur :

AbstractCANOutSatelliteMessage::AbstractCANOutSatelliteMessage() :
  mSatelliteId(NO_SATELLITE_ID)
{
  /* Valeurs par défaut pour les commandes */
  setPointPosition(false);
  for (uint8_t led = 0; led < NUMBER_OF_LED; led++) {
    setLED(led, LED_OFF);
  }
}
void AbstractCANOutSatelliteMessage::setPointPosition(const bool inPosition)
{
  mData[2] &= 0x7F; /* reset the point position */
  mData[2] |= (inPosition ? 0x80 : 0x00); /* set the point position in the frame */
}
void AbstractCANOutSatelliteMessage::setLED(const uint8_t inLED, const uint8_t inState)
{
  if (inLED <= 9) {
    uint8_t byteNum = inLED >> 2;
    uint8_t offsetInByte = (inLED & 0x03) << 1;
    mData[byteNum] &= ~(0x03 << offsetInByte); /* reset */
    mData[byteNum] |= ((inState & 0x03) << offsetInByte);
  }
}
void AbstractCANOutSatelliteMessage::print()
{
  Serial.print('<');
  Serial.print(mSatelliteId);
  Serial.print("> ");
  for (uint8_t led = 0; led < 9; led++) {
    uint8_t element = led >> 2;
    uint8_t offset = (led & 0x3) << 1;
    uint8_t ledState = (mData[element] >> offset) & 0x3;
    Serial.print('[');
    switch (ledState) {
      case LED_OFF:   Serial.print(' '); break;
      case LED_ON:    Serial.print('+'); break;
      case LED_BLINK: Serial.print('^'); break;
    }
    Serial.print("] ");
  }
  Serial.print('<');
  Serial.print(mData[2] & 0x80 ? '/' : '|');
  Serial.print('>');
}
void AbstractCANOutSatelliteMessage::send()
{
  uint32_t frameId =
    (OUT_SATELLITE_MESSAGE_TYPE << NUMBER_OF_BITS_FOR_SATELLITE_ID) | mSatelliteId;
  canController.sendMsgBuf(frameId, 0, 3, mData);
}

L’intégration dans votre programme de gestion de réseau

Le dossier "SAM" est téléchargeable sur le Git Locoduino, ici. Il se trouve à l’intérieur du dossier Locoduinodrome.

Ce dossier SAM contient tout ce qu’il faut ajouter à votre programme de gestion de réseau :

  • le SAM
  • l’interface CAN qui nécessite la présence de la bibliothèque CAN_BUS_SHIELD téléchargeable également sur le Git Locoduino ici :
  • les fonctions setup() et loop() déjà prêtes à recevoir votre propre logiciel.

Il vous appartiendra évidemment d’adapter la configuration, qui est ici adaptée à notre Locoduinodrome, à votre propre réseau.

PNG - 16.8 kio

Il convient de laisser ensemble tous les fichiers de ce dossier. Le mieux est d’ajouter toutes les déclarations, fonctions et variables de votre propre gestionnaire à l’intérieur du fichier SAM.ino de ce dossier.

Les autres fichiers sont nécessaires (sauf README.md) à la compilation.

Dans le programme SAM ( le ".ino"), nous aurons successivement :

  • L’appel de la bibliothèque mcp_can ;
  • La création de l’objet canController ;
  • La routine d’interruption sur réception de messages CAN.
#include <mcp_can.h>
#include <mcp_can_dfs.h>
 
/*
 * Interface CAN
 */
const uint8_t spiCS = 53; // 9 sur Nano ou Uno, 53 sur un Mega

MCP_CAN canController(spiCS);

// CAN interrupt routine
volatile bool FlagReceive = false;      // can interrupt flag
void MCP2515_ISR() {FlagReceive = true;}

Puis l’appel de l’API des satellites (SAM) qui n’est pas une bibliothèque mais qui est sous forme de fichiers annexes du programme SAM.ino, donc placés dans le même dossier que lui :

/*
 * Les satellites
 */
#include "SatelliteWrapper.h"
#include "Feux.h"

/*
 *  Les aiguillages
 */
PointWrapper pointWrapper0(0, 2); /* aiguillage 0 du gestionnaire sur satellite 2 */
PointWrapper pointWrapper1(1, 6); /* aiguillage 1 du gestionnaire sur satellite 6 */

/*
 * Les signaux
 */
SemaphoreSignalWrapper                  S1wrapper(0, 4, SIGNAL_0); /* signal 0 du gestionnaire sur satellite 4, slot 0 */
SemaphoreSignalWrapper                  S2wrapper(1, 3, SIGNAL_0); /* signal 1 du gestionnaire sur satellite 3, slot 0 */
CarreSignalWrapper                      C3wrapper(4, 0, SIGNAL_0); /* signal 4 du gestionnaire sur satellite 0, slot 0 */
CarreSignalWrapper                      C4wrapper(5, 1, SIGNAL_0); /* signal 5 du gestionnaire sur satellite 1, slot 0 */
CarreSignalWrapper                      C5wrapper(6, 6, SIGNAL_0); /* signal 6 du gestionnaire sur satellite 6, slot 0 */
CarreSignalWrapper                      C6wrapper(7, 2, SIGNAL_0); /* signal 7 du gestionnaire sur satellite 2, slot 0 */
CarreRappelRalentissementSignalWrapper  C1wrapper(2, 7, SIGNAL_0); /* signal 2 du gestionnaire sur satellite 7, slot 0 */
CarreRappelRalentissementSignalWrapper  C2wrapper(3, 5, SIGNAL_0); /* signal 3 du gestionnaire sur satellite 5, slot 0 */
SemaphoreRalentissementSignalWrapper    S3wrapper(8, 3, SIGNAL_1); /* signal 8 du gestionnaire sur satellite 3, slot 1 */
SemaphoreRalentissementSignalWrapper    S4wrapper(9, 4, SIGNAL_1); /* signal 9 du gestionnaire sur satellite 4, slot 1 */

Le setup()

C’est un exemple bien-sûr que nous vous proposons avec tout ce qu’il faut pour réussir :

  • L’initialisation complète du bus CAN qui commence par la methode start(), puis la déclaration des filtres que vous devrez adapter à votre réseau, puis la méthode begin() ;
  • le démarrage de SAM ;
  • une sortie sur le moniteur de l’IDE de l’état initial des satellites.
void setup()
{
  Serial.begin(115200);

 // init CAN

  canController.start();
  
 /*
  * set mask & filters
  */
  
  canController.init_Mask(0, 0, 0x3F0);               // there are 2 mask in mcp2515, you need to set both of them
  canController.init_Mask(1, 0, 0x3F0);               // precisement : Id 0x10 à 0x4F
  // filtres du buffer 0
  canController.init_Filt(0, 0, 0x10);                // Reception possible : Id 10 & 1F (hex)  : Satellites
  canController.init_Filt(1, 0, 0x40);                // Reception possible : Id 4x (hex) 
  // filtres du buffer 1
  canController.init_Filt(2, 0, 0x10);                // Reception possible : Id 1x (hex) 
  canController.init_Filt(3, 0, 0x40);                // Reception possible : Id 4x (hex) 40 conduite via centrale DCC
  canController.init_Filt(4, 0, 0x43);                // Reception possible : Id 4x (hex) 43 keepalive
  canController.init_Filt(5, 0, 0x48);                // Reception possible : Id 4x (hex) 48 etat DCC
  
  while (CAN_OK != canController.begin(CAN_500KBPS))              // init can bus : baudrate = 500k (carte NiRem a 16 Mhz)
  {
    Serial.println("CAN BUS Shield init fail");
    delay(100);
  }
  Serial.println("CAN BUS Shield init ok!");
  attachInterrupt(0, MCP2515_ISR, FALLING); // start interrupt

  PointWrapper::begin();
  SignalWrapper::begin();

  Serial.println("Initial");
  printOutBuffers();

  tempo = millis();
}

Nous allons maintenant regarder en détail ce qui se passe dans le loop().

La loop()

Nous avons d’abord le traitement des messages CAN reçus. Ici ils sont simplement affichés sur la console.

  unsigned char len = 0;
  unsigned char buf[8];

  if(FlagReceive)                      // test si message
  {
    FlagReceive=0;
    if (CAN_MSGAVAIL == canController.checkReceive())            // check if data coming
    {
      canController.readMsgBuf(&len, buf);    // read data,  len: data length, buf: data buf
      unsigned int canId = canController.getCanId();
      Serial.print(" ID: 0x");
      Serial.print(canId, HEX);
      Serial.print(" data:");
      for(int i = 0; i<len; i++)    // print the data
      {
        Serial.print(" 0x");
        Serial.print(buf[i], HEX);
      }
      Serial.println();
    }
  }

Dans une application de SAM, les messages CAN seront décodés (à l’aide de l’instruction switch par exemple) et provoqueront un appel de fonction qui dépendra du bit d’occupation lu dans le premier octet du message et de l’identifiant du satellite lu dans l’identifiant du message.

Enfin nous avons la tâche périodique d’interrogation des satellites :

sendSatelliteMessage();

Pour la mise au point, il peut être commode d’afficher la liste des bits d’état des satellites, avec la fonction printOutBuffers() qui se trouve dans le fichier SatelliteWrapper.cpp :

printOutBuffers();

Ce qui se traduira par un tableau comme ci-dessous où les lettres correspondant aux DEL A (jaune), S (rouge), Vl (vert), C (rouge), RR (Jaune) seront figurées par :

  • ’ ’ : éteinte
  • ’+’ : allumée
  • ’^’ : clignotante

et le signe entre < > sera :

  • | : droit
  • / : dévié
<6> [A] [S] [Vl] [C] [o] [ ]  [ ] [ ] [ ] </>
<2> [A] [S] [Vl] [C] [o] [ ]  [ ] [ ] [ ] </>
<4> [A] [S] [Vl] [A] [S] [Vl] [RR] [RR] [ ] <|>
<3> [A] [S] [Vl] [A] [S] [Vl] [RR] [RR] [ ] <|>
<5> [A] [S] [Vl] [R] [M] [M] [o]  [ ] [ ] <|>
<7> [A] [S] [Vl] [R] [M] [M] [o]  [ ] [ ] <|>
<1> [A] [S] [Vl] [C] [o] [ ] [ ]  [ ] [ ] <|>
<0> [A] [S] [Vl] [C] [o] [ ] [ ]  [ ] [ ] <|>

Ce tableau correspond évidemment à la configuration des satellites décrite juste avant le setup().

A titre d’exemple d’application, si on veut tester, sous forme d’animation, tous les cas possibles de signaux sur tous les satellites, on définit quelques variables globales :

unsigned long tempo;
int nsig = 0;
int sig = 0;
unsigned int feu = 1;

puis une fonction périodique de ce genre qui peut être ajoutée dans la loop() :

  if (millis() - tempo > 500)
  {
    tempo = millis();
    Serial.print("signal ");Serial.print(sig);Serial.print(" feu ");Serial.println(feu);
    SignalWrapper::setSignalState(sig, feu);
    sig++;
    if (sig > 9) {
      sig = 0;
      feu = feu << 1;
      if (feu == 0) feu = 1;
    }
  }

Configuration des satellites

Nous sommes partis du principe que tous les logiciels de satellites sont identiques. Une seule exception dans la version V1 : l’identifiant propre de chaque satellite est gravé en même temps que son code. Mais il pourrait être configurable via le bus CAN.

Par contre, après l’installation des satellites sur le réseau, il y a quelques réglages à faire :

  • Les valeurs des butées du servomoteur d’aiguille
  • La vitesse du servo
  • Les intensités lumineuses des DEL pour un meilleur rendu lumineux car nous savons que les DEL blanches éclairent plus que les DEL rouges qui, elles-mêmes, éclairent plus que les jaunes et les vertes.
  • Les temps d’allumage et d’extinction lors des changements d’état progressifs pour simuler l’inertie des lampes à incandescence.
  • Le temps allumé et la période dans le cas des DEL clignotantes.

SAM ne comprend pas d’interface pour assurer ces opérations de configuration qui n’ont lieu qu’une seule fois en principe. Il n’était pas nécessaire d’alourdir SAM de ces fonctions.

Nous avons donc défini un message CAN spécial pour régler individuellement chaque paramètre de chaque satellite. Lorsque le satellite concerné reçoit ce type de message, il agit sur un de ses paramètres et sauvegarde le résultat dans son EEPROM. 

Figure 11 : Trame de réglage
Figure 11 : Trame de réglage
La trame de réglage permet de fixer les positions min et max du servo, sa vitesse et les caractéristiques de chaque LED : intensité et, pour le clignotement, la période, le temps où la LED est allumée et le temps nécessaires pour passer d’éteint à allumé et d’allumé à éteint, ceci dans le but de simuler l’inertie thermique des filaments.

Il est caractérisé par :

  • Un identifiant de 29 bits dont les bits de poids faibles contiennent le numéro de satellite
  • Un premier octet de donnée dont le poids fort signifie ’0’ = réglage temporaire (sans enregistrement en EEPROM dans le satellite et ’1’ = réglage permanent donc avec enregistrement en EEPROM. Dans les bits de poids faible, on peut choisir ’0’ = un réglage de servo ou ’1’ = un réglage de luminosité de DEL.
  • Un deuxième octet dont les bits de poids fort définissent le numéro d’objet (servo ou DEL) à configurer et les bits de poids faible le type de valeur à configurer (voir tableau)
  • de 1 à 4 octets de données pour passer la valeur de configuration dont le type détermine le nombre d’octets.

A titre d’exemple, on pourra réaliser des messages de configuration avec les variables et les fonctions suivantes :

int sMin;    // servo min
int sMax;    // servo max
union sVitType {float f; unsigned char b[sizeof(float)];} ;
sVitType sVit;      // servo vitesse
uint8_t sLum = 255; // intensité
uint8_t sAlt = 255; // temps d'allumage
uint8_t sFad = 255; // temps d'extinction
uint8_t sOnt = 255; // temps allumé
uint8_t sPer = 255; // periode
unsigned char bufS[8]; // les 8 octets du message CAN à envoyer
Fonctions d’envoi d’un message de configuration :

Ces fonctions s’adressent directement à la bibliothèque CAN.

void sendConfig(byte cId, byte cLg)
{
  unsigned long longId = 0x1FFFFF20 + cId;   // cId est l'identifiant du satellite
  canController.sendMsgBuf(longId, 1, cLg, bufS);
}

-1- Réglage des butées de servo, par exemple le coté Max contenu dans une variable sMax codée sur un entier int qu’il faut convertir en 2 octets :

bufS[0]= 0x80; // reglage servo
bufS[1]=1; // a0 max
bufS[2]=(byte)(sMax>>8);
bufS[3]=(byte)(sMax);
sendConfig(satConfig, 4);

-2- Réglage de la vitesse d’un servo contenue dans une variable sVit codée sur un long qu’il faut convertir en 4 octets à partir d’une déclaration de type Union :

bufS[0]= 0x80; // reglage servo
bufS[1]=2; // a0 vitesse
bufS[2]=sVit.b[3];    // octet 3 de la conversion du long en byte
bufS[3]=sVit.b[2];    // octet 2 de la conversion du long en byte
bufS[4]=sVit.b[1];    // octet 1de la conversion du long en byte
bufS[5]=sVit.b[0];    // octet 0 de la conversion du long en byte
sendConfig(satConfig, 6);

-3- Réglage de la luminosité d’une DEL contenue dans une variable sLum codée sur 1 octet :

bufS[0]= 0x81; // reglage led
bufS[1]= (ledConfig << 3) + 0; // numero de led + intensité
bufS[2]=sLum;   // valeur de la luminosité
sendConfig(satConfig, 3);

-4- Réglage de la période de clignotement d’un DEL :

bufS[0]= 0x81; // reglage led
bufS[1]= (ledConfig << 3) + 4; // numero de led + intensité
bufS[2]=sPer;    // valeur de la periode
sendConfig(satConfig, 3);

Ceci conclut cet article consacré à la communication avec les Satellites. Le prochain article présentera le logiciel du Satellite V1.

[1la nécessité de messages périodiques transmettant des états est exposé dans « La carte Satellite V1 - Principes fondateurs ».

2 Messages

Réagissez à « La carte Satellite V1 (3) »

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.

Lien hypertexte

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)

Rubrique « Projets »

LaBox, Une Centrale DCC polyvalente et abordable (1)

LaBox, Une Centrale DCC polyvalente et abordable (2)

LaBox, Une Centrale DCC polyvalente et abordable (3)

Comment piloter trains et accessoires en DCC avec un Arduino (1)

Comment piloter trains et accessoires en DCC avec un Arduino (2)

Comment piloter trains et accessoires en DCC avec un Arduino (3)

Comment piloter trains et accessoires en DCC avec un Arduino (4)

SGDD : Système de Gestion DD (1)

SGDD : Système de Gestion DD (2)

SGDD : Système de Gestion DD (3)

La PWM : Qu’est-ce que c’est ? (1)

La PWM : Qu’est-ce que c’est ? (2)

La PWM : Qu’est-ce que c’est ? (3)

La PWM : Qu’est-ce que c’est ? (4)

Mise en oeuvre du Bus CAN entre modules Arduino (1)

Mise en oeuvre du Bus CAN entre modules Arduino (2)

Un gestionnaire en C++ pour votre réseau (1)

Un gestionnaire en C++ pour votre réseau (2)

Un gestionnaire en C++ pour votre réseau (3)

Un gestionnaire en C++ pour votre réseau (4)

Réalisation de centrales DCC avec le logiciel libre DCC++ (1)

Réalisation de centrales DCC avec le logiciel libre DCC++ (2)

Réalisation de centrales DCC avec le logiciel libre DCC++ (3)

Contrôleur à télécommande infrarouge pour centrale DCC++

Gestion d’une gare cachée (1)

Gestion d’une gare cachée (2)

Gestion d’une gare cachée (3)

Chenillard de DEL

Enseigne de magasin

Feux tricolores

Multi-animations lumineuses

L’Arduino et le système de commande numérique DCC

Un décodeur d’accessoire DCC versatile basé sur Arduino

Un moniteur de signaux DCC

Une barrière infrarouge

Un capteur RFID

Un TCO xpressnet

Une animation sonore

L’Arduino au coeur des systèmes de pilotage analogiques ou numériques

Calcul de la vitesse d’un train miniature avec l’Arduino

La génèse d’un réseau 100% Arduino

Une horloge à échelle H0

Simulateur de soudure à arc

Un automatisme de Passage à Niveau

Automatisation du pont FLEISCHMANN 6152 (HO) avec un ESP32 (1)

Identifier et localiser vos trains avec le RFID/NFC et un bus CAN.

Etude d’un passage à niveau multivoies

La rétro-signalisation sur Arduino

Décodeur pour aiguillage à solénoïdes sur Arduino

Un décodeur DCC pour les signaux à deux ou trois feux sur Arduino NANO/UNO

Etude d’un passage à niveau universel

Réalisation pratique d’un système de mesure de vitesse à l’échelle N

Une Passerelle entre le bus S88 et le bus CAN pour la rétro signalisation

Un décodeur DCC pour 16 feux tricolores

Block Automatique Lumineux avec la carte shield "Arduino 4 relays"

Réalisation d’un affichage de gare ARRIVEE DEPART

Ménage à trois (Ordinateur, Arduino, réseau)

Réalisation d’un va-et-vient automatique et réaliste

Souris et centrale sans fil

Communications entre JMRI et Arduino

Annonces en gare avec la RFID

Une croix de pharmacie animée avec Arduino UNO

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

Passage à niveau géré par Arduino (1)

Passage à niveau géré par Arduino (2)

Passage à niveau géré par Arduino (3)

Passage à niveau géré par Arduino (4)

Passage à niveau géré par Arduino (5)

Une manette simple et autonome pour LaBox

Éclairer le réseau (1)

Éclairer le réseau (2)

Block Automatique Lumineux à 8 cantons analogiques

Un décodeur DCC pour les plaques tournantes Fleischmann et Roco

Éclairer le réseau (3)

Éclairer le réseau (4)

Éclairer le réseau (5)

JMRI pour Ma première centrale DCC

Rocrail pour Ma première centrale DCC

CDM-Rail pour Ma première centrale DCC (1)

CDM-Rail pour Ma première centrale DCC (2)

Banc de test pour les décodeurs DCC

Ma première manette pour les aiguillages DCC

Mon premier décodeur pour les aiguillages DCC

Boitier 3D pour la station DCC minimale

Va-et-vient pour deux trains

Un programme pour régler facilement les servos moteurs avec un ESP32

Affichage publicitaire avec Arduino (1)

Affichage publicitaire avec Arduino (2)

TCO Web interactif avec des ESP32 et des ESP8266 (1)

TCO Web interactif avec des ESP32 et des ESP8266 (2)

TCO Web interactif avec des ESP32 et des ESP8266 (3)

TCO Web interactif avec des ESP32 et des ESP8266 (4)

TCO Web interactif avec des ESP32 et des ESP8266 (5)

Les derniers articles

LaBox, Une Centrale DCC polyvalente et abordable (3)


Thierry

LaBox, Une Centrale DCC polyvalente et abordable (1)


Thierry

LaBox, Une Centrale DCC polyvalente et abordable (2)


Dominique, msport, Thierry

Un programme pour régler facilement les servos moteurs avec un ESP32


bobyAndCo

TCO Web interactif avec des ESP32 et des ESP8266 (5)


utpeca

TCO Web interactif avec des ESP32 et des ESP8266 (4)


utpeca

TCO Web interactif avec des ESP32 et des ESP8266 (3)


utpeca

TCO Web interactif avec des ESP32 et des ESP8266 (2)


utpeca

TCO Web interactif avec des ESP32 et des ESP8266 (1)


utpeca

Affichage publicitaire avec Arduino (2)


catplus, Christian

Les articles les plus lus

Réalisation de centrales DCC avec le logiciel libre DCC++ (3)

La PWM : Qu’est-ce que c’est ? (1)

Mon premier décodeur pour les aiguillages DCC

La rétro-signalisation sur Arduino

Comment piloter trains et accessoires en DCC avec un Arduino (1)

Réalisation de centrales DCC avec le logiciel libre DCC++ (1)

Chenillard de DEL

Mise en oeuvre du Bus CAN entre modules Arduino (2)

LaBox, Une Centrale DCC polyvalente et abordable (1)

Décodeur pour aiguillage à solénoïdes sur Arduino