TT#4 Fault Analyzer
Każdy programista mikrokontrolerów ARM prędzej czy później napotka w swoim kodzie błąd, który powoduje zatrzymanie się programu w obsłudze przerwania od HardFault. Jak znaleźć i zdiagnozować przyczynę? O tym w dzisiejszym wpisie.
Fault Analyzer to narzędzie wbudowane w środowisko STM32CubeIDE, które pomaga zinterpretować informacje pozyskane z zagnieżdżonego kontrolera przerwań NVIC w celu zidentyfikowania przyczyn, które spowodowały błąd działania rdzenia. Informacje te są wizualizowane w widoku analizatora błędów i pomagają identyfikować i rozwiązywać trudne do znalezienia błędy systemowe, które występują, gdy procesor jest wprowadzany w stan błędu przez oprogramowanie aplikacji.
Jakie mogą być przyczyny błędów systemowych? Oto kilka najczęściej spotykanych:
- Próba uzyskania dostępu do nieprawidłowych lokalizacji w pamięci
- Wykonywanie niezdefiniowanej instrukcji
- Dzielenie przez zero
Błędy zostały podzielone na kilka kategorii:
- Hard Fault oraz Bus Fault – występują, gdy podjęta zostanie próba nieprawidłowego dostępu przez magistralę, rejestru układu peryferyjnego, albo pamięci
- Usage Fault – powstają w wyniku niedozwolonych instrukcji lub innych błędów programu
- Memory Managment Fault – obejmują próby dostępu do nielegalnej lokalizacji lub naruszenia zasad utrzymywanych przez jednostkę ochrony pamięci (MPU)
Pełną listę błędów możemy zobaczyć po uruchomieniu trybu debugowania w oknie Fault Analyzera. Jeżeli okno nie uruchomi nam się automatycznie, możemy je otworzyć poprzez Window->Show View->Fault Analyzer.
Poza tym, aby dodatkowo ułatwić szukanie błędów, analizator wyświetla zawartość rejestrów MCU w momencie awarii. Pozwala to na odtworzenie stanu MCU w momencie wykonania błędnej instrukcji.
Jak wygląda taka analiza błędu? Spróbujmy wywołać sobie prosty do powtórzenia błąd np. dzielenie przez zero. Po uruchomieniu projektu w trybie debugowania i wystartowaniu programu, zatrzyma się ono w pliku z obsługą przerwań (w moim przypadku „stm32l4xx_it.c”), a konkretnie w funkcji HardFault_Handler(). Wówczas okno analizatora wygląda jak poniżej.
Widzimy zaznaczone pola „Bus, memory management or usage fault„, które mówi nam o ogólnym typie błędu, oraz w zakładce Usage Fault Details pole „Attempt to perform a division by zero„, które określa błąd bardziej szczegółowo. Dodatkowo możemy podejrzeć zawartość rejestrów wewnętrznych procesora.
Znajdziemy tam takie rejestry, jak:
- SP (Stack Pointer) – wskaźnik stosu. Przechowuje on adres ostatnio zapisanej instrukcji na stos. W nawiasie podane jest, który wskaźnik stosu jest używany: MSP (Main Stack Pointer) lub PSP (Process Stack Pointer)
- r0 do r12 – rejestry ogólnego przeznaczenia
- LR (Link Register) – przechowuje adres powrotu do aktualnie wykonywanego podprogramu
- PC (Program Counter) – licznik programu, który wskazuje adres instrukcji następną po ostatnio pobranej do wykonania
- xPSR (Special-Purpose Program Status Register) – zawiera flagi statusów np. negative, zero, carry, overflow
Wiemy już jaki rodzaj błędu się pojawił. Jednak nasz program zatrzymał się w HardFault_Handler() i nie możemy zidentyfikować linii kodu, która wywołała błąd. W takim wypadku wystarczy, że klikniemy w ikonę w górnej części okna po prawej stronie.
Poszczególne ikony odpowiadają za:
- Open editor on fault location – otwiera edytor kodu w miejscu wystąpienia błędu
- Open dissasembly on fault location – wskazuje linię w edytorze deasemblacji w miejscu wystąpienia błędu
- PC lub LR – wybór rejestru, z którego chcemy pobrać adres do podświetlenia w edytorze kodu lub deasemblacji
W ten sposób możemy dość łatwo odnaleźć źródło błędu. Moglibyśmy to odczytać bezpośrednio w rejestrów wewnętrznych (zresztą Fault Analyzer właśnie z nich korzysta), jednak mając gotowe narzędzie, które dodatkowo wizualizuje nam informację, szukanie błędów jest znacznie łatwiejsze.