Różny czas pomimo synchronizacji z NTP

Eksperymentowałem ostatnio z synchronizacją czasu w układzie z Atmegą i ENC28J60. Użyłem gotowych funkcji zaimplementowanych w stosie tuxgraphics.org, z niewielkimi modyfikacjami (głównie przeliczanie odebranej wartości na Unix epoch. Generalnie rozwiązanie jest bardzo proste. Programowy licznik odlicza w dół liczbę sekund pomiędzy kolejnymi synchronizacjami (w tej chwili

3600). Gdy dojdzie do zera wysyłany jest pakiet z requestem. Jednocześnie w momencie przyjścia pakietu UDP funkcja sprawdza, czy mamy do czynienia z odpowiedzią - jeśli tak, to do zmiennej volatile int32_t rtc ładowany jest odebrany czas. Obliczanie wartości wygląda następująco: *time = ( ( ((uint32_t)buf[0x52]<<24) | ((uint32_t)buf[0x53]<<16) | ((uint32_t)buf[0x54]<<8) | ((uint32_t)buf[0x55]) ) - 2208988800UL )

Pomiędzy synchronizacjami zegar jest inkrementowany w przerwaniu timera. Przerwanie jest wywoływane co 1ms - normalnie zwiększana jest zmienna pomocnicza, jednak w przypadku przekroczenia przez nią wartości 1000 uruchamia się obsługa zegara i kilku programowych liczników.

Program zdaje się działać prawidłowo, jednak byłem ciekaw na ile jest dokładny. Napisałem prosty skrypt na Raspberry Pi, który odczytuje czas z omawianego urządzenia i odejmuje go od czasu systemowego w RasPi. Wyniki są dziwne. Nieraz mam kilka sekund różnicy, czasem jednak pojawia się wynik w granicach 60 sekund. Niekiedy większą różnicę obserwuję tuż po synchronizacji.

Nie wydaje mi się, żeby mogło to wynikać z niedokładności timera Atmegi

- niewielki dryf ie objawiłby się tak szybko. No chyba, że jeszcze zegar w RasPi ma tak tragiczną stabilność.

Gdzie mogę szukać przyczyn takiego zachowania?

Reply to
Atlantis
Loading thread data ...

Czy raspi ma synchroniziwany zegar? Bo nie rozumiem co mierzysz teraz. Dokładność zegara w tym Twoim urządzeniu czy niedokładnośc raspi?

Reply to
Marek

W dniu 2014-11-12 o 01:35, Atlantis pisze:

A obsługujesz poprawnie zapis i odczyt wartości większych niż 8 bitów? Wyłączenie/włączenie przerwań na czas zapisu i odczytu? Tu bym zaczął, sprawdził bym też czy na pewno w obliczeniach używasz zmiennych o odpowiedniej wielkości, czy są ze znakiem itd.

Reply to
Andrzej W.

W dniu 2014-11-12 09:12, Marek pisze:

Musi mieć. RasPi w fabrycznej konfiguracji nie ma nawet RTC - czas brany jest z NTP przy starcie systemu.

Reply to
Atlantis

Ale co ile ta synch. później po starcie jest? Bo to normalne, że jak będzie rzadko to się szybko rozjedzie (czas jest liczony bo zwykłym przerwaniu timera), linux w raspi nie jest rt, przynajmniej w najpupolarniejszych dtstrybucjach dla raspi. Badasz względem siebie dwa niepewne urządzenia. Najpierw sprawdź to z encj wzgl. zaufanego wzorca czasu, później raspi wzgl. tego wzorca, dopiero wtedy wyciągaj wnioski.

Reply to
Marek

W dniu 2014-11-12 10:36, Marek pisze:

Zdaje sobie z tego sprawę. Jednak minuta dryfu na godzinę!?

Reply to
Atlantis

W dniu 2014-11-12 09:45, Andrzej W. pisze:

Jak mam to rozumieć?

Dodałem cli() i sei() w każdej partii kodu, gdzie pojawia się odwołanie do zmiennej rtc. Nie wydaje mi się, żeby pomogło.

Zmienna rtc to volatile uint32_t. Obliczanie jej wartości na podstawie informacji z pakietu (jak już wspominałem) wygląda następująco:

*time = ( ( ((uint32_t)buf[0x52]<<24) | ((uint32_t)buf[0x53]<<16) | ((uint32_t)buf[0x54]<<8) | ((uint32_t)buf[0x55]) ) - 2208988800UL );

"time" to po prostu wskaźnik przekazujący wynik obliczeń wykonywanych wewnątrz funkcji.

Reply to
Atlantis

Hmm.... Wygląda na to, że Raspberry Pi pokazuje dokładnie ten sam czas, który widać np. na epochconverter.com albo currenttimestamp.com. To na układzie z Atmegą pojawia się jakaś dziwna różnica, wynosząca od kilku do kilkudziesięciu sekund. Mam wrażenie, że nie narasta ona w wyniku dryfu wprowadzanego przez timer - zwykle jest ona widoczna już po aktualizacji czasu, którą mogę też inicjować ręcznie.

Kod funkcji analizującej pakiet NTP w tej chwili wygląda następująco:

uint8_t client_ntp_process_answer(uint8_t *buf, volatile uint32_t

*time,uint8_t dstport_l){

uint32_t tm_temp=0; if (dstport_l){ if (buf[UDP_DST_PORT_L_P]!=dstport_l){ return(0); } } if (buf[UDP_LEN_H_P]!=0 || buf[UDP_LEN_L_P]!=56 || buf[UDP_SRC_PORT_L_P]!=0x7b){ // not ntp return(0); } // copy time from the transmit time stamp field: tm_temp = ( ((uint32_t)buf[0x52]<<24) | ((uint32_t)buf[0x53]<<16) | ((uint32_t)buf[0x54]<<8) | ((uint32_t)buf[0x55]) ); tm_temp -= 2208988800UL; cli(); *time = tm_temp; //*time = ( ( ((uint32_t)buf[0x52]<<24) | ((uint32_t)buf[0x53]<<16) | ((uint32_t)buf[0x54]<<8) | ((uint32_t)buf[0x55]) ) - 2208988800UL ); //0x83AA7E80 sei(); return(1); }

Reply to
Atlantis

Użytkownik "Atlantis" napisał w wiadomości W dniu 2014-11-12 10:36, Marek pisze:

Rozumiem ze czas tam jest mierzony przerwaniem programowym na podstawie czestotliwosci zegarowej uP. Jest jakis mechanizm ze to przerwanie jest rowno generowane, i nie wplywa np na niego opoznienie jego obsluzenia ?

Czestotliwosc zegarowa - jesli kwarcem, to powinna byc w miare dokladna. Tak powiedzmy do 100ppm, jesli dales byle jakie elementy. Czyli sekunda na trzy godziny. Ale jesli to nie kwarc, tylko jakis gorszy oscylator, to kto wie czy nie 1% dokladnosci. Ale to powinienes widziec ze dryfuje a nie skacze.

Porownujesz z jakims innym urzadzeniem ... ale jakim ? Moze tam jest numer ze np odczytujesz koncowke 59 sekund, a minuty juz po zmianie ...

J.

Reply to
J.F.

W dniu 2014-11-12 o 12:10, Atlantis pisze:

Ten kod to interpretuje każdy pakiet UDP co ma 56 bajtów, tak? Żeby czytać właściwy pakiet warto sprawdzić czy odpowiedź zawiera znacznik czasu wysłany przez Ciebie w zapytaniu. (RFC4330 Originate Timestamp). Ja u siebie czytam czas z pozycji [40:43], widzę, że Ty czytasz [82:85]. Ten bufor to "czyste" dane z pakietu UDP, czy jest tam też jakiś nagłówek? Bo czytasz znacznie dalej niż długość pakietu UDP.

Reply to
Andrzej W.

W dniu 2014-11-12 12:17, J.F. pisze:

Tak. Przerwanie TIMER0_COMPA_vect jest wywoływane co 10ms. W procedurze jego obsługi inkrementowana jest zmienna. Jeśli jej wartość przekroczy

100, wykonywana jest instrukcja warunkowa wewnątrz której zwiększana jest wartość zegara (a także obsługiwane jest kilka dodatkowych programowych timerów).

MCU jest taktowany sygnałem 12,5MHz, pochodzącym z układu ENC28J60 który z kolei jest taktowany kwarcem 25 MHz. Niedokładność wedle mkAVR Calculatora wynosi 0,06%.

Zresztą zachowanie układu wcale nie wygląda na dryf spowodowany niedokładnością wzorca. Opóźnienie wynoszące od kilku do kilkudziesięciu sekund często mam tuż po synchronizacji.

Porównałem z webowymi wzorcami (np. currenttimestamp.com). Pokazują dokładnie to samo, co Raspberry Pi. Czas na Atmedze prawie zawsze różni się o pewną liczbę sekund...

Nie dokonuję konwersji na "human readable time". Zależy mi tylko na timestampie, najlepiej w formacie Unix epoch.

Reply to
Atlantis

W dniu 2014-11-12 12:36, Andrzej W. pisze:

Sprawdza też jeszcze, czy zgadza się port.

Z tego co pamiętam, to w buforze znajduje się kompletna ramka Ethernet/MAC, pobrana bufora ENC28J60. Tuxgraphics to mocno uproszczony stos, który operuje właśnie na pojedynczych ramkach i nie jest w stanie obsługiwać dłuższych komunikatów.

W każdym razie nie sądzę, żeby tutaj leżała przyczyna. Gdyby do układu "zabłąkał się" przypadkowy pakiet UDP o oczekiwanej długośc, wysłany na oczekiwany port, to otrzymywałbym po prostu bezsensowne dane. Ja tymczasem otrzymuję wartość zbliżoną do obecnego czasu, różniącą się o tych kilka do kilkudziesięciu sekund.

Reply to
Atlantis

Jesteś pewien, że prawidłowo interpretujesz otrzymane dane? Czy czas z serwera ntp już jest przesunięty czy przesunięcie następuje podczas lokalnego odczytu tuż po odbiorze (ta funkcja licząca *time)?

Reply to
Marek

W dniu 2014-11-12 12:59, Marek pisze:

Tego właśnie nie wiem. W każdym razie patrzę na kod i nie widzę co mogłoby być nie tak. Zastosowałem trochę inny, jaśniejszy sposób przeliczania danych z bufora na timestamp:

uint32_t sec_since_1900 = 0; (...) sec_since_1900 = (uint32_t)buf[0x52] << 24; sec_since_1900 |= (uint32_t)buf[0x53] << 16; sec_since_1900 |= (uint32_t)buf[0x54] << 8; sec_since_1900 |= (uint32_t)buf[0x55]; cli();

*time = (sec_since_1900 - 2208988800UL); sei();

Efekt jest dokładnie taki sam.

Gdybym czytał dane z nieprawidłowych komórek bufora, to w efekcie otrzymywałbym kompletnie bezsensowną wartość, a nie wynik różniący się od obecnego czasu o kilka-kilkadziesiąt sekund.

Raspberry Pi jest synchronizowane z tego samego serwera czasu.

Reply to
Atlantis

W dniu 2014-11-12 o 13:49, Atlantis pisze:

W pakiecie SNTP masz chyba cztery różne znaczniki czasu, jakaś szansa na to by odczytać coś sensownego z innej lokalizacji istnieje, jednak nie spodziewał bym się tam różnicy 60 sekund a raczej jednej dwóch. Chyba, że robisz wielokrotne uaktualnienie czasu i za każdym razem wczytujesz czas nadania swojego zapytania (Originate Timestamp).

Reply to
Andrzej W.

Zauważyłem jeszcze jedną ciekawą rzecz. Jeśli przeprowadzę dłuższą serię ręcznych synchronizacji czasu, to różnica się niweluje i wynosi 0-1 sek. Ta jedna sekunda może już po prostu wynikać z różnicy w czasie, jaki jest potrzebny do wykonania skryptu.

Teraz wyłączyłem automatyczną synchronizację i zobaczę jak się będzie zachowywał czas.

Procedura wysyłająca request wygląda następujaco:

void client_ntp_request(uint8_t *buf,uint8_t *ntpip,uint8_t srcport,uint8_t *dstmac) { uint8_t i=0; uint16_t ck; if (!enc28j60linkup())return; // while(i<6){ buf[ETH_DST_MAC +i]=dstmac[i]; // gw mac in local lan or host mac buf[ETH_SRC_MAC +i]=macaddr[i]; i++; } buf[ETH_TYPE_H_P] = ETHTYPE_IP_H_V; buf[ETH_TYPE_L_P] = ETHTYPE_IP_L_V; fill_buf_p(&buf[IP_P],9,iphdr); buf[IP_ID_L_P]=ipid; ipid++; buf[IP_TOTLEN_L_P]=0x4c; buf[IP_PROTO_P]=IP_PROTO_UDP_V; i=0; while(i<4){ buf[IP_DST_P+i]=ntpip[i]; buf[IP_SRC_P+i]=ipaddr[i]; i++; } fill_ip_hdr_checksum(buf); buf[UDP_DST_PORT_H_P]=0; buf[UDP_DST_PORT_L_P]=0x7b; // ntp=123 buf[UDP_SRC_PORT_H_P]=10; buf[UDP_SRC_PORT_L_P]=srcport; // lower 8 bit of src port buf[UDP_LEN_H_P]=0; buf[UDP_LEN_L_P]=56; // fixed len // zero the checksum buf[UDP_CHECKSUM_H_P]=0; buf[UDP_CHECKSUM_L_P]=0; // copy the data: i=0; // most fields are zero, here we zero everything and fill later while(i<48){ buf[UDP_DATA_P+i]=0; i++; } fill_buf_p(&buf[UDP_DATA_P],10,ntpreqhdr); // ck=checksum(&buf[IP_SRC_P], 16 + 48,1); buf[UDP_CHECKSUM_H_P]=ck>>8; buf[UDP_CHECKSUM_L_P]=ck& 0xff; enc28j60PacketSend(90,buf); }

Reply to
Atlantis

Hmm... Sprawa jeszcze dziwniejsza - pomimo wyłączonej synchronizacji różnica czasu zdaje się narastać w tempie dużo szybszym, niż mogłoby to wynikać z zastosowanego wzorca - sekunda na kilka minut...

Jednak po pojedynczej synchronizacji praktycznie nigdy nie mam różnicy wynoszącej 0-1 sek. Muszę wykonać długi ciąg ręcznych synchronizacji, żeby sytuacja tak wyglądała.

Reply to
Atlantis

Bez synch. późni się czy spieszy? Pisałeś, że ten *time liczysz w przerwaniu timera 1ms, który odlicza czas lokalny, uwzględniłeś czas tych obliczeń *time (maltretujesz 8bit mcu obliczeniami na int32, timer powinien być aktywny podczas ich trwania)?

Reply to
Marek

W dniu 2014-11-12 15:02, Marek pisze:

Spóźnia się. Z każdą chwilą coraz bardziej.

W tej chwili używam przerwania 10ms. Hmm... Uważasz, że operacje na zmiennych mogą zajmować więcej czasu, przez co różnica odkłada się z każdą sekundą? Czy to (wraz z czasem potrzebnym na obliczenie nowego czasu z pakietu NTP) tłumaczyłoby także początkowe, kilkusekundowe opóźnienie, pojawiające się zwykle po synchronizacji?

Funkcja obsługująca przerwanie wygląda następująco:

ISR(TIMER0_COMPA_vect) { ten_millis++; if (ten_millis > 100) { geiger_pulses[seconds] = TCNT1; TCNT1 = 0; seconds = (seconds + 1) % 60; one_second_tick = 1; uptime++; if (rtc) rtc++; if (ntp_update_timer) ntp_update_timer--; if (bmp085_update_timer) bmp085_update_timer--; if (dht_update_timer) dht_update_timer--; if (graphite_upload_timer) graphite_upload_timer--; ten_millis = 0; } }

Pierwszy blok poleceń w instrukcji warunkowej to obsługa licznika Geigera (właściwe główny czujnik zamontowany w tym urządzeniu) - co sekundę ilość impulsów zliczonych przez licznik sprzętowy jest zapisywana do tablicy, sam licznik jest czyszczony i liczony jest numer kolejnego pola do zapisania.

Zmienna "one_second_tick" to uint8_t - jej jedyną funkcją jest przekazywanie do pętli głównej informacji o przekręceniu się licznika sekund.

Zmiennymi 32bitowymi są jedynie uptime i rtc. Zmienne bmp085_update_timer i dht_update_timer mają rozmiar ośmiu bitów

- odliczają czas pomiędzy kolejnymi odczytami czujników BMP085 oraz DHT11. Podobne jest przeznaczenie szesnastobitowych zmiennych ntp_update_timer oraz graphite_upload_timer, które odliczają czas do kolejnego wysłania requesta do serwera NTP oraz uploadu wyników pomiarów do Graphite'a.

Jeśli to tu leży przyczyna, to w jaki sposób powinienem wprowadzić poprawkę? Jak ustalić czas potrzebny do wykonania tych operacji?

Reply to
Atlantis

Kod wygląda ok, myślałem, że w przerwaniu robisz te obliczenia. Nie znam się na avr ale domyślam się z kodu, że przerwanie generowane jest przez komparator tmera, przy czym timer biegnie dalej w trakcie obsługi przerwania, tak? Ja bym na razie w funkcji main () obserwował tylko wartość uptime (np. wyświetlając ja przez uart przy każdej jej zmianie, taki stoper) . Chodzi o to by się upewnić czy lokalne odliczanie sekund jest ok. Na pickach bez kwarcu odliczanie sekund (uptime) taką metodą jak Ty zastosowałeś (przerw. co 10ms) na dobę rozjezdżało mi się max kilka - kilkanaście sekund względem dobrego zegarka.

Reply to
Marek

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.