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

Instrukcje współbieżne a wywrotność kodu... [AVR][WinAVR]

AVRowiec 07 Mar 2010 16:11 2558 27
  • #1 7796711
    AVRowiec
    Poziom 18  
    Napisałem pewien kod i interesuje mnie jego awaryjność:

    PRZERWANIE
    {
        FUNKCJAx  // wysyła znaki przez uart.
    }
    
    MAIN
    {
       while 
       {
           FUNKCJAx  //wysyła znaki przez uart.
       }
    }


    Co jeśli funkcja zostanie wywołana jednocześnie przez maina i przerwanie? Czy to sie nie wysypie? Czy procek się nie zawiesi? (Wiem że to nie są wątki)
    Program jest oczywiście dużo bardziej złożony. Większość zadań uartu to tylko ustawianie flag (po porównaniu jakiegoś łańcucha z wzorcem) ale część IFów wysyła również znaki za pośrednictwem FUNKCJIx.
    Teoretycznie uC nie robi dwóch rzeczy na raz ale praktycznie wydaje się że jednak robi bo jak dam delay() w przerwaniu to main się przecież nie zatrzymuje.

    Może lepsza jest metoda flagowa?:

    PRZERWANIE
    {
        if (cośtam) ustaw_flage_Y
    }
    
    MAIN
    {
       while 
       {
           FUNKCJAx  //wysyła znaki przez uart.
    
          if (flaga_Y_ustawiona) { FUNKCJAx;  reset_flagi_Y; }
       }
    }


    Muszę napisać program który jest w 100% bezawaryjny i nie wiem czy lepiej w przerwaniach (uart) tylko i wyłącznie ustawiać flagi czy moge sobie pozwolić na jakieś dodatkowe instrukcje.

    Teoretycznie mój program działa i nie zawiesza się. Ale nie moge liczyć na szczęście :)

    Proszę o radę albo chociaż o namiar na jakąś dobrą książke na tego typu zagadnienia.
  • #2 7796864
    __Maciek__
    Poziom 20  
    Ja bym poszedł w kierunku stosowania flag.

    Powód jest prosty ... jeśli w funkcji wysyłania przez uart grzebiesz w rejestrach .. a pewnie tak .. choćby w rejestrze danych do wysłania to każda niezapowiedziana ( nieprzewidziana ) ingerencja może powodować błędy. Jeśli nie w procesorze to w na drugim końcu kabla.

    Jeśli chodzi o zawieszanie się CPU to osobiście spotkałem się z sytuacją gdy procesor zostawiał ustawione " nadawanie " w sieci RS485 i tym samym blokował całą komunikację, czy funkcjonował prawidłowo tego nie wiem bo nie miałem jak odczytać rejestrów, ale z obserwacji ledów statusu wyglądało że tak. ( Przypadek stosowania FREEMODBUS ) Problemu nie rozwiązałem do tej pory ...

    Z tego co wiem to dobrą zasadą jest pisać program tak aby przerwania były możliwie krótkie.
  • #3 7796876
    asembler
    Poziom 32  
    Według mnie obie metody są do kitu próbuj dalej.
    praktyczne procesor wykonuje jedno zadanie naraz a od programisty wymaga sie zeby sprawdzil wczesniej czy poprzednie zadanie (jezeli jest wykonywane przez sprzet) zostało zakończone. Kumasz baze?
  • #4 7797039
    AVRowiec
    Poziom 18  
    kumam,
    dlatego jestem za systemem flag. funkcje wysyłające po uart zawierają w sobie sprawdzanie czy można wysłać więc nic nowego nie musze dodawać.

    Ale skoro i sposób flagowy Ci się nie podoba to jaki jest lepszy?
  • #6 7798057
    tmf
    VIP Zasłużony dla elektroda
    Wszystko zalezy co ta funkcja robi, jak jest napisana itd. Na tak ogolnym przykladzie jaki pokazales nic nie da sie powiedziec.
  • #7 7798222
    markosik20
    Poziom 33  
    AVRowiec napisał:

    Ale skoro i sposób flagowy Ci się nie podoba to jaki jest lepszy?


    PRZERWANIE
    {
        if (cośtam) ustaw_flage_Y
    }
    
    MAIN
    {
       while
       {
           FUNKCJAx  //wysyła znaki przez uart.
    
          if (flaga_Y_ustawiona && !flaga.UART_wolny) { FUNKCJAx;  reset_flagi_Y;flaga.UART_wolny = 1; }
       }
    }
  • #8 7798330
    asembler
    Poziom 32  
    Dywagujemy na temat wysylaniu znaku z wdoch punktów tylko co ztego bedzie miał odbiorca?
  • #9 7798479
    Konto nie istnieje
    Poziom 1  
  • #10 7798554
    markosik20
    Poziom 33  
    Więc aby nie "przerywać ramek" wystarczy sprawdzić czy poprzednia została w całości wysłana.

    PRZERWANIE
    {
        if (cośtam) ustaw_flage_Y
    }
    
    MAIN
    {
       while
       {
           FUNKCJAx  //wysyła znaki przez uart.
    
          if (flaga_Y_ustawiona && !flaga.ramka_wyslana) { FUNKCJAx;  reset_flagi_Y;flaga.ramka_wyslana = 1; }
       }
    }
  • #11 7798720
    AVRowiec
    Poziom 18  
    ale po co tak?

    przecież najpierw wykona się pierwsza FUNKCJAx
    a potem druga ta po
    if (flaga_Y_ustawiona

    przecież main nie zacznie się w dwóch miejscach wykonywać.

    ew. druga część warunku jeśli w przerwaniu jest FUNKCJAx zamiast ustawiania flagi.
  • #12 7798914
    markosik20
    Poziom 33  
    AVRowiec napisał:
    ale po co tak?

    przecież najpierw wykona się pierwsza FUNKCJAx
    a potem druga ta po
    if (flaga_Y_ustawiona

    przecież main nie zacznie się w dwóch miejscach wykonywać.

    ew. druga część warunku jeśli w przerwaniu jest FUNKCJAx zamiast ustawiania flagi.


    Tak, ale tylko wtedy jeżeli funkcja zakończy się jak cała ramka zostanie wysłana.
    A nie po to są przerwania żeby czekać aż sprzętowy uart wyśle całą ramkę.
  • #13 7798921
    tmf
    VIP Zasłużony dla elektroda
    To wcale nie jest takie oczywiste. Pierwszy przyklad jaki podales jest bledny, bo Funkcja() wywolana z main moze zostac przerwana wywolaniem z procedury obslugi przerwania. Drugi przyklad tez jest bledny, bo testowanie i zmiane flagi musisz realizowac atomowo, czyli na AVR z zablokowanymi przerwaniami. Uprzedzajac - wykorzystanie sei() i cli() prowadzi do bledow zwiazanych z zamiana kolejnosci instrukcji, zapoznaj sie z atomic.h.
  • #14 7798955
    markosik20
    Poziom 33  
    tmf napisał:
    Drugi przyklad tez jest bledny, bo testowanie i zmiane flagi musisz realizowac atomowo, czyli na AVR z zablokowanymi przerwaniami. Uprzedzajac - wykorzystanie sei() i cli() prowadzi do bledow zwiazanych z zamiana kolejnosci instrukcji, zapoznaj sie z atomic.h.


    Biblioteka atomic.h dotyczy raczej zmiennych zmieniających się w przerwaniach i których konkretne wartości łapiemy gdzie indziej. Flaga jako jednobitowy twór jest ustawiana na jeden w przerwaniu więc tutaj nie wystąpi ryzyko "nie złapania wartości", bo jak nie złapiemy w pierwszym obiegu to złapiemy w drugim.
  • #15 7799031
    tmf
    VIP Zasłużony dla elektroda
    Markosik, pomysl jak jest flaga resetowana. Zakladajac, ze flaga nie jest przechowywana w przestrzeni IO do ktorej jest dostep za pomoca sbi/cbi, to kompilator, zeby zresetowac flage musi wykonac sekwencje instrukcji:
    LDI Rx,0
    STS Flaga, Rx
    Teraz pomysl, co sie stanie, kiedy przerwanie zostanie wywolane pomiedzy tymi instrukcjami... Procedura obslugi przerwania ustawi flage, nastapi powrot do main, gdzie flaga zostanie wyzerowana. W efekcie nastapi zgubienie informacji o przerwaniu.
  • #16 7799093
    markosik20
    Poziom 33  
    tmf napisał:
    Procedura obslugi przerwania ustawi flage, nastapi powrot do main, gdzie flaga zostanie wyzerowana. W efekcie nastapi zgubienie informacji o przerwaniu.


    No racja, tylko że jeżeli przerwanie przychodzi częściej jak jeden pełny obieg "main'a" to istnieje także ryzyko niezłapania większej ilości przerwań.
  • #17 7799104
    tmf
    VIP Zasłużony dla elektroda
    To prawda, dlatego ten przyklad ogolnie jest glupi. A jesli go polepszysz zmieniajac flage na jakis licznik to sprawa atomowosci dostepu stanie sie tym bardziej krytyczna.
  • #18 7799125
    markosik20
    Poziom 33  
    tmf napisał:
    A jesli go polepszysz zmieniajac flage na jakis licznik to sprawa atomowosci dostepu stanie sie tym bardziej krytyczna.


    Hmm, użycie lub nieużycie atomic.h zależy od podejścia do problemu :wink:.

    PRZERWANIE
    {
        if (cośtam) 
       {
         if(licznik)licznik--;
     
       }
    
    }
    
    MAIN
    {
       while
       {
           FUNKCJAx  //wysyła znaki przez uart.
           if (!licznik) { FUNKCJAx;licznik = 10; }
       }
    }
    
    


    ale popieram...przykład ewidentnie bezsensowny.
  • #19 7799316
    AVRowiec
    Poziom 18  
    a jakoś po ludzku ktoś mógłby to wytułmaczyć?
    Może odniose się do konkretnego - mojego - przykładu:
    Mam maina który obśługuje flagi pochodzące z trzech przerwań:
    - uart0 Rx
    - uart1 Rx
    - timer1
    W każdym z tych przerwań występue Funkcja wysyłająca znaki przez uart1. Jest też funkcja wysyłająca dane po uart0 i czekające na ustawienie flag z uart0.
    Wszystkie funkcje wysyłające znaki są prosto z datasheeta
    
    void USART1_Transmit( unsigned char data )
    {
    /* Wait for empty transmit buffer */
    while ( !( UCSR1A & (1<<UDRE1)) )
    ;
    /* Put data into buffer, sends the data */
    UDR1 = data;
    }


    pętla while; jest chyba odpowiedzialna za wstrzymanie pewnych instrukcji zanim inne się nie zakończą.
    Krótko mówiąc nie robie wysyłania w przerwaniu bo ramki są zbyt krótkie.
  • #20 7799584
    markosik20
    Poziom 33  
    AVRowiec napisał:
    W każdym z tych przerwań występue Funkcja wysyłająca znaki przez uart1. ..................
    Krótko mówiąc nie robie wysyłania w przerwaniu .........


    To wysyłasz w przerwaniu czy nie?
  • #21 7799673
    tmf
    VIP Zasłużony dla elektroda
    Makrosik, w twoim przykladzie linia:
    if (!licznik) { FUNKCJAx;licznik = 10; }
    musi byc wykonana atomowo. A przynajmniej przypisanie do licznik musi takie byc. Wiec bez atomic sie nie obejdzie.
    AVRowiec: Tak tego nie mozesz zrobic, wykorzystaj przerwanie od nadajnika informujace o pustym buforze to wyeliminujesz petle sprawdzajaca. W twoim przykladzie czas obslugi przerwania moze byc na tyle dlugi, ze mozesz minac kolejne dane, o ile oczywiscie masz full duplex. Przy takiej asynchronicznej obsludze nadajnika potrzebowalbys jakies bufory i kolejkowanie, inaczej to jest bez sensu. Bo albo pewne fragmenty kodu beda sie nawzajem blokowac, lub tak jak pisal Atom, przy tworzeniu ramki danych bedziesz mial wtracenia zaklocajace ich strukture. A z tego juz sie robi powazny wspolbiezny kod. Takze zanim otworzysz puszke Pandory proponuje ci poczytac o tym temacie conieco, postudiowac przyklady i dopiero jak juz bedziesz absolutnie pewien, ze rozumiesz o co chodzi sie za to zabierac. Taki kod jest ekstremalnie trudny do debugowania, takze jak cos skaszanisz to predzej osiwiejesz niz rozwiazasz problem.
  • #22 7799802
    mirekk36
    Poziom 42  
    Może ja czegoś nie rozumiem więc dopytam,

    najpierw markosik20 napisał powiedzmy taki przykład:
    markosik20 napisał:
    PRZERWANIE
    {
        if (cośtam) ustaw_flage_Y
    }
    
    MAIN
    {
       while
       {
           FUNKCJAx  //wysyła znaki przez uart.
    
          if (flaga_Y_ustawiona && !flaga.UART_wolny) { FUNKCJAx;  reset_flagi_Y;flaga.UART_wolny = 1; }
       }
    }


    a tmf na to odpisał tak:
    tmf napisał:
    To wcale nie jest takie oczywiste. Pierwszy przyklad jaki podales jest bledny, bo Funkcja() wywolana z main moze zostac przerwana wywolaniem z procedury obslugi przerwania.

    Przecież procedura przerwania w tym powyższym przykładzie to tylko ustawienie flagi więc co za problem, żeby Funkcja() była przerywana - skoro oba kawałki kodu nie mają nic wspólnego bezpośrednio ze sobą?


    tmf napisał:
    Drugi przyklad tez jest bledny, bo testowanie i zmiane flagi musisz realizowac atomowo, czyli na AVR z zablokowanymi przerwaniami. Uprzedzajac - wykorzystanie sei() i cli() prowadzi do bledow zwiazanych z zamiana kolejnosci instrukcji, zapoznaj sie z atomic.h.


    Jeśli flaga jest zrealizowana w postaci np zwykłej zmiennej bajtowej np.

    volatile uint8_t flaga_Y;

    to po co tu mówić o dostępie z makrami z pliku atomic.h ??? To byłoby chyba istotne wtedy właśnie gdyby ta flaga była licznikiem.

    Zatem uważam, że przykład jest całkiem OK i zakładając że autor chce np wciąż w każdym przebiegu pętli głównej puszczać jakąś ramkę za pomocą Funkcjax() to będzie to działać a czasem gdy wystąpi przerwanie i zostanie ustawiona flaga to zostanie wysłana jedna ramka z pętli głównej po zbadaniu stanu flagi właśnie.

    Jeśli się mylę to podpowiedz mi tmf gdzie ??? (bo rzeczywiście wcześniej nie wiedziałem o atomic.h a sam sobie jakoś radziłem gdy miałem sytuacje z licznikiem 16bitowym zmienianym w przerwaniu a sprawdzanym w pętli głównej .... ale to tylko w takich sytuacjach musiałem kombinować)
  • #23 7800245
    markosik20
    Poziom 33  
    tmf napisał:
    Makrosik, w twoim przykladzie linia:
    if (!licznik) { FUNKCJAx;licznik = 10; }
    musi byc wykonana atomowo. A przynajmniej przypisanie do licznik musi takie byc. Wiec bez atomic sie nie obejdzie.


    No to ja czegoś nie do końca rozumiem. W czasie ładowania adresów I/O (przed ostatecznym zapisem) nawet jeżeli przyjdzie przerwanie to wartość "licznik" jest wyzerowana więc przerwanie wartości licznika nie zmieni (a ma zmieniać jak już licznik będzie ustawiony). Generalnie i tak algorytmy zastosowań tych a nie innych rozwiązań powinno rozpatrywać się nie na ogólnych przykładach.
  • #24 7800866
    AVRowiec
    Poziom 18  
    Przejrzałem atomic.h i z tego co widze w tej bibliotece są tylko dwie funkcje: inkrementacja i dekrementacja licznika. Z tego co wyczytałem to w czasie ink. lub dek. wstrzymywane są inne przerwania (oprócz NMI - nie wiem co to jest).

    void atomic_inc 	( 	atomic_t * 	counter 	 ) 	
      	
    increment atomic counter without interruption. 
    
    locks interrupts except NMI, increments count then restores interrupts


    Teoretycznie takie coś mógłbym napisać sam w funkcji wysyłającej znaki.

    Nie wiem jak zrobić przerwanie wysyłające treść. Skoro i tak do UDRx moge wpisać tylko jeden znak.
    Poszukam jakiś przykładowych kodów ale jeśli macie jakieś swoje rozwiązania to będe wdzięczny za ich pokazanie.
  • #25 7800926
    Dr_DEAD
    Poziom 28  
    AVRowiec - mam dla Ciebie dobrą radę, która właściwie już tu padła ale sposób może nieco zamaskowany. Daj sobie spokój wysyłaniem czegokolwiek w przerwaniu, poszukaj rozwiązania w którym w przerwaniu ustawiasz tylko flagę (że dane przerwanie zaistniało) a w pętli głównej decydujesz jak co i do kogo wysłać.
  • #26 7801100
    tmf
    VIP Zasłużony dla elektroda
    Makrosik: masz racje, moje niedopatrzenie. Rzucilem okiem na przyklad nie patrzac na dokladna realizacje. Niemniej twoj przyklad jest niezyciowy, bo powoduje gubienie przerwan - jesli licznik jest rowny 0 i nastapi kolejne przerwanie to nie wplynie to na licznilk. Takze powinna byc raczej inkrementacja licznika i jesli jest on rozny od 0 to nastepuje wywolanie Funkcja() i licznik jest dekrementowany. I wlasnie w trakcie jego dekrementacji (nawet jesli jest 8 bitowy) nalezy zablokowac przerwania. Ale poniewaz mam wrazenie, ze poziom abstrakcji osiagnal nasze granice percepcji (np. mirek w ogole sie pogubil z przykladami do ktorych sie odnosilem) proponuje dalsze rozwazania robic na konkretnym kodzie (AVRowiec, pokaz co stworzyles).
    AVRowiec: na zle atomic patrzales, nas interesuje atomic.h z AVR-libc:
    http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
    Co do przerwania wysylajacego tresc - musisz zrobic programowy bufor i funkcje, ktore zamiast zapisywac do UDR, zapisuja do bufora. Jesli masz jakies ramki to musisz zadbac, zeby zapis w buforze nie zmienial ich struktury - najprosciej byl atomowy. Procedura obslugi przerwania ma tylko sprawdzac czy w buforze sa dane do nadania - jesli tak to nadaja znak i wracaja. Koniec nadawania wyzwala kolejne przerwanie, w ktorym sprawdzasz czy jest cos do nadania itd. Bufor mozesz zrealizowac jako ringbuffer, kolejki FIFO itd, uwazajac na sytuacje przepelnienia.

    Dodano po 8 [minuty]:

    mirekk36 napisał:

    Jeśli flaga jest zrealizowana w postaci np zwykłej zmiennej bajtowej np.

    volatile uint8_t flaga_Y;

    to po co tu mówić o dostępie z makrami z pliku atomic.h ??? To byłoby chyba istotne wtedy właśnie gdyby ta flaga była licznikiem.


    A jaka roznica, czy uzywamy flagi, czy licznika? Problem najogolniej lezy w tym, ze w AVR (w przeciwienstwie do x86) nie mamy instrukcji jednoczesnie sprawdzajacej stan flagi/licznika i modyfikujacej je. W efekcie przy wszelkich porownaniach z modyfikacja musimy sami zapewnic atomowosc operacji - musimy stworzyc cos na wzor mutexow.Oczywiscie w specyficznych sytuacjach moze byc inaczej, ale najogolniej mowiac jesli mamy zmienna modyfikowana jednoczesnie i w przerwaniu i w petki glownej musimy zadbac o wylaczy do niej dostep. Tu sie czaji wiele zagrozen, ale bezpieczniej bedzie pisac na przykladzie konkretnych programow, bo zaraz sie totalnie pogubimy.
  • #27 7801735
    markosik20
    Poziom 33  
    tmf napisał:
    ... jesli mamy zmienna modyfikowana jednoczesnie i w przerwaniu i w petki glownej musimy zadbac o wylaczy do niej dostep.


    Czyli przerwanie powinno być "powiązane" z wykonywaniem się pozostałych funkcji.
    Jeżeli flaga jest dalej ustawiona znaczy że jeszcze nie wykonała się funkcja która ją zeruje.
  • #28 7803022
    AVRowiec
    Poziom 18  
    tmf -> jeśli mamy bazować na konkretnym przykładzie to musze okroić swój program tak żeby było coś widać. Dajcie mi chwilke..
REKLAMA