Moja koncepcja dla zegarka od strony sprzętowej była taka. Buduję moduł na bazie płytki Nucleo z modułem sieciowym Wiznet W5500 i modułem radiowym lora i to byłby mój "rozsiewca czasu" połączony po LAN z routerem WiFi. Czas będzie pozyskany z wybranego serwera NTP. Zegarki będą doposażone w moduł lora i drogą radiową będą odbierały sygnał z aktualnym czasem i będą się do niego synchronizować. Oczywiście taka synchronizacja nie musi być robiona co sekundę ale np. raz dziennie.
Platformę sprzętową buduję w oparciu o moją nową płytkę , która umożliwia w wygodny sposób połączenie modułów w standardzie Mikroelektroniki z Nucleo 64 . Poniżej komponenty , których użyję do zabawy :
- Płytka Nucleo -G070RB
- Moduł Ethernetowy Wiznet W5500
- Moduł CP2102 do komunikacji PC-UART.
- Moja płytka platforma
Tak przy okazji widać ogromną zaletę mojej płytki, ponieważ nie blokuje ona dostępu do górnych pinów Nucleo.
Komunikacja z serwerem NTP wygląda z grubsza następująco : wysyłamy do serwera zapytanie w postaci znormalizowanej protokołem NTP struktury/ramki, która ma wymiar 48 bajtów. Pierwszy bajt musi być wypełniony danymi , pozostałe wyzerowane. Ramkę zapytania wysyłamy protokołem sieciowym UDP . W odpowiedzi na nasze zapytanie serwer wysyła zwrotnie identyczną postać ramki ale wypełnioną danymi. Czyli wysyłamy 48 bajtów i otrzymujemy zwrotnie 48 bajtów.
Nas interesować będzie tylko to co znajduje się w polu opisanym jako : Transmit Timestamp bo tam będzie się znajdować informacja o aktualnym czasie. Jest to pole 64 bitowe gdzie połowa tego pola czyli 32 bity przekazuje nam informację o ilości sekund jakie upłyneły od początku stulecia czyli od 1 stycznia 1900 do chwili zapytania. Druga połowa przechowuje tzw. frakcje sekundy. Wartość 1 frakcji reprezentuje 1/(2^32) sekundy, czyli 0.2 nanosekundy. Niektóre źródła o NTP błędnie podają co znajduje się w polu frakcje.
Dla systemów UNIX punktem startowym do odliczania czasu jest 1 stycznia 1970 r. Dlatego rozróżniamy dwa systemy wyliczania czasu , czas NTP (od 1.01.1900) i czas UNIX (od 1.01.1970).
Dobra te minimum teorii wystarczy. Teraz zajmijmy się kodem. Mam już uruchomiony moduł Wiznet W5500 i moduł UART CP2102. Moduł W5500 służy nam do komunikacji sieciowej i jest kablem Ethernetowym połączony z domowym routerem wifi. Moduł CP2102 wysyła nam po UART do PC komunikaty m.in o stanie inicjalizacji modułu W5500 / sieci etc. Od strony PC posługuję się programem RealTerm . Program piszę w STM32Cube IDE i posługuję się rejestrami. Kod implementuję z artykułu Davida Lettier'a.
Program cyklicznie co 5 sekund wysyła zapytanie do serwera NTP i obsługuje przychodzącą odpowiedź. Podgląd tego co się dzieje zapewniają mi komunikaty wysyłane po UART do PC. Ot i cała funkcjonalność programu z grubsza.
Na początku deklarujemy typ strukturalny, który odzwierciedla budowę ramki NTP. Potrzebować też będziemy "odległości" w sekundach pomiędzy 1.01.1900 a 1.01.1970 wymiar ten podstawiam pod NTP_TIMESTAMP_DELTA. Dodatkowo musimy mieć funkcję , która konwertuje nam odebrane dane z serwera z big-endian na little-endian. Plik z deklaracjami poniżej :
Plik z definicjami poniżej :
Zmienna packet o typie strukturalnym ntp_packet będzie zawierać fizyczny obraz ramki NTP. W momencie definicji zmiennej packet zerujemy wszystkie pola struktury. Funkcja ntp_packet_initialize to funkcja, która przygotowuje fizyczny obraz ramki NTP do wysłania. Czyli zeruje wszystkie pola a potem przypisuje do pierwszego pola struktury wartość 0x1b co reprezentuje ustawienia : li = 0 , vn = 3, mode = 3 . Te pierwsze pole musi być wypełnione danymi a co one oznaczają doczytać możemy w linku.
Serwer NTP , który będę odpytywał o czas to serwer Głównego Urzędu Miar : tempus1.gum.gov.pl adres: 194.146.251.100 . Komunikację sieciową nawiązujemy na porcie 123.
Całą magię komunikacji sieciowej załatwia nam w przyjazny sposób moduł W5500. Nie musimy się martwic o stos sieciowe etc. W pierwszym kroku nawiązywania komunikacji sieciowej z serwerem NTP jest otwierane gniazdo sieciowe. Poniżej zaznaczyłem fragment kodu odpowiedzialny za tę operację :
Po otworzeniu gniazda sieciowego program przechodzi do sekcji w której wysyłamy, odbieramy jeśli coś przyszło i konwertujemy odebrane dane na obraz czasu przyjazny użytkownikowi. Kod tej funkcjonalności poniżej :
Cała obsługa sieciowa jest zawarta w jednej funkcji UDP_Serwer. Funkcja ta jest wywoływana z pętli głównej programu. Co 5 sekund odmierzanych przez SoftTimer oparty na SysTicku wysyłane jest zapytanie do serwera NTP, zapytanie to nasze 48 bajtów z wypełnionym pierwszym bajtem a z pozostałymi wyzerowanymi. Jeśli serwer NTP będzie nam uprzejmy odpowiedzieć przechwycimy tę odpowiedź i umieścimy ją w strukturze packet. Dodatkowo po UART otrzymamy informację , że odpowiedź otrzymaliśmy i adres IP z którego przyszła odpowiedź. Dane , które nas interesują to sekundy w polu struktury txTm_s i frakcje sekund txTm_f. Ale tak naprawdę to tylko sekundy nas będą interesować , frakcje pomijamy bo dokładność sekundowa nam wystarczy. Surowe dane konwertujemy za pomocą funkcji nthol(), ustawiając właściwą kolejność bajtów big-endian/little-endian.
Nadal jednak mamy dane w postaci sekund jakie upłynęły od początku 1.01.1900 a potrzebujemy mieć czytelne info w postaci roku, dnia, minuty i sekundy, dodatkowo chcemy to wyświetlić na terminalu w postaci uporządkowanej . Aby tego dokonać wykorzystamy bibliotekę systemową <time.h> , która dostarcza funkcję obsługi czasu. Wykorzystamy z tej biblioteki funkcję ctime() , która dokona konwersji z postaci sekundowej na string z uporządkowanym obrazem czasu.
W tym celu tworzymy zmienną txTm o typie time_t . Typ time_t jest przystosowany do operowaniu na czasie UNIX-owym czyli rozumie start odliczania upływu czasu od 1.01.1970 r. dlatego w kodzie pojawia się zapis :
#include <time.h>
time_t txTm
txTm = ( time_t ) ( packet.txTm_s - NTP_TIMESTAMP_DELTA );
wyświetlamy na terminalu uporządkowany czas :
printf( "\r\n Time: %s \r\n", ctime( ( const time_t* ) &txTm ) );
Na końcu zerujemy i inicjalizujemy pakiet NTP.
Efekt końcowy widać na terminalu :
Przy pierwszym zapytaniu serwer nam odpowiedział dopiero za drugim razem, potem już odpowiadał za każdym razem. Może przy pierwszym kontakcie z serwerem musimy być zweryfikowani przez ABW, CBA, CIA etc.
Mój plik main.c wygląda następująco :
I to wszystko w temacie. Operacja pozyskania czasu z serwera NTP powiodła się. Sam byłem zdziwiony, że tak łatwo poszło i to od pierwszego kopa. Kolejny etap to przesłanie czasu drogą radiową do moich zegarków za pomocą modułów Lora. Oczekuję tutaj fajnej zabawy.
Jeszcze na koniec wspomnę, że serwer NTP dostarcza nam czasu UTC czyli aby uzyskać prawidłowy czas na naszej Polskiej ziemi to czas letni UTC + 2 h , czas zimowy UTC + 1h .
PICmajster
picmajster.blog@gmail.com
Brak komentarzy:
Prześlij komentarz