PIC12F683 i SDCC - cały zegar i termometr na dwóch pinach (bez zewnętrznych bibliotek)
Dzisiaj zrealizuję projekt na malutkim, ośmionóżkowym mikrokontrolerze PIC w obudowie DIP8 oferującym skromne 3.5kB pamięci Flash. Co więcej, do komunikacjami z peryferiami użyję tylko dwóch pinów - całość opierać się będzie o I2C. W oparciu o nie uruchomię kontroler wyświetlacza/klawiatury oraz termometr. Na koniec podejmę pierwszą próbę wzbogacenia całości o odliczanie czasu tak, aby zbudowany układ mógł pełnić rolę zegara. Dostępna też będzie opcja ustawienia bieżącego czasu.
Ten miniprojekt jest nieco powiązany z moim tutorialem SDCC dla PIC18F2550 i będę tu częściowo bazować na opisanych tam krokach:
Część 1 - Konfiguracja środowiska pracy
https://www.elektroda.pl/rtvforum/topic3635522.html#18304424
Część 2 - Blink LED, piny IO, cyfrowe wejścia i wyjścia
https://www.elektroda.pl/rtvforum/topic3647884.html#18389188
Część 3 - Ustawienia oscylatora. Oscylator wewnętrzny, zewnętrzny, rezonator kwarcowy, PLL
https://www.elektroda.pl/rtvforum/topic3657704.html
Część 4 - Timery, przerwania
https://www.elektroda.pl/rtvforum/topic3676645.html#18580858
Część 5 - Obsługa wyświetlacza siedmiosegmentowego
https://www.elektroda.pl/rtvforum/topic3676650.html#18580877
Część 6 - Sterownik wyświetlacza LED MM5450
https://www.elektroda.pl/rtvforum/topic3845301.html
Spis treści będzie uzupełniany wraz z pisaniem przeze mnie kolejnych części.
Powiązany temat o PIC12F683:
PIC12F683 i SDCC - tutorial - tworzymy prosty ściemniacz (czytamy noty katalogowe)
Całość napiszę w SDCC pod PIC12F683 bez zewnętrznych bibliotek.
Ze względów praktycznych nie będę tu powtarzać szczegółowo wiedzy z poprzednich części.
Plan projektu
Zacznę od uruchomienia PIC12F683 w trybie pracy z wewnętrznym oscylatorem. Skupię się tu przede wszystkim na skonfigurowaniu jego pinów od oscylatora w roli GPIO, bo resztę już pokazywałem w poprzedniej części. Potem wykorzystam te dwa piny do komunikacji z kontrolerem wyświetlacza/klawiatury. Najpierw uruchomię pokazywanie poszczególnym cyfr, a potem odczyt klawiszy. Na tym etapie będę mieć już gotowe mniej więcej funkcje programowe do I2C, więc spróbuję je wykorzystać by dodatkowo do tej samej magistrali podłączyć prosty termometr TC74 i z tego też odczytać rezultaty. Na koniec zajmę się sprawami bardziej "zegarkowymi", czyli zaimplementuję proste odmierzanie czasu w oparciu o sprzętowy timer z PICa i ostatecznie zepnę wszystko w całość tak, by można było wyświetlać i czas i temperaturę, oraz by można było nastawiać bieżący czas przyciskami.
Projekt zostanie kontynuowany w drugiej części.
Użyte części
Podsumowując, do projektu użyję:
- PIC12F683 w obudowie DIP08 - główny mikrokontroler
- wyświetlacz z tunera z sterownikiem typu TM1650/TM1637/HD2015/itd, również podobne moduły można zakupić online, ich protokoły komunikacji są często podobne, lecz nie identyczne
- TC74 (termometr z prostym interfejsem opartym o I2C)
- PICKIT2 do programowania PICa (bądź dowolny inny programator który da radę)
- opcjonalnie, zasilacz 5V do zasilenia gotowego projektu, u mnie ze starego tunera
- opcjonalnie, analizator stanów logicznych by podejrzeć co się dzieje na liniach od I2C...
Krok 1: Blink
Na początku zawsze uruchamiam program "blink" czyli najprostsze miganie diodą LED. Pozwala mi to upewnić się, że program rzeczywiście rusza. Staram się uniknąć sytuacji, gdzie np. program jest wgrywany ale nie jest wykonywany, np. na skutek braku podłączenia pinu MCLR (RESET) zgodnie z notą katalogową. Również upewniam się wtedy, czy wgrywam wsad kompatybilny z MCU które mam na stole.
A więc zacząłem zgodnie z poprzednim tematem z serii:
PIC12F683 i SDCC - tutorial - tworzymy prosty ściemniacz (czytamy noty katalogowe)
Tutaj jednak wprowadziłem drobną poprawkę, gdyż zauważyłem, że piny "zegarowe" nie migają wraz z pozostałymi:
Trzeba wyszukać co jest nie tak. Wpisujemy "CLKIN" w wyszukiwarkę w nocie katalogowej i przeglądamy rezultaty:
Rejestr Config (słowo konfiguracyjne) określa rolę tych pinów. Chcemy ustawić oba z nich w trybie GPIO, więc dopisujemy ustawienie rejestru CONFIG na wewnętrzny oscylator bez CLKOUT:
Kod: C / C++
Cały kod:
Kod: C / C++
Działa:
Krok 2: Zdobycie wyświetlacza
Użyty wyświetlacz (a właściwie wyświetlacze, bo projekt najpierw realizowałem na jednej płytce, a po przerwie uruchomiłem na drugiej, podobnej) pochodzą ze starego tunera:
W środku zazwyczaj jest główny CPU, pamięć Flash, RAM, osobno głowica, no i interesujący mnie układ obsługujący klawiaturę/wyświetlacz:
Jednym z plusów użycia takiego sprzętu jest darmowy zasilacz o napięciu wyjścia z reguły 5V, chociaż i 3.3V na płytce (za przetwornicą step down) też się znajdzie:
W tym przypadku zasilacz jest oparty o PN8136:
Drugim plusem jest często spotykany w takich produktach wyświetlacz ze sterowaniem opartym o protokół zbliżony do I2C (ale z reguły bez adresów układu?):
Komunikacja z układem odbywa się tu tylko po dwóch liniach - SDA i SCL, dane i zegar. Komunikacja jest dwukierunkowa, SDA jest zdolne zarówno wysyłać dane, jak i je odczytywać. Zatem pora podłączyć SDA i SCL na dwie wybrane nóżki PICa i zacząć przygodę...
Jeśli natomiast chcecie taki wyświetlacz kupić, to w sieci da się znaleźć ciekawe moduły:
Krok 3: Komunikacja I2C
FD650 już ktoś na forum uruchamiał:
Sterownik 4x7seg LED - FD650B + AVR (Bascom)
FD650 nie jest zgodny ze standardem I2C, gdyż nie ma osobno swojego adresu, tylko bezpośrednio "wystawia" rejestry na magistralę. Tych wystawionych rejestrów jest kilka, więc skan I2C będzie go widzieć jako kilka urządzeń. Dodatkowo oznacza to, że nie podłączymy na jedną magistralę I2C dwóch FD650B, tak jak to można np. z MCP23017 gdy się im ustawi różne adresy...
Swoją drogą, przypomina on bardzo mocno popularne TM1650/HD2015/CH455H które już omawiałem. Odsyłam do poniższych tematów po szczegóły:
Uruchamiamy sterownik wyświetlacza/przycisków HD2015 po inżynierii wstecznej, porównanie z TM1637itd
Czytamy notę katalogową i piszemy sterownik wyświetlacza 7-segmentowego LED CH455H w Arduino
Wyświetlacze 7-segmentowe na TM1637 - 4 i 6 cyfr - Arduino, protokół
Adresy rejestrów są zgodne. Bazując na powyższych tematach, mamy:
- 0x48 - rejestr konfiguracyjny
- 0x68, 0x6A, 0x6C, 0x6E - rejestry znaków
Zatem przepisałem do SDCC mój kod ze wspomnianych powyżej tematów:
Kod: C / C++
Działa, migają segmenty pierwszej cyfry.
W celu sprawdzenia poprawności kodu całość podejrzałem na analizatorze logicznym z tematu Analizator stanów logicznych Salae 24MHz za 40 zł - analiza nieznanego protokołu wyświetlacza LED:
Krok 4: Odliczanie i znaki
W tym przypadku też pasowała mapa znaków którą opracowałem w jednym z poprzednich tematów. Jest ona też zgodna z dostępnymi komercyjnie modułami TM1650/TM1638, itd:
Kod: C / C++
Do segmentów danej cyfry (a nawet szerzej - znaku, bo mam aż do F znaki) dostajemy się po prostu indeksując tablicę daną wartością.
Na początek odpaliłem najprostsze "odliczanie":
Kod: C / C++
Rezultat:
Na bazie tego można zrobić funkcję rozdzielającą liczbę na cyfry za pomocą operacji dzielenie całkowitego i modulo; poniżej wersja trzycyfrowa:
Kod: C / C++
A tu wersja "zegarkowa", czyli dwie osobne liczby od 0 do 99:
Kod: C / C++
Z powyższą funkcją mogę już wyświetlać czas w formie HH:MM bądź MM:SS.
Krok 5: Skan I2C
Następnie postanowiłem spróbować podłączyć drugie urządzenia na moją magistralę I2C. Nie byłem pewny jak zachowa się tutaj kontroler wyświetlacza, który zasadniczo nie ma swojego pojedynczego adresu wywołania, tylko od razu odpowiada na adresy rejestrów. Czy za każdym razem będzie odpowiadać na ack?
Skaner I2C miałem z tego tematu:
Budujemy zegar na PIC18F2550, krok po kroku - część 1, BMP280, TC74, 74HCT164, I2C
Przeportowałem go do siebie i zmodyfikowałem tak, by wyświetlał odpowiadające adresy:
Kod: C / C++
Teraz przyszła pora na dwa osobne skany:
- pierwszy bez TC74 na magistrali
- drugi z TC74, tak aby poznać jego adres
Po prostu wykonałem skany i porównałem wyniki.
Otrzymałem adresy 72, 74, 76, 78, 104 i 106 bez TC74, a sam TC74 wniósł dodatkowo adres 154. Czyli jest widziany, pora na odczyt.
Uwaga organizacyjna
Ze względu na czynniki zewnętrzne musiałem projekt nieco rozłożyć w czasie i ostatecznie drugi jego etap realizowałem na nieco innej płytce od tunera. Rejestry cyfr się nie zmieniły, ale rejestr np. przycisków tak. Reszta kodu jest bez zmian. Użytą płytkę prezentowałem już w jednym z tematów:
Czytamy notę katalogową i piszemy sterownik wyświetlacza 7-segmentowego LED CH455H w Arduino
Krok 6: Odczyt z TC74
Kod odczytu TC74 mam z tematu o zegarze:
Budujemy zegar na PIC18F2550, krok po kroku - część 1, BMP280, TC74, 74HCT164, I2C
TC74 jest bardzo prosty w użyciu, temperatura jest jako liczba 8-bitowa, jeden bajt, odczytujemy ją z rejestru o adresie 0x00:
Najpierw wysyłamy do termometru adres rejestru, tutaj 0x00, poprzedzony adresem termometru z bitem W (najmłodszy bit zgaszony), a potem wysyłany odczyt bajtu, też poprzedzony adresem termometru, ale z bitem R (najmłodszy bit zapalony):
Kod: C / C++
Dla uproszczenia nie wykonałem w kodzie na razie poprawnego I2C "restart", tylko osobny "stop" i "start".
Rezultat:
Działa, u mnie w pracowni jest około 17°C.
Weryfikacja z analizatorem stanów logicznych Salae:
Krok 7: Przyciski
Zgodnie z wcześniejszą analizą:
Czytamy notę katalogową i piszemy sterownik wyświetlacza 7-segmentowego LED CH455H w Arduino
Stan przycisków jest w rejestrze o adresie 0x4F, tyle, że nie jest to po prostu mapa bitowa. Zresztą sprawdźmy:
Kod: C / C++
Na filmiku widać, że z pozoru jest coś nie tak::
Odczytane ośmiobitowe słowo okazuje się być zakodowaną informacją o jednym przycisku, a dokładniej jego kod i osobno stan wciśnięcia - wciśnięty lub zwolniony.
Kod: C / C++
Program wgrałem i zbadałem reakcje na przyciski. Na mojej płytce są trzy, ich kody to kolejno 7, 15, 23. W oparciu o to dopisałem obsługę przycisków badającą co jest wciśnięte i dla przykładu zmieniające jedną zmienną:
Kod: C / C++
Analogicznie zostanie zrobiona wkrótce edycja bieżącego czasu.
Zwyczajowo, podgląd transakcji w Pulse View:
Krok 8: Odliczanie czasu
Odliczanie czasu jest nieco trudniejsze, niż mogłoby się to wydawać. Najlepiej by było użyć do tego zewnętrznego układu RTCC (Real-Time Clock/Calendar), takiego jak np. MCP7940N:
Plusem takiego rozwiązania jest też podtrzymanie czasu nawet po utracie zasilania. Służy do tego często mała bateryjka bądź ogniwo. Nowsze PICe mają też wbudowany RTCC.
Tutaj jednak w celu uproszczenia sprawy spróbuję odmierzać czas timerem:
Tutorial PIC18F2550 + SDCC - Część 4 - Timery, przerwania
Weźmy na przykład Timer 0:
Wewnętrzny oscylator ustawiłem wcześniej na 4MHz, więc Fosc/4 to 1MHz.
Teraz można wybrać preskaler w OPTION_REG:
Wybrałem 1:256, więc inkrementacja timera nastąpi nie z częstotliwością 1MHz, lecz z 3.90625 kHz, czyli okres to 0.256ms.
Teraz jeszcze trzeba ustawić od jakiej wartości timer będzie odliczać:
Przerwanie wywoływane jest gdy wartość TMR0 się przepełni (przekroczy 255, bo to 8-bitowy rejestr), więc ustawienie TMR0 na 0 sprawi, że przerwanie wywoła się co 256 cykl timera. Skoro wiemy, że okres timera to 0.256ms, to okres 256 cykli wynosić będzie 65.53600 ms...
No i tu jest mały problem - troszkę ciężko się takie liczy dodaje, o wiele łatwiej by było móc ustawić jakiś lepiej sumujący się okres, ale w tym celu trzeba by zmienić zegar np. na klasyczne 32.768kHz.
32768Hz to nie jest wartość przypadkowa, to jest wartość 2 do potęgi 15. Jakbyśmy mieli Fosc = 32.768kHz, to Fosc/4 wynosiłaby 8.192kHz. Potem dodając preskaler 1:256 byśmy otrzymali inkrementację timera co 32Hz, więc ustawienie TMR0 na 32 by dało nam przerwanie co sekundę...
Jednakże na początek zdecydowałem się na te mniej precyzyjne, gorsze rozwiązanie i ostatecznie wyszło coś takiego - inicjalizacja timera i przerwania:
Kod: C / C++
Przerwanie timera:
Kod: C / C++
Tu widać m. in. dlaczego lepszy byłby rezonator zegarkowy - po prostu gubi się tu część czasu. Można by zliczać w częściach dziesiętnych ms, ale i tak daleko do ideału...
Kod: C / C++
W takiej sytuacji liczymy 65.5ms i na samych obliczeniach tracimy 0.036ms co sekundę. Dzień ma 86400 sekund, więc dziennie tracimy 3.1104 sekund... 90 sekund w miesiąc.
Można by próbować wprowadzić korektę programowo...
Kod: C / C++
Jednakże testy pokazały, że nie jest to idealne rozwiązanie, pewnie dlatego, że nie wziąłem pod uwagę niedokładności wewnętrznego rezonatora oraz czasu wykonywania instrukcji... Eksperymentalnie zmniejszyłem wartość 536 do 200, za każdym razem testując osobno zegar przez kilka godzin ze stoperem. Teraz w skali kilku godzin nie jestem w stanie zaobserwować spóźniania/śpieszenia, na początek starczy, bo i tak planuję zewnętrzne RTCC..
Można by też przenieść tę kalibrację do pamięci EEPROM i po prostu przez kilka dni się pobawić by to odpowiednio ustawić. Zawartość pamięci EEPROM jest pamiętana po utracie zasilania, a użyty PIC ma jej 256 bajtów.
Krok 8: Ustawienie czasu
Ustawienie czasu zrealizowałem w oparciu o pokazany wcześniej kod przycisków. Wprowadziłem zmienną określającą stan zegara (normalna praca, zmiana godziny, zmiana minuty):
Kod: C / C++
a potem osobno podłączyłem zdarzenia przycisków:
Kod: C / C++
Docelowo można tu też zapętlać wartości, godzina będzie z zakresu [0,23] a minuty z zakresu [0,59].
Dodatkowo w głównej pętli wykonuję miganie bieżąco edytowaną wartością (godzinami lub minutami):
Kod: C / C++
Dzięki temu wiemy czy i jaki tryb edycji jest włączony.
Krok 9: Dwukropek
Eksperymentalnie określiłem, że dwukropek jest podpięty do rejestru 0x6C i ma kod bitowy 32. Odpowiednio zmieniłem kod wyświetlania by zawsze ten bit zapalał:
Kod: C / C++
Krok 10: Wyświetlanie na przemian czasu i temperatury
Na koniec wprowadziłem prosty mechanizm wyświetlania na przemian czasu i temperatury. Opiera się ona na liczniku "klatek"/"odświeżeń", w każdej pętli sprawdzam indeks "klatki" i na jego bazie wyświetlam wybraną wartość. Dodatkowo sprawdzam, czy nie jest aktywny tryb edycji, jeśli tak, to zawsze wyświetlam czas.
Kod: C / C++
Efekt na ten moment
Zegar wyświetla naprzemiennie bieżący czas i temperaturę. Czas można nastawić, choć przyciski nie rozróżniają momentu wciśnięcia od ciągłego trzymania klawisza:
Podsumowanie
Chyba najciekawszym wnioskiem z całej zabawy jest to, że nawet w przypadku tych chińskich wynalazków takich jak TM1650, TM1637 itp., gdzie brakuje normalnego, zgodnego z I2C adresowania, i tak można na jedną magistralę dać kilka urządzeń. Nie mam tu oczywiście na myśli dwóch TM1650, bo ze względu na te same adresy rejestrów byłoby to niemożliwe, ale np. TM1650 + TC74 da się uruchomić.
Zegar w bieżącej formie działa, pracuje poprawnie na zasilaczu z użytej płytki, a i 5V dodatkowo też można z niej pobrać.
Oczywiście dużo można by jeszcze rozwinąć i ulepszyć, dlatego w kolejnej części planuję:
- próbę podłączenia i oprogramowania jakiegoś RTCC tak aby czas był liczony poza PICem i dodatkowo by był podtrzymywany przez bateryjkę 3V gdy odłączę sam zegar od sieci (bądź zresetuję PICa, każde wgranie nowego wsadu go resetuje)
- próbę przemyślenia i wprowadzenia jakiejś formy alarmu, może z buzzerem, też z zapisem ustawień w pamięci EEPROM, użyty PIC ma jej 256 bajtów
- próbę podłączenia jakiegoś expandera portów na I2C, o ile adresy pozwolą...
- reorganizację kodu, zwłaszcza tego sterowania i odświeżania, by było sensowniej napisane
Na razie to tyle. W kolejnej części zobaczymy na ile jeszcze starczy pamięci Flash i w jakim stopniu będę dalej w stanie ten projekt rozwinąć. Liczę chociażby na te RTCC...
Załączam "projekt" (kod i skrypt wsadowy do kompilacji) do wglądu:
Komentarze
Wszystko fajnie, ale jak z płyty takiego STB pozbyć się zbędnego obciążenia, tj. SoC i RAM? To jest chyba najtrudniejszy etap, bo trzeba precyzyjnie wylutować gęstonóżkowe układy czy BGA tak, żeby nie... [Czytaj dalej]
Jak rozumiem, pytasz o użycie gorącego powietrza do wylutu tak, aby nie było zwarć? Zwarcia usuwam plecionką, dodatkowo używam topnika. Plecionka pozwala skutecznie zebrać spoiwo z padów. Do SoC też... [Czytaj dalej]
Wau, całkiem stary skool wrx. Kłaniam się, proszę pana !!! Dodano po 3 [minutach]: . Tak jak ja... Jeśli miga, wszystko będzie dobrze ;-) [Czytaj dalej]