Dzisiaj budujemy malutki zegar z podtrzymaniem czasu, zrealizujemy go w oparciu o 8-bitowy mikrokontroler PIC12F683 z jedynym 128 bajtów pamięci RAM, moduł wyświetlacza 7-segmentowego z kontrolerem FD650 odzyskany z elektrośmieci oraz zegar RTC DS1307. W temacie też pokażę pełne przebiegi z komunikacji I2C przechwyconej analizatorem logicznym.
Czemu nie jedna magistrala I2C?
Zacznijmy od najważniejszego problemu projektowego i oczywistego pytania, które pewnie każdemu się nasuwa po przeczytaniu wstępu do tego tematu - po co w ogóle dwie magistrale I2C? Czy nie można użyć adresów?
Tak się niestety składa, że duża ilość popularnych chińskich układów nie trzyma się w pełni standardu I2C i nie wspiera adresowania - tam pierwszy wysyłany bajt nie jest adresem indywidualnego układu, lecz np. komendą lub od razu adresem rejestru na na którym operujemy.
Podobny problem jest w przypadku FD650, na którym oparłem tą konstrukcję. FD650 to klon TM1650, występuje też pod nazwą HD2015. Opisywałem już go szeroko w jednym z tematów:
Uruchamiamy sterownik wyświetlacza/przycisków HD2015 po inżynierii wstecznej, porównanie z TM1637
FD650 ma właśnie tą przypadłość, że zakłócać będzie nam inne adresy na magistrali.
Drugi użyty w projekcie element, DS1307, jest w pełni zgodny z I2C i nie robi problemu, ale co z tego?
Trzeba rozdzielić magistralę - zdecydowałem się mieć dwie linie danych SDA i wspólny zegar SCL.
Organizacja pracy
Projekt jest dość złożony, więc dla uproszczenie pierwsze etapy wykonałem na Arduino. Było to podyktowane przede wszystkim chęcią sprawdzenia, czy mój moduł z FD650 przeżył operację. Dopiero po upewnieniu się, że jest z nim wszystko okej, przeniosę kod na PICa.
Moduł z FD650
Moduł odzyskałem ze starego odbiornika telewizji naziemnej. Sprzęt nie miał pilota i nie wspierał już nowszych standardów z DVB-T2. Od jakiegoś czasu dość często tego typu sprzęty otrzymuję, więc odzyskuję z nich masowo części. Nie miałem już miejsca na kolejną płytkę, więc pozwoliłem sobie wyciąć jedynie to, co mi się przyda:
Oprócz czterocyfrowego wyświetlacza 7-segmentowego jest tam jeszcze jego kontroler, FD650, oraz jakiś układ od RF (tutaj zbędny - do usunięcia).
Układ na płytce może wydawać się niekompletny, ale nic bardziej mylnego. FD650 potrzebuje do działania naprawdę mało komponentów. Poniżej przedstawiam jego przykładową aplikację, która jest w dużej mierze zgodna z tym, co mam na płytce. Usunąłem jedynie przyciski.
Jak widać, nawet nie potrzeba wielu rezystorów - tylko ten 2 kΩ od przycisków. Wyświetlacz LED wcale dodatkowych oporników nie potrzebuje.
Skan I2C z FD650
Nie musicie mi jednak wierzyć na słowo. Prosty skan I2C jest łatwo uruchomić, wystarczy odpytywać kolejne adresy i sprawdzać sygnał ACK. Dla wygody zrealizowałem to na Arduino. Nie musiałem nawet dawać zewnętrznego rezystora pull up (podciągającego do zasilania), bo Arduino pozwala skonfigurować w tym trybie pin. Odpytywanie odbywa się w pętli i te aktywne są wypisywane na UART:
Kod: C / C++
Na magistrali jest tylko FD650, ale zgłasza się mnóstwo adresów. Zgodnie z obawami.
Dla weryfikacji odłączyłem jedną z linii - od razu komunikacja zostaje przerwana:
Pierwsze segmenty
Ten akapit będzie skrócony do minimum, gdyż zasadniczo powtórzyłem to 1:1 pracę z poprzedniej prezentacji:
Uruchamiamy sterownik wyświetlacza/przycisków HD2015 po inżynierii wstecznej, porównanie z TM1637
Wszystko od razu ruszyło i nie było problemu z segmentami i czcionką. Wygląda na to, że mapowanie segment na pin jest tutaj ustandaryzowane, dzięki temu większość tunerów (czy tam urządzeń?) opartych o FD650 i klony może mieć ten sam kod obsługi.
Kod: C / C++
Prosty zegar
Następnie wdrożyłem funkcję showTime, która pozwala po prostu rozdzielić dwie wartości liczbowe na jedności i dziesiątki, a potem ustawia je na odpowiednich pozycjach i wysyła do wyświetlacza. Nie operuję tutaj na stringu (łańcuch znaków), bo bym musiał ręcznie przesuwać liczbę i dopisywać 0 dla wartości minut i godzin z zakresu od 0 do 9.
Kod: C / C++
Działa:
Port na PIC12F
Pora przenieść program na PIC12F. Logika zostanie bez zmian, zmienią się operacje na pinach. Na PICu bezpośrednio wykonujemy to na rejestrach. Dodatkowo ustawiam dla niego wewnętrzny oscylator, taki wystarczy, i tak mam inne plany co do zliczania czasu.
Kod: C / C++
Te delay_ms chyba nawet nie jest przeliczone do bieżącej konfiguracji oscylatora i przekłamuje, ale to nie jest problem, i tak w finalnym produkcie nie powinno być tego typu blokujących oczekiwań. Użyjemy do tego czegoś lepszego.
Skupmy się na razie na samej komunikacji przez I2C. Warto podejrzeć, co dzieje się na magistrali. Użyłem do tego analizatora stanów logicznych, prezentowałem go już w osobnym temacie:
Analizator stanów logicznych Salae 24MHz za 40 zł - analiza nieznanego protokołu wyświetlacza LED
Do zebranych danych dodałem też dekoder I2C, by widzieć przesyłane bajty.
Najpierw całe transakcje - widać, jak dane są wysyłane co te (powiedzmy) 250 ms na wyświetlacz:
Po przybliżeniu widzimy pojedynczą operację showTime, na nią składa się zbiór operacji zapisu danych do FD650.
Mamy tu kolejno uruchomienie wyświetlacza (zapis 0x11 na adres 0x48) i zapis czterech znaków (adresy 0x68, 0x6A, 0x6C, 0x6E). Ostatnia operacja to powtórne włączenie wyświetlacza - zbędne, zostało na skutek moich eksperymentów i zapomniałem je usunąć do zdjęć.
Przykładowe zbliżenie na operację uruchomienia wyświetlacza:
Analogicznie, zapis znaków:
Integracja DS1307
Teraz pora wprowadzić DS1307. DS1307 to tzw. RTC (Real Time Clock), czyli układ zegara czasu rzeczywistego, który służy do podtrzymywania aktualnej daty i godziny, nawet gdy mikrokontroler jest wyłączony lub resetowany.
Dzięki zastosowaniu zewnętrznej baterii podtrzymującej (najczęściej CR2032) DS1307 może pracować niezależnie od zasilania głównego układu. Komunikacja z mikrokontrolerem odbywa się poprzez I2C.
Mój moduł z DS1307 ma jeszcze na pokładzie pamięć EEPROM, ale na razie tu ją pominę.
Cały problem tutaj polega na tym, że nie mogę umieścić DS na tej samej magistrali co FD650, bo FD650 nie wspiera zasadniczo adresowania i zajmuje wszystkie adresy - pokazałem to w akapicie "Skan I2C z FD650".
Z tego powodu wprowadziłem podzielenie magistrali, zgodnie z zapowiedzią.
Teraz:
- SDA wyświetlacza to pin 0
- wspólny SCL to pin 2
- SDA zegara to pin 1
W celu oszczędności pamięci, złamałem zasadę DRY (Don't Repeat Yourself) i przygotowałem dwa zestawy funkcji I2C - jedne dla pin 0, drugie dla pinu 1.
Kod: C / C++
Rezystory pull up okazały się być znów zbędne - te z PICa starczają. Pokazany kod działa - czas jest odliczany i wyświetlany. Wcześniej RTC trzeba było raz zaprogramować, ale na razie załóżmy, że zrobiliśmy to ręcznie, osobno...
Zdjęcia pokazują czas w formacie mm:ss, dlatego może wydawać się on dziwny, ale tak wygodniej się testuje.
Jak wygląda to pod maską? Podłączyłem analizator logiczny do linii D0 (zegar), D1 (SDA pierwsze) i D2 (SDA drugie). Dodałem dekodery I2C.
Po powiększeniu widać poprawnie dekodowane transakcje. Nie ma problemów z warunkami startu/stopu, nie ma "duchów" na linii:
Tak wygląda odczyt czasu z DS1307. Widać tu charakterystyczny adres 0x68 (po przesunięciu i dodaniu bitu read/write 0xD0 lub 0xD1) i odczyt z rejestrów 0, 1, i 2, wartości 00:28:50.
Analogicznie, kontrola wyświetlacza. Tu widać wysyłane segmenty poszczególnych cyfr.
Odczyt klawiszy
Kolejnym etapem jest odczyt klawiszy. Nie mam na swojej płytce klawiszy, więc dodałem dwa wedle schematu wcześniej umieszczonego w poście. Potem zaimplementowałem ich odczyt - odczyt przez I2C z adresu 0x27. Zwracany wtedy jest kod klawisza oraz flaga czy jest/był wciśnięty.
Kod: C / C++
Na próbę wyświetlam ten kod na wyświetlaczu w formacie szesnastkowym. Przy okazji dodałem znaki 10-15 do mapy znaków.
Kod: C / C++
Odczyt widać na analizatorze:
W ten sposób łatwo można zidentyfikować kody posiadanych klawiszy:
Limity, wszędzie limity
Na tym etapie limity pamięci PICa zaczęły dawać się we znaki:
pic12f683_clockI2C.asm:1877:Message[1304] Page selection not needed for this device. No code generated.
message: Using default linker script "C:\Program Files (x86)\gputils\lkr\12f683_g.lkr".
warning: Relocation symbol "_cinit" [0x0000] has no section. (pass 0)
error: No target memory available for section "IDD_idata_0_i".
error: Error while writing hex file.
Program przerobiłem i przepisałem tak, aby w miarę możliwości funkcje umieścić w głównej pętli i zredukować ilość zmiennych.
Finalizacja zegara
Ostatnim krokiem jest dodanie nastawiania godziny i minuty. Wystarczą do tego te dwa przyciski. Założyłem, że:
- jeden tylko przełącza tryb (praca, edycja godziny, edycja minuty)
- drugi zwiększa wybraną wartość, pojedyncze wciśnięcie zwiększa jednokrotnie, a przytrzymanie zwiększa ciągle o 1 dopóki trzymamy klawisz
W tym celu odczytuję klawisze co pętle, badam aktualny tryb (praca, edycja godziny, edycja minuty), w przypadku edycji migam wybranymi cyframi i odpowiednio obsługuję zdarzenia klawiszy. Po edycji czas jest zapisywany.
Kod: C / C++
Rezultat:
Kombinacje z obudową
Zacząłem też robić obudowę, ale o tym będzie w osobnym temacie.
Podsumowanie
Projekt okazał się być bardzo ciekawym i nietypowym doświadczeniem. FD650 uruchamiałem wcześniej, nie było to problemem, ale tutaj przećwiczyłem oszczędzanie pinów (ten PIC12F ma mało wolnych IO, a mogę jeszcze chcieć dodać wkrótce alarm, ADC też by się przydało), a potem ogólną optymalizację. Ostatecznie udało się całość zmieścić na małym PICu i programowo wyszedł funkcjonalny zegar.
Teraz można jeszcze ogarnąć hardware - pokazaną płytkę można by jeszcze zmniejszyć, pewnie odcinając od niej zbędne fragmenty laminatu, a potem wydrukować na drukarce 3D obudowę i jakoś sprytnie umiejscowić przyciski.
Być może zajmę się w tym osobnej części.
Załączam wersję projektu dla SDCC.
Fajne? Ranking DIY Pomogłem? Kup mi kawę.