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

[ATmega32][c] Przerwanie od porównania w timer1 - ograniczenia

macieqs 10 Lis 2011 17:30 2314 10
REKLAMA
  • #1 10123609
    macieqs
    Poziom 10  
    Witam!

    Mam program, który wykorzystuje liczniki do wysyłania impulsów o danej częstotliwości. Częstotliwością steruję za pomocą zmiennej okres1, która jest mnożnikiem preskalera. Np. preskaler=8 clkio=1000000Mhz okres1=127, czyli co 8*128=1024us zmienia się stan na danym pinie przerwania od przepełnienia licznika (dla timera1a jest to pd5), co znaczy, że co 2048us jest ustawiana jedynka co daje nam częstotliwość 488,28Hz
    Po przeliczeniu wychodzi nam, że w ciągu 10s wysłanych będzie 4882 takich impulsów. Wektor przerwania wywołany dwa razy tyle 2*4882 wyłącza timer i zapala diode (lub zeruje zmienną pomocniczą i zmienia stan na diodzie)
    Wszystko działa dobrze - trwa to 10s, sprawdzane wiele razy.
    Klocki się zaczynają jak zmniejszam okres poniżej tej wartości.
    Co gorsza jeśli w takim przypadku chce zatrzymać w jednym momencie wszystkie 3 timery to jest problem, a przy wyższych częstotliwościach np. jeden timer się zatrzymuje, a reszta nie... Nie mam oscyloskopu pod ręką :/

    oto mój kod testowy:

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Podsumowując, potrzebuję odpowiedzi na pytanie:
    Dlaczego przerwanie od porównania dla okresów pomiędzy przerwaniami mniejszych od 1024us nie działa poprawnie?
    Czy wywołanie wektora od przerwania zajmuje czas?
    Jak to inaczej zrealizować, żeby program zliczał poprawnie impulsy w odstępach np 8us, a po zliczeniu wyłączał licznik? A właściwie wszystkie 3 liczniki - bo wszystkich potrzebuje?
    Czy zwiększenie częstotliwości oscylatora wewnętrznego coś da?
    Proszę o pomoc...
  • REKLAMA
  • #2 10123810
    INTOUCH
    Poziom 30  
    Gdzieś na forum był już podobny temat przerabiany.
    Instrukcja skoku do przerwania trwa z tego co wiem 4 cykle zegarowe.
    W trakcie obsługi jednego przerwania inne nie jest obsługiwane. Zostanie wykonane dopiero po zakończeniu obsługi poprzedniego przerwania. Jeżeli co najmniej dwa przerwania wystąpią w tym samym czasie to zostanie obsłużone to z niższą wartością w wektorze przerwań (czyli o wyższym priorytecie ).
    Jeżeli chcesz wyłączyć jednocześnie wszystkie liczniki to musisz zastosować cli(); lub zrobić to w ATOMIC_BLOCK


    Kod: C / C++
    Zaloguj się, aby zobaczyć kod



    i czemu masz
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    powinno być
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
  • #3 10124017
    macieqs
    Poziom 10  
    dziękuję za cenne uwagi, tak właśnie myślałem, że wywołanie przerwania zajmuje trochę czasu, ale rozumiem że z zewnętrznym kwarcem będę w stanie pracować na takich częstotliwością z taką koncepcją programu, prawda?

    Zauważyłem, że po prostu impuls nie może być krótszy od tych kilku taktów oscylatora, wtedy jest ok.
  • REKLAMA
  • #4 10124229
    INTOUCH
    Poziom 30  
    Nie tylko wywołanie ale procedura obsługi przerwania zajmuje troszkę czasu.
    Może zdarzyć się taki przypadek że procedura obsługi przerwania trwa zbyt długo. W tedy aktualne przerwanie blokuje obsługę samego siebie.
    Przykład

    Timer0 - liczysz czar 8us.
    Po obliczeniu 8us następuje procedura obsługi przerwania od timera0, która trwa około 15us.
    W trakcie obsługi przerwania timer0 znów odmierza czas 8us, i po odliczeniu 8us nie możesz ponownie wejść do procedury obsługi przerwania od timera0, tylko po 15us.
  • #5 10124252
    janbernat
    Poziom 38  
    Wejscie zajmuje 13 taktów a wyjście 15 taktów- przynajmniej tak u mnie jest.
    Wejdź po kompilacji w plik .lss i zobacz ile rozkazów zajmuje prolog i ile epilog przerwania.
    Rozkazy trwają 1 lub 2- a reti aż 4 cykle zegara.
    To znajdziesz w tabelce na str.329 i dalej.
    To można precyzyjnie obliczyć.
    No i w przerwanie procesor wejdzie dopiero jak obsłuży rozkaz który aktualnie wykonuje- to może zająć 1-4 cykli zegara i jest losowe.
    W pliku .lss możesz też obliczyć ile trwa obsługa przerwania- w tym czasie żadne inne przerwanie (zwykle) nie jest obsługiwane.
  • REKLAMA
  • #6 10124430
    macieqs
    Poziom 10  
    Proszę o instrukcję jak z pliku .lss obliczyć ile co trwa.
    Czy chodzi o to, żeby policzyć np. w "00000054 <__ctors_end>:" wszystkie 'eor'*1 takt 'out' *1takt i 'ldi'*1takt?
    a które to jest wektor przerwania? '0000008e <__bad_interrupt>:' ?
    tam jest tylko jmp...

    W każdym razie doświadczalnie dowiodłem, że muszę się ograniczyć do okresu 512us czyli do częstotliwości ok 2kHz.
    Ponowię pytanie: Kwarc pozwoli mi zwiększyć tą częstotliwość? Czy muszę zmienić koncepcję programu?
  • #7 10124543
    janbernat
    Poziom 38  
    W pliku .lss szukasz tego wektora na podstawie dokumentacji str44.
    I szukasz prologu- push itp. i epilogu pop itp.
    Wpisz reti w wyszukiwanie- tam będą wyjścia z przerwań.
    A to że kwarc pozwoli na szybsze działanie- to oczywiste.
    AVR można stosunkowo bezpiecznie taktować 20MHz.
  • Pomocny post
    #8 10125465
    Andrzej__S
    Poziom 28  
    Twoje procedury obsługi przerwań są stosunkowo długie. Np. ISR(TIMER1_COMPA_vect) trwa ok. 670 taktów zegara. Z kolei odstęp między kolejnymi przerwaniami przy zmiennej okres1=127, jak sam napisałeś, wynosi 8*128=1024 taktów. Jeżeli zmienną okres1 zmniejszysz np. do 80 taktów, to przerwania będą częściej niż trwa ich obsługa, więc niektóre zostaną po prostu "zgubione". Ale to tylko jedno przerwanie, a co będzie, kiedy włączysz wszystkie, które potrzebują podobną ilość taktów?
    Procedury są długie głównie ze względu na zastosowany typ obsługiwanych danych (64-bitowa liczba całkowita). Z kolei w programie, który przedstawiłeś nie jest to kompletnie uzasadnione. Wystarczyłoby, gdyby zmienne cnt1 i imp1 były 16-bitowe (uint16_t), a cnt i cnt2 mogą być nawet 8-bitowe (uint8_t). Jeśli koniecznie potrzebujesz większego zakresu tych zmiennych, to przynajmniej postaraj się ograniczyć ich zakres do niezbędnego minimum. Nawet zmiana typu na 32-bitowy powinna znacznie skrócić procedurę obsługi przerwania, a co za tym idzie, będziesz mógł je wywoływać częściej.
    //------- EDIT -------------
    Po sprawdzeniu mogę dodać, że po zastosowaniu zaproponowanych przeze mnie typów zmiennych procedura obsługi przerwania trwa ok. 60 taktów, czyli ponad 10-krotnie krócej. Niemniej nie znam całkowitej koncepcji programu, więc do Ciebie należy decyzja, jaki zakres zmiennych potrzebujesz.
    //--- END EDIT ------------
    Zwiększenie częstotliwości oczywiście przyspieszy wykonywanie programu, ale myślę, że w zupełności wystarczyłoby ustawić wewnętrzny zegar na 8MHz (chyba, że potrzebujesz dużej precyzji odmierzania czasu) i ustawić preskaler timera na 64. Uzyskasz wtedy taką samą okres taktowania timera, jak w tej chwili (8us), za to odstęp w taktach będzie 8-krotnie większy. Jeżeli oprócz tego zmniejszysz zakres zmiennych cnt, cnt1, cnt2 i imp1 (zastosujesz inne typy, o czym pisałem wcześniej), to bez problemu powinieneś zejść poniżej tych 512us.

    BTW. Nie wiem do czego stosujesz to _delay_ms(40), ale stosowanie funkcji opóźniających z biblioteki <util/delay.h> przy włączonej obsłudze przerwań jest wysoce niedokładne - czas może być wielokrotnie dłuższy od oczekiwanego.
  • #9 10163853
    macieqs
    Poziom 10  
    Witam ponownie,

    Dziękuję za odpowiedzi, bardzo mi pomogły - zmniejszyłem rozmiar zmiennych, przez co zmniejszyłem czas przerwania (przynajmniej taką mam nadzieję), rozbudowałem trochę program.

    _delay_ms(40) stosowałem żeby przycisk po zmianie stanu nie "iskrzył"

    Niestety problem znów się pojawił:
    Program nie wchodzi do pętli while, gdzie warunek jest niespełniony dopiero po zatrzymaniu dopiero co ustawionego rejestru licznika, próbowałem jeszcze inny sposób, z bitami zmiennej flag ustawianymi w przerwaniach - do przerwania wchodzi, ale jak wróci do programu to nie wychodzi z pętli - tak jakby warunek był cały czas spełniony(dany bit równy 0), mimo, że bit był ustawiony na 1 (podejrzewałem, że to problem z tym, że nie wracał do jakiegoś tam miejsca w pamięci, żeby sprawdzić znów warunek)

    proszę o pomoc i wyrozumiałość, nie jestem wprawnym programistą...

    oto mój kod:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Dodano po 1 [godziny] 2 [minuty]:

    Noc jednak okazała się owocna, problem chyba okazał się rozwiązany - wsadziłem ustawianie timerów do funkcji i już... chyba po problemie, jutro jeszcze raz to sprawdzę, czy aby na pewno mi o to chodziło i zamknę temat. Dodatkowo zmieniłem nieco obsługę przerwań, według zaleceń ze strony: Link

    tutaj kod:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod



    PS. Przepraszam za zamieszanie, tak to jest jak się czegoś nie studiuje dogłębnie...
  • REKLAMA
  • Pomocny post
    #10 10163979
    dondu
    Moderator na urlopie...
    macieqs napisał:
    Niestety problem znów się pojawił:
    Program nie wchodzi do pętli while, gdzie warunek jest niespełniony dopiero po zatrzymaniu dopiero co ustawionego rejestru licznika, próbowałem jeszcze inny sposób, z bitami zmiennej flag ustawianymi w przerwaniach - do przerwania wchodzi, ale jak wróci do programu to nie wychodzi z pętli - tak jakby warunek był cały czas spełniony(dany bit równy 0), mimo, że bit był ustawiony na 1 (podejrzewałem, że to problem z tym, że nie wracał do jakiegoś tam miejsca w pamięci, żeby sprawdzić znów warunek)

    Zainteresuj się VOLATILE: http://mikrokontrolery.blogspot.com/2011/04/problemy-c-volatile.html
    Kolega INTOUCH już 10 listopada podał Ci co powinieneś zrobić:

    INTOUCH 10 listopada napisał:
    i czemu masz
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    powinno być
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
  • #11 10165528
    macieqs
    Poziom 10  
    racja - volatile załatwia sprawę :)
    nie wiedziałem, że jest to aż tak ważne...
REKLAMA