Warsztaty‎ > ‎

uC-UART + C#

Z braku lepszego pomysłu i z racji tego, że pasuje to tematycznie do warsztatów nr 2, zamieszczam ten artykuł w tym miejscu.

Prolog

Niezależnie od tego, jakiego mikrokontrolera używasz, zasada działania poniższych przykładowych kodów źródłowych będzie identyczna. 
Możesz te przykłady uruchomić na V1BASE (MCU 32-bitowy), NE64BASE (16-bitowiec), na maleńkim MC9S08QE4 (8-bitowiec) albo na układzie ATMEGA. Dobre praktyki programistyczne obejmują także pisanie kodu jak najbardziej niezależnego od używanego mikrokontrolera. U podstaw języka C leży przenośność, ale niestety dzisiaj większość dostępnych w Internecie przykładowych programów nie spełnia tego wymogu.

Anty-przykład:
W mikrokontrolerach HCS12 (np. NE64) obsługa portu L (diody świecące) ogranicza się do użycia specjalnego rejestru PTL.
Zapalenie diody wymaga wpisania wartości 0 do odpowiedniego bitu tego portu, np. PTL_PTL3 = 0; a zgaszenie wybranej diody to ustawienie bitu (np. instrukcja PTL = 0x7F; gasi wszystkie 7 diód podłączonych do NE64). Problem pojawia się wtedy, kiedy kod źródłowy języka C jest mocno nasycony takimi odwołaniami do rejestrów specyficznych dla tego konkretnego mikrokontrolera.
Próba przeniesienia takiego kodu na V1BASE (rdzeń ColdFire V1) albo na ATMEGę to droga przez mękę.
W V1BASE mamy 4 diody przylutowane do wyprowadzeń PTJ4..PTJ8 oraz dodatkowo 8 diód na porcie D. Rozkazy włączające/wyłączające diody są tutaj zupełnie inne.

Z drugiej strony, programista używający np. printf("Hello, world!"); nie powinien się przejmować tym, czy jego program jest uruchomiony na wypasionym PC z zainstalowanym systemem Windows czy też może na miniaturowym zestawie Raspberry Pi z Linuksem na pokładzie. 

Processor Expert a dobre praktyki dotyczące przenośności kodu
Narzędzie Processor Expert ułatwia pisanie kodu w taki sposób, że jest on łatwo przenośny między różnymi modelami mikrokontrolerów. Zamiast bezpośrednio odwoływać się do rejestrów mikrokontrolera, można używać wysokopoziomowych funkcji lub makrodefinicji.
Np. hipotetyczna instrukcja leds_SetBit(5); oznacza zapalenie 5 bitu komponentu o nazwie 'leds' bez zmiany pozostałych bitów. Nie jest ważne, do którego portu ktoś przylutował te diody. Nie ma znaczenia, który z mikrokontrolerów jest przeze mnie używany w danym momencie. Ważne jest to, że skomplikowany algorytm napisany w C służący do migania diodą o numerze '5' wie, że wystarczy użyć tej właśnie funkcji, aby spowodować zapalenie konkretnej diody. Nie interesuje mnie, czy jest to fizycznie port L w NE64 czy też może portD w V1BASE - po prostu ma się zapalić dioda 5.

Port szeregowy w mikrokontrolerze

Podążając tym tropem, można napisać podobne funkcje do obsługi bardziej wyszukanych zadań, a dobrym przykładem będzie tutaj obsługa komunikacji znakowej przez mikrokontroler.
Typowo używamy do tego celu asynchronicznego portu szeregowego (UART). Może się jednak zdarzyć (to dość wyszukany przypadek), że do tego celu ktoś zechce się posłużyć układem SPI albo I2C. Właściwie nie ma to znaczenie - niezależnie od użytego układu programista ma dostęp do 2 funkcji: wyślij_znak(char c) oraz odebrano_znak(char c). Osobną sprawą jest to, komu przyjdzie napisać te funkcje. W mikrokontrolerach Freescale wysyłanie i odbieranie pojedynczych znaków z różnych urządzeń sprowadza się zwykle do 2-3 linijek kodu. Poniżej zamieściłem przykłady dla mikrokontrolerów 8-, 16-, 32-bitowych.

Kiedy już mamy te 2 funkcje, pozostały kod (gromadzenie i rozpoznawanie treści polecenia) jest identyczny dla wszystkich mikrokontrolerów, można będzie używać ulubionego copy-paste do przenoszenia kodów źródłowych między mikrokontrolerami różnych rodzin.

Odbieranie pojedynczych znaków w uC
...
Wysyłanie znaków i napisów
...
Aplikacja okienkowa w C# wysyłająca rozkazy do uC
c.d.n.