LOCODUINO

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

mardi 27 octobre 2020

83 visiteurs en ce moment

La bibliothèque ACAN (1)

Prise en main

. Par : Jean-Luc

Le bus CAN a été présenté à plusieurs reprises sur LOCODUINO mais la récente demande d’un des membres du forum m’a fait prendre conscience que la mise en œuvre manquait d’exemples illustratifs pour que les modélistes puisse prendre ce bus en main le plus facilement possible. De plus, depuis les premiers articles, « Mise en oeuvre du Bus CAN entre modules Arduino (1) » et « Mise en oeuvre du Bus CAN entre modules Arduino (2) », de l’eau a coulé sous les ponts et d’autres bibliothèques pour le MCP2515 ou un contrôleur CAN plus récent comme le MCP2518 sont apparues et sont à la fois plus riches et plus faciles à mettre en œuvre que la vénérable bibliothèque de SeeedStudio.

Rappelons rapidement que le bus CAN est particulièrement bien adapté au modélisme ferroviaire et est à la fois simple, robuste et économique. Sa mise en œuvre sur le réseau est aisée. S’agissant d’un bus, il suffit d’une simple paire de câbles torsadés pour relier les contrôleurs entre eux et étendre le réseau. On insère un nouveau contrôleur en coupant le câble existant et en le reliant au nouveau contrôleur.

En CAN, tous les appareils sont en capacité de recevoir l’ensemble des messages circulants sur le bus mais aussi de communiquer des messages à destination de tous les autres appareils. Il y a donc une économie en termes de matériel et d’infrastructure.

En contrepartie, il faut connaître et maîtriser la messagerie propre au CAN. Ce qui est l’objet de cette série d’articles.

Nous allons donc examiner la bibliothèque ACAN et notamment son incarnation pour le MCP2515 dont on trouve des modules facilement et à petit prix sur eBay ou Aliexpress [1]. Je veux parler des modules comme celui présenté à la figure 1 qui est sans doute la façon la moins chère (moins de 1€50) et la plus facile pour ajouter une interface CAN à un Arduino et je dirais même pour mettre des Arduino en réseau de manière générale. Ça sera également l’occasion de voir la façon de concevoir plusieurs petites applications exploitant le bus CAN.

Figure 1 : Module CAN
Figure 1 : Module CAN
Ce module embarque un contrôleur CAN MCP2515 (U2) et un transceiver CAN TJA1050 (U1), il s’agit d’un circuit d’interface entre les niveaux logiques (0 correspond à 0V, 1 correspond à 5V) et le bus qui lui est en différentiel (0 correspond à VH - VL = 5V et 1 correspond à VH = VL). Le quartz (X1) est à 8MHz, ce qui limite la vitesse du bus à 512kbits/s.

Le MCP2515 est un contrôleur CAN qui communique avec l’Arduino via le bus SPI et une ligne d’interruption qui permet au MCP2515 de signaler à l’Arduino qu’un message vient d’arriver ou vient de partir, deux événements que l’Arduino doit prendre en charge. La connexion à l’Arduino est simple et est donnée à la table 1 et la figure 2 ci-dessous.

Table 1 : Connexion à l’Arduino du module CAN
Broche du moduleNom de la broche ArduinoBroche Uno/NanoBroche Mega
INT - 2 ou 3 2, 3, 18, 19, 20 ou 21
SCK SCK 13 52
SI MOSI 11 51
SO MISO 12 50
CS SS Au choix Au choix
GND GND GND GND
VCC 5V 5V 5V

Il faut noter que ce module CAN n’est pas adapté aux Arduino alimentés en 3,3V car le bus est alimenté par la même tension que le contrôleur et il est préférable d’avoir une tension de 5V sur le bus, d’autant plus que la documentation du TJA1050 recommande une tension comprise entre 4,75V et 5,25V.

Figure 2 : Connexions entre l'Arduino Uno et le module CAN
Figure 2 : Connexions entre l’Arduino Uno et le module CAN
Ici, le CS est sur la broche 10 et INT sur la broche 2.

Pour ce premier article et pour les deux premiers programmes, un seul Uno et un seul module CAN suffisent car nous allons exploiter la capacité des contrôleurs CAN à recevoir ce qu’ils émettent, le loopback. Ceci permet de jouer avec le bus CAN sans déployer effectivement un réseau. En revanche, pour la dernière application de cet article ainsi que pour celles proposées dans les articles suivants, il vous faudra disposer de deux modules CAN et deux Arduino, voire 3 pour certains. Le bus en lui même est constitué de deux fils que l’on vissera dans le bornier du module en reliant les H aux H et les L aux L entre les modules CAN et en constituant une chaine. Les deux modules situés aux extrémités de cette chaine reçoivent un cavalier sur le connecteur J1 afin de mettre en œuvre les résistances de terminaison de 120Ω. Ces résistances servent à empêcher la réflexion du signal à l’extrémité de la ligne.

Démarrage rapide

Venons en a la bibliothèque ACAN, écrite par Pierre Molinaro, et plus précisément l’ACAN2515. La première étape est de l’installer. Comme elle est disponible dans le gestionnaire de bibliothèque, l’installation est très simple. Ouvrez le gestionnaire et tapez « ACAN » dans la case de recherche, identifiez celle pour le 2515 puis cliquez « installer ». On peut également la télécharger sur github.

Premier programme

Le premier programme ne nécessite donc qu’un seul Arduino Uno et un module MCP2515. Nous allons exploiter la capacité du MCP2515 de recevoir ce qu’il émet et par conséquent notre programme va donc à la fois émettre et recevoir. Le message que nous envoyons ne contient aucune donnée.

Tout d’abord il faut procéder à quelques déclarations en commençant par l’inclusion de la bibliothèque.

  1. #include <ACAN2515Settings.h> /* Pour les reglages */
  2. #include <ACAN2515.h> /* Pour le controleur */
  3. #include <CANMessage.h> /* Pour les messages */

Il faut ensuite décider des broches pour le CS et l’INT. Ici nous avons choisi les broches 10 [2] et 2. Pour la broche INT, il est impératif qu’il s’agisse d’une broche d’interruption externe, la table 1 donne les broches valides.

  1. static const uint8_t MCP2515_CS = 10;
  2. static const uint8_t MCP2515_INT = 2;

Un objet de type ACAN2515 est instancié pour gérer le MCP2515, les arguments pour le construire sont la broche du CS, le SPI utilisé [3] et la broche INT.

  1. ACAN2515 controleurCAN(MCP2515_CS, SPI, MCP2515_INT);

Tant qu’à faire, et comme il est toujours préférable de déclarer des constantes pour les valeurs numériques plutôt que de les noyer dans le code en dur, on déclare la fréquence du quartz du MCP2515, ici 8MHz et la fréquence du bus, ici 125kb/s. La fréquence du bus est choisie de manière parfaitement arbitraire et peut être différente sans excéder 512kb/s avec un quartz à 8MHz.

  1. static const uint32_t FREQUENCE_DU_QUARTZ = 8ul * 1000ul * 1000ul;
  2. static const uint32_t FREQUENCE_DU_BUS_CAN = 125ul * 1000ul;
Il y a ici une subtilité qui demande quelques explications.
 
Pourquoi les constantes ci-dessus sont-elles suffixées par ul ?
 
Sur un Arduino à base d’AVR, donc un microcontrôleur 8 bits, les entiers font 16 bits, y compris lorsqu’ils sont spécifiés sous forme littérale. Par conséquent écrire 125 * 1000 fait que le compilateur calcule cette multiplication sur 16 bits car 125 et 1000 sont tous deux des entiers 16 bits. Or 125000 est trop grand pour être codé sur 16 bits et la valeur serait fausse. Le ul indique au compilateur que ces constantes sont des entiers 32 bits non signés et le calcul est juste.

On pourra trouver curieux d’avoir besoin d’une fréquence pour un bus qui ne sert à rien puisque nous somme en loopback mais cette fréquence est nécessaire pour initialiser le contrôleur CAN.

Enfin, deux messages, l’un pour l’émission et l’autre pour la réception.

  1. CANMessage messageCANEmission;
  2. CANMessage messageCANReception;

C’est tout pour les déclarations. Passons maintenant à setup où nous allons initialiser le MCP2515. Tout d’abord, il faut démarrer le SPI. En effet, la bibliothèque ACAN ne le fait pas elle même car le SPI peut être utilisé par plusieurs dispositifs et les bibliothèques conçus pour ces dispositifs feraient des initialisations redondantes du SPI. La connexion série est également démarrée pour pouvoir afficher des informations.

  1. void setup()
  2. {
  3. SPI.begin();
  4. Serial.begin(115200);

Ensuite, on construit un objet de type ACAN2515Settings qui permet de fixer les paramètres de fonctionnement. Le constructeur de cet objet prend ici comme arguments la fréquence du quartz et la fréquence du bus. Appelons cet objet configuration.

  1. ACAN2515Settings configuration(FREQUENCE_DU_QUARTZ, FREQUENCE_DU_BUS_CAN);

Il faut ensuite se mettre en mode de fonctionnement loopback.

  1. configuration.mRequestedMode = ACAN2515Settings::LoopBackMode;

Puis démarrer le contrôleur CAN. Le premier argument est la configuration que l’on vient de construire et le second une fonction qui prend en charge l’interruption du contrôleur. ACAN s’occupe presque de tout et cette fonction est toujours l’appel de la méthode isr() du contrôleur. Ici nous utilisons une syntaxe particulière appelée closure qui permet d’insérer au vol le contenu de la fonction et de la passer comme argument.

  1. const uint32_t codeErreur = controleurCAN.begin(configuration, [] { controleurCAN.isr(); });

codeErreur sera différent de 0 si un problème est survenu. Il convient donc de se bloquer dans ce cas, ce que fait le while(1);.

  1. if (codeErreur != 0) {
  2. Serial.print("Erreur : 0x");
  3. Serial.println(codeErreur, HEX);
  4. while (1);
  5. }
  6. else {
  7. Serial.println("OK !");
  8. }
  9. }

Voilà, le CAN est prêt à fonctionner. Il faut ensuite, dans loop(), l’exploiter. Commençons par traiter la réception.

  1. void loop()
  2. {
  3. if (controleurCAN.receive(messageCANReception)) {
  4. /* Message recu */
  5. Serial.println("recu !");
  6. }

receive prend comme argument le message CAN destination. Il retourne true si un message est arrivé et a été copié dans le message CAN passé en argument et false sinon et, dans ce cas, le message passé en argument n’est pas modifié.

Enfin, l’envoi se fait toutes les 2 secondes, nous avons donc une variable permettant de mémoriser la date d’un envoi et une donnant un numéro d’ordre pour l’affichage.

  1. static uint32_t dateDenvoi = 0;
  2. static uint32_t compte = 0;

Puis l’envoi en lui-même.

  1. uint32_t dateCourante = millis();
  2. if (dateCourante - dateDenvoi >= 2000) {
  3. if (controleurCAN.tryToSend(messageCANEmission)) {
  4. Serial.print("envoi ");
  5. Serial.println(compte++);
  6. dateDenvoi = dateCourante;
  7. }
  8. }
  9. }

tryToSend effectue l’envoi du message et retourne true en cas de succès. L’envoi peut échouer parce que la file d’attente d’émission est pleine. Dans ce cas, tryToSend retourne false.

Le code complet de ce premier programme est téléchargeable ci-dessous.

Téléchargement 1 : Programme loopback sans données

Second programme, ajoutons une donnée

Tout cela est très bien, mais le but de la communication est de véhiculer de l’information. Il faut donc pouvoir envoyer des messages comportant des données. Un message CAN peut inclure au maximum 8 octets de données.

La classe CANMessage

Nous avons jusqu’ici utilisé des objets de type CANMessage mais sans détailler ce que l’on y trouve. Cette classe regroupe tous les attributs d’un message :

  • id : l’identifiant sur 11 ou 29 bits selon que le message est standard ou étendu. id est initialisé à 0 ;
  • ext : un booléen qui est à false pour une trame standard et à true pour une trame étendue. ext est initialisé à false ;
  • rtr : un booléen qui est à false pour un message de données et à true pour un message remote [4]. rtr est initialisé à false ;
  • len : un entier qui est le nombre d’octets de données du message. len est initialisé à 0 ;
  • data : un tableau de 8 octets non signés, donc de data[0] à data[7] qui est également accessible en tant que tableau de 4 mots de 16 bits non signés via data16[0] à data16[3], en temps que tableau de deux mots de 32 bits non signés via data32[0] et data32[1] et enfin en tant qu’entier de 64 bits non signé via data64. Tous les éléments du tableau data sont initialisés à 0.

Le programme avec loopback sans donnée est modifié pour envoyer la variable compte. Tout d’abord, compte est un entier de 32 bits, donc occupant 4 octets. La longueur du message messageCANEmission est donc renseignées en conséquence à la fin de loop().

  1. messageCANEmission.len = 4;

Il suffit ensuite, au moment de l’envoi du message de placer compte dans data32[0],ligne 3 dans la portion de code qui suit.

  1. uint32_t dateCourante = millis();
  2. if (dateCourante - dateDenvoi >= 2000) {
  3. messageCANEmission.data32[0] = compte;
  4. if (controleurCAN.tryToSend(messageCANEmission)) {
  5. Serial.print("envoi ");
  6. Serial.println(compte++);
  7. dateDenvoi = dateCourante;
  8. }
  9. }

En ce qui concerne la réception, il suffit de récupérer la donnée et la longueur et d’afficher le tout.

  1. if (controleurCAN.receive(messageCANReception)) {
  2. Serial.print("recu : ");
  3. Serial.print(messageCANReception.data32[0]);
  4. Serial.print(", longueur : ");
  5. Serial.println(messageCANReception.len);
  6. }

Le code complet est disponible en téléchargement ci-dessous.

Téléchargement 2 : Programme loopback avec envoi de donnée

Du concret

Terminé pour le loopback, passons à des choses plus concrètes. L’application que je vous propose d’examiner nécessite 2 Arduino et 2 modules CAN. Sur le premier Arduino nous mettons deux boutons poussoir et sur le second 2 LED. Un bouton est associé à une LED et appuyer sur le bouton change l’état de la LED correspondante via, bien entendu, un message CAN. La figure 3 donne les connexions à réaliser.

Figure 3 : Connexion de l'émetteur et du récepteur via le bus CAN
Figure 3 : Connexion de l’émetteur et du récepteur via le bus CAN
Les connecteurs J1 des modules CAN doivent recevoir un strap.

La base de départ est le programme 2 en séparant la réception de l’émission. L’émission prend place sur le premier Arduino et la réception sur le second. Par conséquent le CANMessage messageCANEmission; se retrouve dans le programme de l’émetteur et le CANMessage messageCANReception; se retrouve dans le programme du récepteur.

L’émetteur

Comme il s’agit de transmettre un numéro de LED, 0 ou 1, 1 octet de données suffira. Il nous faut également gérer nos deux boutons poussoir. Pour cela, nous utilisons la bibliothèque Bounce2 qui permet de mettre en œuvre un anti-rebond et de gérer les boutons simplement. Les deux boutons sont connectés sur les broches 3 et 4 et deux objets Bounce. Le plus simple est d’utiliser des tableaux.

  1. static const uint8_t NB_BOUTONS = 2;
  2. static const uint8_t brocheBouton[NB_BOUTONS] = { 3, 4 };
  3. Bounce poussoir[NB_BOUTONS];

Dans setup(), les broches sont programmées et attachées aux objets Bounce et la taille de données du message est fixée.

  1. for (uint8_t bouton = 0; bouton < NB_BOUTONS; bouton++) {
  2. pinMode(brocheBouton[bouton], INPUT_PULLUP);
  3. poussoir[bouton].attach(brocheBouton[bouton]);
  4. }
  5.  
  6. messageCANEmission.len = 1;

Dans loop(), les deux boutons sont lus et si l’un d’entre eux ou les deux sont pressés, le message correspondant est envoyé.

  1. for (uint8_t bouton = 0; bouton < NB_BOUTONS; bouton++) {
  2. poussoir[bouton].update();
  3.  
  4. if (poussoir[bouton].fell()) {
  5. messageCANEmission.data[0] = bouton;
  6. const bool ok = controleurCAN.tryToSend(messageCANEmission);
  7. if (ok) {
  8. Serial.print("message ");
  9. Serial.print(bouton);
  10. Serial.println(" envoye !");
  11. }
  12. }
  13. }

Le récepteur

Du côté du récepteur, il faut gérer 2 LED, ce qui est très simple. Tout d’abord les deux broches sont déclarées.

  1. static const uint8_t NB_LED = 2;
  2. const uint8_t brocheLED[NB_LED] = { 3, 4 };

Dans setup(), les deux broches sont programmées en sortie.

  1. for (uint8_t led = 0; led < NB_LED; led++) {
  2. pinMode(brocheLED[led], OUTPUT);
  3. }

Enfin, dans loop() le message est reçu, la donnée est extraite et l’état de la LED correspondante est changé.

  1. if (controleurCAN.receive(messageCANReception)) {
  2. /* Un message CAN est arrive */
  3. static uint32_t numero = 0;
  4. controleurCAN.receive(messageCANReception) ;
  5. Serial.print("Recepteur: ");
  6. Serial.print(++numero);
  7. Serial.print(" message recu : ") ;
  8. Serial.println(messageCANReception.data[0]);
  9. /* l'etat de la LED correspondante est change */
  10. const uint8_t led = messageCANReception.data[0];
  11. if (led < NB_LED) {
  12. digitalWrite(brocheLED[led], !digitalRead(brocheLED[led]));
  13. }
  14. else {
  15. Serial.println("donnee incorrecte");
  16. }
  17. }

Les deux programmes sont téléchargeables ci-dessous.

Téléchargement 3 : l’application de commande deux LED via le bus CAN

La prochaine fois nous verrons l’usage des identifiants et des filtres sur des applications de commande de dispositifs, LEDs, servo-moteurs, via le bus CAN.

[1tapez MCP2515 TJA1050 pour trouver les revendeurs

[2Il faut noter que sur les Uno/Nano, cette broche doit être programmée en sortie pour que le SPI soit en mode maître, ce qui est requis ici, c’est donc la broche à utiliser de manière privilégiée pour le CS du SPI.

[3Certaines carte Arduino ou compatibles disposent de plusieurs SPI qui s’appellent alors SPI, SPI1, SPI2, etc. Sur le Uno, il n’y en a qu’un seul, SPI.

[4Nous verrons les messages remote dans un prochain article.

25 Messages

Réagissez à « La bibliothèque ACAN (1) »

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 « Bibliothèques »

La bibliothèque Servo

Bibliothèque SoftWare Serial

Bibliothèque Serial

Bibliothèque EEPROM

Bibliothèque Wire : I2C

Bibliothèque LCD

La bibliothèque ScheduleTable

Bibliothèque MemoryUsage

Bibliothèque EEPROMextent

La bibliothèque SlowMotionServo

Bibliothèque Commanders

Bibliothèque DCCpp

Bibliothèque DcDccNanoController

Bibliothèque LightEffect

Bibliothèque Accessories (1)

Bibliothèque Accessories (2)

Un décodeur d’accessoires universel (1)

Un décodeur d’accessoires universel (2)

Un décodeur d’accessoires universel (3)

Bibliothèque LcdUi (1)

Bibliothèque LcdUi (2)

Les derniers articles

Bibliothèque LightEffect


Christian

La bibliothèque ACAN (2)


Jean-Luc

La bibliothèque ACAN (1)


Jean-Luc

La bibliothèque SlowMotionServo


Jean-Luc

Bibliothèque DCCpp


Thierry

Bibliothèque DcDccNanoController


Thierry

Bibliothèque LcdUi (2)


Thierry

Bibliothèque LcdUi (1)


Thierry

Bibliothèque Accessories (2)


Thierry

Bibliothèque Accessories (1)


Thierry

Les articles les plus lus

Bibliothèque Wire : I2C

La bibliothèque Servo

Un décodeur d’accessoires universel (1)

Bibliothèque DCCpp

Bibliothèque DcDccNanoController

La bibliothèque ACAN (1)

Bibliothèque SoftWare Serial

Bibliothèque LightEffect

Bibliothèque LCD

Bibliothèque MemoryUsage