LOCODUINO

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

mardi 19 mars 2024

Visiteurs connectés : 21

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

2ème partie : la programmation

.
Par : Dominique, Jean-Luc

DIFFICULTÉ :

Dans la première partie de cet article, Mise en oeuvre du Bus CAN, nous vous avons présenté le Bus CAN comme un des meilleurs choix (je crois même que c’est le meilleur !) pour faire communiquer entre elles plusieurs cartes Arduino dans le cadre de nos projets ferroviaires.

Ce choix est d’autant plus raisonnable qu’il est facile de construire ses propres cartes CAN sur la base de la carte Can Locoduino ou à partir de cartes que l’on trouve maintenant facilement dans le commerce, pour peu que l’on fasse attention à leur compatibilité avec la carte Locoduino et le logiciel utilisé.

L’implémentation qui suit concerne uniquement les cartes Arduino à base de processeur Atmel (328, 2560), donc les Uno, Nano, Mega pour ne citer que les principaux.

Pour le Due, qui contient 2 interface CAN intégrées, c’est possible aussi mais avec une bibliothèque différente et du matériel différent mais plus simple.

Il va sans dire que le Due comme gestionnaire et quelques Mega et Nano pour s’occuper de la traction, les aiguilles, les occupations, les signaux et le décor, le tout relié sur un ou deux bus Can, constituent une informatique ferroviaire certainement haut de gamme, mais réalisable par nous autres amateurs.

<

Pour se fixer les idées, on va commencer par décrire les éléments logiciels qui font partie d’une carte de commande d’aiguilles, par exemple à base d’un Mega2560, sans entrer dans le détail de la façon de commander les moteurs d’aiguille (juste un petit peu), par les échanges CAN.

Ces éléments sont présentés dans l’ordre logique d’un programme Arduino. Il vous suffira de "copier-coller" les morceaux de code dans votre projet pour que les communications CAN soient immédiatement opérationnelles.

Mise en place de l’interface CAN : version "recette de cuisine"

Cette première partie dite "recette de cuisine" n’a pour but que de vous livrer les ingrédients à mettre en place dans votre programme, sans explication précise sur le bus CAN et son fonctionnement qui a été présenté dans l’article précédent. Ensuite viendront ces explications (patience !).

Aussi, si vous êtes impatient, le code important est disponible dès le début de l’article.

Le matériel se compose d’une carte Arduino (par exemple ici un Mega2560) et d’une carte CAN Locoduino. Si vous n’avez pas cette carte, mais une autre équipée d’un MCP2515 fréquencé à 16 MHz, ça doit marcher également.

On commence par relier à la carte CAN les broches du bus SPI, ainsi que le +5V et le 0V (Gnd).
Ajoutons une liaison entre la broche INT (interruption) de la carte CAN et la broche 2 (Interruption 0) de l’Arduino.l

Les branchements

Bus SPI de l’Arduino

Module CANArduino Uno/Pro Mini/Nano
SCK 13 (SCK)
SO 12 (MISO)
SI 11 (MOSI)
CS 10 (SS)
INT 2 (INT0)
Module CANArduino Mega
SCK 52 (SCK)
SO 50 (MISO)
SI 51 (MOSI)
CS 53 (SS)
INT 2 (INT0)

Alimentation

Module CANArduino
GND GND
VDD 5V

La bibliothèque

Il faut télécharger une bibliothèque qui se trouve ici : https://github.com/Seeed-Studio/CAN.... Puis il faut placer le dossier téléchargé dans le dossier des autres bibliothèques. Voir l’article Installer une bibliothèque.

Ensuite on doit placer ces 2 lignes en tête de programme pour bénéficier de la bibliothèque :

#include <SPI.h>                 // pour la bibliothèque CAN
#include "mcp_can.h"             // bibliothèque CAN

Puis il faut créer l’objet CAN comme le permet la bibliothèque :

// variables globales pour l'interface CAN
MCP_CAN CAN(53);   // Definition du CS (chip select) pin 53 (SS du bus SPI)
volatile byte Flag_Recv = 0;   // variable d'échange avec l'interruption IRQ

On voit ainsi qu’une variable globale Flag_Recv servira à faire savoir à la LOOP qu’un ou plusieurs messages sont arrivés sous interruption. Attention, quand cette interruption fait monter l’indicateur Flag_Recv, il faut bien prendre soin de vider TOUT le tampon du MCP2515 (sinon, il n’y aura plus d’autre IRQ et tout se bloque !).

Cette variable est positionnée par la routine d’interruption suivante :

/* 
 *  ISR CAN (Routine de Service d'Interruption)
 *  le flag IRQ monte quand au moins un message est reçu
 *  le flag IRQ ne retombe QUE si tous les messages sont lus
 */ 

void MCP2515_ISR()
{
     Flag_Recv = 1;
}

Après avoir placé ces lignes de code en tête de programme, dans la zone des définitions de variables, abordons le SETUP dans lequel on insère les lignes suivantes :

  /* -----------------------------------------------------
  *                       SETUP
  * -----------------------------------------------------
  */

  /////////////// INIT CAN /////////////////
  
while(true)
{
  if (CAN_OK == CAN.begin(CAN_500KBPS))      
   // initialisation du can bus : baudrate = 500k
  {
    Serial.println(F("CAN BUS init ok!"));
    break; // on sort du while.
  }
  else
  {
    Serial.println(F("CAN BUS init echec !"));
    Serial.println(F("Init CAN BUS a nouveau"));
  }
  delay(200);
}

On comprend bien ici que l’instruction CAN.begin(baudrate) démarre l’interface, avec un compte-rendu CAN_OK, sinon cela se répète car, à ce stade de l’initialisation, si le bus CAN ne démarre pas, il est inutile d’aller plus loin.

Personnellement je n’ai jamais vu d’échec sauf si la carte CAN n’est pas (ou est mal) branchée, coté Arduino.

Ensuite il faut attacher l’interruption 0 à la routine MCP2515_ISR() précédente :

  attachInterrupt(0, MCP2515_ISR, FALLING); // interrupt 0 (pin 2)

Enfin on définit les filtres CAN qui limiteront les messages reçus à seulement ceux qui intéressent notre carte :

  /*
   * set mask & filter 
   */
   
  CAN.init_Mask(0, 0, 0x7F0);  // Il y a 2 masques à initialiser dans le mcp2515
  CAN.init_Mask(1, 0, 0x7F0);  // on teste tous les bits sauf les 4 de poids faible
   
  CAN.init_Filt(0, 0, 0x40);        // Reception possible : Id 40 à 4F (hex) 
  CAN.init_Filt(1, 0, 0x40);        // idem
  CAN.init_Filt(2, 0, 0x40);        // Reception possible : Id 40 à 4F (hex) 
  CAN.init_Filt(3, 0, 0x40);        // idem
  CAN.init_Filt(4, 0, 0x00);        // Reception possible : Id 00 à 0F
  CAN.init_Filt(5, 0, 0x00);        // Idem

A titre d’exemple, ces filtres sont initialisés ici pour ma carte de commande d’aiguilles.

Le setup ayant mis en place tous les acteurs, la loop peut commencer son travail répétitif !

/*-----------------------------------------------------
 *                        LOOP
 *-----------------------------------------------------                       
 */

void loop()
{

  if (Flag_Recv)  {
    Flag_Recv = 0;  // Flag MCP2515 prêt pour un nouvel IRQ
    CAN_recup();    // récupération du ou des messages CAN reçus
  }
  ...

Cette fonction CAN_recup() se charge de lire tous les messages reçus par le MCP2515 et les sauvegarder dans une mémoire tampon circulaire. Notre programme aura alors le loisir d’exploiter ces messages au rythme de son choix. Par exemple, on peut ne traiter qu’un seul message par tour de LOOP, mais j’ai constaté rapidement des pertes de messages. Il vaut mieux tout traiter d’un coup.

Tout d’abord il faut ajouter quelques variables globales :

// Variables globales pour la gestion des Messages reçus et émis
byte IdR;                       // Id pour la routine CAN_recup()
unsigned char lenR = 0;         // Longueur "    "       "
unsigned char bufR[8];          // tampon de reception      "
unsigned char bufS[8];          // tampon d'emission

// Variable globale Mémoire circulaire pour le stockage des messages reçus
unsigned char _Circule[256];    // récepteur circulaire des messages CAN sous IT
int _indexW, _indexR, _Ncan;    // index d'écriture et lecture, nb d'octets a lire
byte _CANoverflow = 0;          // flag overflow (buffer _Circule plein)

Voici la fonction CAN_recup() qui se charge du boulot :

/*
 * Routine de récupération des messages CAN dans la mémoire circulaire _Circule
 * appelée par LOOP lorsque Flag_Recv = 1;
 */
 
void CAN_recup()
{
  unsigned char len = 0;  // nombre d'octets du message
  unsigned char buf[8];   // message
  unsigned char Id;   // Id (on devrait plutôt utiliser un int car il y a 11 bits)

  while (CAN_MSGAVAIL == CAN.checkReceive())  {
    CAN.readMsgBuf(&len, buf);        // read data, len: data length, buf: data buf
    Id = CAN.getCanId();
    if ((_Ncan+len+2) < sizeof(_Circule))  { // il reste de la place dans _Circule
      _Circule[_indexW] = Id;         // enregistrement de Id
      _indexW++;
      _Ncan++;
      if (_indexW == sizeof(_Circule))  {_indexW = 0;}
      _Circule[_indexW] = len;        // enregistrement de len
      _indexW++;
      _Ncan++;
      if (_indexW == sizeof(_Circule))  {_indexW = 0;}
      for (byte z = 0; z<len; z++)  {
        _Circule[_indexW] = buf[z];    // enregistrement du message
        _indexW++;
        _Ncan++;
        if (_indexW == sizeof(_Circule))  {_indexW = 0;}
      }
    } else {
      _CANoverflow = 1;  // dépassement de la capacite de Circule
                            // le message est perdu
    }
  } 
}

A ce stade, on a juste reçu et sauvegardé les messages CAN qui viennent d’arriver.

Pour exploiter les messages, de façon indépendante de leur réception en temps réel, j’utilise le code suivant :

  byte RId;  // variables pour le traitement des messages lus dans _Circule
  byte Rlen;
  byte Rbuf[8];
  
  // traitement des messages stockés dans la mémoire circulaire _Circule
    
  while (_Ncan > 2)  {    // chaque message dans _Circule occupe au moins 3 octets
    _Ncan--;
    RId = _Circule[_indexR];        // recup Id
    _indexR++;
    if (_indexR == sizeof(_Circule))  {_indexR = 0;}
    _Ncan--;
    Rlen = _Circule[_indexR];       // recup longueur
    _indexR++;
    if (_indexR == sizeof(_Circule))  {_indexR = 0;}
    if (_dumpCan)  {     	 // _dumpCan est un boolean a déclarer en globale 
      Serial.print("CAN id ");	// si on veut conditionner l'affichage des message
      Serial.print(RId);
      Serial.print(", data ");
    }
    for (int k = 0; k < Rlen; k++)  {
      _Ncan--;
      Rbuf[k] = _Circule[_indexR];  // recup octets message
      _indexR++;
      if (_indexR == sizeof(_Circule))  {_indexR = 0;}
      if (_dumpCan)  {  
      Serial.print("0x");
      Serial.print(Rbuf[k], HEX);
      }
    }
    if (_dumpcan) Serial.println();
   // le message est maintenant dans les globales RId, Rlen et Rbuf[..]
  // ---> Votre traitement du message à ajouter ici <---
}

La suite du code est maintenant personnelle, selon la signification donnée à l’identifiant RId et aux octets de Rbuf.
On pourra avantageusement utiliser l’instruction switch pour traiter les différents cas de RId.

switch (Rid) {
  case xxx:
  // votre code
  break;
}

On pourra également utiliser les test de bits comme :

if (bitRead(Rbuf[0], 7) {
  // votre code
}

Et l’émission de messages ?

Mais c’est très simple :
J’envoie par exemple des messages ultra simples contenant un seul octet avec la fonction suivante :

void CANMessage(byte Message)
{
  unsigned char bufS[8];
  bufS[0] = Message;
  CAN.sendMsgBuf(0x30, 0, 1, bufS);  
  // Id = 30H (Aiguille), message standard avec 1 seul octet
}

Tous les éléments de base sont maintenant en place.
Voici quelques explications complémentaires.

Retour sur la fonction CAN_recup()

Pourquoi ne pas traiter directement les messages reçus par le MCP2515 dans la routine d’interruption ?
Il y a plusieurs raisons à cela :

  • il faut libérer cette routine le plus vite possible pour ne pas perdre de nouveaux messages
  • il faut éviter d’être obligé d’utiliser un grand nombre de variables globales qui seraient spécifiques aux traitements.

Le meilleur moyen de libérer le MCP2515 est de récupérer ce qui se trouve dans son tampon et le transférer en mémoire. Comme il est impossible de prévoir combien de messages arriveront, à quelle fréquence et avec quelle taille, un tableau ne convient pas du tout car il pourrait conduire au gaspillage de la mémoire.

J’utilise donc une mémoire circulaire, c’est à dire une série d’octets (un tableau unsigned char _Circule[256] précisément ici) qui est gérée comme une FIFO (1er entré, 1er sorti) : Les messages composés de leur Id (ramené à 1 octet), leur longueur len et leurs données (len octets) sont stockés à la queue leu leu au moyen d’un pointeur d’écriture _indexW qui avance d’un cran à chaque octet écrit. Lorsque ce pointeur arrive en bout de tableau, le pointeur est remis à zéro et ça continue.
Evidemment la lecture des messages se fait concurremment ailleurs dans la LOOP, à l’aide d’un pointeur de lecture _indexR répondant au même principe.

Pour savoir s’il y a des messages dans la mémoire circulaire, un compteur Ncan est incrémenté par la fonction CAN_recup() et est décrémenté par la fonction qui les traite.

Au fur et à mesure de la lecture des messages, le pointeur _indexR avance et Ncan diminue, ce qui libère de la place dans la mémoire.

Ncan = 0 signifie qu’il n’y a pas de message. Un message existe quand Ncan > 2 (un octet Id, un octet longueur, un octet de donnée).

Si Ncan = 256, cela veut dire que la mémoire circulaire est pleine, ce qui ne doit jamais arriver (il faudrait alors augmenter sa taille), ou que la fonction de traitement est en panne quelque part (c’est un bug !).

A vous de jouer !

Maintenant vous pouvez mettre du CAN dans vos projets ferroviaires !.

Yes you CAN !

79 Messages

Réagissez à « Mise en oeuvre du Bus CAN entre modules Arduino (2) »

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)

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)

La carte Satellite V1 (1)

La carte Satellite V1 (2)

La carte Satellite V1 (3)

La carte Satellite V1 (4)

La carte Satellite V1 (5)

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

Chenillard de DEL

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

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

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

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

Block Automatique Lumineux à 8 cantons analogiques