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

AVR - Przerwanie w przerwaniu czy tak się da?

Marek_Gorecki 31 Maj 2015 10:48 2811 49
  • Pomocny post
    #31 31 Maj 2015 10:48
    Andrzej__S
    Poziom 28  

    Marek_Gorecki napisał:
    Do kolegów co podpowiadają mi użycie makr, tak jak pisałem wczesniej, uzywam Codevision a w nim nie ma czegoś takiego - chyba.


    Jak już wcześniej napisałem, moim zdaniem przy takich procedurach obsługi przerwań program powinien się wyrobić bez konieczności zagnieżdżania przerwań, a przyczyna złego zliczania może leżeć gdzie indziej.

    Możesz oczywiście przetestować zagnieżdżanie, by się upewnić czy to pomoże, ale wtedy jednak proponowałbym wyłączać wszystkie pozostałe przerwania włącznie z tym, które aktualnie obsługujesz, oprócz tego od enkodera.
    Nie znam CodeVisionAVR, więc nie wiem czy ma odpowiedniki ISR_NOBLOCK oraz sei(), ale na pewno pozwala na użycie wstawek assemblerowych, czyli na przykład przerwania można włączyć za pomocą #asm("sei"), a wyłączyć za pomocą #asm("cli").

    Czyli każda procedura (oprócz INT4) powinna wyglądać tak:
    1. Na początku procedury wyłączenie wszystkich przerwań oprócz INT4.
    2. Włączenie globalnej flagi przerwań #asm("sei")
    3. Właściwy kod przerwania.
    4. Wyłączenie globalnej flagi przerwań #asm("cli")
    5. Ponowne włączenie odpowiednich przerwań na końcu procedury.

    Marek_Gorecki napisał:
    To dodawanie na końcu znaku null rzeczywiscie nie jest potrzebne. Moge je przeciez dodać wtedy gdy potrzebuję.

    Niekoniecznie to zmniejszy ilość taktów potrzebnych na wykonanie procedury. Żeby wiedzieć, kiedy wstawić znak NULL, a kiedy nie, będziesz musiał sprawdzać jakiś warunek, a to też skonsumuje kilka taktów, oby nie więcej, niż zyskasz.
    No chyba, że będziesz ten znak dopisywał poza procedurą, w głównym programie.

  • Arrow Multisolution Day
  • #32 31 Maj 2015 12:20
    Marek_Gorecki
    Poziom 16  

    No to zysk będzie 2 cykle, ale potem znów jest kopiowanie do zmiennej Volatile. Wiec czy coś sie poprawi?

    Dodano po 25 [minuty]:

    Andrzej__S napisał:


    Czyli każda procedura (oprócz INT4) powinna wyglądać tak:
    1. Na początku procedury wyłączenie wszystkich przerwań oprócz INT4.
    2. Włączenie globalnej flagi przerwań #asm("sei")
    3. Właściwy kod przerwania.
    4. Wyłączenie globalnej flagi przerwań #asm("cli")
    5. Ponowne włączenie odpowiednich przerwań na końcu procedury.



    a po co na końcu dawac komende #asm("cli")?
    Czy kompilator nie zrobi tego natychmiast sei ?

  • #33 31 Maj 2015 12:46
    dondu
    Moderator Mikrokontrolery Projektowanie

    Marek_Gorecki napisał:
    a po co na końcu dawac komende #asm("cli")?
    Czy kompilator nie zrobi tego natychmiast sei ?

    Po to, by włączanie przerwania np. UART w punkcie 5 nie spowodowało wykonania tego przerwania, gdy mikrokontroler będzie jeszcze w przerwaniu INT0.

  • Pomocny post
    #34 31 Maj 2015 13:36
    Andrzej__S
    Poziom 28  

    Marek_Gorecki napisał:
    a po co na końcu dawac komende #asm("cli")?
    Czy kompilator nie zrobi tego natychmiast sei ?


    Kompilator nie dodaje SEI. Flaga I w SREG jest automatycznie - sprzętowo, a nie programowo - czyli procesor sam ją ustawia w momencie wyjścia z procedury obsługi przerwania. Jednak obsługa przerwania nie kończy się w momencie zakończenia wykonywania kodu napisanego przez programistę, po tym następuje jeszcze tzw. epilog, czyli przywrócenie wartości rejestrom używanym w procedurze. Przerwania pojawiają się w przypadkowych momentach, dlatego na początku obsługi przerwania należy zapamiętać stan używanych rejestrów oraz rejestru statusowego SREG (na stosie), a po zakończeniu przywrócić. Pisząc w C nie widzimy tego, bo robi to za nas kompilator.

    W czasie wykonywania procedury obsługi przerwania wyłączasz tylko zezwolenie na inne przerwania, ale ich flagi mogą nadal być ustawiane. Jeśli później zezwolisz na jakieś przerwanie, którego flaga została ustawiona w międzyczasie, to (przy włączonej globalnej fladze zezwolenia, którą ustawiasz na początku) mikrokontroler natychmiast przejdzie do procedury jego obsługi, nie czekając na zakończenie aktualnej. To może znacząco wydłużyć procedurę obsługi obecnego przerwania, bo musi ono czekać na wykonanie innego przerwania, w którym to może dojść do podobnej sytuacji, itd. itd. W ten sposób procedura może trwać tak długo, że niektóre przerwania zostaną "przeoczone" przez mikrokontroler. A może się przecież zdarzyć, że zostanie ustawiona więcej niż jedna flaga. Prawdopodobieństwo rośnie wraz z ilością obsługiwanych przerwań, a Ty masz ich, jeśli dobrze policzyłem, siedem.

    Podobny schemat może się pojawić w czasie wykonywania epilogu. No i oczywiście, jeśli nie wyłączymy zezwolenia dla innych przerwań przed SEI w procedurze obsługi przerwania. Generalnie im mniej przerwań mamy aktywnych w czasie obsługi przerwania, tym lepiej. W skrajnych przypadkach może wtedy nawet dojść do zagnieżdżania się procedury w samej sobie, co już w ogóle jest niebezpieczne i zwykle prowadzi do katastrofy.

    Wyzerowanie flagi globalnej #asm("cli") zapobiegnie temu mechanizmowi, ponieważ mikrokotroler nawet po zezwoleniu na poszczególne przerwania, których flagi są aktualnie ustawione, musi czekać do końca procedury obsługi aktualnego przerwania i ustawienie flagi globalnej.

  • Pomocny post
    #35 31 Maj 2015 13:39
    dondu
    Moderator Mikrokontrolery Projektowanie

    Andrzej__S napisał:
    Kompilator nie dodaje SEI. Flaga I w SREG jest automatycznie - sprzętowo, a nie programowo - czyli procesor sam ją ustawia w momencie wyjścia z procedury obsługi przerwania.

    ... a konkretnie robi to rozkaz powrotu z funkcji obsługi przerwania: RETI (Interrupt Return)
    i tym właśnie różni się od rozkazu RET (Subroutine Return) dla zwykłych powrotów (język C: return).

  • #36 31 Maj 2015 13:47
    Marek_Gorecki
    Poziom 16  

    Dzieki Panowie - mozna sie wiele nauczyć.
    Jutro będę próbował wszystkie uwagi zastosować.
    Tak sie zastanawiam i wiele racji może miec kolega Fredy.
    Rzczywiscie może byc tak, że na pojemnościach kabla nie zdąży się zmienić stan drugiego kanału bo zasotosowałem rezystor aż 10k.
    Jutro zmniejszę jego wartość, oraz zrobie próbe z otwarciem przerwań w przerwaniach.

  • Arrow Multisolution Day
  • #37 31 Maj 2015 14:04
    Andrzej__S
    Poziom 28  

    dondu napisał:


    Andrzej__S napisał:

    Kompilator nie dodaje SEI. Flaga I w SREG jest automatycznie - sprzętowo, a nie programowo - czyli procesor sam ją ustawia w momencie wyjścia z procedury obsługi przerwania.

    ... a konkretnie robi to rozkaz powrotu z funkcji obsługi przerwania: RETI (Interrupt Return)


    No tak, czyli jednak też i w pewnym sensie programowo :)
    Niezbyt dobrze to sprecyzowałem, więc dziękuję za wyprostowanie :)

    dondu napisał:
    i tym właśnie różni się od rozkazu RET (Subroutine Return) dla zwykłych powrotów (język C: return)

    Dodam, iż kompilator avr-gcc (nie wiem jak inne) jest na tyle sprytny, że tłumaczy użycie return wewnątrz procedury obsługi przerwania nie jako RET, a jako skok do epilogu, który na końcu posiada instrukcję RETI. Można więc bezpiecznie używać instrukcji return w obsłudze przerwania, bez obawy, że ustawienie flagi zezwalającej na przerwania i/lub epilog zostaną pominięte.

  • #39 31 Maj 2015 14:54
    tmf
    Moderator Mikrokontrolery Projektowanie

    @Andrzej__S Nie tyle jest to spryt kompilatora, co spryt osób robiących protezę (w C nie jest określony sposób obsługi przerwań, w ogóle C nie definiuje przerwań). Funkcje obsługi przerwań w avr-gcc są protezowane w ten sposób, że przypisuje im się atrybut interrupt, a w backendize avr-gcc są jawnie określone prologi i epilogi takich funkcji. Tak naprawdę akurat jest to przykład na głupotę kompilatora, bo niezależnie jakie rejestry w ISR są użyte, to prolog i epilog zachowuja, a następnie odtwarzają wszystkie rejestry MCU. W efekcie proste funkcje mają olbrzymi narzut.
    BTW, część programistów uważa, że użycie return w kodzie funkcji, w innym miejscu niż na jej końcu jest niewłaściwe. Dlaczego? Bo powoduje, że funkcja ma kilka miejsc w których się końcy, co utrudnia analizę kodu. Warto o tym pamiętać, oczywiście nie warto być zbyt ortodoksyjnym :)

  • #40 31 Maj 2015 15:40
    Marek_Gorecki
    Poziom 16  

    A wracając jeszcze do analizy detekcji encodera.
    Rozumiem czemu analiza na jednym zboczu jest niebezpieczna. Może po prostu encoder stać dokładnie w miejscu przełaczenia i minimalne drgania przenoszą się na wynik.
    To zaobserwowałem i zlikwidowałem dodając jeszcze analizę drugiego zbocza.
    Ale Kolega Atom wspomniał tu o analizie na wszystkich czterech zboczach.
    Mam pytanie - czy jest to konieczne?
    Czy są jakieś sytuacje, gdy może pojawić się drgania encodera, które spowodują błedy jeśli analiza odbywa sie na 2 zboczach jednego impulsu (narastające i opadające) ?

  • #41 31 Maj 2015 15:43
    atom1477
    Poziom 43  

    Analiza na 4 zboczach zwiększa rozdzielczość.
    Tobie nie jest potrzebna bo jak pisałeś i tak masz dużo za dużo impulsów na obrót.

  • #42 31 Maj 2015 18:22
    jnk0le
    Poziom 18  

    A jak działa enkoder bez odbierania danych z uartów? Może problem jest gdzie indziej niż szukamy.

  • #43 31 Maj 2015 18:29
    Andrzej__S
    Poziom 28  

    @tmf
    Skierowałeś swoją odpowiedź do mnie, a skąd przypuszczenie, że ja tego nie rozumiem? ;)

    Nie twierdzę, że jestem alfą i omegą, ale co nieco tam wiem ;). Akurat o tym, o czym piszesz już trochę czytałem.

    W tym przypadku po prostu tylko uzupełniłem odpowiedź kolegi dondu:

    dondu napisał:
    i tym właśnie różni się od rozkazu RET (Subroutine Return) dla zwykłych powrotów (język C: return)

    żeby ktoś źle nie zrozumiał, że return w C jest równoznaczne z RET w assemblerze, bo takiej zależności nie ma, a że zwykle zbytnio się wdaję w szczegóły :), więc się nie rozpisywałem.

    tmf napisał:
    BTW, część programistów uważa, że użycie return w kodzie funkcji, w innym miejscu niż na jej końcu jest niewłaściwe. Dlaczego? Bo powoduje, że funkcja ma kilka miejsc w których się końcy, co utrudnia analizę kodu. Warto o tym pamiętać, oczywiście nie warto być zbyt ortodoksyjnym

    Pewnie to jednak lepsze niż np. goto (trudno się było oduczyć po przejściu z assemblera na C :))

    tmf napisał:
    Tak naprawdę akurat jest to przykład na głupotę kompilatora, bo niezależnie jakie rejestry w ISR są użyte, to prolog i epilog zachowuja, a następnie odtwarzają wszystkie rejestry MCU

    Nigdy w to nie wnikałem. Zawsze jak potrzebuję wydajnej procedury obsługi przerwania, piszę ją w asm i wtedy odkładam na stos tylko używane rejestry (tego już pominąć się nie da, ale żeby wszystkie?). Z ciekawości spojrzałem w okno disassembly i u mnie zwykle kompilator C odkłada rejestry R0, R1 i R18 do R31. Być może nadmiarowo, nie sprawdzałem czy wszystkie są używane, ale nie są to "wszystkie rejestry MCU". Może źle zrozumiałem zwrot "wszystkie rejestry MCU", może chodziło o "wszystkie wcześniej ustalone, niezależnie od tego, czy jest to potrzebne".

  • #44 31 Maj 2015 18:37
    dondu
    Moderator Mikrokontrolery Projektowanie

    Andrzej__S napisał:
    W tym przypadku po prostu tylko uzupełniłem odpowiedź kolegi dondu:
    dondu napisał:
    i tym właśnie różni się od rozkazu RET (Subroutine Return) dla zwykłych powrotów (język C: return)

    żeby ktoś źle nie zrozumiał, że return w C jest równoznaczne z RET w assemblerze, bo takiej zależności nie ma, a że zwykle zbytnio się wdaję w szczegóły :), więc się nie rozpisywałem.

    Tomek jedynie nieco rozwinął temat, Ty uzupełniłeś mnie, ja Ciebie - wszystko jest OK :)

  • #45 31 Maj 2015 18:46
    Andrzej__S
    Poziom 28  

    ...ale ja nie mam pretensji, po prostu odczuwałem potrzebę, żeby się wytłumaczyć :D

  • Pomocny post
    #46 31 Maj 2015 18:49
    jnk0le
    Poziom 18  

    Kod: C
    Zaloguj się, aby zobaczyć kod


    U mnie odkłada tylko to co używa.

    EDIT:

    Andrzej__S napisał:

    Marek_Gorecki napisał:
    To dodawanie na końcu znaku null rzeczywiscie nie jest potrzebne. Moge je przeciez dodać wtedy gdy potrzebuję.

    Niekoniecznie to zmniejszy ilość taktów potrzebnych na wykonanie procedury. Żeby wiedzieć, kiedy wstawić znak NULL, a kiedy nie, będziesz musiał sprawdzać jakiś warunek, a to też skonsumuje kilka taktów, oby nie więcej, niż zyskasz.
    No chyba, że będziesz ten znak dopisywał poza procedurą, w głównym programie.


    W każdym przypadku gdy nie potrzeba ultra-krótkiej procedury odbioru należy używać buforów kołowych.

    Dla przykładu - wycinek z mojej biblioteki (po optymalizacjach):
    https://github.com/jnk0le/Easy-AVR-USART-C-Library
    Kod: c
    Zaloguj się, aby zobaczyć kod

  • #47 02 Cze 2015 13:21
    Marek_Gorecki
    Poziom 16  

    Do jnkOle - możesz mi powiedzieć czym różni się Twoja procedura odczytu RSa od mojej, która wygląda tak:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    Wydaje mi się że są prawie identyczne.

  • #48 02 Cze 2015 14:33
    jnk0le
    Poziom 18  

    Marek_Gorecki napisał:
    Do jnkOle - możesz mi powiedzieć czym różni się Twoja procedura odczytu RSa od mojej, która wygląda tak:
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Wydaje mi się że są prawie identyczne.


    http://pl.wikipedia.org/wiki/Bufor_cykliczny

    Krótko mówiąc - rx0_index w końcu zrówna się z rx0_buffer_size, i co dalej ?
    Procedura odbioru przesuwająca całą tablice o jeden element w lewo będzie troszeczkę nieefektywna, A u mnie poprzez proste maskowanie wskaźniki się zerują po osiągnięciu rx0_buffer_size.

    Oczywiście rozmiar bufora musi być równy 2^n (BUFFER_MASK = BUFFER_SIZE -1)

  • #49 18 Sie 2015 19:18
    tmf
    Moderator Mikrokontrolery Projektowanie

    Andrzej__S napisał:

    tmf napisał:
    Tak naprawdę akurat jest to przykład na głupotę kompilatora, bo niezależnie jakie rejestry w ISR są użyte, to prolog i epilog zachowuja, a następnie odtwarzają wszystkie rejestry MCU

    Nigdy w to nie wnikałem. Zawsze jak potrzebuję wydajnej procedury obsługi przerwania, piszę ją w asm i wtedy odkładam na stos tylko używane rejestry (tego już pominąć się nie da, ale żeby wszystkie?). Z ciekawości spojrzałem w okno disassembly i u mnie zwykle kompilator C odkłada rejestry R0, R1 i R18 do R31. Być może nadmiarowo, nie sprawdzałem czy wszystkie są używane, ale nie są to "wszystkie rejestry MCU". Może źle zrozumiałem zwrot "wszystkie rejestry MCU", może chodziło o "wszystkie wcześniej ustalone, niezależnie od tego, czy jest to potrzebne".


    Ponieważ w innym temacie wywiązała się dyskusja na ten temat więc wyjaśniam - oczywiście nie wszystkie, lecz wiele niepotrzebnych - wynika to z ABI kompilatora, w którym pewne rejestry są zachowywane na stosie, gdyż ich zawartość jest gwarantowana po wyjściu z funkcji. Aby to zaobserwować niestety nie wystarczy trywialny handler przerwań pokazany wcześniej, lecz handler w którym jest wywoływana jakaś funkcja (której kompilator nie może osadzić). W takim przypadku kompilator zachowuje wiele rejestrów określonych przez ABI, nawet jeśli nie jest to konieczne. Zachowuje też R1 i go zeruje, nawet jeśli R1 nie jest w ISR wykorzystywany co znowu wynika z ABI kompilatora. Nie są to oczywiście wszystkie rejestry MCU i w tym zakresie mój post był błędny, ale jest zachowywanych dużo więcej rejestrów niż jest wymagane.

  • #50 19 Wrz 2015 20:49
    jnk0le
    Poziom 18  

    Jeśli nie jest jeszcze za późno to mógłbym polecić najbardziej hardcorowe rozwiązanie dla uarta

    Kod: c
    Zaloguj się, aby zobaczyć kod




    Podobne rozwiązanie można zastosować dla INTn_vect.