Pour la partie message CAN, j'ai bien parcouru les fichiers .cpp et .h , assez compliqué de s'y retrouver.
Un peu plus abordable en regardant le code qui envoie automatiquement des ordres à la centrale mais je ne comprend pas tout quand même:
Par contre, jai passé un peu de temps avec la bibliotheque ACAN et je visualise assez bien la structure d'un message.
Pourrais tu m'indiquer quelle structure de données a tu choisis pour commander Labox?
Par exemple, pour controler une loco, tu ecris
CanMsg::sendMsg(0, cmde, resp, loco->address & 0xFF00, loco->address & 0x00FF, loco->speed, loco->direction);
Pourrais tu expliquer un peu en détail?
-0
-cmde : adresse d'envoi qui défini le type de message et sa priorité? ( controle loco, fonction loco, arret d'urgence,....)
-resp : reponse mais pas bien compris
- je n'ai pas compris les adresse locos et pourquoi 2 datas
-speed/direction OK
Bonjour becbunsen,
Je te remercie de poser ces questions car je suis certain que d’autres sont comme toi mais n’osent pas poser ces questions. C’est dommage, le CAN n’est pas aussi compliqué qu’il n’y parait à condition de passer le temps nécessaire à le comprendre.
Le CAN, c’est une partie matérielle et surtout, ce qui va vous intéresser ici, une messagerie. Je vais me risquer à prendre l’analogie d’une phrase dans le langage courant. Phrase qui doit respecter une certaine structuration pour être comprise par celui ou ceux à qui elle est destinée.
La trame CAN est en quelque sorte à la communication CAN ce que la phrase est au langage avec un sujet, un verbe un complément d’objet direct…
Fondamentalement la trame CAN se divise en un identifiant (sur 11 ou 29 bits) et un champ de données dont la taille totale peut atteindre 64 bits (ou 8 octets) et que l’on divise souvent en 8 champs de 8 bits : data[0], data[1], data[n]
On est libre du nombre de champ data que l’on envoie selon nos besoins, dans certains cas on peut même n’envoyer aucune data ou jusqu’à 8 champs de 8 bits.
Le champ d’identification du message sur 11 ou 29 bits est totalement libre dans sa construction et c’est ce qui le rend parfois difficile à comprendre.
La principale règle est que, pour des questions de priorité de distribution sur le bus et de gestion des conflits entre messages qui arrivent en même temps sur le bus, ce sont les messages qui ont les identifiants les plus faibles qui sont prioritaire sur les autres.
Ainsi, le message qui aurait pour identifiant 1 est prioritaire sur l’identifiant 536 870 912 (29 bits à 1).
Si je reprends mon analogie avec le langage, il va falloir passer un peu de temps pour structurer intelligemment les informations que l’on va placer dans l’identifiant et l’ordre dans lequel on va placer ces informations.
Dans le code que j’ai proposé, on trouve cela illustré ici :
uint16_t hash = 0x1801;
frame.id |= prio << 25; // Priorite
frame.id |= cmde << 17; // Commande
frame.id |= resp << 16; // Reponse : Commande = 0 / Reponse = 1
frame.id |= hash; // Hash
frame.ext = true;
frame.id, dans la bibliothèque ACAN_ESP32 correspond à l’identifiant
J’ai choisi que la priorité des messages tiendrait sur 4 bits et aurait pour valeur 0 (car pour moi les messages à la centrale sont plus prioritaires que les autres sur le bus) et je place cette valeur à la position 25 sur les 29 bits de l’identifiant (que j’ai choisi long).
frame.id |= prio << 25; // Priorite
Position 25 plus longueur de priorité 4 bits remplissent bien les bits 25 à 29 de l’identifiant
J’ai ensuite décidé que les bits qui seront lu comme des bits pour identifier quelles commandes de locomotives allaient être exécutés viendraient se positionner à partir du bit 17 de l’identifiant et tiendraient sur 8 bits.
Concernant l’information de réponse dont tu parles, j’ai jugé que cette information était très intéressante pour savoir s’il s’agissait d’une commande envoyée bit 16 à 0) ou si c’était une réponse de la centrale qui confirme qu’elle a bien reçue la commande (bit 16 à 1). Dans ce cas, la centrale confirme la réception en revoyant un message en tout point identique à celui reçu mais en changeant seulement la valeur du bit 16. Une sorte d’accusé de réception.
Nous verrons cela en détail un peu plus loin.
Enfin, les 16 derniers bits de poids faible de l’identifiant, j’ai choisi de les remplir avec une valeur choisie arbitrairement mais qui souvent est l’ID du nœud CAN qui expédie le message. Ici, j’ai choisi uint16_t hash = 0x1801;
Enfin, deux derniers points, avec la bibliothèque ACAN_ESP32, c’est au programmeur d’indiquer si l’identifiant est sur 29 (frame.ext = true;) ou sur 11 bits (frame.ext = false;). Ext pour extended frame format.
Le programmeur doit aussi renseigner la longueur du champ de données frame.len (en octets).
Si l’on se place maintenant dans la position du récepteur du message, c’est ce que l’on trouve dans le fichier CommandStation-EX-LaBox2/CanMsg.cpp
On voit tout d’abord que l’on « décode » l’identifiant :
if (ACAN_ESP32::can.receive(frameIn))
{
const uint8_t cmde = (frameIn.id & 0x1FE0000) >> 17; // Commande
const uint8_t resp = (frameIn.id & 0x10000) >> 16; // Commande = 0 / Reponse = 1
const uint16_t hash = (frameIn.id & 0xFFFF); // Hash
ensuite, sans entrer dans les détails, on voit que selon la commande envoyée (switch(cmde)), on execute l’une ou l’autre des fonctions internes à la box :
switch (cmde) // Fonction appelée
{
case 0xF0:
DCC::setThrottle(loco, frameIn.data[2], frameIn.data[3]);
xQueueSendToBack(xQueue, &frameIn, 0);
break;
case 0xF1:
DCC::setFn(loco, frameIn.data[2], frameIn.data[3]); // frame.data[2] = fonction, frame.data[3] : 'on' ou 'off'
break;
case 0xF7:
// WRITE CV on MAIN <w CAB CV VALUE>
DCC::writeCVByteMain(loco, frameIn.data[2], frameIn.data[3]);
break;
case 0xFE:
TrackManager::setMainPower(frameIn.data[0] ? POWERMODE::ON : POWERMODE::OFF);
xQueueSendToBack(xQueue, &frameIn, 0);
break;
case 0xFF:
DCC::setThrottle(0, 1, 1); // emergency stop
xQueueSendToBack(xQueue, &frameIn, 0);
break;
}
setThrottle, commande de traction ou de e-stop dans le cas où les paramètres sont :
DCC::setThrottle(0, 1, 1); // emergency stop
Commande de mise sous tension ou d’extinction de la box :
case 0xFE:
TrackManager::setMainPower(frameIn.data[0] ? POWERMODE::ON : POWERMODE::OFF);
La fonction
xQueueSendToBack(xQueue, &frameIn, 0);
est la réponse que la commande à bien été reçue.
Voilà pour ne pas faire trop long.
Deux réponse complémentaire cependant, pourquoi ceci :
loco->address & 0xFF00, loco->address & 0x00FF,
simplement pour diviser l’adresse qui peut être en 16 bits sous forme de de deux valeurs en 8 bits.
Je vois d’ailleurs qu’il y a une erreur ici puisque cela devrait être :
(loco->address & 0xFF00) >> 8, loco->address & 0x00FF
Attention, ceci n'a rien à voir : Ou tout simplement si je veux controler la loco 5 à vitesse 25 en avant sans passer par la programmation objet comme dans la box: que dois-je ecrire :
ACANSettings settings (125 * 1000) ; // 125 kbit/s est la vitesse de transmission des messages sur le bus CAN.
Comme tu me sembles vouloir comprendre le CAN je te conseille de continuer à expérimenter et poser des questions et de bien lire les articles de base en particulier celui de Jean-Luc :
https://www.locoduino.org/spip.php?article268Je te conseille vraiment de réaliser (pas simplement lire) ce que présente Jean-Luc et tu auras acquis 80% de ce qu'il faut savoir.
Christophe