O ile przy typowym multipleksowaniu n wyjść układu umożliwia sterowanie nie więcej niż (n/2)2 diod, to przy nharlieplexingu możliwe jest sterowanie n×(n–1) diod. Dysponując trzema wyjściami można sterować 6 diodami, 4 – 12 diodami, a 9 – np. 8-cyfrowym wyświetlaczem 7-segmentowym z kropkami. Oczywiście, ten sposób sterowania oprócz zalet ma też pewne wady. Główną z nich jest konieczność trójstanowego sterowania linii połączonych z diodami LED (a nie dwustanowego, jak przy zwykłym multipleksowaniu). Ponadto poprawne sterowanie matrycą wymaga zapewnienia odpowiednich własności elektrycznych wyjść sterujących, co przy użyciu w tym celu wyjść mikrokontrolera nie zawsze jest możliwe. Problemem dla początkujących programistów może być również nietrywialny sposób wyznaczania stanów wyjść niezbędnych do uaktywnienia wybranej diody matrycy, co jest potrzebne przy zamianie obrazu do wyświetlenia na dane sterujące wyświetlaczem.
Sterowanie elektryczne wyświetlacza charlieplexing
W celu zrozumienia zasady charlieplexingu warto zacząć od podstaw, czyli sposobu sterowania zwykłym wyświetlaczem multipleksowanym. W wyświetlaczu takim mamy dwa rodzaje wyjść sterujących – wyjścia wspólnych elektrod (wyboru cyfr lub wierszy matrycy) oraz wyjścia sterowania segmentów lub kolumn. Wyjścia wspólnych elektrod są wyjściami typu napięciowego – w idealnym przypadku powinny to być klucze o zerowej rezystancji; zwykle w tym charakterze używa się tranzystorów MOS. Wyjścia kolumn/segmentów powinny być sterowanymi źródłami prądowymi – w tym przypadku używamy albo prawdziwych źródeł prądowych dostępnych w specjalizowanych układach scalonych do sterowania diod LED, albo zwykłych wyjść układów logicznych lub wzmacniaczy tranzystorowych z rezystorami szeregowymi, stanowiących uproszczone i dalekie od doskonałości źródła prądowe. W odniesieniu do sterowanych w ten sposób matrycami używamy określeń typów konfiguracji „wspólna anoda” lub „wspólna katoda”, mając ma myśli elektrody diod LED sterowane z kluczy napięciowych. W wyświetlaczu ze wspólną anodą anody diod są sterowane przez klucze napięciowe, a katody – przez źródła prądowe.
Przy wyświetlaniu typu charlieplexing każde wyjście układu sterującego służy zarówno do sterowania wierszem, jak i kolumną matrycy. W przypadku pełnej matrycy każde z n wyjść steruje jednym wierszem i n–1 kolumnami matrycy, przy czym wyjście sterujące wierszem nie może równocześnie sterować żadną kolumną w tym wierszu.
Podstawowy schemat najprostszego wyświetlacza typu charlieplexing, z sześcioma diodami LED, przedstawiono na rysunku 1. Wyświetlacz taki jest multipleksowany trójfazowo, a w każdej fazie mogą być uaktywnione dwie diody LED.
W idealnym wypadku wyjście, które w danej fazie wyświetlania steruje wierszem, powinno pracować jako klucz napięciowy, a wyjścia sterujące kolumnami aktywnymi w danym wierszu – jako źródła prądowe. Wyjście sterujące kolumną nieaktywną w danej fazie jest całkowicie wyłączone – znajduje się ono w stanie wysokiej impedancji. W taki sposób są skonstruowane specjalizowane układy sterowników matryc działające w technice charlieplexing, np. z serii Maxim MAX695x lub Austria Microsystems AS11xx.
Celem tego artykułu jest przedstawienie sposobu ekonomicznego sterowania wieloma diodami LED bez użycia specjalizowanych układów – bezpośrednio z wyjść mikrokontrolera. Rozwiązanie takie jest szczególnie atrakcyjne, gdy np. musimy wysterować z mikrokontrolera kilka diod pełniących funkcję wskaźników, umieszczonych na oddzielnej płytce drukowanej przymocowanej do obudowy urządzenia, minimalizując przy tym liczbę połączeń pomiędzy płytką mikrokontrolera i płytką z diodami.
Ponieważ mikrokontrolery nie są zwykle wyposażone w wyjścia mogące pełnić równocześnie funkcję źródeł napięciowych i prądowych, warto przyjrzeć się sposobom elektrycznego sterowania matrycami LED umożliwiającym uzyskanie własności wyjść zbliżonych do idealnych, opisanych powyżej.
Typowo przy sterowaniu matrycy w konwencji charlieplexing używamy rezystorów szeregowych pomiędzy wyjściami mikrokontrolera i matrycą, ograniczających natężenie prądu płynącego przez diody matrycy. Odpowiada to zastosowaniu źródeł prądowych zarówno do wysterowania wierszy, jak i kolumn, co nie jest właściwe, gdyż jasność świecenia diod zależy wówczas od liczby diod zaświecanych równocześnie w wybranym wierszu. Pomimo niedoskonałości tego sposobu sterowania matrycą jest on często stosowany – w taki sposób jest skonstruowany np. popularny moduł LoL (Lots of Lights) w formacie zgodnym z Arduino (rysunek 2).
Nieco lepszym i równie tanim rozwiązaniem jest połączenie sterowania wierszami bezpośrednio do wyjść mikrokontrolera, a kolumn – przez rezystory. W ten sposób nie podwyższając kosztu elementów, uzyskujemy wysterowanie napięciowe wierszy i wysterowanie prądowe kolumn, dzięki czemu jasność diody nie zależy od liczby diod aktywnych w danym wierszu matrycy. W tym przypadku jednak rezystory muszą być umieszczone przy matrycy diod, a nie przy mikrokontrolerze, co nie zawsze jest możliwe. Rozwiązanie takie zastosowano np. w module KA-NUCLEO-MULTISENSOR do sterowania czterema diodami LED RGB.
Zasady programowego sterowania wyświetlaczem
Podobnie jak w przypadku klasycznego multipleksowania, wyświetlacz typu charlieplexing musi być odświeżany ze stałą częstotliwością, gwarantującą wrażenie ciągłości wyświetlanego obrazu (brak migotania). Jeżeli dopuszczamy możliwość ruchu obserwatora względem wyświetlacza, częstotliwość odświeżania nie powinna być niższa od 400 Hz. Górna wartość częstotliwości odświeżania jest uwarunkowana parametrami czasowymi elementów przełączających oraz – przy programowym sterowaniu wyświetlaniem – zajętością czasu procesora.
Maksymalne rozmiary matrycy wynikają z wydajności prądowej wyjść. Maksymalne natężenie prądu płynącego przez wyjście sterujące wierszem jest sumą natężeń prądów wyjść sterujących kolumnami. Stopnie wyjściowe spotykane w większości mikrokontrolerów są specyfikowane na maksymalne natężenie 20 mA. Oznacza to, że maksymalne natężenie prądu pojedynczej diody w matrycy sterowanej przez n wyjść nie może przekroczyć wartości 20 mA / (n – 1), czyli 7 mA dla matrycy 4×3 lub ok. 1,8 mA dla matrycy 12×11 (takiej jak w module LoL).
Odświeżanie następuje zawsze w procedurze obsługi przerwania timera. W porównaniu ze zwykłym multipleksowaniem mamy tu jednak istotną różnicę, polegającą na sposobie sterowania portami mikrokontrolera oraz przygotowaniu używanych do tego danych: zawartość wyświetlacza wpływa nie na poziomy logiczne wyjść sterujących, a na ich aktywację – wyjścia sterujące nieaktywnymi w danej fazie kolumnami pozostają w stanie wysokiej impedancji, a dane zapisywane do rejestrów wyjściowych nie zależą od zawartości wyświet-
lacza i są zawsze takie same dla danego wiersza matrycy. W przypadku konfiguracji ze wspólną anodą wyjście sterujące wierszem jest na poziomie wysokim, wyjścia sterujące kolumnami – na poziomie niskim, przy czym wyjścia sterujące kolumnami aktywnymi są włączone, a nieaktywnymi – wyłączone.
Podczas przełączania wierszy matrycy w obsłudze przerwania timera należy kolejno:
- Wyłączyć sterowanie liniami portów, ustawiając je w stan wysokiej impedancji (np. poprzez skonfigurowanie ich jako wejść).
- Ustawić w rejestrze wyjściowym nowy stan linii, niezależny od wyświetlanego obrazu i wynikający jedynie z fazy wyświetlania (wybranego wiersza).
- Włączać sterowanie wyjścia wyboru wiersza oraz wyjść sterowania a tymi kolumnami, które w danym wierszu mają być aktywne (zaświecone).
Wygodnie jest, gdy wszystkie linie sterujące wyświetlaczem są wyprowadzone z jednego portu GPIO mikrokontrolera – ułatwia to tworzenie programu sterującego wyświetlaczem.
Charlieplexing w STM32
W mikrokontrolerach rodziny STM32 do sterowania trybem pracy linii portu służy rejestr MODER, w którym każdej linii portu odpowiadają dwa kolejne bity. W celu wyłączenia sterowania liniami i ustawienia stanu wysokiej impedancji należy zaprogramować linię jako wejście cyfrowe lub analogowe, co odpowiada kombinacjom 00 lub 11. W celu włączenia sterowania cyfrowego linii należy ustawić ją jako wyjście GPIO, zapisując na odpowiednie pozycje rejestru MODER kombinację 01.
Wyświetlany obraz jest przygotowywany w pamięci w postaci, w której każdy bit odpowiada stanowi pojedynczej diody. Dla ułatwienia tworzenia obrazu wygodnie jest zorganizować dane w postaci odpowiadającej geometrii wyświetlacza. Postać ta w przypadku charlieplexingu nie ma prostego odwzorowania w wartości danych potrzebnych do sterowania wyświetlaczem, dlatego przy każdej zmianie wyświetlanego obrazu należy przygotować nowe dane do sterowania wyświetlaczem, przechowywane w oddzielnym wektorze, którego każdy element odpowiada fizycznemu wierszowi matrycy diod wyświetlacza. Warto zwrócić uwagę, że rozmieszczenie diod nie musi mieć prostego związku ze sposobem ich połączenia, więc organizacja danych sterujących wyświetlaczem jest zupełnie inna od organizacji danych reprezentujących obraz, a zaprojektowanie postaci tych danych i fragmentów kodu przekształcającego obraz w dane sterujące nie jest zadaniem trywialnym.
Dwa przedstawione poniżej przykłady pokazują sterowanie czterema diodami RGB (czyli dwunastoma diodami) przy użyciu czterech linii oraz sterowanie matrycą 126 diod przy użyciu 12 linii. Oba przykładowe programy zostały napisane dla mikrokontrolera STM32L476, umieszczonego na płytce Nucleo-64.
Projekty z przykładami są zawarte w archiwum ep_l476_cpx.zip.
Konfigurowanie taktowania mikrokontrolera
Mikrokontroler STM32L476 jest w obu przykładowych projektach taktowany częstotliwością 80 MHz, uzyskaną przez powielenie częstotliwości 4 MHz pochodzącej z wewnętrznego generatora MSI. W celu uzyskania takiego taktowania należy kolejno:
- skonfigurować parametry głównej pętli PLL dla uzyskania częstotliwości 80 MHz z częstotliwości wejściowej 4 MHz, pochodzącej z MSI,
- skonfigurować parametry dostępu do pamięci Flash odpowiednie dla docelowej częstotliwości pracy,
- poczekać na synchronizację PLL i włączyć taktowanie mikrokontrolera z PLL.
Wymienione powyżej czynności są wykonywane w obu programach na początku funkcji main(). Ponieważ maksymalne dozwolone częstotliwości pracy wszystkich szyn wewnętrznych mikrokontrolera L476 wynoszą 80 MHz, nie ma potrzeby konfigurowania dzielników częstotliwości poszczególnych szyn – wszystkie peryferiale, w tym timery, są taktowane częstotliwością 80 MHz.
Przykład 1 – Sterowanie czterech diod RGB przy użyciu czterech wyjść mikrokontrolera
Pierwszy przykład demonstruje sterowanie diod RGB umieszczonych na płytce KA-NUCLEO-MULTISENSOR umieszczonej w złączach Morpho, zawartych na płytce Nucleo-64. Schemat połączenia diod przedstawia rysunek 3. Diody są sterowane z czterech linii portu GPIOB. Program został uruchomiony na płytce L476RG-Nucleo, zawierającej mikrokontroler STM32L476RGT.
Program, przedstawiony na listingu 1, animuje sekwencję, w której kolejno zaświecane są na poszczególnych pozycjach trzy kolory podstawowe. Plik nagłówkowy ka_nuc_multis.h zawiera definicje zasobów płytki, w tym linii portów, do których jest podłączony wyświetlacz. Ponadto w skład projektu wchodzą trzy własne pliki zawierające definicje przydatne przy programowaniu mikrokontrolerów rodziny STM32. Stanowią one uzupełnienie pliku definicji zasobów mikrokontrolera dostarczonego przez producenta.
Po każdej modyfikacji obrazu następuje przygotowanie danych do sterowania wyświetlaczem. Mamy to do czynienia z multipleksowaniem 4-fazowym, więc dane sterujące składają się z 4 słów 32-bitowych, zawierających wartości wpisywane do rejestru MODER portu GPIOB w poszczególnych fazach wyświetlania.
Dane do wyświetlania są przygotowywane w postaci 12 bitów 16-bitowego argumentu, przekazywanego do funkcji cpx_encode(). Każde kolejne trzy bity odpowiadają stanowi trzech składowych jednej diody RGB. Funkcja cpx_encode() najpierw zeruje elementy wektora sterującego wyświetlaniem, a następnie wpisuje do nich przy użyciu operacji sumy logicznej maski odpwiadające aktywacji diod, które mają być zaświecone.
Do przekodowania obrazu na postać odpowiadającą zawartości rejestru MODER służy 12-elementowy wektor, którego każdy element zawiera maskę bitową służącą do aktywowania sterowania wiersza i kolumny sterujących daną diodą. Każde kolejne trzy elementy wektora odpowiadają jednej diodzie RGB o wspólnej anodzie i mogą służyć do uzyskania maski dla jednej fazy wyświetlania, sterującej świeceniem wszystkich składowych danej diody RGB.
Do odświeżania wyświetlacza i animacji jego zawartości użyto przerwania timera SysTick, zgłaszanego z częstotliwością 1600 Hz. W przerwaniu następuje kolejno:
- deaktywacja wyjść sterujących wyświetlaniem,
- ustawienie stanów logicznych wyjść dla kolejnej fazy wyświetlania,
- włączenie wyjść sterujących diodami, które mają być zaświecone.
Co jedną sekundę wyświetlany obraz jest zmieniany, a nowy wzorzec zaświeconych diod jest zamieniany na słowa sterujące dla rejestru MODER poprzez wywołanie funkcji cpx_encode().
Przykład 2 – Sterowanie matrycy 126 diod przez 12 wyjść mikrokontrolera
Drugi przykład demonstruje sterowanie matrycy 126 diod umieszczonej na płytce KAMduino LoL, zgodnej elektycznie z popularnym modułem Arduino LoL. Matryca zawiera 9 wierszy po 14 diod, jednak jej organizacja elektryczna jest zupełnie inna – diody są połączone w 12 wierszy zawierających po 11 lub 9 diod. Organizacja sterowania nie ma więc w tym przypadku prostego odwzorowania w geometrii wyświetlacza, co komplikuje projekt oprogramowania.
Matryca jest sterowana z linii D2..13 złącza Arduino. Dodatkowym problemem jest fakt, że linie te są na płytce Nucleo-64 dołączone do trzech portów GPIO: PA, PB i PC. Projekt struktur danych służących do sterowania wyświetlaczem wymaga starannego przemyślenia.
Wygenerowany obraz jest przechowywany w 9-elementowym wektorze słów 16 bitowych lol_img[], w którym każde słowo reprezentuje jeden wiersz wyświetlacza. Ponieważ mamy do czynienia z multipleksowaniem 12-fazowym, dane sterujące wyświetlaczem mają postać tablicy dwuwymiarowej moder_val, złożonej z dwunastu 3-elementowych wektorów słów 32-bitowych, przesyłanych w kolejnych fazach do rejestrów MODER portów GPIOA, GPIOB i GPIOC. Zamiana obrazu bitowego na wartości sterujące rejestrami MODER wymaga zdefiniowania kilku dodatkowych struktur danych i wyliczeń enum ułatwiających dostęp do nich.
Wyliczenie gpio_ służy do intuicyjnego indeksowania portów GPIO. Wektor portmap[] zawiera adresy portów GPIO. Wyliczenie dind_ definiuje symbole dla linii D2..13 złącza Arduino. Wektor ard_pinmap zawiera struktury złożone z indeksów i numerów linii portów GPIO, odpowiadających liniom D2…D13 Arduino. Dwuwymiarowa tablica lolmap zawiera struktury określające numery linii Arduino sterujących anodą i katodą każdej z diod matrycy. Opisuje ona odwzorowanie pikseli matrycy obrazu w położenie diody w matrycy sterowania.
Fragment kodu odpowiedzialny za przekodowanie obrazu pobiera z tablicy lolmap numery linii Arduino, a następnie używa ich do zaindeksowania wektora ard_pinmap w celu wyliczenia masek bitowych dla rejestrów MODER i umieszczenia ich we właściwych elementach tablicy moder_val.
Program, przedstawiony na listingu 2, animuje obraz przesuwających się na wyświetlaczu strzałek. Podobnie. jak w poprzednim przykładzie, do odświeżania wyświetlacza i animacji jego zawartości użyto przerwania timera SysTick,. Tym razem jednak z powodu znacznie większej liczby faz multilpleksowaniam jest ono zgłaszane z częstotliwością 4000 Hz. W przerwaniu następuje kolejno:
- deaktywacja wyjść portów PA, PB i PC sterujących wyświetlaniem,
- wyzerowanie wyjść,
- włączenie wyjść sterujących wierszem i kolumnami, które mają być zaświecone,
- ustawienie wyjścia sterującego wierszem.
Obraz do wyświetlenia jest zmieniany co 1 sekundę, a nowy wzorzec zaświeconych diod jest przetwarzany do postaci tablicy słów sterujących dla rejestrów MODER po zakończeniu najbliższego pełnego cyklu odświeżania. W ten sposób unikamy artefaktów, które mogłyby powstawać, gdyby zmiana obrazu nie była zsynchronizowana z cyklem odświeżania wyświetlacza.
Grzegorz Mazur