Débogage de systèmes embarqués : CLI sur CAN avec ISO-TP et CANshell
Le développement de systèmes embarqués est souvent semé d'embûches, notamment en raison d'interfaces de débogage limitées. Lorsque l'UART traditionnel est indisponible mais que des bus CAN sont présents, des méthodes alternatives d'interaction à distance deviennent indispensables. Cet article présente une approche pour implémenter une interface en ligne de commande (CLI) via un bus CAN en utilisant le protocole ISO-TP et un utilitaire spécialisé, CANshell, offrant ainsi un environnement de débogage complet pour les microcontrôleurs.
Le défi du débogage sans UART
Le débogage classique des systèmes embarqués repose traditionnellement sur des interfaces série comme l'UART. Cependant, dans les projets réels, en particulier lors du développement de cartes électroniques spécialisées, l'UART peut être absent ou réservé à d'autres usages. Dans de tels scénarios, avec plusieurs ports CAN disponibles, la tâche consiste à utiliser le bus CAN pour transmettre des informations de débogage et des commandes de contrôle.
Les principales complexités sont les suivantes :
- Taille limitée des paquets CAN : Un paquet CAN standard ne contient que 8 octets de données. C'est largement insuffisant pour transmettre de longues commandes CLI, qui peuvent atteindre 150 octets ou plus, ainsi que pour recevoir des réponses et des journaux volumineux. Résoudre ce problème nécessite un protocole capable de fragmenter et de réassembler les données à partir de plusieurs paquets CAN.
- Séparation des flux de données : Le débogage à distance exige souvent une interaction simultanée avec la CLI du débogueur lui-même et la CLI du dispositif distant (ECU). Il est crucial de séparer et d'afficher efficacement ces flux sur un seul terminal.
- Redirection des journaux : Le micrologiciel embarqué produit généralement des journaux vers l'UART ou le SWO. Pour utiliser la CLI via CAN, un mécanisme est nécessaire pour rediriger ces journaux vers l'interface CAN choisie via ISO-TP.
Pour relever ces défis, nous proposons d'utiliser le protocole ISO-TP (ISO 15765-2), qui permet de transmettre des messages de longueur arbitraire sur un bus CAN, et de développer un utilitaire de console spécialisé, CANshell, pour le PC.
Architecture de la solution : ISO-TP et CANshell
La solution proposée repose sur la combinaison du protocole ISO-TP et de l'utilitaire CANshell. ISO-TP assure une transmission fiable de grands volumes de données sur CAN, tandis que CANshell agit comme un pont entre un client TCP (par exemple, PuTTY ou TeraTerm) et le bus CAN.
Principe de fonctionnement :
- Côté PC : L'utilitaire CANshell démarre un serveur TCP et ouvre un socket TCP. Ce socket est connecté d'un côté au protocole ISO-TP et de l'autre au client TCP. Les paramètres de connexion (adresses ISO-TP, port COM, vitesse CAN) sont transmis à l'utilitaire via des arguments de ligne de commande, permettant l'automatisation par des scripts.
- Côté Microcontrôleur (ECU) : Une pile ISO-TP est implémentée, qui reçoit les données du bus CAN et les transmet à l'analyseur de commandes CLI. Pour la sortie des journaux et des réponses, un mécanisme de redirection est utilisé, permettant à ces données d'être renvoyées via ISO-TP.
- Interaction : Lorsqu'un client TCP envoie une commande, CANshell l'intercepte, l'encapsule dans des paquets ISO-TP et l'envoie via un convertisseur USB-CAN vers le bus CAN. Sur le microcontrôleur, la pile ISO-TP réassemble les paquets, et la commande est transmise à l'analyseur CLI. Les réponses et les journaux du microcontrôleur sont encapsulés de manière similaire en ISO-TP, transmis via le bus CAN à CANshell, puis au client TCP.
Implémentation des composants et exemples de code
Pour une implémentation pratique, le convertisseur USB-CAN USB2CANFD_V1 a été choisi en raison de sa disponibilité et de sa facilité d'interaction via un port COM série utilisant le protocole SLCAN. La carte de développement JZ-F407VET6, avec ses deux ports CAN, a été utilisée comme plateforme cible.
Le serveur TCP côté PC est implémenté en C pur en utilisant les fonctions WinSock2. Cela permet une réutilisation maximale des composants du protocole ISO-TP et des files d'attente FIFO, à la fois dans le micrologiciel du microcontrôleur et dans l'application PC.
La réception des données sur le microcontrôleur via ISO-TP et leur transmission à l'analyseur CLI se présente comme suit :
void iso_tp1_rx_done(n_indn_t* in_done){
LOG_DEBUG(ISO_TP,"ISO_TP1,%s",Iso15765_n_indn_ToStr(in_done));
if(N_OK==in_done->rslt){
#ifdef HAS_CLI
IsoTpHandle_t* Node=IsoTpGetNode(1);
if(Node) {
Node->target_id = in_done->n_ai.n_sa;
cli_process_data(Node->cli_num, in_done->msg, in_done->msg_sz);
memset(in_done->msg,0,I15765_MSG_SIZE);
}
#endif
}
}
Ce rappel iso_tp1_rx_done est invoqué à la fin de la réception d'un message ISO-TP complet. Il extrait les données et les transmet à la fonction cli_process_data pour traitement.
Pour rediriger les journaux du microcontrôleur vers ISO-TP, une commande spéciale tpw 1 est utilisée. Après son exécution, toutes les fonctions de journalisation (LOG_ERROR, LOG_INFO, LOG_DEBUG) commencent à écrire des données dans la file d'attente TxFIFO associée à la première instance ISO-TP.
void iso_tp1_puts(void* stream_ptr, const char* str, int32_t len) {
IsoTpHandle_t* Node = IsoTpGetNode(1);
if(Node) {
if(str) {
if(len) {
bool res = fifo_push_array(&Node->TxFifo, (uint8_t*)str, (uint32_t)len);
if(!res) {
Node->error_cnt++;
}
}
}
}
}
void iso_tp1_putc(void* stream_ptr, char ch) {
IsoTpHandle_t* Node = IsoTpGetNode(1);
if(Node) {
bool res = fifo_push(&Node->TxFifo, (uint8_t)ch);
if(!res) {
Node->error_cnt++;
}
}
}
Les fonctions iso_tp1_puts et iso_tp1_putc placent des caractères ou des chaînes de caractères dans le tampon TxFifo, d'où ils seront envoyés via ISO-TP.
Contrôle et synchronisation de la transmission des données
La tâche IsoTpTx sur le microcontrôleur est responsable de l'extraction des données de TxFifo et de leur envoi via ISO-TP lorsque la machine d'état du protocole est prête.
static bool iso15765_tx_next(IsoTpHandle_t* const Node) {
bool res = false;
res = iso15765_is_idle( &Node->instance);
if( res) {
uint32_t count = fifo_get_count(&Node->TxFifo);
if(0 < count) {
uint32_t txLen = 0;
n_req_t isoTpFrame={0};
isoTpFrame.fr_fmt=CBUS_FR_FRM_STD;
res = iso_tp_node_to_address_info( Node, &isoTpFrame.n_ai);
res = iso_tp_node_to_proto_ctrl_info( Node, &isoTpFrame.n_pci);
res = fifo_pull_array(&Node->TxFifo, isoTpFrame.msg, sizeof(isoTpFrame.msg), &txLen);
if(res) {
isoTpFrame.msg_sz = txLen;
n_rslt ret = iso15765_send(&Node->instance, &isoTpFrame);
res = iso15765_ret_to_res(ret);
}
}
}
return res;
}
bool iso_tp_tx_proc_one(uint8_t num) {
bool res = false;
IsoTpHandle_t* Node = IsoTpGetNode(num);
if(Node) {
LOG_PARN(ISO_TP, "IsoTp%u,ProcTx", num);
res = iso15765_tx_next(Node);
Node->spin++;
}
return res;
}
Ces extraits de code illustrent la logique de transmission des données. La fonction iso15765_tx_next vérifie si l'émetteur-récepteur ISO-TP est inactif, et si c'est le cas, elle extrait les données de TxFifo et initialise la transmission.
Côté PC, dans l'utilitaire CANshell, les données reçues via ISO-TP sont également placées dans une file d'attente, d'où elles sont ensuite envoyées au client TCP.
bool can_shell_iso_tp_rx_data(uint8_t num,
uint8_t iso_num,
const uint8_t* const data,
uint16_t msg_sz) {
bool res = false;
if(data) {
if(msg_sz) {
SocketHandle_t * Socket = SocketGetNode(1);
if(Socket) {
res = fifo_push_array(&Socket->TxFifo, data, (uint32_t) msg_sz);
log_debug_res(CAN_SHELL, res, "SocketTxFiFoPush");
}
}
}
return res;
}
static bool iso_tp_rx_done(uint8_t iso_num, n_indn_t* in_done) {
bool res = false;
if(in_done) {
LOG_INFO(ISO_TP, "ISO_TP_%u,MoveDone:%s", iso_num, Iso15765_n_indn_ToStr(in_done));
res = iso15765_ret_to_res(in_done->rslt);
if(res) {
IsoTpHandle_t *Node = IsoTpGetNode(iso_num);
if(Node) {
Node->target_id = in_done->n_ai.n_sa;
Node->rx_done = true;
#ifdef HAS_CAN_SHELL
res = can_shell_iso_tp_rx_data(1, iso_num, in_done->msg, in_done->msg_sz);
#endif
}
}else{
LOG_ERROR(ISO_TP, "MoveErr,Ret,%u=%s", in_done->rslt,
Iso15765retToStr(in_done->rslt));
}
}
return res;
}
Ces fonctions sont responsables de la réception des données ISO-TP côté PC et de leur transfert vers le tampon désigné pour le socket TCP.
La dernière étape implique la transmission des données du serveur TCP au client TCP connecté (PuTTY, TeraTerm) :
static bool socket_server_connected_tx_proc(SocketHandle_t *const Node) {
bool res = false;
uint32_t tx_len = fifo_get_count(&Node->TxFifo );
if(tx_len) {
uint8_t TxPart[150] = {0};
uint32_t tx_size = 0 ;
res= fifo_pull_array(&Node->TxFifo , TxPart, sizeof(TxPart), &tx_size);
if(res) {
int tx_done_cnt = send(Node->socket_remote, (char*) TxPart, (int) tx_size, 0 );
if(tx_done_cnt==tx_size) {
res = true;
} else {
res = false ;
int ret = 0;
ret = WSAGetLastError();
LOG_ERROR(LG_SOCKET_SERVER,
"SendFailedWithErrorCode,ErrCode:%d=%s",
ret, WSAErrorToStr(ret));
}
}
}
return res;
}
Ce code démontre comment les données du tampon de socket sont envoyées au client en utilisant la fonction send de WinSock2.
Un aspect important est la synchronisation des sessions ISO-TP. Des situations ont été observées où CANshell était submergé par le flux de données, entraînant une perte de messages. La solution a consisté à échelonner les sessions ISO-TP dans le temps pour permettre aux deux instances de protocole de se réinitialiser et de traiter les données avec succès.
Utilisation de l'utilitaire CANshell
Pour lancer l'utilitaire CANshell, une série d'arguments positionnels doit être spécifiée :
- Port COM : Le numéro du port COM auquel le convertisseur USB-CAN est connecté (par exemple,
COM3). - Débit CAN : La vitesse du bus CAN en kbit/s (par exemple,
500000pour 500 kbit/s). - Adresse ISO-TP du PC : L'adresse ISO-TP propre à l'utilitaire CANshell sur le réseau (par exemple,
0xC). - Adresse ISO-TP du dispositif cible : L'adresse ISO-TP du microcontrôleur (ECU) avec lequel la communication doit être établie (par exemple,
0xA). - Port TCP : Le numéro de port TCP que CANshell ouvrira pour les connexions client (par exemple,
50003).
Exemple de lancement de l'utilitaire :
CANshell.exe COM3 500000 0xC 0xA 50003
Après le lancement, l'utilitaire CANshell ouvrira deux terminaux : un pour sa propre CLI, et un autre pour la CLI du dispositif distant, accessible via un client TCP (PuTTY/TeraTerm) en utilisant le port TCP spécifié. Cela offre au débogueur un accès complet au système distant, simulant une connexion UART directe.
Points clés à retenir
- Pallier l'absence d'UART : La méthode proposée permet une CLI complète pour le débogage de systèmes embarqués, même lorsque l'UART traditionnel est absent de la carte, en utilisant les bus CAN disponibles.
- Exploitation d'ISO-TP pour les transferts de données volumineux : Le protocole ISO-TP est d'une importance capitale pour la transmission des commandes CLI et des journaux, car il surmonte la limitation des paquets CAN de 8 octets en fragmentant et en réassemblant les messages.
- L'utilitaire CANshell comme pont : L'utilitaire spécialisé CANshell sur PC agit comme un intermédiaire efficace entre les clients TCP standards (PuTTY, TeraTerm) et le bus CAN, assurant un transfert de données transparent.
- Communication bidirectionnelle et journalisation : Le système prend en charge à la fois l'envoi de commandes à l'ECU et la réception de journaux et de réponses asynchrones, avec la possibilité de rediriger les journaux directement vers ISO-TP.
- Polyvalence et évolutivité : L'approche est basée sur des protocoles standards et du matériel largement disponible, ce qui la rend applicable à un large éventail de projets embarqués.
— Editorial Team
Aucun commentaire pour le moment.