logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

Złe odliczanie czasu w zegarku na ATMEGA8(L) z kwarcem 8MHz - jak poprawić?

Kubbaz 30 Sie 2006 16:30 5542 15
REKLAMA
  • #1 2969888
    Kubbaz
    Poziom 26  
    Posty: 1237
    Pomógł: 9
    Ocena: 30
    Witam

    Temat być może pojawia się po raz wtóry, ale poprzednie tematy nie rozwiązały problemu.

    Chcę wykonać prosty zegarek cyfrowy oparty na uC ATMEGA8(L) oraz wyświetlaczu LCD 2x24.

    Wszystko chodzi bez zarzutów, gdyby nie fakt złego odliczania czasu przez mój uC.
    Otóż mój ATMEGA8 pracuje z kwarcem zewnętrznym 8MHz + dwa kondensatory ceramiczne 22pF (zostały poprawnie zmienione bity CKSEL 3 ... 1 oraz SUT 1, 0 w Fuse Bits).

    Dotychczas uC pracował na wewnętrznym oscylatorze 1MHz, gdzie również źle odmierzał czas za pomocą poniższej funkcji, lecz w poprzednich moich programach na uC nie miało to takiego znaczenia; teraz ma :).

    Oto dotychczasowy kod na opóźnianie (odliczanie czasu):

    #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))   
    #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
    
    #define F_CPU        1000000                   // 1MHz zegar procesora -> częstotliwość kwarcu
    #define CYCLES_PER_US ((F_CPU+500000)/1000000) // cpu cykli na mikrosekunde
    
    #define LED_ON sbi(DDRB,PB1);sbi(PORTB,PB1)
    #define LED_OFF sbi(DDRB,PB1);cbi(PORTB,PB1)
    
    
    void delay(unsigned int us) // opóźnienie w mikrosekundach us -> delay
    { 
        unsigned int delay_loops;
        register unsigned int  i;
        delay_loops = (us+3)/5*CYCLES_PER_US; // +3 for rounding up (dirty) 
        for (i=0; i < delay_loops; i++) {};
    }
    
    void delayms(unsigned int ms) // opóźnienie w milisekundach ms -> delayms
    {
                unsigned int i;
                for (i=0;i<ms;i++)
                            {
                            delay(999);
                            asm volatile ("WDR"::);
                            }
    
    int main (void)
    {
    	for (;;)
    	{
    		LED_ON;
    		delayms (1000);
    		LED_OFF;
    		delayms (1000);
    	}
    }
    


    Mimo, że z kodu wynika że dioda LED powinna mrugać co jedną sekundę (czas zapalenia diody LED również wynosi jedną sekundę), w rzeczywistości dioda mruga znacznie szybciej.

    Szczerze przyznam, że nie wiem o co chodzi i co robią poniższe linie powyższego kodu :
    
    #define CYCLES_PER_US ((F_CPU+500000)/1000000) // cpu cykli na mikrosekunde

    Co to za obliczenia ?? Dlaczego jest F_CPU + 500000 i podzielone przez 1000000-to jest chyba częstotliwość pracy uC .. ??

    delay_loops = (us+3)/5*CYCLES_PER_US; // +3 for rounding up (dirty)

    Dlaczego do argumentu przekazywanego dodawane jest 3 ;|, dzielone przez 5 i mnożone razy ilość cykli na sekundę...czyli to co wyżej ...??

    asm volatile ("WDR"::);

    Domyślam się, że to jest część assemblerowska oparta na Watch Dogu, w celu uzyskania jakiegoś wymuszenia w opóźnieniu czasu....??

    Korzystałem z gotowej biblioteki WinAVR delay.h, gdzie funkcja opóźniania wygląda następująco:

    static inline void _delay_loop_1(uint8_t __count) __attribute__((always_inline));
    static inline void _delay_loop_2(uint16_t __count) __attribute__((always_inline));
    static inline void _delay_us(double __us) __attribute__((always_inline));
    static inline void _delay_ms(double __ms) __attribute__((always_inline));
    
    void
    _delay_loop_1(uint8_t __count)
    {
    	__asm__ volatile (
    		"1: dec %0" "\n\t"
    		"brne 1b"
    		: "=r" (__count)
    		: "0" (__count)
    	);
    }
    
    void
    _delay_loop_2(uint16_t __count)
    {
    	__asm__ volatile (
    		"1: sbiw %0,1" "\n\t"
    		"brne 1b"
    		: "=w" (__count)
    		: "0" (__count)
    	);
    }
    
    #ifndef F_CPU
    /* prevent compiler error by supplying a default */
    # warning "F_CPU not defined for <util/delay.h>"
    # define F_CPU 1000000UL
    #endif
    
    void
    _delay_us(double __us)
    {
    	uint8_t __ticks;
    	double __tmp = ((F_CPU) / 3e6) * __us;
    	if (__tmp < 1.0)
    		__ticks = 1;
    	else if (__tmp > 255)
    		__ticks = 0;	/* i.e. 256 */
    	else
    		__ticks = (uint8_t)__tmp;
    	_delay_loop_1(__ticks);
    }
    
    void
    _delay_ms(double __ms)
    {
    	uint16_t __ticks;
    	double __tmp = ((F_CPU) / 4e3) * __ms;
    	if (__tmp < 1.0)
    		__ticks = 1;
    	else if (__tmp > 65535)
    		__ticks = 0;	/* i.e. 65536 */
    	else
    		__ticks = (uint16_t)__tmp;
    	_delay_loop_2(__ticks);
    }


    Ta funkcja również źle odlicza czas w układzie uC z kwarcem.
    Nie bardzo wiem jak zrobić poprawne odmierzanie czasu. Już próbowałem "na pałę" zmieniać niektóre wartości w wyżej wymienionych funkcjach, ale "na pałę" to sobie nożna zmieniać ....;d.

    Jeśli ktoś wie jak zrobić funkcję dobrego odmierzania czasu na uC ATMEGA8, to prosiłbym o pomoc w tym zagadnieniu.
  • REKLAMA
  • #2 2969978
    SnowBizz
    Poziom 15  
    Posty: 134
    Pomógł: 9
    Ocena: 4
    Jesli chcesz zrobic dokladny zegarek to zapomnij o funkcjach dalayms są one strasznie niedokladne. Korzystając z gotowej biblioteki delay.h musisz także pamiętać że max czas opóźnienia dla danego procka w wywołaniu funkcji delayms() jest ograniczony, i żeby uzyskać spore opóźnienie trzeba zamknąć to w pętli, a instrukcja pętli też zżera czas na swoje wykonanie i już masz niedokładność.. Najlepiej odmierzanie 1s zrobić na Timerze, a jeszcze lepiej na Timerze i kwarcu zegarkowym.
  • #4 2970244
    GienekS
    Poziom 32  
    Posty: 1971
    Pomógł: 139
    Ocena: 15
    Bez przerwan nie da rady na dokladny zegarek na procku.
  • REKLAMA
  • #5 2970311
    Kubbaz
    Poziom 26  
    Posty: 1237
    Pomógł: 9
    Ocena: 30
    GienekS napisał:
    Bez przerwan nie da rady na dokladny zegarek na procku.


    czy można podłączyć wyjście US NE555 ustawionego na 1MHz pod pin INT0 uC ATMEGA8(L) ?? Czy trzeba sygnał z NE555 przez jakiś tranzystor puścić zanim trafi do uC ... ??
  • REKLAMA
  • #6 2970818
    Prymulka
    Poziom 18  
    Posty: 378
    Pomógł: 9
    Ocena: 8
    Juz dawno z tych funkcji nie korzystalem ale z tego co pamietam to maksymalny parametr dla delayms wynosi:
    255/Fosc
    czyli mozesz tam maksymalnie wprowadzic jednorazowo opoznienie 256 ms. Zeby odmierzyc 1s musisz zrobis oponienie np delayms(100) i umiescic w petli for *10.

    Mam nadzieje ze w niczym Cie nie okłamałem.
    Pozdrawiam
  • #7 2971134
    Kubbaz
    Poziom 26  
    Posty: 1237
    Pomógł: 9
    Ocena: 30
    Prymulka napisał:
    mozesz tam maksymalnie wprowadzic jednorazowo opoznienie 256 ms. Zeby odmierzyc 1s musisz zrobis oponienie np delayms(100) i umiescic w petli for *10


    Tzn. że mam użyć gotowej funkcji bibliotecznej WinAVR - "void _delay_ms (double __ms)" z bibliotegki "delay.h" i wsadzić ją w takiego fora:

    #include <util/delay.h>
    ......
    void delays ( )
    {
        int i;
    
        for (i=0;i<11;i++)
        {
            _delay_ms (100);
        }
    }


    I ona będize mi odliczała 1 sekunde na moim ATMEGA8 z zewnętrznym kwarcem 8MHz ??
  • REKLAMA
  • #8 2971186
    Prymulka
    Poziom 18  
    Posty: 378
    Pomógł: 9
    Ocena: 8
    Nie czytasz dokładnie :) Dla kwarcu 8MHz maksymalny delay wyniesie 256/8 czyli 32ms. Czyli robisz pętlę for np na 40x25ms.
  • #9 2971852
    SnowBizz
    Poziom 15  
    Posty: 134
    Pomógł: 9
    Ocena: 4
    #include <avr/io.h>
    #include <avr/interrupt.h>
    #include <avr/signal.h>

    #define tau0 6;

    main()
    {
    unsigned int=3200;

    DDRB=0x01;
    TCNT0=tau0;
    TCCR0=1; //ustawia presckaler

    for(;
    {
    while(bit_is_clear(TIFR,TOV0)); //sprawdza przepełnienie
    TCNT0=tau0;
    licznik--;
    if(licznik==0) //sprawdza czy licznik zmalał do0 z 3200
    {
    PORTB^=0x01;
    licznik=32000;
    }
    TIFR=1<<TOV0;
    }
    }

    Ten kod odmierza 1s tzn zapala na 1s diode na porcie B a potem ją gasi.
    Nie jest jednak też super dokładny ale napewno lepiej niż z funcjami z delay.h niedokładność tutaj polega na dużej stracie czasu przez funkcje sprawdzania flagi TOV0: while(bit_is_clear(TIFR,TOV0));
    Ale jak chcesz 1 s to faktycznie jest tak jak napisał kolega, że bez przerwań nie da rady..
  • #10 2972751
    Kubbaz
    Poziom 26  
    Posty: 1237
    Pomógł: 9
    Ocena: 30
    Prymulka napisał:
    robisz pętlę for np na 40x25ms.


    Spróbuje :>, to dam znać.
    SnowBizz napisał:

    #include <avr/io.h>
    #include <avr/interrupt.h>
    #include <avr/signal.h>

    #define tau0 6;

    main()
    {
    unsigned int=3200;

    DDRB=0x01;
    TCNT0=tau0;
    TCCR0=1; //ustawia presckaler

    for(;
    {
    while(bit_is_clear(TIFR,TOV0)); //sprawdza przepełnienie
    TCNT0=tau0;
    licznik--;
    if(licznik==0) //sprawdza czy licznik zmalał do0 z 3200
    {
    PORTB^=0x01;
    licznik=32000;
    }
    TIFR=1<<TOV0;
    }
    }


    Też spróbuje.
  • #11 2984118
    Kubbaz
    Poziom 26  
    Posty: 1237
    Pomógł: 9
    Ocena: 30
    Chciałbym zrobić zegar na przerwaniach wykorzystując jako zewnętrzny układ czasowy NE555 (1Hz). Tylko nasuwa się jedno pytanie:

    Dotychczas używając przerwań stosowałem się do poniższego schematu:
    Złe odliczanie czasu w zegarku na ATMEGA8(L) z kwarcem 8MHz - jak poprawić?
    Jak widać przerwanie było "widoczne" przez uC jako stan niski (logiczne 0) - poprzez zwarcie do masy (GND) - jeśli się mylę poprawcie mnie.

    Teraz jeśli chciałbym podłączyć NE555, to na INT0/INT1 podawane było by napięcie ok. 5V z częstotliwością 1Hz, czy to że na tym wejściu pojawi się napięcie nie uszkodzi uC ??
  • #12 2986358
    Prymulka
    Poziom 18  
    Posty: 378
    Pomógł: 9
    Ocena: 8
    W zadnym wypadku :) Wylacz tylko wewnetrzne pull-upy tak aby Ci portu nie podciagalo do plusa a wykrycie przerwania zmien na zmiane stanu wejscia.

    PS. Czy delay juz dobrze dziala?
  • #13 2986744
    Kubbaz
    Poziom 26  
    Posty: 1237
    Pomógł: 9
    Ocena: 30
    Prymulka napisał:
    W zadnym wypadku

    Również tak sądziłem, stąd to upewniające pytanie.

    Prymulka napisał:
    Wylacz tylko wewnetrzne pull-upy tak aby Ci portu nie podciagalo do plusa a wykrycie przerwania zmien na zmiane stanu wejscia.


    ;|, myślałem żę dobrze znam slang elektroników, ale okazało się że ciągle się uczę:D.
    Jak byśmógł nieco prościej powiedzieć co mam zrobić, aby działało, ewentualnie jakiś schemat ....a będzie wszystko O.K.

    Prymulka napisał:
    PS. Czy delay juz dobrze dziala?

    Generalnie ten fabryczny _delay_ms w pętli się całkiem dobrze spisuje (10 sekund można odliczyć całkiem dokładnie), pomijając momenty, kiedy to kompilator w ogóle nie widzi delayów, nie wiedzieć dlaczego ....
    pozdrawiam.
  • #14 2987767
    Prymulka
    Poziom 18  
    Posty: 378
    Pomógł: 9
    Ocena: 8
    Ucz sie ucz :)
    Pull-upy masz wtedy gdy do PORTD wpiszesz 1 (zakładając ze DDRD = 0 czyli wejscie). Jest to typowe podlaczenie dla przyciskow zwierających do masy. Teraz wpisz do PORTD = 0 a na wejsciu bedziesz mial 0 albo 1 banalne :) powodzenia z tym ukladem. Mam tylko nadzieje ze nie wstawiles generatora 1 Hz bo nie mogles sobie poradzic z wewnetrznym odliczaniem czasu bo to by bylo juz male przegiecie. Pozdrawiam
  • #15 2988524
    przemek20
    Poziom 21  
    Posty: 328
    Pomógł: 41
    Ocena: 25
    Delaye są niedokładne ale nie tak jak mówisz. Skoro piszesz ze masz 8MHz a w programie zadeklarowane 1MHz to to jest problem z delayami. Chcecie zegarek robić na 555??? Skoro Atmega8 ma real time counter???
    podłączasz do atmega8 kwarc zegarkowy 32kHz i masz wspaniały precyzyjny zegarek po odpowiednim ustawieniu timera0. Pozdrawiam
  • #16 2989768
    Kubbaz
    Poziom 26  
    Posty: 1237
    Pomógł: 9
    Ocena: 30
    Na razie zaprzestałem działań nad dosyć dokładnym delayem.
    do moich celów wystarczy mi ten:
    
    #define F_CPU       8000000                   // 1MHz zegar procesora -> częstotliwość kwarcu
    #define CYCLES_PER_US ((F_CPU+500000)/1000000) // cpu cykli na mikrosekunde 
    
    void delay(unsigned int us) // opóźnienie w mikrosekundach us -> delay
    {
        unsigned int delay_loops;
        register unsigned int  i;
        delay_loops = (us+3)/5*CYCLES_PER_US; // +3 for rounding up (dirty)
        for (i=0; i < delay_loops; i++) {};
    } 

    działa, jest całkiem niezły.


    Temat zamykam.

Podsumowanie tematu

✨ Problemem było niedokładne odliczanie czasu w zegarku cyfrowym opartym na mikrokontrolerze ATMEGA8(L) z zewnętrznym kwarcem 8MHz i wyświetlaczem LCD 2x24. Użytkownik stosował funkcje opóźniające z biblioteki delay.h, które okazały się niedokładne i ograniczone maksymalnym czasem opóźnienia zależnym od częstotliwości taktowania. Zalecano rezygnację z funkcji delay na rzecz wykorzystania timerów mikrokontrolera oraz przerwań, co pozwala na precyzyjne odmierzanie czasu. Przykładowy kod z użyciem timera0 i preskalera pokazano jako lepszą alternatywę, choć nadal nie idealną. Dyskutowano także o podłączeniu zewnętrznego generatora impulsów NE555 (1Hz) do wejścia przerwań INT0 mikrokontrolera, z zaleceniem wyłączenia wewnętrznych rezystorów podciągających i wykrywania przerwania na zmianę stanu sygnału. Podkreślono, że dla dokładnego zegara lepiej użyć kwarcu zegarkowego 32kHz i timerów w trybie rzeczywistego czasu. Ostatecznie autor zaakceptował prostą funkcję delay opartą na pętli i poprawnym ustawieniu F_CPU na 8MHz, zamykając temat.
Wygenerowane przez model językowy.
REKLAMA