W procesie projektowania i budowania mojego super zegarka , który będzie poligonem doświadczalnym w zakresie programowania , chodzą mi po głowie różne pomysły. Jednym z takich pomysłów jest zastosowanie dwóch MCU z dwóch konkurencyjnych firm tj. STM i Microchip. Ponieważ docelowo mój zegarek będzie miał dwie płytki : front i tył, dlatego jedną płytkę obsadzę STM32G0 a drugą PIC32CM lub ATSAML. Tym samym podnoszę sobie poprzeczkę i nie pójdę na łatwiznę w postaci MCU 8 bit Microchipa. Płytka frontowa zegarka z STM32G0 jest już na ukończeniu. Zabieram się za uruchomienie zegarka MCP79410 przy pomocy MCU STM32G071. Programuję w stylu Bare Metal czyli za pomocą rejestrów bez użycia HAL .
W uzupełnieniu informacji , STM32G0 posiada na pokładzie wydzielony moduł RTC, który można w prosty sposób aplikować w zegarkach. Ja jednak chcę wykorzystać w swoim zegarku zewnętrzny moduł w postaci MCP79410.
W artykule skupię się na uruchomieniu zegarka MCP79410 i zbudowaniu podstawowych funkcji do rozmowy z nim. Moja baza narzędziowa to :
- moja autorska płytka deweloperska dla STM32G0 z STM32G071KBT6 32-piny na pokładzie .
- programator J-LINK EDU-mini ,plus kabelek przejściówka JTAG: 1,27mm na IDC20 o rastrze 2,54 mm
- IDE SEEGER Embedded Studio
- płytka stykowa
- MCP79410 w obudowie SOIC wlutowany w uniwersalna płytkę plus osprzęt typu kwarc zegarkowy
- analizator stanów logicznych
Typowa aplikacja dla MCP79410 wygląda jak poniżej :
MCP79410 jest prostym w użyciu i obsłudze zegarkiem. Na pokładzie tego małego układziku znajdziemy m.in pamięć EEPROM o rozmiarze 1 Kbit. Jeśli nie wykorzystujemy wejścia bateryjnego to trzeba je podłączyć do masy (do testów tak zrobiłem).
Piny MCU jakie wykorzystuję to :
- PA9 jako I2C1_SCL
- PB9 jako I2C1_SDA
O STM32G0 i I2C pisałem już tutaj STM32G0 i I2C.
Nie będę się zbytnio skupiał na szczegółach ale pokażę kolejność kroków niezbędnych do rozmowy z MCP79410 , przykładowe kody i jak taki projekt do testowania zegarka może wyglądać.
Tworzymy sobie nowy projekt w IDE SEEGER-a dla STM32G071KBT6. Mój szablon projektu widoczny po lewej stronie na obrazku poniżej. Staram się aby w każdym projekcie wyglądał on mniej więcej podobnie . Po prawej stronie funkcja inicjalizująca zegary od której zaczynam pisanie kodu :
Oczywiście zapis do rejestru IOPENR robimy w jednym wierszu a nie rozwlekamy go na trzy wiersze :).
Aby nauczyć się kodowania na rejestrach musimy mieć przed oczyma budowę fizyczną rejestru i jego elementy składowe. Dlatego Manual dla naszego MCU musimy często odwiedzać. Widząc zapisy powyższe na rejestrach nie kopiujemy ich na pałę ale zaglądamy do manuala o co tam chodzi w konkretnym rejestrze. Im bardziej się opatrzymy z rejestrami w manualu tym szybciej nasz mózg przestawi się na Bare Metal :).
W powyższej funkcji inicjalizującej nadmiarowo odpalam zegar dla wszystkich portów GPIO. Walić tę ekologię, bo przez nią ludzie będą w przyszłości biedniejsi. Ubóstwo energetyczne w Niemczech przyszło wraz z eko-energią z paneli i wiatraków. 3 mln Niemców nie stać na ogrzewanie domów w zimie. Nas to też czeka tylko pewnie w większej skali.
Teraz pokażę co ja robię z taką funkcją jak powyżej. Poniżej plik gdzie zbieram wszystkie funkcje konfiguracyjne MCU i opakowuję je jedną funkcją SYSTEM_MANAGER_Initialize(), którą potem odpalam w pliku main.c
Po napisaniu funkcji konfiguracyjnej zegary w naszym projekcie, czas na konfigurację pinów PA9 i PB9 do współpracy z I2C1.
Kolejne kroki to :
- W rejestrze MODER ustaw Alternate function mode dla PA9 i PB9
- W rejestrze OTYPER ustaw opcję Output open-drain dla PA9 i PB9
- W rejestrze PUPDR ustaw opcję Pull-up dla PA9 i PB9 (nie trzeba wtedy zewnętrznych R na liniach SCL i SDA I2C1)
- W rejestrze OSPEEDR utaw Low Speed dla PA9 i PB9
- W rejestrze AFR utaw I2C1 dla pary PA9 i PB9
Cztery pierwsze punkty są zbyt proste aby się na nich zatrzymywać. Skupmy się na punkcie 5-tym czyli jak ustawić funkcjonalność I2C1 na pinach PA9 i PB9. A do tego służy rejestr AFR.
Rejestr ten składa się z dwóch rejestrów AFRL i AFRH , rejestr AFRL dotyczy pinów od 0 do 7 a AFRH pinów od 8 do 15. Ponieważ nasze piny mają numerki 9 (PA9 i PB9) to zapisów będziemy dokonywać w rejestrze AFRH. W rejestr ten wpisujemy jakieś magiczne AF0...AF7. Aby rozszyfrować tę zagadkę musimy w tym jednym przypadku zajrzeć nie do manuala ale do datasheet tam odnajdujemy tabelki opisane jako alternate function mapping przykładowo dla PORTA tabelka wygląda jak poniżej :
Dla PA9 odnajdujemy funkcję I2C1_SCL i ta kolumna jest oznaczona jako AF6 . Teraz wracamy do rejestru AFRH i ustawiamy w nim pole bitowe AFSEL9 (bo pin nr 9) na wartość 0b0110 przyporządkowaną dla AF6 (patrz opis pod rejestrem AFRH w manualu).
Samo odwołanie w kodzie do rejestru AFRH wygląda przykładowo tak :
GPIOA->AFR[1]
a dla AFRL jak poniżej
GPIOA->AFR[0]
Sprawę konfiguracji pinów mamy z głowy. Teraz czas zająć się funkcją konfiguracyjną dla I2C1. Moja propozycja takiej funkcji wygląda jak poniżej :
Kolejnym krokiem jest napisanie funkcji hardware dla I2C1 realizującą fizyczną transmisję po I2C. W pierwszej kolejności zajmę się funkcją wysyłającą dane na magistralę I2C . W tym momencie warto zaznajomić się z konwencją rozmowy z zegarkiem podczas zapisu a wygląda to tak :
Po starcie na I2C1 leci bajt kontrolny potem zegarek wystawia ACK następnie leci bajt reprezentujący adres rejestru w zegarku po nim ACK i na koniec bajt z danymi jakie chcemy zapisać do rejestru zegarka. Potem ACK i STOP. W takim schemacie transmisji piszę funkcję testową jak poniżej :
#define ADDR_EEPROM_WRITE 0xAE // DEVICE ADDR for EEPROM (writes)
#define ADDR_EEPROM_READ 0xAF // DEVICE ADDR for EEPROM (reads)
#define ADDR_RTCC_WRITE 0xDE // DEVICE ADDR for RTCC MCHP (writes)
#define ADDR_RTCC_READ 0xDF // DEVICE ADDR for RTCC MCHP (reads)
Teoretycznie mógłbym już zakończyć i użyć funkcji hardware I2C1_Write() do rozmowy z zegarkiem ale chciałbym aby ta rozmowa była przeprowadzona w sposób bardziej kulturalny na wyższym poziomie . Dlatego stworzę sobie interfejs do tej rozmowy. Interfejsy to bardzo fajny wynalazek programistyczny , warto ich używać w swoich programach bo skutecznie bronią nas przed pisaniem kodu typu spaghetti. Do projektu dodaję dwa pliki o nazwie mcp79410_interface.h i mcp79410_interface.c . Moim docelowym zamiarem jest uzyskanie zapisu jak poniżej :
mcp79410.WriteRegisterRTCC(ADDR_CTRL, 0x80);
mcp79410.ReadRegisterRTCC(ADDR_CTRL);
czyli uzyskanie takiej quasi obiektowości. Na obiekcie mcp79410 mogę dokonywać różnych operacji i taka jest idea interfejsu.
Plik mcp79410_interface.h
Powołuje typ strukturalny mcp79410_interface_t zbudowany ze struktury a w niej na razie dwa wskaźniki na funkcje narzędziowe. Do tych wskaźników przypisałem funkcje do zapisu RTCC i EEPROM w zegarku . Na końcu deklaruję nasz "obiekt" mcp79410. Oczywiście zamiast powoływać kolejne wskaźniki na funkcje o identycznej budowie funkcji, można użyć typów co zoptymalizuje nam kod.
Plik mcp79410_interface.c
W pliku tym wypełniam nasz "obiekt" mcp79410 treścią czyli przypisuję do poszczególnych wskaźników na funkcje konkretne funkcje. Ale nie pokazałem jeszcze tych funkcji narzędziowych. Znajdują się one w pliku mcp79410.c i wyglądają jak poniżej :
W pliku mcp79410.h mamy wszystkie definicje dotyczące zegarka MCP79410 , fragment pliku poniżej :
Ostatnim etapem projekciku do testów zegarka MCP79410 jest napisanie testu :). Test będzie polegał na cyklicznym zapisie do wybranego rejestru zegarka i podglądzie na analizatorze stanów czy transmisja przebiega zgodnie z oczekiwaniem. Moja propozycja takiego testu poniżej w pliku main.c
Podłączę wszystkie kabelki i zobaczę czy to działa, bo na tym etapie nie wiem :). No cóż działa a poniżej dowód życia :
Zapis do zegarka mam zrealizowany. Kolejnym krokiem będzie zbudowanie funkcji narzędziowej do odczytu z rejestru zegarka. Zerknijmy zatem jak taki odczyt powinien być realizowany na podstawie dokumentacji MCP79410 :
Po starcie na I2C1 leci bajt kontrolny ADDR_RTCC_WRITE potem zegarek wystawia ACK ,następnie leci bajt reprezentujący adres rejestru w zegarku, po nim ACK . Potem restart i wysyłamy bajt kontrolny ADDR_RTCC_READ i czekamy na bajt danych przesłanych zwrotnie przez zegarek . W takim schemacie transmisji piszę funkcję testową jak poniżej :
Warto tutaj nadmienić, że w trybie odczytu danych , STM32G0 po restarcie I2C , automatycznie wysyła bajt kontrolny ze zmienionym sprzętowo bitem R/W na podstawie adresu jaki umieściliśmy w rejestrze SADD. Brawo STM32G0.
Funkcję testową I2C dla odczytu danych z zegarka, dodaję do pliku mcp79410.c i na podstawie tej funkcji uzupełniam mój interfejs do kulturalnej rozmowy z zegarkiem. I tu ukazuje się potęga interfejsu , który możemy dowolnie kształtować, podmieniać funkcje etc.
Uzupełniam mój interfejs o nowo stworzoną funkcję w tym celu modyfikuje pliki mcp79410_interface.h i mcp79410_interface.c:
Poniżej dowód życia działania funkcji odczytującej rejestr . Odczyt z rejestru ADDR_CTRL. Standardowa wartość tego rejestru to 0x80:
Wszystko działa zgodnie z oczekiwaniem i mam pełne zadowolenie z osiągniętego efektu :). Mając funkcje do zapisu i odczytu z MCP79410, zegarek stoi przed nami otworem. Możemy go konfigurować w/g potrzeb i odczytywać czas :)
Jako ciekawostkę wspomnę, że STM ma w planach produkcję STM32G0 z pamięcią RAM na poziomie 144 kB. Co idealnie się nada na zabawy z RTOS-ami.
Zróbmy sobie jakąś konfigurację zegarka i zbudujmy funkcję inicjalizującą zegarek . W tym celu należy zajrzeć do rejestrów MCP79410: CONTROL i RTCWKDAY
W rejestrze CONTROL interesują nas dwa pola SQWEN i SQWFS. Ustawienie bitu w SQWEN spowoduje generowanie na wyjściu MFP zegarka MCP79410 fali prostokątnej . W polu bitowym SQWFS ustawiamy częstotliwość dla tej fali. Nas interesuje ustawienie 1 Hz. Po co to wyjście jest nam potrzebne ?. Kluczowym zagadnieniem przy wyświetlaniu czasu jest synchronizacja pomiędzy wyświetlaniem czasu a jego aktualizacją. Problem ten rozwiązuje nam wyjście MFP zegarka, które generuje przebieg o częstotliwości 1Hz. Na opadającym lub narastającym (w.g konfiguracji w rejestrze CONTROL) zboczu tego sygnału wyzwalamy przerwanie zewnętrzne mikrokontrolera .
W funkcji obsługi tego przerwania ustawiamy flagę programową , za pomocą której aktywujemy odświeżanie czasu na wyświetlaczu.
Ustalmy wartość jaką chcemy zapisać do rejestru CONTROL : 0b11000000 / HEX : 0xC0
W przypadku rejestru RTCWKDAY nie możemy wpisać całej wartości ponieważ rejestr ten zawiera flagę statusową OSCRUN. Dlatego najpierw należy odczytać zawartość tego rejestru i odczytaną wartość zmodyfikować w/g potrzeb. Tutaj głównie interesuje nas ustawienie VBAT (on/off).
Nasza funkcja inicjalizująca dla zegarka będzie wyglądała jak poniżej :
Dołączmy ją do naszego interfejsu. W tym celu modyfikujemy pliki mcp79410_interface.h i mcp79410_interface.c
mcp79410.InitRTCC(); . I tym sposobem nasz interfejs się rozbudowuje.
Ostatnim zagadnieniem jest odczyt czasu i daty z naszego zegarka. Poniżej propozycja funkcji do tego celu.
Ponieważ z zegarka odczytujemy czas w kodzie BCD dlatego przydać się może konwersja do postaci DEC . Funkcje odczytu czasu pakujemy do interfejsu czyli najpierw deklarujemy wskaźniki o przyjaznych nazwach a potem przypisujemy tym wskaźnikom nasze funkcje :
Przykład użycia interfejsu do pobrania godziny :
mcp79410.getTime_HOUR();
Pozdrawiam
Tak się zastanawiam - skoro masz RTC ze wszystkimi funkcjonalnościami wbudowany w stm32, to po co Ci zewnętrzny RTC? Wystarczy zewnętrzny kwarc 32,768kHz + bateria :)
OdpowiedzUsuńWesołych Świąt Bożego Narodzenia , wszelkich łask Bożych w nowym roku
OdpowiedzUsuńDziękuję Witek. Zdrowych Świąt Bożego Narodzenia i wszystkiego dobrego Tobie i Twoim Bliskim.Niech moc Chrystusa będzie z Tobą.
OdpowiedzUsuńDo zastanawiającego się Gala Anonima :). Zegarek w STM32G0 jest obecny jak najbardziej ale co poradzisz , że ja lubię MCP79410 i większą przyjemność sprawia mi obcowanie z nim niż z rejestrami zegarkowymi w STM32G0. Poza tym myślę, że więcej ludzi skorzysta z artykułu o zegarku MCP79410 niż o zegarku w STM32G0. Mając na uwadze ,że ktoś może nie mieć w swoim MCU, zegarka. Noszę się, też z zamiarem zrobienia modułu z MCP79410 dla złącza mikroBus i to też jest przyczynek do poobcowania z nim.
OdpowiedzUsuńGdzie Pany kupujecie podzespoły elektroniczne ze sprawdzonego źródła ? bo tme świeci pustkami.
OdpowiedzUsuńMamy czasy , że na części trzeba teraz polować :). Kto sprytniejszy w polowaniu ten przeżywa :). Czasami rzeczy których zabrakło w TME można zobaczyć w AVT. Pamiętajmy też , że cena z dnia na dzień może wzrosnąć. MCU STM32 , którymi ja się interesowałem w skali 30 dni poszły ok 30% up. A to dopiero początek tak myślę. Jak nowe podwyżki cen energii wejdą w cenę towarów to co teraz wydaje nam się drogie to fatamorgana :). Obyśmy Turcji nie dogonili w parametrach finansowych :)
OdpowiedzUsuńWow, that is quite informative. I like this article very much. The content was good. If any of the engineering students are looking for a pic microcontroller projects, I found this site and they are providing the best service to the engineering students regarding the projects pic microcontroller projects
OdpowiedzUsuń