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

Brak ustawiania FLAGA_CZAS w przerwaniu timera - kod w pętli głównej nie wykonuje się

nowakus 07 Cze 2022 23:46 702 11
REKLAMA
  • #1 20052085
    nowakus
    Poziom 19  
    Posty: 464
    Pomógł: 1
    Ocena: 100
    Dzień dobry,

    Trenuję programowanie i natrafiłem na mały lecz bardzo irytujący problem. Po krótce ma działać tak: ustawiony jest Timer0 na generowanie przerwania co 1ms (kwarc 8Mhz, preskaler 64, TCNT0=131), z tym jest wszystko okej. W przerwaniu dodany jest kod który zlicza 20 tyknięć timera i ustawia flagę: FLAGA_CZAS, która ma odblokować kod znajdujący się w pętli głównej i ma się wykonywać co 20ms (część kodu potrzebna do obsługi klawiatury). Testowo do debagowania w przerwaniu dodana instrukcja: PORTB ^= (1<<PB5); której docelowo nie będzie.

    W wyniku działania programu na oscyloskopie widzę przebieg prostokątny na PB5 ze zboczami co 20ms (działanie poprawne) natomiast nie wykonuje się instrukcja: PORTB ^= (1<<PB4); zawarta w pętli gółnej programu. Wg mnie na obu pinach powinny być zbliżone do siebie przebiegi prostokątne.

    Z tego co ustaliłem w przerwaniu od Timer0 nie jest ustawiana FLAGA_CZAS, ponieważ gdy strSprzet.Flagi |= FLAGA_CZAS; przeniosę na górę funkcji głównej, na PB4 mam przebieg prostokątny o przypadkowej częstotliwości około 500kHz.

    Poniżej kod który napisałem:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Co robię nie tak jak trzeba ?? Siedzę nad tym już dłuższy czas i nie mam pomysłu co robić.
  • REKLAMA
  • #2 20052099
    nuclear
    Poziom 16  
    Posty: 194
    Pomógł: 18
    Ocena: 144
    Najprawdopodobniej kompilator "nie widzi" zależności między użyciem zmiennej globalnej w przerwaniu i w pętli głównej i sobie to nazbyt optymalizuje. Możesz to prawdopodobnie wychwycić obserwując kod wynikowy w asemblerze. Rozwiązaniem może być zmiana stopnia optymalizacji kompilacji lub dodanie parametru "volatile" dla zmiennej globalnej.
  • REKLAMA
  • #3 20052133
    hajy
    Poziom 22  
    Posty: 300
    Pomógł: 54
    Ocena: 59
    Witam

    Jak wspomniał przedmówca potrzebne jest volatile.
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Drugi babol jest w pętli głównej powinno być tak.
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Modyfikacja twojej flagi wygląda tak że pobierana jest z pamięci cały bajt modyfikowany i zapisywany, jak w tym momencie wystąpi przerwanie i będzie modyfikowana w przerwaniu flaga to zostanie ona nadpisana zmodyfikowaną starą wartością. Ten problem teraz raczej nie wystąpi ale jak program utyje to może powodować dziwne zachowanie.
    Zamiast kombinować z flagami i sprawdzać klawiaturę w głównej pętli zrób to w przerwaniu jak to zazwyczaj się robi.
  • #4 20052296
    nowakus
    Poziom 19  
    Posty: 464
    Pomógł: 1
    Ocena: 100
    W życiu bym nie wpadł na to że kompilator zoptymalizuje program, tak że nie będzie działać. Raczej szukałem problemu w moim kodzie i logice działania. W planie procesor ma obsługiwać wyświetlacz, przerwania zewnętrzne (pomiar prędkości maszyny) oraz sterowanie PWM i przekaźnikami.

    Wracając do tematu powinienem zadeklarować całą strukturę jako volatile czy tylko zmienną Flagi w tej strukturze, bo w strukturze maja być jeszcze inne zmienne dotyczące sprzętu używane w przerwaniach i pętli głównej (między innymi do obsługi wspomnianych klawiszy)?

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


    Czy tak jak pisał Kolega:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Co do umieszczania obsługi przycisków w przerwaniu. Ktoś mi kiedyś powiedział, żeby duże partie kodu umieszczać w pętli głównej, i odblokowywać tylko flagami ustawianymi w przerwaniu timera, żeby przerwanie tego timera było maksymalnie krótkie i nie zajmowało niepotrzebnie czasu. Tak się nie powinno robić?
  • REKLAMA
  • #5 20052314
    jvoytech
    Poziom 22  
    Posty: 362
    Pomógł: 61
    Ocena: 136
    Jeżeli to jest np. atmega328 to zamiast definiować strukturę z jednym polem Flagi użyj rejestr GPIOR0.
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Ten rejestr znajduje się w pierwszych 32 adresach pamięci i każdy bit z osobna jest adresowalny. Gdy w kodzie C ustawisz albo wyzerujesz 1 bit to kompilator powinien wygenerować jedną instrukcję w asemblerze , przez co taka operacja jest atomowa i nie trzeba wyłączać przerwań "cli()" na czas modyfikacji bitu. Taki mały przykład:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Poniżej to co stworzył kompilator:
    Kod: Text
    Zaloguj się, aby zobaczyć kod

    Jak widać ustawianie 1 bitu tworzy jedną atomową operację, ale gdy na raz ustawia się więcej bitów to do pracy zaprzęga się rejestry i ALU, więc trzeba uważać z przerwaniami. Toglowanie bitu generuje 3 instrukcje, więc trzeba deaktywować przerwania.

    Toglowanie bitu jako instrukcja atomowa dotyczy jedynie rejestrów portów B,C i D, bo tak zostały sprytnie skonstruowane te porty I/O. Wystarczy do rejestru pinu zapisać jedynkę, np:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
  • #6 20053287
    nowakus
    Poziom 19  
    Posty: 464
    Pomógł: 1
    Ocena: 100
    Znaczy toglowanie nie jest docelową instrukcją. Zostało dodane w celu debagowania na oscyloskopie. Procesor docelowy ATMega32 bo takie akurat mam w zapasie.

    Przeanalizuję ten przykład kodu, popróbuję testować i zobaczę co wyjdzie.

    Po dodaniu volatile wszystko działa poprawnie. Mam dokładnie 2 takie same przebiegi prostokątne na PB4 i PB5, jeden z przerwania drugi z funkcji głównej. Jak Waszym zdaniem powinno się robić? Kod danej funkcji w przerwaniu, czy w przerwaniu tylko ustawianie flagi uruchamiającej tą część kodu.

    Dziękuję za dotychczasowe cenne wskazówki!!
  • #7 20053372
    Janusz_kk
    Poziom 39  
    Posty: 5801
    Pomógł: 220
    Ocena: 1454
    nowakus napisał:
    Jak Waszym zdaniem powinno się robić? Kod danej funkcji w przerwaniu, czy w przerwaniu tylko ustawianie flagi uruchamiającej tą część kodu.

    Dziękuję za dotychczasowe cenne wskazówki!!

    To drugie, zasadą jest aby przerwania były szybkie, wtedy sobie najmniej przeszkadzają, oczywiście są wyjątki jak np falownik gdzie w przerwaniu PWM musisz obliczyć nową próbkę i wysłać ją do rejestru, wtedy nie ma sensu z niego wychodzić bo się robią za duże opóźnienia. Wejdź sobie w katalog " output files" i kliknij dwa razy na pliku *.lss, tam będziesz miał asembler i zobacz jak wyglądają przerwania, nie dość że procesor musi zapamiętać aktualną pozycję, wczytać nowy adres i tam skoczyć (przerwanie) i tego jeszcze nie widzimy, to potem już w samej procedurze musi odłożyć rejestry które zmienia i stworzyć sobie środowisko czy zerowanie r1 (to widzimy), potem na wyjściu robi wszystko odwrotnie, a to trwa.
  • REKLAMA
  • #8 20053560
    bart-projects
    Poziom 30  
    Posty: 836
    Pomógł: 200
    Ocena: 260
    "Nowe" czyli od Xmega i nowsze AVR mają mozliwość priorytetu przerwania. Poprzednie miały ustalony.
    Nie wiem jaki to AVR, ale jeśli ustawisz Timer i przerwanie od niego to jak będzie w przerwaniu to oczywiście zauważy potem flagę przerwania z USART, ale może być za późno, dlatego jeśli nie trzeba, to się przerwań od wszystkiego nie uruchamia.

    Rzadko w swoich programach uruchamiam przerwania od Timerów. Tylko jeśli to ścisle czasowo zależne ponieważ drugim sposobem obsługi Timerów jest obsługa ich flag.

    Możesz ustawić w programie Timer2 dla np. Mega328P na Clear Timer i w pętli głownej będzie na Ciebie czekała flaga "Tifr2.ocf2a" i jeśli równa sie jeden to "czas wybił".
    Nie wiem po co co milisekundę przerywać program jeśli można OCF2A ustawić z prescalerem 1024 na 10ms (178 = 8MHz/1024=10ms)
    Pamiętać należy to że flagi przy wejściu w przerwanie AVR starego typu kasuje sam, czego nie robi Xmega E5 i podobne, ale też czytając flagi w petli głównej już sami musimy je kasować wpisując jedynkę do rejestru ( w to samo miejsce wpisanie jedynki kasuje flagę).


    @Janusz_kk
    Atmega 328P z kwarcem 18432000 (w specyfikacji mozna ja kręcić do 20MHz) spokojnie potrafi wygenerować trzy PWM przesunięte w fazie o 120 stopni z krokiem 256 z częstotliwością 50Hz przy tym nadając na USART o aktualnych warunkach jednego potencjometru.
    Nie liczyłem na palcach jak z wyższymi Freq.
  • #9 20053664
    Janusz_kk
    Poziom 39  
    Posty: 5801
    Pomógł: 220
    Ocena: 1454
    bart-projects napisał:
    Atmega 328P z kwarcem 18432000 (w specyfikacji mozna ja kręcić do 20MHz) spokojnie potrafi wygenerować trzy PWM przesunięte w fazie o 120 stopni z krokiem 256 z częstotliwością 50Hz przy tym nadając na USART o aktualnych warunkach jednego potencjometru.

    Pewnie że potrafi, ja na attiny 861 wygenerowałem 3 sinusy co 120 st z regulacją F od 20 do 250Hz i V od 0 do max na 16khz PWM.

    bart-projects napisał:
    "Nowe" czyli od Xmega i nowsze AVR mają mozliwość priorytetu przerwania.

    Ale on ma stare megi, tam nie ma piorytetów.

    bart-projects napisał:
    Rzadko w swoich programach uruchamiam przerwania od Timerów. Tylko jeśli to ścisle czasowo zależne ponieważ drugim sposobem obsługi Timerów jest obsługa ich flag.

    Pooling, jeszcze bardziej nieefektywny od przerwań. A można to zrobić bardzo efektywnie na przerwaniach za pomocą sbi, cbi, wtedy nawet rejestru stanu nie trzeba zachowywać bo się żadne flagi nie zmieniają.
    Robimy to tak, tworzymy strukturę bitów
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    'upychamy' ją w rejestrze z dostępem bitowym, w nowych jest GPIO w starszy w niewykorzystanym rejestrze, np TWAR
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    co można 'zautomatyzować' jak widać :) i dostajemy zmienną bitową 'INT' którą np ustawiamy komendą
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

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

    w przerwaniu ją ustawiamy
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    i tyle.
  • #10 20053827
    bart-projects
    Poziom 30  
    Posty: 836
    Pomógł: 200
    Ocena: 260
    Odniosłem sie do tego że OT przerwanie ustawił co 1ms i zlicza w nim do 20ms.
    Napisałem też że będąc w przerwaniu co 1ms można "przeoczyć" inne przerwanie.
    Jeśli odbieramy szybko dane na przerwaniach to niekoniecznie takie 10ms jest najważniejsze.
  • #11 20053841
    Janusz_kk
    Poziom 39  
    Posty: 5801
    Pomógł: 220
    Ocena: 1454
    bart-projects napisał:
    Odniosłem sie do tego że OT przerwanie ustawił co 1ms i zlicza w nim do 20ms.

    Wiem, też to zauważyłem, pisałem bardziej ogólnie, ale masz racje, niepotrzebnie 'młóci' to przerwanie co 1 ms chyba że jeszcze coś będzie odmierzał.
    To raz a dwa to w pętli głównej bym odliczał te 20ms i od razu czytał klawiaturę a nie w przerwaniu, tam niech sobie tylko leci licznik milisekund.

    bart-projects napisał:
    Napisałem też że będąc w przerwaniu co 1ms można "przeoczyć" inne przerwanie.

    Nie przeoczy tylko opóźni. Przerwania nie przepadają, nawet jak będzie drugie takie same, po wyjściu z pierwszego po jednym rozkazie z pętli głównej wróci z powrotem do przerwania.
  • #12 20053846
    bart-projects
    Poziom 30  
    Posty: 836
    Pomógł: 200
    Ocena: 260
    Wiem że flaga w rejestrze będzie ustawiona, ale co po niej jak już następny bajt w kolejce... komunikacja posypana. a po co to , nie mozna sprawdzić flagi timera 1,5ms (czy szybciej)/później) później?
    Pisałem że na przerwaniach Timera tylko ściśle zależnie czasowo..reszta nie zauważy opóźnienia.

Podsumowanie tematu

✨ Użytkownik napotkał problem z ustawianiem flagi FLAGA_CZAS w przerwaniu timera, co uniemożliwia wykonanie kodu w pętli głównej. Timer0 jest skonfigurowany do generowania przerwania co 1 ms, a w przerwaniu zliczane są tyknięcia timera. Użytkownik zauważył, że mimo poprawnego działania sygnału na PB5, kod na PB4 nie jest wykonywany. Odpowiedzi sugerują, że problem może wynikać z optymalizacji kompilatora, który nie widzi zależności między zmiennymi globalnymi. Rekomendowane jest użycie słowa kluczowego "volatile" dla zmiennej globalnej oraz przeniesienie logiki obsługi klawiatury do przerwania, aby uniknąć problemów z synchronizacją. Dodatkowo, dla mikrokontrolera ATmega328, zasugerowano użycie rejestru GPIOR0 do zarządzania flagami, co może poprawić wydajność operacji.
Wygenerowane przez model językowy.
REKLAMA