Popularny post Elvis Napisano Styczeń 27, 2020 Popularny post Udostępnij Napisano Styczeń 27, 2020 W poprzedniej części zobaczyliśmy jak działa wyświetlacz TFT podłączony do Arduino Uno. Wiemy już, że nie jest to demon szybkości, czas przeanalizować nieco dokładniej przyczyny takiego, a nie innego działania. Ta część będzie nieco bardziej techniczna od poprzedniej, znajdziemy w niej więcej obliczeń oraz odniesień do dokumentacji. Niestety pewna dawka "teorii" będzie konieczna dla zrozumienia działania wyświetlacza oraz poprawienia wydajności. Spis treści: Sterowanie wyświetlaczem TFT - część 1 - wstęp, podstawowe informacje Sterowanie wyświetlaczem TFT - część 2 - analiza problemu Sterowanie wyświetlaczem TFT - część 3 - testy prędkości na STM32 Sterowanie wyświetlaczem TFT - część 4 - własny program Sterowanie wyświetlaczem TFT - część 5 - optymalizacja programu Dlaczego to tak wolno działa? Na początek coś prostego, czyli oszacowanie ilości danych koniecznych do przesłania. Dla porównania zacznijmy od wyświetlacza alfanumerycznego, takiego jak był używany w kursie Arduino. Wyświetlacz posiada dwa wiersze po 16 znaków, a każdy znak jest przesyłany jako bajt. Mamy więc razem 32 bajty danych. Mnożąc przez 8 bitów w każdym bajcie otrzymujemy więc 256 bitów. W kursie STM32 F1 używany był graficzny wyświetlacz o rozdzielczości 84 na 48 pikseli. Każdy piksel mógł być albo zapalony, albo wygaszony, więc do jego sterowania wystarczał jeden bit. Mamy więc 84 x 48 = 4032 bity, czyli inaczej 504 bajty danych. Ten wpis brał udział konkursie na najlepszy artykuł o elektronice lub programowaniu. Sprawdź wyniki oraz listę wszystkich prac » Partnerem tej edycji konkursu (marzec 2020) był popularny producent obwodów drukowanych, firma PCBWay. Teraz czas na bohatera tego artykułu - domyślna biblioteka używa trybu 16-bitowego koloru, a rozdzielczość ekranu to 160 na 128 pikseli. Mnożymy więc 160 x 128 x 16 i uzyskujemy wynik 327680 bitów, albo 40960 bajtów. Z tych ogólnych rachunków łatwo wywnioskować, że aby wyświetlić obraz na naszym ekranie TFT musimy przetworzyć 1280 razy więcej danych niż w przypadku wyświetlacza alfanumerycznego. To chyba jest najprostsza i najbardziej ogólna odpowiedź na pytanie dlaczego jest wolno... Oczywiście nie jest to pełna odpowiedź, ale mam nadzieję że pokazuje ona o ile trudniej jest szybko wysterować wyświetlacz TFT. Jak szybko powinien działać nasz program? Poprzednio zmierzyliśmy czas rysowania ekranu i wyszło nam ponad 1200 ms, kasowanie zawartości zajmowało ok. 96 ms. Spróbujmy teraz oszacować jak szybko "powinien" nasz program działać, albo raczej jaka jest granica związana z prędkością interfejsu SPI. W konfiguracji interfejsu widzimy linijkę: SPI.setClockDivider(SPI_CLOCK_DIV2); A jak wiemy Arduino UNO jest taktowane z częstotliwością 16 MHz. Dzieląc przez dwa, otrzymujemy 8 MHz dla interfejsu SPI, co daje 8'000'000 bitów na sekundę. Skoro dane obrazu wymagają 327800 bitów, więc teoretycznie przesłanie danych zajmuje 327800 / 8000000 = 0,041 s, czyli 41 ms. Taki czas dawałby nam przyzwoite 24 klatki na sekundę, ale nawet kasowanie ekranu działa ponad dwa razy wolniej niż powinno, o rysowaniu nawet lepiej nie wspominać. Analiza procedury kasowania ekranu Zacznijmy od podłączenia analizatora stanów logicznych i upewnienia się, że wszystko działa jak oczekujemy. Na początek pomiar czasu kasowania ekranu - łatwo go zmierzyć, bo wysyłamy kod koloru białego czyli same bajty 0xff: Na razie wszystko wygląda zgodnie z oczekiwaniami. Sprawdźmy więc, czy na pewno SPI pracuje z częstotliwością 8 MHz. Częstotliwość się zgadza, ale niemiłym zaskoczeniem są przerwy między wysyłanymi bajtami danych: Niestety, ale między wysłaniem kolejnych bajtów procesor musi mieć trochę czasu - na odczyt statusu zakończenia transmisji, powrót z procedury, załadowanie kolejnej danej, a to wszystko trwa. Optymalizacja programu jest oczywiście możliwa, ale wymagałaby rezygnacji z użycia klasy SPI dostarczanej przez Arduino oraz chyba wskazane byłoby dodanie wstawek w asemblerze. W każdym razie taka optymalizacja nie jest prostym zadaniem, to niewątpliwie ciekawe wyzwanie, ale niekoniecznie przeznaczone dla początkujących. Na tą chwilę pozostaje się chyba pogodzić z tym, że maksymalna transmisja to nie 8 Mb/s, ale około połowa tej prędkości. To nadal całkiem niezły wynik, gdyby rysowanie zajmowało tyle samo czasu, co kasowanie mielibyśmy 10 klatek na sekundę. Zobaczmy więc dlaczego rysowanie jest tak strasznie powolne. Sterownik obrazu Nasz wyświetlacz wyposażony jest w kontroler ST7735S, poprzednio podawałem do niego link, ale na wszelki wypadek podam jeszcze raz. Zachęcam do zapoznania się z całą dokumentacją, ale chwilowo opiszę tylko absolutne minimum niezbędne z zrozumienia sterowania oraz poprawienia wydajności programu. Do komunikacji z ST7735S można użyć jednego z czterech interfejsów. Dostępne są dwa interfejsy równoległe (6800/8080) oraz dwa szeregowe, określane jako 3- i 4- przewodowy. W przypadku opisywanego modułu niestety nie ma wyboru i dostępny jest tylko ostatni, czyli "4-line Serial". Jak pisaliśmy wcześniej jest to SPI, ale nie do końca... co więcej nazewnictwo linii w module, standardzie SPI i dokumentacji sterownika jest inne. Poniżej przykładowy diagram przedstawiający komunikację: Linia CSX to odpowiednik linii CS zarówno w module wyświetlacza, jak i standardzie SPI. Nazwa SDA jest najczęściej używana w interfejsie i2c, ale tutaj oznacza po prostu linię danych, która na module ma nazwę DIN, a łączymy ją z pinem MOSI (Master-Output-Slave-Input). Warto pamiętać, że SDA może być używana też do odczytu danych... Zostańmy jednak przy jednokierunkowej transmisji. Kolejna linia to D/CX, na naszym module oznaczona jako DC - która nie jest częścią standardu SPI, więc pozostaje ją połączyć z pinem GPIO i sterować "ręcznie". Na koniec SCL, czyli linia zegara, na module dostępna jako CLK, a standardowo nazywana SCLK, albo SCK. Mamy więc straszny bałagan w nazwach, ale na koniec w sumie prostą sytuację - jednokierunkowy interfejs SPI oraz dodatkową linię DC. Jak widzimy transmisja polega na przesyłaniu 8-bitowych bajtów, czyli nic strasznego. Sterowanie ST7735S polega na przesyłaniu do niego komend. Pierwszy bajt zawiera wówczas kod komendy, a podczas jego przesyłania linia DC powinna być ustawiona w stan niski. Same kody dostępnych poleceń znajdziemy w dokumentacji sterownika, przykładowo: Po niektórych komendach przesyłane są parametry - podczas ich transmisji linia DC musi być w stanie wysokim. To ile parametrów jest potrzebnych znajdziemy w dokumentacji. Obsługiwanych komend jest dużo, na szczęście nie musimy ich wszystkich od razu poznawać. Dostarczony program przykładowy zawiera kod niezbędny do uruchomienia wyświetlacza, właściwie jedyne na co warto w nim zwrócić na początek uwagę to możliwość zmiany orientacji wyświetlacza. To bardzo wygodna opcja, dzięki niej nasz wyświetlacz może pracować zarówno w pionie (jak w przykładach z poprzedniej części), jak i w poziomie, co moim zdaniem ładniej wygląda. Ale najważniejsze, że wszystko jest wspierane sprzętowo, nie musimy więc programowo obracać obrazu. Przesyłanie danych obrazu Inicjalizacja wyświetlacza jest wykonywana tylko raz, więc w naszych amatorskich zastosowaniach jej optymalizacja może być mniej istotna. Warto natomiast zrozumieć jak wygląda przesyłanie obrazu. Do tego celu wykorzystywane są trzy komendy: CASET (0x2a), RASET (0x2b) oraz RAMWR (0x2c). Pierwsze dwie definiują wielkość okna, do którego będziemy zapisywać dane: Gdy przesyłamy CASET podajemy dwa parametry (każdy 2-bajtowy), które ustalają współrzędne x początku i końca okna, RASET ustala współrzędne y. Ustalenie wielkości okna jest konieczne przed rozpoczęciem zapisu danych. Do zapisu używamy komendy RAMWR: Po wysłaniu RAMWR możemy przesyłać już dane obrazu. Jak widzimy to nic specjalnego. Dlaczego więc kod przykładowy działa tak wolno? Teraz gdy wiemy już trochę więcej o działaniu ST7735S, możemy wrócić do kodu dostarczanego przez WaveShare. Na początek metoda LCD_ST7735S::LCD_SetWindows: void LCD_ST7735S::LCD_SetWindows( POINT Xstart, POINT Ystart, POINT Xend, POINT Yend ){ //set the X coordinates LCD_WriteReg ( 0x2A ); LCD_WriteData_8Bit ( 0x00 ); //Set the horizontal starting point to the high octet LCD_WriteData_8Bit ( (Xstart & 0xff) + sLCD_DIS.LCD_X_Adjust); //Set the horizontal starting point to the low octet LCD_WriteData_8Bit ( 0x00 ); //Set the horizontal end to the high octet LCD_WriteData_8Bit ( (( Xend - 1 ) & 0xff) + sLCD_DIS.LCD_X_Adjust); //Set the horizontal end to the low octet //set the Y coordinates LCD_WriteReg ( 0x2B ); LCD_WriteData_8Bit ( 0x00 ); LCD_WriteData_8Bit ( (Ystart & 0xff) + sLCD_DIS.LCD_Y_Adjust); LCD_WriteData_8Bit ( 0x00 ); LCD_WriteData_8Bit ( ( (Yend - 1) & 0xff )+ sLCD_DIS.LCD_Y_Adjust); LCD_WriteReg(0x2C); } Można oczywiście dyskutować, czy używanie "magicznych liczb" to elegancki sposób programowania, ale jak widzimy są tutaj wykonywane trzy, znane nam komendy: CASET, RASET oraz RAMWR. Więc po wywołaniu tej metody możemy już przesyłać dane obrazu (używając np. metody LCD_SetColor). To brzmi całkiem nieźle, ale teraz zobaczmy implementację rysowania punktu: void LCD_ST7735S::LCD_SetPointlColor ( POINT Xpoint, POINT Ypoint, COLOR Color ){ if ( ( Xpoint <= sLCD_DIS.LCD_Dis_Column ) && ( Ypoint <= sLCD_DIS.LCD_Dis_Page ) ){ LCD_SetCursor (Xpoint, Ypoint); LCD_SetColor ( Color , 1 , 1); } } void LCD_ST7735S::LCD_SetCursor ( POINT Xpoint, POINT Ypoint ){ LCD_SetWindows ( Xpoint, Ypoint, Xpoint , Ypoint ); } Czyli chcąc narysować jeden punkt najpierw ustawiamy okna o wielkości 1x1, a następnie zapisujemy dwa bajty koloru. Czyli jak łatwo policzyć na jeden piksel przesyłamy 13 bajtów. Pomijając niezbyt optymalny kod, to właśnie jest przyczyna tak strasznie powolnego rysowania. Każda linia, okręg, a nawet znak rysowane są jako punkty. A każdy z tych punktów wymaga aż 13 transmisji przez SPI (po których są jeszcze przerwy). To wszystko daje nam rysowanie demonstracyjnego ekranu w czasie ponad sekundy zamiast 40-90 ms. Co ciekawe kasowanie obrazu jest wykonywane optymalniej - raz ustawiane jest okno na cały ekran, a następnie po prostu przesyłane są wszystkie dane. Dlatego zajmuje to 96, a nie 1000 ms. Podsumowanie Mam nadzieję, że w tej części udało mi się pokazać dlaczego sterowanie wyświetlaczem działa wolniej niż moglibyśmy tego oczekiwać. Okazuje się, że kod dostarczany przez producenta to raptem demo. Jeśli chcemy sensownie sterować wyświetlaczem, musimy napisać własny program. Możliwości optymalizacji to między innymi: ograniczenie odrysowywanego obszaru zwiększenie prędkości komunikacji przez SPI (wyświetlacz obsługuje do 15MHz, wypadałoby również wyeliminować przerwy między wysyłanymi bajtami) o wiele wydajniejsze jest jednoczesne odrysowywanie okna, rysowanie punktów działa strasznie wolno Użyte rzez Waveshare rozwiazanie ma za to jedną, niezaprzeczalną zaletę - używa bardzo mało zasobów mikrokontrolera. Wszystkie dane obrazu są od razu wysyłane, więc za cenę prędkości ograniczyliśmy wykorzystanie pamięci. W kolejnej części przetestuję działanie przykładów dla STM32 oraz pokażę jak można przyspieszyć rysowanie obrazu - chociaż już nie na Arduino Uno. Spis treści: Sterowanie wyświetlaczem TFT - część 1 - wstęp, podstawowe informacje Sterowanie wyświetlaczem TFT - część 2 - analiza problemu Sterowanie wyświetlaczem TFT - część 3 - testy prędkości na STM32 Sterowanie wyświetlaczem TFT - część 4 - własny program Sterowanie wyświetlaczem TFT - część 5 - optymalizacja programu 8
Gieneq Styczeń 28, 2020 Udostępnij Styczeń 28, 2020 (edytowany) Zapowiada się bardzo ciekawy artykuł! Można by się zatrzymać na samym połączeniu wyświetlacza, wgraniu biblioteki, ale zadanie sobie pytania jak coś działa może być ciekawą i pouczającą przygodą. Podoba mi się konkretne podejście do analizy - znany jest czas, odwołania do dokumentacji, sprawdzenie kodu źródłowego - super! 20 godzin temu, Elvis napisał: Mamy więc straszny bałagan w nazwach, ale na koniec w sumie prostą sytuację Akurat drążę temat ESP i jest to dla mnie po raz kolejny frustrująca przygoda przez gąszcz domniemań - niektóre zakamarki internetu przypominają czytanie legend, gdzie coś z czymś się łączy i wychodzą z tego niestworzone rzeczy. Dlatego chciałbym kiedyś zobaczyć artykuł, gdzie byłby konkret na temat ESP - jak jest to zbudowane, jak wykorzystać możliwości jakie to daje. Podobnie jak z tym wyświetlaczem - jak to się dzieje, że w telefonie (nawet starej Nokii) jakoś to płynnie działa, a mija ponad 20 lat a mamy rozwiązanie gorsze niż te co było kiedyś. Dlatego miło widzieć artykuł zachęcający do bardziej świadomego podejścia do tematu Edytowano Styczeń 28, 2020 przez Gieneq
ethanak Styczeń 28, 2020 Udostępnij Styczeń 28, 2020 @Gieneq przepraszam za offtopic ale piszesz o problemach z ESP. Ten akurat jest całkiem nieżle udokumentowany - pytanie tylko, o którą wersję ESP Ci chodzi i czy chcesz programować w Arduino IDE czy używać narzędzi dołączonych przez producenta. Poza tym pamiętaj, że ESP8266 i ESP32 to dwa różne światy - o ile w pierwszym przypadku jest to w praktyce zwykły Wiring (oczywiście w przypadku Arduino IDE), o tyle w drugim jest to zmodyfikowany FreeRTOS. W każdym razie w obu przypadkach nie miałem większych problemów ze znalezieniem czegoś w dokumentacji (łącznie z obsługą drugiego procka w ESP32 i synchronizacją wątków na różnych rdzeniach). Temat jest niestety zbyt obszerny na pojedynczy artykuł - dla przykładu znany mi artykuł omawiający rolę.poszczególnych pinów w ESP32 jest zbyt wielki, aby zmieścić się w ograniczeniach konkursu... A w ogóle @Elvis robi świetną robotę, czekam na następną część.
Gieneq Styczeń 28, 2020 Udostępnij Styczeń 28, 2020 (edytowany) @ethanak To może podam przykład. Przeglądałem ostatnio sporo kursów w temacie obu wersji ESP pod kątem tego jak ludzie tłumaczą rolę wyprowadzeń - sporo z nich było dość zgrubne - tak by gdy ktoś trafi na kurs i będzie chciał podłączyć LED to mu się uda. Ale trafiłem też na materiały, np. ten artykuł, który tłumaczy głębiej - jakie stany przyjmują wyprowadzeniu przy bootowaniu. Nie jestem specem w ESP ale przekładając to na powyższy artykuł to dobrze że w internecie pojawiają się materiały - jedne upraszczające życie - szybki star z Arduino i wyświetlaczem xxx, ale i takie jak tu, które uświadamiają jak co działa Edytowano Styczeń 28, 2020 przez Gieneq
ethanak Styczeń 28, 2020 Udostępnij Styczeń 28, 2020 Akurat przytaczany artykuł jest jednym z gorszych które znam (co ciekawsze, analogiczny artykuł z tego samego serwisu traktujący o ESP32 jest całkiem niezły). W tym przypadku zamiast chrzanić farmazony o tym, jaki to stan panuje na danym pinie wystarczyłoby od razu napisać do czego dany pin służy. Niepotrzebne by były tabelki, stwierdzenia że "jak podepniemy pin do masy to nie będzie działać" (oczywiście błędne, bo częściowo zachowanie układu przy pinie podpiętym do masy jest opisane parę linijek dalej). Dobra, dość offtopiców. 1
msalamon Styczeń 28, 2020 Udostępnij Styczeń 28, 2020 Czekam na implementację na STM32 Będziesz używał buforowania w RAM? Ja na ogół tak właśnie robię. 1
Elvis Styczeń 28, 2020 Autor tematu Udostępnij Styczeń 28, 2020 Buforowanie w RAM będzie jak najbardziej - chociaż najpierw będzie kod od WaveShare, tak dla porównania. 2
Pomocna odpowiedź
Bądź aktywny - zaloguj się lub utwórz konto!
Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony
Utwórz konto w ~20 sekund!
Zarejestruj nowe konto, to proste!
Zarejestruj się »Zaloguj się
Posiadasz własne konto? Użyj go!
Zaloguj się »