Voici une "recette de cuisine" pour la mise en oeuvre rapide du bus CAN.
Au dela de l'installation et de la mise en oeuvre de la carte CAN Locoduino, j'ai mis au point une gestion de mémoire tampon circulaire dans laquelle les messages sont enregistrés aussitôt que possible, et exploités ensuite tranquillement par la fonction LOOP.
Comme cela, on est certain de ne rien perdre !
Le matériel se compose d'une carte Arduino (par exemple ici un Mega2560) et d'une carte CAN Locoduino.
On commence par relier à la carte CAN les broches du bus SPI du Mega : 50 (MISO), 51 (MOSI), 52 (SCK), 53 (SS : chip select CAN), 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.
Il faut aussi télécharger une bibliothèque qui se trouve ici :
https://github.com/Seeed-Studio/CAN_BUS_ShieldPuis il faut placer le dossier téléchargé dans le dossier des autres bibliothèques (voir l'article "Installer un bibliothèque").
Ensuite on peut placer ces 2 lignes en tête de programme :
#include <SPI.h> // pour la bibliotheque CAN
#include "mcp_can.h" // bibliotheque 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)
unsigned char Flag_Recv = 0; // variable d'échange avec l'interruption IRQ
Ainsi, on le voit, qu'une variable "Flag_Recv" qui servira à faire savoir à la LOOP qu'un ou plusieurs messages sont arrivés "sous interruption".
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, abordons le SETUP dans lquel on insère les lignes suivantes :
/* -----------------------------------------------------
* SETUP
* -----------------------------------------------------
*/
/////////////// INIT CAN /////////////////
START_INIT:
if(CAN_OK == CAN.begin(CAN_500KBPS)) // initialisation du can bus : baudrate = 500k
{
Serial.println(F("CAN BUS init ok!"));
}
else
{
Serial.println(F("CAN BUS init echec !"));
Serial.println(F("Init CAN BUS a nouveau"));
delay(200);
goto START_INIT;
}
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 (SETUP), 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 .
Puis 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 cette réalisation :
/*
* set mask & filter
*/
CAN.init_Mask(0, 0, 0x3ff); // there are 2 mask in mcp2515, you need to set both of them
CAN.init_Mask(1, 0, 0x3ff); // a preciser
CAN.init_Filt(0, 0, 0x03); // Reception possible : Id 03 (hex)
CAN.init_Filt(1, 0, 0x04); // Reception possible : Id 04 (hex)
CAN.init_Filt(2, 0, 0x30); // Reception possible : Id 30 (hex)
CAN.init_Filt(3, 0, 0x40); // Reception possible : Id 40 (hex)
CAN.init_Filt(4, 0, 0x31); // Reception possible : Id 31 (hex)
CAN.init_Filt(5, 0, 0x41); // Reception possible : Id 41 (hex)
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 pret pour un nouvel IRQ
CAN_recup(); // récupération et traitement du ou des messages CAN reçus
}
et la fonction CAN_recup() est là :
// Message recu
byte IdR; // Id pour la routine CAN_recup()
unsigned char lenR = 0; // Longueur " " "
unsigned char bufR[8]; // buffer reception "
// Message emis
unsigned char bufS[4]; // buffer emission
// Memoire circulaire pour le stockage rapide des messages recus
unsigned char _Circule[256]; // recepteur circulaire des messages CAN sous IT
int _indexW, _indexR, _Ncan; // index d'ecriture et lecture, nb d'octets a lire
byte _CANoverflow = 0; // flag overflow (buffer _Circule plein)
/*
* Routine de récuperation des messages CAN dans la memoire circulaire _Circule
* appelee 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
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; // depassement de la capacite de Circule
}
}
}
Dans Loop, récupérer un message, en parfaite indépendance de leur réception se fait ainsi :
// traitement d'un seul message par loop dans la memoire circulaire _Circule
if (_Ncan > 2) { // messages dans _Circule : au moins 3 bytes
_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 flag qui permet de "sortir" les messages ou non
Serial.print("CAN id ");
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);
}
} // le message est dans les globales RId, Rlen et Rbuf[..]
Serial.println();
Voilà que ce je dois expliquer…