Łapiąc "pluskwy"

| Prezentacje firmowe Artykuły

Dobierając mikrokontroler do aplikacji, bierzemy pod uwagę różne czynniki - najczęściej rdzeń, pojemność pamięci, układy peryferyjne i cenę. Jest jednak jeszcze jeden aspekt, którego we współczesnych mikrokontrolerach pomijać nie należy - zintegrowane w układzie mechanizmy debugera (bug - pluskwa), decydujące o możliwościach i wygodzie testowania oprogramowania. Ponieważ w tematyce debuggerów implementowanych nie ustalono jak dotąd jednoznacznego nazewnictwa, a pojęcia są ciągle mylone, w artykule podjęto próbę "rozszyfrowania" tematu dla zrozumienia, jaki konkretnie produkt kryje się pod daną nazwą lub skrótem.

Łapiąc "pluskwy"

ICE

Rys. 1. Zintegrowane mechanizmy debuggera CoreSIght w architekturze ARM Cortex-M3

Nowe mikrokontrolery, coraz lepsze pod względem parametrów technicznych, uniemożliwiają praktycznie stosowanie najbardziej zaawansowanych narzędzi uruchomieniowych dostępnych w przeszłości - emulatorów sprzętowych ICE (In-Circuit Emulator). Pierwsze mikroprocesory miały architekturę opartą o zewnętrzne magistrale oraz pracowały z zewnętrznymi pamięciami i układami peryferyjnymi. Próbkując i zapamiętując stan magistral synchronicznie z sygnałami sterującymi, można było rejestrować cykle pobrania instrukcji, zapisu/odczytu pamięci i układów peryferyjnych.

W pamięci zapisywany był tzw. ślad - adresy i kod kolejnych instrukcji oraz adresy i kod zapisywanych/odczytywanych pamięci lub układów we/wy. Wykorzystując wejścia sterujące typu "single step" i "hold", można było wykonywać program krokowo lub wstrzymać działanie procesora na pobraniu instrukcji spod zadeklarowanego adresu, na tzw. pułapce (breakpoint). Po wstrzymaniu wykonania programu można było zmusić jednostkę centralną do wystawienia na zewnątrz stanu rejestrów wewnętrznych i zawartości pamięci.

Dzięki pełnej dostępności magistral łatwa była realizacja dowolnej liczby sprzętowych pułapek na cyklu pobrania instrukcji wraz z ewentualnymi warunkami dodatkowymi. Ideą ICE było zastąpienie procesora w układzie docelowym wtykiem sondy, która z punktu widzenia aplikacji działała jak rzeczywisty procesor, a z punktu widzenia komputera nadzorującego dawała wgląd do "wnętrza" systemu. Typowy ICE emulował, poza procesorem, także pamięci i inne bloki funkcjonalne.

Dzięki temu emulowane "serce" projektowanego układu aplikacyjnego funkcjonowało od razu, gdy docelowy "organizm" nie był jeszcze w pełni sprawny. Praktycznie od razu można było przystąpić do uruchamiania oprogramowania i urządzenia. Emulator sprzętowy był jednak urządzeniem skomplikowanym i przez to drogim. Po pojawieniu się systemów zintegrowanych (embedded) konstruowanie emulatorów stało się trudniejsze.

Układy embedded nie miały magistral zewnętrznych i aby umożliwić konstruowanie emulatorów, projektowano i produkowano specjalne wersje mikrokontrolerów, tzw. bond-out z wyprowadzonymi magistralami i sygnałami kontrolnymi. Jednak specjalnie projektowane, dedykowane tylko dla emulatorów układy "bond-out" produkowane w małych ilościach stanowiły niepotrzebne obciążenie mocy projektowo- produkcyjnych producentów półprzewodników, co znajdowało odzwierciedlenie w cenie. Powszechne stało się wykorzystywanie różnych półśrodków, praktycznie królowała jednak metoda prób i błędów.

ICD

Rys. 2. Środowisko IDE μVision4 firmy Keil

Rozsądnym rozwiązaniem dla mikrokontrolerów z zewnętrzną pamięcią programu stało się uruchamianie w układzie docelowym ICD (In-Circuit Debugging). Wykorzystując standardowy interfejs procesora (najczęściej szeregowy RS-232) i instalując prosty program monitora, można było programowo realizować mechanizmy debuggera. Pułapki i praca krokowa były realizowane poprzez modyfikacje kodu programu aplikacyjnego i wstawianie odwołań do monitora w formie "łat" (patch). Po wirtualnym zatrzymaniu programu aplikacyjnego monitor zachowywał w pamięci rejestry procesora.

Następnie, na polecenie komputera nadrzędnego, stan rejestrów oraz stan wskazanych komórek pamięci wysyłał po interfejsie szeregowym i ewentualnie je zmieniał. Debuggery ICD były początkowo rozwiązaniami czysto programowymi, pozwalającymi na uruchamianie oprogramowania w układzie docelowym, w rzeczywistym środowisku, lecz nie w pełni w czasie rzeczywistym. Ich wadą było też blokowanie na czas uruchamiania zasobów mikrokontrolera wykorzystywanych docelowo w aplikacji, np. interfejsu szeregowego. Obecnie znaleziono złoty środek między kosztami i funkcjonalnością, a mianowicie OCD.

OCD

Rys. 3. Realizacja "Execution Profilera" w środowisku μVision firmy Keil

Wybrane mechanizmy sprzętowe emulatora wbudowane w struktury standardowych, seryjnie produkowanych układów mikrokontrolerów tworzą tu tzw. blok OCD (On-Chip Debugger) pozwalający w tle wykonywanego programu aplikacyjnego realizować "debugging". Nie zapewniają one pełnego spektrum mechanizmów, jakie mogły być i były dostępne w dobrych sprzętowych emulatorach.

Emulatory sprzętowe odchodzą jednak do lamusa, a metoda uruchamiania ICD oparta już o wbudowane w strukturę rozwiązania sprzętowe OCD zastępuje technologię ICE. Typowy zintegrowany w strukturze półprzewodnikowej moduł OCD zawsze implementuje najczęściej używane podstawowe bloki funkcjonalne emulatora.

Na przykładzie ARM-ów są to:

  • RCU (Run Control Unit) - blok odpowiedzialny za uruchamianie i wstrzymywanie procesora oraz pracę krokową,
  • BPU (Breakpoint Unit) - blok pułapek sprzętowych pozwalający definiować adresy zatrzymania procesora na cyklu pobrania instrukcji,
  • MAU (Memory Access Unit) - blok dostępu do rejestrów i pamięci wewnętrznych,
  • DIU (Debug Interface Unit) - blok interfejsu debuggera pozwalający na kilku liniach szeregowo transmitować dane między wbudowanym debuggerem OCD i komputerem nadzorującym.

Z ostatniego punktu wynika, że do uruchamiania konieczny jest układ pośredniczący między komputerem nadrzędnym i blokiem OCD. Od strony komputera taki adapter używa najczęściej interfejsu USB, a w bardziej rozbudowanych rozwiązaniach alternatywą jest Ethernet. Z kolei od strony uruchamianego układu stosowane są najczęściej złącze i interfejs JTAG.

Trzeba tu zwrócić uwagę, że JTAG stał się synonimem debuggera OCD, co może prowadzić do nieporozumień. Służy on jedynie do automatycznego testowania pakietów elektronicznych w technice Boundary-Scan (norma IEEE 1149.1), a tylko wykorzystywanym w technice ICD. Z obecności linii JTAG nie można wnioskować, że układ ma wbudowany debugger OCD.

CoreSight

Rys. 4. Realizacja "Performance Analyzera" w środowisku μVision firmy Keil

Spójrzmy dalej na OCD przez pryzmat najpopularniejszych obecnie na rynku mikrokontrolerów ARM Cortex-M3. Zaimplementowano w nich rozbudowane mechanizmy debuggera określane mianem technologii CoreSight. W odróżnieniu od bloków OCD operujących przez JTAG, CoreSight daje możliwość zastawiania pułapek, podglądu stanu procesora i pamięci w locie (on the fly), tzn. w trakcie wykonywania programu.

Rozbudowuje mechanizmy debuggera poprzez implementację mechanizmów śladu. Wprowadza nowy kilkuliniowy interfejs Serial Wire złożony z dwuliniowego SWD (Serial Wire Debug) obsługującego bloki debuggera i jednoliniowego SWV (Serial Wire Viewer) obsługującego bloki śladu. Wszystko to zbliża pod względem możliwości funkcjonalnych technikę uruchamiania oprogramowania ICD do wzorcowej ICE. I tak CoreSight w stosunku do bazowej architektury OCD wprowadza (rys. 1):

  • DWT (Data Watchpoint & Trace) - blok umożliwiający śledzenie "w locie" licznika instrukcji, zdarzeń i przerwań oraz realizację pułapek na dostępie do pamięci,
  • ITM (Instrumentation Trace Macrocell) - blok umożliwiający śledzenie "w locie" zmiennych programu oraz sygnalizowanie zdarzeń i przejść w programie w technice "debug printf", tzn. poprzez wstawianie do kodu źródłowego instrukcji printf(,),
  • ETM (Embedded Trace Macrocell) - opcjonalny blok umożliwiający śledzenie "w locie" wykonywanych instrukcji na potrzeby analizy przetwarzania.

Te dodatkowe bloki są obsługiwane przez tzw. TPIU (Trace Port Interface Unit), przy czym sygnały Debug Interface i Trace Port Interface mogą być wyprowadzone na jedno z trzech złączy: JTAG, Cortex Debug lub Cortex Debug+ETM.

IDE

Rys. 5. Realizacja "Logic Analyzera" w środowisku μVision firmy Keil

IDE (Integrated Development Enviroment) jest zintegrowanym środowiskiem programowym dla komputera nadrzędnego, obsługującym OCD za pośrednictwem adaptera USB/Ethernet-JTAG/SW. Wygoda obsługi IDE i sposób wyświetlania danych decyduje o użyteczności całego rozwiązania debuggera. Istotne jest, aby system okien był maksymalnie elastyczny i ze względu na dużą ilość analizowanych danych umożliwiał pracę na kilku monitorach. IDE zapewnia interpretację danych odbieranych z OCD. Typowo:

  • integruje wizualizację z edytorem i kompilatorem języka C,
  • zapewnia podgląd i modyfikację programu, rejestrów, pamięci, układów I/O itd.,
  • umożliwia debuging na poziomie asemblera i języka C,
  • zarządza projektami i procesem uruchamiania poprzez obsługę poleceń operatora i komunikatów
  • oraz czasami,
  • integruje symulator programowy mikrokontrolera.

IDE we współpracy z rozbudowanym blokiem OCD (np. CoreSight z modułem ETM) może realizować zaawansowane mechanizmy analizy śladu (Trace), pokrycia kodu (Code Coverage), przetwarzania (Performance Analysis), stanu sygnałów (Logic Analyzer) i profilowania oprogramowania (Program Profiling).

Zaawansowane mechanizmy debuggerów

Rys. 6. Adapter Ulink Pro firmy Keil

W aplikacjach szczególnie wrażliwych na błąd, np. w lotnictwie czy medycynie, jakość kodu jest kluczowa. Nie wystarcza tam słowo programisty, że przetestował wszystko. Obowiązujące normy wymuszają udokumentowanie tego faktu. Coraz większe zastosowanie mają więc wymienione wcześniej zaawansowane mechanizmy debuggerów. Przyjrzyjmy się im bliżej. Pokrycie kodu jest techniką systematycznego testowania oprogramowania polegającą na ocenie, które obszary kodu zostały wykonane i sprawdzone w trakcie uruchamiania. Kryteriami oceny pokrycia kodu testami mogą być:

  • pokrycie funkcji (czy każda funkcja i każdy podprogram zostały wywołane),
  • pokrycie instrukcji (czy każda instrukcja została wykonana),
  • pokrycie warunków (czy każdy warunek logiczny został sprowadzony do wartości prawda i fałsz),
  • pokrycie rozgałęzień (czy każde rozgałęzienie zostało sprawdzone dla warunków prawda i fałsz).

W praktyce program aplikacyjny jest wykonywany pod nadzorem środowiska IDE wraz z rejestracją śladu, a każda wykonana instrukcja jest mapowana zwrotnie w kodzie źródłowym. Pozwala to na identyfikację obszarów niewykonywanych i nietestowanych. Na tej podstawie można modyfikować podejście i docelowo opracować kompletny zbiór testów regresywnych. Program Profiler jest narzędziem analizy przetwarzania, które w podstawowej wersji rejestruje sumaryczny czas wykonania poszczególnych rozkazów.

Pozwala identyfikować krytyczne czasowo obszary oprogramowania i zoptymalizować kod poprzez reedycję kodu źródłowego, tym samym skracając czas potrzebny na wykonanie podprogramów. Oczywiste jest, że implementacja mechanizmów Profilera i Code Coverage jest możliwa bez problemu w symulatorze programowym mikrokontrolera, o ile odtwarza on rzeczywiste relacje czasowe wykonywanych instrukcji. W technologii ICD wymaga już zwrotnej informacji z systemu docelowego do komputera nadzorującego.

Aby zaawansowana analiza przetwarzania miała sens, musi być realizowana w czasie rzeczywistym. Tak więc w ARM-ach w debuggerach OCD dopiero technologia CoreSight, a w szczególności wbudowany blok ETM otworzyły możliwości analizy przetwarzania. Analizator przetwarzania pozwala na rejestrowanie czasu wykonywania zadeklarowanych procedur lub fragmentów programu i wyświetlanie tej informacji w postaci bezwzględnego sumarycznego czasu lub jego procentowego udziału w globalnym czasie przetwarzania, wizualizowanego dodatkowo wykresem paskowym.

Taka informacja pozwala łatwo zidentyfikować krytyczne czasowo fragmenty programu, które należy poddać optymalizacji. Analizator stanów logicznych pozwala na rejestrowanie "w locie" zmiennych programu i sygnałów oraz na ich wizualizację graficzną. Pozwala analizować zależności czasowe między poszczególnymi sygnałami i identyfikować instrukcje programu aplikacyjnego zmieniające stan tych sygnałów w miejscach wskazanych przez operatora kursorem.

ULink

Rys. 7. Analizator iC5000 firmy iSystem

ULink-2 i ULink-Pro firmy Keil są uniwersalnymi adapterami sprzęgającymi uruchamiany układ z komputerem nadrzędnym. Z jednej strony obsługują interfejs JTAG i SW, z drugiej USB. Droższy ULink-Pro obsługuje pełen zakres mechanizmów CoreSight, m.in. blok ETM i realizuje tzw. strumieniowy ślad (Streaming Trace). Odbiera strumień danych śladu z OCD z szybkością 100 MB/s, poddaje kompresji i wysyła z szybkością 25MB/s do komputera, gdzie dane są pod nadzorem IDE buforowane na dysku twardym. Szybkości te są wystarczające do obsługi mikrokontrolerów Cortex- M pracujących z zegarem do 200 MHz.

Współczesne komputery bez problemu obsługują te szybkości transmisji, więc pojemność bufora jest ograniczona tylko pojemnością dysku. Ślad może więc być analizowany na dużej przestrzeni czasowej, a gromadzona informacja zawiera wszystkie dane potrzebne do śledzenia i analizy przetwarzania, m.in. wartości licznika rozkazów, zapisy/odczyty danych, liczniki zdarzeń oraz informacje o cyklach i czasie wykonania instrukcji. ULink-Pro jest więc relatywnie tanim narzędziem pozwalającym w pełni wykorzystać wszystkie wbudowane mechanizmy debuggera technologii CoreSight w połączeniu ze środowiskiem μVision pakietu MDK-ARM.

iC5000

Rys. 8. Środowisko winIDE firmy iSystem

Innym rozwiązaniem jest analizator iC5000 firmy iSystem. W odróżnieniu od Keila obsługuje różne rdzenie od różnych producentów (ARM, Power PC, Coldfire/ S12/S08, TriCore/XC2000). Resztę zapewnia elastyczne środowisko programowe WinIdea. iC5000 jest również bardziej rozbudowany pod względem sprzętowym. Zawiera m.in. bufory pamięci śladu zapewniające transmisję danych z maksymalną dopuszczalną przez procesor prędkością.

Z komputerem nadrzędnym może się komunikować przez USB lub Ethernet. Opcjonalny moduł we/wy umożliwia monitorowanie sygnałów cyfrowych i analogowych w układzie aplikacyjnym i generowanie wymuszeń. Środowisko WinIdea zapewnia też bardziej zaawansowane mechanizmy analizy przetwarzania, a w szczególności analizy pokrycia.

Podsumowanie

W dawnych czasach wybór procesora do aplikacji był niezależny od możliwości debugingu. Projektant wybierał procesor najlepszy, a później starał się o jakikolwiek emulator. Po zainwestowaniu w konkretne narzędzia i poznaniu jakiejś rodziny kontrolerów z reguły przez lata obracał się w jej kręgu. Te czasy minęły. Obecnie w wyniku galopującego postępu i unifikacji można bez problemu, stosując np. ARM-y, w każdym kolejnym projekcie wybierać inny, nowszy procesor i to każdorazowo innej marki.

Projekty są tworzone "na miarę" z każdorazową optymalizacją kosztów i rozmiarów projektowanego urządzenia. Narzędzia uruchomieniowe nie są tak rozbudowane jak kiedyś, lecz za to bardziej uniwersalne. Pokrywają szersze spektrum procesorów od różnych producentów i są przede wszystkim łatwiej dostępne.

Tadeusz Górnicki
WG Electronics

www.wg.com.pl

Zobacz również