Dzisiejszy projekt będzie opierać się o Arduino. Pokażę tutaj jak uruchomić kontroler wyświetlacza/klawiatury ze starego tunera sat w oparciu o jego notę katalogową oraz bibliotekę Wire z Arduino. Poznamy szczegóły jego interfejsu szeregowego opartego o linie SDA i SCL i spróbujemy przygotować podstawę zegara bądź wyświetlacza temperatury. Całość pokażę w praktyce, krok po kroku, chociaż jakaś podstawowa znajomość programowania i protokołów się przyda.
Prezentowany tu układ zdobyłem za darmo ze starego tunera Wiwa HD90. Szczegóły tutaj:
Stare tunery sat, Wiwa HD90, podwójny teardown, obudowa, części DIY za darmo?
Wstępne rozpoznanie
Pierwszym krokiem jest wyszukanie noty katalogowej. W tym przypadku udało mi się znaleźć angielskojęzyczną wersję:
Są tutaj tylko dwie linie do komunikacji. Jest obiecująco. Jest jeszcze sygnał przerwania (pewnie od klawiatury), ale to nie jest konieczne na początek. Patrzymy dalej:
Komunikacja kompatybilna z I2C, nieźle! Zaraz sprawdzimy szczegóły.
Schemat podłączenia
Schematów połączenia jest kilka, oto najbardziej podstawowy (8 segmentów, wyświetlacz z wspólną katodą):
Dzięki dodaniu kilku tranzystorów też i wyświetlacze o wspólnej anodzie wysteruje:
... ale my mamy całe PCB gotowe, wystarczy wlutować się w oznaczone punkty na płytce: 3.3V, GND, SCL i SDA:
Całość:
Interfejs szeregowy
Poniższa sekcja noty katalogowej może wyglądać strasznie, ale na szczęście to wszystko jest mniej więcej standaryzowane, tak jak zresztą napisali na początku, "compatible with I2C". Komunikacja tam podzielona jest na transakcje, każda wysyła daną ilość bajtów, poprzedzona jest warunkiem start i zakończona warunkiem stop.
W tym konkretnym przypadku transakcja ma sześć kroków: sygnał start, pierwszy bajt, pierwsze acknowledge, drugi bajt, acknowledge dwa, oraz stop. Czyli nie można tu wysłać więcej niż dwóch bajtów...
Dodatkowo nie widzę tu adresacji urządzeń. Pierwszy bajt to rejestr, a drugi to wartości do wpisania. Ten sam schemat występuje i dla konfiguracji wyświetlacza (jeden rejestr) i dla wyświetlania cyfr (cztery rejestry, po jednym na cyfrę).
Skaner I2C
Zasadniczo ten protokół nie jest zgodny z I2C, bo nie ma adresacji urządzenia, ale skaner I2C z dokumentacji Arduino też powinien coś wykryć. Odpowiadać powinny adresy, pod którymi jest kontrola wyświetlacza oraz wartości segmentów. To pozwoli nam sprawdzić, czy poprawnie podłączyliśmy SDA i SCL.
Na Arduino UNO to piny A4(SDA) i A5(SCL).
Kod: C / C++
Wyświetlacz odpowiada - pora pójść o krok dalej.
Zweryfikujmy te adresy z notą. Tutaj są przesunięte, bo Wire wysyła adres urządzenia z dopisanym bitem R/W po prawej stronie. A więc, 0x24 razy dwa to 0x48. Hm, to by się zgadzało z notą:
Pierwsze segmenty
Teraz wracamy znów do noty katalogowej. Pierwsze co trzeba ruszyć to rejestr kontrolny. Tutaj jednak trzeba pamiętać, że funkcja Wire.beginTransmission bierze za argument adres urządzenia I2C, który jest tutaj 7-bitowy, a na magistralę wysyła go po przesunięciu o jeden bit, dopisując najmłodszy bit, czyli read/write. Szczegóły:
https://www.arduino.cc/reference/en/language/functions/communication/wire/
Nasze urządzenie nie ma tej adresacji, więc stosujemy mały trik - podzielimy na 2 adres rejestru do którego chcemy się dostać. Jest to równoznaczne z przesunięciem go o 1 bit w prawo.
A więc mamy format komendy najpierw 0x48, a potem bitowo 0[INTENS][7SEG][SLEEP]0[ENA].
Intensity (poziom jasności) jest 3 bitowy.
A więc tak, ustawiamy:
- intens na największą
- 7seg tryb na włączony (tak nastawiłem na początku, potem to poprawiałem...
- sleep na wyłączony
- enabled na włączony
Wpisujemy bity:
0[111][1][0]0[1]B
Czyli zasadniczo mamy:
01111001b binarnie a w hex 0x79.
Otwieramy dokumentację Wire:
https://www.arduino.cc/reference/en/language/functions/communication/wire/
Wpisujemy w kod:
Kod: C / C++
Myślałem, że jeszcze będę musiał coś wpisać w rejestry od cyfr, ale nie - pierwsze LEDy już zaświeciły. Coś działa:
Rejestry cyfr
Teraz patrzymy jak zapalić segmenty. Jest o tym nieco informacji w nocie katalogowej:
Wpisujemy adres rejestru w kod i wysyłamy do niego 0xff, wszystkie bity zapalone. Oczekujemy wyświetlenia ósemki.
Kod: C / C++
Działa!:
Autoinkrementacja adresu?
W poprzednich kontrolerach była wspierana autoinkrementacja adresu, czyli mogłem podać adres rejestru a potem wpisać na kolejne komórki pamięci kilka bajtów. Tutaj wedle noty katalogowej tak nie można, ale czy na pewno?
Kod: C / C++
Nie działa. Trzeba osobno adresować. Nie ma tu raczej zaskoczenia, w nocie katalogowej widziałem wzmianki o tym, że wysyłamy dwa bajty na raz...
Wszystkie cyfry
Pora już zrobić funkcję pomocniczą która reprezentować będzie jedną transakcje, czyli wpisanie bajtu na dany adresy:
Kod: C / C++
W oparciu o to uruchamiam wyświetlacz i wysyłam cztery ósemki:
Kod: C / C++
Działa. Mamy cztery ósemki:
Dla pewności zrobiłem odliczanie:
Kod: C / C++
Prawie działa, chociaż mam wrażenie, że jeden segment cały czas się świeci...
Następnie kolejno wgrywałem programy wysyłające kolejne potęgi dwójki (1, 2, 4, 8, 16, 32, 64, 128) aby zbadać, który segment to który.
Kod: C / C++
Wpisuję kolejno potęgi dwójki, patrzę co się zapala, patrzę na nazewnictwo z Wikipedii (segmenty określone są literami) i spisuję do kodu:
Kod: C / C++
Udało się tak uzupełnić wszystkie segmenty, oprócz jednego...
Poprawiam swoje błędy
W trakcie wypisywania segmentów zorientowałem się, że ciągle świeci mi się segment E. Szybka weryfikacja płytki pokazała, że nie jest to zwarcie. Winna okazała się niepoprawna inicjalizacja wyświetlacza - u mnie on pracuje w trybie 8 segmentów, więc bit "7seg" powinien być zgaszony:
Kod: C / C++
Wraz z tą zmianą zapalanie segmentów zaczęło przebiegać w pełni poprawnie.
Łączenie segmentów w znaki
Mamy już segmenty. Teraz trzeba utworzyć cyfry (i ewentualnie kilka liter), ale... to już było szczegółowo omawiane w moim temacie o PIC18F2550:
Tutorial PIC18F2550 + SDCC - Część 5 - Wyświetlacz 7-segmentowy i przerwania
Więc, powtarzając ten proces, miałem już gotowe segmenty:
Kod: C / C++
Potem wprowadziłem znaki do tablicy. Operatorem | łączę tutaj poszczególne bity w całość. Zerowe miejsce na tablicy odpowiada znakowi 0, pierwsze miejsce znakowi 1, itd.
Kod: C / C++
Dzięki temu łatwo jest indeksować tę tablicę wartościami liczbowymi by dostać się do kodów znaków.
Oto testowe odliczanie:
Kod: C / C++
Wszystko działa:
Kolejność znaków
Mamy cztery rejestry - czy odpowiadają one kolejnym znakom?
Kod: C / C++
Rezultat:
Kolejność znaków jest odwrócona.
A ósmy segment?
W trakcie wypisywania segmentów pominąłem jedną z potęg dwójki - 32. Za co ona odpowiada?
Kod: C / C++
Eksperymentalnie określiłem, że jest to dwukropek w przypadku rejestru 0x6C. Dla pozostałych rejestrów nic nie jest zapalane.
Łączymy wszystko w całość
Załóżmy, że robimy zegar i odliczamy osobno minuty i godziny. Musimy jakoś rozbić minuty i godziny na osobne cyfry i włożyć je w odpowiednie rejestry w pamięci. Przyda nam się do tego operator dzielenia całkowitego (na integerach), nim wyłuskamy ilość dziesiątek, oraz operator modulo (reszta z dzielenia całkowitego), by wyłuskać jedności:
Kod: C / C++
Do rejestru 0x6C dopisuję bit od dwukropka.
No i jeszcze jakieś przykładowe wywołanie (to nie odlicza poprawnie czasu, to tylko demonstracja):
Kod: C / C++
Działa:
Odczyt klawiszy
Zaglądamy do noty katalogowej:
Tym razem najmłodszy bit z adresu jest zapalony. To by się zgadzało z adresacją 7-bitową gdzie doklejany bit R/W w przypadku operacji read jest 1. A więc piszemy pomocniczą funkcję:
Kod: C / C++
Wywołujemy ją dla naszego rejestru (przy dzieleniu na 2 adresu jedynka się zgubi, ale requestFrom potem ten bit i tak dopisze zapalony bo operacja to odczyt):
Kod: C / C++
Testujemy i wciskamy osobne przyciski:
Trzeba jeszcze te wartości zinterpretować:
Czyli bit 0x40 mówi nam czy klawisz jest w tym momencie wciśnięty a pozostałe bity to zasadniczo indeks klawisza (a dokładniej indeksy wiersza i kolumny). Nie ma tu wsparcia wciśnięcia kilku klawiszy na raz. A więc, wyodrębnijmy w kodzie informacje o wciśnięciu oraz indeks klawisza:
Kod: C / C++
Bardzo ładnie widać tu jak trzymałem klawisz o kodzie 7 a potem go zwolniłem:
Podsumowanie
Wyświetlacz udało się uruchomić, chociaż nie obyło się bez pomyłki (źle oceniłem, że bit 7seg powinien być 1, a powinien być 0, gdyż tu ósmym segmentem jest dwukropek). Dodatkowo okazało się, że kropki nie są nigdzie podłączone, więc w tej formie temperatury nie wyświetlimy. Można by spróbować rozważyć jakąś modyfikację, nie wiem, może przełączyć kropki pod dwukropek a dwukropek odpiąć lub do MCU podłączyć do wolnego GPIO, musiałbym sprawdzić dokładniej PCB.
Jaki z tej zabawy wniosek? To raczej pozostawiam wam, chociaż ja bym żartobliwie dodał, że jak się umie czytać noty katalogowe, to prawie każde urządzenie staje się "modułem z chin do uruchomienia z Arduino".
Fajne? Ranking DIY Pomogłem? Kup mi kawę.
