Kanał - ATNEL tech-forum
Wszystkie działy
Najnowsze wątki



Teraz jest 26 kwi 2026, o 02:08


Strefa czasowa: UTC + 1





Utwórz nowy wątek Odpowiedz w wątku  [ Posty: 14 ] 
Autor Wiadomość
PostNapisane: 12 lis 2014, o 12:30 
Offline
Użytkownik

Dołączył(a): 05 gru 2013
Posty: 246
Pomógł: 0

Ostatnio trochę popracowałem nad jedną ze swoich konstrukcji - ethernetowym "czujnikiem" mierzącym różne wartości. Dodałem do niego funkcję zegara NTP, aby móc rejestrować czas każdego pomiaru. Wykorzystałem w tym celu funkcje znajdujące się w bibliotece ze strony tuxgraphics.org. Jedną z nich tylko minimalnie zmodyfikowałem, żeby wynik odpowiadał formatowi Unix epoch.

Generalnie schemat synchronizacji jest bardzo prosty. Mam programowy licznik, który co sekundę jest dekrementowany w przerwaniu timera. Gdy jego wartość dojdzie do zera, w pętli głównej wykonuje się warunek wysyłający request do serwera NTP. W przypadku odebrania pakietu NTP wykonywana jest funkcja sprawdzająca, czy nie mamy do czynienia z pakietem NTP, Jeśli tak, liczona jest wartość czasu i przekazywana do właściwej zmiennej przez wskaźnik.

Czas przechowywany jest w zmiennej volatile uint32_t rtc, która jest inkrementowana we wspomnianym wyżej przerwaniu timera. Każda operacja odczytu i zapisy tej zmiennej w programie jest poprzedzona instrukcją cli(), a po jej zakończeniu występuje sei().

Całość jednak nie chce działać tak, jak powinna. Czas jest przesunięty o kilka-kilkadziesiąt sekund względem tego, co widzę na swoim Raspberry Pi albo na stronach takich jak epochconverter.com lub currenttimestamp.com. Raczej nie mamy tutaj do czynienia z dryfem wprowadzanym przez timer Atmegi, gdyż błąd zwykle występuje nawet tuż po synchronizacji (którą mogę również inicjować ręcznie).

Kod funkcji przetwarzającej odpowiedź serwera NTP wygląda następująco:

Składnia: [ Pobierz ] [ Ukryj ]
język c
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.


A tutaj można zobaczyć kod obsługi przerwania timera:

Składnia: [ Pobierz ] [ Ukryj ]
język c
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.


Ktoś ma jakiś pomysł co do tego, gdzie może tkwić błąd?



Góra
 Zobacz profil  
cytowanie selektywne  Cytuj  
PostNapisane: 12 lis 2014, o 20:30 
Offline
Użytkownik

Dołączył(a): 05 gru 2013
Posty: 246
Pomógł: 0

Moje początkowe obserwacje nie były jednak do końca poprawne. Błąd nie pojawia się podczas synchronizacji czasu z serwerem, a przynajmniej nie tylko. Występuje pewien nieustanny "dryf". Z każdą chwilą czas w urządzeniu opóźnia się coraz bardziej względem rzeczywistej wartości, a proces ten postępuje o wiele szybciej, niż mogłoby wynikać z niedokładności timera. W tej chwili mam dwunastominutowy uptime, a opóźnienie wynosi aż 14 sekund!

Początkowo sądziłem, że winę ponosi zbyt duża ilość instrukcji w funkcji obsługi przerwania. Podejrzewałem, że przeoczona zostaje jedna z inkrementacji zmiennej pomocniczej i po wielu takich sytuacjach dochodzi do zafałśzowania czasu. Dlatego też wprowadziłem pewne zmiany. Przede wszystkim wydłużyłem czas pomiędzy przerwaniami do 20 ms. Po drugie mniej krytyczne liczniki software'owe przeniosłem z funkcji obsługi przerwania do pętli głównej - nic złego się nie stanie, gdy uptime zostanie po jakimś czasie zafałśzowany o kilka sekund albo odpalenie jakiejś mniej krytycznej operacji opóźni się o sekundę.

W tej chwili obsługa przerwania timera wygląda następująco:

Składnia: [ Pobierz ] [ Ukryj ]
język c
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.


Coś mi się nie chce wierzyć, że wykonanie tego kodu zajmuje więcej niż 20ms, przez co przeoczona zostaje kolejna inkrementacja zmiennej twenty_millis. Zresztą w takim przypadku pełna sekunda opóźnienia uzbierałaby się dopiero po 50 razach. Tymczasem nieraz zdarza mi się obserwować kilka (a nawet kilkanaście) sekund opóźnienia tuż po starcie urządzenia lub wymuszonej synchronizacji z serwerem. Tu coś innego musi być na rzeczy...

Ktoś ma jakąś sugestię?



Góra
 Zobacz profil  
cytowanie selektywne  Cytuj  
PostNapisane: 12 lis 2014, o 21:04 
Offline
Użytkownik

Dołączył(a): 20 wrz 2013
Posty: 647
Zbananowany użytkownik

Pomógł: 101

Atlantis napisał(a):
Ktoś ma jakąś sugestię?

Tak, Ktoś ma sugestię, żebyś pokazał więcej kodu ponad te marne strzępki.

_________________
+++++[>++++<-]>[>++++++<-]>.---------.+++.



Góra
 Zobacz profil  
cytowanie selektywne  Cytuj  
PostNapisane: 12 lis 2014, o 21:14 
Offline
Użytkownik
Avatar użytkownika

Dołączył(a): 17 sty 2013
Posty: 123
Lokalizacja: Warszawa
Pomógł: 10

Może problem nie jest z czasem odczytanym z serwera, a z emulacją zegara w urządzeniu?
Jak przestaniesz synchronizować czas z serwera (tymczasowe wyłączenie), to czas systemowy zachowuje się normalnie czy nadal dryfuje?

Ja u siebie mam np RTC na DS1307, który generalnie jest zegarem systemowym, a tylko od czasu do czasu synchronizuję czas z serwera NTP w celu "naprostowania" czasu z DS1307 (który z powodu marnego kwarcu rozjeżdża się kilkanaście sekund na dobę).

Kolega - jak mi się wydaje, za każdym razem przelicza czas ze zmiennej zawierającej liczbę sekund od 1.1.1900?



Góra
 Zobacz profil  
cytowanie selektywne  Cytuj  
PostNapisane: 12 lis 2014, o 22:52 
Offline
Użytkownik

Dołączył(a): 05 gru 2013
Posty: 246
Pomógł: 0

Jado napisał(a):
Może problem nie jest z czasem odczytanym z serwera, a z emulacją zegara w urządzeniu?
Jak przestaniesz synchronizować czas z serwera (tymczasowe wyłączenie), to czas systemowy zachowuje się normalnie czy nadal dryfuje?


Chyba masz rację. Wyłączyłem synchronizację z NTP i kazałem programowi po starcie zwiększać co sekundę wartość zmiennej rtc, zaczynając od 1. Po trochę ponad godzinie pracy narobiło się 81 sekund opóźnienia, chociaż serwer NTP nie był wcale używany.

Ustawienia timera wyglądają następująco:

Składnia: [ Pobierz ] [ Ukryj ]
język c
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.


Konfigurację przygotowałem przy pomocy mk AVR Calculatora, sprawdziłem także za pomocą jakiegoś webowego przelicznika.
Częstotliwość taktowania (linia CLKOUT układu ENC28J60) została sprawdzona częstotliwościomierzem, który pokazał 12.500xx MHz. Ostatnie dwie cyfry lekko "pływały" w trakcie pomiaru, ale chyba nie tyle wina samego sygnału, co niedokładności miernika.
Z tego samego powodu nie byłem w stanie ustalić dokładnej częstotliwości przerwania. Z procedurze jego obsługi dodałem machanie pinem, ale częstotliwościomierz pokazuje wartość zmieniającą się pomiędzy 25-30Hz. To raczej znów wpływ miernika, więc trudno powiedzieć coś konkretnego... :/

Cytuj:
Kolega - jak mi się wydaje, za każdym razem przelicza czas ze zmiennej zawierającej liczbę sekund od 1.1.1900?


Nie. W tym przypadku nie potrzebuję czasu w formacie "human readable". Interesuje mnie tylko timestamp (Unix epoch) do oznaczania czasu pomiarów i paru innych zdarzeń. Dlatego tylko raz, przy synchronizacji przeliczam go na liczbę sekund od 1.1.1970. Potem w przerwaniu wartość ta już tylko musi się zwiększać o 1 co sekundę.



Góra
 Zobacz profil  
cytowanie selektywne  Cytuj  
PostNapisane: 12 lis 2014, o 23:13 
Offline
Użytkownik
Avatar użytkownika

Dołączył(a): 17 sty 2013
Posty: 123
Lokalizacja: Warszawa
Pomógł: 10

Możesz spróbować skorygować nieco liczbę podziału licznika (o 1) i zobaczyć jak się zmienią wskazania czasu - może uda się zmniejszyć błąd.
Być może kwarc nie daje idealnie takiej częstotliwości jaką deklaruje producent - przy pomiarze czasu nawet tak mała różnica może mieć duży wpływ na końcowy wynik.

Ewentualnie błąd powstaje gdzieś w programie (wykradanie przerwań) - wtedy należałoby wyłączyć wszystko oprócz procedur odmierzania (i wyświetlania) czasu, tak aby nic nie zakłócało procedury odmierzającej czas - i sprawdzić jak się sprawy mają.



Góra
 Zobacz profil  
cytowanie selektywne  Cytuj  
PostNapisane: 12 lis 2014, o 23:40 
Offline
Użytkownik

Dołączył(a): 05 gru 2013
Posty: 246
Pomógł: 0

Jado napisał(a):
Możesz spróbować skorygować nieco liczbę podziału licznika (o 1) i zobaczyć jak się zmienią wskazania czasu - może uda się zmniejszyć błąd.


Też o tym pomyślałem. Po prostu dziwię się, że coś takiego ma miejsce.

Cytuj:
Być może kwarc nie daje idealnie takiej częstotliwości jaką deklaruje producent - przy pomiarze czasu nawet tak mała różnica może mieć duży wpływ na końcowy wynik.


Też o tym myślałem, ale zmierzyłem swoim częstotliwościomierzem linię CLKOUT (wyjście zegarowe układu ENC28J60).
Miernik jest "taką sobie" konstrukcją domowej roboty. Wskaźnik pokazywał 12.500xx MHz - dwie ostatnie cyfry nieco fluktuowały. Przy czym taka zmiana w mkAVRcalculator nie wywołuje zmiany sugerowanej wartości rejestru OCR0A.

Cytuj:
Ewentualnie błąd powstaje gdzieś w programie (wykradanie przerwań) - wtedy należałoby wyłączyć wszystko oprócz procedur odmierzania (i wyświetlania) czasu, tak aby nic nie zakłócało procedury odmierzającej czas - i sprawdzić jak się sprawy mają.


W programie nie są wykorzystywane żadne inne przerwania.
Timer1 pracuje w trybie licznika zliczającego impulsy z zewnętrznego źródła.



Góra
 Zobacz profil  
cytowanie selektywne  Cytuj  
PostNapisane: 13 lis 2014, o 00:02 
Offline
Użytkownik
Avatar użytkownika

Dołączył(a): 17 sty 2013
Posty: 123
Lokalizacja: Warszawa
Pomógł: 10

Mam jedno takie urządzenie - zegar z alarmami.
Moja stara konstrukcja - jeszcze na 89C2051. Kwarc 11.059MHz.
Przy idealnym ustawieniu licznika (tj. dla tego kwarcu) zegar spieszy się kilka minut na tydzień. Niestety częstotliwość kwarcu nie jest idealna. Próba zmiany licznika o 1 pozycje skutkowała koszmarnym błędem. Zostawiłem więc jak jest - bo konstrukcja i tak zostanie zastąpiona nowszą.
Niestety, ale stopień podziału licznika - nawet ostatnia pozycja, jest zbyt duża zmianą, żeby móc dobrze wyregulować czas (przynajmniej w tych procesorach).
Być może korekta polegająca na odjęciu kilku-kilkudziesięciu sekund raz na dobę, pozwoliłaby na zadowalającą korektę czasu zegara.
Trzeba by tylko zadbać o jednorazowość takiego procesu - żeby nie wpaść w pętlę nieskończoną ;-)

Takie korekty zawsze się wiążą z pewnym "nieustalonym" obszarem czasu - który może występować ten sam - dwa razy (przed korektą i po korekcie). Im większy dryft czasu i częstsze korekty (np. z NTP), tym więcej takich nieustalonych obszarów będzie - jeśli więc zależy komuś na dokładnym timestamp'ie, może to być dość zawodne.

Sam u siebie widzę jak (w innym urządzeniu - tym z RTC), najpierw następuje synchronizacja czasu, a po kilku sekundach ponowna, bo "czas cofnął się o te kilka sekund" - w/g serwera NTP). Po drugim odczycie już błąd jest poniżej 1s i "czas idzie dalej" nie wpadając w nieskończoną pętlę.



Góra
 Zobacz profil  
cytowanie selektywne  Cytuj  
PostNapisane: 13 lis 2014, o 13:04 
Offline
Użytkownik

Dołączył(a): 05 gru 2013
Posty: 246
Pomógł: 0

Udało mi się empirycznie skorygować pracę timera. Po wpisaniu wartości 238 do rejestru OCR0A różnica czasu się zmniejszyła. Teraz zegar spieszy o około jedną sekundę na każde 10 minut. To wciąż sporo w porównaniu z przeciętnym zegarem kwarcowym, jednak taka dokładność już by wystarczyła - co najwyżej trzeba będzie ustawić synchronizację co 10 minut.

Jak się jednak okazuje, nie są to wszystkie problemy. Moje poprzednie podejrzenia co do problemów z synchronizacją przez NTP okazały się słuszne. Przy wyłączonej synchronizacji występuje stały błąd (wspomniana sekunda co około 10 minut). Jednak gdy włączę synchronizację, zaczynają się dziać dziwne rzeczy. Otrzymany czas jest mniejszy od rzeczywistej wartości nawet o kilkadziesiąt sekund (wyprzedzenia nie zaobserwowałem jak do tej pory). Żeby uzyskać prawidłową wartość, muszę ręcznie wysłać kilka-kilkanaście requestów do serwera NTP. Dopiero wtedy odchylenie znów wynosi 0-1s.

Co może być przyczyną tak dziwnego zachowania?

Funkcja wysyłająca request wygląda następująco:

Składnia: [ Pobierz ] [ Ukryj ]
język c
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.


Zmienna ntpreqhdr wygląda następująco:

Składnia: [ Pobierz ] [ Ukryj ]
język c
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.



Góra
 Zobacz profil  
cytowanie selektywne  Cytuj  
PostNapisane: 13 lis 2014, o 18:19 
Offline
Użytkownik
Avatar użytkownika

Dołączył(a): 17 sty 2013
Posty: 123
Lokalizacja: Warszawa
Pomógł: 10

Atlantis napisał(a):
Udało mi się empirycznie skorygować pracę timera. Po wpisaniu wartości 238 do rejestru OCR0A różnica czasu się zmniejszyła. Teraz zegar spieszy o około jedną sekundę na każde 10 minut. To wciąż sporo w porównaniu z przeciętnym zegarem kwarcowym, jednak taka dokładność już by wystarczyła - co najwyżej trzeba będzie ustawić synchronizację co 10 minut.

Oczywiście dla każdego egzemplarza ENC28J60 ta wartość zapewne będzie różna - trzeba będzie dla każdego fizycznego urządzenia przeprowadzać indywidualną kalibrację.

Atlantis napisał(a):
Jak się jednak okazuje, nie są to wszystkie problemy. Moje poprzednie podejrzenia co do problemów z synchronizacją przez NTP okazały się słuszne. Przy wyłączonej synchronizacji występuje stały błąd (wspomniana sekunda co około 10 minut). Jednak gdy włączę synchronizację, zaczynają się dziać dziwne rzeczy. [b]
Co może być przyczyną tak dziwnego zachowania?

A z jakiego pola danych odpowiedzi serwera NTP pobierasz dane czasu?
Ja też miałem na początku głupoty, ale poprawnym polem danych okazało się NTP_TRAN_TIME.



Góra
 Zobacz profil  
cytowanie selektywne  Cytuj  
PostNapisane: 13 lis 2014, o 18:59 
Offline
Użytkownik

Dołączył(a): 05 gru 2013
Posty: 246
Pomógł: 0

Jado napisał(a):
A z jakiego pola danych odpowiedzi serwera NTP pobierasz dane czasu?
Ja też miałem na początku głupoty, ale poprawnym polem danych okazało się NTP_TRAN_TIME.


Początkowo też mi to przyszło do głowy, dlatego sprawdziłem dokładniej.
Początek danych z pakietu UDP znajduje się w 0x2A komórce bufora. Czas jest składany z komórek 0x52..0x55, są to więc bajty od 40 do 43, licząc od początku danych z pakietu UDP. Wychodzi więc na to, że odczytuje część sekundową transmit timestamp - czyli jak sam stwierdziłeś, to poprawne.

Oczywiście funkcję odbiorczą można by trochę ulepszyć. Przede wszystkim warto byłoby sprawdzać, czy faktycznie mamy do czynienia z pakietem UDP oraz czy przyszedł on spod numeru IP naszego serwera czasu. Poza tym warto byłoby odczytywać originate timestamp oraz porównywać go z wartością wysłaną jako transmit timestamp w requeście - wtedy miałoby się większą pewność, że to faktycznie odpowiedź na nasze zapytanie.

Przy czym nie wydaje mi się, żeby powyższe kwestie mogły odpowiadać za te dziwne opóźnienia, a ja naprawdę nie mam pomysłów. Jaki mechanizm może powodować, że czas po synchronizacji jest opóźniony o kilka-kilkanaście (a niekiedy nawet kilkadziesiąt sekund), ale synchronizuje się dokładnie po wysłaniu szybkiej serii requestów?



Góra
 Zobacz profil  
cytowanie selektywne  Cytuj  
PostNapisane: 13 lis 2014, o 20:13 
Offline
Użytkownik
Avatar użytkownika

Dołączył(a): 17 sty 2013
Posty: 123
Lokalizacja: Warszawa
Pomógł: 10

Atlantis napisał(a):
Jaki mechanizm może powodować, że czas po synchronizacji jest opóźniony o kilka-kilkanaście (a niekiedy nawet kilkadziesiąt sekund), ale synchronizuje się dokładnie po wysłaniu szybkiej serii requestów?

Może być tak, że dane z poprzedniego odczytania czasu są wciąż obecne w zmiennych. Przychodzi jakiś pakiet-zakłócenie - nie będący pakietem NTP, ale program źle to rozpoznaje (albo w ogóle tego nie sprawdza) i uznaje że nadszedł pakiet NTP. Traktuje on wówczas stare dane jako bieżący czas, a ponieważ od tamtego czasu upłynęło już trochę sekund, pojawia się "uwstecznienie czasu".
Dlatego dobrze byłoby - po odczytaniu każdego poprawnego czasu z NTP, zerować pola danych odebranego pakietu (w buforach odbiorczych, zmiennych tymczasowych, itp) - jak również zamykać port nr 123.
Ja u siebie np. otwieram port 123 tylko na czas = czasowi timeout'u oczekiwania na odpowiedz z serwer NTP - tylko po wysłaniu requestu do serwera NTP (wtedy zaczynamy odliczanie czasu oczekiwania na odpowiedź).

Sprawdź więc czy jakiś nieprawidłowy lub błędny pakiet może wywołać działanie takie jakby odebrano poprawny pakiet. Czy stare dane w tym wypadku nie są traktowane jako valid.



Góra
 Zobacz profil  
cytowanie selektywne  Cytuj  
PostNapisane: 14 lis 2014, o 08:53 
Offline
Użytkownik

Dołączył(a): 05 gru 2013
Posty: 246
Pomógł: 0

Jado napisał(a):
Może być tak, że dane z poprzedniego odczytania czasu są wciąż obecne w zmiennych. Przychodzi jakiś pakiet-zakłócenie - nie będący pakietem NTP, ale program źle to rozpoznaje (albo w ogóle tego nie sprawdza) i uznaje że nadszedł pakiet NTP. Traktuje on wówczas stare dane jako bieżący czas, a ponieważ od tamtego czasu upłynęło już trochę sekund, pojawia się "uwstecznienie czasu".


Wielkie dzięki za naprowadzenie mnie na właściwy trop. Sytuacja nie wyglądała dokładnie tak, jak w powyższym opisie, ale była bardzo podobna. W pętli głównej zabrakło jednej instrukcji warunkowej, która w pewnym kontekście sprawdzałaby wynik działania jednej z instrukcji parsujących nagłówki w buforze. Efekt był taki, że przy spełnieniu kilku dodatkowych warunków funkcja analizująca bufor w poszukiwaniu nowego czasu NTP włączała się. Jeśli w buforze znajdował się akurat stary pakiet NTP, był on uznawany za dopiero co otrzymany czas.

Teraz wszystko działa. Co prawda z uwagi na dryf lokalnego zegara muszę przeprowadzać synchronizację w odstępach mniejszych niż 10 minut, ale nie jest to przecież jakimś wielkim problemem.


Cytuj:
Ja u siebie np. otwieram port 123 tylko na czas = czasowi timeout'u oczekiwania na odpowiedz z serwer NTP - tylko po wysłaniu requestu do serwera NTP (wtedy zaczynamy odliczanie czasu oczekiwania na odpowiedź).


W przypadku tuxgraphics.org to niestety nie działa w ten sposób. Tam nie ma czegoś takiego jak sockety, otwieranie portów itp. Wszystko robi się bardziej "niskopoziomowo". Cała komunikacja polega na analizowaniu kolejnych nagłówków znajdujących się w odebranym pakiecie ethernetowym i decydowanie z czym mamy do czynienia. Można więc co najwyżej przerwać wykonywanie procedury obsługującej jakąś transmisję, jeśli port nie jest tym, którego się spodziewaliśmy.



Góra
 Zobacz profil  
cytowanie selektywne  Cytuj  
PostNapisane: 14 lis 2014, o 12:12 
Offline
Użytkownik
Avatar użytkownika

Dołączył(a): 17 sty 2013
Posty: 123
Lokalizacja: Warszawa
Pomógł: 10

Generalnie to tak właśnie działa.
Jak taka sortownia paczek - odebrany pakiet jest obłuskiwany z kolejnych warstw, aby na koniec trafić na półkę z napisem np. UDP.
A tam analizujemy numery portów do których jest adresowany dany pakiet czyli dalsza selekcja.
Wystarczy w procedurze sprawdzającej nr. portów dać zmienną, której wartość możesz przeładowywać i jeśli załadujesz ją np. numerem 123, to pakiety zaadresowane na ten port, będą dalej obsługiwane. Jeśli zmienną wyzerujesz lub ustawisz na inny nr portu, to pakiety na adres 123 są pomijane z dalszej obsługi.
Można to jeszcze powiązać z adresem IP nadawcy pakietu - dla pewności, że pakiet przyszedł z określonego źródła.
No i jest socket :-)



Góra
 Zobacz profil  
cytowanie selektywne  Cytuj  
Wyświetl posty nie starsze niż:  Sortuj wg  
Utwórz nowy wątek Odpowiedz w wątku  [ Posty: 14 ] 

Strefa czasowa: UTC + 1


Kto przegląda forum

Użytkownicy przeglądający ten dział: Brak zidentyfikowanych użytkowników i 1 gość


Nie możesz rozpoczynać nowych wątków
Nie możesz odpowiadać w wątkach
Nie możesz edytować swoich postów
Nie możesz usuwać swoich postów
Nie możesz dodawać załączników

Szukaj:
Skocz do:  
Sitemap
Technologię dostarcza phpBB® Forum Software © phpBB Group phpBB3.PL
phpBB SEO