Powrót do strony głównej

CLI przez CAN: Debugowanie systemów wbudowanych z ISO-TP i CANshell

Głębokie zanurzenie w implementację CLI dla systemów wbudowanych przez magistralę CAN z wykorzystaniem protokołu ISO-TP i narzędzia CANshell. Efektywne rozwiązanie do debugowania bez UART.

CLI przez CAN: Debugowanie systemów wbudowanych z ISO-TP i CANshell
Advertisement 728x90

Zarządzanie i debugowanie systemów wbudowanych: CLI przez CAN z wykorzystaniem ISO-TP i narzędzia CANshell

Rozwój systemów wbudowanych często napotyka problem ograniczonych interfejsów debugowania. W sytuacjach, gdy tradycyjny UART jest niedostępny, ale istnieją magistrale CAN, pojawia się potrzeba alternatywnych metod zdalnej interakcji. Ten artykuł opisuje podejście do implementacji interfejsu wiersza poleceń (CLI) przez magistralę CAN, wykorzystując protokół ISO-TP oraz specjalistyczne narzędzie CANshell, które zapewnia pełnoprawne środowisko debugowania dla mikrokontrolerów.

Wyzwanie: debugowanie bez UART

Klasyczne debugowanie systemów wbudowanych tradycyjnie opiera się na interfejsach szeregowych, takich jak UART. Jednak w rzeczywistych projektach, zwłaszcza podczas opracowywania specjalistycznych płytek elektronicznych, UART może być albo nieobecny, albo zarezerwowany do innych celów. W takich scenariuszach, przy dostępności wielu portów CAN, pojawia się zadanie wykorzystania magistrali CAN do przesyłania informacji debugowania i komend sterujących.

Główne trudności w tym zakresie to:

Google AdInline article slot
  • Ograniczony rozmiar pakietów CAN: Standardowy pakiet CAN mieści zaledwie 8 bajtów danych. Jest to zdecydowanie niewystarczające do przesyłania długich komend CLI, które mogą osiągać 150 bajtów i więcej, a także do odbierania obszernych odpowiedzi i logów. Rozwiązanie tego problemu wymaga użycia protokołu zdolnego do fragmentacji i składania danych z wielu pakietów CAN.
  • Separacja strumieni danych: Podczas zdalnego debugowania często wymagana jest jednoczesna praca z własnym CLI narzędzia debugującego i CLI urządzenia zdalnego (ECU). Należy efektywnie rozdzielać i wyświetlać te strumienie na jednym terminalu.
  • Przekierowanie logów: Oprogramowanie wbudowane zazwyczaj wyprowadza logi do UART lub SWO. Aby używać CLI przez CAN, należy zapewnić mechanizm przekierowania tych logów do wybranego interfejsu CAN za pośrednictwem ISO-TP.

Aby rozwiązać te problemy, proponuje się użycie protokołu ISO-TP (ISO 15765-2), który umożliwia przesyłanie wiadomości o dowolnej długości przez magistralę CAN, oraz opracowanie specjalistycznego narzędzia konsolowego CANshell dla PC.

Architektura rozwiązania: ISO-TP i CANshell

Proponowane rozwiązanie bazuje na połączeniu protokołu ISO-TP i narzędzia CANshell. ISO-TP zapewnia niezawodną transmisję dużych ilości danych przez CAN, a CANshell działa jako most między klientem TCP (np. PuTTY lub TeraTerm) a magistralą CAN.

Schemat działania:

Google AdInline article slot
  • Po stronie PC: Narzędzie CANshell uruchamia serwer TCP i otwiera gniazdo TCP. To gniazdo jest z jednej strony połączone z protokołem ISO-TP, a z drugiej — z klientem TCP. Parametry połączenia (adresy ISO-TP, port COM, szybkość CAN) są przekazywane do narzędzia poprzez argumenty wiersza poleceń, co pozwala na automatyzację procesu za pomocą skryptów.
  • Po stronie mikrokontrolera (ECU): Implementowany jest stos ISO-TP, który odbiera dane z magistrali CAN i przekazuje je do parsera komend CLI. Do wyprowadzania logów i odpowiedzi używany jest mechanizm przekierowania, umożliwiający wysyłanie tych danych z powrotem przez ISO-TP.
  • Interakcja: Gdy klient TCP wysyła komendę, CANshell przechwytuje ją, hermetyzuje w pakiety ISO-TP i wysyła przez konwerter USB-CAN do magistrali CAN. Na mikrokontrolerze stos ISO-TP składa pakiety, a komenda trafia do parsera CLI. Odpowiedzi i logi z mikrokontrolera są analogicznie hermetyzowane w ISO-TP, przesyłane przez magistralę CAN do CANshell, a następnie do klienta TCP.

Implementacja komponentów i przykłady kodu

Do praktycznej implementacji wybrano konwerter USB-CAN USB2CANFD_V1 ze względu na jego dostępność i prostotę interakcji przez szeregowy port COM za pomocą protokołu SLCAN. Jako platformę docelową wykorzystano płytkę rozwojową JZ-F407VET6 z dwoma portami CAN.

Serwer TCP po stronie PC zrealizowano w czystym C z wykorzystaniem funkcji WinSock2. Pozwala to na maksymalne ponowne wykorzystanie komponentów protokołu ISO-TP i kolejek FIFO zarówno w oprogramowaniu mikrokontrolera, jak i w aplikacji na PC.

Odbiór danych na mikrokontrolerze przez ISO-TP i przekazanie ich do parsera CLI wygląda następująco:

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

Ta funkcja zwrotna iso_tp1_rx_done jest wywoływana po zakończeniu odbioru pełnej wiadomości ISO-TP. Wyodrębnia ona dane i przekazuje je do funkcji cli_process_data w celu przetworzenia.

Do przekierowania logów z mikrokontrolera do ISO-TP używana jest specjalna komenda tpw 1. Po jej wykonaniu wszystkie funkcje logowania (LOG_ERROR, LOG_INFO, LOG_DEBUG) zaczynają zapisywać dane do kolejki TxFIFO, skojarzonej z pierwszym instancją 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++;
        }
    }
}

Funkcje iso_tp1_puts i iso_tp1_putc umieszczają znaki lub ciągi znaków w buforze TxFifo, skąd zostaną wysłane przez ISO-TP.

Zarządzanie transmisją danych i synchronizacja

Zadanie IsoTpTx na mikrokontrolerze odpowiada za pobieranie danych z TxFifo i ich wysyłanie przez ISO-TP, w miarę gotowości automatu protokołu.

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

Te fragmenty kodu pokazują logikę wysyłania danych. Funkcja iso15765_tx_next sprawdza, czy transceiver ISO-TP jest wolny, a jeśli tak, pobiera dane z TxFifo i inicjuje wysyłanie.

Po stronie PC, w narzędziu CANshell, dane odebrane przez ISO-TP są również umieszczane w kolejce, skąd następnie są wysyłane do klienta 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;
}

Te funkcje odpowiadają za odbiór danych ISO-TP po stronie PC i ich przekazanie do bufora przeznaczonego dla gniazda TCP.

Ostatni etap — przekazanie danych z serwera TCP do podłączonego klienta TCP (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;
}

Ten kod demonstruje, jak dane z bufora gniazda są wysyłane do klienta za pomocą funkcji send z WinSock2.

Ważnym aspektem jest synchronizacja sesji ISO-TP. Obserwowano sytuacje, w których CANshell „dławiła się” strumieniem danych, co prowadziło do utraty wiadomości. Rozwiązaniem było rozłożenie sesji ISO-TP w czasie, aby obie instancje protokołu miały czas na ponowne załadowanie i przetworzenie danych.

Użycie narzędzia CANshell

Aby uruchomić narzędzie CANshell, należy podać szereg argumentów pozycyjnych:

  • Port COM: Numer portu COM, do którego podłączony jest konwerter USB-CAN (np. COM3).
  • Szybkość transmisji CAN (bitrate): Szybkość magistrali CAN w kbit/s (np. 500000 dla 500 kbit/s).
  • Adres ISO-TP PC: Własny adres ISO-TP narzędzia CANshell w sieci (np. 0xC).
  • Adres ISO-TP urządzenia docelowego: Adres ISO-TP mikrokontrolera (ECU), z którym należy nawiązać połączenie (np. 0xA).
  • Port TCP: Numer portu TCP, który zostanie otwarty przez CANshell dla połączenia klienta (np. 50003).

Przykład uruchomienia narzędzia:

CANshell.exe COM3 500000 0xC 0xA 50003

Po uruchomieniu narzędzie CANshell otworzy dwa terminale: jeden dla własnego CLI, drugi — dla CLI urządzenia zdalnego, do którego można podłączyć się za pomocą klienta TCP (PuTTY/TeraTerm) przez wskazany port TCP. W ten sposób debugger uzyskuje pełnoprawny dostęp do zdalnego systemu, symulując bezpośrednie połączenie UART.

Kluczowe aspekty

  • Rozwiązanie problemu braku UART: Proponowana metoda pozwala na zorganizowanie pełnoprawnego CLI do debugowania systemów wbudowanych, nawet jeśli na płytce nie ma tradycyjnego UART, wykorzystując dostępne magistrale CAN.
  • Wykorzystanie ISO-TP dla dużych danych: Protokół ISO-TP jest krytycznie ważny dla przesyłania komend CLI i logów, ponieważ pozwala ominąć ograniczenie 8 bajtów na pakiet CAN, fragmentując i składając wiadomości.
  • Narzędzie CANshell jako most: Specjalistyczne narzędzie CANshell na PC działa jako efektywny pośrednik między standardowymi klientami TCP (PuTTY, TeraTerm) a magistralą CAN, zapewniając transparentną transmisję danych.
  • Dwukierunkowa komunikacja i logowanie: System obsługuje zarówno wysyłanie komend do ECU, jak i odbieranie asynchronicznych logów i odpowiedzi, z możliwością przekierowania logów bezpośrednio do ISO-TP.
  • Uniwersalność i skalowalność: Podejście opiera się na standardowych protokołach i szeroko dostępnym sprzęcie, co czyni je możliwym do zastosowania w szerokim zakresie projektów wbudowanych.

— Editorial Team

Advertisement 728x90

Czytaj dalej