
Witajcie, przedstawię tu od 0 rozpoznanie wyprowadzeń, podłączenie, zaprogramowanie i uruchomienie z Arduino wyświetlacza LCD z elektrośmieci. Omawiany tu wyświetlacz pochodzi z pralki Amica AWSN12DA i posiada sterownik BL55066 do którego nie znalazłem biblioteki, tylko samą notę katalogową.
Dodatkowo, w celu urozmaicenia tematu, wyświetlacz ten będę sterować z poziomu komputera z systemem Windows. Na Windowsie (w Visual Studio, języku C#, Windows Forms) napiszę aplikację która będzie komunikowała się z Arduino poprzez UART i pozwoli wygodnie określić które segmenty za co odpowiadają.
UWAGA: W temacie zastosuję pewne uproszczenia które są niezbędne by móc sensownie w krótkim czasie to wszystko zademonstrować. Przykładowo, komunikacja UART będzie najprostsza jak się da (bez nagłówka, sumy kontrolnej, itd), obsługa wejść i portu UART w C# też nie będzie sprawdzała czy nie został rzucony wyjątek, itd. Uzupełnienie projektu o te brakujące, lecz też wręcz niezbędne rzeczy zostawiam czytelnikowi.
Konkurs znajduje się na końcu tematu.
Pokrewne tematy z serii
W tym stylu napisałem już co najmniej kilka tematów, wszystkie przeznaczone raczej dla początkujących i czasem korzystające z Arduino:
Stary DVD Wiwa HD-128U - wnętrze, obsługa wyświetlacza i klawiatury SM1628B
Teardown drukarki HP Deskjet D1360 i przykład użycia jej części z Arduino
Wnętrze odtwarzacza DVD United 7071, wykorzystanie części z Arduino
Wnętrze HP DeskJet 990Cxi C6455A oraz wykorzystanie zasilacza i przetwornicy
Stary tuner sat Kathrein - wnętrze, wykorzystanie części, zabawy z PAL
Stary modem ADSL Acer Surf USB - wnętrze, wykorzystanie przetwornic ze środka
Drugie życie zasilaczy impulsowych ze starych telewizorów CRT
Wszystkie tematy z serii zawierają moją "radosną twórczość" i operują na elektronice odzyskanej ze złomu, czyli na tzw. "przydasiach".
Moduł z LCD z pralki AWSN12DA
Pierwszym krokiem jest określenie sygnałów do sterowania wyświetlaczem.
Potrzebne pewnie będą ze dwa rodzaje sygnałów:
- coś do samej komunikacji z LCD (przykładowo I2C)
- coś do kontroli jego podświetlenia (może to być osobne podłączenie, może być zintegrowane z kontrolerem)
Sam moduł prezentował się tak:





Widać tu, że sercem układu jest BL55066:


Kontroler wyświetlacza LCD z interfejsem IIC pracujący na napięciu do 6V, wspierający zarówno multipleksing jak i statyczną kontrolę segmentów. Dodatkowo umożliwia kaskadowanie kilku takich samych układów w celu obsłużenia wiekszej ilości wyjść. Występuje w malutkiej obudowie LQFP44.
Układ ten pozwala rozdzielić swoje napięcie zasilania od napięcia zasilania LCD.
Dodatkowo widać, że jedna z anod od podświetlenia jest urwana:


co można szybko naprawić - starłem nożykiem maskę, dodałem topnika i cyny:


oraz, że obok BL55066 obecny jest tranzystor BC807:

Sprawdzenie multimetrem ścieżek pokazuje, że załącza on podświetlenie (jego emiter podłączony jest do masy, kolektor do anody, a baza - to sprawdzimy).
W celu ułatwienia sobie pracy można wylutować (np. plecionką) stare niestandardowe złącze i wlutować 2x5 goldpiny 2.54mm zgodne z popularnymi kabelkami od Arduino i płytek stykowych:




Wyprowadzanie modułu
Określenie wyprowadzeń jest bardzo proste - bierzemy multimetr w teście ciągłości i sprawdzamy gdzie idą interesujące nas piny, czyli zwłaszcza masa, zasilanie, linie I2C czyli SCL i SDA, itd.
W ten sposób określiłem:
Quote:
SDA SCL Q1 baza przez rez. 1k ??? VDD podśw. K przez rez 220 ??? ??? ??? GND
Uwagi:
- podświetlenie to dwie diody, zarówno z prawej jak i z lewej strony wyświetlacza są wyprowadzone anoda i katoda.
- diody podświetlenia są szeregowo
- podświetlenie dodatkowo jest podłączone przez rezystor 220 omów ograniczający prąd oraz poprzez tranzystor Q1 BC807 pewnie pozwalający np. regulować jego jasność przez zewnętrzne PWM. Q1 ma na bazie rezystor 1k omów, co trzeba wziąć pod uwagę przy sprawdzaniu połączęń multimetrem
- Q1 zwiera podświetlenie do masy, natomiast na pinach wyprowadzona jest.... katoda jednej z diod podświetlenia.
Oznaczona to, że w tej konfiguracji cały moduł oczekiwał +5V zasilania BL55066 oraz -8V (powiedzmy) zasilania podświetlenia.
Weryfikacja działania podświetlenia:


Tak jak napisałem - to podświetlenie w takim układzie wymaga ujemnego napięcia.
Modyfikacja podświetlenia
Nie miałem pasującego zasilacza, więc podświetlenie przerobiłem tak by pracowało na około +6.5V. Niżej nie mogłem zejść ze względu na spadek na diodach (choć mogłem próbować je dawać równolegle...). Usunąłem podłączenie do masy, usunąłem tranzystor Q1 i wyprowadziłem dodatkowy kabelek:


Teraz całość może ruszyć z zasilaczem dającym 7V (około - właśnie taki mam) i regulatorem 7805 zapewniającym 5V dla samego kontrolera LCD (przy okazji przypomnę, że napięcie zasilania 7805 musi być większe od wyjściowego 5V o co najmniej wartość określoną w nocie katalogowej, o dropout voltage).
Arduino - protokół komunikacji - I2C z BL55066
Punktem wyjściowym do zabaw z I2C jest z reguły skaner I2C, jeden z oficjalnych przykładów Arduino:
Code: c
Źródło: https://playground.arduino.cc/Main/I2cScanner/
Dokumentacja: https://docs.arduino.cc/learn/communication/wire
Oprócz zasilania i masy trzeba podłączyć sygnały SCL i SDA.
U mnie skaner znajduje adres 0x3E:

Arduino - protokół komunikacji - uruchomienie BL55066
Samą bibliotekę komunikacji I2C w Arduino już mamy, ale trzeba też wiedzieć co wysłać.
Odpowiedź na to pytanie znajdziemy w nocie katalogowej.
Cała komunikacja składa się z adresu urządzenia (wraz z bitem read/write), opcode komendy (jeśli jest więcej komend to z bitem carry) oraz ewentualnych danych komendy:

Na początek przyda się komenda Mode Set:

Aby poznać znaczenie jej poszczególnych bitów, patrzymy do określonych tabel:


W celu wpisania tych komend do Arduino, najlepiej jest przenieść je jako wartości bitowe, które można połączyć razem operatorem bitowe OR - |, np. FLAGA1 | FLAGA2.
Code: c
Teraz należy wysłać to przez I2C. Przede wszystkim zależy nam na ustawieniu stanu Enabled, czyli włączony.
Code: c
Wywołanie endTransmission sprawdza, czy odebrany został ack. Jeśli podany adres urządzenia I2C jest nieprawidłowy, to powyższy kod wypisze "ERROR" na UART. W przeciwnym razie "OK".
Sprawdzamy...

Sukces! Pora dostać się do fragmentów.
Arduino - protokół komunikacji - fragmenty BL55066
Teraz trzeba będzie wysłać komendę z danymi. Osobno adres a potem dane jakie chcemy zapisać:

Wyślemy kolejno:
- komendę Load Data Pointer z offsetem 0 (pierwsza komórka w pamięci)
- kilka kolejnych bajtów do wpisania do pamięci (dane)
Adres docelowej komórki w pamięci sam ulegnie inkrementacji, jak również I2C samo odkryje ile danych wysyłamy.
Code: c
Oczywiście w bloku setup wciąż mam wysłanie danych uruchamiających LCD.
Sprawdzamy:

Działa! Teraz trzeba ogarnąć, jak w rzeczywistości te adresy w pamięci mapują się na segmenty...
Arduino - protokół komunikacji - fragmenty BL55066, część 2
Mamy kilka rodzajów adresacji fragmentów/segmentów LCD:

Każdy z nich sprawdziłem w praktyce. Policzyłem ilu segmentów w sumie oczekuję i próbowałem ustawić każdy z nich. Szybko zdecydowałem sie na:
Code: c
Niestety nie znam wyprowadzeń tego LCD, z tego powodu po prostu spróbowałem każdej opcji.
Powyższy kod zapala i gasi wszystkie segmenty. Dodatkowo każdy bit wysyłanych danych odpowiada jednemu segmentowi (za wyjątkiem ostatnich 4 bitów, które nie mają swoich fragmentów).
Teraz trzeba jakoś określić który bit to który segment...
Dalszy plan - Visual Studio i komunikacja z Arduino przez UART
Uznałem, że zrobię aplikację w Visual Studio która pozwoli zwizualizować poszczególne bity. Zdecydowałem się na język C# i klasyczne okienka Windows Forms. Aplikacja ta będzie komunikować się z Arduino przez UART, co pozwoli potem sterować poszczególnymi segmentami poprzez checkboxy.
Do pisania użyłem Visual Studio 2017, po prostu taką wersję mam wciąż zainstalowaną od około pięciu lat.

Typ projektu - Windows Forms App, w tym mi się wygodnie pisze (kwestia przyzwyczajenia):

Edytor Formsów jest bardzo wygodny:

Mimo to, checkboxy generuję proceduralnie w Form1_Load:
Code: csharp
Rezultat:

Aby wykrywać kliknięcie w checkbox korzystam ze zdarzenia CheckedChanged:
Code: csharp
Sama implementacja CheckboxChanged znajduje się poniżej:
Code: csharp
Code: csharp
Powyższy kod zawiera bardzo duże uproszczenia. W Form1_Load tworzony i otwierany na sztywno jest port UART. W docelowej wersji powinno być to konfigurowalne, gdyż nawet jak zmieni port USB do którego podłączamy Arduino to wirtualny UART może zmienić nazwę na np. COM16.
Dokumentacja klasy SerialPort używanej tutaj do komunikacji z Arduino przez UART: https://docs.microsoft.com/pl-pl/dotnet/api/system.io.ports.serialport?view=dotnet-plat-ext-6.0
SendState kolejno spisuje z checkboxów ich stany do kolejnych bitów tablicy bajtów. Potem są one wysyłane do Arduino poprzez UART w formie binarnej, bez żadnego nagłówka, długości i sumy kontrolnej. Najprościej jak się da, lecz jest to sposób podatny na błędy.
Odbiór bajtów po stronie Arduino:
Code: c
Kod Arduino również zakłada 100% rzetelną komunikację. Starcza to tylko na potrzeby krótkiej demonstracji.
Bieżący stan całego ekranu przechowywany jest w tablicy bajtów nazwanej segments. Odbiór danych realizowany jest do tymczasowego bufora, który dopiero przepisywany jest do segmentów gdy odbiór się zakończy a same segmenty wysyłane są do ekranu przez I2C. Do odbioru danych przez UART używana jest funkcja Serial.read():
https://www.arduino.cc/reference/en/language/functions/communication/serial/read/
Mamy tu zasadniczo komunikację:
- PC do Arduino poprzez USB i UART (wirtualny port COM)
- Arduino do LCD przez I2C
Pora to przetestować.
Zgaśmy ten segment:

Widać, któremu fragmentowi on odpowiada:

Zgaśmy drugi:


Działa, tylko co można zrobić z tym dalej?
Ułatwienie - podpisywanie segmentów
Teraz trzeba jakoś zorganizować pracę z segmentami. W tym celu postanowiłem dodać możliwość przypisywania im tekstowych nazw, wpisywanych do pól tekstowych na interfejsie programu, pamiętanych pomiędzy sesjami działania programu w pliku tekstowym na dysku komputera.
Jest to usprawnienie tylko dla użytkownika - sam program nic z tymi nazwami nie robi.
Kod odczytu nazw:
Code: csharp
Code: csharp
Nowy kod generacji pól (obok checkboxów tworzy textboxy):
Code: csharp
No i zapis:
Code: c
Funkcje File.WriteAllLines i File.ReadAllLines operują na tablicy stringów i zapisują/odczytują ją z pliku tekstowego. To bardzo wygodne. Pozwala uniknąć nam to zabawy w tokenizację.
Jak widać nazwy pól zapisują się gdy zmienimy checkbox, ale nie chciałem dodawać już osobnego zdarzenia "onChange" dla pola tekstowego.
Oto jak nazwy wyglądają w praktyce:


Tak zrobiony system pozwolił mi bardzo szybko i komfortowo rozpisać który segment jest czym:


Co do samych nazw - niektóre nazwy dałem dość żartobliwe, z kolei segmenty cyfr nazwałem zgodnie ze standardem z wikipedii:

Uruchamiamy wyświetlacz trzycyfrowy
Pora zająć się już wyświetlaniem cyfr. Na pierwszy rzut pójdzie ten wyświetlacz z dodatkową jedynką na przodzie ("1888"). Spróbuję pokazać, jak można względnie łatwo zorganizować sterowanie takim wyświetlaczem, w tym przypadku dla demonstracji po stronie C#, choć normalnie robi się to bezpośrednio na mikrokontrolerze.
UWAGA: W idealnym świecie byłoby tu mapowanie 1 bajt (8 bitów, 8 segmentów) = 1 cyfra, i cały kod wykonywałoby się w pętli, ale niestety jak widać z mojej rozpiski segmentów niestety segmenty są troszkę porozrzucane w pamięci... na szczęście i tak są w obrębie 4 bajtów, więc może uda się to jakoś uprościć:

Ok, kolejno... funkcja zamieniająca indeks bitu na jego maskę (zwraca liczbę o zapalonym danym bicie):
Code: csharp
Następnie tworzymy maski dla kolejnych segmentów danej cyfry:
Code: csharp
(jeśli by przypadało po bajcie na jedną cyfrę i mapowanie dany bit = dany segment było stałe, to nie trzeba byłoby z osobna mapować bitów osobnych cyfr, tylko raz dla jednej cyfry by starczyło)
Teraz, mając maski segmentów, tworzymy zbiorcze maski poszczególnych cyfr (każda cyfra zapala inny zbiór segmentów):
Code: c
Powyższy zbiór masek jest tablicą integerów, więc jak mamy cyfrę 0, to zawartość tej tablicy pod miejscem 0 odpowiada zapalonym segmentom cyfry 0. I tak dalej. Specjalnie tak dobrałem kolejność.
Teraz nieco mniej istotna sprawa - pole do wpisania tekstu na próbę (Textbox):

Przechwytuję dla niego zdarzenie modyfikacji:

A w nim, najprościej jak się da, pobieram (na ten moment) tylko jego pierwszą cyfrę, znajduję jej maskę i nakładam ją na checkboxy:
Code: csharp
Co robi funkcja setMask? Funkcja ta bierze maskę (integer) oraz offset (numer bajtu) i nakłada tą maskę (na ten moment zapala i gasi odpowiednie bajty) na checkboxy w danym miejscu segmentów.
Code: csharp
Tablica bits to tablica kontroler checkbox. Funkcja kolejno sprawdza bity danej wartości i ustawia je na checkboxach. Potem zdarzenie OnChange wysyła to przez UART do Arduino. Rezultat:

Funkcję można łatwo poszerzyć o kolejne cyfry. Oto zmodyfikowane zdarzenie OnChange:
Code: csharp
W tym kodzie zakładam, że na miejscu w tablicy jest pusty znak (wszystkie segmenty zgaszone). Nie ma tam A, czyli szesnastkowego 10.
Działa:


Demo - odliczanie
Nie będę już tego szczegółowo opisywać, ale w ramach demonstracji dodałem prostą funkcję odliczania czasu poprzez użycie gotowego komponentu Timer.
Code: csharp
Efektem będzie odliczanie, ale wciąż z pewnym mankamentem..
Poprawka - ustawianie tylko segmentów cyfr
Uważny czytelnik zobaczy jeden problem - mój kod ustawiania cyfr licznika też przy okazji gasi wszystkie sąsiednie segmenty, nawet te, które nie dotyczą samego wyświetlacza liczbowego, tylko też ikonki.
Można temu łatwo zaradzić.
Wystarczy zrobić maskę bitową segmentów danego wyświetlacza i tylko te wartości zmieniać. Przykładowo:
Code: csharp
Nowe wywołanie ustawiania segmentów:
Code: csharp
Od teraz zmieniany jest tylko stan tych segmentów, których bity są zapalone w masce określającej cały wyświetlacz (zmienna "filter")
Code: csharp

Pasek postępu
Postanowiłem jeszcze ożywić pasek postępu - jest to nawet protsze niż w przypadku cyfr.
Tak jak wcześniej - najpierw osobne maski bitowe, a potem tablica z maskami dla kolejnych segmentów (by móc indeksować ją i poprzez to mapować indeks na dany segmenty):
Code: csharp
No i dodatkowa maska, która określa które w ogóle bity dotyczą paska postepów:
Code: csharp
Od strony graficznej, tylko w celach edukacyjnych, użyłem kontrolki Trackbar/Slider:

Oto obsługa jej zdarzenia "Scroll":
Code: csharp
Wartości maksymalna i minimalna trackbar są odpowiednio ustawione, więc wystarczy tylko zapalić kolejne bity i już mamy animowany pasek.
Pasek postępu też zyskał animację w timerze:
Code: csharp
Ostateczny efekt, ze wszystkimi trzema animacjami włączonymi:


Ciekawa obsługa przycisków - jeden pin ADC
Tu już chciałem zakończyć, ale wtedy zwróciłem uwagę na jeszcze jedną ciekawostkę jaką by można było pokazać.
Chodzi o obsługę przycisków z tej płytki.
Moją uwagę przykuły wartości rezystorów:
1k? 2.2k? 4.7k? 10k? 22k? 47k?
Każdy przycisk inny rezystor?

To nie przypadek - te przyciski są wszystkie na jednej linii i są obsługiwane poprzez jeden pin, który jest wejściem analogowym (ADC). Mamy aż 6 przycisków na jednym pinie a określenie który przycisk jest wciśnięty odbywa się poprzez pomiar napięcia na tym pinie. Różne rezystory = różne wartości napięcia na dzielniku napięcia, który one tworzą.
Każdy przycisk "załącza" inny rezystor więc daje inny pomiar.
Swoją drogą, tu też musiałem naprawić przerwane ścieżki:


Następnie rozpisałem połączenia:
- wspólna linia od rezystorów idzie do +5V
- wspólne piny przycisków idą do pinu przy +5V (tu będzie podłączone ADC)
Brakowało mi to rezystora między ADC i masą, więc go dodałem. Początkowo 1k a potem 2k omów.
W Arduino użyłem pinu A0 do odczytu wartości.
UWAGA: tu jest potencjalna pułapka - na płytce UNO A4 i A5 są używane też jako SCL i SDA od I2C, o czym można bardzo łatwo zapomnieć i potem dziwić się, czemu komunikacja I2C przestała działać po dodaniu odczytu napięcia...
Na początek, do sprawdzenia działania, wystarczy taki fragment kodu:
Code: c
Wciśnięcie SW1:

SW2:

SW3:

Rzeczywiście, każdy przycisk daje nieco inną wartość na ADC.
Teraz przydałoby się to zinterpretować, ale jest do tego gotowa biblioteka.
https://www.arduino.cc/reference/en/libraries/analogmultibutton/
Repozytorium:
https://github.com/dxinteractive/AnalogMultiButton
Przykładowy kod z ich dokumentacji:
Code: c
Użycie tej biblioteki jest bardzo proste. Najpierw dodajemy ją przez Library Manager:

Potem uzupełniamy wartości zwrócone przez analogRead dla kolejnych przycisków (możemy ustalić ich różną ilość) i odpalamy.
Kolejno musimy uzupełnić:
Code: c
Pin na którym podłączone są przyciski (musi być wejście ADC)
Code: c
Ilość przycisków (u mnie 6)
Code: c
Wartości odczytów ADC dla przycisków w kolejności rosnącej.
Code: c
Kody przycisków - dla wygody. Odpowiadają kolejnym pozycjom z tablicy.
UWAGA: jeśli wartości odczytu ADC dla przycisków będą do siebie zbliżone, to odczyty mogą być przekłamane. Trzeba dobrać odpowiednio rezystory, tak by odczyty dla osobnych przycisków były równo rozłożone od 0 do 1024
W przypadku mojej płytki zastosowałem jeszcze pewne usprawnienie - jako wartość 0 dodałem siódmy, "wirtualny" przycisk, bo inaczej biblioteka ciągle mi interpretowała wciśnięcia różnych przycisków jako wciśnięcia z dodatkiem SW1.
Poniżej finalny kod:
Code: c
Oto rezultat działania, przy kolejnym nacisku klawiszy począwszy od SW1:

Biblioteka ta oferuje znacznie więcej - eliminuje drganie styków, wykrywanie bardziej złożonych zdarzeń (dłuższe przytrzymanie, czas przytrzymania przycisku, czas do zwolnienia, itd).
Oczywiście te rozwiązanie ma też minusy - nie stosuje się go raczej gdy chcemy móc wciskać dwa przyciski jednocześnie.
Kontrola jasności podświetlenia LCD
Na koniec tylko podkreślę, że można by łatwo regulować jasność naszego ekraniku - wystarczy tranzystor i PWM nim sterujący.
W Arduino służy do tego funkcja analogWrite:
https://www.arduino.cc/en/Tutorial/Foundations/PWM
Podsumowanie
Uruchomienie kontrolera LCD BL55066 okazało się być bardzo proste. W Arduino prawie wszystko jest gotowe, a to, czego brakuje, to zasadniczo kilka komend I2C które musimy samodzielnie wysłać (inicjalizacja LCD i ustawianie segmentów w jego pamięci). Wszystko co i jak jest opisane w nocie katalogowej wspomnianego układu. Oczywiście w tym przypadku sama kontrola segmentów też spoczywa na nas, ale to też nie jest większy problem.
Zdaję sobie sprawę z tego, że cały fragment prezentacji z C# był raczej ciekawostką i normalnie tak się nie steruje wyświetlaczami, ale sama idea (operacje logiczne, tablice, maski) jest w C# bardzo podobna jak w C/C++, składnia też jest do złudzenia podobna.
Trochę szkoda, że wyświetlacz ten posiada tak dużo wyspecjalizowanych ikonek, ale może uda się go do czegoś wykorzystać. Zresztą, chodziło o samą demonstrację obsługi a nie o ten konkretny ekranik.
PS: Jeśli interesuje was nieco "normalniejsze" podejście do wyświetlania i kontrola zwykłego wyświetlacza 7-segmentowego LED w języku C, to polecam zapoznać się z tym tematem:
Tutorial PIC18F2550 + SDCC - Część 5 - Wyświetlacz 7-segmentowy i przerwania
A teraz pora na mały konkurs.
Szukam pomysłu na sensowne i praktyczne DIY wykorzystujące ten wyświetlacz w jak największym stopniu (najlepiej uwzględniając jego oba liczniki (jeden pokazujący od 0 do 1999, drugi z dwukropkiem, a może i pasek postępu i ikonki...). Ale praktyczne pomysły, a nie "zrób DIY pralkę".
Najsensowniejszy pomysł moim zdaniem otrzyma 100 punktów oraz multimetr UT33D+
Czas na zgłaszanie pomysłów - dwa tygodnie.
Cool? Ranking DIY