Aby zastosować wyświetlacz kolorowy TFT, trzeba go sterować za pomocą przeznaczonego do tego celu sterownika lub mikrokontrolerem z odpowiednimi układami peryferyjnymi. Jak wiemy, biblioteka graficzna wspiera sporą liczbę sterowników i może być stosowana z jednym mikrokontrolerów rodziny PIC24 lub PIC32 w roli sterownika wyświetlacza TFT. Ponieważ w module MEB jest używany sterownik SSD1926, to na początku najbardziej naturalnym wydaje się użycie wyświetlacza TFT z takim sterownikiem. Niestety, po sprawdzeniu oferty krajowych dystrybutorów nie znalazłem takiego wyświetlacza. Sięgnąłem po raz kolejny do dokumentacji biblioteki i znalazłem informację, że możliwe jest jej proste skonfigurowanie do pracy z alternatywnym sterownikiem S1D13517 firmy Epson. I tu również poszukiwania nie dały pozytywnego wyniku. Pozostało znalezienie wyświetlacza TFT ze sterownikiem z listy wspieranych przez bibliotekę lub w ostateczności z dowolnym sterownikiem i napisanie własnych procedur warstwy Device Driver Layer. To ostatnie, przy pewnym doświadczeniu, nie powinno być specjalnie trudne.
W trakcie dalszych poszukiwań kupiłem na aukcji wyświetlacz TFT o rozdzielczości 240×320 pikseli, z układem SSD1289 będącym na liście wspieranych sterowników. Dodatkowo, wyświetlacz był wyposażony w podświetlenie LED, rezystancyjny panel dotykowy ze sterownikiem XPT2046 oraz złącze dla karty SD. Jeżeli dodamy do tego cenę ok. 70 złotych, to wydaje się on idealny do stosowania we własnych urządzeniach. Niska cena być może będzie skutkowała gorszymi parametrami matrycy, ale to nie jest przesądzone.
Sterowanie wyświetlaczem – podstawowe funkcje warstwy Device Driver Layer
Biblioteka graficzna jest przeznaczona do używania z mikrokontrolerami rodzin PIC24 i PIC32. Do testów z wyświetlaczem użyłem modułu PIC32 USB Stater kit II z mikrokontrolerem PIC32MX795F512L. Jest to dokładnie taki sam moduł, jaki sterował wyświetlaczem w module Multimedia Expansion Board. Do połączenia modułu z wyświetlaczem jest potrzebna specjalna płytka-przejściówka Starter Kit I/O Expansion Board. Oczywiście, taki zestaw nie jest niezbędny. Wyprowadzenia wyświetlacza można połączyć z płytką mikrokontrolera w dowolny sposób.
Na płytce wyświetlacza jest zamontowane złącze szpilkowe IDC40. Każdy z pinów jest opisany (fotografia 1), więc wykonanie połączeń nie powinno sprawiać problemu. Sterownik SST1289 jest produkowany przez firmę Solomon Sysytech i przeznaczony do sterownia kolorowymi matrycami TFT o rozdzielczości 320×240 pikseli RGB i 18-bitowej głębi kolorów. Jest wyposażony w pamięć obrazu RAM o pojemności 172800 bajtów (240×320×18/8). Sterownik ma również wbudowaną przetwornicę DC-DC wytwarzającą napięcie do zasilania driverów matrycy LCD. Zapisywanie i odczytywanie pamięci obrazu oraz rejestrów konfiguracyjnych umożliwiają równoległe interfejsy pracujące w standardzie Motorola 6800 lub Intel 8080 oraz szybki interfejs szeregowy SPI. Interfejs komunikacyjny wybiera się poprzez wymuszenie kombinacji poziomów na wejściach konfiguracyjnych. Producent panelu wyświetlacza może udostępnić te wejścia lub nie. W naszym wypadku interfejs jest ustalony na stałe i nie można go zmienić. Jest to 16-bitowa, równoległa magistrala danych z sygnałami sterującymi w standardzie Intel 8080.
W pliku drvTFT2.c znajdziemy parę niezbędnych procedur, ale nie ma funkcji komunikacji poprzez magistralę równoległą. Microchip ma do sterowania interfejsami równoległymi specjalny moduł peryferyjny PMP (Parallel Master Port). Może on pracować z 8- lub 16-bitową szyną danych i jest elastycznie konfigurowany. Ja do celów testowych nie użyłem PMP, a zastosowałem programową emulację standardu Intel 8080 z 16-bitową magistralą danych.
Programowe integrowanie biblioteki z wyświetlaczem zaczniemy od napisania procedur emulujących magistralę równoległą oraz zapisywania oraz odczytywania danych z/do pamięci obrazu i oraz rejestrów sterujących. Procedury te zostaną umieszczone w pliku drvTFT2.c. Zanim to zrobimy, trzeba będzie zdefiniować środowisko sprzętowe. Tradycyjnie w projektach Microchipa jest do tego przeznaczony plik HardwareProfile.h. W pliku wygenerowanym przez GDDX należy zawrzeć definicje, jak na listingu 1.
Definicja sprzętu zostanie umieszczona w pliku MY_BOARD.h. Jest to zmodyfikowany do potrzeb testów plik konfiguracyjny pierwotnie przeznaczony dla modułu MEB (listing 2). Znajdziemy tu definicje stałych odpowiednich dla użytego sterownika i definicje linii sterujących magistrali równoległej.
Magistrala Intel 8080 składa się z linii danych i linii sterujących (opcjonalnie z linii adresowych). Linie sterujące to !WR (zapis) i !RD (odczyt). Cykl zapisu danych rozpoczyna się od wyzerowania linii !WR, a następnie wystawienia ważnych danych na magistrali. Narastające zbocze sygnału !WR zapisuje dane do rejestru sterownika. Interfejs sterujący jest uzupełniony o 2 dodatkowe linie: wyboru interfejsu CS i źródła zapisywanych danych D/!C ( w opisie wyprowadzeń wyświetlacza ta linia nazywa się RS). Jeżeli linia D/!C jest ustawiona, to jest zapisywana pamięć danych, jeżeli wyzerowana, to jest zapisywany rejestr (adres rejestru). Na rysunku 3 pokazano przebiegi czasowe w czasie zapisu danych. Na listingu 3 pokazano procedurę zapisu danej (zaadresowanej komórki pamięci obrazu), a na listingu 4 zapisu rejestru sterownika.
Mikrokontroler jest taktowany z częstotliwością 80 MHz, a układy peryferyjne – w tym linie portów – z częstotliwością 40 MHz. Aby zapewnić poprawne zbocza sygnałów sterujących, po każdej zmianie ich poziomu jest wykonywanych klika rozkazów NOP (dodatkowe opóźnienie). W czasie zapisu danych linia !RD musi być ustawiona. Odczytywanie danych również nie jest skomplikowane. Na listingu 5 pokazano procedurę odczytu słowa danych z pamięci obrazu.
Każdy sterownik wyświetlacza graficznego wymaga inicjalizacji polegającej na zapisaniu rejestrów konfiguracyjnych. W sterowniku SSD1289 zapisywanie rejestrów polega na wykonaniu cyklu zapisu adresu, a po nim cyklu zapisu 16-bitowej danej (listing 6). Kompletny cykl przykładowej inicjalizacji pokazano na listingu 7.
Opisywanie tutaj wszystkich rejestrów sterujących nie jest konieczne. Te dane można sobie znaleźć w dokumentacji sterownika i na ich podstawie zobaczyć jak jest wykonywana inicjalizacja. W Internecie można znaleźć sekwencje inicjalizacji SSD1289 różniące się nieco do tej z list. 7. W trakcie testów wypróbowałem kilka alternatywnych i efekt działania był taki sam. Zainicjowany wyświetlacz jest gotowy do wyświetlania zawartości pamięci obrazu.
W dokumentacji biblioteki, w części poświęconej dodawaniu nowego sterownika jest opisany zestaw funkcji warstwy Device Driver Layer niezbędnych dla prawidłowego działania wyświetlacza. Ponieważ SSD1289 jest wspierany przez bibliotekę, to teoretycznie wszystkie funkcje powinny być gotowe. W praktyce jednak konieczne były zmiany w kilku kluczowych procedurach. Jedną z najważniejszych procedur w tej warstwie jest z „zapalenie” jednego piksela matrycy – PutPixel. Każdy piksel ma dwa podstawowe atrybuty: adres (wyznaczany na postawie współrzędnych x, y) oraz kolor. Standardowa procedura PutPixel ma dwa argumenty: współrzędną x i współrzędną y ( listing 8). Kolor piksela jest zawarty w zmiennej globalnej _color. Przed ustaleniem adresu i zapisaniem danej sprawdzane jest czy współrzędne x i y nie mają wartości spoza dopuszczalnego zakresu określonego przez wielkość matrycy. Procedurę SetAdress pokazano na listingu 9. Zależnie od przyjętej orientacji (pionowa lub pozioma) współrzędne są zapisywane zamiennie komendami o adresie 0x4e, lub 0x4f. Procedurę kończy wysłanie komendy 0x22 (Write data to GRAM). Funkcja Write-Piksel to makro:
#define WritePixel(color)
{LcdWriteData(color);}
Jest ona używana w dwóch kolejnych ważnych procedurach: czyszczącej ekran ClearDevice() – listing 10 oraz rysującej prostokąt Bar() – listing 11. Obie procedury w wersji oryginalnej nie działały poprawnie. Szczególnie Bar() przysporzyła mi trochę kłopotów. Wynikały one z tego, że biblioteka ma możliwość konfigurowania orientacji, a ja przyjąłem, że wyświetlacz będzie zorientowany poziomo. Oryginalna procedura z drvTFT2.c nie chciała w tej orientacji pracować poprawnie. Na początku funkcji Bar() następuje sprawdzenie czy współrzędne wierzchołków prostokąta nie mają wartości większych, niż wynika to z wielkości matrycy wyświetlacza. Po napisaniu i uruchomieniu procedur komunikacji ze sterownikiem wyświetlacza, poprawnym zainicjowaniu sterownika i korekty procedur Bar i Clear-Device można by było w zasadzie przystąpić do prób działania biblioteki. Jednak GDD-X generuje projekt, w którym jest definiowana obsługa rezystancyjnego panelu dotykowego, którym to zajmiemy się później. Na tym etapie ta obsługa musiała być wyłączona przez usunięcie definicji #define USE_TOUCH-SCREEN, żeby projekt mógł się skompilować bez błędów. Nie są wtedy kompilowane funkcje obsługi paneli z pliku TouchScreen.c.
Graphic Display Designer X – testy działania
Mamy już wszystko gotowe by rozpocząć testy działania biblioteki. W tym celu tworzymy projekt w środowisku MPALB X i uruchamiamy wtyczkę Graphic Display-Designer. Ponieważ jeszcze nie mamy obsługi panelu dotykowego, to nie można testować działania interakcji z użytkownikiem. Testy będą polegały tylko na wyświetlaniu obiektów (głównie widżetów). W GDD-X utworzyłem projekt ekranu (rysunek 4) z umieszczonymi w nim bitmapą z logo Elektroniki Praktycznej oraz obiektami Meter i Button. Po skompilowaniu projektu i zaprogramowaniu mikrokontrolera te elementy zostały wyświetlone prawidłowo na ekranie wyświetlacza (rysunek 5).
Teraz nadszedł czas na ocenę jakości matrycy wyświetlacza. Pomimo niskiej ceny wyświetlany obraz jest kontrastowy i wyraźny. Nie widziałem zauważalnej różnicy pomiędzy tym wyświetlaczem, a wyświetlaczem z fabrycznego modułu MEB. Ekran wyświetla się bez dużych opóźnień. To zapewne zasługa szybkiego mikrokontrolera, ale też 16-bitowej magistrali równoległej (pomimo zastosowania emulatora programowego).
W tym momencie możemy uznać, że integracja wyświetlacza z biblioteką zakończyła się częściowym sukcesem. Częściowym, bo przed nami jeszcze ważny element: obsługa interfejsu dotykowego, pozwalająca na interakcję graficznego interfejsu z użytkownikiem.
Panel dotykowy – obsługa sterownika XPT2046
Jak już wspomniałem, wyświetlacz jest wyposażony w 4-przewodowy, rezystancyjny panel dotykowy. Taki panel może być obsłużony przez mikrokontroler z wbudowanym przetwornikiem analogowo-cyfrowym lub za pomocą wyspecjalizowanego kontrolera – tu jest to XPT2046. Biblioteka Microchipa wspiera obsługę za pomocą przetwornika (plik TouchScreenResistive.c) oraz specjalizowanego kontrolera AR1020 (plik AR1020.c). Jak widać, nie ma wsparcia dla XPT2046 i w zasadzie prawie identycznego układu ADS7843. Dlatego trzeba napisać obsługę od podstaw.
Zasada działania pomiaru zmiany rezystancji rezystancyjnego panelu dotykowego jest opisana w wielu źródłach. Można tez o tym poczytać w dokumentacji układu. XPT2046 komunikuje się z mikrokontrolerem z pomocą szeregowego interfejsu SPI. Podobnie jak przy komunikacji ze sterownikiem wyświetlacza, obsługa magistrali została wykonana programowo, bo w trakcie uruchamiania w programowych emulacjach jest łatwiej znaleźć błędy. Po uruchomieniu, kiedy wszystko działa prawidłowo, można zamienić programową obsługę na transmisję wykonywaną przez sprzętowe moduły komunikacyjne.
Z punktu widzenia XPT2046, magistrala SPI składa się z następujących linii: danych wyjściowych DOUT, danych wejściowych DIN, zegara taktującego DCLK oraz linii wyboru interfejsu CS. Interfejs komunikacyjny jest uzupełniony o linię sygnalizacyjną PENIRQ. Na listingu 12 pokazano definicję linii portów, a w tabeli 1 zamieszczono sposób połączenia mikrokontrolera z wyświetlaczem.
Po zdefiniowaniu linii trzeba je z zainicjować przez zdefiniowanie kierunku (wejście/ wyjście) oraz stanu początkowego linii – listing 13. Do komunikacji z układem potrzebne będą dwie procedury: zapisu 8-bitowej danej (listing 14) i odczytu 12-bitowej danej (listing 15). Do sterownika będziemy zapisywali komendy sterujące jego pracą, a potem odczytywali 12-bitową wartość rezystancji zmierzoną przez przetwornik A/C.
W trakcie uruchamiania transmisji napotkałem na nieoczekiwane problemy. Procedury zostały sprawdzone ze sprzętowym emulatorem modułu PIC32 Starter Kit i zgodnie z dokumentacją powinny działać prawidłowo. Jednak po normalnym uruchomieniu komunikacja nie działała. Obserwacja przebiegu transmisji na oscyloskopie cyfrowym pokazała, że przyczyną problemów były zbyt małe opóźnienia po zmianach stanów na liniach interfejsu SPI. Dodanie dodatkowego opóźnienia Delay() wyeliminowało ten problem.
Pomiar położenia naciskanego punktu powinien się rozpocząć po wykryciu momentu naciśnięcia folii panelu dotykowego. Wykrycie naciśnięcia można wykonać programowo testując wartości odczytane z modułu. XPT2046 ma wyprowadzenie PENIRQ, które w czasie naciśnięcia folii przechodzi w stan niski. Wystarczy testować stan PENIRQ by wiedzieć czy panel został przyciśnięty, czy nie. Ja skorzystałem z tego mechanizmu, ponieważ jest dużo prostszy w realizacji i pewny w działaniu.
Po wykryciu naciśnięcia można przystąpić do odczytania położenia miejsca nacisku. Jest ono identyfikowane przez współrzędne x oraz y. Jeżeli wyświetlacz ma rozdzielczość 240×320 pikseli, to x zmienia się w zakresie 0
Do odczytania współrzędnych są używane dwie funkcje: TouchGetXRAW() – listing 16 i TouchGetYRAW – listing 17. Obie podają zmierzone wartości rezystancji w rozdzielczości 12 bitowej, a więc ich wartości zmieniają się w zakresie 0...4095. Żeby uzyskać rzeczywiste współrzędne z zakresu 0
Na podstawie tej informacji zostały napisane 2 procedury odczytujące pomiary z XPT2046 i zwracające po przekonwertowaniu współrzędne x i y punktu dotyku na ekranie. Te procedury zostały pokazane na listingach 18 i 19. Na początku obu procedur jest sprawdzany stan wejścia PENIRQ i jeżeli jest on wysoki, to procedura kończy działanie i zwraca wartość -1. Jeżeli ten stan jest niski, to odczytywana jest wartość z XTP2046 dla konkretnych kanałów. Każda z procedur zwraca po konwersji swoja współrzędną.
Wróćmy teraz do biblioteki i projektu generowanego w GDD-X. Jak już wspomniałem, taki projekt, w którym wybrano MEB standardowo generuje pliki ze skonfigurowaną obsługą panelu rezystancyjnego za pomocą przetworników A/C mikrokontrolera. Musimy teraz go tak przekonfigurować, aby biblioteka korzystała z obsługi napisanej przez nas. Po pierwsze trzeba wyłączyć kompilację dla procedur umieszczonych TouchScreenResistive.c przez usuniecie definicji #define USE_TOUCHSCREEN_ RESISTIVE w pliku konfiguracji MY_BOARD.h. Obsługa panelu dotykowego sprowadza się głównie do funkcji void TouchGetMsg(GOL_MSG *pMsg) umieszczonej w pliku TouchScreen.c. Ta funkcja korzysta z wcześniej opisanych procedur TouchGetX() i TouchGetY(). Ponieważ po usunięciu #define USE_TOUCHSCREEN_ RESISTIVE nie będą funkcje z TouchScreenResistive.c, to projekt użyje naszych procedur. Funkcje TouchGetMsg na podstawie odczytanych współrzędnych x, oraz y modyfikuje składowe struktury GOL_MSG dotyczące obsługi panelu dotykowego.
Jak widać z powyższego opisu, żeby dodać własną obsługę panelu dotykowego trzeba utworzyć dwie procedury, TouchGetX() i TouchGetY(), zwracające współrzędne dotkniętego punktu i tak skonfigurować bibliotekę, aby z tych procedur korzystała. Zależnie od użytych rozwiązań sprzętowych, trzeba też zmodyfikować procedurę TouchInit inicjującą (w naszym przypadku) programowy interfejs SPI.
Testowanie działania ekranu dotykowego można szybko sprawdzić przez postawienie na ekranie obiektów wykorzystujących ekran dotykowy. Ja użyłem trzech: przycisku Button, suwaka Slider i pokrętła Round Dial (rysunek 8). Testy wykazały, ze wszystkie te obiekty współpracują prawidłowo z obsługa ekranu dotykowego, a ich działanie nie odbiega od działania takich samych obiektów w projekcie przeznaczonym dla firmowego modułu MEB.
Podsumowanie
Pokazałem jak można w praktyce zintegrować dostępny na rynku wyświetlacz LCD z panelem dotykowym z graficzna biblioteką firmy Microchip. Jak już wspomniałem wyświetlacz jest dostępny przynajmniej w czasie pisania tego artykułu i jego cena jest stosunkowo niska. W trakcie pracy nad artykułem miałem możliwość porównania efektów działania z wyświetlaczem modułu Multimedia Expansions Board. Oba wyświetlacze zachowywały się identycznie, poza szybkością rysowania niektórych obiektów. Nieznacznie szybszy był wyświetlacz ze sterownikiem SSD1926 modułu MEB. Wynika to najprawdopodobniej z różnicy w budowie sterowników. Poza tym nie starałem się optymalizować czasowo obsługi komunikacji z SS1289.
Integracja biblioteki z nowym sterownikiem, szczególnie wykonywana po raz pierwszy, nie jest zadaniem łatwym, chociaż być może na takie wygląda. Jest to spowodowane dość sporym skomplikowaniem projektu, dużą liczbą plików źródłowych i nagminnym stosowaniem kompilacji warunkowej. Przykładowe projekty Microchipa maja tę właściwość, że bardzo dobrze działają z modułami formowymi, ale ich modyfikacja dla innego sprzętu wymaga trochę wysiłku. W przypadku biblioteki graficznej naprawdę warto tę pracę wykonać, bo efekty są znakomite.
Tomasz Jabłoński, EP