sobota, 18 grudnia 2021

STM32G0 - Bare Metal i zabawy z zegarkiem Microchipa MCP79410


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 :
  1. W rejestrze MODER ustaw Alternate function mode dla PA9 i PB9
  2. W rejestrze OTYPER ustaw opcję Output open-drain   dla PA9 i PB9
  3. W rejestrze PUPDR ustaw opcję  Pull-up   dla PA9 i PB9 (nie trzeba wtedy zewnętrznych R na liniach SCL i SDA I2C1)
  4. W rejestrze OSPEEDR utaw Low Speed dla PA9 i PB9
  5. 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 7AFRH  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 :



Powyższa funkcja jest funkcją testową , docelowo dodam do niej m.in obsługę błędów tak aby nie była blokująca. Poniżej jeszcze podam adresy bajtów kontrolnych dla zegarka MCP79410:

     #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



Pamiętajmy , że test bez migającej diody LED to nie test :). Dlatego dałem "toglowanie" LED znajdującym się na pinie PA8 na mojej płytce.


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 mcp79410_interface.c:




Używam mojego interfejsu do cyklicznego odczytu rejestru zegarka : 


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 mcp79410_interface.c




Aby wywołać funkcję konfiguracyjną za pomocą naszego interfejsu posłużymy się zapisem :
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

PICmajster
picmajster.blog@gmail.com



Linki :


7 komentarzy:

  1. 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ń
  2. Wesołych Świąt Bożego Narodzenia , wszelkich łask Bożych w nowym roku

    OdpowiedzUsuń
  3. Dziękuję Witek. Zdrowych Świąt Bożego Narodzenia i wszystkiego dobrego Tobie i Twoim Bliskim.Niech moc Chrystusa będzie z Tobą.

    OdpowiedzUsuń
  4. 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ń
  5. Gdzie Pany kupujecie podzespoły elektroniczne ze sprawdzonego źródła ? bo tme świeci pustkami.

    OdpowiedzUsuń
  6. 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ń
  7. 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ń