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

Pierwsze kroki z programowaniem RTL8196C z routera - wgrywanie, blink, UART

p.kaczmarek2 24 Lip 2026 09:40 882 1

TL;DR LABEL_AI_GENERATED

  • Na routerze Cyfrowego Polsatu LT-6408n z RTL8196C uruchomiono własne programy MIPS bez wylutowywania Flash, zamieniając urządzenie w prostą platformę deweloperską.
  • Kod wgrywano przez bootloaderowe komendy EW/DW/J do RAM, najpierw ręcznie w hex, potem przez mips-linux-gnu-gcc, linker i objcopy do surowego binarium.
  • Pierwszy testowy program pisał znak „!” na UART pod adresem 0xB8002000, a później uruchomiono miganie LED i licznik na konsoli.
  • Flashowanie zautomatyzował skrypt Python uart_auto_flash.py, który wykrywa „Booting”, wciska ESC, wysyła paczki EW i weryfikuje je DW, a reboot realizował Tasmota.
  • Po drodze pojawiały się problemy ze znikającymi znakami, stosem, przerwaniami, GPIO i błędnym adresem startowym w linkerze.
Podsumowanie AI na podstawie dyskusji. Może zawierać błędy.
REKLAMA
Słuchaj:
  • Pierwsze kroki z programowaniem RTL8196C z routera - wgrywanie, blink, UART
    W poprzednim temacie przedstawiałem wnętrze routera Cyfrowego Polsatu LT-6408n. Określiłem, że jest zbudowany on na układzie RTL8196C, którego bootloader oferuje prostą linię komend wraz z możliwością wgranie dowolnego kodu maszynowego do pamięci RAM. Tutaj postaram się to wykorzystać - zacznę, od wgrywania prostych instrukcji napisanych ręcznie w notacji szesnastkowej, a potem uruchomię kompilator i rozwinę własny "flasher", zamieniający stary router w namiastkę Arduino.

    Poprzedni temat z serii: Analiza wnętrza routera, zgrywanie pamięci, bootloader, własny program do UART

    Kilka słów o architekturze RTL8196C
    W poprzednim temacie łatwo było zauważyć, że omawiany router opiera się na RTL8196C. RTL8196C to bardzo popularny, wysoce zintegrowany układ typu SoC (System on a Chip) ze stajni Realteka, będący sercem wielu budżetowych urządzeń sieciowych na rynku. Pod maską kryje się tu sprawdzony, 32-bitowy rdzeń procesora w architekturze MIPS (taktowany zazwyczaj zegarem około 400 MHz). Jak przystało na dedykowany układ sieciowy, procesor ten jest obudowany szeregiem sprzętowych dodatków - znajdziemy tu m.in. wbudowany 5-portowy switch Fast Ethernet (10/100 Mbps), interfejs PCIe (często używany do modułów Wi-Fi), kontrolery pamięci SDRAM i SPI Flash, a także pakiet podstawowych peryferiów, takich jak piny GPIO czy kontroler UART. To właśnie dzięki temu ostatniemu interfejsowi i specyfice tutejszego bootloadera, będziemy mogli rozpocząć naszą zabawę w programowanie bez wylutowywania pamięci i korzystania z zewnętrznego programatora.
    Pierwsze kroki z programowaniem RTL8196C z routera - wgrywanie, blink, UART
    Sprawa jest o tyle ciekawa, że mamy też dokumentację rejestrów, choć przyznam, że... na początku pisania tego tematu o tym nie wiedziałem, eksperymentowałem i bez pełnej wiedzy.
    Pierwsze kroki z programowaniem RTL8196C z routera - wgrywanie, blink, UART


    Użyte komendy
    Nie musimy na szczęście programować bezpośrednio pamięci Flash, tym razem mój sprawdzony CH341 może zostać w szufladzie. Dostępne komendy z bootloadera pozwalają wgrać program w pamięć RAM i stamtąd go wykonać. Należą do nich:
    KomendaSkładniaOpis działania
    EWEW <adres> <słowo1> [słowo2] ...Zapis 32-bitowych słów (word) bezpośrednio do pamięci pod wskazany adres.
    DWDW <adres> <ilość>Zrzut 32-bitowych słów z pamięci – przydatne do weryfikacji wgrywania.
    JJ <adres>Skok (Jump) do podanego adresu – wyzwala wykonanie wgranego kodu.

    Plan wgrywania jest prosty - wysyłamy słowa do wybranego miejsca w pamięci przez EW, potwierdzamy zapis przez DW, i wykonujemy skok J.

    Pierwszy program pisany bez kompilatora
    Zaczynamy od kompletnego zera. Program trzeba napisać w kodzie maszynowym. Wiemy, że RTL opiera się o rdzeń MIPS RLX4181. Możemy więc użyć podstawowych instrukcji tego asemblera do napisania, dla przykładu, pętli wysyłającej znak wykrzyknika '!' do portu nadawczego UART. Jego adres udało mi się określić przeszukując kody źródłowego podobnych Realteków:
    Pierwsze kroki z programowaniem RTL8196C z routera - wgrywanie, blink, UART
    Znajduje się on pod adresem 0xB8002000. Wysłanie każdego znaku przerywane będzie pustą pętlą (delay), aby nie przepełnić bufora nadawania. Można pomóc sobie dokumentacją oraz narzędziami online, przykładowo konwerterem MIPS.
    Pierwsze kroki z programowaniem RTL8196C z routera - wgrywanie, blink, UART
    Sam kod sprowadza się do kilku prostych instrukcji asemblera: załadowania docelowego adresu UART, zapisania do niego pojedynczego bajtu (wykrzyknika w kodzie ASCII), odliczenia czasu w pustej pętli (aby sprzęt zdążył "wypchnąć" znak na zewnątrz) i ostatecznie - wykonania bezwarunkowego skoku z powrotem na początek.
    W efekcie uzyskujemy poniższy program:
    AddressHexInstrukcjaOpis
    805000003C08B800lui $t0, 0xB800Załaduj górne 16 bitów adresu bazowego UART
    8050000435082000ori $t0, $t0, 0x2000$t0 = 0xB8002000 (rejestr nadawczy UART THR)
    8050000834090021ori $t1, $zero, 0x21$t1 = '!' (ASCII 0x21)
    8050000CA1090000sb $t1, 0($t0)Zapisz znak '!' do rejestru transmisji UART
    80500010340A8000ori $t2, $zero, 0x8000$t2 = 32768 (licznik opóźnienia)
    805000141540FFFFbne $t2, $zero, delayJeśli licznik ≠ 0, skocz z powrotem do pętli
    80500018214AFFFFaddi $t2, $t2, -1Zmniejsz licznik o 1 (slot opóźnienia)
    8050001C08140002j 0x80500008Skok z powrotem do początku pętli
    8050002000000000nopBrak operacji (slot opóźnienia)

    Po przerwaniu bootloadera wgrywamy ten kod (szesnastkowo, słowo po słowie) używając polecenia EW:
    
    EW 80500000 3C08B800 35082000 34090021 A1090000 340A8000 1540FFFF 214AFFFF 08140002
    EW 80500020 00000000
    

    Rezultaty można zweryfikować poleceniem DW 80500000 9.

    Uruchamiamy kod skokiem:

    Rezultatem jest natychmiastowe zalanie okna terminala nieskończonym ciągiem wykrzykników, "!!!!".
    Pierwsze kroki z programowaniem RTL8196C z routera - wgrywanie, blink, UART
    Proof of concept działa, ale kto by chciał pisać kod maszynowy ręcznie?

    Kompilator i język C
    Ręczne pisanie instrukcji MIPS i konwertowanie ich na kody szesnastkowe w hex konwerterze jest uciążliwe. Do tworzenia bardziej skomplikowanych programów zdecydowałem się użyć odpowiednio dobranego kompilatora, zgodnego z użytym procesorem - mips-linux-gnu-gcc. Pozwala to pisać programy w C. Nie udało mi się go uruchomić na Windowsie, więc posłużyłem się środowiskiem Linuxowym na "okienka" - WSL. Nie miałem żadnych nagłówków pod tego Realteka, ale są one mi zbędne. Zacząłem od najprostszego kodu, który chcę skompilować:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Taki kod trzeba dalej skompilować. Uruchamiam mips-linux-gnu-gcc z następującymi parametrami:
    - -march=mips32 - generuj kod dla architektury MIPS32 (zgodnej z rdzeniem RTL8196C)
    - -mno-abicalls -fno-pic - wyłącz kod pozycyjnie niezależny (PIC) - program działa pod stałym adresem
    - -nostdlib -nostartfiles - nie linkuj biblioteki standardowej C ani domyślnego kodu startowego
    - -ffreestanding - nie zakładaj obecności standardowego środowiska wykonawczego
    - -O2 - optymalizacja - mniejszy i wydajniejszy kod maszynowy
    - -T rtl8196c.ld - użyj własnego skryptu linkera definiującego układ pamięci (opisany niżej)
    Kompilator generuje plik ELF, z którego za pomocą 'mips-linux-gnu-objcopy -O binary' wyciągany jest surowy plik binarny - sam kod maszynowy, bez nagłówków, gotowy do załadowania wprost do pamięci RAM.

    Kluczowym elementem jest skrypt linkera rtl8196c.ld, który informuje kompilator, pod jakim adresem w pamięci RAM docelowo znajdzie się nasz program:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Skrypt kompilacji (przepraszam, za formę obrazkową, ale silnik forum nie pozwala mi wkleić tych komend...):
    Pierwsze kroki z programowaniem RTL8196C z routera - wgrywanie, blink, UART
    Drugim etapem jest eksport komend z pliku binarnego do ładnych napisów, które można przekleić do Realteka. Normalnie się tego nie robi, więc posłużyłem się własnym skryptem Python:
    Kod: Python
    Zaloguj się, aby zobaczyć kod

    Wygenerowane komendy:
    
    EW A0500000 3C05A050 24A50050 3C04B800 3C06001E 24020048 00A01825 304200FF 24630001
    EW A0500020 A0822000 80620000 1440FFFC 304200FF 34C28480 00000000 2442FFFF 1440FFFD
    EW A0500040 00000000 1000FFF3 24020048 00000000 4865792E 2E0D0A00 00000000 00000000
    
    DW A0500000 24
    J A0500000
    
    

    Rezultat:
    Pierwsze kroki z programowaniem RTL8196C z routera - wgrywanie, blink, UART
    Program działa, choć tym razem wyszło nieco gorzej - znaki się gubią, chyba moja pętla oczekująca na zwolnienie UART nie zadziałała.


    Własne narzędzie do wgrywania wsadu
    Instrukcje już generują się same, trzeba je jeszcze automatycznie wgrywać. Tryb linii komend włącza wciśnięcie ESCAPE w trakcie bootowania, przecież to może robić program. Komendy też może wysyłać program. Musimy tylko określić, kiedy startuje router - można przechwycić jego komunikat startowy. Potem można kolejno przesyłać poszczególne komendy 'EW', weryfikując je poleceniem 'DW'. Taki właśnie proces realizuje udoskonalony przeze mnie skrypt w języku Python ('uart_auto_flash.py'). Działa on w prostej pętli: nasłuchuje komunikatu "Booting", automatycznie wciska klawisz ESCAPE, aby wejść do linii komend a następnie wysyła do konsoli paczki bajtów pobrane prosto ze skompilowanego pliku binarnego ('.bin'), weryfikując poprawność operacji po każdym bloku komendą 'DW'. Pozwala to całkowicie zautomatyzować proces bez uciążliwego kopiowania i wklejania instrukcji w oknie terminala.
    Brakowało mi jeszcze automatycznego rebootowania. Docelowo reboot może robić pin DTR/RTS od odpowiedniego konwertera USB na UART, ale na razie takiego nie miałem takiego pod ręką, więc użyłem... gniazdka z oprogramowaniem Tasmota. Steruję nim przez klasyczne REST API dla komend, OpenBeken też trzyma ten standard.
    Pierwsze kroki z programowaniem RTL8196C z routera - wgrywanie, blink, UART
    W ten sposób wgranie testowanego programu na router sprowadza się już tylko do wywołania jednej komendy:
    
    python ..\uart_auto_flash.py hello_world.bin --tasmota 192.168.0.176
    



    Obsługa GPIO i niezbędne poprawki
    Kolejna rzeczą którą próbowałem uruchomić jest klasyczne miganie diodą LED. Diody od statusu portów LAN są podłączone bezpośrednio do pinów Realteka, więc jest potencjał, by je wysterować. Opisane są jako, przykładowo, LED_PORT1/GPIOB3, co sugeruje dwojaką ich rolę - albo przejmowaną przez sterownik Ethernetowy, albo tryb generycznego, programowalnego IO.
    Pierwsze kroki z programowaniem RTL8196C z routera - wgrywanie, blink, UART
    Dodatkowo po drodze natrafiłem na kilka problemów - program zawieszał się po chwili, więc próbowałem ręcznie zainicjować wskaźnik stosu. Dodatkowo wyłączyłem przerwania. GPIO również na początku nie reagowało na sterowanie, ale to dlatego, że domyślnie był włączony tam kontroler sprzętowego switcha. Musiałem go ręcznie wyłączyć w kodzie.
    Oto ostateczny, działający kod sterujący diodami:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Rezultat:
    Pierwsze kroki z programowaniem RTL8196C z routera - wgrywanie, blink, UART


    Odliczanie na UART i finalizacja
    Następne co chciałem uruchomić to wyświetlanie liczb przez UART. Na początek coś prostego, jakieś odliczanie. Zdecydowałem się na prosty sposób zamiany typu integer na napis:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Niespodziewanie jednak potem program przestał działać. Zorientowałem się, że zapomniałem w linkerze wymusić pozycji funkcji startowej, na skutek czego bootloader Realteka skakał nie tam, gdzie trzeba. Musiałem do linkera, na pierwszy miejscu, dodać wpis: z entry a potem uwzględnić ten identyfikator w kodzie C:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Reszta kodu zgodnie z oczekiwaniami - teraz główna pętla ma licznik i wyświetla go z każdą wiadomością. Nie ma funkcji sprintf, osobno wyświetlam statyczny napis, a osobno przekonwertowaną na napis liczbę.
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Rezultat:
    Pierwsze kroki z programowaniem RTL8196C z routera - wgrywanie, blink, UART

    Podsumowanie
    W ten sposób udało mi się zorganizować podstawowe środowisko pracy dla RTL8196C. Eksperymenty zacząłem od prostych instrukcji, które można pisać ręcznie lub wspomagać się odpowiednim narzędziem online, potem znalazłem pasujący kompilator i od 0 przygotowałem dla niego własny skrypt linkera i kod startowy C. Rozwiązałem problemy z niepożądanymi przerwaniami, stosem oraz multipleksowaniem pinów, a cały proces flashowania zautomatyzowałem w Pythonie. Tym samym przekształciłem stary router w wygodną platformę deweloperską, na której mogę odpalać własny kod maszynowy. Zapraszam do dyskusji - a może ktoś z Was próbował podobnych zabaw z innymi SoC?
    PS: Nie jest to ostatni temat z tej serii, a w całości mojego workflow wciąż są problemy do rozwiązania - próbowałem zrobić prostą konsolę na ten router i nieznanych przyczyn program stawał po przetworzeniu kilku znaków. Jak to rozwiążę, to pokażę bardziej złożony projekt oparty na tym Realteku.

    Fajne? Ranking DIY
    Pomogłem? Kup mi kawę.
    O autorze
    p.kaczmarek2
    Moderator Smart Home
    Offline 
    Inżynier programista z wieloletnim doświadczeniem embedded i full stack developer.
    Specjalizuje się w: embedded, Full-Stack Developer
    p.kaczmarek2 napisał 14753 postów o ocenie 12875, pomógł 659 razy. Jest z nami od 2014 roku.
  • REKLAMA
  • #2 21944554
    E8600
    Poziom 41  
    Posty: 8967
    Pomógł: 529
    Ocena: 2519
    Ciekawe modyfikacja była by przeróbka takiego routera w pełnoprawny serwer NAS.
Słuchaj:
REKLAMA