Elektroda.pl
Elektroda.pl
X

Search our partners

Find the latest content on electronic components. Datasheets.com
Elektroda.pl
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

[Mega8][C] Bardzo dokładny zegar - problem z Timer1

17 Nov 2009 17:29 3650 24
  • Level 38  
    Witam kolegów,

    głowię się już ładnych parę dni nad taką kwestią w procku:

    zrobiłem na Atmedze8 zegarek. Zewnętrzny kwarc 4MHz. Timer1 zajmuje się odliczaniem równych sekund, Timer0 osługą wyświetlania itp.

    Na czym polega problem? Zegarek spóźnia się, wg moich obliczeń na symulatorze w AVR Studio, o 0,345s na dobę. Nie jest to szok ani porażka, raptem 2 minuty rocznie, ale jednak chciałbym wiedzieć skąd bierze się to opóźnienie. Opóźnienie widać gołym okiem już po pierwszej dobie (w porównaniu z kwarcowym zegarem ściennym).
    Skąd te wyliczenia? Już tłumaczę. Timer1 skonfigurowany jest tak:
    Code:
    TCNT1 = 3036; // wartość początkowa
    
    TCCR1A = 0x00; // timer1 w trybie czasomierza
    TCCR1B = (1 << CS11) | (1 << CS10); // preskaler 64

    co daje przerwanie co równą sekundę.
    Przy każdym przerwaniu Timer1 ładuję do niego ponownie 3036, oczywiście.
    Po tym jak zacząłem dociekać przyczyny spóźnienia, wydedukowałem przy pomocy symulatora, że problem może tkwić tutaj:
    Code:
    ISR (TIMER1_OVF_vect) {
    
       TCNT1 = 3036;

    Otóż po wywołaniu przerwania TIMER1_OVF zanim wpiszę do niego wartość 3036, upływa 4us. Czas zwiększania licznika TIMER1 wynosi 16us, co oznacza, że co cztery "takty" tego timera gubię 1 z jego wartości. Na dobę daje to stracone 900. Jest to właśnie brakujące 0,345s.

    Jak to załatać? Próbowałem korygować w TIMER0 wartość TIMER1, co godzinę dodając brakujące 900 (pierwsza sekunda każdej godziny nieco krótsza), ale ku mojemu zdziwieniu, po jednej dobie zegarek spóźnia się już dobre 3 sekundy.
    Efekt całkiem odwrotny.

    Proszę w takim razie o opinię gdzie może leżeć problem i/lub co jest błędnego w moim rozumowaniu/wyliczeniach.

    ------------------------------
    Dodam jeszcze, żeby uprzedzić pytania, że czas wykonania procedury w przerwaniu T1 jest znikomy, są tam tylko 3 instrukcje, czas wykonania T0, który cyka co 2ms, jest poniżej 0,3ms, więc też ze spokojnym zapasem.
    Kwarc niski z kondensatorami 33p, cały układ zasilany z zasilacza ATX linii +12V przez stabilizator 7805, więc "podwójnie" stablilizowany.
    [30.03.2021, webinar elektroda] Nowoczesna diagnostyka maszyn, monitorowanie i przewidywanie awarii. Zarejestruj się
  • Level 33  
    Najprostszy sposob to dac kwarc zegarkowy, i uzyc timera 2 taktowanego zewnetrznym kwarcem :)
  • Level 33  
    Zeby skalibrowac kwarc, to daje sie dwie rozne pojemnosci kondensatorow. Ale przez to procek moze w ogole nie wstac. Bez oscyloskopu raczej bedzie ciezko.
  • Level 38  
    Czyli, kolego _Robak_, sugerujesz że problem leży w niedokładnym odmierzaniu czasu przez kwac...
    Brałem to pod uwagę, ale przy założeniu, że przyczyną nie jest sprzęt, tylko program, najpierw szukam problemów z programem.
    Kwarcu zegarkowego niestety nie posiadam i sprawdzić nie mogę.

    W poszukiwaniu zagubonych taktów zmieniłem nawet sposób przechowywania danych z charów minuty i godziny na pary charów przechowujących jedności i dziesiątki godzin i minut. Pozwoliło mi to uniknąć stosowania operacji / i % i zaoszczędzić coś około 200 taktów procesora w każdym przerwaniu T0, ale koniec końców i tak to nic nie zmieniło, bo T0 wyrabiał się w czasie przed zmianami również.

    Zachęcam kolegów do dalszych propozycji!
  • Helpful post
    Moderator of Microcontroller designs
    Ja bym zmienil tryb pracy timera, tak, zeby nie trzeba bylo co przerwanie przeladowywac jego rejestrow - np. na CTC. Wtedy tym ile trwa obsluga przerwania sie nie martwisz - bo tam tylko zwiekszasz licznik sekund, a timer liczy swoje, czyli nic sie nie gubi. Kolejna sprawa to kwarc - jaki masz? Zapewne cos w stylu 4,000kHz, czyli sam kwarc ma +/-1kHz, to juz daje 21s na dobe. Do tego dryft zwiazany z temp., parametry kondensatorow i okazuje sie, ze twoj wynik jest naprawde dobry.
  • Level 38  
    Dokładnie tak jak mówisz, Super Japoński Kwarc, "SJK 4.000".
    W sumie lepsze to niż krzak w programie, lepiej mi z tą świadomością :)
    W istocie rzeczy takie opóźnienie to żadne opóźnienie, bo i tak dwa razy rocznie się zegar przestawia.

    Sprawdzę zgodnie z Twoją radą inną metodę liczenia, chociaż jeżeli przyczyną niedokładności jest kwarc, to i tak nic nie zmieni inny program.

    ED: a może nie sprawdzę, bo w zasadzie nie wiem jak to zrobić bez przeładowywania mu wartości :)
  • Level 39  
    wlw_wl wrote:
    ED: a może nie sprawdzę, bo w zasadzie nie wiem jak to zrobić bez przeładowywania mu wartości :)

    Jak już się upierasz przy rezonatorze 4MHz - a szkoda - to ustaw taki tryb pracy Timer1 by była aktywna opcja CTC. Potem do OCR1A lub ICR1 wpisz wartość jaką ma zliczyć tajmer i się skasować. Tylko pamiętaj, że licznik liczy wtedy od zera, a zero w OCR1A nie jest wartością poprawną. Potem napisz przerwanie z porównania OC1A zamiast z przepełnienia OVF.

    No i na koniec standardowa formułka: Zawsze czytaj dokumentacje - nie gryzie, a z reguły rozwiązuje problem.
  • Level 33  
    Tylko tak na prawde czy to cos da ? Wydaje mi sie ze to samo bedzie jesli licznik bedzie w trybie przepelnienia, tylko wystarczy wziac poprawke na ilosc cykli przypisania i wywolania przerwania.
  • Level 39  
    Cóż zrobić, jak kolega nie chce skorzystać z jedynej teraz słusznej - Twojej zresztą - rady?
  • Level 38  
    ZbeeGin,
    czemu tak negatywnie i lekko lekceważąco się wypowiadasz?
    Przy niczym się nie upieram, podałem w jakim układzie pracuje teraz uC.
    Moja wątpliwość jest taka sama jak _Robak_a, czy da się to zrobić dokładniej niż jest do tej pory?
    Błędy wynikają albo z niedokładności w odmierzaniu czasu przez rezonator albo z niedokładności odliczania sekundy przez T1. O tyle o ile pierwszej kwestii nie umiem rozwiązać w tym układzie, o tyle program można przerabiać.
    Nie chodzi tu o "zmień kwarc na zegarkowy" ani "RTC!".
    Dokumentację czytałem nie raz i jak widzisz problemu nie rozwiązuje.
  • Level 19  
    Problem raczej nie tkwi w tym że za długo trwa przeładowanie timera ponieważ preskaler masz ustawiony na 64, tak więc po przepełnieniu masz aż 64 takty na przeładowanie. Po upływie 64 taktów od przepałnienia wartość licznika jest zwiększana o 1 - tak więc to całkiem sporo czasu czyli ten sposób w jaki autor tematu naisał program jest dobry
  • MCUs specialist
    wlw_wl wrote:

    Code:
    ISR (TIMER1_OVF_vect) {
    
       TCNT1 = 3036;

    Otóż po wywołaniu przerwania TIMER1_OVF zanim wpiszę do niego wartość 3036, upływa 4us. Czas zwiększania licznika TIMER1 wynosi 16us, co oznacza, że co cztery "takty" tego timera gubię 1 z jego wartości. Na dobę daje to stracone 900. Jest to właśnie brakujące 0,345s.


    Jeśli jakieś inne przerwanie nie trwa dłużej niż 12µs to nic tutaj nie gubisz. Nie zapominaj że masz preskaler który nie jest zerowany podczas zapisu do TCNT1, czyli masz 64 takty zegara na uaktualnienie TCNT1 wartością początkową bez obawy że zgubisz jakieś takty. Zmiana trybu pracy timera nic nie da jeśli masz kiepskiej jakości kwarc, co jest najbardziej prawdopodobne i 0,3...s wcale nie jest jeszcze tragiczna wartością bo bywa gorzej. Częstotliwość twojego kwarca to 3999984,028Hz czyli odchyłka to tylko 0,0003993%. Skoryguj to trymerem, ale i zmień tryb pracy timera.
  • Level 39  
    wiw_wl:
    Kolega _Robak_ już na początku napisał:
    Quote:
    Najprostszy sposob to dac kwarc zegarkowy, i uzyc timera 2 taktowanego zewnetrznym kwarcem :)

    Już dawno kolega by się zapytał jak to zrobić i poczekał do momentu kiedy będzie ten rezonatorek miał. A tak brniemy dalej w ślepy zaułek z programowo-sprzętowym skracaniem licznika.

    Przy zegarkowym kwarcu problem niedokładności jest dalej, ale jest on mniejszy, gdyż jeśli przyjmiemy podawaną już tu dokładność rezonatora głównego to przecież ułamek procenta z 4MHz jest na 100% większy niż ten sam ułamek procenta z 32kHz - w uproszczeniu.
    Parafrazując pewne przysłowie: "Użyj kwarcu zegarkowego. Przecież tysiące układów RTC z niego korzystających nie mogą się mylić."

    A i Timer2 wtedy jest dość prosty i prawie bezobsługowy. Ustawiasz preskaler na 128, bit AS2 i przerwanie z przepełnienia dostajesz co 1 sekundę! Żadnych wstępnych przeładowań, żadnych porównań z zerowaniem licznika. Czysty i prosty hardware.

    Teraz spróbuj zadać pierwsze pytanie jeszcze raz. Tym razem jednak zapytaj sam siebie.
  • MCUs specialist
    ZbeeGin wrote:
    wiw_wl:
    Przy zegarkowym kwarcu problem niedokładności jest dalej, ale jest on mniejszy, gdyż jeśli przyjmiemy podawaną już tu dokładność rezonatora głównego to przecież ułamek procenta z 4MHz jest na 100% większy niż ten sam ułamek procenta z 32kHz - w uproszczeniu.
    Parafrazując pewne przysłowie: "Użyj kwarcu zegarkowego. Przecież tysiące układów RTC z niego korzystających nie mogą się mylić."


    W starszych casio były trymery, a teraz kalibruje to się za pomocą rejestrów.
    0,0003993% odchyłki na 32768Hz = 0,0003993% odchyłki na dobę
    0,0003993% odchyłki na 4MHz = 0,0003993% odchyłki na dobę

    Do tego jeszcze dryft temperaturowy kwarca, zazwyczaj 20ppm
  • Moderator of Microcontroller designs
    Z zegarkowymi zazwyczaj jest jeszcze gorzej, bo te dostepne w malych walcowych obudowach sa dostosowane do stalej temp., co wynika bezposrednio ze srodowiska w ktorym sa uzywane - na dloni temp. jest stala i wyzsza niz otoczenia. Mozna sie wiec spodziewac duzej, stalej odchylki. Jesli autorowi taka dokladnosc wystarczy to nie ma o czym mowic. Jesli nie to mozna kupic precyzyjne generatory, termostatowane.
  • Level 38  
    Dziękuję wszystkim za odpowiedzi i rady.

    michalko12 wrote:
    Jeśli jakieś inne przerwanie nie trwa dłużej niż 12µs to nic tutaj nie gubisz.

    Przerwanie od T0 trwa ok 312us*, więc jeśli w procedurze obsługi przerwania od T1 przed wpisaniem nowej wartości co TCNT1 zostanie wywołane przerwanie T0, to powstanie opóźnienie. Żeby tego uniknąć musiałbym na wejściu do obsługi przerwania T1 wyłączyć przerwanie T0 na czas wpisania nowej wartości do TCNT1 i potem ponownie włączyć... Tylko że sei() zajmuje prawie 2000us (przynajmniej pierwsze a mainie), nie wiem ile cli(), ale to może spowodować jeszcze większe jajca.

    *) obsługuje wyświetlanie na czterech wyśw. 7-seg przez rejestry szeregowo-równoległe, dlatego tak długo trwa
  • User removed account  
  • Level 38  
    A jaka to będzie różnica, czy robi to przerwanie a program sam nic, czy robi to program a przerwanie nic?
    Poza tym przerwanie realizuje przyciski zmiany godziny, żeby to miało ręce i nogi trzeba polling robić w równych odstępach czasu, program główny tego nie gwarantuje, musiałbym używać delay. Po zmianie godziny przyciskami trzeba przeprowadzić aktualizację liczników i odświeżenie wyświetlacza, musiałbym to robić dwa razy w programie głównym i w przerwaniu osobno, moim zdaniem zwiększyłoby to jeszcze bardziej opóźnienia.
    Dodałem do T1 cli() i sei(), jeszcze nie minęła doba więc za wcześnie oceniać rezultat tej zmiany, ciężko na razie ocenić.
  • Moderator of Microcontroller designs
    Doprawdy? A wyswietlanie multipleksowe chcesz zrealizowac jako wywolanie funkcji w main?
    Co do sei to na pewno nie trwa 2000us, sei i cli to pojedyncze instrukcje assemblera, ktore zajmuja 1 takt procesora, czyli w twoim przypadku 250ns. Mozesz zdefiniowac ISR jako nieblokujace (zobacz w helpie jakie ma opcje) i po klopocie. No i koniecznie zmien timer na CTC, nawet jesli w tym konkretnym wypadku to niewiele zmienia to lepiej sobie wyrabiac dobre nawyki, ktore zaowocuja w kolejnych projektach.
  • Level 38  
    Akurat nie jst multipleksowo bo nie lubię tego sposobu wyśw. - wolę rejestry przesuwne szeregowo-równoległe i tak też to jest zrobione.

    Co do tych 2000us - tak pokazał symulator, ale tylko przy pierwszym sei() w mainie po ustawieniu timerów, albo mu się coś pokićkało albo robi wtedy coś więcej.
    Rozumiem, że masz na mysli ustawienie "małego" przerwania (T0) jako nieblokującego, czyli
    Code:
    ISR (TIMER0_OVF_vect, ISR_NOBLOCK)
    
    {
      ...
    }

    Wg dokumentacji moja zmiana z dodaniem cli() i sei() w przerwaniu T1 na czas wpisywania nowej wartości TCNT1 nie ma sensu, bo domyślnie przerwania są blokujące. W takim razie następnym krokiem będzie sprawdzenie T0 jako nieblokującego...
  • User removed account  
  • Level 38  
    Przerwania mam dwa, jedno co sekundę, drugie co 2ms, oba blokujące, więc "nawzajem" się nie wywołają. Tendencji nie mam, jeżeli czas wykonania mieści się w czasie trwania to pół biedy, a wciupcianie bitów w rejestry jednak trochę trwa.
    Co do oszczędzania prądu, w Idle będę miał ok 3mA zamiast 6 w Active, niewielka to oszczędność biorąc pod uwagę wyświetlacze LED, gdzie każda dioda ma 5mA.

    Changelog:
    1. ustawiłem T1 w CTC
    2. przerwanie od T0 ustawiłem na nieblokujące, żeby w razie wystąpienia przerwania od T1 w trakcie trwania przerwania T0 zinkrementował licznik sekund. Być może zmiana interwału wywołania przerwania T0 byłaby lepszym rozwiązaniem? Np. 3ms, żeby przerwania T0 i T1 "nie trafiały" na siebie, bo przy 2ms pewnie wywołują się razem?

    Ustawienia do skontrolowania:
    Code:
    ISR(TIMER1_COMPA_vect) {
    
       sekundy++;
    }
    // (...)
       TCCR1B = (1<<WGM12);      //Timer1 jako CTC z TOP w OCR1         
       OCR1A = 62499ul;         //ustaw TOP
       TIMSK |= (1 << OCIE1A);   // Zezwolenie na przerwania CTC
       TCCR1B = (1 << CS11) | (1 << CS10); // preskaler 64
       // przerwanie co 1,000000s


    ED: jakieś czary, źle obliczyłem wartość OCR1A? Spóźnia się prawie 5s na każdą minutę! Chyba spojrzę na to jak się wyśpię bo jakieś herezje odstawiam.
  • Level 19  
    wlw_wl wrote:
    Przerwanie od T0 ustawiłem na nieblokujące, żeby w razie wystąpienia przerwania od T1 w trakcie trwania przerwania T0 zinkrementował licznik sekund.


    Podczas obsługi jakiegokolwiek przerwania wskaźnik I jest zerowany automatycznie, wiec procek nie reaguje wtedy na żadne inne przerwanie az do zakonczenia obsługi przerwania kiedy to jest ustawiany automatycznie.
  • Level 38  
    Zgadza się, doczytałem wcześniej, że domyślnie w avr gcc przerwania są blokujące, można ręcznie ustawić na nieblokujące, ale nic to u mnie nie dało. Pisałem to w EDzie w poprzednim poście, ale potem się okazało, że zegarek zaczął się kosmicznie spóźniać po przestawieniu Timer1 na CTC, kwestia przerwania w tym momencie jest mało istotna...