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

ATMega8 - Jakie rejestry timera zerować przed uśpieniem mikrokontrolera?

Asmox 04 Sie 2014 23:17 2538 17
  • #1 13851812
    Asmox
    Poziom 9  
    Uczę się programowania ATMegi8 z książki Pawła Borkowskiego: "AVR8 i ARM7. Programowanie mikrokontrolerów dla każdego.". Moje ćwiczenie wygląda następująco:
    Cytat:
    W celu maksymalnego oszczędzania energii w telefonach komórkowych po kilkunastu sekundach bezczynności następuje automatyczne przejście w tryb zmniejszonego poboru energii. Ten fakt zaobserwowaliśmy na pewno niejednokrotnie, gdy spieszyliśmy z naciśnięciem dowolnego klawisza w celu uaktywnienia wyświetlacza LCD. Projektowany program ma zachowywać się podobnie: należy obsłużyć układ z rysunku 8.17 tak, aby po dziesięciokrotnym mrugnięciu diody D1 (w odstępach półsekundowych) mikrokontroler przeszedł w tryb uśpienia. Jeśli zostanie wykryte wciśnięcie przycisku S1, liczenie mrugnięć należy zacząć od początku. Także wyjście z trybu uśpienia ma nastąpić w wyniku wykrycia tego przycisku.

    Mówiąc w skrócie: Po naciśnięciu przycisku układ budzi się, mryga 10 razy i zasypia. Przy naciśnięciu podczas mrugania ma ono zacząć się od początku. Dany układ wygląda następująco:
    ATMega8 - Jakie rejestry timera zerować przed uśpieniem mikrokontrolera?

    Do rozwiązania problemu postanowiłem wykorzystać ustawienie timera w tryb CTC. Dzięki temu mogę ustawić miganie w rytmie dokładnie równym pół sekundy. Zanim jednak przedstawię program, mam pytanie:
    [?] Które rejestry timera należy zerować przed uśpieniem mikrokontrolera?
    Pytam dlatego że przy wybudzaniu na nowo je ustawiam, choć nie wiem czy jest taka potrzeba.

    Oto mój program, który jak się domyślacie nie działa... Aby szybciej widzieć co się dzieje, ustawiłem na razie 5 mrugnięć. W oryginalnej postaci nic się nie działo, po zakomentowaniu skoku do uśpienia program działa dziwnie. Chodzi o to, że po pierwszym naciśnięciu mryga 6 razy (dlaczego?), a potem w ogóle. Tak więc mam dwa problemy:
    [?] Gdy tylko program skoczy do etykiety z usypianiem, mikrokontroler przestaje mrygać
    [?] Jeżeli usunę usypianie od razu po ustawieniu wszystkich rejestrów to za pierwszym razem dioda mruga o 1 raz za dużo
    Zauważyłem, że ten "1 raz za dużo" pojawia się od razu po naciśnięciu przycisku - mimo że pierwsze mrygnięcie powinno pojawić się dopiero po połowie sekundy.
    To są moje obserwacje, ale nie znam ich przyczyny. Bardzo proszę o pomoc w rozwiązaniu problemów.
    Kod: text
    Zaloguj się, aby zobaczyć kod
  • Pomocny post
    #2 13852494
    Akane
    Poziom 27  
    Pierwszy i bardzo poważny problem zauważyłem w funkcji TIMER1_COMPA, jest nim linijka rjmp uspij. Gdy się wykona, to przerwania pozostaną zablokowane, oraz kilka bajtów na stosie zostanie uwięzionych - głównie adres powrotu z przerwania. Można temu zaradzić poprzez wstawienie pop + pop + sei przed rjmp.

    Drugi problem, to karygodna pętla główna - dławienie procesora zbędnymi cyklami - nie jest zgodne z założeniem projektu.
    Trzeci problem, to brak rozkazu sleep, który usypia procesor w stan ustawiony w MCUCR.

    Proponuję małą zmianę, która przekazuje całe sterowanie do pętli głównej:
    1. Tuż przed wejściem do pętli głównej, ustaw tryb sleep na najwyższy możliwy, pozwalający na pracę timera i przerwania zewnętrznego - ja zazwyczaj używam IDLE. Nadaj tej części kodu jakiś label, np. setup_sleep. Będzie to potrzebne później.
    Przykład: MCUCR.SM=000b, MCUCR.SE=1

    2. W pętli głównej pozostaw zapętlenie, ale rozbuduj je do postaci:
    petla:
    	sleep ; czekaj na dowolne przerwanie
    
    	if mruganie_zakonczone (bity CS12,CS11,CS10 w TCCR1B są zerowe)
    		tryb uśpienia = powerdown; (MCUCR.SM=010b, MCUCR.SE=1)
    		sleep ; czekaj na EXT_INT0
    		przywróć bity CS12,CS11,CS10 w TCCR1B (TCCR1B.CS=010b)
    		rjmp setup_sleep ; przywróć tryb uśpienia na idle
    	endif
    
    	rjmp petla


    3. Usuń fragment "obudz" z EXT_INT0, niech tylko zresetuje timer, i ustawi licznik i odblokuje przerwanie. To ostatnie, można przenieść na początek programu, do start.
    4. W TIMER1_COMPA, po wyzerowaniu licznika, wyłącz timer, ustawiając źródło zegara na "none". Będzie to dokładnie w TIMER1_COMPA_STOP - zamiast rjmp uspij, wstaw zerowanie bitów CS12,CS11,CS10 w TCCR1B, a następnie reti.

    Warunek mruganie_zakonczone może sprawdzać, czy timer jest wyłączony, lub czy licznik mrugnięć jest zerem.

    W trybie power down, taktowanie procesora i jego peryferii jest zatrzymane, więc timery nie działają. Przejścia w ten tryb można dokonać równie dobrze w TIMER1_COMPA_STOP (gdy licznik mrugnięć dojdzie do zera), wtedy warunek "if mruganie_zakonczone" w głównej pętli nie będzie potrzebny:

    TIMER1_COMPA_STOP:
    	sei
    	tryb uśpienia = powerdown; (MCUCR.SM=010b, MCUCR.SE=1)
    	sleep ; czekaj na EXT_INT0
    	tryb uśpienia = idle; (MCUCR.SM=000b, MCUCR.SE=1)
    	reti; ewentualnie RET, bo mamy sei powyżej (dotyczy tylko procesorów niezapisujących flag na stosie)
    
    EXT_INT0:
    	TCNT1 = 0
    	R17 = 10
    	reti
    
    start:
    	tylko dodaj zezwolenie na przerwanie OCI1A, reszta bez zmian
    	ldi R16, (1<<OCIE1A)
    	out TIMSK, R16
    petla:
    	sleep
    	rjmp petla


    [?] Które rejestry timera należy zerować przed uśpieniem mikrokontrolera?
    - nie ma takiej potrzeby, uśpienie procesora nie ustawia rejestrów w stan początkowy, jak po resecie. Trzeba tylko pamiętać, że timery mogą zostać wyłączone na czas uśpienia, zależnie od trybu uśpienia i czy taktowanie timerów jest synchroniczne (pdf: Power Management and Sleep Modes).

    [?] Gdy tylko program skoczy do etykiety z usypianiem, mikrokontroler przestaje mrygać
    - patrz pierwszy wspomniany błąd

    [?] Jeżeli usunę usypianie od razu po ustawieniu wszystkich rejestrów to za pierwszym razem dioda mruga o 1 raz za dużo
    - timer działa/zlicza cały czas, co powoduje, że po pewnym czasie jego zawartość dochodzi do wartości granica,OCR1A i ustawia flagę przerwania OCF1A. Gdy po tym fakcie odblokujesz przerwanie OC1A, procesor natychmiast skoczy do obsługi przerwania, po uprzednim wyzerowaniu tej flagi.
    Środek zaradczy: wyzerowanie flagi OCF1A przed odblokowaniem przerwania OC1A, poprzez typowe wpisanie do niej jedynki: TIFR = TIFR or (1<<OCF1A).
  • #3 13853443
    Asmox
    Poziom 9  
    Dzięki za pomoc :-) Przy okazji przypomniałeś mi, że po wywołaniu przerwania ich obsługa zostaje zatrzymana. Wprowadziłem Twoje modyfikacje, jednakże nadal układ nie działa. Po naciśnięciu przycisku nic się nie dzieje. Oto poprawiony kod:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    Zastanawiam się czy oprócz włączenia przerwań przy etykiecie TIMER1_COMPA_STOP nie należy także zdjąć adresu przerwania ręcznie. Może stos się zapchał?
  • Pomocny post
    #4 13853673
    Akane
    Poziom 27  
    Przyznam się, że nie próbowałem pomijać reti skacząc do pętli głównej z ISR, ale już podmieniałem adres powrotu w projekcie minidds. Ale zakładam, że AVR ma to gdzieś i nie obchodzi go, czy ISR jest zapętlone, czy nie. Zresztą gdzieś w nocie pisało, że SEI w obsłudze przerwania może spowodować nadpisanie całego RAMu, jeżeli kod będzie wolny, więc raczej taka technika jest dozwolona, o ile ściągniesz adres powrotu ze stosu.

    Stos zapcha się po (RAMEND+1)/2=512 zatrzymaniach migacza. Możesz albo ściągnąć adres powrotu ze stosu (dodając dwa POP do jakiegoś wolnego rejestru) przed rjmp setup_sleep w TIMER1_COMPA_STOP, albo zrobić to zwyczajowo, jak "wszyscy": zamiast skoku do pętli głównej, przywróć tryb IDLE i pozwól procesorowi normalnie wyjść z funkcji.

    W setup_sleep nie ustawiłeś bitu SE. Żeby było jasno: ANDI nie ustawia, tylko zeruje bity celu (lewy argument), te, które w prawym argumencie są wyzerowane.

    W takiej postaci, jak powyżej podałeś, sleep nie działa tylko w pętli głównej.
    Potrzebuję dłuższej chwili na analizę ciągu. Jeśli coś jeszcze wypatrzę, to dopiszę.
  • #5 13854251
    Asmox
    Poziom 9  
    Postanowiłem na moment uprościć sobie zadanie - użyłem przerwań przepełnienia Timera1 zamiast przerwania porównania. Nie wiem czy program jest napisany efektywnie, ale za to działa. Wrzucam jego kod:
    Kod: text
    Zaloguj się, aby zobaczyć kod


    Spróbuję na jego podstawie napisać taki program, który działa w idealnych odstępach półsekundowych.

    @Edit:
    Po kilku minutach przerabiania programu udało mi się wykorzystać przerwania porównania Timera1, a nie jego przepełnienia. Oto wyniki mojej pracy:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    Zmieniłem tam kilka rzeczy:
    1. Procedura usypiania jest realizowana w pętli głównej. Jest tam również nieustanne sprawdzanie czy licznik mrugnięcia jest większy od zera. Nie wiem czy to lepsze rozwiązanie od pustej pętli, ale mikrokontroler i tak musi mieć jakąś nieskończoną pętlę, więc chyba wszystko jedno.
    2. Dodałem "natychmiastową reakcję" na naciśnięcie przycisku. Pod etykietą przerwania INT0 znajduje się również instrukcja zapalenia diody.
    3. Licznik mrugnięć zmniejszyłem do 19. Dzięki temu ostatnie przełączenie powoduje zgaszenie diody (nieparzysta liczba przełączeń - czyli na końcu będzie stan odwrotny do początkowego a na początku dioda jest zapalana przyciskiem). Dzięki temu etykieta TIM1_COMPA_STOP jest niepotrzebna.

    Wiem, że sprawdzanie cudzego kodu w poszukiwaniu błędów to okropna robota. Ostatecznie zrobiłem wszystko po swojemu, pisząc program od nowa, ale dziękuję jednocześnie za pomoc.
    Jeżeli masz chęć, sprawdź proszę czy program jest ogólnie poprawny. Mam na myśli czy mikrokontroler nie wykonuje zbyt wielu operacji ani czy nie występuje tu jakieś zapychanie pamięci.
  • #6 13854789
    excray
    Poziom 41  
    Nie czytałem kodu ale rozłożył mnie tekst że chcesz użyć timera aby dioda migała dokładnie co 0,5s. Mistrzostwo w utrudnianiu sobie życia i koncentrowaniu się na głupotach zamiast na konkretach. Cały kod jest na kilkanaście linijek:
    //START:
    skocz do MAIN
    //obsługa przerwania IRQ:
    ustaw zmienną globalną - TEMP=10
    reti
    //MAIN:
    konfiguruj stos
    konfiguruj DDR i PORT
    konfiguruj przerwanie od INT0
    konfiguruj tryb uśpienia
    ustaw zmienną globalną TEMP=10
    sei
    //jmp:
    zapal diodę
    czekaj około 0,1s
    zgaś diodę
    czekaj około 0,4s
    zmniejsz TEMP
    jeśli TEMP<>0 to skocz do jmp
    sleep
    skocz do jmp
  • #7 13855341
    Asmox
    Poziom 9  
    Oczywiście jest to robienie sobie pod górkę, ale chcę jakoś nauczyć się obsługi timerów. Umiem napisać procedury opóźniające, które nabijają cykle procesora żeby opóźnić wywołanie dalszych instrukcji. Teraz chciałem użyć timera aby przećwiczyć jego używanie.
    Idąc Twoim rozumowaniem, całe to ćwiczenie można uznać za głupotę, bo przecież ten program tak naprawdę do niczego się nie przyda. No ale tak to jest z ćwiczeniami - same z siebie wiele pożytku nie dają, ale pozwalają zrozumieć pewne rzeczy. Nie znam nikogo kto bez żadnej nauki oraz ćwiczeń zrobił jakiś praktyczny projekt.
    Prawdę mówiąc, zastanawiam się czy pisanie w Assembly na AVRy ma sens w czasach C oraz platformy Arduino. No cóż, pewnie przyjdzie taki moment że przerzucę się na C. Póki co, jednak chcę popisać trochę w Asm żeby dobrze zrozumieć jak działają mikrokontrolery.
  • #8 13855569
    excray
    Poziom 41  
    Idąc moim tokiem myślenia programy pisze się tak aby były szybkie proste i bezbłędne. Krowę programową każdy głupi napisać może i to żaden sukces nauczyć się pisać duże obszerne wolne, pełne błędów i niepotrzebnie angażujące peryferia programy. Zadanie dostałeś na 20 minut a już 2 dni siedzisz bez skutku bo niepotrzebnie wchodzisz w jakieś peryferia. Chcesz się uczyć liczników to zrób zadanie gdzie rzeczywiście liczniki będą przydatne.
  • #9 13855708
    szczywronek
    Poziom 28  
    excray a odmierzanie czasu nie jest zadaniem, gdzie liczniki są przydatne? Co z tego, że to migająca dioda. Autor postanawia sobie mniej lub bardziej utrudnić życie, uczy się dla siebie, nie robi tego na czas. Wydaje mi się, że należy go raczej pochwalić za ambicje i wspierać a nie proponować programowego muła z pustą pętlą...
  • #10 13855862
    excray
    Poziom 41  
    Mylisz się kolego szczywronek. Ambicja nie jest rzeczą godną pochwały. Tutaj sami ambitni się zgłaszają z kosmicznymi pomysłami na pierwszy program i szybko kończą zniechęceni. Trzeba zaczynać od podstaw a nie porywać się z motyką na słońce. Proponuję by kolega Asmox najpierw napisał swój program w oparciu o tego "programowego muła". A jak to mu się uda to dopiero niech rozwija program o kolejne peryferia.
  • #11 13856268
    Asmox
    Poziom 9  
    Hej, excray, to może jednak obejrzysz mój program? Albo chociaż przeczytasz post który go zawiera? Zrobiłem już to zadanie. Faktycznie zajęło mi to dwa dni, ale co z tego? Nigdzie mi się nie spieszy.
    excray napisał:
    Tutaj sami ambitni się zgłaszają z kosmicznymi pomysłami na pierwszy program i szybko kończą zniechęceni.

    Myślę że gubią ich nie tyle ambicje, co krótki horyzont czasowy. Nie można mieć wszystkiego od razu. Kosmiczne pomysły wymagają czasu i konsekwencji w działaniu, aby je zrealizować. Widzisz, ja postanowiłem trochę "zwolnić" i opisać swój problem. Nie leciałem dalej ("bo nie umiem zrobić to olewam"), tylko poprosiłem o pomoc. I ją uzyskałem, za co jestem wdzięczny.
    Kolejne zadanie do zrobienia z książki (nie będę go tutaj opisywał) wymaga (o dziwo!) wykorzystania obu timerów. Dzięki zrobieniu opisanego wyżej ćwiczenia z timerem teraz łatwiej będzie mi do niego przystąpić.
  • Pomocny post
    #12 13857016
    Akane
    Poziom 27  
    Asmox, sprawdź opis rozkazu brge, bo ja widzę, że on wykonuje skok, gdy wynik porównania wartości całkowitych jest nieujemny, czyli
    Kod: text
    Zaloguj się, aby zobaczyć kod
    Skok nie wykona się tylko wtedy, gdy R17 będzie "ujemne", czyli z ustawionym najstarszym bitem (wartości od 128 do 255).

    Założenia są takie, że R17 jest licznikiem, więc nie ma mowy o wartościach ujemnych; używamy arytmetyki bez znaku (pomijamy rozkazy z wyrazem signed w opisie). Najlepiej pasuje tutaj BRNE - branch if not equal.
  • #13 13858028
    Asmox
    Poziom 9  
    Wielki dzięki, teraz znowu mi nie działa xD
    Ale zauważyłem coś dziwnego. Kiedy zmieniłem tryb uśpienia z Power-down na Idle:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    To teraz wszystko działa! Nie rozumiem tego. Przecież tryb Power-down ma obsługiwać przerwania zewnętrzne, a takim jest INT0. Natomiast gdy włączam bit SM1, to układ nie chce wstawać. Jaka może być tego przyczyna?
  • #14 13858262
    Akane
    Poziom 27  
    Możliwe, że problem leży w sposobie wyzwalania INT0. U Ciebie jest to opadające zbocze, a to wymaga działającego zegara, który jest wyłączony w trybie power-down.
    Cytat:
    Only an External Reset [...] or an external level interrupt on INT0 or INT1, can wake up the MCU.


    Proponuję zmianę trybu wyzwalania z toggle (ISC01=1, ISC00=0) na level (ISC01=ISC00=0) przed wejściem w power-down, i przywrócenie reakcji na zbocze po rozkazie sleep.

    Kod: text
    Zaloguj się, aby zobaczyć kod


    ** Na stosie zapisuję aktualny tryb uśpienia i wyzwalania przerwań zewnętrznych, a pod koniec przywracam zapamiętane ustawienia.
  • #15 13862180
    Asmox
    Poziom 9  
    Hej, miałem problem ze zrozumieniem kodu, który napisałeś, ale Twoja wskazówka znów okazała się cenna. Rozumiem, że przy przerwaniu w reakcji na niskie napięcie wciśnięcie przycisku niesie za sobą nieustanne wywoływanie przerwań, natomiast gdy układ ma reagować na opadające zbocze, to tylko raz.
    Zauważyłem jednak pewną zależność, która tym razem chyba jest przypadkowa. Albo ustawiam bity dot. uśpienia (a wtedy nie ustawiam tych na kształt sygnału wywołującego przerwanie ponieważ to same zera) albo te dotyczące przerwania (bo wtedy układ nie śpi czy SE=SM0..2=0 ale trzeba ustawić kształt sygnału na opadające zbocze).
    Suma sumarum mój kod wyszedł prostszy od Twojego, ale rodzaj zapisu który przedstawiłeś trochę mnie intryguje. Być może przyda mi się przy bardziej skomplikowanych programach.
    A oto moja pętla główna:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    I ogólnie jest w porządku, chociaż błąd o którym pisałem, dalej potrafi się pojawić. Następuje to jednak dużo rzadziej i tylko przy wybudzaniu, nigdy przy mryganiu. Czasami gdy nacisnę przycisk zapalają się obie diody i świecą na raz. Zastanawiam się czy można coś jeszcze z tym zrobić? Jedyne co mi przychodzi do głowy to dodać instrukcje opóźniającą o jakieś 50ms żeby to przerwanie nie było uruchamiane wielokrotnie. Co o tym sądzisz? Czy jest inne wyjście?
    A to moja poprawiona wersja programu:
    Kod: text
    Zaloguj się, aby zobaczyć kod
  • Pomocny post
    #16 13862632
    2rs232
    Poziom 18  
    Spróbuj takiej zmiany.
    Kod: text
    Zaloguj się, aby zobaczyć kod
  • #17 13863083
    Akane
    Poziom 27  
    2rs232 podał ważną, choć niekompletną wskazówkę, bo dotyczącą jednej modyfikacji.
    W każdej obsłudze przerwania - podobnie jak z rejestrami - należy zabezpieczyć flagi (rejestr SREG), jeśli tylko ich modyfikacja może wpłynąć na działanie innych części programu. Jeżeli flagi nie są modyfikowane, to nie trzeba ich zabezpieczać.
    SREG można zapisać na stosie, lub lepiej pozostawić w jakimś rejestrze, jeżeli jest jakiś wolny na całą obsługę przerwania.

    Mając pętlę główną sprawdzającą czy coś jest zerem, równe/większe/mniejsze od czegoś innego, wykonujemy najpierw porównanie, które zazwyczaj jest odejmowaniem bez zapisania wyniku, i w tym kroku procesor ustawia flagi. Następny krok to jakiś selektywny skok, na przykład BRxx. Gdy pomiędzy tymi dwoma krokami pojawi się przerwanie, a jego obsługa zmieni flagi i nie przywróci poprzedniego ich stanu przed RETI, to drugi krok nie wykona się zawsze poprawnie, bo flagi uzyskanie z porównania zostaną nadpisane.

    Problem lawiny przerwań przy wybudzaniu procesora może występować też przy reakcji na przycisk, gdy przerwanie wywołuje zbocze sygnału. Pierwszy przypadek, gdy poziom wyzwala przerwanie, wygasa najpóźniej po trzech wejściach do INT0 (dla mojego przykładu: sleep, przerwanie, pop, przerwanie, out MCUCR, przerwanie). Procesor AVR po wyjściu z obsługi przerwania, gwarantuje wykonanie co najmniej jednej instrukcji z miejsca, gdzie wystąpiło przerwanie, zanim aktywuje kolejne przerwanie.
    Cytat:
    When the AVR exits from an interrupt, it will always return to the main program and execute one more instruction before any pending interrupt is served.


    Skutecznym rozwiązaniem będzie tutaj zmiana sposobu wyzwalania INT0 (na opadające zbocze) w samym przerwaniu INT0. Dodatkowo opcjonalne opóźnienie, typowo 20ms, przed wyjściem z INT0, zapewni wyeliminowanie reakcji na drgania styku.

    Ten drugi przypadek - drgania styku - sam wygasa po kilku milisekundach, zależnie od właściwości fizycznych elementów ruchomych i ich otoczenia. Ilość zwarć podczas wciśnięcia przycisku może wynosić (idealnie) jeden, lub więcej.

    Obie części rozwiązania nie są konieczne w tym konkretnym programie, bo kilkukrotne wyzerowanie licznika, w czasie kilku milisekund, niczego nie zmieni, w porównaniu do połowy sekundy, po której licznik osiąga wartość maksymalną. Jedyne, co tracisz, to kilka milisekund poślizgu od pierwszego zwarcia styków przycisku, do pierwszego przepełnienia licznika.

    Z drganiami styków możesz poeksperymentować - zlicz ich ilość i tyle samo razy zamrugaj diodą. Licznik ilości zmian stanu diody początkowo ustaw na zero. W INT0 zwiększ o dwa. Aktywuj przycisk krótko i długo, obserwuj diodę - ile razy miga po kliknięciu/wciśnięciu/zwolnieniu przycisku.
  • #18 13880147
    Asmox
    Poziom 9  
    No cóż, wydaje mi się że problem został rozwiązany w 100%, gdyż nie ma już żadnych zacięć.
    2rs232, mimo że ostatecznie inaczej zmodyfikowałem program, dzięki że chciałeś mi pomóc. Dzięki temu zwróciłem uwagę, że w programie jest usterka przez którą jest nieciągłość. Ogólnie mógłbym zamknąć już temat, ale jeszcze do końca nie rozumiem co było złego w poprzednim kodzie.
    Przypuśćmy, że do ATMegi dotarło "za dużo" sygnałów przerwania z powodu drgań styków. Nawet jeżeli tak by się działo, to przecież porównanie cpi R17, 0 jest wykonywane w przerwaniu TIM1_COMPA więc nie powinno być to żadnych zakłóceń ponieważ obsługa przerwań w tym momencie jest jeszcze wyłączona. Mogę prosić o wytłumaczenie jeszcze raz?
REKLAMA