Auteur Sujet: Réception CAN optimale  (Lu 36452 fois)

bricoleau

  • Jr. Member
  • **
  • Messages: 51
    • Voir le profil
Réception CAN optimale
« le: décembre 22, 2017, 01:30:56 pm »
Un petit sujet de discussion pour partager mon expérimentation de ce matin.
Rien de fracassant, mais quelques infos quand même.

La question de départ était : Depuis le programme arduino, comment vérifier la présence de message reçu dans le MCP2515, de la manière la plus rapide possible ?

En effet, quel que soit le programme, l'arduino va devoir se poser cette question très régulièrement, disons plusieurs milliers de fois par seconde.
Et dans la plupart des cas, la réponse sera négative.
Il serait donc dommage que la question coûte cher en CPU, surtout sur les cas de réponse négative.

Le MCP2515 dispose d'une sortie INT. Celle-ci donne l'état de ses buffers de réception internes.
Elle est à GND tant qu'il y a un message disponible à récupérer.

Par ailleurs, l'arduino peut dialoguer avec le MCP2515 via la liaison SPI, au travers des méthodes CAN.checkReceive() et CAN.readMsgBuf().
Il est donc possible de ne pas utiliser la sortie INT et d'utiliser seulement ces méthodes d'accès.

De plus, comme la méthode CAN.readMsgBuf() retourne la valeur CAN_NOMSG lorsqu'il n'y a rien à lire,  on peut s'interroger sur l'utilité d'appeler systématiquement CAN.checkReceive() avant CAN.readMsgBuf().
Cet appel systématique pourrait être justifié si le checkReceive() répondait significativement plus vite en cas d'absence de message à récupérer.

J'ai donc testé plusieurs cas :
  • sortie INT utilisée via une interruption
  • sortie INT lue par un digitalRead
  • sortie INT lue par accès direct aux registres de l'arduino
  • utilisation de la méthode checkReceive()
  • utilisation directe de la méthode readMsgBuf()

Le test a été effectué à vide, c'est-à-dire avec un bus CAN sur lequel aucun message ne circule, car l'objectif était de vérifier l'absence de message disponible dans le MCP2515, le plus vite possible

Le programme de test
#include <mcp_can.h>

const char* pgm =
"Programme d'evaluation des diverses façons de verifier la presence d'un message CAN a lire";

/* Cablage Arduino <-> MCP2515
 *   D3  <-> INT
 *   D13 <-> SCK
 *   D11 <-> SI
 *   D12 <-> SO
 *   D10 <-> CS
 *   GND <-> GND
 *   5V  <-> VCC
 */
const uint8_t pin_INT = 3;
const uint8_t pin_CS = 10;

MCP_CAN CAN(pin_CS);

const uint32_t nb_tests = 100000UL;

uint32_t bidon;//variable globale bidon pour tromper le compilo

char* reference()
{
  for (uint32_t i=0; i<nb_tests; i++)
  {
    //Si on ne met rien de consistant dans la boucle for,
    //le compilo va optimiser tout ça et virer le code inutile
    if (bidon < 100) bidon++;
    //A cet endroit on va mettre les differentes versions à tester
  }
  return __func__;
}

volatile bool message_a_lire = false; //mise à jour par interruption, non implémentée pour le test
char* version_interruption()
{
  for (uint32_t i=0; i<nb_tests; i++)
  {
    if (bidon < 100) bidon++;
    if (message_a_lire)
    {
      bidon++;//on n'arrive jamais ici car aucun message echangé lors du test
    }
  }
  return __func__;
}

char* version_lecture_pin_INT_digitalRead()
{
  for (uint32_t i=0; i<nb_tests; i++)
  {
    if (bidon < 100) bidon++;
    if (!digitalRead(pin_INT))
    {
      bidon++;
    }
  }
  return __func__;
}

char* version_lecture_pin_INT_directe()
{
  for (uint32_t i=0; i<nb_tests; i++)
  {
    if (bidon < 100) bidon++;
    if (!(PIND & (1 << pin_INT)))
    {
      bidon++;
    }
  }
  return __func__;
}

char* version_CAN_checkReceive()
{
  for (uint32_t i=0; i<nb_tests; i++)
  {
    if (bidon < 100) bidon++;
    if (CAN.checkReceive() == CAN_MSGAVAIL)
    {
      bidon++;
    }
  }
  return __func__;
}

char* version_CAN_readMsgBuf()
{
  uint32_t rxId;
  uint8_t rxTaille;
  uint8_t rxData[8];
  for (uint32_t i=0; i<nb_tests; i++)
  {
    if (bidon < 100) bidon++;
    if (CAN.readMsgBuf(&rxId, &rxTaille, rxData) == CAN_OK)
    {
      bidon++;
    }
  }
  return __func__;
}

void evaluer(char* fonction())
{
  static uint32_t chrono_reference;
 
  uint32_t chrono = micros();
  char* nom = fonction();
  chrono = micros() - chrono;

  if (fonction == reference)
  {
    chrono_reference = chrono;
  }

  Serial.print("\nEvaluation ");
  Serial.print(nom);
  Serial.println(" :");
  Serial.print("  chrono_global_brut=");
  Serial.print(chrono);
  Serial.println(" µs");
  Serial.print("  chrono_unitaire_brut=");
  Serial.print(chrono / (nb_tests / 1000));
  Serial.println(" ns");
  chrono -= chrono_reference;
  Serial.print("  chrono_global_net=");
  Serial.print(chrono);
  Serial.println(" µs");
  Serial.print("  chrono_unitaire_net=");
  Serial.print(chrono / (nb_tests / 1000));
  Serial.println(" ns");
}

void setup()
{
  pinMode(pin_INT, INPUT);
  Serial.begin(115200);
  Serial.println(pgm);
  Serial.print("\nNombre de tests de lecture par tir : ");
  Serial.println(nb_tests);
  evaluer(reference);
  evaluer(version_interruption);
  evaluer(version_lecture_pin_INT_digitalRead);
  evaluer(version_lecture_pin_INT_directe);
  evaluer(version_CAN_checkReceive);
  evaluer(version_CAN_readMsgBuf);
}

void loop()
{
}

Résultat sur le terminal :
Programme d'evaluation des diverses façons de verifier la presence d'un message CAN a lire

Nombre de tests de lecture par tir : 100000

Evaluation reference :
  chrono_global_brut=75968 µs
  chrono_unitaire_brut=759 ns
  chrono_global_net=0 µs
  chrono_unitaire_net=0 ns

Evaluation version_interruption :
  chrono_global_brut=107392 µs
  chrono_unitaire_brut=1073 ns
  chrono_global_net=31424 µs
  chrono_unitaire_net=314 ns

Evaluation version_lecture_pin_INT_digitalRead :
  chrono_global_brut=478392 µs
  chrono_unitaire_brut=4783 ns
  chrono_global_net=402424 µs
  chrono_unitaire_net=4024 ns

Evaluation version_lecture_pin_INT_directe :
  chrono_global_brut=145120 µs
  chrono_unitaire_brut=1451 ns
  chrono_global_net=69152 µs
  chrono_unitaire_net=691 ns

Evaluation version_CAN_checkReceive :
  chrono_global_brut=1974784 µs
  chrono_unitaire_brut=19747 ns
  chrono_global_net=1898816 µs
  chrono_unitaire_net=18988 ns

Evaluation version_CAN_readMsgBuf :
  chrono_global_brut=2006252 µs
  chrono_unitaire_brut=20062 ns
  chrono_global_net=1930284 µs
  chrono_unitaire_net=19302 ns

Il y a une fonction de référence dont on mesure le temps d'exécution, puis les diverses versions testées.
Afin d'évaluer le temps d'exécution spécifique à chacune d'elles, je prends son temps d'exécution brut, duquel je déduis le temps d'exécution de la fonction de référence, pour avoir un résultat net qui correspond exactement aux quelques lignes de différence dans le code.

Evidemment, sans surprise, le test sur un booléen d'état mis à jour par une interruption est le plus rapide. Il ne coûte que 314 nanosecondes.
Encore que, cela me semble vraiment très peu. Je me demande si le compilo n'a pas optimisé ce petit bout de programme trop simple, pour laisser cette variable dans un registre de l'atmega.

On voit bien la différence entre un bon gros digitalRead et une lecture directe du registre PIND. En termes de rapidité, celui-ci n'est d'ailleurs pas loin du cas avec interruption.

Enfin, le checkReceive et le readMsgBuf ont des temps d'exécution voisins à 19 microsecondes (toujours dans le cas où il n'y a rien à lire), avec un petit avantage pour le checkReceive.


Au vu de ces résultats, je pars sur le modèle de réception CAN ci-dessous
#include <mcp_can.h>
/* Cablage Arduino <-> MCP2515
 *   D3  <-> INT
 *   D13 <-> SCK
 *   D11 <-> SI
 *   D12 <-> SO
 *   D10 <-> CS
 *   GND <-> GND
 *   5V  <-> VCC
 */
const uint8_t pin_INT = 3; //valeur 2 ou 3
const uint8_t pin_CS = 10;

MCP_CAN CAN(pin_CS);

volatile bool message_CAN_disponible = false;

void ISR_reception_CAN()
{
  message_CAN_disponible = true;
}

void initialiser_reception_CAN()
{
  pinMode(pin_INT, INPUT);
  int8_t numero_interruption = digitalPinToInterrupt(pin_INT);
  if (numero_interruption != -1)
  {
    attachInterrupt(numero_interruption, ISR_reception_CAN, FALLING);
  }
  else
  {
    //erreur : pas d'interruption disponible pour la valeur de pin_INT
  }
}

void CAN_recup()
{
  if (message_CAN_disponible)
  {
    message_CAN_disponible = false;
    while (!(PIND & (1 << pin_INT)))// ou bien !digitalRead(pin_INT)
    {
      uint32_t rxId;
      uint8_t rxTaille;
      uint8_t rxBuffer[8];
      if (CAN.readMsgBuf(&rxId, &rxTaille, rxBuffer) == CAN_OK)
      {
        traiter_message_CAN_recu(rxId, rxTaille, rxBuffer);
      }
    }
  }
}

void traiter_message_CAN_recu(uint32_t id, uint8_t taille, uint8_t buffer[])
{
  //...ou bien chargement dans un buffer en RAM pour traitement ultérieur
}

void setup()
{
  initialiser_reception_CAN();
  //...
}

void loop()
{
  CAN_recup();
  //...
}

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 3039
  • 100% Arduino et N
    • Voir le profil
Re : Réception CAN optimale
« Réponse #1 le: décembre 22, 2017, 10:07:07 pm »
Très belle démonstration avec un programme de test qui peut servir à plein de choses  :D

Si le but est de réduire le temps de traitement hors message CAN (c’est à dire quand il n’y a aucune chance d’en perdre), quand un message arrive, il faut ajouter le traitement qui peut être plus ou moins long.

Sur MEGA j’utilise le buffer circulaire, mais pas sur le DUE qui est infiniment plus rapide.

Lors de mes tests il y a maintenant 2 ans, j’avais développé un générateur de messages avec des données qui étaient incrémentées à l’emission pour détecter les pertes de messages à la réception.

Mais je pense que le plus utile est de définir tous les types de messages nécessaires et les circonstances de transmission. En DCC, il n’y a pas de contraintes de temps donc on peut toujours s’arranger pour ne pas en perdre.
Cordialement,
Dominique

bricoleau

  • Jr. Member
  • **
  • Messages: 51
    • Voir le profil
Re : Re : Réception CAN optimale
« Réponse #2 le: décembre 23, 2017, 12:11:36 am »
Lors de mes tests il y a maintenant 2 ans, j’avais développé un générateur de messages avec des données qui étaient incrémentées à l’emission pour détecter les pertes de messages à la réception.

Oui oui t'inquiète j'ai commencé par ça aussi  :D

Un arduino émetteur et un arduino récepteur.
L'arduino émetteur procède à une série de tirs.
Chaque tir est annoncé par un message, qui contient les caractéristiques des messages qui vont suivre : identifiant, longueur et nombre d'envois. Les donnée sont incrémentées de manière circulaire.
L'arduino récepteur vérifie qu'il reçoit tout bien avec les bonnes valeurs, et retourne un message d'acquittement du tir. Côté récepteur, j'affiche aussi la part de cpu dévolue à la récupération des messages.
J'ai fait varier la longueur des messages, la quantité de la rafale, ainsi que la durée entre deux émissions, en mesurant tout sans jamais rien perdre.

Comme on ne peut pas avoir deux terminaux ouverts sur l'IDE arduino, j'ai utilisé putty.

Je mets en pièce jointe les deux programmes et les deux résultats.

Par la suite, je pense faire des tests de tirs croisés avec deux émetteurs et deux récepteurs, histoire d'observer la saturation du bus. Il me faudra un cinquième arduino pour rendre compte des annonces et acquittements sur un terminal unique.
« Modifié: décembre 23, 2017, 12:15:42 am par bricoleau »

CATPLUS

  • Sr. Member
  • ****
  • Messages: 435
    • Voir le profil
Re : Réception CAN optimale
« Réponse #3 le: décembre 23, 2017, 10:44:22 am »
Bonjour

Tout ceci est légèrement confus dans mon esprit.
Ma question ?
Si j'ai bien tout compris, on envoie des informations de l'Arduino1 vers l'Arduino2, en retour le l'Arduino2 revoie l'information au 1 et ainsi de suite.
Pourrais-tu STP faire un schéma

Cordialement
Bonnes Fêtes de Fin d'Année

Marcel
Best Regards

bricoleau

  • Jr. Member
  • **
  • Messages: 51
    • Voir le profil
Re : Réception CAN optimale
« Réponse #4 le: décembre 23, 2017, 02:04:40 pm »
Par exemple si je prends le tir 150 que l'on peut voir dans le fichier joint ci-dessus CAN_00_E.txt

Declenchement tir 150 delai=00500 nb_envois=1000 taille=5 ... OK duree=00499832 microsecondes.  Accuse reception OK
L'arduino 1 a d'abord émis un tout premier message CAN d'annonce de tir, dans lequel il indique qu'il va envoyer 1000 messages de 5 octets. L'identifiant CAN de chacun de ces 1000 messages est 150 (=le numéro de tir)

L'arduino 2 lit ce tout premier message d'annonce, et se met en attente de la salve

L'arduino 1 envoie les 1000 messages, à raison d'un message toutes les 500 microsecondes (valeur du délai pour ce tir)

L'arduino 2 recoit les 1000 messages, contrôle tout (identifiant, longueur, nombre de messages, contenu des données), puis émet un seul message, qui sert d'accusé de réception pour rendre compte de ses contrôles.

L'arduino 1 reçoit l'accusé de réception en provenance de l'arduino 2. Si un problème a été détecté par l'arduino2, le programme s'arrête, sinon il passe au tir suivant, et on recommence
« Modifié: décembre 23, 2017, 02:07:39 pm par bricoleau »

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 3039
  • 100% Arduino et N
    • Voir le profil
Re : Réception CAN optimale
« Réponse #5 le: décembre 23, 2017, 03:09:37 pm »
Bon d’accord c’est un benchmark du bus CAN et des fonctions d’emission et réception des Arduino qui sont de bons exemples.

Plusieurs questions :
- sur quels Arduino ?
- avec quelle interface CAN (le petit chinois bleu à 8 MHz ?)?

Donc les tests sont OK et on peut passer à autre chose, par exemple les applications au modelisme qui nous intéressent ici.

Quel est ton projet personnel ?
Bien entendu le sujet du train du futur avec des modules communicant via CAN est très intéressant, mais surtout la définition des messages à échanger, qui va déterminer les risques de pertes et leurs solutions.

Je suis ravi que tu nous rejoignes sur ce terrain.

Mais Noël approche et je ne vais pas avoir beaucoup de temps.

Bon Noel en tout cas  ;D
Amicalement
Dominique
Cordialement,
Dominique

bricoleau

  • Jr. Member
  • **
  • Messages: 51
    • Voir le profil
Re : Réception CAN optimale
« Réponse #6 le: décembre 23, 2017, 04:33:15 pm »
Oui exact c'est juste du benchmark et ce n'est pas plus intéressant que ça.
Mais sur ce point je n'ai fait que réagir et répondre, je n'ai pas introduit le sujet ici.

Je suis effectivement en train de faire diverses expérimentations pour bien définir les limites d'usage des petites cartes CAN à 8Mhz (oui les "petits chinois" à 2€ pièce).
J'utilise des cartes arduino standard, c'est-à dire tout ce qui a un atmega328p à 16 Mhz (arduino classique / nano / pro mini / arduino ethernet)

Mon projet modélisme est exposé dans un fil de discussion dédié, dans la section du forum prévue à cet effet. C'est là qu'il sera développé au grée de mes avancées.
Ici on est dans les discussions générales autour du bus CAN, il m'a donc semblé naturel d'y mettre ce que j'aurais aimé trouver à ce sujet, et qui pourrait être utile pour les suivants.
Mais bon si tu trouves que c'est sans valeur ajoutée pour le forum, ok je m'adapte sans une once de contrition. Cela me va aussi.

Sur le bus CAN spécifiquement : je travaille d'abord tous les aspects techniques émission/réception. Là j'en suis à l'implémentation du buffer circulaire en réception, qui me semble indispensable.
J'ai encore pas mal de tests à faire, en particulier sur les diverses stratégies possibles d'utilisation des filtres (et donc de codage des identifiants de messages).
Car je pense que ce sont les contraintes techniques qui vont dicter les bonnes règles d'usage des filtres.
Au bout du bout, je vise une implémentation la plus propre possible, packagée sous forme d'une bibliothèque la plus simple possible d'utilisation.
Je peux détailler ce cheminement ici dans la section CAN, ou bien me contenter d'en exposer un exemple de mise en application dans le cadre de mon projet, lorsque j'en serais là. Tout me va.

Bon Noel également  :)

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 3039
  • 100% Arduino et N
    • Voir le profil
Re : Re : Réception CAN optimale
« Réponse #7 le: décembre 23, 2017, 05:04:14 pm »
Mais bon si tu trouves que c'est sans valeur ajoutée pour le forum, ok je m'adapte sans une once de contrition. Cela me va aussi.

C'est maladif chez moi, il faut que je modère  8) ??? ::)

Mais pas du tout, c'est très utile pour tout le monde car le CAN doit faire peur à beaucoup de modélistes et on est malheureusement trop peu à s'en servir. De plus tes tests confirme ceux que j'avais fait il y a 2 ans.
Il se trouve qu'au bout de quelques semaines, j'en ai eu marre des tests et je suis parti dans l'utilisation du Can dans mon projet et j'avoue que ça se passe plutôt bien.

En passant j'ai regardé tes autres contributions, toujours très interessantes et précises, et j'ai pensé ajouter un peu de mon cru à celle consacrée aux boutons poussoirs.
http://forum.locoduino.org/index.php?topic=122.msg4268#msg4268

Bonne fêtes
Dominique
« Modifié: décembre 23, 2017, 05:07:42 pm par Dominique »
Cordialement,
Dominique

Jean-Luc

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 1714
    • Voir le profil
Re : Réception CAN optimale
« Réponse #8 le: décembre 24, 2017, 10:39:11 am »
Dominique, le modérateur immodéré  :)

Le sujet est tout à fait intéressant et il faut continuer ce fil sans aucun doute.

Coincidence, j'avais choisi le MCP2515 pour une étude de cas dans un cours sur la modélisation et le model-checking (c'est juste parce que le composant est assez simple pour que le modèle soit court)

Concernant cette histoire de bufferisation, il faut souligner quelques petites choses (bus à 1Mb/s):

1) un message CAN nécessite de 47 à (trame standard, zéro octets de données, bit stuffing minimum) à 160 µs (trame étendue, 8 octets de données, bit stuffing maximum) pour être transmis sur le bus. Les tables ci-dessous donnent ces temps

Trame standard
Nb. d'octets de donnéesMinimumMaximum
04755
15565
26375
37185
47995
587105
695115
7103125
8111135

Trame étendue
Nb. d'octets de donnéesMinimumMaximum
06780
17590
283100
391110
499120
5107130
6115140
7123150
8131160

Il faut également ajouter les temps inter-messages qui est de 4 µs

2) récupérer un octet par le SPI à 8MHz nécessite 1µs et un message occupe de 5 à 13 octets selon le nombre d'octets de données. À cela il faut ajouter 2 octets pour connaître l'état des message buffer et un octet envoyé au MCP2515 pour demander la lecture du message buffer. On ne distingue pas les trames étendues et les trames standard car ceci demanderait plus de temps que de récupérer l'identifiant complet même si la partie étendue ne sert pas. Lire l'état et lire un message buffer prend donc de 8 à 16 µs.

Dans le pire cas, on vide un message buffer contenant une trame de 8 octets, soit 16µs pendant que le message le plus court possible est en cours de réception dans le MAB, soit 47µs + 4µs. Il faut évidemment ajouter le temps d'exécution des instructions de traitement du message mais je doute que ça atteigne les 35µs restantes.

Donc je pense que si on procède correctement on ne perd pas de message à ce niveau à condition de vider les message buffers dans l'ISR. On doit aussi prendre en compte le temps d'exécution des autres ISR (par défaut il y a seulement l'ISR du Timer0) et également le temps d'envoi d'un message vers le 2515 (14µs au pire) qui bloque le SPI et les IT quand on utilise les transactions. Dans certaines circonstances, ça commence à être tendu.

Toutefois, il faut aussi prendre en compte le système complet. Un nœud ne va pas recevoir des messages à n'importe quel moment et à n'importe quel rythme. Si on veut maîtriser ce qui se passe sur le bus, il faut définir pour chaque message une période d'émission. Par conséquent, au lieu d'envoyer un message quand la donnée correspondante est mise à jour, il faut envoyer la donnée périodiquement sur le bus avec une période compatible avec le procédé piloté. C'est à dire transmettre des états et non des événements. Ceci a deux avantages :
1) on connaît pas construction la périodicité des messages et le temps minimum d'inter-arrivée des messages sur un nœud. On est donc plus prédictible sur le comportement.
2) si suite à un bug ou un dysfonctionnement un message est perdu, ça n'a pas d'importance puisque le message est répété. On est donc plus robuste.
Cordialement

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 3039
  • 100% Arduino et N
    • Voir le profil
Re : Réception CAN optimale
« Réponse #9 le: décembre 24, 2017, 12:56:37 pm »
Grandiose  ;D

A déguster sans modération !

Joyeux Noël à tous
Cordialement,
Dominique

CATPLUS

  • Sr. Member
  • ****
  • Messages: 435
    • Voir le profil
Re : Réception CAN optimale
« Réponse #10 le: décembre 25, 2017, 03:26:42 pm »
Bonjour à tous en espérant que vous passé un Joyeux Noel

Désolé pour ma question somme toute incongrue.

J'aime bien faire des montages, copier bêtement sans rien comprendre cela n'a aucune utilité (pour ma part :-\)
Merci pour les explications. J'attends une application réelle pour mettre à profis vos tests.

Cordialement
Marcel
Best Regards

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 3039
  • 100% Arduino et N
    • Voir le profil
Re : Réception CAN optimale
« Réponse #11 le: décembre 26, 2017, 07:30:15 pm »
Rassures-toi Marcel, ça va venir.
Bonne fêtes.
Dominique
Cordialement,
Dominique

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 3039
  • 100% Arduino et N
    • Voir le profil
Re : Re : Réception CAN optimale
« Réponse #12 le: décembre 26, 2017, 07:45:44 pm »
Donc je pense que si on procède correctement on ne perd pas de message à ce niveau à condition de vider les message buffers dans l'ISR.

Merci Jean-Luc,

Dans mes versions AVR en effet, j'utilise l'ISR uniquement pour faire monter un flag et je vide les buffers de message du 2515 juste après dans la loop. L'ISR arrivant n'importe quand par rapport à la loop, il peut s'être écoulé pas mal de µs avant de vider les buffers.

Je vais donc intégrer ma routine CAN_recup() dans l'ISR à partir de maintenant. Le flag Flag_Recv n'est même plus nécessaire.

//--- Globales : Memoire circulaire pour le stockage rapide des messages recus
unsigned char _Circule[512];          // recepteur circulaire des messages CAN sous IT
unsigned int _indexW, _indexR, _Ncan; // index d'ecriture et lecture, nb d'octets a lire
byte _CANoverflow = 0;                // flag overflow (buffer _Circule plein)


//--- Interruption (ISR) CAN
void MCP2515_ISR()
{
  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
    }
  }
}

Je vais donc tester et corriger ce que j'ai écrit là http://forum.locoduino.org/index.php?topic=290.msg4254#msg4254

Amicalement

Dominique
Cordialement,
Dominique

bricoleau

  • Jr. Member
  • **
  • Messages: 51
    • Voir le profil
Re : Réception CAN optimale
« Réponse #13 le: décembre 27, 2017, 07:08:54 pm »
Bonjour

Pour ma part je ne le sens pas trop, de mettre le CAN_recup() dans l'ISR.
Même si effectivement en cherchant sur le sujet, je vois plusieurs avis sur le net qui tendent à montrer que l'on peut utiliser sans trop de risque la communication SPI dans une routine d'interruption.
Je reste un peu inquiet des potentiels effets de bord sur les montages où le SPI est utilisé par ailleurs (shield ethernet par exemple), ou bien ceux où on utilise l'I2C.

Perso je me sens plus à l'aise avec un programme bien structuré en unités d'oeuvres très courtes, capable de lancer un CAN_Recup() à une fréquence suffisante. Ca au moins je suis à peu près sûr d'arriver à le maîtriser sans mauvaises surprises, mais il est vrai que je suis loin du niveau de maîtrise technique de Jean-Luc.

Donc sauf à me retrouver coincé à un moment donné, je crois que je vais rester sur mon réflexe pavlovien acquis de longue date : moins on met de code dans une routine interruption, moins on est emm..bêté.  :D


Il y a aussi un tout autre sujet qui me chatouille avec le code de Dominique : visiblement nous n'utilisons pas tous la même bibliothèque mcp_can.
Et ceci pour une bonne (?) raison : les articles publiés sur le site éditorial ne référencent pas tous la même.

Par exemple :
L'article Une Passerelle entre le bus S88 et le bus CAN pour la rétro signalisation invite à utiliser https://github.com/coryjfowler/MCP_CAN_lib
L'article Mise en oeuvre du Bus CAN entre modules Arduino invite à utiliser https://github.com/Seeed-Studio/CAN_BUS_Shield

J'utilise la première listée, et je vois bien que Dominique utilise la seconde.
Les deux codes me semblent assez proches, mais il y a quelques différences, comme la méthode getCanId() présente d'un côté mais pas de l'autre.

Y a des chances pour que ces bibliothèques soient aussi valables les unes que les autres, mais pour partager nos codes il serait quand même préférable que nous utilisions tous la même.

Quelle est la version officiellement préconisée par Locoduino ? la Seeed-Studio ?

Jean-Luc

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 1714
    • Voir le profil
Re : Re : Réception CAN optimale
« Réponse #14 le: décembre 27, 2017, 09:13:44 pm »
Pour ma part je ne le sens pas trop, de mettre le CAN_recup() dans l'ISR.
Même si effectivement en cherchant sur le sujet, je vois plusieurs avis sur le net qui tendent à montrer que l'on peut utiliser sans trop de risque la communication SPI dans une routine d'interruption.
Je reste un peu inquiet des potentiels effets de bord sur les montages où le SPI est utilisé par ailleurs (shield ethernet par exemple), ou bien ceux où on utilise l'I2C.

Il y aurait potentiellement des problèmes avec un autre périphérique SPI mais pas avec l'I2C. Concernant le SPI, la réponse est ici : https://www.arduino.cc/en/Reference/SPIusingInterrupt. La bibliothèque mcp_can utilise les transactions, la bibliothèque Ethernet également.


Citer
Il y a aussi un tout autre sujet qui me chatouille avec le code de Dominique : visiblement nous n'utilisons pas tous la même bibliothèque mcp_can.
Et ceci pour une bonne (?) raison : les articles publiés sur le site éditorial ne référencent pas tous la même.

Par exemple :
L'article Une Passerelle entre le bus S88 et le bus CAN pour la rétro signalisation invite à utiliser https://github.com/coryjfowler/MCP_CAN_lib
L'article Mise en oeuvre du Bus CAN entre modules Arduino invite à utiliser https://github.com/Seeed-Studio/CAN_BUS_Shield

J'utilise la première listée, et je vois bien que Dominique utilise la seconde.
Les deux codes me semblent assez proches, mais il y a quelques différences, comme la méthode getCanId() présente d'un côté mais pas de l'autre.

Y a des chances pour que ces bibliothèques soient aussi valables les unes que les autres, mais pour partager nos codes il serait quand même préférable que nous utilisions tous la même.

Quelle est la version officiellement préconisée par Locoduino ? la Seeed-Studio ?

La Seeed-Studio pour ma part mais à vrai dire aucune des deux ne me plait, je suis en train de faire la mienne :)
« Modifié: décembre 28, 2017, 09:06:05 am par Jean-Luc »
Cordialement