Elektroda.pl
Elektroda.pl
X
Elektroda.pl
Proszę, dodaj wyjątek dla www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

Atmega16, licznik impulsów, częstościomierz, c

11 Paź 2011 09:56 3342 13
  • Poziom 9  
    uczę się programowania AVR w c i na ATMEGA16 napisałem prosty programik mający zliczać impulsy w czasie 1 sekundy

    timer 1 odmierza czas 1 sekundy
    timer 0 zlicza impulsy i przy przepełnieniu inkrementuje zmienną
    do wyświetlenia wyniku zmienna jest przemnażana przez 256 a do wyniku dodawana jest bieżąca wartość rejestru TCNT0

    do celów ćwiczenia, jako źródła zliczanych impulsów użyłem zegara procesora i skierowałem je do timera 0
    przy takiej konfiguracji oraz zegarze procesora 8MHz na wyświetlaczu powinien zawsze pojawiać się wynik 8.000.000

    na wyświetlaczu pojawiają się jednak 3 różne wyniki:

    7.999.746
    8.000.002
    8.000.258

    jak łatwo zauważyć, wyniki różnią się od siebie kolejno o 256
    gdzie leży przyczyna?
    skąd pochodzą te 2 takty przy wskazaniu 8.000.002?


    Kod: c
    Zaloguj się, aby zobaczyć kod
  • Poziom 30  
    Przyczyn może być kilka.
    1). W trakcie obsługi jednego przerwania drugie jest automatycznie blokowane. Przez co są opóźnienia.
    2). Znienne
    unsigned long imp_curr=0;
    unsigned long imp_prev=0;
    powinny być opisane jako
    volatile unsigned long imp_curr=0;
    volatile unsigned long imp_prev=0;

    3). 8.000.002 = (imp_prev*256)+TCNT0; // Cy w trakcie tego działania czasem nie wykonują się przerwania od timerów?

    4). Możesz mieś losowe wyniki ponieważ na na wykonanie każdej instrukcji jest określony czas. Podczas wykonywania procedury obsługi przerwania program w pętli głównej programu nie jest wykonywany.

    5). sei(); powinieneś dać zaraz przed pętlą główną programu.
    6). Co właściwie chcesz osiągnąć?
  • Poziom 28  
    Dodam do tego, co napisał przedmówca, że przerwania TIMER1_COMPA będą generowane co (OCR1A+1)*preskaler (patrz dokumentacja mikrokontrolera ATmega16), czyli w Twoim przypadku będzie to 8000256 taktów (czyli wcale nie co 1 sekundę).

    Ponadto ten fragment:
    Kod: c
    Zaloguj się, aby zobaczyć kod

    w pętli głównej, to już robi kompletny bałagan.
    Po pierwsze funkcja _delay_ms() nie działa prawidłowo przy włączonych przerwaniach. Generowane opóźnienie będzie nie tylko odbiegało od wartości zamierzonej, ale będzie też mało powtarzalne. Jeśli chcesz uzyskać wyświetlanie wyniku tylko po zmianie wartości wyświetlanej, można to zrobić w prostszy i lepszy sposób.
    Po drugie zerowanie licznika TCNT0 w dosyć przypadkowym momencie (patrz wyżej uwagę na temat funkcji _delay_ms() i uwagi w poście kolegi INTOUCH) na pewno nie wpłynie dobrze na dokładność zliczania impulsów.
  • Poziom 9  
    dziękuję Wam za odpowiedź

    INTOUCH napisał:
    Przyczyn może być kilka.
    1). W trakcie obsługi jednego przerwania drugie jest automatycznie blokowane. Przez co są opóźnienia.
    2). Znienne
    unsigned long imp_curr=0;
    unsigned long imp_prev=0;
    powinny być opisane jako
    volatile unsigned long imp_curr=0;
    volatile unsigned long imp_prev=0;

    3). 8.000.002 = (imp_prev*256)+TCNT0; // Cy w trakcie tego działania czasem nie wykonują się przerwania od timerów?

    4). Możesz mieś losowe wyniki ponieważ na na wykonanie każdej instrukcji jest określony czas. Podczas wykonywania procedury obsługi przerwania program w pętli głównej programu nie jest wykonywany.

    5). sei(); powinieneś dać zaraz przed pętlą główną programu.
    6). Co właściwie chcesz osiągnąć?


    pkt 1
    blokowanie przez obsługę przerwania generowanego przez TIMER0 przerwań z TIMER1 istotnie może mieć wpływ na wynik pomiaru; jak temu zaradzić?

    pkt 2 i 5 poprawiłem wg Twoich wskazówek
    różnica: wskazania są o 8 taktów wyższe, tj 8.000.010, 8.000.266

    pkt 3
    masz rację; jak poprawić kod programu?

    pkt 6
    chcę stworzyć najprostszy kod programu wykorzystujący maksymalnie rozwiązania sprzętowe procesora w celu zliczania impulsów
    chce rozumieć każdy krok i każdą zależność-uczę się :)

    czy możecie zaproponować jak konkretnie zmienić kod programu? mnie niestety brakuje wiedzy aby samemu zweryfikować wszystkie wasze propozycje
    bardzo proszę o dalszą pomoc i z góry dziękuję
  • Poziom 9  
    zmieniłem kod programu:
    Kod: c
    Zaloguj się, aby zobaczyć kod


    wskazania na wyświetlaczu są następujące:

    7.999.984
    8.000.239
    8.000.240
    8.000.241

    różnica pomiędzy pierwszą wartością a pozostałymi to ok 256

    przerwanie generowane przez TIMER1 ma pierwszeństow przed generowanym przez TIMER0
    TIMER1 odlicza czas 1 sekundy i generuje przerwanie; w przerwaniu od TIMER1 następuje wyłączenie przerwań od TIMER0
    obsługa przerwania od TIMER1 zeruje zmienną, wartość licznika TCNT0 i wartość licznika TCNT1 (to ostatnie chyba niepotrzebnie)
    ale co się dzieje z TCNT1 w tym czasie? on dalej zlicza 0-255?
    problem leży w wartości tego rejestru, zerowanie go w obsłudze przerwania od TIMER1 nie rozwiązuje problemu

    proszę, zasugerujcie coś, oczywiście oprócz lektury książek :)
  • Moderator Mikrokontrolery Projektowanie
    Pobieżne uwagi z braku czasu na dokładną analizę kodu:
    1. Używasz zmiennej TCNT0_WART w przerwaniu a nie deklarujesz jej jako volatile.
    2. Preskaler można (a czasami, trzeba) wyzerować - sprawdź w dokumentacji - krótkie info na ten temat: http://mikrokontrolery.blogspot.com/2011/03/prescaler-postscaler-co-to.html
  • Poziom 9  
    deklaracja 'volatile' niestety nie rozwiązała problemu, podobnie jak reset preskalera
    poleceniem SFIOR |=(1<PSR10);
  • Poziom 28  
    Obawiam się, że w ten sposób, jak próbujesz to zrobić, nie da się uzyskać precyzji jakiej oczekujesz, przynajmniej dla dużych częstotliwości zliczania (zbliżonych do częstotliwości taktowania mikrokontrolera lub ją przekraczających). Postępując umiejętnie można jedynie zminimalizować błąd. Za dużo by tłumaczyć szczegółowo dlaczego, ale jest to związane z budową systemu przerwań. Sam fakt, że skok do procedury obsługi przerwania trwa 4 takty zegara, powoduje, że każde inne przerwanie pojawiające się w ciągu tych 4 taktów będzie musiało odczekać na swoją kolejkę, nawet przy zastosowaniu zagnieżdżania przerwań. Już to może dać 4 taktowy błąd.

    Nawet gdybyś zastosował zagnieżdżanie, to i tak dojście do momentu, w którym kopiujesz zawartość TCNT0 do zmiennej w najlepszym przypadku (procedura obsługi przerwania napisana w ASM, zmienna TCNT0_WART w rejestrze zamiast w pamięci RAM) zostanie opóźnione o jakieś kilkanaście taktów.

    Ty piszesz w C i nie stosujesz zagnieżdżania. Sama procedura obsługi przerwania od timera 0 (imp_curr++;) zajmuje ok. 60 taktów. Teraz jeśli przerwanie od timera 1 pojawi się w trakcie obsługi przerwania od timera 0, to procedura obsługi przerwania timera 1 rozpocznie się z opóźnieniem trwającym maksymalnie nawet do tych 60 taktów. Do tego zawartość TCNT0 zostanie skopiowana do zmiennej też z opóźnieniem kilkanaście lub nawet kilkadziesiąt taktów. Oczywiście timer 0 w tym czasie zlicza impulsy, które potem dodajesz do wartości wyświetlanej. Rozumiesz teraz skąd się biorą błędy.

    Do takich celów, jakie chcesz osiągnąć należałoby raczej użyć techniki nazywanej "Input Capture". Problemem może być fakt, że można to zastosować tylko dla 16-bitowego timera 1 (ICP1), więc trudno będzie precyzyjnie odmierzyć czas 1 sekundy przy częstotliwości taktowania 8MHz za pomocą timera 8-bitowego (bo tylko takie zostaną do dyspozycji).

    Napisz może, co dokładnie chciałbyś osiągnąć (do czego jest Tobie to potrzebne), wtedy może łatwiej byłoby znaleźć rozwiązanie.
    Bez urazy, ale jeśli problem jest czysto teoretyczny (ma służyć tylko edukacji), to odpuść sobie i spróbuj może na początek nauczyć się łatwiejszych rzeczy.

    No i czytanie książek to wcale nie jest zły pomysł ;)
  • Poziom 38  
    Idealnie to podobno jest na innym świecie.
    Masz 256/8000000 =32ppm
    Stabilność kwarcu- takiego zwykłego- jest 50ppm.
    A dokładność 30ppm.
    Czyli nawet jeśli program napiszesz optymalnie i przerwania będą nieblokujące innych przerwań- (co dałoby się zrobić- ale tylko dla nauki) to i tak dokładność będzie taka jaką masz.
    Możesz poczytać w książce tmf jakich rejestrów można użyć do tego aby przerwania były "naked" i "noblock".
    No i jak to zrobić.
    Poczytać o tym w avr-libc też trzeba.
    W takim projekcie trzeba albo założyć jaką się chce mieć dokładność albo dość głęboko zapoznać się ze sprzętem i językiem aby sprawdzić ile z tego można "wydusić".
    Oczywiście zamiast kwarcu można zastosować rezonator kwarcowy z termostatem- wtedy można się zająć programem i sprzętem.
  • Poziom 28  
    janbernat napisał:

    Masz 256/8000000 =32ppm
    Stabilność kwarcu- takiego zwykłego- jest 50ppm.
    A dokładność 30ppm.

    Oczywiście w realnym projekcie te dokładności mają znaczenie. Nie pomyślałem, żeby o tym wspomnieć, bo autor wątku dla celów testowych użył taktowania obydwu timerów wewnętrznym sygnałem zegarowym. W związku z tym, przynajmniej teoretycznie można przyjąć, że dokładność do 1 taktu zegara jest osiągalna, niestety nie przy pomocy metody jakiej użył kolega PiotrK. W celu uzyskania takiego efektu chyba raczej konieczne jest sprzętowe (równoległe do programu) kopiowanie zawartości licznika do rejestru (ICR), i to starałem się zasugerować.

    Niemniej masz rację, że realnie uzyskanie pomiaru częstotliwości z precyzją 7-cyfrową byłoby dosyć trudne (no i zapewne dosyć kosztowne), głównie ze względu na stabilność częstotliwości wzorcowej (taktowania mikrokontrolera), bo stały błąd można skorygować programowo (oczywiście z maksymalną dokładnością +/- 1/(2*F_CPU)).
  • Poziom 38  
    Wydaje mi się że zamiast unsigned long wystarczy unsigned int.
    Chyba że się pomyliłem w obliczeniach.
    Po kompilacji masz w AVRStudio taki plik z rozszerzeniem .lss.
    Zajrzyj do niego i zobacz ile instrukcji zajmuje odsługa przerwania od Timer1 i Timer2.
    Niektóre z nich trwają jeden cykl maszynowy- 0.125us dla 8MHz- ale inne dwa albo więcej cykli.
    Można obliczyć ile trwa obsługa przerwania.
    Bo Twoje założenie o priorytetach obsługi przerwań
    "przerwanie generowane przez TIMER1 ma pierwszeństow przed generowanym przez TIMER0 "
    jest lekkomyślne.
    W tych procesorach jest tak że priorytet ten jest istotny tylko wtedy gdy oba przerwania
    wystąpią w tym samym cyklu zegara.
    A normalnie jest tak że po wejściu w przerwanie procesor jest "głuchy" na wystąpienie
    innych przerwań.
    Oczywiście można to zmienić atrybutem NOBLOCK- ale trzeba sobie dokładnie zdawać sprawę z konsekwencji.
    W Twoim wypadku próbował bym zrobić przerwanie z atrybutem NAKED- czyli bez prologu i epilogu a zamiast zmiennej imp_curr zastosować jakiś rejestr sprzętowy 16bit- np.OCR1B- i tak nie jest używany.
    Oczywiście jak zajrzymy w .lss to widać że jednak rejestry R24 i R25 są wykorzystywane.
    Trzeba je więc "ręcznie" odłożyć na stos i potem z niego ściągnąć.
    Tzn. procedurę obsługi przerwania napisać w asm.
    No ale zamiast 30 instrukcji zostaje 8.
    Co znacząco skraca czas obsługi przerwania.
    Więcej i dokładniej jest to opisane w książce tmf.
    Tak że książki warto mieć i czytać- zwłaszcza że nareszcie są.
    Można też szukać w sieci- ale zabiera dużo czasu i nie zawsze można ufać.
    Więcej się wymądrzał nie będę- sam jestem początkujący.
  • Poziom 28  
    Ja to widzę tak:
    W celu uzyskania 1 taktowej precyzji należy odczytać (w tym przypadku) rejestr TCNT0 dokładnie w 8000000 takcie.
    Można też go odczytać w innym momencie i odjąć odpowiednią ilość taktów.
    Tylko jaką?
    Kluczowy jest ten fragment dokumentacji technicznej:
    Atmel napisał:

    The interrupt execution response for all the enabled AVR interrupts is four clock cycles minimum. After four clock cycles the program vector address for the actual interrupt handling routine is executed. During this four clock cycle period, the Program Counter is pushed onto the Stack. The vector is normally a jump to the interrupt routine, and this jump takes three clock cycles. If an interrupt occurs during execution of a multi-cycle instruction, this instruction is completed before the interrupt is served.

    Jeśli mikrokontroler jest akurat w stanie wchodzenia w procedurę obsługi przerwania od timera 0, to niemożliwe jest przerwanie tej procedury i wywołanie procedury obsługi przerwania od timera 1 (jeśli takie się pojawi w tym czasie) zostaje opóźnione.
    O ile?
    Tego nie wiemy, bo nie ma możliwości stwierdzenia w którym takcie wchodzenia w procedurę obsługi przerwania timera 0 została ustawiona flaga przerwania od timera 1. Nie wiemy nawet, ile taktów będzie trwało wejście w procedurę obsługi, bo nie da się przewidzieć, czy flaga nie została ustawiona podczas multi-cycle instruction.
    Skąd będzie wiadomo, ile taktów odliczyć, by wynik był prawidłowy?

    Taka technika z przerwaniami i odliczaniem taktów może się udać raczej tylko w przypadku, gdy częstotliwość zliczanych impulsów jest dużo niższa od częstotliwości taktowania mikrokontrolera lub przy mniejszej rozdzielczości pomiaru.

    Moim zdaniem przy takiej częstotliwości zliczania i takiej oczekiwanej precyzji to tylko sprzętowy PWM w połączeniu ze sprzętowym Input Capture zdadzą egzamin, bo wtedy zarówno odmierzanie czasu jak i sczytywanie zawartości licznika są niezależne czasowo od stanu, w jakim znajduje się aktualnie mikrokontroler (wykonywane są "równolegle" do normalnego przebiegu programu).
    Inna sprawa, że trudno będzie ustawić PWM o częstotliwości 1 Hz przy taktowaniu 8MHz na liczniku 8-bitowym. Prawdopodobnie będzie się to wiązało z zastosowaniem jakiegoś precyzyjnego, dodatkowego źródła sygnału zegarowego.
    No chyba, że autor wreszcie zweryfikuje swoje oczekiwania co do precyzji/rozdzielczości pomiaru.