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.

atmega8 i dziwny kłopot z pamięcią

szeryf.rm 20 Sie 2015 04:10 1188 26
  • #1 20 Sie 2015 04:10
    szeryf.rm
    Poziom 22  

    Napisałem program, ale zastanawiam się jakie są szanse na jakiś sprzętowy błąd w atmedze8.

    Mam dwa przerwania TIMER i UART. Timer uruchamia się co ~8000 cykli na 1000 cykli. UART uruchamia się maksymalnie co 2000 cykli na zaledwie 100, bo tam w sumie wszystko usunąłem na próbę. UART niczego modyfikuje tylko jedną zmienną, ale to zwykła zmienna globalna (wszystko co obliczane usunąłem).

    TIMER ma sei(), żeby UART uruchamiał się obowiązkowo zawsze, ale za względu na logikę, to uruchomi max 1 (no liczmy 2) razy w czasie przerwania TIMER, czyli nadal mieścimy się w okienku.

    Dodałem pułapki sprawdzające stan SP i Heap na wszelki wypadek, ale to bez znaczenia, bo choć sprawdziłem czy działają i chyba jest z nimi ok, to nie wyłapują jakiegoś nadpisywania.

    Kod: c
    Zaloguj się, aby zobaczyć kod


    Przejrzałem wszystko co tylko się da, pomiędzy dolną pamięcią na zmienne i heap jest jeszcze kilkaset bajtów wolnej przestrzeni do górnej granicy zmiennych lokalnych SP. Z malloca nie korzystam poza w/w kodem.

    W TIMERze mam jedną lokalną tablicę 160 elementów. I jak ona jest w timerze to nadpisuje mi pamięć globalną raz na ileś sekund w czasie odbierania danych przez UART (uart nie modyfikuje nawet tego obszaru pamięci, który ulega uszkodzeniu, TIMER też nie modyfikuje tej pamięci globalnej). W TIMERze jest te 160 elementów utworzone i tam jest tylko kopiowana zawartość pamięci z globalnej takiej samej tablicy (to na przyszłość kodu, żeby UART modyfikował globalną, a TIMER zawsze dostawał tablicę niezmodyfikowaną, co tutaj nie ma znaczenia dla tego przykładu).

    Jak tablicę lokalną 160 elementów wyrzucę przed przerwanie, to wszystko działa elegancko.

    Dodałem jeszcze na wszelki wypadek licznik na początku i na końcu przerwań. Na początku jest ++ na końcu -- i za każdym razem po wejściu do przerwań sprawdzam, czy wcześniej się nie uruchomiło. I wszystko jest ok.

    Na symulatorze, podgląd ASM też bez zarzutów.

    Myślałem, że może problem jest w płytce czy coś, ale nic nie znalazłem, napięcia wzorowe, włączony dodatkowo BOR, żeby jak coś skoczy to mi reset zrobił. Zmieniłem nawet zasilanie z przetwornicy na zwykły stabilizator. Nic nie jest obciążone, procesor się nie grzeje, problem powtarza się w 3szt. urządzeń, które mam. Nie ma żadnych zakłóceń dodatkowych, żadnych obciążeń na pinach, no dosłownie nic. Kondensatory po 100nF na zasilaniu przy samym procesorze. Wszędzie wszystko jest.

    Nawet sprawdziłem dodatkowo czy SP nie schodzi jakoś niżej niż normalnie (0x45F-240 bajtów) i wszystko w najlepszym porządku, a nadpisuje mi i koniec.

    memcpy (działa w TIMERze w sekcji gdzie przerwanie jest wyłączone jeszcze) użyte do kopiowania zmieniłem też na zwykłą pętlę i też nic.

    Brak mi jakiekolwiek wyjaśnienia tej sytuacji. Wygląda jakby gdzieś na ślepo strzelał nie w tym miejscu pamięci co potrzeba raz na jakiś czas kiedy jedno przerwanie uruchomi się w drugim.

    Dla formalności kod przerwań:
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Jak widać przerwania są pozbawione wszystkiego, a ewentualne zmienne są zwykłe globalne i funkcje są jakieś prymitywne do włączania tylko diod.

    Wyrzucenie tablicy przed przerwanie i wszystko działa.

    Co o tym sądzicie?

    Dodano po 12 [minuty]:

    Jeszcze sprawdziłem opcje optymalizacji. Zarówno -Os jak i -O0 dają ten sam rezultat. Nawet -O0 nie powoduje, że TIMER nie uruchamia się drugi raz w czasie jednego przerwania.

    Dodano po 2 [godziny] 3 [minuty]:

    To jest nie możliwe. Próbowałem zrobić ten sam efekt z TIMER1 i TIMER2 zamiast TIMER1 i UART i z samymi TIMERami chodzi poprawnie ten sam kod, mimo, że drugi TIMER startował co 600 cykli i problemu nie było. A z UARTem jest. Sądzicie, że to możliwy problem z samymi procesorami? Jeśli ktoś jest zainteresowany sprawdzeniem dogłębnym tego do czego doszedłem to piszcie, podeślę wtedy jakiś kompletny kod do testowania. Nie wiem czy kogoś prócz mnie to interesuje.

    0 26
  • Arrow Multisolution Day
  • #2 20 Sie 2015 07:48
    2rs232
    Poziom 17  

    Skąd trafiają dane na UART? Może odbierasz kilku bajtowe paczki danych i to powoduje wywołanie kilku przerwań od UARTa. Spróbuj może jeszcze z jakimś niskim bautrate.

    0
  • #3 20 Sie 2015 09:26
    BlueDraco
    Specjalista - Mikrokontrolery

    Masz dwa poważne błędy: alokacja dużej tablicy na stosie w przerwaniu timera i odblokowanie przerwań w przerwaniu timera. Ten drugi błąd służy zapewne do zamaskowania poważniejszego błędu - koncepcyjnego. Przerwanie timera nie powinno przeszkadzać w obsłudzie przerwania UART, a zapewne przeszkadza, bo wykonuje się za długo.

    0
  • #4 20 Sie 2015 10:02
    szeryf.rm
    Poziom 22  

    2rs232 napisał:
    Skąd trafiają dane na UART? Może odbierasz kilku bajtowe paczki danych i to powoduje wywołanie kilku przerwań od UARTa. Spróbuj może jeszcze z jakimś niskim bautrate.


    Zmianiałem baund na 9600 z 38400, ale to nic nie dało. Nawet dałem ekstra przerwy 100ms (tak 100ms) pomiędzy znakami (!!) i jedyne co się zmieniło to problem był rzadziej, chociaż to rzadziej nie było 1/10min tylko dalej kilkanaście sekund. Zresztą wystarczy przeliczyć 8MHz/9600=833cykli. Przy założeniu, że 1 bajt, to bit startu, 8 danych, bit stopu, to minimum przerwanie jest co 8333cykli dla 9600, a dla 38400 to jest aż 2083 cykle. Biorąc pod uwagę ile nopów jest w przerwaniu, to od razu widać, że tu problem być nie może, bo gdzie tam 200 cykli, a co dopiero 2000...

    BlueDraco napisał:
    Masz dwa poważne błędy: alokacja dużej tablicy na stosie w przerwaniu timera i odblokowanie przerwań w przerwaniu timera. Ten drugi błąd służy zapewne do zamaskowania poważniejszego błędu - koncepcyjnego. Przerwanie timera nie powinno przeszkadzać w obsłudzie przerwania UART, a zapewne przeszkadza, bo wykonuje się za długo.


    Nie to nie jest problem. Wielkość samej pamięci nie może być kłopotem, bo alokacja i małej tablicy i dużej wygląda tak samo. To jest tylko kilka instrukcji asemblera. Podejrzałem kod. Tam po uruchomieniu przerwania jest tylko zrzut rejestrów (czyli rzecz normalna i na wyłączonym przerwaniu, bo odbywa się jeszcze na długo przed moim sei()) a po zrzucie następuje odjęcie stałej wartości (akurat tutaj było to 152 bajty) od rejestru SP (R29:R28). Alokacja nie jest więc obciążeniem. Kopiowanie i wszystko to co wewnątrz zajmuje około 1000 cykli, a przerwania startuje co 8000 cykli, więc zapasu jest jeszcze tyle. Zresztą jak napisałem miałem nawet test:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    Rolą testu było sprawdzenie, czy przypadkiem nie dochodzi do sytuacji, kiedy następuje dwukrotne wejście w przerwanie i nic takiego nie następowało.

    Poczyniłem nawet ekstra kod, gdzie nie alokowałem 150 bajtow, ale zaledwie 50!! i efekt bez zmian, dalej to samo po pewnym czasie.

    Jak napisałem zmieniłem też zamiast UART na inny TIMER. Drugi TIMER puściłem z częstotliwością znacznie większą niż UART mógł odbierać i na TIMERZe chodziło prawidłowo, a na UART nie.

    Dodam tylko, że UART sprawdzony, baundrate i odbiór danych jest bez zastrzeżeń, odbiera prawidłowo (sprawdzone na wiele sposobów, zresztą wystarczy wywalić tablicę za przerwanie i wszystko chodzi bez zarzutów, chociaż jak wspomniałem sama alokacja to tylko kilka instrukcji), wszystko się zgadza w odbieranych danych i w częstotliwości wykonywania przerwania (akurat częstotliwość wykonywania przerwania potwierdza też w/w kod testowy, bo skoro problem występuje, a dwukrotnie nie ma wejścia do przerwania, to UART nie może przyblokować nadmiarem danych).

    Poniżej zrzut z tego co tworzy kompilator dla przerwania i tam jest cała alokacja pamięci (przypomnę, że testowane dla Os i dla O0, ten kod poniżej jest dla Os, ale dla O0 też podwójnych wejść do przerwania nie było, a problem był).
    3f4: i następna to pobranie aktualnego stanu rejestru SP.
    następne dwie to cała alokacja i ostatnie dwie to zapis do SP. Jak widać, cudu tutaj nie znajdziemy. Też myślałem, że może ta alokacja jakoś długo trwa, ale spodziewałem się właśnie tylko samej rezerwacji przez SP.
    Kod: avrasm
    Zaloguj się, aby zobaczyć kod


    Jeszcze jakieś pomysły?

    0
  • #5 20 Sie 2015 10:15
    BlueDraco
    Specjalista - Mikrokontrolery

    Owszem, to właśnie jest problem. Jeśli w przerwaniu potrzbujesz alokować tablicę, to zapewne w celu jej użycia - czyli przerwanie timera operuje na 160 wartościach danych - to jest właśnie poważny błąd koncepcyjny, który skutkuje protezą (odblokowanie przerwania UART w przerwaniu timera), a ta proteza - następnymi błędami. Usunąć trzeba przyczynę
    kolejnych błędów, czyli błąd w algorytmie wymagający analizy 160 wartości danych w jednym przerwaniu timera.

    0
  • Arrow Multisolution Day
  • #6 20 Sie 2015 10:23
    Tomasz Gumny
    Poziom 27  

    Może to:
    "It is important to notice that accessing 16-bit registers are atomic operations. If an interrupt
    occurs between the two instructions accessing the 16-bit register, and the interrupt code
    updates the temporary register by accessing the same or any other of the 16-bit Timer Registers,
    then the result of the access outside the interrupt will be corrupted. Therefore, when both
    the main code and the interrupt code update the temporary register, the main code must disable
    the interrupts during the 16-bit access."

    0
  • #7 20 Sie 2015 10:36
    szeryf.rm
    Poziom 22  

    Ale gdzie jest błąd?
    Jeśli uruchomi się TIMER, trwa analiza danych, uruchomi się UART, UART się kończy, wraca do analizy danych, kończy się TIMER, wraca do programu głównego. Po to jest przecież stos.

    Zwróć też uwagę że w kodzie TIMERa analiza danych nie trwa na oryginalnych danych (notabene, które nie są modyfikowane, tylko raz zainicjowane, modyfikacje ich usunąłem w ogóle) tylko na ich kopii, a kopiowanie odbywa się na wyłączonych przerwaniach.

    Jak wspomniałem ten problem wystąpił nawet na 50 bajtach danych i jak również wspomniałem nie występuje sytuacja:
    Dwukrotne uruchomienie UART w UART, bo UART uruchamia się na wył przerwaniu jak zawsze zresztą, poza tym ma ze 200 cykli może max, a uruchamia się raz na 2000 cykli, więc pomiędzy jest jeszcze szczelina

    Code:
    TIMER
    
       ALOKACJA
       COSTAM
          UART
          COSTAM
             UART
             COSTAM
       COSTAM
    TIMER


    Dwukrotne uruchomienie TIMERA też nie występuje, bo to sprawdzone w w/w sposób.
    Code:
    TIMER
    
       ALOKACJA
       COSTAM
          UART
          COSTAM
       COSTAM
         UART
          COSTAM
       COSTAM
         UART
          COSTAM
       COSTAM
         UART
          COSTAM
       COSTAM
         UART
          COSTAM
          
          TIMER
          ALOKACJA
          COSTAM
            UART
             COSTAM
    TIMER


    Żaden z powyższych nie występuje.
    Występuje jedynie:
    Code:

    TIMER
       ALOKACJA
       COSTAM
          UART
          COSTAM
       COSTAM
          UART
          COSTAM
       COSTAM
          UART
          COSTAM
       COSTAM
          UART
          COSTAM
    TIMER

    Ale tutaj nie ma żadnego błędu, bo przecież inne przerwanie ma prawo się uruchomić w trwającym już przerwaniu. Zresztą do niedawna było nawet rozdzielone ISR na SIGNAL i INTERRUPT, różniące się właśnie tym, że jedno zabraniało drugie zezwalało na start przerwań.

    Dodano po 7 [minuty]:

    Tomasz Gumny napisał:
    Może to:
    "It is important to notice that accessing 16-bit registers are atomic operations. If an interrupt
    occurs between the two instructions accessing the 16-bit register, and the interrupt code
    updates the temporary register by accessing the same or any other of the 16-bit Timer Registers,
    then the result of the access outside the interrupt will be corrupted. Therefore, when both
    the main code and the interrupt code update the temporary register, the main code must disable
    the interrupts during the 16-bit access."


    Tak, ten warunek jest spełniony.
    Po starcie ISR przerwania są domyślnie wyłączone. Dopiero po alokacji się włączają. Zwróć uwagę, że powyższy kod asm, pokazuje, że po starcie przerwania nie ma sei() przed alokacją pamięci w SP. A więc w trakcie operacji na 16 bitowych rejestrach nigdzie nie występuje możliwość wystąpienia przerwania.

    Dla formalności nawet wyrzuciłem tę funkcję z przerwania, żeby nie było i dałem tylko jeden if(), więc rejestr SP nie jest nawet tknięty. Poza tym układ się nie resetuje i to jest ważna informacja. On normalnie biegnie, nie ma żadnego resetu. Gdyby przecież losowo nadpisał informacje o powrocie, to byłby natychmiastowy reset, a nie jest.

    0
  • #8 20 Sie 2015 10:39
    BlueDraco
    Specjalista - Mikrokontrolery

    Ja twierdzę, że jest cała masa błędów, Ty twierdzisz, że nie ma błędów.
    Kod nie działa. Kto ma rację?

    Masz błędną koncepcję algorytmu - źle zaplanowałeś to, co dzieje się w przerwaniu timera. Z tego powodu wpakowałeś w program kilka błędów, które skutkują tym, co obserwujesz.

    Wymyśl algorytm tak, by przewanie timera zajmowało się jedną daną, a nie kiliudziesięcioma w pętli. Wtedy nie będziesz musiał oblokowywać przerwań w przerwaniu timera, co ZAWSZE jest źródłem problemów (czasem tylko uda się te problemy wyeliminować, i wtedy taki pokręcony kod o dziwo działa poprawnie).

    Nie alokuj tablicy na stosie w procedurze przerwania ani nie używaj malloc w komputerze, który ma 1 KiB pamięci RAM, bo sensu nie ma to żadnego - od używania malloc pamięci ubywa, a nie przybywa.

    0
  • #9 20 Sie 2015 10:55
    Tomasz Gumny
    Poziom 27  

    szeryf.rm napisał:
    Po starcie ISR przerwania są domyślnie wyłączone. Dopiero po alokacji się włączają. Zwróć uwagę, że powyższy kod asm, pokazuje, że po starcie przerwania nie ma sei() przed alokacją pamięci w SP. A więc w trakcie operacji na 16 bitowych rejestrach nigdzie nie występuje możliwość wystąpienia przerwania.
    Z rejestru "temporary" korzysta wiele rejestrów 16-bitowych i nie da się go odłożyć na stos.

    0
  • #10 20 Sie 2015 11:10
    szeryf.rm
    Poziom 22  

    Kolego BlueDraco, rozumiem, że jesteś specjalistą od mikrokontrolerów, ale wskaż mi gdzie widziałeś inny watek, w którym ktoś potrafi coś przeliczyć na cykle, kto przeanalizował tyle przypadków. Traktowanie mnie jak byle kogoś (czyt. jak wszystkich) raczej niszczy Twoją reputację.

    Znalazłem przyczynę wszystkich problemów.
    Okazało się, że to NIE moja wina, nie błędna koncepcja, a kompilator dopuszcza do sytuacji, w której następuje naruszenie pamięci. Zdecydowanie, jeśli użytkownik znajduje swój błąd na etapie kompilatora i kodu ASM to już nie jest jego wina.

    A teraz do rzeczy.
    Oto co robi kompilator w byle jakiej funkcji uruchamianej normalnie:

    Kod: avrasm
    Zaloguj się, aby zobaczyć kod


    Zwróć uwagę, na początek i koniec:
    Kod: avrasm
    Zaloguj się, aby zobaczyć kod


    Zobacz w jaki sposób następuje zmiana SP (rej 0x3e i 0x3d).
    Najpierw jest CLI, potem jest SPH, potem jest powrót do SREG (czyli defacto najczęście SEI), potem jest SPL. SPL jest już po uruchomieniu przerwań, ale to nie problem, bo reakcja na przerwanie jest dopiero w cyklu następnym, więc bezpieczeństwo SP jest zachowane.

    Sytuacja w przerwaniu występuje bardziej swawolnie:
    Kod: avrasm
    Zaloguj się, aby zobaczyć kod


    Zwróć uwagę, że o ile na wejściu jest na pewno wyłączone przerwanie o tyle na wyjściu być nie musi, bo nikt nie zabrania włączyć przerwań. A pomimo tego, kompilator nie chroni SP tak jak w przyp. głupiej funkcji, tylko zwyczajnie zachowuje się jakby dla niego przerwania były na pewno wyłączone.

    Dokopałem się jeszcze do
    Code:
    ISR(TIMER1_COMPA_vect, ISR_NOBLOCK)

    To już w ogóle potwierdza moje przypuszczenia. Ewidentny błąd kompilatora. Kompletny brak ochrony rejestru SP. Wygenerował mi coś takiego
    Kod: avrasm
    Zaloguj się, aby zobaczyć kod


    Pełna frywolność. Przerwanie uruchomione, a on sobie modyfikuje na całego rejestr SP.


    W moim kodzie rozwiązałem problem dodając na końcu przerwania CLI i to pomogło, chociaż jest do bez wątpienia bug.

    0
  • #11 20 Sie 2015 11:21
    BlueDraco
    Specjalista - Mikrokontrolery

    Chyba jednak jest w porządku. Jeśli na początku funkcji najpierw jest zapisywany SPH, a potem SPL, a na końcu funkcji odwrotnie I MASZ 256 B WOLNYCH na stosie - nie grozi to żadnym problemem. Bardzo dokładnie tego nie analizowałem, ale na pierwszy rzut oka jest ok.

    0
  • #12 20 Sie 2015 11:29
    szeryf.rm
    Poziom 22  

    Ale gdzie jest wg Ciebie w porządku?
    Jeśli zapis do SP odbywa się w dwóch instrukcjach i pomiędzy nimi wystąpi przerwania, a przerwania są odblokowane, to SP ma zmodyfikowany jeden rejestr (nawet niech to będzie tymczasowy, bo to bez różnicy), przerwanie zmodyfikuje SPH i SPL, a potem jest powrót i modyfikowane jest tylko SPL, to wynikiem musi być błąd, bo przerwanie nie przywróciło rejestru tymczasowego.

    Dlatego w trakcie wywołania funkcji na czas SPH i SPL jest wyłączone przerwanie i to jest ok. Ale w trakcie wykonywania ISR nie następuje wyłączenie przerwania (nawet przy orygnalnym NO_BLOCK) i przerwanie bez problemu może wystartować pomiędzy tymi dwoma OUTami.

    Przeanalizuj to dokładnie, bo to jest błąd kompilatora.

    0
  • #13 20 Sie 2015 11:32
    BlueDraco
    Specjalista - Mikrokontrolery

    Przerwanie nie modyfikuje SP (z punktu widzenia przerwanego kodu). Wartość SP po powrocie z przerwania jest taka sama, jak przed wywołaniem.

    0
  • #14 20 Sie 2015 12:09
    szeryf.rm
    Poziom 22  

    BlueDraco napisał:
    Przerwanie nie modyfikuje SP (z punktu widzenia przerwanego kodu). Wartość SP po powrocie z przerwania jest taka sama, jak przed wywołaniem.


    I tu właśnie problem, bo też tak myślałem, dlatego pisałem wcześniej że błędu w logice nie ma. Mój kod nie jest błędem. Dodałem brakujące CLI i kod od godziny chodzi bez zarzutów.

    Spójrz na to:
    Kod: avrasm
    Zaloguj się, aby zobaczyć kod


    Teraz wersja z opcją NO_BLOCK, czyli brak mojej jawnej ingerencji w sterowanie przerwaniami, zostawiamy wszystko kompilatorowi
    Kod: avrasm
    Zaloguj się, aby zobaczyć kod


    Przeanalizuj dwa powyższe kody. Pokazują one, że takie podejście kompilatora jest błędne, a potwierdza to schemat modyfikacji SP w przypadku funkcji, gdzie odbywa się to wg schematu:
    Code:

    zapamiętaj SREG (przerwania włączone)
    wyłącz przerwanie
    modyfikuje SPH
    przywróć SREG (włącz przerwanie), instrukcja out
    modyfikuj SPL

    Modyfikacjia SPH i SPL odbywa się na wyłączonych przerwaniach, bo instrukcja OUT ma jeden cykl zwłoki, więc wszystko odbywa się w sposób hermetyczny. Tej zasady nie ma jednak dla kompilacji NO_BLOCK i przerwania. I to jest błąd kompilatora.

    Dodano po 15 [minuty]:

    PS. wrzuciłem to na symulatorze. Nawet tam jak jest NO BLOCK i wystartuje przerwanie pomiędzy dwoma outami zapisu do SP to wszystko się wywala.

    Dodano po 4 [minuty]:

    A większa ilość pamięci to akurat czysty przypadek. Głownie chodzi o to, że jak SPH przed alokacją i po alokacji jest taka sama to będzie ok, ale jak się zmieni to nie będzie ok, a SPH zależy oczywiście od również innych zmiennych, nie tylko przerwania. Tak więc jeśli ktoś chce, proszę o potwierdzenie.

    Proszę wygenerować kod assemblera i wkleić tutaj dla takiego kodu C:
    Code:


    #define XXX 150
    volatile uint8_t wyjsciepwm[XXX];
    ISR(TIMER1_COMPA_vect, ISR_NOBLOCK)   //1000 razy na sekundę
    {
       uint8_t _wyjsciepwm[XXX];


       if (czas_prog) czas_prog--;
       sei();
       wdt_reset();
       czas++;

       cli();
       memcpy(_wyjsciepwm, (void*)wyjsciepwm, sizeof(wyjsciepwm));
       sei();


       uint16_t j = 0;
       while(j < sizeof(_wyjsciepwm))
       {
          if (_wyjsciepwm[j] != 0xFF)
          {
             break;
          }
          j++;
       }

       if(j < sizeof(_wyjsciepwm))
       {
          PORTD &= ~_BV(1);
       }
       else
       {
          PORTD |= _BV(1);
       }
    }

    1
  • #15 20 Sie 2015 12:41
    BlueDraco
    Specjalista - Mikrokontrolery

    Już napisałem: przy 256 bajtach luzu na stosie problemu nie ma. Za to algorytm wygląda fatalnie i implementacja też. To przerwanie timera wykonuje się parę tysięcy cykli - zupełnie niepotrzebnie.

    Czym i w jakim celu zapełnisz 160-elementową tablicę w jednym przerwaniu timera? Po co tę tablicę kopiować do innej zamast użycia dwóch buforów? Błąd na błędzie. Coś tu śmierdzi...

    0
  • #16 20 Sie 2015 12:57
    Tomasz Gumny
    Poziom 27  

    Dla porównania obsługa przerwania w ICCAVR:

    Kod: avrasm
    Zaloguj się, aby zobaczyć kod

    0
  • #17 20 Sie 2015 13:08
    szeryf.rm
    Poziom 22  

    Kolego BlueDraco, skompiluj to, pokaż co wyjdzie w ASM dla przerwania.

    Nie analizuj algorytmu, bo nie on jest problemem. Programowaniem (w tym mikrokontrololerów) zajmuje się już tak długo, że łohoho, skoro wziąłem taką tablicę lokalnie to uznałem, że tam może być (akurat tutaj nie musi, ale może), bo zostaje mi jeszcze sporo pamięci.

    Nie czepiaj się też algorytmu, bo to nie jest algorytm finalny, a obskrobany do minimum, żeby wyeliminować wszystkie problemy. Nie sądzisz chyba, że tak jak wszyscy dam Ci tutaj 3000 linii bezsensownego kodu niezwiązanego z problemem i przekopuj się o co autorowi chodzi. Zawęziłem kod tyle ile się dało, żeby pokazać tylko to co najważniejsze w problemie.

    Tablica jest i będzie, bo tak chcę. Mam prawo chcieć, co nie oznacza, że ma nie działać, nieprawdaż? Cały ram zajęty był na poziomie 450 bajtów, więc zapas był znacznie większy niż 256 bajtów.

    Problem tkwi w tym, że jeśli po wejściu do przerwania np. 0x405, a w przerwaniu zarezerwujesz zaledwie 10 bajtów, to spowoduje, że wskaźnik przesunie się na 0x3FB (pomijam tutaj to co będzie jeszcze na zrzut powrotu). Wówczas kompilatorowe dwa OUTy najpierw modyfikuje SPH, potem SPL. Niestety widzę, że chyba nie ma bufora dla SPH (co i tak nie zmieniałoby problemu w rozwiązanie), więc jak najpierw wpisze SPH to będzie SP=0x305. Potem jak wpisze SPL to będzie SP=0x3FB. Jeśli natomiast pomiędzy wystąpi przerwanie, to SP=0x305 i z taką wartością wystartuje nowe przerwanie. I nieważne czy nowe przerwanie tworzy jakieś tablice, to bez znaczenia, bo w nowym przerwaniu jest głupi PUSH, które wskaźnik przesunie i zapisze dane pod błędne miejsce. Po skończony przerwaniu oczywiście wróci się do poprzedniego przerwania z SP=0x305, nastąpi aktualizacja jeszcze zaległego SPL i będzie SP=0x3FB, jednak... w drugim przerwaniu nastąpił PUSH do złego obszaru pamięci i nadpisuje niedozwolony obszar.

    Taka jest zasada problemu. Przyjrzyj się jej i nie krytykuj mnie. Zajmuje się tyle lat programowaniem, że nawet nie pamiętam. Swego czasu pisałem na studiach nawet program dla rozproszonej struktury danych. Dostałem algorytm, który był dawany najlepszym studentom na pracę zaliczeniową semestru. Przez 5 lat, kiedy prof. dawał to zadanie najlepszym tylko jedno rozwiązanie dostał ode mnie, które działało :P. Dlatego ja znalazłem rozwiązanie i jestem jego pewny. Jeśli jesteś ciekawy czy mam rację, to weź proszę pod uwagę to co piszę i to przeanalizuj a nie ignoruj tak jakby to był wątek założony przez kogoś kto wkleja 3000 linii kodu i oczekuje, że ktoś do tego zajrzy :).

    Dodano po 5 [minuty]:

    Tomasz Gumny napisał:
    Dla porównania obsługa przerwania w ICCAVR:
    [/syntax]


    Fajnie że się zainteresowałeś. Chętnie zobaczę dla ICC, tylko akurat tutaj nie ma głównego problemu. Musisz utworzyć tablicę jakąś w przerwaniu, żeby była modyfikacja rejestru SP. Tablice globalne nie są problemem, bo nie przesuwają SP. SP modyfikowane i przesuwane jest w sumie jak korzystasz tylko z wielu zmiennych. Inaczej kompilator korzysta tylko z rejestrów bez jawnej modyfikacji SP. Dlatego utwórz byle jaką tablicę lokalną, coś w niej modyfikuj i wyrzucaj na PORT (żeby inteligentny kompilator nie pominął tablicy, z którą nic nie robisz :) )

    Fajnie też, jak dasz kod zarówno dla standardowego ISR, jak również dodasz w ISR opcję NO_BLOCK bo to w moim przypadku od razu pokazało skrajnie nieodpowiedzialne obchodzenie się z rejestrem SP.

    0
  • #18 20 Sie 2015 13:12
    szczywronek
    Poziom 27  

    szeryf.rm napisał:
    Proszę wygenerować kod assemblera i wkleić tutaj dla takiego kodu C:
    A proszę bardzo :)
    Kod: avrasm
    Zaloguj się, aby zobaczyć kod
    Ja tam widzę "cli" na początku epilogu w 0xCC. Potem w 0xD0 przerwania zostają włączone, ale jak sam pisałeś - reakcja na przerwanie nie będzie natychmiast :) GCC 4.8.2, kompilacja -Os.

    0
  • #19 20 Sie 2015 13:15
    BlueDraco
    Specjalista - Mikrokontrolery

    AVR nie programuję, jeśli jednak nie ma buforowania SP, to rzeczywiście, jak piszesz przerwanie użyje stosu pod umownym adresem 0x305, tylko że w normalnych warunkach nic złego z tego nie wyniknie, bo masz ciut wolnego miejsca na stosie, a przyzwoicie napisana procedura obsługi przerwania nie będzie przecież alokowała dużych danych na stosie, nieprawdaż? Więc zamiast 180 bajtów stoso skonsumujesz 230 - niewielka różnica, a każdy dobry i świadomy programista zostawia na stosie min. kilkadziesiąt bajtów luzu.

    W czym więc problem? ;)

    Piszezsz w kodzie, że przerwanie wywołuje się 1000 razy na sekundę, a mocno okrojony kod obsługi przerwania wymaga paru tysięcy instrukcji. Ilu będzie wymagał nieokrojony? Jak to się ma do wydajności procesora?
    Kopiowanie danych, zwłaszcza na 8-bitowcach, jest drogie i nie należy go nadużywać w przerwaniach.

    0
  • #20 20 Sie 2015 13:24
    szeryf.rm
    Poziom 22  

    No i ślicznie, u Ciebie jest właśnie CLI tak jak być powinno. Masz zarówno dla wejścia w przerwanie jak i wyjścia. A w WinAVR brakuje tego. Jeszcze dla formalności skompiluje potem na linuksie nową wersję avr-gcc i to sprawdzę, ale póki co...

    A sprawdź jeszcze jak możesz, bez NO_BLOCK, tylko wstaw na początku przerwania ręcznie sei();
    i ciekawe czy kompilator przy kończeniu przerwania sam się zorientuje i zrobi cli.

    Dodano po 6 [minuty]:

    BlueDraco napisał:
    AVR nie programuję, jeśli jednak nie ma buforowania SP, to rzeczywiście, jak piszesz przerwanie użyje stosu pod umownym adresem 0x305, tylko że w normalnych warunkach nic złego z tego nie wyniknie, bo masz ciut wolnego miejsca na stosie, a przyzwoicie napisana procedura obsługi przerwania nie będzie przecież alokowała dużych danych na stosie, nieprawdaż? Więc zamiast 180 bajtów stoso skonsumujesz 230 - niewielka różnica, a każdy dobry i świadomy programista zostawia na stosie min. kilkadziesiąt bajtów luzu.

    W czym więc problem? ;)


    Tak tylko, że programista nie może dostać kodu w ciemno. Wystarczy, że zacznę używać floata i wszystko walnie z miejsca, bo float wymaga rezerwacji sporej ilości pamięci. Potem jeszcze cokolwiek i leżymy. Poza tym jak się ma 1024 bajty dostępne, to ograniczenie się o 256 jest jakimś nieporozumieniem, bo to 25% tylko na obsługę przyszłego problemu. Akurat w tym przerwaniu jest robiona kopia tablicy głównej, bo tablicę główną modyfikuje mi UART. I celem tej operacji jest zagwarantowanie, że w trakcie pracy z tablicą, UART nic mi nie nadpisze, dlatego robiłem na szybko kopię, bo czemu nie, skoro miałem tyle pamięci, to sobie pozwoliłem, nie jest ani błędem ani grzechem, nieprawdaż?

    BlueDraco napisał:
    Piszezsz w kodzie, że przerwanie wywołuje się 1000 razy na sekundę, a mocno okrojony kod obsługi przerwania wymaga paru tysięcy instrukcji. Ilu będzie wymagał nieokrojony? Jak to się ma do wydajności procesora?
    Kopiowanie danych, zwłaszcza na 8-bitowcach, jest drogie i nie należy go nadużywać w przerwaniach.

    Tak, ale to akurat wiem i dokładnie sprawdziłem. Całość w tej wersji w pesymistycznym przyp. nie przekraczała 1/5 czasu pomiędzy przerwaniami. Wiesz, głupi nie jestem, wiem czym grozi start tego samego przerwania w tym samym przerwaniu :).

    0
  • #22 20 Sie 2015 13:31
    szeryf.rm
    Poziom 22  

    no i tak powinno być. Pozostaje więc jeszcze sprawdzić nową wersję avr-gcc, czy tam ten problem jest nadal. Swoją drogą jeszcze sprawdzę czy na innych prockach jest ten sam problem, czy to przypadłość atmegi8

    0
  • #23 20 Sie 2015 14:28
    BlueDraco
    Specjalista - Mikrokontrolery

    Czyli wersja pełna obsługi przerwania jest krótsza i szybsza, niż okrojona? OK. ;)

    0
  • #24 20 Sie 2015 14:59
    szeryf.rm
    Poziom 22  

    BlueDraco dokładnie tak. Okrojona została do stanu problemu, co nie wyklucza, że podczas prób coś bardziej działało w takim przypadku niż w oryginalnym. Jak zapewne wiesz, przy błędach nadpisywania, element losowości odgrywa ważną rolę i jeden nop może mieć większe znaczenie niż coś innego.

    Nie zmienia to jednak faktu, że ani wersja okrojona ani nawet oryginalna nie dochodzą nawet do połowy dostępnych cykli zegara, a jest ich 8000, więc nawet na ośmiobitowcu przeanalizowanie 150 bajtów, kopiowanie 150 i parę innych rzeczy, nie da 8000 cykli. Bądźmy rozsądni :). Kopiowanie to ładowanie rejestru, zwiększanie itd. więc max 8 cykli na bajt. Analiza w postaci kilku warunków i to nie na wszystkich 150 bajtach (bo to struktura) to np. 15 warunków, które jak się spełnią to uruchamiają funkcję po 30 cykli, w efekcie kolejne max 1000 cykli. Po prostu szacowanie cykli wychodzi mi po tylu latach dość dobrze :P, więc jak wiem że dopuszczalne jest 8000 a mi z zawyżonych szacunków wychodzi 3000 to nie tracę czasu na zabawę w ekstra optymalizację, bo nikt mi za to ekstra nie płaci. A jak już trzeba zoptymalizować co do bajta i cyklu to wtedy to robię - a zdarzyło się tak. Krótko mówiąc nie rozstrzygam ile okrojona dokładnie zabiera i ile oryginalna, bo obie wyrabiają się na 100% w dopuszczalnych 8000 cykli zegar, co zresztą potwierdza test __i++, który podałem wcześniej i co do którego chyba nie masz wątpliwości? A to czy będzie to 1000 cykli, 2000 cykli, czy nawet 4000 cykli to całkiem bez znaczenia, bo i tak się wyrabia, więc nie stanowi problemu.

    0
  • #25 21 Sie 2015 03:46
    szeryf.rm
    Poziom 22  

    Ok, problem w takim razie wygląda na rozwiązany.
    Przekompilowałem AVR-GCC najnowszą wersję wraz z binutils itd. i problem w najnowszej wersji jest rozwiązany. Pięknie tam gdzie trzeba jest cli(). Jak nie ma NOBLOCK, to cli jest prawidłowo i oszczędnie tylko przy wyjściu z przerwania. Jak jest NOBLOCK to cli jest wszędzie.

    W związku z powyższym, dalsze korzystanie z WinAVR staje się niebezpieczne. Ponieważ nie widzę, żeby była nowsza wersja po 2010 roku, należałoby w takim razie odradzić korzystanie z tego oprogramowania w tej wersji.

    Z jakiego kompilatora korzysta najnowsze AVR STUDIO 6 dla procesorów 8 bitowych?

    0
  • #26 21 Sie 2015 07:03
    dondu
    Moderator Mikrokontrolery Projektowanie

    szeryf.rm napisał:
    W związku z powyższym, dalsze korzystanie z WinAVR staje się niebezpieczne. Ponieważ nie widzę, żeby była nowsza wersja po 2010 roku, należałoby w takim razie odradzić korzystanie z tego oprogramowania w tej wersji.

    Oj, to nieźle jesteś z tyłu - 5 lat co najmniej :)
    http://mikrokontrolery.blogspot.com/2011/04/kompilator-i-srodowisko-programistyczne.html
    http://mikrokontrolery.blogspot.com/2011/04/atmel-studio-spis-tresci.html

    szeryf.rm napisał:
    Z jakiego kompilatora korzysta najnowsze AVR STUDIO 6 dla procesorów 8 bitowych?

    GCC, a wersja zależy od tego, jaki toolchain masz zainstalowany.
    Aktualnie jest to Toolchain 3.4.5 z AVR/GNU C Compiler 4.8.1
    http://www.atmel.com/tools/atmelavrtoolchainforwindows.aspx

    0
  • #27 21 Sie 2015 10:34
    szeryf.rm
    Poziom 22  

    Wiesz 5 lat, nie 5 lat. Atmega8 to proc. który wg noty katalogowej pierwsze wydania miał w latach 2001-2002, więc posiadanie kompilatora z 8 letnim stażem i uaktualnieniami powinno być zdecydowanie bezpieczne, zwłaszcza, że atmega, czy attiny to nie kombajny posiadające takie perełki jak wsparcie dla zewnętrznych pamięci SDRAM, wsparcie dla wielowątkowości, nawet priorytetów przerwań nie ma, ani wsparcia sprzętowego liczb zmiennoprzecinkowych, brak ochrony pamięci itd. Dlatego nie sądziłem, że błąd może leżeć w takich miejscach jak absolutnie niedopuszczalne modyfikowanie rej. 16 bitowych bez wyłączenia przerwań. Takich rzeczy to ja nawet nie robię, a jak robi to kompilator to już jest zdecydowane nieporozumienie. Już nie wspomnę, że ten sam błąd jest na -O0 i mówimy to o C a nie C++ i o stałych elementach takich jak rezerwacja pamięci... Swoją drogą to duży błąd, że w funkcjach bez problemu przewidziano cli, a w ISR już nie.

    Pierwszy WinAVR widzę jest z 2002 roku, więc wypadałoby, żeby po 8 latach wpadki zdarzały się jakieś mało znaczące albo brak super ekstra optymalizacji, ale nie tak znaczące wpadki.

    Z WinAVR do dzisiaj korzysta wiele osób. W poszukiwaniu różnych rzeczy wielokrotnie natrafiam na strony PL i obcojęzyczne z wątkami, w których przewija się nazwa WinAVR w kontekście instalacji (chociażby słynne dodanie do PATH) a wątki są nowiutkie. Pozostaje się tylko zastanowić ile błędnych kodów powstaje, bo właściwie każdy, który używa przerwania z opcją NOBLOCK i tworzy chociaż jedną lokalną tablicę w ISR ma błąd w kodzie wynikowym.

    Nie mniej jednak mam już gcc 5.2, więc jestem uaktualniony.

    0