Skocz do zawartości

[C][Atmega16] Timer0 po raz setny...


mopsiok

Pomocna odpowiedź

Witam wieczorową porą i z góry przepraszam forumową brać za tak prymitywny problem 😋. Chodzi o to, że nie mogę się dogadać z moją Atmegą jeśli chodzi o Timer0. Nie mam nawet pojęcia dlaczego. Poczytałem sporo artykułów, przerobiłem książkę pana Borkowskiego, nie ominąłem również poczciwej dokumentacji Atmela. I nic. Nawet najprostszy kod nie działa. Mam na myśli coś takiego:

#include <avr/io.h>
#define F_CPU 16000000
#include <avr/interrupt.h>

ISR (TIMER0_OVF_vect) {
PORTA = 0;
}


ISR (BADISR_vect) {}


int main(void)
{
DDRA = 255;
DDRC = 255; 
PORTA = 255;
PORTC = 0;

TCNT0 = 0; //ustaw na poczatek
TCCR0 = (1<<CS01); //ustaw preskaler timera0 na 8
TIMSK = (1<<TOIE0); //zezwol na przerwanie przy przepelnieniu
sei();

for(;;) 	
return 0;
}

Ten kod jest tylko testowy - po podłączeniu zasilania dioda podłączona pod port A powinna zaświecić na ułamek sekundy, a potem na stałe zgasnąć. Niestety świeci od pół godziny 🤣. Ogólnie to chciałbym wywoływać przerwanie co 40us, do czego zamierzam wykorzystać timer0 w trybie porównywania TCNT0 z OCR0. Wtedy ustawiając OCR0 na 80 powinienem otrzymać przerwanie co dokładnie 40us. No ale nie mogę tego osiągnąć jeśli timer nie chce mi działać nawet w domyślnej konfiguracji...

Czy ma ktoś jakiekolwiek przypuszczenia co może być przyczyną? Siedzę nad tym kilka godzin i już średnio wytrzymuję nerwowo.

Dzięki i pozdrawiam

mopsiok

Link do komentarza
Share on other sites

Niestety, nic nie pomogło...

W książce Borkowskiego jest napisane, że raz na kilka miesięcy programy w C po prostu nie chcą działać 🤣. Czy w tym wypadku może właśnie o to chodzić?

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Ooo nie wierzę 🤯... po prostu nie wierzę. Taki durny błąd... powiem szczerze że akurat tę linijkę napisałem na szybciora i nawet się nad nią nie zastanowiłem myśląc, że bez klamer też będzie okej.

Mój Mistrzu, leci Piwo Mocy bo naprawdę się należy! 🙂

Proszę jeszcze nie wywalać tematu, bo czeka mnie jeszcze sprawienie, by przerwanie występowało co 40us, więc mogą pojawić się problemy.

Dzięki Panowie za zainteresowanie i pomoc.

//Edit:

Dobra, problem rozwiązany w stu procentach. Na wypadek gdyby komuś się przydało - wrzucam kod:

#include <avr/io.h>
#define F_CPU 16000000
#include <avr/interrupt.h>

int licznik = 0;
ISR(TIMER0_COMP_vect) {
licznik++;
if (licznik == 25000) { //40us * 25000 = 1s; sprawdzenie dzialania
	PORTA = ~PORTA;
	licznik = 0;}
}


ISR (BADISR_vect) {}


int main(void)
{
DDRA = 255;
DDRC = 255; 
PORTA = 255;
PORTC = 0;

TCNT0 = 0; //ustaw na poczatek
OCR0 = 80; //gdy licznik dojdzie do 80, wykonaj kod z przerwania
TCCR0 = (1<<WGM01) | (1<<CS01); //preskaler 8, tryb porownania z OCR0
TIMSK = (1<<OCIE0) | (1<<TOIE0); //odblokowanie przerwania porownania i przepelnienia
sei();

for(;;) 	
return 0;
}
Link do komentarza
Share on other sites

albo for(;😉; ale najlepiej jakby był sprawdzany jakikolwiek warunek więc raczej while(1);

W książce Borkowskiego jest napisane, że raz na kilka miesięcy programy w C po prostu nie chcą działać 🤣. Czy w tym wypadku może właśnie o to chodzić?
To raczej subiektywne wrażenie. Zwykle po kilku godzinach ślepienia w kod znajdujesz błąd tak głupi że po prostu wstyd się przyznać;

Hm, wkleiłeś to samo co było 😉

Link do komentarza
Share on other sites

Ale zaraz, to ja też chcę zrozumieć. Znaczy kapuję, że instrukcja for potraktowała instrukcję return jako ciało pętli i zechciała ją powtarzać. Conajmniej raz to się udało, bo return od razu zwróciło sterowanie z main do "rozbiegówki" która tę funkcję wywołała. Tylko, że tam jest tylko skok do samego siebie. Jeżeli nie ma tam globalnej blokady przerwań a o ile pamiętam nie ma, wszystko powinno być OK. Za odliczony czas powinno wejść przerwanie i zgasić diodę. Więc jak to było naprawdę?

No dobra, sprawdziłem, po wyjściu z main przerwania są blokowane i procesor "zamiera" w nieskończonej pętli - pamięć zaczyna mnie zawodzić.

Link do komentarza
Share on other sites

Zamiera w nieskończonej pętli, czy po prostu kończy działanie programu?

Jeśli return wykonał się raz, to wskaźnik programu (czy jak on się fachowo nazywa?) powinien wskazać, na następną instrukcję po wywołaniu funkcji main a takowych instrukcji już nie ma, więc nastąpił end.

Czy nie dlatego przerwania zostały zablokowane?

Link do komentarza
Share on other sites

1. Zmienna licznik powinna być volatile: http://mikrokontrolery.blogspot.com/2011/04/problemy-c-volatile.html

2. Return 0

Return jest zbędny ponieważ nie jest to program działający pod nadrzędnym systemem operacyjnym, który go wywałał i po skończeniu realizacji program zwraca 0. W Twoim przypadku mikrokontroler ma kręcić się w kółko w pętli.

Dla porządku przejście do nowej linii bez zakończenia poprzedniej znakiem średnika:

for(;😉

return 0;

oznacza, że wykonujesz tak naprawdę:

for(;😉 return 0;

czyli pętlę nieskończoną wywołań rozkazu return 0.

Ale ponieważ wywołanie return 0 wprowadza mikrokontroler w maliny, to pętla nie jest wykonywana, a mikrokontroler zaczyna robić głupoty, skacząc do bliżej nieokreślonej części pamięci programu - sprawdź w symulatorze.

Prawidłowe pętle główne to:

for(;😉;

//lub

while(1);

lub z programem w środku:

for(;😉{

... jakiś program

}

//lub

while(1){

... jakiś program

}

Wywal Return 0.

Reasumując: Return 0 w main() jest zabronione, podobnie jak inne każde inne Return 🙂

Link do komentarza
Share on other sites

A czy gdyby po

int main(void) 
{ 
   DDRA = 255; 
   DDRC = 255; 
   PORTA = 255; 
   PORTC = 0; 

   return 0; 
} 

była inna funkcja, np. wywoływana z main

int funkcja() 
{ 
   return 0; 
} 

nie byłaby ona wykonana po wyskoczeniu z main?

Co zatem kończy wykonywanie programu? W Bascom po "End" program się kończy i procesor nie robi nic. Tzn. nie wiem, czy nic nie robi i nie potrafię tego jednoznacznie stwierdzić. Podejrzewam tylko, że trafiając na ostatni adres rozkazu do wykonania, po jego wykonaniu nie zwiększa wskaźnika programu, czyli chyba de facto "zawiesza się".

Link do komentarza
Share on other sites

Co zatem kończy wykonywanie programu?

W Bascom po "End" program się kończy i procesor nie robi nic.

Po pierwsze dlaczego miałby się zakończyć i stanąć? Cóż to za projekt?

Po drugie BASCOM-owe End nie czyni cudów zatrzymując mikrokontroler, tylko zapewne wyłączyło przerwania globalne i wprowadziło mikrokontroler w pętlę nieskończoną. I Ty zrób to samo.

Return w funkcji powoduje wyjście z funkcji. Return w main powoduje to co opisałem w poprzednim poście.

Podejrzewam tylko, że trafiając na ostatni adres rozkazu do wykonania, po jego wykonaniu nie zwiększa wskaźnika programu, czyli chyba de facto "zawiesza się".

Co znaczy "zawiesza się"? Jest taki rozkaz w datasheet mikrokontrolera?

BASCOM to nie cudotwórca i takiej funkcji mikrokontrolerowi nie wciśnie w jego tranzystory.

To właśnie przykład złych nawyków, które uczy BASCOM, a które ciągną się przy przesiadce na dowolny inny język wyższego poziomu.

Link do komentarza
Share on other sites

"Jeśli return wykonał się raz, to wskaźnik programu (czy jak on się fachowo nazywa?) powinien wskazać, na następną instrukcję po wywołaniu funkcji main a takowych instrukcji już nie ma, więc nastąpił end.

Czy nie dlatego przerwania zostały zablokowane?"

Nazywa się "licznik instrukcji" ale tłumaczeń zwrotu Program Counter jest wiele. Ależ oczywiście, że wskazuje na następną instrukcję po instrukcji wywołania funkcji main() i oczywiście są tam jakieś instrukcje bo nie może ich "nie być" - każda kombinacja bitów w pamięci programu jest jakąś instrukcją. Inna sprawa, czy prawidłową i czy sensownie wstawioną. Nie,nie daltego zostały zablokowane, że nie ma żadnej instrukcji tylko dlatego, że szkielet programu w C (to coś co wykonuje sie przed main i zaraz po nim) przewiduje wykonanie instrukcji zablokowania przerwań i skok do samego siebie wykonywany w nieskończoność aż do resetu lub wyłączenia zasilania.

"Ale ponieważ wywołanie return 0 wprowadza mikrokontroler w maliny,"

"Return 0 w main() jest zabronione, podobnie jak inne każde inne Return "

Nie wprowadza w żadne maliny i nie pisz nie sprawdzonych informacji. Zajrzyj do zrzutu asemblerowego (plik .lss) i zobacz co tam się dzieje po main. Z tego powodu return w main nie jest zabronione i jak najbardziej dopuszczalne. Sterowanie oddawane jest z powrotem do "rozbiegówki" czyli fragmentu kodu który po resecie ustawia wskaźnik stosu, zeruje zmienne które mają być wyzerowane i wpisuje wartości do zmiennych statycznych którym wartości muszą być nadane przed startem funkcji main. Potem wywoływana jest funkcja main normalną instrukcją CALL a po powrocie z tamtąd wykonywany jest skok do etykiety _exit gdzie znajdziesz instrukcję wyłączenia przerwań i skok do samego siebie, co de facto zawiesza procesor do końca (jego) życia.

Nawiasem mówiąc istnieją specjalnie przewidziane do tego sekcje kodu, do których możemy przy pomocy specjalnych atrybutów wpisywać nasz własny kod (choćby w C) i wykonywać go przed lub po main(). To przydaje się np. do sytuacji, gdy mamy duży zewnętrzny RAM dołączony do ATmegi. Jak z niego skorzystać w sposób naturalny w C, gdy domyślnie ta pamięć nie jest widoczna i trzeba odblokować ją wpisami do rejestru procesora? Jeśli zrobimy to w main, już będzie za późno. Zmienne tam przechowywane nie dostaną swoich wartości początkowych bo "rozbiegówka" nie ma dostępu do tej pamięci.

"wyjscie z maina nie powoduje zakończenia programu, a przejsćie do pamięci niekoniecznie przeznaczonej na program"

Nieprawda. Wyjście z maina jest kontrolowane i wykonywany jest ściśle określony ciąg instrukcji. Nie ma żadngo wyjścia do pamięci niekoniecznie przeznaczonej na program. Z resztą w architekturach typu Harvard (a takie są nasze AVRy) nie ma szans wykonywać kodu z pamięci danych.

"Podejrzewam tylko, że trafiając na ostatni adres rozkazu do wykonania, po jego wykonaniu nie zwiększa wskaźnika programu, czyli chyba de facto "zawiesza się"."

AVR nie ma instrukcji STOP albo HALT jak niegdysiejsze procesory zatrzymującej sekwencer instrukcji. Nie licząc trybów uśpienia (ale to zupenie inna bajka i z innego powodu wprowadzona) po każdej instrukcji PC zwiększa się i zaczynany jest cykl pobrania następnego kodu operacyjnego. Procesor zawieszany jest poprzez wykonanie instrukcji wyłączenia wszelkich przerwań i nieskończone wykonywanie prostego skoku do samego siebie. Nie ma czegoś takiego jak "ostatni adres rozkazu do wykonania" - skąd procesor miałby niby wiedzieć, co jest ostatnie do wykonania?

Chłopaki, co jest? Ilu ludzi tyle poglądów i zabobonów? Może by tak który zajrzał do kodu wynikowego i listy instrukcji zamiast pleść takie rzeczy?

  • Lubię! 1
Link do komentarza
Share on other sites

Nie wprowadza w żadne maliny i nie pisz nie sprawdzonych informacji. Zajrzyj do zrzutu asemblerowego (plik .lss) i zobacz co tam się dzieje po main.

No to popatrzy 🙂

Program:

#include <avr/io.h> 

int main(void) 
{ 

   return 0; 

} 

kod wynikowy:

---- UNKNOWN_FILE ---------------------------------------------------------------------------------
0: File not found
+00000000:   940C002A    JMP       0x0000002A     Jump
+00000002:   940C0034    JMP       0x00000034     Jump
+00000004:   940C0034    JMP       0x00000034     Jump
+00000006:   940C0034    JMP       0x00000034     Jump
+00000008:   940C0034    JMP       0x00000034     Jump
+0000000A:   940C0034    JMP       0x00000034     Jump
+0000000C:   940C0034    JMP       0x00000034     Jump
+0000000E:   940C0034    JMP       0x00000034     Jump
+00000010:   940C0034    JMP       0x00000034     Jump
+00000012:   940C0034    JMP       0x00000034     Jump
+00000014:   940C0034    JMP       0x00000034     Jump
+00000016:   940C0034    JMP       0x00000034     Jump
+00000018:   940C0034    JMP       0x00000034     Jump
+0000001A:   940C0034    JMP       0x00000034     Jump
+0000001C:   940C0034    JMP       0x00000034     Jump
+0000001E:   940C0034    JMP       0x00000034     Jump
+00000020:   940C0034    JMP       0x00000034     Jump
+00000022:   940C0034    JMP       0x00000034     Jump
+00000024:   940C0034    JMP       0x00000034     Jump
+00000026:   940C0034    JMP       0x00000034     Jump
+00000028:   940C0034    JMP       0x00000034     Jump
+0000002A:   2411        CLR       R1             Clear Register
+0000002B:   BE1F        OUT       0x3F,R1        Out to I/O location
+0000002C:   E5CF        LDI       R28,0x5F       Load immediate
+0000002D:   E0D4        LDI       R29,0x04       Load immediate
+0000002E:   BFDE        OUT       0x3E,R29       Out to I/O location
+0000002F:   BFCD        OUT       0x3D,R28       Out to I/O location
+00000030:   940E0036    CALL      0x00000036     Call subroutine        <---- skok do main()
+00000032:   940C0039    JMP       0x00000039     Jump                    <---- skok do wyłączenia przerwań 
+00000034:   940C0000    JMP       0x00000000     Jump                    <--- skok do początku pamięci programu
@00000036: main
---- PROBY.c --------------------------------------------------------------------------------------
4:        { 
+00000036:   E080        LDI       R24,0x00       Load immediate       <--- początek main()
+00000037:   E090        LDI       R25,0x00       Load immediate
+00000038:   9508        RET                      Subroutine return        <---- return z programu w C
8:        } 
+00000039:   94F8        CLI                      Global Interrupt Disable
+0000003A:   CFFF        RJMP      PC-0x0000      Relative jump <----   skok do początku programu

Czyli robi dokładnie to co napisał marek1707:

Sterowanie oddawane jest z powrotem do "rozbiegówki" czyli fragmentu kodu który po resecie ustawia wskaźnik stosu, zeruje zmienne które mają być wyzerowane i wpisuje wartości do zmiennych statycznych którym wartości muszą być nadane przed startem funkcji main. ...

Ale czy to nie są właśnie maliny?

Ja uważam, że właśnie są to maliny w szczególności dla autora tematu któremu podpowiadam, ponieważ return 0 nie oznacza tego samego co RESET mikrokontrolera.

Nawet jeżeli przyjąć, że ktoś świadomie chce zresetować programowo mikrokontroler, to wykonanie Return 0 jest co najmniej niebezpieczne dlatego, że taki "restart" nie zeruje chociażby bitów w rejestrach przez co można się naciąć na nieprawidłowe działanie programu po takim restarcie.

Dlatego nadal podtrzymuję, że:

Reasumując: Return 0 w main() jest zabronione, podobnie jak każde inne Return

w szczególności dla początkującego w C, którym jest autor tematu.

Dodatkowa mniej istotna uwaga:

Taki kod powstaje w kompilatorze GCC, ale nie koniecznie w innym.

Pomijam już fakt, że autorowi tematu nie chodziło o to, by zaczynał program od nowa, tylko by zatrzymał się w miejscu tak jak robił to do tej pory w BASCOM.

-------------------------------

BTW: Wyznaję zasadę, że początkujący oczekuje minimum informacji bez zbędnych szczegółów, co zresztą pochwaliłeś:

Podziwiam Cię dondu za umiejętność ograniczenia się do niezbędnego minimum 🙂

tutaj: https://www.forbot.pl/forum/postlink/61253.htm#61253

Takim minimum dla początkującego jest zasada: Return 0 w main() jest zabronione, podobnie jak każde inne Return

BTW 2: Umieszczę tę zasadę na blogu 😉 w odpowiednim artykule.

Link do komentarza
Share on other sites

Czy

+0000003A:   CFFF        RJMP      PC-0x0000      Relative jump <----   skok do początku programu

nie jest raczej zapętleniem programu na adresie 3A? To chyba cos innego niż skok do początku programu? 😐

A jeszcze wcześniej piszesz:

Ale ponieważ wywołanie return 0 wprowadza mikrokontroler w maliny, to pętla nie jest wykonywana, a mikrokontroler zaczyna robić głupoty, skacząc do bliżej nieokreślonej części pamięci programu - sprawdź w symulatorze

To ja, jako zupełnie zielony pytam: jak to w końcu jest? Bo na moje niewprawione w asm oko Return 0 niczego tu nie psuje, ale mogę się mylić. 🙁

  • Lubię! 2
Link do komentarza
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.