- liczba przegródek: 16,
- maksymalna liczba dziennych dawek: 5,
- źródło napięcia zasilającego: bateria alkaliczna typu AAA,
- prąd pobierany ze źródła zasilania (maksymalny/tryb uśpienia): 55 mA/80 μA (szczegóły w tekście artykułu).
Program sterujący
Zaczniemy od pliku nagłówkowego do obsługi diod LED RGB upraszczającego i porządkującego późniejszy kod, który to plik pokazano na listingu 1.
#define LED_PORT PORTA
#define LED_DDR DDRA
#define LED_PIN_NR PA3
#define LED_SET LED_PORT |= (1<<LED_PIN_NR)
#define LED_RESET LED_PORT &= ~(1<<LED_PIN_NR)
#define LED_PIN_AS_OUTPUT LED_DDR |= (1<<LED_PIN_NR)
Dalej, na listingu 2, pokazano ciało funkcji odpowiedzialnej za przesłanie jednego bitu danych przy udziale wspomnianego wcześniej asynchronicznego interfejsu komunikacyjnego. Warto zauważyć, że stosowną funkcję zdefiniowano, używając atrybutu __always_inline__, który instruuje kompilator, by ZAWSZE wstawiał rozwinięcie funkcji w miejscu wywołania, nie zaś skok do jej ciała. Jest to o tyle istotne, że operujemy na dość rygorystycznych timingach rzędu setek nanosekund, gdzie, przy częstotliwości taktowania mikrokontrolera rzędu 8 MHz (okres 125 ns), niepotrzebne (z punktu widzenia programisty, nie kompilatora) wykonanie jakiegoś rozkazu asemblera może rozłożyć całą transmisję danych, uniemożliwiając jakiekolwiek sterowanie wspomnianymi elementami.
__inline__ void __attribute__((__always_inline__))
ledSendBit(uint8_t Bit) {
if(Bit) {
LED_SET;
_delay_us(0.65);
LED_RESET;
_delay_us(0.65);
} else {
LED_SET;
_delay_us(0.25);
LED_RESET;
_delay_us(0.65);
}
}
Skoro mamy już funkcję umożliwiającą przesłanie jednego bitu informacji, to pora na funkcję (także odpowiednio zadeklarowaną) umożliwiającą przesłanie kompletnego bajta danych, przy udziale wspomnianego wcześniej interfejsu danych, której ciało pokazano na listingu 3.
__inline__ void __attribute__((__always_inline__))
ledSendByte(uint8_t Byte){
ledSendBit(Byte & 0x80);
ledSendBit(Byte & 0x40);
ledSendBit(Byte & 0x20);
ledSendBit(Byte & 0x10);
ledSendBit(Byte & 0x08);
ledSendBit(Byte & 0x04);
ledSendBit(Byte & 0x02);
ledSendBit(Byte & 0x01);
}
Dalej, by uprościć zapis danych do adresowalnych diod LED RGB, przewidziano funkcję pozwalającą na przesłanie kompletnej informacji o kolorze tejże diody LED, której ciało pokazano na listingu 4.
__inline__ void __attribute__((__always_inline__))
ledSendColor(uint8_t R, uint8_t G, uint8_t B){
ledSendByte(G);
ledSendByte(R);
ledSendByte(B);
}
Zapewne pamiętacie, że asynchroniczny interfejs komunikacyjny diod LED typu WS2812B-V5 przewiduje transmisję jeszcze jednego sygnału, a mianowicie sygnału RESET. Ciało funkcji odpowiedzialnej za transmisję sygnału RESET interfejsu diod WS2812B-V5 pokazano na listingu 5.
inline void ledReset(void){
LED_RESET;
_delay_us(300);
}
Normalnie w tym miejscu zakończyłbym opis obsługi naszych ciekawych diod LED RGB, ale nie tym razem. Prawdę mówiąc, funkcje pokazane powyżej będą działać bez problemów wyłącznie na szybszych mikroprocesorach, dla których jeden cykl zegarowy jest najlepiej o rząd wielkości krótszy niż timingi odpowiedzialne za wysłanie poszczególnych bitów koloru diod LED. W innym przypadku, czyli jak u nas, gdzie takt zegara trwa 125 ns (dla częstotliwości taktowania 8 MHz), wykonanie każdego dodatkowego rozkazu (jak na przykład sprawdzenie wartości każdego bitu zmiennej przeznaczonej do wysłania) może dość skutecznie rozłożyć całą transmisję danych, gdyż niezachowane są wówczas rygorystyczne timingi kodujące bity informacji.
Jak sobie z tym poradzić? Dość prosto, choć nie nazbyt optymalnie. Ja utworzyłem osobne funkcje dla każdego rodzaju bitu (0 i 1) i każdego koloru, który zamierzałem przesyłać, a wszelkie warunki zastąpiłem "gotowym" do wykonania kodem bez zbędnych (z punktu widzenia transmisji) rozkazów. Spowodowało to powstanie wielu funkcji o bardzo prostym (i w gruncie rzeczy podobnym), acz długim kodzie, który zachował niezbędne timingi. Te uproszczone funkcje, o których mowa powyżej, pokazałem na listingu 6, gdzie zamieszczono odrębne funkcje dla każdego z bitów informacji (0 i 1), jak i przykładową funkcję wysyłającą dane dla koloru czerwonego. Dzięki takiemu rozwiązaniu bez problemu możemy transmitować dane do naszych diod LED, korzystając z mikrokontrolera taktowanego zegarem 8 MHz.
#define NOP() __asm__ __volatile__ (“nop”)
__inline__ void __attribute__((__always_inline__))
ledSendBit0(void) { //Dla zegara 8MHz (NOP = 125ns)
LED_SET;
NOP();
LED_RESET;
NOP();
NOP();
NOP();
NOP();
NOP();
}
__inline__ void __attribute__((__always_inline__))
ledSendBit1(void) { //Dla zegara 8MHz (NOP = 125ns)
LED_SET;
NOP();
NOP();
NOP();
NOP();
NOP();
LED_RESET;
NOP();
NOP();
NOP();
NOP();
NOP();
}
__inline__ void __attribute__((__always_inline__))
ledRed(void) {
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit1();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
ledSendBit0();
}
Oczywiście, i czego się zapewne domyślacie, w czasie takiej transmisji należy blokować możliwość obsługi przerwań systemowych. Można również poradzić sobie w prostszy sposób, zwyczajnie zwiększając prędkość zegara taktującego mikrokontroler (najlepiej co najmniej dwukrotnie), lecz ja nie chciałem iść tą drogą, gdyż wydatnie zwiększa się wtedy zapotrzebowanie systemu mikroprocesorowego na energię. Inną sprawą jest to, że w przypadku naszego mikrokontrolera nie mam już wolnych portów I/O, by podłączyć niezbędny rezonator kwarcowy, w związku z czym należałoby zastosować element o większej liczbie wyprowadzeń, co z kolei nie jest pożądane z uwagi na wymiary obwodu drukowanego. Kolejną możliwością jest użycie jakiegoś sprzętowego, szeregowego medium transmisyjnego wbudowanego w mikrokontroler (USART lub SPI), ale i w takim przypadku nie uciekniemy od konieczności zwiększenia częstotliwości taktowania mikrokontrolera. Tyle w kwestiach implementacyjnych, w zawiązku z czym przejdźmy do zagadnień montażowych.
Montaż i uruchomienie
Schemat płytki PCB naszego urządzenia pokazano na rysunku 5. Jak widać, zaprojektowano bardzo zgrabną, dwustronną płytkę drukowaną ze zdecydowaną przewagą elementów THT montowanych po obu stronach laminatu (po stronie BOTTOM montowane są wyłącznie elementy SMD). Montaż urządzenia rozpoczynamy od warstwy BOTTOM, gdzie w pierwszej kolejności montujemy półprzewodniki, zaś w kolejnym kroku elementy bierne. W tym momencie przechodzimy na warstwę TOP, gdzie, podobnie jak poprzednio, w pierwszej kolejności montujemy diody LED RGB, następnie pozostałe elementy półprzewodnikowe (z wyłączeniem wyświetlacza), a na końcu elementy bierne (z wyłączeniem buzzera piezoelektrycznego) oraz elementy mechaniczne w postaci koszyczka baterii AAA czy przycisków funkcyjnych. Na koniec, do tak przygotowanego obwodu drukowanego, montujemy wyświetlacz LCD, lutując jego wyprowadzenia w przeznaczone do tego celu punkty lutownicze, co zapewni mu zarówno niezbędne połączenia elektryczne, jak i pożądaną stabilizację mechaniczną.
Na fotografii 1 pokazano zmontowane urządzenie od strony warstwy TOP tuż przed przylutowaniem wyświetlacza LCD (wersja prototypowa, przed niewielkimi modyfikacjami), zaś na fotografii 2 to samo urządzenie od strony warstwy BOTTOM (pokazano wyłącznie ten obszar obwodu drukowanego, gdzie montowane są elementy SMD i to przed niewielkimi modyfikacjami sekcji zasilania).
Obsługa
Projektując system Menu urządzenia e-Nurse oraz sposób jego obsługi, przyjąłem, że ergonomia i prostota jego użytkowania i czytelność interfejsu użytkownika powinny być najważniejszym kryterium przy konstruowaniu stosownych procedur sterujących. Zgodnie z tymi podstawowymi założeniami, na płytce sterownika przewidziano wyłącznie 4 przyciski funkcyjne pozwalające na poruszanie się po dostępnych ekranach Menu. Mówiąc zupełnie ogólnikowo, przyciski oznaczone jako NEXT, PREV (symbolizowane przez stosowne symbole strzałek) służą do zmiany typu edytowanego elementu, zaś przyciski oznaczone jako UP, DOWN (symbolizowane przez stosowne symbole strzałek) służą do zmiany wartości edytowanego elementu oraz poruszania się po kolejnych opcjach Menu urządzenia.
W ramach systemu Menu dostępne są 2 ekrany użytkownika: ekran Menu głównego oraz ekran Menu ustawień. Poniżej, na rysunku 6, pokazano ekran Menu głównego urządzenia e-Nurse, zaś na rysunku 7 pokazano ekran Menu ustawień naszego urządzenia. Jak widać, w ramach Menu głównego dostępna jest informacja o bieżącej dacie i godzinie systemowej oraz informacja o aktywnych alarmach (aktywność alarmu pokazywana jest w postaci wypełnienia wybranego symbolu "przegródki"). Z tego poziomu dokonujemy potwierdzenia przyjęcia dawki leku, co skutkuje skasowaniem alarmu, posiłkując się przy tym przyciskami funkcyjnymi umożliwiającymi poruszanie się po liście ikonek alarmów (przesuwającymi podkreślenie konkretnego symbolu "przegródki") oraz ich kasowanie. Kasowanie może przebiegać w dwojaki sposób: długie przyciśnięcie przycisku DOWN kasuje jeden alarm dla danej "przegródki", zaś długie przyciśnięcie przycisku UP kasuje wszystkie alarmy dla tejże "przegródki". Dodatkowo, długie przyciśniecie przycisku NEXT wprowadza nas w tryb Menu ustawień.
Tryb Menu ustawień pozwala nam na dokonanie ustawień zarówno bieżącej godziny i dnia tygodnia, jak i skonfigurowanie ustawień dla każdej "przegródki" oznaczonej tu jako "Slot". Ustawienia, o których mowa powyżej, to: aktywność "przegródki" (gdyż można ją wstępnie skonfigurować, ale jeszcze nie aktywować), dzienna liczba dawek leku (1…5), godziny przyjmowania leku (dowolne z zakresu 0…23) oraz dni tygodnia przyjmowania leku. Poruszanie się pomiędzy elementami poddawanymi edycji, jak i ich edycję umożliwiają krótkie naciśnięcia przycisków funkcyjnych, zaś poruszanie się pomiędzy wierszami opcji Menu ustawień umożliwiają długie naciśnięcia przycisków UP i DOWN.
Wyjście z Menu ustawień i przejście do Menu głównego dokonywane jest poprzez długie naciśnięcie przycisku NEXT. Procesowi temu towarzyszy zapisanie bieżących ustawień urządzenia (ustawień "przegródek") w nieulotnej pamięci EEPROM mikrokontrolera oraz aktualizacja czasu zegara RTC. Do czasu wyjścia z Menu ustawień niemożliwe jest automatyczne przejście urządzenia do stanu uśpienia w przypadku braku aktywnych alarmów. Z kolei, proces sprawdzania alarmów i sygnalizacja ich wystąpienia (za pomocą wbudowanych diod LED RGB oraz buzzera) dokonywana jest również w czasie konfiguracji urządzenia (w Menu ustawień). Na rysunku 8 pokazano kompletny diagram systemu Menu urządzenia e-Nurse wraz ze sposobem jego obsługi.
Obudowa
I na sam koniec obiecany "ekstras". Gotowy panel obudowy "przegródek" wykonany w aplikacji do projektowania 3D przez mojego, zawsze niezawodnego, Kolegę Bartłomieja Wawrzyszko. Element ten zaprojektowany został w taki sposób, by po jego przykręceniu do obwodu drukowanego (4 śruby ∅ 3 mm maks.) stanowił kompletne rozwiązanie dozownika leków zbliżone swoim wyglądem do prostych kasetek na leki dostępnych w aptekach czy drogeriach. Co więcej, w bocznych ściankach elementu (trzech z czterech) przygotowano specjalne wcięcia do wsunięcia w nie przezroczystej pleksi o grubości do 1,5 mm i wymiarach 68×80 mm tak, by stanowiła ona pokrywę dozownika uniemożliwiającą wypadnięcie leków ze swoich "przegródek". Prawda, że fajne?
Na rysunku 9 pokazano rysunek techniczny panelu "przegródek" (z zaznaczeniem ważniejszych wymiarów), zaś na rysunku 10 pokazano z kolei model 3D tegoż elementu.
Plik produkcyjny do wykonania wyżej wymienionej obudowy udostępniony jest w materiałach dodatkowych do artykułu.
Robert Wołgajew, EP