Un Raspberry Pi, une interface CAN à quelques euros et le runtime LabVIEW suffisent pour piloter un variateur de moteur au profil CANopen DS402. Cet article décrit la chaîne complète : configuration du bus sous Linux, passerelle entre LabVIEW et les sockets du noyau, puis deux façons de parler CANopen, l’une en s’appuyant sur la pile CANopenNode, l’autre en construisant soi-même les trames NMT, SDO et PDO dans des classes LabVIEW.
Pour décoder les trames citées au fil du texte ou en construire de nouvelles, le décodeur de trames CANopen fait le travail dans le navigateur.
L’architecture
- Le Raspberry Pi exécute le programme LabVIEW grâce à LINX, qui installe le moteur d’exécution LabVIEW dans un chroot (voir Le Raspberry Pi se propulse vers LabVIEW 2019).
- L’interface CAN est vue par Linux comme une interface réseau SocketCAN,
can0: carte d’extension à contrôleur MCP2515 sur le bus SPI ou adaptateur USB reconnu par le noyau. - Un démon fait le lien entre le bus et une socket Unix qui accepte des commandes texte.
- Une petite bibliothèque C, appelée par un nœud d’appel de bibliothèque (Call Library Function Node), permet à LabVIEW d’ouvrir cette socket, d’écrire une commande et de lire la réponse.
LabVIEW ne voit donc jamais le bus directement : il échange des chaînes de caractères avec un processus qui, lui, dispose de toute l’API SocketCAN. Cette séparation simplifie énormément le code LabVIEW et permet de déboguer le bus avec les outils Linux habituels pendant que l’application tourne.
Configurer l’interface can0
Le débit doit être identique sur tous les nœuds du bus ; ici 1 Mbit/s, le maximum de CAN classique. Le paramètre restart-ms relance automatiquement le contrôleur après un bus-off, faute de quoi l’interface reste muette jusqu’au prochain redémarrage. Pour régler les registres de temps de bit d’un microcontrôleur à l’autre bout du câble, le calculateur de débit CAN donne les valeurs de prédiviseur et de segments.
# /etc/network/interfaces.d/can0 : interface montée au démarrage, 1 Mbit/s
auto can0
iface can0 can static
bitrate 1000000
# équivalent à la main (utile pour essayer un autre débit)
sudo ip link set can0 down
sudo ip link set can0 type can bitrate 1000000 restart-ms 100
sudo ip link set can0 up
# observer le bus (paquet can-utils)
candump -td can0
# envoyer une trame : NMT « start » vers tous les nœuds
cansend can0 000#0100
La bibliothèque partagée pour LabVIEW
LabVIEW sait parler TCP et UDP, mais pas aux sockets Unix. Quatre fonctions C suffisent : Open, Close, Write et Read. Le point important est la compilation : le runtime LabVIEW tournant dans un chroot, la bibliothèque doit être compilée dans ce chroot (sudo schroot --run-session -c lv, puis installation des outils de compilation avec opkg) et copiée dans son /usr/lib. Dans le nœud d’appel, le nom seul de la bibliothèque suffit alors, et l’option « exécuter dans n’importe quel thread » évite de bloquer le thread d’interface pendant une lecture.
/* unix_socket.c : accès à une socket Unix (AF_UNIX) depuis LabVIEW,
* via un nœud d'appel de bibliothèque (Call Library Function Node).
* Compilation dans le chroot du runtime LabVIEW :
* gcc -shared -fPIC -o libunixsocket.so unix_socket.c
* puis copie dans /usr/lib du chroot (/srv/chroot/labview/usr/lib vu de l'extérieur)
*/
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
/* Ouvre une connexion sur la socket ; renvoie le descripteur ou une valeur négative */
int Open(const char *socketPath)
{
struct sockaddr_un addr;
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1)
return -1;
memset(&addr, 0, sizeof addr);
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, socketPath, sizeof addr.sun_path - 1);
if (connect(fd, (struct sockaddr *)&addr, sizeof addr) == -1) {
close(fd);
return -2;
}
return fd;
}
int Close(int fd)
{
return close(fd);
}
/* Écrit une commande texte ; 0 si tout est parti */
int Write(int fd, const char *command, size_t length)
{
return write(fd, command, length) == (ssize_t)length ? 0 : -1;
}
/* Lit la réponse (appel bloquant) ; renvoie le nombre d'octets reçus ou -1 */
int Read(int fd, char *replyBuf, int bufSize)
{
ssize_t n = read(fd, replyBuf, (size_t)bufSize);
return n < 0 ? -1 : (int)n;
}
/* Commande puis réponse en un seul appel */
int SendCommand(int fd, const char *command, size_t length, char *replyBuf, int bufSize)
{
if (Write(fd, command, length) != 0)
return -1;
return Read(fd, replyBuf, bufSize);
}
CANopen en quelques notions
CANopen ajoute au bus CAN un adressage et des services. Chaque nœud porte un numéro de 1 à 127 ; l’identifiant d’une trame (le COB-ID) combine un code de fonction sur 4 bits et ce numéro : COB-ID = code × 0x80 + nœud. Le tableau ci-dessous reprend la répartition par défaut des identifiants.
| Service | COB-ID | Rôle |
|---|---|---|
| NMT | 0x000 | Commandes de gestion du réseau envoyées par le maître (démarrer, arrêter, réinitialiser) |
| SYNC | 0x080 | Top de synchronisation qui déclenche les PDO synchrones |
| EMCY | 0x080 + nœud | Message d’urgence émis par un nœud en défaut |
| TPDO1 à 4 | 0x180, 0x280, 0x380, 0x480 + nœud | Données de process émises par le nœud |
| RPDO1 à 4 | 0x200, 0x300, 0x400, 0x500 + nœud | Données de process reçues par le nœud |
| SDO | 0x580 + nœud (réponse), 0x600 + nœud (requête) | Lecture et écriture du dictionnaire d’objets |
| Heartbeat | 0x700 + nœud | État NMT du nœud, émis périodiquement |
- Le dictionnaire d’objets rassemble tous les paramètres d’un nœud, repérés par un index sur 16 bits et un sous-index sur 8 bits : 0x1017 pour la période du heartbeat, 0x6040 pour le mot de contrôle d’un variateur, 0x6064 pour sa position…
- Les SDO lisent ou écrivent une entrée du dictionnaire, avec accusé de réception. Lents mais sûrs, ils servent au paramétrage.
- Les PDO transportent jusqu’à 8 octets de données sans protocole, sur un identifiant dédié. Ils ne circulent que lorsque le nœud est en état Operational. Attention au point de vue : un RPDO est reçu par le nœud, donc émis par le maître.
- NMT fait passer les nœuds de Pre-operational (après le démarrage) à Operational ou Stopped, et le heartbeat permet au maître de vérifier qu’ils sont toujours là.
Première approche : la pile CANopenNode
Le projet libre CANopenNode fournit, avec canopend, un nœud CANopen complet qui s’exécute sous Linux et peut jouer le rôle de maître. Lancé avec l’option -c, il ouvre une socket Unix de commandes : on lui envoie des lignes de texte, il se charge des trames, des délais et des transferts segmentés.
# LaunchCanOpen.sh : démarre la pile CANopenNode (maître) et sa passerelle de commandes, # puis affiche le trafic du bus dans le terminal CANopenSocket/canopend/app/canopend can0 -c $HOME/canopen.socket & candump can0 # Depuis un autre terminal, la même socket accepte des commandes texte : # ./canopencomm [1] 4 read 0x1017 0 u16 -> lecture SDO du heartbeat du nœud 4 # ./canopencomm [2] 4 write 0x1017 0 u16 1000 -> écriture SDO # ./canopencomm [3] 4 start -> NMT : passage en Operational
Côté LabVIEW, une classe encapsule la référence de la socket et propose une méthode par commande : elle formate la ligne ([séquence] nœud read index sous-index type), l’envoie, lit la réponse et la convertit. Une réponse commence par le même numéro de séquence suivi de la valeur ou de OK, ou d’un message ERROR: contenant le code d’abandon SDO. L’approche est rapide à mettre en place et très robuste ; elle convient bien au paramétrage et à la supervision. Elle devient moins naturelle dès qu’il faut produire des PDO à cadence fixe ou réagir à chaque trame reçue.
Seconde approche : construire les trames dans LabVIEW
La seconde approche ne délègue au démon que l’accès au bus, avec le protocole texte de socketcand. Une trame s’envoie avec send, une émission périodique se programme avec add et se met à jour avec update, et les trames reçues arrivent sous la forme < frame … >. Tout le reste, c’est-à-dire le CANopen, est écrit en LabVIEW, dans une bibliothèque de classes : NMT, SDO, SYNC, RPDO, TPDO et une classe DS402 pour le variateur.
# Protocole texte de socketcand (mode « BCM ») : une commande entre < > < open can0 > # rattache la connexion à l'interface < add 0 100000 201 2 0F 00 > # émission cyclique : 0 s + 100 000 µs, ID 0x201, 2 octets < update 0 100000 201 2 06 00 > # change le contenu de la trame cyclique < delete 201 > # arrête l'émission cyclique < send 601 8 40 41 60 00 00 00 00 00 > # trame unique : lecture SDO de 0x6041 sur le nœud 1 < subscribe 0 0 181 > # reçoit les trames 0x181 (TPDO1 du nœud 1) # réponse reçue : < frame 181 1697551234.123456 37 02 >
L’émission cyclique est le point fort de cette méthode : c’est le noyau Linux (le gestionnaire de diffusion, ou BCM) qui envoie la trame à la période demandée, avec une régularité qu’une boucle LabVIEW sur Raspberry Pi ne garantit pas. Le VI ci-dessous convertit la période en secondes et microsecondes, met en forme l’identifiant et les octets en hexadécimal, puis écrit la commande sur la socket.

< add … > est écrite sur la socket.Lire un objet par SDO
Une lecture SDO dite expédiée tient en deux trames. La requête, envoyée sur 0x600 + nœud, contient l’octet de commande 0x40, l’index en petit-boutiste (poids faible d’abord), le sous-index et quatre octets à zéro. Le nœud répond sur 0x580 + nœud avec 0x4F, 0x4B, 0x47 ou 0x43 selon que la donnée fait 1, 2, 3 ou 4 octets, suivis de la donnée, elle aussi poids faible en premier. Une réponse 0x80 signale un abandon, avec un code d’erreur sur 32 bits.
Dans le VI, la trame est assemblée à partir de l’index et du sous-index, envoyée, puis la réponse est décodée : les bits 2 et 3 de l’octet de commande donnent le nombre d’octets inutilisés, et une boucle recompose la valeur en décalant chaque octet de 8 bits.

Exemple, pour lire la période de heartbeat (0x1017) du nœud 4 : la requête est 604#4017100000000000 et la réponse 584#4B171000E8030000, soit 0x03E8 = 1000 ms. L’onglet « Construire un SDO » du décodeur CANopen produit ces trames pour n’importe quel objet.
Piloter un variateur DS402
Le profil DS402 normalise le comportement des variateurs : quelle que soit la marque, le mot de contrôle (0x6040) fait évoluer une machine d’états et le mot d’état (0x6041) indique où l’on se trouve. Pour mettre un moteur sous couple, la séquence est toujours la même : Shutdown (0x0006) vers Ready to switch on, Switch on (0x0007) vers Switched on, puis Enable operation (0x000F) vers Operation enabled. Après un défaut, un front montant sur le bit 7 (0x0080) acquitte l’erreur.
L’état se lit en comparant certains bits du mot d’état à un tableau de référence : pour chaque état, un masque garde les bits significatifs et une valeur indique ce qu’ils doivent valoir. Le VI applique les huit masques d’un coup et cherche la première correspondance.

| État | Masque | Valeur | Exemple de mot d’état |
|---|---|---|---|
| Not ready to switch on | 0x4F | 0x00 | 0x0000 |
| Switch on disabled | 0x4F | 0x40 | 0x0250 |
| Ready to switch on | 0x6F | 0x21 | 0x0231 |
| Switched on | 0x6F | 0x23 | 0x0233 |
| Operation enabled | 0x6F | 0x27 | 0x0237 |
| Quick stop active | 0x6F | 0x07 | 0x0217 |
| Fault reaction active | 0x4F | 0x0F | 0x021F |
| Fault | 0x4F | 0x08 | 0x0218 |
L’onglet DS402 du décodeur CANopen affiche la machine d’états complète : un clic sur un état ou une transition charge le mot d’état et le mot de contrôle correspondants.
Le programme de test
Le VI de test réunit toutes les briques. À gauche, il crée les objets : variateur DS402, NMT, SYNC, un RPDO pour recevoir le mot d’état et un TPDO pour envoyer le mot de contrôle. Le TPDO est démarré en émission cyclique à 100 ms, si bien que la boucle principale n’a plus qu’à mettre à jour son contenu. Dans la boucle, les SDO lisent le mode de fonctionnement, la position et la vitesse, écrivent le mode demandé et la consigne de position ; le RPDO reçu est décodé pour afficher l’état du variateur. À droite, tout est arrêté et détruit proprement, et les erreurs sont fusionnées.

Les pièges classiques
- Aucune trame ne passe : vérifier les deux résistances de terminaison de 120 Ω (60 Ω mesurés entre CAN_H et CAN_L, alimentation coupée) et le débit de chaque nœud. Un seul nœud au mauvais débit suffit à générer des erreurs pour tout le monde.
- Les PDO restent muets : le nœud est probablement encore en Pre-operational. Envoyer la commande NMT Start (
000#0100pour tous les nœuds) et vérifier le heartbeat, qui doit passer de 0x7F à 0x05. - Le variateur refuse de passer en Operation enabled : la séquence doit être respectée pas à pas, et un défaut actif bloque tout. Lire le mot d’état avant chaque commande plutôt que d’envoyer 0x000F d’emblée.
- Des valeurs incohérentes : l’ordre des octets. CANopen transmet tout en petit-boutiste, alors que LabVIEW aplatit les données en gros-boutiste par défaut.
- Un SDO sans réponse : prévoir un délai d’attente et un abandon propre, sinon une lecture bloquante fige la boucle.
Pour aller plus loin
- Décodeur de trames CANopen : NMT, SDO, PDO, heartbeat et DS402
- Débit CAN : calcul des registres de temps de bit
- LINX 3.0 : programmer un Raspberry Pi avec LabVIEW
- CANopenNode et socketcand sur GitHub
- La documentation CiA 301 (couche application) et CiA 402 (profil des variateurs), publiées par l’association CAN in Automation

Laisser un commentaire
Vous devez vous connecter pour publier un commentaire.