Guardians of the CAN bus
come usare il CAN bus in C e C++ – pt.3
Mantis: È sbagliato manipolare i sentimenti degli amici. Drax: E quella volta che mi hai fatto innamorare del mio calzino? Mantis: Beh, è stato divertente.
Ed eccoci alla terza parte di Guardians of the CAN bus, un ciclo di articoli ispirati al bellissimo Guardiani della Galassia Vol.3 (ripassatevi la prima e la seconda parte, please…). Nel dialogo qui sopra c’è tutta la vena comica del film, che viaggia sempre in parallelo alla vena seria e drammatica. Un po’ come il CAN bus, che è così ben studiato ed efficace che rende divertente scrivere Software che lo usa, salvo quando devi risolvere un problema molto complicato e allora entri nella fase seria e drammatica… ma siamo qui per questo! In questa terza parte scopriremo gli ultimi segreti che ci mancano per diventare dei veri e propri Guardians of The CAN bus!
Nelle parti una e due abbiamo affrontato un bel po’ di argomenti: la struttura del CAN bus e dei frame, il protocollo SocketCAN, esempi elementari di send e receive, il can_frame di SocketCAN, le can-utils, il setup di un CAN device sotto Linux, il sistema di filtraggio… E cosa ci manca allora? Direi che per completare l’opera bisogna descrivere: il trattamento degli errori, il CAN extended e, dulcis in fundo, il CAN FD. Uh, è molta roba e il tempo stringe: cominciamo!
E cominceremo dal trattamento degli errori: nel caso che qualcosa vada male nel protocollo è prevista la generazione di messaggi di errore che, opportunamente decodificati, conducono alla identificazione del problema: ossia, la mia funzione di ricezione riceverà de messaggi auto-generati dal controller che sta nel dispositivo fisico con cui comunico, e questi messaggi mi diranno cosa c’è che non va. È un sistema di auto-diagnostica molto efficace, quindi. Vediamo allora una versione di canrecv.c che realizza questo punto: vai col codice di canrecverr.c!
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <net/if.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <linux/can.h> #include <linux/can/raw.h> #include <linux/can/error.h> // canrecverr - funzione main() int main(int argc, char *argv[]) { // creo il socket int sockfd; if ((sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) == -1) { // errore socket() printf("%s: errore socket() (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // set degli attributi di i/o struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", "vcan0"); if (ioctl(sockfd, SIOCGIFINDEX, &ifr) == -1) { // errore ioctl() printf("%s: errore socket() (%s)\n", argv[0], strerror(errno)); close(sockfd); return EXIT_FAILURE; } // set attributi dell'indirizzo struct sockaddr_can addr; memset(&addr, 0, sizeof(addr)); addr.can_family = AF_CAN; addr.can_ifindex = ifr.ifr_ifindex; // assegna l'indirizzo al socket if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { // errore bind() printf("%s: errore bind() (%s)\n", argv[0], strerror(errno)); close(sockfd); return EXIT_FAILURE; } // set errori da testare (in questo caso 3 ma se ne possono aggiungere a piacere) can_err_mask_t err_mask = (CAN_ERR_TX_TIMEOUT | CAN_ERR_LOSTARB | CAN_ERR_PROT); setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask)); // ricevo il frame struct can_frame frame; if (read(sockfd, &frame, sizeof(struct can_frame)) == -1) { // errore read() printf("%s: errore read() (%s)\n", argv[0], strerror(errno)); close(sockfd); return EXIT_FAILURE; } // analizzo se è un error frame if ((frame.can_id & CAN_ERR_FLAG) != 0) { printf("%s: messaggio di errore\n", argv[0]); close(sockfd); return EXIT_FAILURE; } // mostro il frame ricevuto printf("id:0x%x dlc:%d data: ", frame.can_id, frame.can_dlc); for (int i = 0; i < frame.can_dlc; i++) printf("%c ", frame.data[i]); printf("\n"); close(sockfd); return EXIT_SUCCESS; }
Visto? Usando la onnipresente system-call setsockopt(2) si attiva la gestione degli errori usando il flag CAN_RAW_ERR_FILTER e passandole la maschera err_mask degli errori che vogliamo vedere: io ne ho messi tre, ma se ne possono mettere altri a piacere, o addirittura aggiungerli tutti assegnando a err_mask la maschera speciale CAN_ERR_MASK. Per attivare questa gestione l’unico altro cambio da effettuare nel codice è il test della presenza del flag CAN_ERR_FLAG nel can_id del messaggio ricevuto: se il flag è preesente non è un messaggio normale ma è un messaggio di errore da analizzare per estrarre l’informazione contenuta. L’analisi dell’errore si fa cercando nel can_id quali sono i bit attivi, seguendo questo esempio:
// errore: "TX timeout (by netdevice driver)" if (frame->can_id & CAN_ERR_TX_TIMEOUT) printf("TX timeout (by netdevice driver)"); // errore: "arbitration lost in bit: (data[0])" if (frame->can_id & CAN_ERR_LOSTARB) printf("arbitration lost in bit: 0x%02x", frame->data[0]); // errore: "protocol violations: type (data[2]) location (data[3])" if (frame->can_id & CAN_ERR_PROT) printf("protocol violations: type 0x%02x location 0x%02x", frame->data[2], frame->data[3]);
L’esempio evidentemente non è completo, visto che gli errori trattabili sono veramente molti, ma per completarlo basta aiutarsi con le molte #define contenute in linux/can/error.h, è una attività relativemente semplice. Notare che la gestione degli errori è compatibile con quella dei filtri: nessuno impedisce di usarle (con oculatezza) allo stesso tempo.
Chiuso il capitolo errori possiamo passare al CAN extended, identificato dal protocollo CAN2.0(B) (mentre il CAN standard è CAN2.0(A)). Come si può intuire dal nome è una versione (quasi identica) del bus che usa un can_id esteso, e il risultato lo possiamo vedere nella seguente figura, dove (a) el il tipo normale e (b) è l’extended:
Con l’extended, praticamente, possiamo trasferire gli stessi dati, ma abbiamo una capacità di indirizzamento superiore. Dal punto di vista che ci interessa, quello del codice, questo cambio implica poche differenze visto che il “lavoro sporco” lo effettua il protocollo SocketCAN che, come gia visto nella primo articolo del ciclo, usa un can_id di 32 bit, quindi è compatibile con il CAN extended. La differenza fondamentale è nell’uso delle maschere definite nell’header can.h, di cui vi passo un estratto (utile anche per il punto precedente, quello degli errori):
/* special address description flags for the CAN_ID */ #define CAN_EFF_FLAG 0x80000000U /* EFF/SFF is set in the MSB */ #define CAN_RTR_FLAG 0x40000000U /* remote transmission request */ #define CAN_ERR_FLAG 0x20000000U /* error message frame */ /* valid bits in CAN ID for frame formats */ #define CAN_SFF_MASK 0x000007FFU /* standard frame format (SFF) */ #define CAN_EFF_MASK 0x1FFFFFFFU /* extended frame format (EFF) */ #define CAN_ERR_MASK 0x1FFFFFFFU /* omit EFF, RTR, ERR flags */
quindi, ad esempio, nel codice per la gestione degli errori visto sopra (canrecverr.c) si dovrebbe cambiare una piccola parte del codice per usare CAN extended (vi mostro solo il frammento modificato):
// set di 2 filtri (ma possono essere di più) e attivazione con setsockopt(2) struct can_filter rfilter[2]; rfilter[0].can_id = 0x100; rfilter[0].can_id |= CAN_EFF_FLAG; rfilter[0].can_mask = (CAN_EFF_FLAG | CAN_RTR_FLAG | CAN_EFF_MASK); rfilter[1].can_id = 0x200; rfilter[1].can_id |= CAN_EFF_FLAG; rfilter[1].can_mask = (CAN_EFF_FLAG | CAN_RTR_FLAG | CAN_EFF_MASK); setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
E, analogamente, quando inviamo o riceviamo un messaggio in modo CAN extended dobbiamo fare questo:
// preparo la spedizione di un messaggio ... send_frame.can_id = 0x100; send_frame.can_id |= CAN_EFF_FLAG; // per usare CAN extended ... // esamino un messaggio ricevuto ... printf("recvd: 0x%03X\n", frame->can_id & CAN_EFF_MASK); ...
Tutto qua!
E adesso dovremmo concludere in bellezza con il CAN FD. Dovremmo, ma l’atmosfera pre-vacanziera mi sta travolgendo, e poi non voglio buttare troppa carne al fuoco a Luglio inoltrato (scrivo questo pensiero in tempo reale: mentre scrivevo quello che avete appena letto sopra non avevo ancora questa intenzione. Giuro!)… quasi quasi lo rimando al prossimo articolo. Si, lo rimando… termineremo con una quarta parte in Agosto, tutta sul CAN FD, sperando che qualcuno la legga in spiaggia, ah ah ah.
Ciao, e al prossimo post!