Programowanie, a ESD.


Można sobie zadać pytanie... co wspólnego może mieć programowanie z zakłóceniami
elektrostatycznymi?

Otóż ma i to sporo ;-) Wszystko zależy od tego oczywiście na co piszemy program...
Jeśli ma to być zegarek stojący na biurku, to faktycznie nie ma się czym przejmować
(no chyba, że emisja zakłóceń skutecznie uniemożliwi odbiór radia!!!), gorzej jeśli to jest
sterownik przemysłowy, który steruje maszyną i na dodatek zależy od tego czyjeś życie.
W Polskiej literaturze technicznej jest bardzo mało materiałów na ten temat.
W zagranicznej znacznie lepiej np:

Link1
Link2
Link3(pdf)
PCB (zasady projektowania płytek PCB)
PCB1(pdf)


W paru słowach chciałbym poruszyć pewien aspekt programowania mikrokontrolerów
w środowisku zakłóceń i sytuacji awaryjnych. Poniżej zamieszczam nieudolne tłumaczenie
artykułu ze strony http://www.dbicorporation.com/esd-art2.htm


Prewencja programowa:

  1. Czytaj krytyczne sygnały wejściowe (input) dwa lub trzy razy co kilka-kilkanaście mikrosekund przed ich użyciem
  2. Po wysłaniu danych do wyjścia (output) przeczytaj to wyjście ponownie, aby zweryfikować poprawność wysłanych danych
  3. Trzymaj kopię wszystkich portów wyjściowych (output) w pamięci RAM
  4. W regularnych odstępach czasu:
Przyjmij wzajemne oddziaływanie pomiędzy sprzętem, a oprogramowaniem
  1. Stwórz metody logowania nienormalnych zdarzeń w celu późniejszej analizy (jeżeli to możliwe dla kilku zdarzeń ponieważ jeden błąd może powodować inne błędy gdy poprawki nie działają)
  1. Kontroluj czas watchdog'a
  2. Wypełnij nieużywaną pamięć NOP'ami
  3. Wypełnij nieużywaną pamięć instrukcjami wielobajtowymi (np. Skok absolutny lub wywołanie absolutne), których operandy są takie same jak kod rozkazu
  4. Skieruj wszystkie nieużywane przerwana do procedury obsługi błędu
  5. Sprawdzaj sprzęt, RAM, ROM w poszukiwaniu błędów
  6. Rozważ możliwość uśpienia procesora o ile błędy występują zbyt często.
  7. Spróbuj odzyskać poprzedni poprawny stan kiedy wykryłeś stan niewłaściwy, jeżeli to nie jest możliwe, przejdź do stanu który najprawdopodobniej powoduje problem i poinformuj współpracujące jednostki, że właśnie zakończyłeś operację odzyskiwania stabilności
  8. Odtwórz stan sprzętu
  1. Sprawdź wszystkie dane pod względem składni i wartości
  1. Sprawdzaj odebrane dane, potwierdzaj prawidłowość danych i wysyłaj kod błędu w wypadku nieprawidłowych danych
  2. Wyślij ponownie dane w przypadku braku potwierdzenia
  3. Sprawdzaj dane przed ich użyciem
  4. W rozsądny sposób sprawdzaj dane wejściowe:
    1. typy
    2. zakres
    3. ramkę
    4. parzystość
    5. sumy kontrolne
  5. Zapamiętuj krytyczne dane w różnych lokacjach pamięci i porównuj je od czasu do czasu
  6. Duże tablice danych przerywaj sumą kontrolną
  7. Używaj parzystości, sum kontrolnych do sprawdzania obszarów danych
  8. Użyj maksymalnej długości licznika do wiązania danych ??
  9. Niech proces czasu rzeczywistego werifikuje czy aktualnie wykonywane zadanie wykonuje się w przeznaczonym dla niego czasie, kiedy zadanie się kończy lub jest wstrzymywane
  10. Sprawdzaj cyklicznie w programie głownym, czy stos jest pusty
  11. Sprawdzaj, czy stos jest pusty po zakończeniu zadania
  12. Rób kopię stosu, gdy skaczesz do podprogramu i porównuj je przy powrocie z podprogramu
  13. Zapisz znacznik  przed wejściem ...
  14. Sprawdzaj rejestry indeksowe przed ich użyciem
  15. Sprawdź przepełnienie licznika przed wejściem do pętli opóźniającej
  16. Opuść pętlę jeśli licznik jest poza zakresem
  17. Pracuj na kopi rekordu i sprawdzaj go przed zapisem do bazy danych
  18. Uruchom watchdoga
  19. Sprawdź wskaźniki i rejestry indeksowe czy są w zakresie tablic, danych, stosu przed ich użyciem.
  20. Utwórz dodatkowe wskaźniki (podwójnie dowiązane listy), liczniki wpisów, identyfikatory typu/stanu do rekordów, co pozwoli na ich łatwą naprawę
  21. Daj możliwość wyłączenia rozszerzeń ESD na czas testów, to może być specjalnie kompilowana wersja oprogramowania do celów deweloperskich
  22. Dla testów ESD przygotuj wersję oprogramowania która ciągle wykonuje wszystkie funkcje bez udziału operatora
  23. Opracuj sposób na łatwe poinformowanie testerów że stało się coś złego (LED, wyświetlacz stanu itp.)
  24. Poinformuj użytkownika o poważnym błędzie, który nie może być automatycznie naprawiony
  25. Umieszczaj co jakiś czas w programie kilka instrukcji NOP, w grupach tak długich jak najdłuższa instrukcja procesora. Pozwoli to na synchronizację procesora z kodem kiedy licznik programu przekłamie się.
  26. Umieszczaj grupy instrukcji NOP poprzedzające rozkaz przerwania programowego pomiędzy każdą parą podprogramów. Procedura przerwania wykonuje reset procesora i może ustawiać linię wyjściową aby ostrzec programistę/użytkownika że wystąpił poważny błąd programu.
  27. Zabezpiecz sprzętowo pamięć, specjalną sekwencją zapisu, aby uniknąć błędnych wpisów
  28. Próbuj wyłapać:
    1. zapis do zabezpieczonej pamięci
    2. zapis do ROM'u
    3. zapis i odczyt do nieistniejącej pamięci
    4. zapis i odczyt do nieistniejących urządzeń
  29. Obsługuj wszystkie sytuacje które można przewidzieć.
  30. Sprawdź kody wynikowy programu pod kątem występowania w nim rozkazów self-testu w danych, tabelach itp. Jeżeli program zagubi się i wykona w jednej z tych lokacji, konieczne będzie całkowite wyłączenie zasilania i jego ponowne włączenie aby system działał.
  31. Twórz program w taki sposób, by nieprzewidziane dane wejściowe nie miały wpływu na wykonywanie programu
  32. Sprawdzaj kod  i instrukcje, sekwencje instrukcji, aby uniknąć niespodzianki w postaci:
    1. zresetowania watchdoga
    2. zatrzymania procesora
    3. wstawienia opóźnień
  33. Ustaw flagę w programie w krytycznych funkcjach i sprawdzaj jej poprawność
  34. Stosuj licznik (w sensie timeout) w krytycznych funkcjach.
  35. Kontroluj nieużywane przerwania
  36. Zapisz flagę (flagi) w pamięci RAM (volatile), będą wskazówką do zaników zasilania i sprawdzaj tę flagę przy resecie
    1. jeśli jakaś flaga jest błędna dokonaj, zrestartuj program i ustaw flagę domyślną wartością (nie 0 i nie 1)
    2. jeśli wszystkie flagi są dobre to skocz do dalszej części programu
  37. Co jakiś czas zapisuj dane do nieulotnej pamięci, aby zminimalizować utratę danych
  38. Jeśli to możliwe, czytaj peryferia bez użycia przerwań
  39. Wybierz lub zaprojektuj protokół komunikacji szeregowej taki, aby jeden „wysoki” znak nie znalazł się w ciągu „niskich” znaków i odwrotnie (wysoki = 0x80-0xFF; niski - 0x00 - 0x7F)
  40. Zapisz znacznik w podstawowej pamięci, aby kontrolować przypadkowe skoki w programie:
    1. Ustaw zero we fladze w pętli głównej programu
    2. Jeśli skoczyłeś do funkcji(podprogramu)

                3. Za pomocą funkcji inline sprawdź jej poprawność

                4. Wewnątrz pętli sprawdzaj jej poprawność

                5. Przy wyjściu z pętli (podprogramu) wyzeruj ją

                6.Przy krytycznych danych wyjściowych sprawdzaj flagę co najmniej dwukrotnie

  1. Sprawdź różne warianty konfiguracji i jego zachowanie w trybie IDLE i resetu itp...
  2. Upewnij się że detekcja błędu i proces rozruchu jest wystarczająco szybki aby błąd który mógłby wystąpić będzie wykryty zanim podłączony mechanizm zdąży zrobić coś niebezpiecznego.
  3. Strzeż się dostępu do wewnętrznych rejestrów sprzętu poprzez auto inkrementrację(dekrementację) rejestru indeksowego. Ustawiaj rejestry indeksowe jawnie przed każdym zapisem/odczytem do sprzętu.
  4. Sprawdzaj dane, skoki, czy nie występują w nich niedozwolone instrukcje HALT, WAIT, DISABLE INTERRUPT
  5. Na końcu tablicy danych umieszczaj ciąg instrukcji NOP ze skokiem do procedury obsługi błędu
  6. Strzeż się używania instrukcji zarezerwowanych dla twardego resetu.
  7. Przed powrotem z podprogramu sprawdzaj;
    1. czy stos mieści się w dozwolonym zakresie
    2. czy adres powrotu odnosi się do sekcji kodu
    3. czy instrukcja CALL poprzedza adres powrotu
  8. Zapełnij nieużywany RAM losowymi wartościami (różnymi, i nie zerami), sprawdzaj obszar  podczas odzyskiwania danych, aby zadecydować, czy nie trzeba reinicjalizować danych w pamięci


Praktyka:

Jak widać jest tego sporo. Oczywiście nie ma najmniejszych szans zastosować tych wszystkich metod w małym kontrolerze,
obsługa wyjątków tutaj opisanych zajęła by nam 120% mocy obliczeniowej procesora ;-) o zajętości pamięci nie wspominając.
W "małych" procesorach (o ile ARM'y można nazwać małymi...) przeważnie wystarcza kilka podstawowych metod zabezpieczenia:

  1. watchdog
  2. praca na kopii portów wyjściowych
  3. sumy kontrolne CRC na obszarach pamięci z ważnymi danymi
  4. wypełnianie nieużywanej przestrzeni pamięci programu NOP'ami
  5. obsługa nieużywanych przerwań
  6. wielokrotny odczyt portów i porównywanie kolejnych odczytów
  7. kontrola przyczyn wystąpienia resetu (w procesorach AVR rejestr MCUSR/MCUCSR)
  8. używanie timeoutów w pętlach while (lub innych oczekujących na jakieś zdarzenie, które np. w wyniku awarii nigdy nie nadejdzie...)
  9. kontrolowanie przypadkowych skoków w programie (punkt 55)
  10. umieścić w pamieci RAM zmienną pseudolosową (long, 4-bajty) i okresowo sprawdzać jej poprawność

Pisząc program należy przyjąć zasadę, że RESET jest normalnym zjawiskiem (stanem pracy procesora), które może zajść od czasu
do czasu pomimo wszelakich  zabezpieczeń sprzetowo-programowych. Program należy tak skonstruować, aby w przypadku
Reset'u system sam się podniósł, ratując wszystkie możliwe dane (do tego służy np. obszar .noinit) i powrócił do normalnej pracy.
 Oczywiście nie zawsze się tak da, ale zawsze trzeba dążyć do tego stanu.