logo elektroda
logo elektroda
X
logo elektroda
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

Polonez-FSO TESTER, część 2 - Dekodowanie sygnałów z lampki

trwgQ26xxx 03 Sty 2026 21:31 1140 3

TL;DR LABEL_AI_GENERATED

  • Analiza protokołu diagnostycznego ALDL 160 baud w Polonezie-FSO oraz działania programu MULTEC MONITOR, który odczytuje dane z ECU przez lampkę CEL.
  • Dekodowanie opiera się na kablu z trzema rezystorami, zwarciu TXD-RXD, transoptorze i MAX232, a program mierzy bity na podstawie zmian stanu CTS.
  • Ramka ma 1 bajt SYNC i 20 bajtów DATA, a przy 7,8 ms na bit transmisja jednej ramki trwa około 1474 ms.
  • Zmodyfikowany program poprawnie pokazuje dane z prawdziwych Polonezów: bieg jałowy, test TPS, jazdę i odczyt błędów.
  • Program słabo współpracuje z przejściówkami USB-UART, bo krótkie impulsy 500 us giną przy około 1 ms pollingu, a 6400 8E2 bywa upraszczane do 9600 8N1.
Podsumowanie AI na podstawie dyskusji. Może zawierać błędy.
Słuchaj:
  • Przemysłowy komputer diagnostyczny z uruchomionym programem FSO-MPFI ECM Monitor 3.4

    Witam Szanownych Kolegów

    W kolejnej publikacji chciałbym rozwinąć temat firmowego protokołu diagnostycznego ALDL o prędkości 160 baud. Był on stosowany w epoce OBD1, między innymi w starszych modelach Opla (np. w Kadecie), a za sprawą "zapożyczenia" rozwiązań GM trafił również do Polonezów.

    Ponadto przyjrzymy się bliżej "Specjalnemu programowi w języku angielskim z kablem", który w środowisku miłośników Poloneza zyskał miano "praktycznie niehakowalnego".

    Warstwa elektryczna

    Schemat kabla diagnostycznego ALDL z rezystorami i złączami DB9 i ALDL.


    Podstawowy kabel spełniający minimalne wymagania diagnostyczne składa się z trzech rezystorów. Znaczenie dwóch z nich omówiłem w poprzedniej części. Trzeci rezystor łączy linię MODE (pin B w złączu ALDL) z masą, aktywując funkcję wyprowadzania sygnału za pomocą kontrolki CEL w sterowniku. To te same piny, które zwiera się spinaczem biurowym, w celu "wywoływania kodów migowych".

    Drugą charakterystyczną cechą kabla jest zwarcie linii TXD i RXD po stronie PC. Na pierwszy rzut oka wydaje się to pozbawione sensu, gdyż te linie nie powinny wymagać bezpośredniego połączenia. Jednak zaraz okaże się, dlaczego angielski program diagnostyczny odmawia współpracy, gdy tego połączenia brakuje.

    Schemat interfejsu diagnostycznego ALDL z transoptorem i układem MAX232


    Dla bardziej wymagających polecam wykonanie kabla według mojego projektu. Zanim jednak Koledzy zdążą słusznie uznać, że komplikuję coś, co zazwyczaj działa, pozwolę sobie wyjaśnić powody tego podejścia:
    * Zastosowanie transoptora umożliwia przeprowadzenie diagnostyki w większości spotykanych Pontonów, w których kontrolka Check Engine (CEL) została wymontowana. Wyjście sygnału 160 baud z ECU ma charakter otwartego kolektora (OC), więc bez odpowiedniej polaryzacji sygnał zniknie razem z żarówką.
    * Użycie układu MAX232 zapewnia prawidłowe poziomy napięć na złączu RS232 komputera niezależnie od tego, czy PC wystawia odpowiednio niskie napięcie na pinie RTS, oraz od tego, czy akumulator w Pontonie nadal zachowuje pełnię sił.

    Płytka urządzenia diagnostycznego z oznaczeniami MULTEC MONITOR i złączem DB9


    Ja mam zmontowane dwa egzemplarze: jeden demonstracyjny oraz drugi "deweloperski", w którym sygnały z punktów testowych wyprowadziłem na dodatkowe złącze.

    Protokół ALDL 160 baud

    Zrzut ekranu z analizatora logicznego pokazujący sygnały CTS i ich znaczniki czasowe


    Mimo że w protokole ALDL 160 baud czas transmisji pojedynczego bitu powinien wynosić 6,25ms, to w Polonezie jest on wyraźnie dłuższy i dochodzi do około 7,8ms. Standardowa procedura dekodowania polega na odczytaniu stanu linii mniej więcej 1500us po wykryciu ujemnego zbocza sygnału. Jeśli w tym momencie linia nadal znajduje się w stanie niskim, oznacza to logiczną "1"; jeśli w stanie wysokim, to logiczniejsze "0".

    Na ekranie z analizatora logicznego para markerów A1–A2 wskazuje transmitowane "0", natomiast para B1–B2 odpowiada "1". Widoczny fragment danych przedstawia sekwencję 10001100. Tak odebrane bity należy następnie złożyć w bajty, przy czym każdy bajt ma długość 9 bitów. Pierwszy bit ma znaczenie specjalne:
    * 1 oznacza bajt synchronizacyjny (SYNC),
    * 0 oznacza bajt danych (DATA).

    Cała ramka danych w tym standardzie składa się z 1 bajtu SYNC oraz N bajtów DATA. W przypadku Pontona N wynosi 20. Mnożąc 21 bajtów przez 9 bitów i przez 7,8ms, otrzymujemy czas transmisji jednej ramki rzędu 1474ms. Innymi słowy, prędkość tej transmisji można określić "jak krew z nosa".

    Zrzut z analizatora logicznego z sygnałami CTS, #CTS i TXD, pokazuje transmisję RS232.


    A co właściwie dzieje się na linii TXD, kiedy uruchamia się "specjalny program w języku angielskim"? Okazuje się, że pojawiają się na niej tajemnicze impulsy. Program wykorzystuje bowiem loopback RS232 do generowania impulsów synchronizujących (ang. timing pulses). Konkretne działanie wygląda następująco: po wykryciu ujemnego zbocza na linii CTS, program wysyła do siebie bajt 0x00. Po natychmiastowym odebraniu tego bajtu (co jest możliwe dzięki zwarciu TXD i RXD) program ponownie sprawdza stan linii CTS i na tej podstawie dekoduje bit.

    Nie dość, że jest to książkowy sposób dekodowania, to w dodatku bardzo sprytny. Długość impulsu synchronizującego jest stała i nie zależy od szybkości procesora w PC, dzięki czemu czas próbkowania pozostaje powtarzalny i niezależny od szybkości procesora w PC. Informacja specjalna dla osób doskonale znających się na Plusie: Program otwiera port COM z parametrami 6400 8E2, co daje impuls o długości około 1562us (cały znak trwa 1875us, pamiętajmy o dwóch bitach stopu).

    Specjalny program w języku angielskim

    Zrzut ekranu programu FSO MPFI ECM Monitor 3.4 z danymi diagnostycznymi silnika


    Czyli MULTEC MONITOR. Program występuje w kilku wersjach, różniących się językiem, przeznaczeniem oraz rokiem wydania. Znane mi warianty to:

    NazwaJęzykData wydaniaPrzeznaczenieParametry COM
    FSO-ECM monitor 2.6Angielski1991SPI5760 8E2
    FSO-ECM monitor 2.9"Polskawy"1993SPI5760 8E2
    FSO-ECM monitor 2.91Angielski1995SPI6400 8E2
    FSO-ECM monitor 3.18Angielski1995SPI6400 8E2
    FSO-MPFI monitor 3.4Angielski1996MPI6400 8E2
    FSO-ECM monitor 3.5"Polskawy"1995SPI5760 8E2

    Skupię się na wersji 3.4, przeznaczonej do silników z wtryskiem MPI. Poza tym, że program prezentuje właściwie wszystkie parametry, które są realnie użyteczne podczas diagnostyki, wciąż stosunkowo niewiele wiadomo o tym, kto i w jaki sposób go przygotował. Zawartość paczek krążących w internecie sugeruje, że ktoś w Polsce miał kiedyś dostęp do całości lub przynajmniej części kodów źródłowych. Wygląda na to, że pewna adaptacja oprogramowania GM została wykonana lokalnie, i nie mam tu na myśli jedynie tego fatalnej jakości tłumaczenia. Sterowniki Poloneza mają konkretne cechy, które wyraźnie odróżniają je od rozwiązań stosowanych w Oplu czy Daewoo. Czy było to działanie celowe, na przykład w celu uniemożliwienia użycia sprzętu diagnostycznego jednego z tych producentów w Pontonie? Tego dzisiaj nie sposób stwierdzić. Faktem pozostaje natomiast, że różnice w zachowaniu protokołu oraz w implementacji funkcji diagnostycznych wskazują, iż oprogramowanie przeznaczone dla polskich sterowników musiało zostać intencjonalnie zmodyfikowane.

    Zrzut ekranu zdefiniowanych komunikatów w kodzie programu diagnostycznego


    Jako że wszystko staje się "open source", jeśli tylko zna się assembler, podrzucam kilka do tej pory nigdzie nieopisanych ciekawostek wynikających z mojej szybkiej dekompilacji programu w środowisku Ghidra:
    * Program został przygotowany w środowisku programistycznym Microsoft C/C++ 7.0.
    * Program generuje ciekawy ruch na porcie LPT2.
    * Program się pogniewa, jeśli naruszy się jego integralność. Na początkujących (ang. entry stage) czekają dwa główne sidła, które skutecznie utrudniają nawet drobne modyfikacje pliku wykonywalnego.
    * Program akceptuje opcjonalny argument w linii poleceń. Może być to: "COM1" - co wymusi użycie portu COM1; "COM2" - analogicznie COM2; brak argumentu - domyślnie COM1.
    * Odbiór danych nie jest realizowany metodą pollingową, w tzw. super‑loopie. Zamiast tego program instaluje własną procedurę obsługi przerwania, a cały mechanizm odbioru danych działa w kontekście wektora IRQ portu COM. To dość eleganckie i nietypowe rozwiązanie jak na ówczesne programy użytkowe.

    Ten ostatni aspekt chciałbym rozwinąć, prezentując zdekompilowany wektor przerwania w czytelnym C, z moimi komentarzami i objaśnieniem, jak dokładnie wygląda proces odbioru ramek ALDL w tej implementacji.

    Porównanie kodu asemblera z jego odpowiednikiem w języku C w Ghidra IDE


    void Serial_IRQ(void)
    {
       uint8_t   *aldl_data_buffer_addr;
       uint8_t   modem_status_reg;
       uint8_t   line_status_reg;
       uint8_t   temp_data;
       bool   aldl_received_bit;
    
       // Get pointer for current data buffer
       aldl_data_buffer_addr = aldl_data_buffer;
       
       // Get modem status register
       modem_status_reg = in(0x3FE);
       
       // Check if CTS line has changed state
       if((modem_status_reg & 0x01) != 0)
       {
          // Check if CTS line is low
          if((modem_status_reg & 0x10) == 0)
          {
             // CTS is low
             
             // Echo one byte as a reference (timing mark)
             // (~1875us = 1 byte @ 6400 baud, 8E2)
             temp_data = in(0x3F8);
             out(0x3F8,temp_data);
          }
          else
          {
             // CTS is high
             
             // Get line status register
             line_status_reg = in(0x3FD);
             
             // Check if new data is available
             if((line_status_reg & 0x01) != 0)
             {
                // New data is available
                
                // So that means, CTS changed state to high later than timing mark (~1875us)
                // Thus, received ALDL bit is HIGH
                
                // Clear line status register ? - it's probably a bug
                out(0x3FD, 0x00);
                
                // Increment number of received HIGH ALDL bits
                aldl_rx_bits_counter = aldl_rx_bits_counter + 1;
                
                // Check if number of HIGH bits is less than 9
                // 9 HIGH bits in a row mean than SYNC character was received
                aldl_received_bit = aldl_rx_bits_counter < 9;
                
                // That logic is pretty tricky, but keep in mind that aldl_received_bit would be there high too,
                // when code would be receiving DATA bytes too
                
                // Check if SYNC character was received - i.e. 9 high bits in a row
                if(aldl_received_bit == false)
                {
                   // Yes, SYNC character was received
                   aldl_rx_bits_counter = 0x80;
                   
                   // Start collecting data bytes
                   aldl_byte_idx = 0;
                   
                   goto IRQ_end;
                }
             }
             else
             {
                // No new data is available
                
                // So that means, CTS changed state to high earlier than timing mark (~1875us)
                // Thus, received ALDL bit is LOW
                
                // Clear counter for SYNC character
                aldl_rx_bits_counter = 0;
                
                // Mark that received ALDL bit is LOW
                aldl_received_bit = false;
                
                // Check if SYNC character was received
                if((aldl_rx_bits_counter & 0x80) != 0)
                {
                   // Yes, SYNC character was received, and the current bit is LOW,
                   // so start collecting DATA byte
                   
                   // Set that 8 DATA bits would need to be received
                   aldl_rx_bits_counter = 8;
                   
                   goto IRQ_end;
                }
             }
             
             // Collect incoming ALDL bit
             aldl_received_byte = aldl_received_byte << 1 | aldl_received_bit;
             
             // Decrement number of ALDL bits to collect
             aldl_rx_bits_counter = aldl_rx_bits_counter - 1;
             
             // Check if all bits of one DATA byte was collected
             if(aldl_aldl_rx_bits_counter == 0)
             {
                // Yes, reset bits counter to SYNC mark
                aldl_aldl_rx_bits_counter = 0x80;
                
                // Store collected DATA byte in buffer
                aldl_data_buffer[aldl_byte_idx] = aldl_received_byte;
                
                // Increment buffer index
                aldl_byte_idx = aldl_byte_idx + 1;
                
                // Check if at least 20 bytes was received - redundant check, it's probably a bug
                if(aldl_byte_idx > 0x13)
                {
                   // Check if exactly 20 bytes was received (i.e. whole ALDL Ponton frame)
                   if(aldl_byte_idx == 0x14)
                   {
                      // Set data ready flag, when earlier data was processed in main loop
                      if(new_aldl_frame_received_flag == false)
                      {
                         new_aldl_frame_received_flag = true;
                         aldl_data_buffer_ptr = aldl_data_buffer_addr;
                      }
                      
                      // Switch data buffers
                      if(aldl_data_buffer_ptr == (uint8_t *)0x5b4)
                      {
                         aldl_data_buffer = (uint8_t *)0x598;
                      }
                      else
                      {
                         aldl_data_buffer = (uint8_t *)0x5b4;
                      }
                   }
                   else
                   {
                      // Wrong number of bytes collected, start over
                      aldl_byte_idx = 0;
                   }
                }
             }
          }
       }
       
       IRQ_end:
       // Acknowledge the interrupt with a signal to the interrupt controller (PIC)
       out(0x20,0x20);
       
       return;
    }


    Przerwanie wyzwalane jest zmianą stanu linii CTS. Po odnotowaniu zmiany program sprawdza, czy bieżący stan linii jest wysoki czy niski. Logika działania wygląda następująco: Jeśli bieżący stan jest niski (czyli wystąpiło zbocze ujemne), to wysyłany jest impuls synchronizacyjny. Jeśli bieżący stan jest wysoki (czyli wystąpiło zbocze dodatnie), program sprawdza, czy został wcześniej odebrany impuls. Tym samym:
    * Jeśli impuls nie został odebrany, oznacza to, że linia CTS zmieniła stan wcześniej niż ~1500us, czyli odebrany bit to 0.
    * Jeśli impuls został odebrany, oznacza to, że linia zmieniła stan później niż ~1500us, czyli odebrany bit to 1.

    Następnie bity składane są w bajty, a bajty w pełną ramkę danych. Proces ten w pełni pokrywa się z opisem protokołu przedstawionym wcześniej.

    Modernizacje

    Ogólnie rzecz biorąc, mimo dobrego pomysłu, sama implementacja jest niepotrzebnie zagmatfana i mocno odbiega od współcześnie przyjmowanych norm programistycznych. Dotyczy to głównie wykorzystania instrukcji goto do ominięcia dalszego przetwarzania, gdy odbierane są bity specjalne. Szczególnie zabawny jest fakt recyklingu zmiennej licznika bitów, który to raz jest licznikiem, a raz znacznikiem synchronizacji. Innym problemem jest zapis zera do rejestru LSR 16650A. Podejrzewam że potencjalną intencją było skasowanie flagi "Data available", ale problem z tym jest taki że ten rejestr jest wyłącznie do odczytu. W rzeczywistości kasowanie tej flagi odbywa się przez odczyt z RBR, czyli przez pobranie bajtu danych.

    Dociekliwi (nie tylko Ci "doskonale znający się na Plusach") mogli zauważyć, że program generuje dodatkowy impuls na zboczu dodatnim, ale tylko wtedy, gdy poprzednio odebrany bit był jedynką. Impuls ten nie ma żadnego zastosowania i jest całkowicie zbędny. Co ciekawe, w kodzie nie ma ani jednej instrukcji, która mogłaby go bezpośrednio wywołać. Wszystkie operacje na porcie COM (poza wstępną konfiguracją) wykonywane są wyłącznie w opisanej wcześniej procedurze przerwania.

    Sytuacja robi się podejrzana w momencie, gdy weźmiemy pod uwagę dokumentację układu 16650A: zapis do rejestru LSR jest oznaczony jako nieefektywny (N/A), ale nie sprecyzowano, co faktycznie dzieje się w układzie, jeśli taki zapis mimo wszystko zostanie wykonany. Jedynym sensownym wytłumaczeniem wydaje się być to, że sam zapis do LSR - mimo że formalnie niedozwolony - wywołuje niepotrzebny impuls.
    W trakcie pisania tego tekstu uświadomiłem sobie, że będąc w pełni władzy nad programem, to dlaczego nie spróbować zastąpić problematycznej instrukcji "OUT DX, temp_data" instrukcją NOP? Po wprowadzeniu tej poprawki impulsy na zboczu dodatnim całkowicie znikają, a program nadal poprawnie odbiera dane. Oznacza to, że spontaniczny impuls był w istocie skutkiem zapisu do rejestru LSR, a nie celowego działania kodu.
    Wszystkie nagrania zamieszczone poniżej pochodzą z wersji poprawionej, którą można rozpoznać po drobnej ciekawostce, dodanej przeze mnie do pliku wykonywalnego.

    Zrzut ekranu z analizatora logicznego pokazujący przebiegi CTS, #CTS i TXD


    Swoją drogą, w tym miejscu staje się całkowicie jasne, dlaczego program nie będzie chciał zadziałać z większością przejściówek USB‑UART. Pierwszy problem to minimalny czas pollingu magistrali USB w popularnych układach (np. FT232R), który wynosi około 1 ms. Oznacza to, że krótkie impulsy o długości ~500us, reprezentujące bit "0", będą po prostu znikać, zanim sterownik USB zdąży je odczytać.
    Drugim punktem zgonu są nietypowe ustawienia portu COM używane przez program, czyli 6400 8E2. Zdecydowana większość sterowników USB‑UART ignoruje tak egzotyczne parametry i "naprostowuje" konfigurację portu do najbliższego standardowego zestawu, zazwyczaj 9600 8N1. To oczywiście całkowicie popsuje timing.

    Podsumowanie

    Przemysłowy komputer diagnostyczny z uruchomionym programem FSO-MPFI ECM Monitor 3.4


    Na koniec chciałbym zademonstrować działanie "specjalnego programu" na równie specjalnym wózku diagnostycznym przygotowanym na potrzeby OG Ponton Plus Experience. Sygnał wejściowy pochodzi z prostego symulatora opartego na Arduino. Z kolei dane, które są odtwarzane, zostały zarejestrowane na dwóch prawdziwych Polonezach przy użyciu dedykowanego loggera, którego też prędzej czy później opiszę.

    Polonez #1, bieg jałowy. Brak błędów.




    Polonez #2, bieg jałowy. Uszkodzony TPS, martwa lambda. Do tego zwarcie w obwodzie wtryskiwaczy, przez zgwałcenie silnika gazem.




    Polonez #1, jazda.




    Polonez #1, test TPS.




    Bonus. Skróciłem czas transmisji pojedynczego bitu z 7800us na 2500us.




    Niektóre materiały znajdują się na moim Github.

    Także to by było na razie na tyle.

    Fajne? Ranking DIY
    O autorze
    trwgQ26xxx
    Poziom 11  
    Offline 
    trwgQ26xxx napisał 28 postów o ocenie 103, pomógł 1 razy. Mieszka w mieście Kraków. Jest z nami od 2012 roku.
  • #2 21798127
    _ACeK_
    Poziom 15  
    Posty: 203
    Pomógł: 6
    Ocena: 221
    :smile: WOW To się nazywa reverse engineering 👀
    :idea: Zamiast Max232 lepiej używać Max3232 dzięki czemu zamiast można używać 100n :wink:
    Btw jeśli to nie tajemnica możesz pokazać zdjęcie drugiej strony płytki PCB 🤔
  • #3 21798315
    trwgQ26xxx
    Poziom 11  
    Posty: 28
    Pomógł: 1
    Ocena: 103
    A proszę bardzo :) To akurat ten drugi egzemplarz z wyprowadzonymi sygnałami pod analizator stanów logicznych. W każdym razie projekt PCB, kod emulatora itd.. znajdują się na moim Github, w tym linku który podałem.

    Niebieska płytka PCB z układami SMD i złączem DB9 oraz kolorowymi przewodami
    Moduł Multec Monitor z wyprowadzonymi przewodami sygnałowymi do analizy
  • #4 21799180
    ORMO_PL
    Poziom 19  
    Posty: 774
    Pomógł: 16
    Ocena: 80
    Nie wiem, czy to kwestia ówczesnych możliwości, czy po prostu tego konkretnego ECU, ale ta transmisja jest bardzo bardzo wolna. A przecież znany wszystkim Mono Motronic 1.2 dawał się pięknie diagnozować w czasie rzeczywistym.

    Dlaczego się tego czepiam?

    Jestem amatorem-gawędziarzem. Bawiłem się w amatorskie modyfikacje MED9.1, EDC15P i pochodnych. Są takie przypadki, że korelacja np. lambdy do doładowania jest kluczowa do poprawnej diagnostyki. Tego programy diagnostyczne nie pokażą, a wychwyci wprawne oko diagnosty.
    Chyba, że wówczas diagnostyka opiewała na "Dobra, pokazuje wartość, znaczy, że czujnik jako-tako działa"

    Mały wtedy byłem, choć GSI w domu był :)

    Pozdrawiam Cię, lubię takie projekty, gdzie ktoś rozbiera coś znanego na czynniki pierwsze.
Słuchaj:
REKLAMA