Narzędzia do rozwiązywania problemów w systemach z mikrokontrolerami z rdzeniem Cortex-M3/M4

Wykorzystanie systemu czasu rzeczywistego w systemach wbudowanych i jego wpływ na debugowanie

Zastosowanie systemu czasu rzeczywistego w projektowanej aplikacji ma wady i zalety, które zostały już dokładnie omówione w mnóstwie publikacji, artykułów, dyskusji na forach oraz wewnątrz zespołów projektowych. W ostatnich latach, wzrastające możliwości i coraz niższe ceny mikrokontrolerów opartych na rdzeniach Cortex-M3 i M4 spowodowały obniżenie bariery zastosowania systemu typu RTOS.

Warto zauważyć, że użycie RTOS’a  było kiedyś traktowane jako utrudnienie w debugowaniu. W artkule Michaela Melkoniana zatutułowanym „Getting by Without an RTOS” („Radzenie sobie bez RTOS’a”), autor stwierdza: Wiele emulatorów ICE i debugerów nie współpracuje z RTOS’ami. Trzeba być przygotowanym na uspokajanie podenerowanych inżynierów próbujących ustawić breakpoint, gdy jądro systemu kolejkuje to zadanie na później. Ten interesujący komentarz w oczywisty sposób odsłania swój wiek, ale podejście wielu inżynierów pozostało bez zmian. W wielu zespołach projektowych RTOS jest uważany za kolejną warstwę utrudniającą debugowanie. Możemy jednak mieć pewność, że nowoczesne narzędzia, takie jak JTAG czy Serial Wire Viewer pracują stabilnie także z aplikacjami wykorzystującymi system czasu rzeczywistego.

Aby poprawie ocenić wpływ systemu real-time na problem debugowania trzeba rozważyć, jakie informacje są potrzebne przy debugowaniu aplikacji wykorzystującej taki system. Wszystkie implementacje RTOS’ów mają pewne cechy wspólne, a mianowicie:

  • rozbijają aplikacje na osobne zadania zarządzane przez system operacyjny,
  • wykorzystują API do tworzenia i usuwania zadań oraz zarządzania nimi,
  • wykorzystują różne mechanizmy komunikacji między zadaniami i synchronizacji,
  • używają mechanizmów zarządzania czasem.

Gdy popatrzymy z takiej perspektywy, problem debugowania systemu wykorzystujacego RTOS przenosi się na znacznie wyższy poziom abstrakcji. W „tradycyjnym” debugowaniu, projektant chce zwykle znać dokłanie niskopoziomowe detale, takie jak: wartosć zmiennej, zawartość rejestru, układ bitów w SFR, zachowanie aplikacji przy pracy krokowej itp. Mimo, że część z nich nadal jest przydatna, przy debugowaniu RTOS’a potrzebne jest też kilka zupełnie innych informacji:

  • jakie są stany poszczególnych zadań w momencie wstrzymania pracy systemu?
  • które zadania są wykonywane, które gotowe, a które czekają?
  • jeśli zadanie czeka, to na jakie zasoby?
  • ile obiektów synchronizacyjnych, takich jak: semafory i mutexy, jest wykorzystywanych i jakie są ich statusy?
  • jakie zdarzenia i komunikaty istnieją w systemie i jakie są statusy tych obiektów?
  • które timery zakończyły zliczanie, a które nadal pracują?

Tu właśnie pojawia się potrzeba rozwiniętej i dobrej jakościowo wizualizacji, a jej zaspokojenie jest szczególnie istotne przy debugowaniu i testowaniu w czasie integracji całego systemu.

Termin kernel-aware debugging (który można tłumaczyć dosłownie jako „debugowanie z wzięciem pod uwagę/ze świadomością jądra systemu”), oznaczający obserwowanie raczej informacji dotyczących obiektów systemowych niż konkretnych elementów kodu, pojawił się około dziesięciu lat temu. Początkowo uważany za tanią sztuczkę czy chwyt marketingowy, teraz uznawany jest często za niezbędny przez inżynierów rozwijających systemy wbudowane. Stoją za tym dwie przesłanki. Po pierwsze, wiedza o wymienionych powyżej sprawach daje wysokopoziomowy ogląd kondycji całej aplikacji z perspektywy elementów systemu, których prawidłowa współpraca jest podstawą jej działania. Po drugie, wspomniane niskopoziomowe „detale” są często ukryte głęboko w strukturach danych użytych do konstruowania zadań systemowych, serwisów i mechanizmów kontrolnych. Projektant nie zawsze chce szukać miejsca w pamięci, w którym przechowywany jest mutex czy flaga przedawnienia i wyświetlać wartość takiej zmiennej, jeśli można tego uniknąć. Co więcej, dobrze przetestowane i klarownie przedstawione informacje dostępne w nowoczesnych środowiskach projektowych można łatwiej i szybciej odczytać oraz dużo lepiej zrozumieć.

Poniższy rysunek przedstawia typowy zestaw okien używany podczas debugowania w stylu kernel-aware.

 

Rys. 4. Widok okna programu w czasie debugowania z wzięciem pod uwagę jądra systemu; w projekcie wykorzystano system embOS

Rys. 4. Widok okna programu w czasie debugowania z wzięciem pod uwagę jądra systemu; w projekcie wykorzystano system embOS

 

Kernel aware debugging z wykorzystaniem Serial Wire Viewer

Podstawowa wada debugowania w stylu kernel-aware na chwilę obecną jest taka sama, jak przy tradycyjnym wykorzystaniu JTAG’a. Aby zwizualizować informacje specyficzne dla systemu czasu rzeczywistego, np. stan wykonywanych zadań, semaforów, komunikatów i zdarzeń, aplikację trzeba wstrzymać. Dla niektórych układów, np. sterowników silników, nie jest to optymalna strategia, gdyż czasami jest to niemożliwe bez poświęcania sprzętu. Na dzień dzisiejszy, istnieją dwa rozwiązania problemów tego typu.

Po pierwsze, ograniczone, ale często kluczowo ważne informacje o obiektach systemu real-time można uzyskać na bieżąco używając SWV. Takie rozwiązanie wymaga wycofania się nieco z wcześniejszego retorycznego pytania o to czy projektant chce się zagłębiać w struktury danych systemu na niskim poziomie i wyszukiwać potrzebne mu informacje ręcznie. Jeśli korzyści mają być znaczne, czasami warto poświęcić chwilę, aby zapoznać się z systemem operacyjnym i znaleźć w nim ważne miejsca, w których ukryte są potrzebne dane, a następnie skonfigurować SWV, by powiadamiał o nich automatycznie.

Alternatywą jest zakup dodakowego narzędzia, np. programu µC/Probe firmy Micrium. Jest to aplikacja dostarczająca informacje do debugowania w stylu kernel-aware oraz szczegóły pracy systemu w czasie rzeczywistym. Wymaga ona kanału komunikacyjnego, np. JTAG’a, USB, LAN czy UART i działa w sposób intruzyjny, czyli zaburza pracę programu.

Skupiając się na pierwszej metodzie – przykład jej wykorzystania można zobaczyć przyglądając się ponownie rysunkowi 4. Okno w prawym, górnym rogu przedstawia Serial Wire Data Trace Timeline Graph, czyli zmianę w czasie wartości jednego pola w strukturze danych, które przechowuje liczbę wiadomości w skrzynce pocztowej. Wiadomości są normalnie tworzone przez jedno z zadań, aby przesłać informacje do drugiego zadania. Zadanie nadające wiadomości wstawia je do skrzynki, a zadanie-odbiorca zwykle jest zablokowane do czasu udostępnienia kolejnej wiadomości, a następnie pobiera ją i wykorzystuje. Wykres prezentuje regularny, okresowy przebieg cyklu generowania i pobierania wiadomości. Ma to, oczywiście, znaczenie tylko w kontekście projektu i implementacji tej konkretnej aplikacji. Wystąpienie takiego regularnego przebiegu nie jest gwarancją, że cały program działa poprawnie.

Mając to na uwadze załóżmy jednak, że przedstawiony w oknie Serial Wire Data Trace Timeline Graph wykres jest oznaką poprawnego i pożądanego zachowania. Gdyby w tym miejscu programu pojawił się problem polegający na zaburzeniu procesu przesyłania wiadomości, byłoby to więc natychmiast widoczne na wykresie. Trzeba pamiętać, że dane zmieniają się w czasie rzeczywistym (i bez dodatkowego obciążania mikrokontrolera), więc można je przeglądać w czasie działania programu w naturalnym środowisku pracy, czyli na docelowym sprzęcie i przy interakcjach z czynnikami zewnętrznymi. Zaburzenie w przepływie wiadomości można więc łatwo skojarzyć z jakimś czynnikiem czy działaniem. Co więcej, oś wykresu jest wyskalowana w jednostkach czasu, więc tester wie dokładnie w którym momencie od rozpoczęcia testu pojawił się błąd, co dodatkowo ułatwia identyfikację problemu.

O autorze