Pierwszym programem napisanym dla mikrokontrolera w nowym układzie powinien być program, który zaświeci diodę LED lub wystawi jakiś stan logiczny na porcie 1/0. W ten sposób można szybko zweryfikować, czy używane środowisko programistyczne generuje poprawny kod, czy programator działa prawidłowo, czy procesor ma sygnał zegarowy oraz czy dioda LED działa poprawnie i czy nie została wlutowana odwrotnie. Całkiem sporo jak na parę linijek kodu. Jeżeli jednak dioda LED nie zaświeci, to warto zweryfikować omomierzem, czy pomiędzy portem mikrokontrolera a diodą LED nie ma przerw. Można się także pokusić o podłączenie do tej ścieżki masy bądź plusa zasilania (w zależności od tego, co zaświeca diodę LED) przez rezystor 1 k£ź. Jeżeli dioda wtedy się nie zaświeci, to wina leży po jej stronic, a jeżeli zaświeci - po stronie mikrokontrolera.
Mając napisany tak prosty program, warto poświęcić jeszcze trochę czasu na pisanie bardzo prostych procedur wymuszających pracę poszczególnych podzespołów, takich jak diody LED, przyciski, wyświetlacze, pamięci, przetworniki, przekaźniki, etc. Pozwoli to wyeliminować błędy sprzętowe już na tym etapie. Tak opracowany kod warto zostawić na później, gdyż może się przydać w innych układach oraz na dalszych etapach pracy z projektem. Czasami po wielu nieudanych próbach uruchomienia jakiegoś modułu lub układu zaczyna pojawiać się pytanie, czy nie został on w międzyczasie uszkodzony. W razie takich wątpliwości możliwe będzie załadowanie takiej właśnie procedury, która pozwoli sprawdzić, czy całość pracuje prawidłowo.
Pisząc kod, warto korzystać z udogodnienia, jakim są komentarze. Na etapie tworzenia projektu, kiedy kod źródłowy jest nieustannie przetwarzany, zmieniany i modyfikowany, w zasadzie nic ma problemu ze zrozumieniem, co poszczególne instrukcje robią. Jeżeli układ jest robiony na potrzeby bieżące, jest gdzieś implementowany i Czytelnik o nim „zapomina”, to brak komentarzy można w zasadzie wybaczyć. Problemy zaczynają się w momencie, gdy trzeba coś zmienić. Sytuacja taka może mieć miejsce, gdy jakiś projekt zostanie opublikowany, ktoś znajdzie w nim błąd i zaistnieje potrzeba jego usunięcia. Mija zazwyczaj trochę czasu i poszczególne linie kodu nie są już tak oczywiste. Zmiany w kodzie przypominają wtedy siąpanie po polu minowym - zmiana jakiejś zmiennej lub fragmentu kodu może powodować trudne do przewidzenia skutki. Warto zatem umieszczać komentarze, możliwie dokładne, aby nie mieć problemów z poprawkami po dłuższym czasie. Jest to również dotkliwe, gdy pracuje się na zlecenie dla jakiejś firmy i po pewnym czasie przychodzi grzeczny e-mail z prośbą o poprawienie tego i owego. Czas rzekomo zaoszczędzony na pisaniu komentarzy jest po wielekroć tracony.
Warto stosować sensowne nazwy zmiennych, gdyż błędy popełniane przez podstawienie wartości do niewłaściwej zmiennej trudno jest zdiagnozować. Układ najczęściej zaczyna „wariować” i działać w sposób „magiczny”. Nie chodzi oczywiście o pisanie wymyślnych i długich nazw, ale o unikanie nazewnictwa typu zz, xJ, x2, czy temp 16. Zamiast tego lepiej sprawdzają się nazwy w stylu adcresult czy pwmvalue. Są one nadal nieczytelne, więc warto korzystać z separatora (np. adcjyalue) czy liter małych i dużych (ADCresult, pwmVa/ue, etc.). Oczywiście nic nie stoi na przeszkodzie, aby używać polskiego nazewnictwa (wynikADC, wypełnienie czy liczbalteracji). Używanie terminów angielskich ma tę zaletę, że kod jest zrozumiały dla szerszego grona. Jest to wartościowe, gdy kod urządzenia zostanie udostępniony innym osobom za pośrednictwem Internetu. Nietrudno zauważyć, że kod napisany np. po czesku, dla Polaka nie nadaje się w zasadzie do niczego, bo trudno się w nim rozeznać, a co dopiero wprowadzić jakieś zmiany. Jest to dobry nawyk także na przyszłość, gdy zaistnieje konieczność pracy w międzynarodowym zespole i taka praktyka będzie wtedy wymagana. Co prawda do tego jeszcze pewnie daleka droga, ale zawsze trochę więcej satysfakcji ma się z projektu, który ma znamiona rozwiązania profesjonalnego.
Ład w kodzie jest łatwiej utrzymać, gdy oprogramowanie ma określoną strukturę. Popularne środowisko WinAVR daje możliwość pisania oprogramowania w języku C++, który oferuje programowanie obiektowe. W uproszczeniu polega to na podziale kodu na niezależne moduły, które realizują określone funkcje. W ten sposób można wydzielić np. moduły odpowiedzialne za obsługę wyświetlacza, termometru cyfrowego, pamięci EEPORM, etc. W przypadku C warto podzielić kod w podobny sposób, umieszczając funkcje związane z obsługą jakiegoś elementu urządzenia w osobnym pliku. Znacznie upraszcza to proces zarządzania kodem, gdyż chcąc zmodyfikować funkcjonalność np. wyświetlacza, wiadomo, gdzie szukać kodu odpowiedzialnego za jego pracę - we właściwym pliku i/ lub klasie.
Zagadnienie programowania obiektowego (podobnie jak sam język C++) zostało bardzo przystępnie omówione w książce Symfonia C++ autorstwa Jerzego Grębosza.
Warto jeszcze wspomnieć o metodologii tworzenia kodu. W swojej pracy staram się pisać go małymi fragmentami i testować te fragmenty w miarę dokładnie przed przejściem do dalszego etapu pracy. Mając do oprogramowania hipotetyczny projekt, który pobiera zawartość pamięci Flash i wyświetla dane z określonej komórki na ekranie LCD, warto podzielić go na moduły. Jednym z nich będzie fragment kodu odpowiedzialny za obsługę wyświetlacza, drugi za obsługę pamięci, trzeci za obsługę klawiatury, a program (pętla) główny zapewni sterowanie i przekazywanie informacji pomiędzy tymi modułami. Czy ma znaczenie, który moduł zostanie opracowany jako pierwszy? Zdecydowanie tak, gdyż oprogramowanie wyświetlacza zapewni możliwość prezentowania różnorodnych informacji związanych z działaniem programu. Mając oprogramowany wyświetlacz, można dokonać próbnego zapisu, odczytać pamięć Flash i wyświetlić rezultat na LCD. Jest to test wystarczający do stwierdzenia, czy kod odpowiedzialny za obsługę Flash pracuje prawidłowo. Gdyby rozpocząć pisanie oprogramowania od obsługi pamięci, to powstanie problem, jak zweryfikować poprawność działania tego elementu. Warto przyjąć jako zasadę, że najpierw należy oprogramować elementy stanowiące interfejs użytkownika (LCD, porty szeregowe, wyświetlacze LED, diody, etc.), następnie elementy odpowiedzialne za wprowadzanie informacji do sytemu (klawiatura, port szeregowy, zworki, etc.). Taka kolejność pracy zwiększa szansę na szybkie wykrycie usterek.
Następną kwestią jest podział poszczególnych modułów na pojedyncze funkcje. Oprogramowując za jednym zamachem port SPI, komunikację z pamięcią, funkcje zapisującą oraz odczytujące dane, zmniejsza się szanse na sukces. W takiej sytuacji jednocześnie dokonuje się komunikacja po SPI (pierwszy element), zapis rejestrów pamięci (drugi element) oraz odczyt z pamięci (trzeci element). Teoretycznie jest to dobry sposób, bo zapisując do komórki pamięci jakiś bajt danych i odczytując go po chwili, można szybko zweryfikować, czy całość pracuje poprawnie. Niestety najczęściej nie będzie pracować poprawnie. Pytanie: który z tych
Elektronika dla Wszystkich Styczeń2010 61