Volver al inicio

CLI vía CAN: Depuración de sistemas embebidos con ISO-TP y CANshell

Inmersión profunda en la implementación de CLI para sistemas embebidos vía bus CAN usando el protocolo ISO-TP y la utilidad CANshell. Solución efectiva para la depuración sin UART.

CLI vía CAN: Depuración de sistemas embebidos con ISO-TP y CANshell
Advertisement 728x90

Depuración de Sistemas Embebidos: CLI sobre CAN con ISO-TP y CANshell

El desarrollo de sistemas embebidos a menudo presenta desafíos debido a la limitación de interfaces de depuración. Cuando la UART tradicional no está disponible, pero sí existen buses CAN, se vuelven esenciales métodos alternativos para la interacción remota. Este artículo describe un enfoque para implementar una Interfaz de Línea de Comandos (CLI) sobre un bus CAN utilizando el protocolo ISO-TP y una utilidad especializada llamada CANshell, lo que proporciona un entorno de depuración completo para microcontroladores.

El Desafío de la Depuración Sin UART

La depuración clásica de sistemas embebidos se basa tradicionalmente en interfaces seriales como la UART. Sin embargo, en proyectos reales, especialmente al desarrollar placas electrónicas especializadas, la UART podría estar ausente o reservada para otros fines. En tales escenarios, con múltiples puertos CAN disponibles, surge la tarea de utilizar el bus CAN para transmitir información de depuración y comandos de control.

Las principales complejidades involucradas son:

Google AdInline article slot
  • Tamaño Limitado del Paquete CAN: Un paquete CAN estándar solo contiene 8 bytes de datos. Esto es gravemente insuficiente para transmitir comandos CLI largos, que pueden alcanzar 150 bytes o más, así como para recibir respuestas y registros voluminosos. Resolver este problema requiere un protocolo capaz de fragmentar y reensamblar datos de múltiples paquetes CAN.
  • Separación de Flujos de Datos: La depuración remota a menudo requiere una interacción simultánea con la propia CLI del depurador y la CLI del dispositivo remoto (ECU). Es crucial separar y mostrar eficientemente estos flujos en una única terminal.
  • Redirección de Registros (Logs): El firmware embebido típicamente envía los registros a UART o SWO. Para usar CLI sobre CAN, se necesita un mecanismo para redirigir estos registros a la interfaz CAN elegida a través de ISO-TP.

Para abordar estos desafíos, proponemos utilizar el protocolo ISO-TP (ISO 15765-2), que permite transmitir mensajes de longitud arbitraria sobre un bus CAN, y desarrollar una utilidad de consola especializada, CANshell, para el PC.

Arquitectura de la Solución: ISO-TP y CANshell

La solución propuesta se basa en la combinación del protocolo ISO-TP y la utilidad CANshell. ISO-TP asegura la transmisión fiable de grandes volúmenes de datos sobre CAN, mientras que CANshell actúa como un puente entre un cliente TCP (por ejemplo, PuTTY o TeraTerm) y el bus CAN.

Principio de Funcionamiento:

Google AdInline article slot
  • En el Lado del PC: La utilidad CANshell inicia un servidor TCP y abre un socket TCP. Este socket está conectado por un lado al protocolo ISO-TP y por el otro al cliente TCP. Los parámetros de conexión (direcciones ISO-TP, puerto COM, velocidad CAN) se pasan a la utilidad mediante argumentos de línea de comandos, lo que permite la automatización a través de scripts.
  • En el Lado del Microcontrolador (ECU): Se implementa una pila ISO-TP que recibe datos del bus CAN y los pasa al analizador de comandos CLI. Para la salida de registros y respuestas, se utiliza un mecanismo de redirección, permitiendo que estos datos se envíen de vuelta a través de ISO-TP.
  • Interacción: Cuando el cliente TCP envía un comando, CANshell lo intercepta, lo encapsula en paquetes ISO-TP y lo envía a través de un convertidor USB-CAN al bus CAN. En el microcontrolador, la pila ISO-TP reensambla los paquetes, y el comando se alimenta al analizador CLI. Las respuestas y los registros del microcontrolador se encapsulan de manera similar en ISO-TP, se transmiten a través del bus CAN a CANshell y luego al cliente TCP.

Implementación de Componentes y Ejemplos de Código

Para la implementación práctica, se eligió el convertidor USB-CAN USB2CANFD_V1 debido a su disponibilidad y facilidad de interacción a través de un puerto COM serial utilizando el protocolo SLCAN. La placa de desarrollo JZ-F407VET6, con sus dos puertos CAN, se utilizó como plataforma de destino.

El servidor TCP en el lado del PC está implementado en C puro utilizando funciones de WinSock2. Esto permite la máxima reutilización de los componentes del protocolo ISO-TP y las colas FIFO tanto en el firmware del microcontrolador como en la aplicación de PC.

La recepción de datos en el microcontrolador a través de ISO-TP y su paso al analizador CLI se ve así:

Google AdInline article slot
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
    }
}

Esta función de callback iso_tp1_rx_done se invoca al finalizar la recepción de un mensaje ISO-TP completo. Extrae los datos y los pasa a la función cli_process_data para su procesamiento.

Para redirigir los registros del microcontrolador a ISO-TP, se utiliza un comando especial tpw 1. Después de ejecutarlo, todas las funciones de registro (LOG_ERROR, LOG_INFO, LOG_DEBUG) comienzan a escribir datos en la cola TxFIFO asociada con la primera instancia de 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++;
        }
    }
}

Las funciones iso_tp1_puts e iso_tp1_putc colocan caracteres o cadenas en el búfer TxFifo, desde donde se enviarán a través de ISO-TP.

Control y Sincronización de la Transmisión de Datos

La tarea IsoTpTx en el microcontrolador es responsable de extraer datos de TxFifo y enviarlos a través de ISO-TP a medida que la máquina de estados del protocolo esté lista.

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;
}

Estos fragmentos de código ilustran la lógica de transmisión de datos. La función iso15765_tx_next verifica si el transceptor ISO-TP está inactivo y, si es así, extrae datos de TxFifo e inicia la transmisión.

En el lado del PC, en la utilidad CANshell, los datos recibidos a través de ISO-TP también se colocan en una cola, desde donde luego se envían al cliente 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;
}

Estas funciones son responsables de recibir datos ISO-TP en el lado del PC y transferirlos al búfer designado para el socket TCP.

La etapa final implica la transmisión de datos desde el servidor TCP al cliente TCP conectado (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;
}

Este código demuestra cómo los datos del búfer del socket se envían al cliente utilizando la función send de WinSock2.

Un aspecto importante es la sincronización de las sesiones ISO-TP. Se observaron situaciones en las que CANshell se veía abrumado por el flujo de datos, lo que provocaba la pérdida de mensajes. La solución implicó escalonar las sesiones ISO-TP en el tiempo para permitir que ambas instancias del protocolo se reiniciaran y procesaran los datos con éxito.

Uso de la Utilidad CANshell

Para iniciar la utilidad CANshell, se debe especificar una serie de argumentos posicionales:

  • Puerto COM: El número del puerto COM al que está conectado el convertidor USB-CAN (por ejemplo, COM3).
  • Velocidad de Bit CAN: La velocidad del bus CAN en kbit/s (por ejemplo, 500000 para 500 kbit/s).
  • Dirección ISO-TP del PC: La propia dirección ISO-TP de la utilidad CANshell en la red (por ejemplo, 0xC).
  • Dirección ISO-TP del Dispositivo Objetivo: La dirección ISO-TP del microcontrolador (ECU) con el que se establecerá la comunicación (por ejemplo, 0xA).
  • Puerto TCP: El número de puerto TCP que CANshell abrirá para las conexiones de clientes (por ejemplo, 50003).

Ejemplo de inicio de la utilidad:

CANshell.exe COM3 500000 0xC 0xA 50003

Después del inicio, la utilidad CANshell abrirá dos terminales: una para su propia CLI y otra para la CLI del dispositivo remoto, a la que se puede acceder a través de un cliente TCP (PuTTY/TeraTerm) utilizando el puerto TCP especificado. Esto proporciona al depurador acceso completo al sistema remoto, simulando una conexión UART directa.

Conclusiones Clave

  • Solución a la Ausencia de UART: El método propuesto permite una CLI completa para la depuración de sistemas embebidos, incluso cuando la UART tradicional está ausente en la placa, utilizando los buses CAN disponibles.
  • Aprovechamiento de ISO-TP para Grandes Transferencias de Datos: El protocolo ISO-TP es de vital importancia para transmitir comandos CLI y registros, ya que supera la limitación de 8 bytes de los paquetes CAN al fragmentar y reensamblar mensajes.
  • Utilidad CANshell como Puente: La utilidad especializada CANshell en el PC actúa como un intermediario eficiente entre los clientes TCP estándar (PuTTY, TeraTerm) y el bus CAN, asegurando una transferencia de datos transparente.
  • Comunicación Bidireccional y Registro: El sistema soporta tanto el envío de comandos a la ECU como la recepción de registros y respuestas asíncronas, con la capacidad de redirigir los registros directamente a ISO-TP.
  • Versatilidad y Escalabilidad: El enfoque se basa en protocolos estándar y hardware ampliamente disponible, lo que lo hace aplicable a una amplia gama de proyectos embebidos.

— Editorial Team

Advertisement 728x90

Leer después