U potrazi za tartufima AVR-a

Rasprava o AVR mikrokontrolerima, AVR projekti i drugo vezano za AVR...

Moderators: pedja089, stojke369, trax, InTheStillOfTheNight

User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" - TIMER (6-dio)

TCCR0B |= (1<<CS00) | (1<<CS01);
O ovoj liniji programa mislim da bi trebao napisati nešto teksta, jer do sada nisam govorio o MACRO naredbama, a TIMER sam pokrenuo upravo onako kako se to radi na pravi način.

Što je TCCR0B u programskom jeziku "C"?
Po standardu programskog jezika "C" TCCR0B ne znači apsolutno ništa. Ali ipak postoji linija programa koja daje odgovor na ovo pitanje.
#include <avr\io.h>
Ovu liniju programa obavezno moramo napisati u svim programima, a upravo ona sadrži adrese svih registara mikrokontrolera, pa kada vi napišete TCCR0B onda koristite MACRO naredbe avr biblioteke. Ova MACRO naredba u sebi sigurno sadrži adresu registra TCCR0B. Evo dio biblioteke koji tada koristimo:

Code: Select all

#define TCCR0B  _SFR_IO8 (0x25)

/* TCCR0B */
#define FOC0A   7
#define FOC0B   6
#define WGM02   3
#define CS02    2
#define CS01    1
#define CS00    0
Umjesto TCCR0B sigurno smo mogli napisati i _SFR_IO8(0x25)
_SFR_IO8() je također MACRO naredba koji možemo pronaći ako idemo dalje kopati po biblioteci, no u toj MACRO naredbi jasno vidimo da se radi o adresi 0x25.

U datasheetu page 345 na adresi 0x25 možemo pronaći upravo ovaj red:
0x25 (0x45) TCCR0B FOC0A FOC0B – – WGM02 CS02 CS01 CS00

@buco je napisao jedan post o registrima AVR-a te njihovim adresama, pa neću ulaziti u taj dio iz razloga jer u programskom jeziku C o tome ne moramo razmišljati.

Adresa registra nas niti ne zanima jer za to se brine biblioteka AVR-a te compiler, stoga gledano sa strane C programera nikada ne moramo znati adresu registra, nego jednostavno koristimo gotove MACRO naredbe. Ovo vredi za sve registre AVR-a.

Bitno je primjetiti da sam u konfiguraciji CS bitova koristio "ILI" operaciju ali sa dvije maske. Ali isto tako sam koristio MACRO naredbu prilikom guranja broja 1 u lijevo.
Mogao sam naredbu napisati i ovako:
TCCR0B |= (1<<0) | (1<<1);
Ali očito nisam. Zašto?
Zato jer znam da postoji MACRO naredba koja će mi program učiniti čitljivijim. Uvijek je bolje gurati broj 1 za CS00 iz razloga jer mene ne interesira o kojem bitu se točno radi, nego o kojoj funkcionalnosti se radi. Ako pogledamo ostale MACRO naredbe gore vidjeti ćemo da je CS00 zapravo 0, a CS01 broj 1.

00000001 guram u lijevo za CS00 ili "0" mjesta i dobijem 00000001
00000001 guram u lijevo za CS01 ili "1" mjesto i dobijem 00000010

Idemo ponovno izračunati ILI operaciju sa 2 makse:

(TCCR0B) 00000000 ILI
(1<<CS00) 00000001 ILI
(1<<CS01) 00000010 ILI
___________________
(TCCR0B) 00000011


Ovim načinom sigurno smo podigli 2 desna Bit-a u logički "1" i pokrenuli TIMER sa željenim prescalerom /64.

Pitanje iz prošlosti: Kako konfigurirati izlaz za PC0? Tamo je bila LED-ica ako se ne varam?
Odgovor:
DDRC |= (1<<PC0);
U postu u kojem sam objašnjavao kako se radi "ILI" operacija te pristupa jednom bitu namjerno sam koristio broj pa sam napisao DDRC |= (1<<0) kako bi razumjeli što točno radim.
Ja uvijek pišem sa korištenjem MACRO naredbe pa bi PCO za izlaz sigurno konfigurirao ovako:
DDRC |= (1<<PC0);, a ne ovako DDRC |= (1<<0);
U pomenutoj AVR biblioteci sigurno postoji ovakva linija koja nam dozvoljava da program pišemo nešto ljepše i čitljivije:
#define PC0 0

To be continued...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" - TIMER (7-dio)

Timer 0 koji napravi jedan krug svakih 2.048 ms još uvijek nam ne znači ništa i ne možemo trenutnu situaciju korisno iskoristiti za dobro praćenje vremena. Ovaj način rada u kojemu TIMER radi i po datasheetu se zove "NORMAL".
Način rada "Normal" na svaki pulse koji uđe u TIMER poveća "TCNT" registar za 1, te kad TIMER nabroji do 255 "Control Logic" koji je vidljiv u diagramu TIMER-a 0, resetira brojač te pomenuti ciklus kreće iznova.

Postoji razlog zašto sam za ovu priču odabrao 8 bitni TIMER 0. Najbolji i najsuperiorniji TIMER svakog AVR mikroračunala je svakako 16 bitni TIMER 1. Svaki početnik prvo bi trebao naučiti koristiti TIMER 0 i TIMER 2 jer su oni nešto jednostavniji sa manje mogućnosti podešavanja.

TIMER 0 može raditi na 8 različitih načina, a mi kao programeri sami odlučujemo koji način rada nam najviše odgovara. Ponovno se vraćam na datasheet page 103. Pri dnu stranice postoji tablica koja se zove: "Waveform generation mode bit description."
http://www.atmel.com/dyn/resources/prod ... oc2545.pdf
Image

Ako pogledamo u prvi red kada su svi WGM bitovi logička 0 onda vidimo da se način rada zove NORMAL, da je gornja vrijednost TIMER-a 255 ili 0xFF, ali nama ovaj način rada ne odgovara. Iskusni programer i ovaj način rada vrlo lako može prilagoditi 1mS vremena, ali ja ću pisati o najboljem načinu na koji ovo možemo napraviti.

Način rada TIMER-a 0 koji nam najviše odgovara je - "Clear timer on compare match (CTC) mode"
U principu mi samo moramo podesiti WGM bitove na sljedeće postavke:
WGM02 = 0
WGM01 = 1
WGM00 = 0


U datasheetu je svaki mod rada TIMER-a opisan detaljno, i kada god nešto zapne uvijek čitamo datasheet.
Ja ću pisati samo o suštinskom radu, i ne mislim prevoditi datasheet pošto u njemu piše apsolutno sve što se uopće moglo napisati o svemu. Svakako je dobro barem jedamput pročitati nekoliko stranica o CTC modu rada TIMER-a bez obzira što napišem o njemu.
Vraćam se ponovno na page 90 datasheeta, te na isti block diagram TIMER-a 0.

Normalni način rada TIMER-a nam ne odgovara jer TIMER uvijek broji do 0xFF ili 255, dakle TIMER uvijek pravi čitav krug, te za taj krug prođe točno 2.048 mS vremena. Kako mi želimo napraviti krug svake 1mS vremena očito TIMER nikada ne smije stići do 255. U CTC načinu rada TIMER će uvijek brojati samo do vrijednosti upisane u OCRnA registar.

Ako pogledamo block diagram TIMER-a vidimo da iz OCRnA registra postoji jedna isprekidana linija koja može postaviti drugačiju TOP ili gornju vrijednost TIMER-a. Suštinski gledano ovo je sve što nas zanima, jer ako promijenimo gornju vrijednost TIMER-a upravo smo TIMER natjerali da prije nabroji do naše nove gornje vrijednosti. U CTC načinu rada TIMER uvijek broji od 0 do vrijednosti upisane u OCR0A registar te se resetira i ciklus počinje iznova. To je svakako glavna osobina CTC načina rada, doduše sažeta u nekoliko rečenica.

Ono što prvo moram napraviti je postaviti TIMER u CTC način rada. Po WGM postavkama vidim da moram samo WGM01 Bit dići u logički "1". Ostali WGM bitovi su i tako logička nula i njih sigurno ne moram podešavati. Prvo što tražim je ime registra u kojemu se nalazi bit WGM01 i pronalazim ga u datasheet-u na stranici 101.
Image

Bitno je primjetiti da konfiguracija TIMER-a nije samo u jednom registru jer naprosto sve konfiguracije TIMER-a ne stanu u jedan 8 Bitni registar. Podešavanje CS "Clock Select" bitova bilo je u TCCR0B registru, dok podešavanje WGM bitova koje trebamo za promjenu moda rada su u TCCR0A registru. Također moramo primjetiti da u ovom registru nedostaje jedan konfiguracijski bit za mod rada, a to je WGM02, no za njega se ne moramo brinuti jer on se nalazi u TCCR0A registru, upravo onom gdje se nalaze i postavke CS bitova koje smo podesili, ali on je sigurno logička nula i tako treba i ostati.
Image

Nikada ne pamtimo u kojem se registru što nalazi jer to je apsurdno zamaranje mozga. Neki programeri često koriste gotove alate u kojima podešavaju Timer, ali ja to namjerno izbjegavam kako bih svaki puta ponovio podešavanje TIMER-a i održavao se na nivou datasheet-a. Ako netko drugi za mene podesi TIMER, onda kroz neko vrijeme gubim tu naviku čitanja datasheeta a to svakako nije dobro.

Mod rada CTC za TIMER 0 očito je jednostavno podesiti iz C-a. Ako se radi o TCCR0A registru i bitu WGM01 onda linija C-a izgleda ovako:
TCCR0A |= (1<<WGM01);

Mogao sam to napisati i ovako:
TCCR0A |= (1<<1); ali onda moram znati točnu poziciju ovog bita u registru, pa ponovno ponavljam da je jednostavije uvijek gurati u lijevo broj 1 za MACRO naredbu koju nam poklanja biblioteka.

Problematičnije pitanje je kamo sa ovom naredbom?
Ide li ona prije konfiguracije CS bitova ili poslje?

Već sam rekao da će TIMER odmah početi brojati kada konfigurirate CS bitove, tj kada prespojite TIMER na neki prescaler, stoga lako je zaključiti kako mi to želimo napraviti zadnje. U praksi TIMER uvijek pokrećemo nakon čitave konfiguracije tako da će konfiguracija CS bitova biti na zadnjem mjestu.

Do sada naša konfiguracija TIMER-a 0 izgleda ovako:

Code: Select all

#include <avr\io.h>                                                

int main(void){

   TCCR0A |= (1<<WGM01);        
   TCCR0B |= (1<<CS00) | (1<<CS01);   
   
   while(1);                           
}
Timer smo sigurno postavili u željeni CTC mod rada, te smo ga sigurno pokrenuli sa željenom frekvencijom. Sve što još moramo napraviti je izračunati koju vrijednost upisati u OCR0A registar kako bi to bila gornja granica našeg TIMER-a. Ta gornja granica mora nam osigurati 1mS vremena za 1 krug TIMER-a i svakako smo sve bliže cilju o kojem pišem već nekoliko postova.

Još nešto sam zaboravio davno napisati. Pošto se ja pomalo snalazim u TIMER-ima uvijek pišem imena registara jer ih znam napamet, ali za početnika ovo može biti zbunjujuće, pa ću napisati sve registre koje spominjem u ovom tekstu:
OCR0A -> U CTC modu rada definira gornju vrijednost TIMER-a
TCCR0A -> Kontrolni registar Timera u kojemu konfiguriram mod rada TIMER-a (WGM)
TCCR0B -> Kontrolni registar Timera u kojem konfiguriram prescaler TIMER-a (CS)
TCNT0 -> Registar koji se povećava na svaki pulse koji uđe u TIMER. Ovaj registar je uvijek brojač u svim načinima rada, a u našoj konfiguraciji znamo da se ovaj registar povećava svakih 8uS jer smo TIMER podesili na frekvenciju 125 khz sa prescalera.

To be continued...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" - TIMER (8-dio)

Možda pomislite da sam davež sa istim postovima, ali kako ne pišem često, opet ću računati sve iznova, na malo brži način kako bi bolje opisao situaciju sa trenutnim postavkama. Cilj je identičan a to je sat koji nam pokazuje vrijeme sa korakom od 1mS. Ono što prvo tražim uvijek je prescaler koji mi neće napraviti krug prije nego li prođe 1 mS vremena. Taj sam našao po proračunu prescalera 8 Mhz / 64 = 125 khz, period jednog pulsa je 8 uS, a TIMER će napraviti krug za 2.048 mS. Pogledam u datasheet te vidim točno koje CS bitove u kojem registru moram podići i napišem prvu liniju programa:
TCCR0B |= (1<<CS00) | (1<<CS01);
No kako mi 2.048 mS ne odgovara moram promjeniti mod rada TIMER-a te ga ubacujem u CTC mod rada, tj mod u kojemu mogu definirati novu gornju granicu TIMER-a upisom vrijednosti u OCR0A registar.
Pogledam u datasheet postavke za CTC mod rada i pronađem koje WGM bitove u kojem registru moram postaviti u logički "1".
TCCR0A |= (1<<WGM01);

Što upisati u OCR0A registar?
Već sam rekao kako je u CTC modu rada gornja vrijednost do koje će TIMER brojati biti vrijednost OCR0A registra o kojem naravno sve detaljno piše u datasheetu. Možda je bolje pitanje: " Koliko nam treba impulsa sa prescalera da prođe vrijeme od 1mS?" Izračunati je jednostavno: Ako je jedam impuls 8uS onda metodom zvanom dijeljenje podjelim 1mS sa vremenom jednog impulsa:
1ms / 0.008ms = 125.
Dakle nakon 125 impulsa sa prescalera proći će točno 1mS vremena.
Netko bi pomislio kako u OCR0A registar treba upisati 125, ali to nije točno.

TIMER broji od 0, a kad dođe na vrijednost 1 proći će 8uS, kada dođe na vrijednost 2 proći će 16uS... Ako računamo vrijeme sve do vrijednosti 125 doći ćemo do zaključka kako će proći točno 1mS u trenutku kada TIMER dođe na vrijednost 125.
A gdje je tu onda greška?
Greška je u tome što će TIMER biti na vrijednosti 125 točno 8 uS vremena, dakle odraditi će i taj impulse, a tek tada će se resetirati na vrijednost 0. Ako dodamo tih 8 uS kada TIMER bude odbrojavao zadnji impuls prije reseta dobijemo točno 1.008 mS.
Mislim da i u datasheetu postoji nekakva formula za ovo, a i tamo u formuli postoji broj (-1), pa onda sigurno moramo brojati do vrijednosti 124, a ne 125 kako bi imali točno 1 mS nakon svakog kruga TIMER-a.
Po ovom proračunu ostala je jednostavna naredba:
OCR0A = 124;

A kamo sad s tim? Prije konfiguracije WGM bitova, ili možda prije konfiguracije CS bitova?
Postoji samo jedno pravilo, a to je da pravila nema. U registre TIMER-a moguće je bilo kad upisati bilo što, ali tada morate savršeno poznavati TIMER. S ovim o čemu trenutno govorim nema nikakvih problema da nešto neće raditi, ali TIMER i dalje ima 8 načina rada, pa kada ga koristimo za nešto drugo ipak postoje nekakva pravila kojih se moramo držati.
Ovakvu konfiguraciju TIMER-a u CTC načinu rada koju gotovo uvijek morate raditi, bez obzira što programirali najbolje je napraviti dok je TIMER zaustavljen, pa brzo trik pitanje: Kad je TIMER zaustavljen, ili bolje pitanje kada je TIMER pokrenut? I to sam već naveo, ali evo ponovno: TIMER počinje brojati u trenutku kada konfiguriramo CS bitove "CLOCK SELECT", a tim bitovima odmah spojimo TIMER na prescaler.
U trenutnoj konfiguraciji nebitan je redosljed naredbi, ali čisto realno razmišljanje bilo bi: Konfiguriraj pa onda pokreni TIMER. Dakle naredba konfiguracije prescalera tj CS bitova biti će na kraju.

Code: Select all

#include <avr\io.h>

int main(void){
    
    TCCR0A |= (1<<WGM01);              // Config TIMER0 CTC mode      
    
    OCR0A = 124;                       // Set new TOP value -> (124+1)*8uS = 1mS
    
    TCCR0B |= (1<<CS00) | (1<<CS01);   // Start TIMER from prescaler /64 = 125 khz 

    while(1);                          // do nothing
}
Ovo je na neki način kraj konfiguracije samog TIMER-a. U samo 3 linije programa TIMER će sigurno napraviti krug svake 1mS vremena. Samim mukama nije kraj, jer što će nam sat u koji ne znamo pogledati?

Ovo "gledanje" u sat usudim se definirati kao rat C-a i assemblera.
Da bi pogledali u naš sat morati ćemo uvesti ponovno dosta pojmova u ovu priču, a onaj najteži i najkompliciraniji je ISR.

U ovoj situaciji moram priznati da je razumjevanje assemblera neophodno da bi pravilno programirali u C-u. Dosta programera iz C-a koristi ISR, ali često ih ubiju bugovi koji nastaju upravo zato jer ne razumiju assembler, niti znaju što se dogodi sa njihovim programom. Sa pravilnim razumjevanjem onoga što se događa vrlo lako možemo iz C-a predvidjeti programersku grešku višeg jezika prije nego li se ona dogodi. U ovom poglavlju ISR-a objasniti ću sve meni poznate propuste programera koji razmišljaju na način C-a, a ne razumiju stvarnost assemblera.

To be continued...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" - "MCU TIME (1-dio)"

Rekao sam da je TIMER naš SAT, ali gledano sa konačne programerske strane to nije sasvim točno. Točno je, ali istovremeno i nije. Mi možemo iz programa gledati stalno u registar TCNT TIMER-a 0 i računati koliko vremena prolazi, ali TIMER se svake 1mS resetira na 0. Dakle sve što mi možemo pogledati je vrijeme između 0mS i 1mS. (Timer broji impulse sa oscilatora, a mi uvijek možemo pročitati njegov registar koji se povećava svakih 8uS (TCNT)).

Što ako mi možemo svaki puta kada TIMER napravi jedan krug povećati varijablu u RAM memoriji? Nije li to puno bolji sat za potrebe realnog vremena? Druga komponenta ovog sata biti će u RAM memoriji, a na svaki krug TIMER-a povećavati ćemo varijablu RAM memorije.

Što to točno znači?
Ako TIMER napravi krug 1000 puta, varijabla u RAM memoriji povećati će se za 1000.

A što sad ovo znači?
Proći će točno 1 sekunda vremena kad se neka varijabla u RAM memoriji poveća za 1000.
(1mS * 1000 = 1S).

Znam da je početnicima teško objasniti zašto se ne koristi jednostavni DELAY nego se koristi ovakva metoda. Možda je najbolje objašnjenje jedna igra:

Kako blinkati LED-icom svake 1 sekunde, a istovremeno paliti i gasiti bilo koji drugi port MCU-a najbržom brzinom kojom to AVR može napraviti. Pobjednik je onaj tko dobro blinka LED diodom svake sekunde i ima brže vrijeme promjene stanja na nekom drugom portu. (Slika se osciloskopom).

U trenutku kad napišete _DELAY_ upravo uništavate brzinu promjene nekog porta, a i dalje morate blinkati svake 1S LED diodom. Ako se u ovoj ludoj igri natječe C i assembler, ipak će assembler biti brži na promjeni stanja tog porta. Ako se natječemo Buco i ja u istoj toj igri vjerojatno će biti brži onaj koji ima malo brži oscilator, dakle programerski smo isti. (Ovo bih pisao u asm).

Kada program pišete tako da TIMER za vas broji vrijeme, te na neki način povećava varijablu u RAM memoriji, nikada ne morate čekati i sve slobodno vrijeme posvećujete promjeni stanja nekog porta.

Ova igra je suština svakog napisanog programa. Bez obzira koliko program imao linija vi u svakom trenutku morate moći promijeniti stanje bilo kojeg porta jaako velikom brzinom (reda veličine u uS), i u takvom programu ne smije postojati niti jedan DELAY, jer vam bilo kakvo čekanje uništava čitav program.

Što imamo od ovoga?
Ako smo u stanju promijeniti neki PORT svakih 100 uS, znači li to da mi zapravo ništa ne radimo?
U pravilu DA. Naši programi nikada ništa ne rade, a i kada rade onda to rade brzo, jako brzo. Toliko brzo vrtimo program da izvršimo i 10000 puta isti program, a ništa ne odradimo jer TIMER još nije nabrojao dovoljno vremena koje treba proći da bi mi nešto napravili.

Što nam to dozvoljava?
To nam dozvoljava da napišemo 50 različitih funkcija i programa u samo jednom mikrokontroleru. Svaki program je u svojoj datoteci, svaki program radi svoj dio posla i svoju funkcionalnost, a opet kad ih sve izvršavamo velikom brzinom korisniku se čini da se radi o samo jednom programu, kompliciranom programu u realnom vremenu.

To be continued...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" "Stil pisanja programa"

Većina programera uvijek trpa program kako stigne i dobiva programski kupus, ali kod tartufa to ne može biti slučaj. Svaki program pisati ćemo na pravi način, komentirati na pravi način, te strukturirati, filtrirati i ne znam što više... Svaki napisan program mora imati svoj stil pisanja, prepoznatljivu razliku funkcija, varijabli, MACRO naredbi te komentara.

Ako pogledate moj zadnji objavljen program možete vidjeti da sam pisao proklete komentare gotovo na svakoj liniji programa. Ovu naviku komentiranja programa većina programera uopće nema jer misle da znaju sve što su napisali. Gadno se varaju jer i sam sam se opekao puno puta, a isto tako i spasio puno puta. Danas ne znam programirati bez komentara, zaboravio sam kako se to radi, pogotovo ako pišem programe preko 10 000 linija teksta. U takvim programima ljudski mozak je jednostavno "malen" da bi znali što ste razmišljali na kojoj liniji programa.

Uvijek se trudimo programirati najbolje što znamo, ali ipak često dođemo u situaciju da "prljavo" napišemo nekoliko linija programa, da ih napišemo pomalo nestručno. Ponekad dolazimo u situaciju da iskoristimo neku metodu koja nije standardna u programiranju samo da bi postigli u toj situaciji željeni rezultat.
Problem nije u tom danu programiranja, nego tek nakon mjesec dana kada ponovno otvorimo isti program, ili otvorimo tu liniju programa. Nakon mjesec dana zaboraviti ćemo razlog zbog kojeg smo nešto napisali prljavo i nestručno, a ako tada nemamo komentar onda smo u pravim problemima.

U tartufima ću pisati svoj stil programa, iako on ne mora biti ispravan i točan. Svaki programer ima svoju metodu programiranja, svoje načine pisanja programa i eventualno komentiranja programa i svi su u pravu.
Moj stil programiranja razlikuje se od stila ATMEL-ovim programera u dosta stvari, ali ipak postoji planina programa koji su pisani slično načinu mog programiranja.

Naš TIMER morati će povećavati varijablu u RAM memoriji, ali da bi uopće napravio varijablu moram nešto napisati i o tom stilu programiranja:

Sve moje varijable isključivo su pisane malim slovima, a ako se sastoje od 2 riječi onda su odvojene sa "_";
my_variable;
temp_counter;
i;
j;
pero;
mato;
jurica;


Sve moje funkcije uvijek su pisanje prvim velikim slovom, a ako se sastoje od 2 riječi onda svaka riječ počinje novim velikim slovom:
GetMyPosition();
SetPosition();
TestUart();
LcdRefresh();

Ovo je malo teže čitati, ali kada se naviknete onda je normalno i logično.

Svi MACRO-i konfiguracije programa pisani su velikim slovima:
END_POSITION;
START_ADDRESS;
MAX_USERS
EEPROM_SIZE


Svi MACRO-i koji nisu u konfiguracijskom file-u programa pisani su kao i funkcija:
RelayOn();
RelayOff();
InputState();
ConfigTimer();


Od čitavog stila programiranja trenutno nas zanimaju varijable u RAM memoriji jer tamo moramo imati lokaciju koju će TIMER povećavati svake 1 mS.
U mojem stilu programiranja postoji još jedno pravilo, a to je da svaka varijabla koja ima veze sa vremenom 1 mS ispred imena mora imati "t_"

Ovo mi uvijek donosi dobro raspoznavanje varijabla, funkcija i svega što se nalazi u programu. Ako se bilo koja moja varijabla zove recimo t_relay, t_application, t_neki_vrag, onda znam da se radi o varijabli koju će mi TIMER povećavati svakih 1mS, i odmah znam da se radi o mom satu o kojem pišem već dugo vremena.

Ako mi želimo blinkati LED diodom tada bi bilo logično da se naša varijabla u RAM memoriji zove:
t_led;

To be continued...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" "Leglo Bugova" (1-dio)

Koliko sam puta samo čuo jednu te istu priču ili više njih:
1. Program mi "tu i tamo" poludi
2. Resetira mi se, a sve sam dobro napisao
3. MCU je loš i ne radi po programu

Nemojte živjeti u zabludi, svaki MCU je fantastično računalo i nevjerojatno dobro radi.
Vjerovali ili ne on radi upravo ono što mu vi kažete. U tome i je problem svih problema. Kada nešto ne radi kako ste zamislili onda je problem u vama, a ne mikrokontroleru.

Programiranje u višim jezicima tipa c ili bascom sa sobom nosi 2 najveća programerska problema...
1. AVR je 8 bitno računalo
2. Optimizator programa stvarno optimizira program (nije šala)


Ove 2 stavke su jako teške za objaniti i jednostavno nema načina da ih napišem u jednom postu, pa ću vas programski uvesti u "Leglo Bugova", te problematiku objasniti na razini assemblera jer je to jedini način.

Ime varijable smo smislili, dakle t_led, ali još moramo vidjeti koji tip varijable nam najviše odgovara. U AVR Studiu i WinAVR-u implementirani su sljedeći tipovi varijabla:
[signed | unsigned] char : 8 bits
[signed | unsigned] short [int] : 16 bits
[signed | unsigned] int: 16 bits
[signed | unsigned] long [int] : 32 bits
[signed | unsigned] long long [int] : 64 bits
float : 32 bit
double : 32 bit


Puno nečega ali ću odmah odabrati tip varijable koji želim koristiti:
"unsigned int" - 16 bits
unsigned - "Varijabla sadrži isključivo pozitivne brojeve"
int - "Varijabla zauzima 2 Byte-a (16 bits) u RAM memoriji"

Koja je najveća vrijednost naše varijable?
BIN(11111111 11111111)
HEX(FFFF)
DEC(65535)


Ako želimo blinkati LED-icom svakih 1 sekunde tada naša varijabla u RAM-u mora moći pohraniti vrijednost 1000, jer će ju toliko puta morati povećati TIMER kako bi prošla jedna sekunda vremena. Doseg varijable tipa unsigned int je [0-65535] što znači da je TIMER može povećati maksimalno 65536 puta prije nego li varijabla opet dođe na 0, a za to će mu trebati dobrih 65,536 sekundi.

Ime varijable pisano je malim slovima, počinje sa t_ što nas treba stalno bosti u glavu kako će TIMER povećavati varijablu svake 1mS i bez obzira na našu simbolički divnu varijablu napraviti ćemo prvi BUG u do sad napisanom programu: (2. Optimizator programa stvarno optimizira program (nije šala))

Code: Select all

#include <avr\io.h>

unsigned int t_led;                    // RAM variable used for led_blink timer

int main(void){
   
    TCCR0A |= (1<<WGM01);              // Config TIMER0 CTC mode     
   
    OCR0A = 124;                       // Set new TOP value -> (124+1)*8uS = 1mS
   
    TCCR0B |= (1<<CS00) | (1<<CS01);   // Start TIMER from prescaler /64 = 125 khz

    while(1);                          // do nothing
}
Oni napredni sigurno slute da ću napraviti ISR TIMER-a 0 koji će povećavati t_led, glavni program će pratiti t_led, ali program neće raditi ono što bi trebao... Tko god zna razloge zašto ne radi slobodno neka pošalje u PM i biti ću zadivljen... :D

To be continued...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" "Kad logika kaže NE!"

Ljudski mozak očito razmišlja totalno drugačije od AVR-a, barem onaj koji pokušava programirati... Svaki početnički program u pravilu završava nakon nekoliko linija programa te konačnim detaljem: »šah-mat«. (većinom zbog DELAY-a i gubljenja vremena na stvaranju DELAY-a)
Čest problem početnika je onaj "Nemam vremena, kasnim..." Problem je samo u tome što svaka jezgra izvršava samo jednu instrukciju u nekom vremenu, a mi bi željeli 100 funkcionalnosti u istom tom vremenu.

Prije nego što zaključimo kako nemamo vremena u programu trebali bi malo računati:
ATMEGA88 ima 8192 Byte-a FLASH memorije u koju može pohraniti maximalno 4096 "1 cycle" instrukcija.
Ako su sve instrukcije "1 cycle" onda vrlo lako možemo izračunati koliko vremena treba AVR jezgri da izvrši sve instrukcije iz FLASH memorije (4096):
ATMEGA88 može raditi na 20 Mhz, pa mu po tome za jednu "one cycle" instrukciju treba točno:
1/20 000 000 = 50nS.
Po tome AVR jezgra može izvršiti sve instrukcije iz FLASH-a za 4096*50nS = 204,8 uS

Logično je dakle da AVR jezgra može izvršiti čitav program FLASH-a (8192 Byte-a) u samo 205 uS, ali ono što je nelogično u toj priči je činjenica da pišete isti taj program za FLASH i dolazite do istog problema:
"Nemate vremena za programe koji trebaju raditi u realnom vremenu (1 sekunda, 1 minuta, 1 sat)".
Očito se razlikuje razmišljanje čovjeka u realnom vremenu i rada računala u nano Sekundama (nS).
Početnici bi često željeli napisati "WAIT 2 sata", ali moram ih razočarati...

Kako mi ne možemo prilagoditi računalo našem realnom vremenu, očito moramo naše realno vrijeme prilagoditi računalu. Sve programe pišemo u nS, uS, mS, a realno vrijeme uvijek opisujemo varijablama koje se povećavaju, povećavaju, povećavaju i dođu do vrijednosti koja opisuje realno vrijeme. To je osnovna logika svakog programa u realnom vremenu.

Sva potreba za realnim vremenom u svim računalima, operacijskim sustavima, uvijek se radi povećavanjem varijable u RAM memoriji, osim u situaciji kada je čekanje toliko malo da nemamo vremena povećavati RAM onda radimo assemblerski "NOP (No Operation)", tj izvršavamo instukciju balkan jezika "NIŠTA" i kad izvršimo "NIŠTA" za AVR jezgru proći će vrijeme izvršavanja instrukcije "NIŠTA", a instrukcija "NIŠTA" treba 50uS (20 Mhz clock) vremena da se izvrši. (Najljenija assemblersa instrukcija, valjda zato jer ne radi ništa, osim što će proći nešto vremena da se instrukcija izvrši).

Istom logikom povečavanja varijable RAM-a pišem program za blinkanje LED-icom u realnom vremenu. Moj TIMER će stalno povećavati varijablu, a moj program će promijeniti stanje LED-a tek kad RAM varijabla bude na vrijednosti 1000.

Na debugu AVR studia napravio sam mjerenje vremena konfiguracije TIMER-a 0 (Program u prošlom postu):
AVR jezgra treba oko 1.5 uS na 8 Mhz da konfigurira i pokrene TIMER... Prema tome na 20 Mhz AVR jezgra je u stanju konfigurirati i pokrenuti TIMER za manje od 1uS. U realnom vremenu usudimo se reći da je TIMER pokrenut "ODMAH".

Možda glupa teza je zamisliti u našem programu _delay_ms(1) naredbu, jer dok se izvrši taj "delay" mogli smo barem 1000 puta podesiti i pokrenuti TIMER, a nezamislivo je usporediti koliko smo mogli korisnog posla napraviti u tih 1mS vremena.
Već 100 puta pišem istu stvar -> "Učite programirati bez DELAY naredbe"

Kako za blinkanje LED-om moramo programirati u realnom vremenu (blinkati LED-om svake sekunde), a istovremeno ne smijemo koristiti DELAY podesili smo TIMER da za nas napravi krug svake 1mS. Isto tako smo definirali i varijablu u RAM memoriji koju će povećavati TIMER svaki krug kako bi pratili realno vrijeme sa korakom od 1mS.

U trenutnoj situaciji kada TIMER mora povećati varijablu RAM memorije dolazimo u sasvim novo poglavlje mikrokontrolera. Nažalost opet HARDWARE i assembler te u tartufe moramo uvesti nove pojmove:

1. ISR
2. ISR_VECTOR
3. STACK
4. STACK_POINTER
5. I bit SREG-a
6. TIMSK

Možda ovi pojmovi i način programiranja izgleda komplicirano, ali nije. Kada napišemo nekoliko programa na ovaj način vrlo lako možemo shvatiti poantu. Svi ovi pojmovi nisu pojmovi samo TIMER-a. Radi se o pojmovima koje ćemo koristiti gotovo u svim programima. Sve je to ista priča u svim programima i svim mikrokontrolerima. Da sam za prvi program odabrao ispis "HELLO" preko UART-a opet bi morao pisati o ISR-u, varijablama RAM-a, I bitu SREG-a... Zato se početnici ne trebaju plašiti novih pojmova, jer svaki novi pojam vredi za sve mikrokontrolere...

Što se tiče osnovna 2 programerska buga koja ću napraviti jedino @Kizo je znao BUG koji sam već napravio, i osmah naslutio drugi koji ću napraviti, te rekao načine kako izbjeći osnovne bugove programiranja... Svaka čast @kizo. To samo govori o tome koliko dobro razumiješ prevodilac, assembler i mikrokontorler za kojeg pišeš program...

To be continued...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" - ISR

Prije ISR-a još nekoliko sitnica:
Rekli smo kako svaki napisan program u C-u uvijek počinje sa funkcijom "main", ali još trebamo odvojiti 2 bitne stvari oko "main-a":
TIMER, portovi i ostale "drangule" u programu moramo podesiti samo jedamput, dok "glavni" program moramo vrtiti beskonačno u jednoj petlji. Zato u mikrokontrolerima često govorimo o "inicijalizacijskom dijelu programa" i glavnom "loop-u".

Inicijalizacijski dio programa uvijek podešava sve registre, TIMER-E, Hardware MCU-a, čita EEPROM, postavke za startanje MCU-a ili bilo što drugo što nam treba za nekakav program koji pišemo.

Npr, ako želimo blinkati LED-om u inicijalizacijskom dijelu programa moramo postaviti port kao izlaz, konfigurirati TIMER u CTC modu (WGM bitovi), pokrenuti TIMER (CS Bitovi), uključiti TIMER-ov prekid (TIMSK registar), uključiti globalne prekide. (I BIT u SREG-u).
Kada konfiguriramo registre, te odradimo dio posla koji je potrebno odraditi samo jedamput tada program zavrtimo u bilo kojoj beskonačnoj petlji.

Ovo je već imaginacija, jer inicijalizacijski dio i glavni program uopće ne postoje. MCU ih ne poznaje kao takve, niti ih programski jezik C poznaje kao takve, pa kad govorimo o ovim terminima zapravo govorimo o programu koji pišemo prije glavnog "loop-a" i zato ga nazivamo inicijalizacijskim. Jedina razlika između ovih termina je to što smo inicijalizacijski dio izvršili samo jedamput, a glavni loop izvršavamo beskonačno iznova i iznova.

U gornjem programu C-a možemo vidjeti "while(1);", a to u C-u znači da će se MCU zavrtiti na toj naredbi, tj više neće raditi ništa programski korisno. Taj dio ću odmah promijeniti u programu da jasno možemo raspoznati glavni program i inicijalizacijski dio:

Code: Select all

#include <avr\io.h>

unsigned int t_led;                    // RAM variable used for led_blink timer

int main(void){
    
    TCCR0A |= (1<<WGM01);              // Config TIMER0 CTC mode     
    OCR0A = 124;                       // Set new TOP value -> (124+1)*8uS = 1mS
    TCCR0B |= (1<<CS00) | (1<<CS01);   // Start TIMER from prescaler /64 = 125 khz

    while(1){
       // main loop
    }     
}                     
TIMER kao dio hardware-a nema mogućnost da samostalno upravlja RAM memorijom, te povećava neku željenu varijablu (u našem slučaju t_led), ali ipak ima jednu moć, kao i većina AVR periferije, a to je da pozove "prekid" eng. "interrupt".

Pošto je iz glavnog loop-a suludo gledati u TIMER te pratiti svaki krug koji će TIMER napraviti, nastao je pojam "interrupt". Mi možemo TIMER-u dozvoliti da svaki krug koji napravi pozove prekid programa. Bez obzira na naš program koji će se vrtiti najvećom brzinom u while(1) petlji TIMER će svake 1mS morati prekinuti naš glavni program i skočiti na ISR.

"ISR" eng: "Interrupt service routine", balkan: "Prekidna servisna rutina"
Sama riječ "rutina" govori o tome da je to zapravo dio programa u FLASH-u koji se nalazi na "nekoj" adresi. Možemo odabrati bilo koju adresu u FLASH memoriji i reći: "Ovo je ISR" i gotovo smo u pravu.
Zašto bi se onda neki dio FLASH programa nazivao ISR, a drugi dio programa nazivao samo "program", kad se i tako radi o programu koji se nalazi u FLASH memoriji?
Prekidna rutina (ISR) ne izvršava se u glavnom loop-u nego je poziva naš TIMER svaki krug (1ms).
Pozivanje ISR-a je na razini hardware-a MCU-a, i ovaj dio morati ćemo razraditi do detalja jer bez toga je suludo pokušavati koristiti prekide i prekidne rutine.

Da bi uopće mogli koristiti prekid, najprije moramo uključiti I-BIT SREG-a. Za sada o SREG-u trebate znati da je to registar AVR-a. Ako je "I" Bit isključen, tj log "0" ne može se dogoditi niti jedan prekid programa. Postoje 2 assemblerske instrukcije koje upravljaju ovim bitom u STATUS REGISTRU (SREG), a zovu se "sei" i "cli"
sei - Global interrupt Enable (SET I)
cli - Global interrupt Disable (CLEAR I)


Da bi uključili interrupte u C-u moramo napisati 2 linije programa. Prvom govorimo compileru da koristimo AVR biblioteku interrupt.h, a drugom naredbom jednostavno pozovemo dio te biblioteke koji uključuje I BIT u SEG-u, tj omogućava globalne interrupte.
#include <avr\interrupt.h>
sei();


U programu to izgleda ovako:

Code: Select all

#include <avr\io.h>
#include <avr\interrupt.h>

unsigned int t_led;                    // RAM variable used for led_blink timer

int main(void){
    
    TCCR0A |= (1<<WGM01);              // Config TIMER0 CTC mode     
    OCR0A = 124;                       // Set new TOP value -> (124+1)*8uS = 1mS
    TCCR0B |= (1<<CS00) | (1<<CS01);   // Start TIMER from prescaler /64 = 125 khz
    sei();                             // Enable Global Interrupts

    while(1){
       // main loop
    }     
}                     
To be continued...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" - ISR

Konfiguracija prekida i prekidne rutine TIMER-a koji služi za pračenje vremena gotovo uvijek se radi u inisijalizacijskom djelu programa, dakle prije while(1).

Razlog tome je jednostavan pošto TIMER i njegov prekid moramo podesiti samo jedamput i zato to radimo u inicijalizacijskom kodu. U kompleksnijim projektima često možemo gasiti TIMER, iznova ga konfigurirati, mijenjati način rada ili frekvenciju ovisno o onome što radimo. Jednostavan primjer bi bio recimo generator PWM signala sa širokim opsegom frekvencije. Ako želimo širok opseg frekvencije onda moramo mijenjati TIMER-ov prescaler (CS bitovi) ovisno o željenom izlaznom PWM-u. Ovo govorim samo iz razloga jer TIMER ne mora "nužno" biti podešen u inicijalizacijskom programu i možemo ga podešavati kada hoćemo i odakle hoćemo. Ovo je svakako još jedan primjer kako inizijalizacijski program i glavni loop za računalo ne postoje.

Da bi uopće koristili interrupt nije dovoljno samo podići I bit SREG-a (sei()). Taj bit je glavni za bilo koji prekid, ali TIMER nije jedini koje može pozvati prekid.
Prekid programa kod ATMEGA88 MCU-a može pozvati ukupno 25 izvora što znači da ATMEGA88 može imati 25 prekidnih rutina. Nakon reseta MCU-a svih 25 prekida je isključeno, te je isključen i globalni I BIT SREG-a. Da bi se mogao dogoditi prekid TIMER-a 0 kada nabroji do OCR0A registra očito uz glavni I bit moramo uključiti i odgovarajući prekid za TIMER 0. Svi ostali (24) moraju ostati isključeni jer ih ne koristimo, a kasnije ćete vidjeti i zašto moraju biti isključeni.

Upoznajmo se dakle sa još jednim registrom TIMER-a u kojemu se ne nalazi ništa više nego samo bitovi uključenih i isključenih prekida TIMER-a 0:
TIMSK0 - Timer/Counter Interrupt Mask Register

Ako pogledate u datasheet page 109 možete vidjeti da ovaj registar koristi samo 3 desna bita.
TOIE0 - Timer/Counter0 Overflow Interrupt Enable
OCIE0A - OCIE0A: Timer/Counter0 Output Compare Match A Interrupt Enable
OCIE0B - OCIE0B: Timer/Counter Output Compare Match B Interrupt Enable

Trebalo bi biti jasno kao dan da TIMER0 može pozvati samo 3 prekidne rutine a one su:
Kada TIMER napravi overflow (resetira se sa 255 na 0)
Kada TIMER bude jednak OCR0A registru
Kada TIMER bude jednak OCR0B registru

Koji od tih mi trebamo uključiti?
Ako smo TIMER konfigurirali da broji samo do OCR0A registra (CTC način rada) i tada se resetira na 0, te prođe točno 1mS vremena onda je očito da moramo uključiti srednji bit od ova 3, dakle OCIE0A jer želimo da TIMER pozove prekid svaki puta kada nabroji do OCR0A registra (1mS).

U programu to izgleda jednostavno. Opet koristimo samo ime registra, macro OCIE0A. ILI operaciju i ono šiftanje broja 1 u lijevo.
TIMSK0 |= (1<<OCIE0A);

Sad se samo postavlja pitanje kamo s tim?
Kamo god želite. Pa čak i da to stavite u while(1) petlju sve bi super radilo.
Ako pogledate start MCU-a kada su svi registri na 0, onda je lako zaključiti kako će se prvi prekid dogoditi tek nakon 1mS vremena jer TIMER počinje brojati od 0. Sama inicijalizacija TIMER-a traje svega nekoliko uS, a prvi prekid se događa tek nakon 1mS vremena a tada će naš program sigurno biti u while(1) pelji.
No zašto kažem da ga možete staviti i u while(1). Pa zato što bi sigurno i tamo uključili prekid, ali bi to radili uzaludno stalno i bez veze bacali korisno vrijeme na nečemu što smo već napravili.
Ovo svakako ubacujemo u inicijalizaciji TIMER-a, recimo prije sei(); instrukije, pa novi program izgleda ovako:

Code: Select all

#include <avr\io.h>
#include <avr\interrupt.h>

unsigned int t_led;                    // RAM variable used for led_blink timer

int main(void){
    
    TCCR0A |= (1<<WGM01);              // Config TIMER0 CTC mode     
    OCR0A = 124;                       // Set new TOP value -> (124+1)*8uS = 1mS
    TCCR0B |= (1<<CS00) | (1<<CS01);   // Start TIMER from prescaler /64 = 125 khz
    TIMSK0 |= (1<<OCIE0A);             // Enable Output Compare Match A Interrupt
    sei();                             // Enable Global Interrupts

    while(1){
       // main loop
    }     
}      
To be continued...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" - ISR

U ovom programu još uvijek nedostaje ISR. (Prekidna servisna rutina). Jasno je kako smo mi uključili globalne prekide (I Bit SREG-a), te smo uključili i prekid TIMERA kada nabroji do OCR0A vrijednosti.

Za bilo koji prekid AVR-a potrebno je uključiti samo ove 2 stvari, dakle globalni I bit SREG-a i interrupt koji želimo omogućiti (u našem slučaju prekid TIMER-a 0).
Svakako trebamo primjetiti da se prekid neće dogoditi ukoliko ne pokrenemo TIMER (CS Bitovi), jer u toj situaciji TIMER će biti čvrsto na vrijednosti 0, neće brojati, pa tako nikada neće ni nabrojati do OCR0A registra.

U našoj konfiguraciji prekida sve smo super napravili, odabrali smo mod rada CTC (WGM Bitovi), podesili godnju vrijednost TIMER-a u OCR0A registru, pokrenuli smo TIMER (CS Bitovi), Omogućili smo prekid TIMER-a kada dođe do OCR0A registra (TIMSK reg), te smo omogućili globalne prekide (I BIT SREG-a).

No postoji jedan problem: Što kad se interrupt stvarno dogodi???
U jednom od davnih postova "Bitka kod HEX-a, Juriš na assembler" napisao sam palenje LED-a u assembleru, te palenje LED-a u C-u. Jedan od tih programa je drastično veći od drugog i tome naravno postoji pravi razlog do kojeg smo trenutno došli. Evo paste tih programa za palenje LED-a.

Palenje LED-a pisano u C-u:

Code: Select all

:1000000019C020C01FC01EC01DC01CC01BC01AC00C
:1000100019C018C017C016C015C014C013C012C034
:1000200011C010C00FC00EC00DC00CC00BC00AC064
:1000300009C008C011241FBECFEFD4E0DEBFCDBF82
:1000400002D004C0DDCF81E087B9FFCFF894FFCFA5
:00000001FF
Palenje LED-a pisano u assembleru:

Code: Select all

:0600000081E087B9FFCF8B
:00000001FF
Za korištenje prekida moramo jasno odvojiti 2 usko povezane stvari -> Hardware i Software.
Prvo moramo znati da bilo koji prekid u mikrokontroleru uvijek poziva Hardware MCU-a, a druga opako bitna stvar je činjenica da se prekid događa na razini assemblera, a ne C-a ili Bascoma.
Ovo je jako bitno jer ljudski mozak u C-u očekuje da će se prekid programa dogoditi nakon naredbe C jezika, a on se uz vraga dogodi nakon jedne assemblerske naredbe. Iz C-a ne možemo niti vidjeti kako se dogodi prekid, niti možemo razumjeti stvarni prekid jer je on naprosto skriven iza samog prevodilca.

U toj priči nemoguće je izbjeći pojam ISR_VECTOR jer je on direktno vezan za HARDWARE prekida,
Davno sam rekao da mikrokontroler starta sa adrese 0 u FLASH-u, no nisam rekao da ta specifična adresa ima i svoje ime, a zove se RESET_VECTOR. No nije to jedini VECTOR u AVR računalu. Nakon adrese 0 idu ISR VECTORI. Nije slučajnost to što u ATMEGA88 računalu postoji 1 RESET_VECTOR i 25 ISR_VECTOR-a.
To znači da svaki prekid u mikrokontroleru ima vlastiti ISR vector. (Postoji 25 izvora prekida, te 25 ISR_VECTOR-a).

Ovo je jedan od razloga zašto je C napravio tako velik program za palenje LED-a. Compiler zna točnu lokaciju VECTORA i namjerno ih programski preskoči jer zna svrhu VECTOR-a i zato je prvih 26 adresa u FLASH-u sigurno assemblerska naredba skoka RJMP. (RESET_VECTOR + 25 ISR_VECTOR)

No isto tako kad sam ja palio LED iz assemblera znam da su tu VECTORI, ali također znam jedan podatak koji compiler ne zna, a to je da sam siguran 100% kako se neće dogoditi niti jedan prekid u programu za palenje LED-a. (U tom programu sigurno nisam uključio I BIT SREG-a). Tu informaciju assemblerski programer može iskoristiti i namjerno napisati program preko ISR_VECTOR-a. Zdrava pamet ovo nikada ne radi i u C-u se to ne može napraviti jer su svi VECTORI naprosto briga compilera i on će ih uvijek preskočiti sa assemblerskom RJMP naredbom. No ja sam si dozvolio slobodu assemblera, pa sam namjerno upalio LED preko VECTOR-a kako bih imao što manji program.

Dio prekida koji će HARDWARE odraditi upravo se tiče ovih VECTORA, pa evo da napišem i sve VECTORE ATMEGA88 MCU-a:
1 0x000(1) RESET External Pin, Power-on Reset, Brown-out Reset and Watchdog System Reset
2 0x001 INT0 External Interrupt Request 0
3 0x002 INT1 External Interrupt Request 1
4 0x003 PCINT0 Pin Change Interrupt Request 0
5 0x004 PCINT1 Pin Change Interrupt Request 1
6 0x005 PCINT2 Pin Change Interrupt Request 2
7 0x006 WDT Watchdog Time-out Interrupt
8 0x007 TIMER2 COMPA Timer/Counter2 Compare Match A
9 0x008 TIMER2 COMPB Timer/Counter2 Compare Match B
10 0x009 TIMER2 OVF Timer/Counter2 Overflow
11 0x00A TIMER1 CAPT Timer/Counter1 Capture Event
12 0x00B TIMER1 COMPA Timer/Counter1 Compare Match A
13 0x00C TIMER1 COMPB Timer/Coutner1 Compare Match B
14 0x00D TIMER1 OVF Timer/Counter1 Overflow
15 0x00E TIMER0 COMPA Timer/Counter0 Compare Match A
16 0x00F TIMER0 COMPB Timer/Counter0 Compare Match B
17 0x010 TIMER0 OVF Timer/Counter0 Overflow
18 0x011 SPI, STC SPI Serial Transfer Complete
19 0x012 USART, RX USART Rx Complete
20 0x013 USART, UDRE USART, Data Register Empty
21 0x014 USART, TX USART, Tx Complete
22 0x015 ADC ADC Conversion Complete
23 0x016 EE READY EEPROM Ready
24 0x017 ANALOG COMP Analog Comparator
25 0x018 TWI 2-wire Serial Interface
26 0x019 SPM READY Store Program Memory Ready

Nama najzanimljiviji ISR_VECTOR je upravo ovaj na adresi 0x00E FLASH-a:
15 0x00E TIMER0 COMPA Timer/Counter0 Compare Match A

Ponovno se vraćam na onaj problem: Što kad se interrupt stvarno dogodi???
E tada ćemo zbog samog HARDWARE-a računala sigurno programski završiti na gore navedenom VECTORU. Nije to sve što će hardware za nas odraditi, pa će usput siguno isključiti i I BIT SREG-a (dakle isključiti globalne prekide) kako se niti jedan drugi prekid ne bi mogao dogoditi, pospremiti PC na stack, obrisati TIFR za ovaj prekid, te tek se onda čudom pojaviti upravo na ovoj adresi u FLASH-u.

Sada ne moramo točno analizirati sve što odradi sam hardware jer ćemo to kasnije još detaljnije proučavati, no kada program dođe na VECTOR onda mi preuzimamo stvar.

Prekid nema nikakvog smisla ako ne postoji dio programa koji će se odraditi u prekidu, a taj dio se zove ISR ili prekidna servisna rutina. (U njoj mi moramo povećati varijablu RAM-a). Pošto svaki prekid TIMER-a uvijek i isključivo uvijek završava na specifičnom ISR VECTORU tamo mora biti naredba skoka na našu prekidnu rutinu.

Sve što mi moramo napraviti je reći compileru koja je naša prekidna rutina, kako bi on znao kamo treba skočiti kada se program pojavi na ovom specifičnom ISR_VECTORU.

Kada compileru kažemo koje je ISR rutina, on će sigurno na poziciju vectora staviti assemblersku naredbu RJMP koja skače na ISR, a mi ćemo se čudom u C-u tamo i pojaviti, a da ne znamo ni zašto smo točno, niti kako smo točno došli u ISR. :azdaja:

Zdravo seljački: Što je ISR_VECTOR?
Adresa u FLASH memoriji na koju će skočiti program kada se dogodi specifičan prekid, a služi samo da bi na toj adresi mi mogli napisati instrukciju koja skače na našu ISR rutinu koja se nalazi negdje u FLASH-u.
Ovim načinom ISR rutinu možemo smjestiti gdje hoćemo i ona može biti velika koliko hoćemo jer VECTOR uvijek skače na adresu gdje sama servisna rutina počinje.

Iz ovog svakako treba izvući i zaključak:
ISR nije ništa drugo nego obični dio programa u FLASH memoriji, a od glavnog programa, koji se vrti u while(1), razlikuje se samo po tome što ISR_VECTOR skače upravo na taj dio FLASH programa, a to se dogodi svaki puta kada TIMER pozove prekid. Postoji tu još detalja, a o njima naravno kasnije...
Jedina korist od toga svege je činjenica da će se ISR rutina izvršiti svakih 1mS vremena i to je najbitnije u ovoj pomalo kompliciranijoj prici.

To be continued...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" - ISR

Kako reći compileru koje je naša ISR rutina?
U principu sve o AVR biblioteci možemo pronaći u datoteci imena "interrupt.h", pa i u ostalim *.h datotekama koje su dio AVR biblioteke. No na internetu postoji i nešto puno pametnije a nalazi se na ovoj stranici:
http://www.nongnu.org/avr-libc/user-man ... 64a81f8dba

Ovo sve zajedno baš i nije kratko jer sama interrupt biblioteka ima dosta nepotrebnih stvari, neke od njih ću naravno objasniti. Evo dakle prva glupost AVR biblioteke i njene interrupt.h datoteke:

#define SIGNAL (vector)
This is the same as the ISR macro without optional attributes.
Deprecated:
Do not use SIGNAL() in new code. Use ISR() instead


Najobičniju ISR rutinu možemo napisati na 2 načina -> SIGNAL(vector) i ISR(vector, attributes), a jedina razlika između ova 2 načina je to što ISR ima attributes, a SIGNAL ih nema.

U samom libu i uputama piše -> U NOVOM PROGRAMU NEMOJTE KORISTITI "SIGNAL" nego "ISR", pa po tome ne bi trebao niti postojati jer i ISR(vector, attributes) ne mora imati attributes, a u to ćemo se i sami uvjeriti. Ovu osnovnu glupost biblioteke nikada nećemo koristiti, nego se držati onog starog i pametnog MACRO-a:
#define ISR(vector, attributes)

Ovo je najosnovnije što se koristi u 99% slučajeva, a neki specijalni slučajevi su preusmjeravanje jednog ISR_VECTORA, na drugi, dizanje I Bita SREG-a u samoj prekidnoj rutini kako bi se mogao dogoditi prekid iz prekida... itd.

E sad kad ste ovo pročitali zaboravite sve što gore piše i držite se zlatnog pravila i najboljeg načina za povezivanje ISR_VECTOR-a sa prekidnom rutinom. U naš program ću dodati jednu prekidnu rutinu:

Code: Select all

#include <avr\io.h>
#include <avr\interrupt.h>

unsigned int t_led;                    // RAM variable used for led_blink timer

ISR(){                                 // Timer0 "Compare Match A" ISR

}

int main(void){
   
    TCCR0A |= (1<<WGM01);              // Config TIMER0 CTC mode     
    OCR0A = 124;                       // Set new TOP value -> (124+1)*8uS = 1mS
    TCCR0B |= (1<<CS00) | (1<<CS01);   // Start TIMER from prescaler /64 = 125 khz
    TIMSK0 |= (1<<OCIE0A);             // Enable Output Compare Match A Interrupt
    sei();                             // Enable Global Interrupts

    while(1){
       // main loop
    }     
}  
Sve ISR rutine, dakle njih 25 koliko ih ima ATMEGA88 uvijek i isključivo uvijek pišite ovako:
ISR(){
}

No opet problem... Kada napišete ISR(){} compiler zna da se radi o vašoj prekidnoj rutini, ali jednu stvar ne zna. Nismo mu rekli koji ISR_VECTOR treba preusmjeriti na ISR rutinu. Eh kad ih ima 25 onda compileru moramo znati reći točno koji od tih 25 ISR_VECTORA, jer ako to zajebemo upravo se srušio naš program.

Mi znamo da se radi o VECTORU kada TIMER nabroji do OCR0A registra, te skoči na adresu vectora:
15 0x00E TIMER0 COMPA Timer/Counter0 Compare Match A

Najbolja i univerzalna stvar je ići metodom header datoteka. 100% najbolje i 100% isprobano. Fora je u tome što gotovo svaki AVR ima različite VECTORE, na drugim adresama, ili ih ima manje ili više od 25, pa onda bi mi kao trebali "kopati" po netu da saznamo kako se zove ovaj ebeni Vector. Umjesto toga Compiler će na to sam reći. Pošto smo u programu napisali #include <avr\interrupt.h> i ja sam napravio HEX file bez ove ISR rutine compiler je morao ubaciti sve potrebne header datoteke vezane za ATMEGA88 računalo i prekide, i zato u AVR Studio pod "External Dependencies" postoji datoteka imena "iomx8.h"
Upravo na kraju te datoteke stoje MACRO naredbe svih vectora u ATMEGA88 računalu i tamo možete metodom intuicije te zdravog razuma vrlo lako pronaći vector koji trebamo. Evo linije programa koje govore o tom vectoru a nalazi se u "iomx8.h" datoteci:

Code: Select all

/* TimerCounter0 Compare Match A */
#define TIMER0_COMPA_vect		_VECTOR(14)
#define SIG_OUTPUT_COMPARE0A		_VECTOR(14)
Iz macro naredbe jasno vidimo da se radi o vectoru na adresi 14, a on je u tablici vectora na 15 mjestu.
15 0x00E TIMER0 COMPA Timer/Counter0 Compare Match A

Adresa 0x00E je DEC(14), a vector je pod rednim brojem 15 jer redni brojevi idu od broja 1, a stvarni FLASH ide od adrese 0. Zato se redni broj vectora ne poklapa sa adresom samog vectora.

Još trebamo primjetiti da vector ima 2 naziva, a i to je bezveze jer u oba naziva se radi o istom VECTORU i programerski je identično što napisali. Zamisao biblioteke je bila kada koristimo SIGNAL() da u njemu vector nazovemo SIG_OUTPUT_COMPARE0A, a kada koristimo ISR() da u njemu vector nazovemo TIMER0_COMPA_vect. Po ovome imamo 4 kombinacije:

Code: Select all

ISR(TIMER0_COMPA_vect){
}
ISR(SIG_OUTPUT_COMPARE0A){
}
SIGNAL(TIMER0_COMPA_vect){
}
SIGNAL(SIG_OUTPUT_COMPARE0A){
}
Sve 4 kombinacije prekidne rutine su pravilno napisane i možemo za rutinu odabrati bilo koju od njih. Trebali bi samo znati da ne možete imati 2 prekidne rutine za jedan vector jer to je greška koju vam niti compiler neće oprostiti. Za svaki interrupt postoji jedan ISR_VECTOR i samo jedna prekidna rutina jer svaki ISR_VECTOR može skočiti na samo jednu FLASH adresu na kojoj se nalazi sam ISR.
Za moju prekidnu rutinu od ove 4 odabrati ću onu koju ja koristim. I evo program ponovno:

Code: Select all

#include <avr\io.h>
#include <avr\interrupt.h>

unsigned int t_led;                    // RAM variable used for led_blink timer

ISR(TIMER0_COMPA_vect){                // Timer0 "Compare Match A" ISR

}

int main(void){
   
    TCCR0A |= (1<<WGM01);              // Config TIMER0 CTC mode     
    OCR0A = 124;                       // Set new TOP value -> (124+1)*8uS = 1mS
    TCCR0B |= (1<<CS00) | (1<<CS01);   // Start TIMER from prescaler /64 = 125 khz
    TIMSK0 |= (1<<OCIE0A);             // Enable Output Compare Match A Interrupt
    sei();                             // Enable Global Interrupts

    while(1){
       // main loop
    }     
}  
Evo napokon i situacije kada mogu reći da je sve ono komplicirano završilo, jer smo upravo napravili sve potrebno da zaobiđemo DELAY. Pošto sada znamo da će TIMER pozvati ISR svakih 1mS vremena vrlo lako u samom ISR-u možemo povećavati naš sat koji se nalazi u RAM memoriji i taj program izgleda ovako:

Code: Select all

#include <avr\io.h>
#include <avr\interrupt.h>

unsigned int t_led;                    // RAM variable used for led_blink timer

ISR(TIMER0_COMPA_vect){                // Timer0 "Compare Match A" ISR
    t_led++;                           // increment led_timer
}

int main(void){
   
    TCCR0A |= (1<<WGM01);              // Config TIMER0 CTC mode     
    OCR0A = 124;                       // Set new TOP value -> (124+1)*8uS = 1mS
    TCCR0B |= (1<<CS00) | (1<<CS01);   // Start TIMER from prescaler /64 = 125 khz
    TIMSK0 |= (1<<OCIE0A);             // Enable Output Compare Match A Interrupt
    sei();                             // Enable Global Interrupts

    while(1){
       // main loop
    }     
}  
To be continued...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" - ISR

Pregrizao si jezik kada sam rekao da je komplicirano gotovo jer to nije točno. U toj priči oko ISR-a nedostaje jedna jako bitna stvar. Već smo rekli da prekid poziva hardware ako su zadovoljeni uvjeti prekida, a naš program može biti na bilo kojoj adresi FLASH memorije i raditi bilo što. Pitanje na koje u tartufima još nema odgovora je sljedeće:
Kako se program nakon izvršene servisne rutine zna vratiti točno tamo gdje je bio prije ISR-a?
Odgovor opet moramo tražiti u assembleru, pa ću ja bezveze napisati par assemblerskih naredbi u naš program.

Code: Select all

#include <avr\io.h>
#include <avr\interrupt.h>

unsigned int t_led;                    // RAM variable used for led_blink timer

ISR(TIMER0_COMPA_vect){                // Timer0 "Compare Match A" ISR
    t_led++;                           // increment led_timer
}

int main(void){
   
    TCCR0A |= (1<<WGM01);              // Config TIMER0 CTC mode     
    OCR0A = 124;                       // Set new TOP value -> (124+1)*8uS = 1mS
    TCCR0B |= (1<<CS00) | (1<<CS01);   // Start TIMER from prescaler /64 = 125 khz
    TIMSK0 |= (1<<OCIE0A);             // Enable Output Compare Match A Interrupt
    sei();                             // Enable Global Interrupts

    while(1){                          // main loop
        asm("nop");                    // No Operation
        asm("nop");                    // No Operation
        asm("nop");                    // No Operation
        asm("nop");                    // No Operation
    }     
}  
"nop" sam već spominjao a to je naredba koja ništa ne radi osim što će bez veze potrošiti vrijeme MCU-a. Ista stvar kao i DELAY, no napisao sam 4 instrukcije koje se stalno vrte u glavnom loop-u. Davno sam govorio o nečemu što se zove PC, puni naziv je "Program Counter".
Program Counter je direktno vezan za CPU CORE i FLASH, i uvijek pokazuje jezgri koju instrukciju treba izvršiti u FLASH memoriji. Logično dakle da prilikom reseta mikrokontrolera PC (Program Counter) pokazuje na adresu 0 u FLASH-u tj (RESET_VECTOR).
Prvih 26 adresa u FLASH memoriji ne smijemo koristiti za program, pa ih prevodilac uvijek preskače sa RJMP naredbom. U assembleru ovog programa to izgleda ovako:
+00000000: C019 RJMP PC+0x001A Relative jump
+00000001: C028 RJMP PC+0x0029 Relative jump
+00000002: C027 RJMP PC+0x0028 Relative jump
.....
.....
+00000019: C010 RJMP PC+0x0011 Relative jump

Nama je svakako najzanimljivija ona na koju pokazuje PC (Program Counter), a on prilikom reseta pokazuje na adresu 0 (prvi red):
+00000000: C019 RJMP PC+0x001A Relative jump

Ne znam bode li vas, ali mene nekako najviše bode u oči ovo: "PC+0x001A"
Assemblerska naredba RJMP izvršava relativni skok sa adrese na kojoj se sam PC nalazi, pa je lako zaključiti da assemblerska naredba RJMP samo utječe na PROGRAM COUNTER, tj uvećava ga za 0x1A(HEX) ili 26(DEC).

Sada bi vas trebao bosti u oko adresa FLASH-a 26??? Što je tamo?
Reći ću vam što je tamo. Zadnji ISR_VECTOR je na adresi 25, a adresa 26 je prva slobodna u FLASH-u za pisanje programa i zato assemblerski RJMP pomiče PROGRAM COUNTER na sljedeću instrukciju koju jezgra treba izvršiti da bi PC(Program Counter) preskočio ISR_VECTORE.

Sve assemblerske instrukcije direktno utječu na "Program Counter" jer bez toga bi jezgra stalno izvršavala jednu te istu instrukciju u FLASH-u.

U gornji program sam ubacio 4 assemblerske "nop" instrukcije pa vas pitam što radi "nop" sa program counterom? Evo i odgovor:

Code: Select all

    asm("nop");              //PC <- PC+1
    asm("nop");              //PC <- PC+1
    asm("nop");              //PC <- PC+1
    asm("nop");              //PC <- PC+1
Dakle čak i instrukcija koja ništa ne radi ipak stalno povećava Program Counter i on uvijek pokazuje na sljedeću instrukciju za jezgu. Program Counter je jako bitna stvar kod AVR mikrokontrolera jer je on direktno zaslužan za izvršavanje bilo koje instrukcije u FLASH memoriji.

Što je onda tu bitno?
Ako "Program Counter" uvijek i isključivo uvijek pokazuje na instrukciju koju AVR jezgra treba izvršiti, to znači da prilikom poziva prekidne rutine nekako, negdje i netko mora pohraniti Program Counter(PC), jer kad se prekidna rutina završi Program counter se mora vratiti na instrukciju koju je trebao izvršiti prije nego se prekid dogodio.

Što dakle sam HARDWARE napravi sa Program Counterom kada se dogodi prekid?
Ništa posebno mudro. Kada se dogodi prekid našeg TIMER-a 0 HARDWARE će:
1. Isključiti I BIT SREG-a kako se ne bi dogodio još neki prekid prije nego odradimo ovaj.
2. Pohraniti trenutni "Program Counter" (STACK <- PC)
3. Program Counter postaviti na lokaciju prekidnog VECTORA, a to je za naš prekid upravo ovo:
15 0x00E TIMER0 COMPA Timer/Counter0 Compare Match A
Na toj lokaciji u FLASHU compiler uvijek upisuje skok na ISR rutinu koja je negdje u FLASH-u i evo nas kod onog t_led++;

Što kad odradimo ISR te povećamo naš t_led?
Postoji assemblerska naredba "RETI" i ona je zadnja naredba koju compiler ubacuje u samoj ISR rutini, a ona radi upravo ovo:
1. Sa STACK-a uzima vrijednost Program Countera koju je sam hardware tamo i spremio (PC <- STACK)
2. Uključuje I BIT SREG-a kako bi se mogli dogoditi prekidi u programu.

U toj situaciji "Program Counter" će ponovno pokazivati na naredbu koju bi izvršio u slučaju da se prekid nije dogodio i tu je zatvoren krug prekidne rutine, a isti taj krug dogoditi će se nakon 1mS vremena kada TIMER ponovno pozove prekid.

Ovih specifičnih 26 adresa u FLASH memoriji (RESET_VECTOR + ISR_VECTORS) hardverski gledano po ničemu se ne razlikuju od čitave FLASH memorije, a opet ih nazivamo malo drugačije. Pa ako je čitav FLASH identičan što je onda različito?
Jedino različito je Program Counter. Svaki prekid (njih 25) ne dogode se u FLASH-u, nego Hardware samog prekida postavi "Program Counter" upravo na tu specifičnu lokaciju u FLASH-u. Svih 25 prekida rade istu stvar, samo što svaki od njih postavlja "Program Counter" na svoj ISR_VECTOR.

Evo nas i kod još dva nova pojma: STACK i STACK_POINTER
STACK nije ništa drugo nego lokacija u RAM memoriji, i upravo tamo sam hardware prekida sprema Program Counter, i od tamo ga uzima kad završi prekidna rutina (RETI). Ovaj dio također je sakriven i nevidljiv u višim programskim jezicima pa zato se svi početnici često potepu na overflow STACK-a.
Kako vidim morati ćemo se ponovno vratiti na assembler i adresu 0 u FLASH-u jer znam da compiler podesi STACK_POINTER i da se to u C-u ne može vidjeti, no za razumjevanje samog prekida, te računala moramo znati čemu služi STACK.

Prekid i prekidnu rutinu nemoguće je razumjeti u C-u. Tamo je možemo samo koristiti, no cilj tartufa nije da bi netko koristio prekid, nego da netko shvati kako radi računalo, jer kad to znamo onda ne postoji MCU kojeg ne možemo razumjeti i programirati.

To be continued...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" - Blink LED

Vraćam se dakle C-u i ponovno toj dosadnoj LED-ici. Prvo što moramo dodati u ovaj program je konfiguracija DDRx registra. Davno sam pisao o tome kako se podešava smijer podataka na portovima, pa ću sada skratiti taj dio. LED dioda je spojena na PC0 i linija programa koja postavlja PC0 kao izlaz piše se ovako:
DDRC |= (1<<PC0);

U programu to možemo ubaciti gdje hoćemo, ali ja ću namjerno to staviti na prvo mjesto jer se gotovo uvijek smijer podataka radi na početku programa. (Inicijalizacijski dio programa). Kada to ubacimo naš program izgleda ovako:

Code: Select all

#include <avr\io.h>
#include <avr\interrupt.h>

unsigned int t_led;                    // RAM variable used for led_blink timer

ISR(TIMER0_COMPA_vect){                // Timer0 "Compare Match A" ISR
    t_led++;                           // increment led_timer
}

int main(void){
    
    DDRC |= (1<<PC0);                  // Config data direction registar ( PCO -> Out)
    
    TCCR0A |= (1<<WGM01);              // Config TIMER0 CTC mode     
    OCR0A = 124;                       // Set new TOP value -> (124+1)*8uS = 1mS
    TCCR0B |= (1<<CS00) | (1<<CS01);   // Start TIMER from prescaler /64 = 125 khz
    TIMSK0 |= (1<<OCIE0A);             // Enable Output Compare Match A Interrupt
    sei();                             // Enable Global Interrupts

    while(1){                          // main loop
        // Do Nothing
    }     
} 
Sa svime ovime u programu stvar je gotovo završena i postoje samo 2 načina na koja mogu završiti program:
1. Provjera t_led varijable iz while(1) beskonačne petlje
2. Provjera t_led varijable u samom ISR-u


Ovo je stvar koju odlučuje sam programer, i obe stavke imaju prednost i manu u programu.
Prilikom korištenja prekidnih rutina i prekida moramo znati najbitniju stvar o prekidima: Prekidna rutina mora biti kratka i brza. Po mogućnosti da iz rutine izađemo što prije i potrošimo što manje vremena CPU-a. Zašto to tako mora biti?
Pošto ulazimo u prekidnu rutinu svakih 1mS vremena u toj rutini nikako ne smijemo ostati 1mS vremena, jer bi u toj situaciji doslovno stalno odrađivali samo servisnu rutinu i "zaklali" program koji se vrti u while(1) petlji.
S druge strane AVR hardware sam će zabraniti prekide kada se dogodi bilo koji prekid (I bit SREG-a), tako da se u prekidnoj rutini ne može dogoditi niti jedan drugi prekid dok ne završimo ovaj. To prevedeno znači, ako smo "dugo" vremena, recimo 500 uS u ISR-u TIMER-a ne možemo za to vrijeme iskoristiti niti jedan drugi prekid. Mogli bi ako sami u ISR rutini uključimo I bit SREG-a, ali to izbjegavamo...

Prva mana provjere t_led varijable u prekidnoj rutini je upravo ova gore opisana. Ako svaki puta u ISR-u provjeravamo t_led to znači da će ISR rutina duže trajati. Neće se ništa dogoditi ako izgubimo koju uS vremena jer ova prekidna rutina traje nekih 1-2 uS vremena i to je daleko od 1mS kada bi apsolutno zaklali while(1) petlju u glavnom programu.

Prednost provjere varijable u ISR-u je ta što je samo blinkanje LED-a neovisno o glavnom programu i u njemu možemo pisati što hoćemo, pa čak koristiti i _delay_ms(1000). Prekid i prekidna rutina mogu se brinuti oko ove jednostavne stvari, a naš glavni program može doslovno ne raditi ništa.

Prednost provjere t_led varijable u while(1) petlji je samo ta što će ISR rutina trajati kraće, pa zato većina programera namjerno to rade u while(1) petlji kako bi ISR bio što je moguće kraći. Mana provjere t_led varijable iz while(1) je ta što doslovno stalno moramo provjeravati t_led i ne smijemo nikada i nigdje stati jer bi LED blinkao netočno.

Pošto je ovo prvi program koji pišem napisati ću ga onako kako ga ja ne bih pisao. Dakle ISR će se brinuti oko blinkanja LED-a. Oba BUG-a koja sam davno spominjao su ako t_led provjeravamo iz while(1), pa zato obilazim taj dio.

Program za blinkanje LED-om izgleda ovako:

Code: Select all

#include <avr\io.h>
#include <avr\interrupt.h>

unsigned int t_led;                    // RAM variable used for led_blink timer

ISR(TIMER0_COMPA_vect){                // Timer0 "Compare Match A" ISR
    t_led++;                           // increment led_timer
    if(t_led >= 1000){                 // 1000 ms? 
        PORTC ^= (1<<PC0);             // Toggle LED
        t_led = 0;                     // Reset t_led timer variable;
    }    
}

int main(void){
    
    DDRC |= (1<<PC0);                  // Config data direction registar ( PCO -> Out)
    
    TCCR0A |= (1<<WGM01);              // Config TIMER0 CTC mode     
    OCR0A = 124;                       // Set new TOP value -> (124+1)*8uS = 1mS
    TCCR0B |= (1<<CS00) | (1<<CS01);   // Start TIMER from prescaler /64 = 125 khz
    TIMSK0 |= (1<<OCIE0A);             // Enable Output Compare Match A Interrupt
    sei();                             // Enable Global Interrupts

    while(1){                          // main loop
        // Do Nothing
    }     
} 
Ovo je konačan program, no u njemu ima nekih neobjašnjenih stvari. Zašto sam napisao (>=1000) te kako sam okrenuo stanje LED-a sa DDRC ^= (1<<PC0); Objasnim u sljedećem postu.

To be continued...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" - Blink LED

if(t_led >= 1000)
Ako je t_led veći ili jedak broju 1000 okreni stanje LED-a. (Ako je bio upaljen ugasi, ako je bio ugašen upali)

Najjednostavnije je na to gledati ovako:
Mi želimo blinkati LED-om svakih 2mS i promijenimo ovaj uvijet u ovo:
if(t_led >= 2)

Sada analiziramo program:
1. TIMER je 0
2. varijabla t_led je 0
3. Dogodi se prvi prekid, što znači da je prošlo 1mS vremena
4. Varijabla t_led se uvećava za 1
5. Uvijet za okretanje LED-a nije zadovoljen pošto t_led mora biti veći ili jedak 2.
6. Timer se resetirao na 0, ponovno odbrojao 1mS i dogodi se drugi prekid što znači da su prošle 2mS.
7. varijablu t_led povećamo još za 1 i dobijemo t_led = 2.

Dakle upravo u tom prekidu kada povećamo t_led sa 1 na 2 prošlo je točno 2mS vremena i zato tada mjenjamo stanje LED-a.

Ako se vratimo na naše blinkanje svake 1 sekunde stvar je identična. No logičan uvjet bi bio ovaj:
if(t_led == 1000)

Zašto sam napisao "veće ili jednako" kada sam mogao napisati samo "jednako"?
Pošto mi u samom prekidu provjeravamo uvijet i povećavamo t_led nikad se neće dogoditi greška, ali zamislite da smo to radili u while(1) petlji. Što ako nam se u while(1) petlji nakupi toliko programa da ne stignemo provjeriti t_led kada bude na vrijednosti 1000?

Prekid je brz i sigurno će se dogoditi svake 1mS, ali naš glavni program može ići puno sporije. Ako recimo emuliramo komunikaciju, pišemo po LCD-u, ili ne znam što računamo to može potrajati 2, 5 ili 10 mS. I ako tek tada provjerimo varijablu t_led ona već može biti na 1001, 1002, 1010.

Kada bi t_led provjeravali u glavom while(1) loop-u, onda bi čitav uvijet morali staviti tamo, dakle ovaj komad programa:

Code: Select all

if(t_led >= 1000){                 // 1000 ms? 
    PORTC ^= (1<<PC0);             // Toggle LED
    t_led = 0;                     // Reset t_led timer variable;
} 
I ako tu piše (t_led == 1000) može nam se dogoditi da glavni program zakasni na taj uvijet, a prekid poveća varijablu na 1001 i upravo smo napravili BUG. Ali kad stavimo ">=" znamo da će LED malo zakasniti, ali će bez problema promijeniti stanje. Pa ako se stanje LED-a ne promijeni točno nakon 1000 mS nitko to neće znati osim nas na forumu. Možete vi promijeniti 100 korisničkih naočala, ali niti jedan normalan ne može skužiti kašnjenje od 200 mS, a pogotovo 10mS.

Nemojte me krivo shvatiti. Ovaj program neće nikad zakasniti ako prati t_led iz glavnog loop-a jer i tako tamo ništa drugo i ne radi, no što ako ja napišem kompliciranu aplikaciju kojoj ponekad nije dosta 1mS da se izvrši. Možda jednom od 1000 izvršavanja programa može se dogoditi da glavni loop ne stigne provjeriti t_led varijablu. Tada se ovaj uvijet mora pisati ">=", a ne "==".

To sam dakle u ISR-u napisao čisto iz prakse, jer da sam pisao u glavnom loop-u to bih morao napisati i zato bez puno razmišljanja uvijek ograničite ovu stvar sa uvijetom "veće ili jednako".

To be continued...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" -XOR

Ostala je još jedna neobjašnjena stvar koju sam napisao u programu:

Code: Select all

PORTC ^= (1<<PC0);           // Toggle LED
Izgleda slično kao i upravljanje portovima OR i AND:

Code: Select all

PORTC |=  (1<<PC0);         // Postavi PC0 u log 1  (| -> OR )
PORTC &=~ (1<<PC0);         // Postavi PC0 u log 0  (& -> AND)
AND i OR operatori su ranije objašnjavani, a ovaj treći koji sam uveo u priču zove se XOR.
E da se ne uplašite novih pojmova oko upravljanja bitom ili pinom MCU-a ova tri navedena su kraj. Za upravljanje PORT-ovima uvijek ćemo koristiti samo ovo:

Code: Select all

PORTC |=  (1<<PC0);         // Postavi PC0 u log 1
PORTC &=~ (1<<PC0);         // Postavi PC0 u log 0
PORTC ^=  (1<<PC0);         // Okreni stanje PC0 bita
Pošto neću pisati o osnovama digitalne elektronike dati ću vam link:
http://hr.wikipedia.org/wiki/Logi%C4%8Dki_sklopovi

XOR je predzadnji logički sklop na gornjem linku, a u c-u se označava sa ovim čudnim znakom:
"^" (ALT_GR + 3), samo što ovo morate napraviti 2 puta da dobijete 2 znaka, pa jedan obrišete. Nikad nisam niti tražio kako je ovo sranje najjednostavnije napisati. Dakle drži ALT_GR i lupaj po broju 3, pa kad bude viška onda briši višak ili napravite paste sa foruma :)

Najbitnija stvar kod svakog operatora je tablica istine, a ona za XOR izgleda ovako:

Code: Select all

  ULAZ         IZLAZ
A      B      A XOR B
0      0          0
0      1          1
1      0          1
1      1          0
Idemo sad vratiti našoj liniji programa za mijenjanje stanja leda. Očito je da XOR radi operaciju nad PORTC registrom sa (1<<PC0). PC0 je MACRO naredba AVR biblioteke koja govori o poziciji BIT-a u registru. Pošto je sam PC0 krajnji desni bit, onda je njegova MACRO naredba broj 0 pa to možemo zapisati i ovako:
PORTC ^= (1<<0);

Zamislimo da je stanje PORTC registra BIN(00000000), i idemo izvršiti XOR operaciju sa (1<<0).
Vraćamo se istoj tematici zagrada i prioriteta C-a i prvo što radim je da poguram broj BIN(00000001) za 0 mijesta u lijevo i dobivam isto to: BIN(00000001)

Sada radim XOR operaciju sa PORTC registrom i gledam gore u tablicu:

(PORTC) 00000000 XOR
( 1<<0 ) 00000001
______________________
(PORTC) 00000001


Ako računamo s desne strane vidimo da u desnom stupcu imamo jednu jedinicu i jednu nulu, a u tablici za taj "A" i "B" broj rezultat je uvijek 1. Isto tako trebamo primjetiti da je za obe 0 u svim ostalim stupcima rezultat 0 i sigurno smo promijenili stanje PC0 linije MCU-a (krajnji desni BIT).

Idemo sada napraviti istu operaciju na promijenjenom stanju krajnjeg desnog BIT-a. Sada je PC0 logička jedinica, a (1<<0) (Guram broj 1 za 0....) je i dalje BIN(00000001)

(PORTC) 00000001 XOR
( 1<<0 ) 00000001
______________________
(PORTC) 00000000


Opet bacimo pogled na tablicu i jasno vidimo da u slučaju da su i "A" i "B" broj logički "1" rezultat će uvijek biti logički "0"

Jedino što tu treba možda skužiti da će se svakim izvršavanjem ove instrukcije uvijek okrenuti stanje izlaza PC0, i upravo taj način koristimo kada nas ne zanima jeli LED upaljen ili ugašen. Jedino nas zanima da mu promijenimo stanje.

Ovo sve zajedno oko ILI, AND, XOR i prvog komplementa ~ (okretanje svih bitova) ne moramo uopće znati. To sam pisao da bi netko tko čita znao o čemu se radi i davno prije sam mogao napisati univerzalna 3 reda kojih ćemo se držati u upravljanju bitovima:

Code: Select all

IME_REGISTRA |= (1<<BROJ_BITA); // Ova linija uvijek BROJ_BITA diže u logičku jedinicu
IME_REGISTRA &=~(1<<BROJ_BITA); // Ova linija uvijek BROJ_BITA spušta u logičku nulu
IME_REGISTRA ^= (1<<BROJ_BITA); // Ova linija uvijek okreće stanje bita BROJ_BITA
Ovih 3 linije koje sam napisao vrede više od sve nauke o upravljanju jednim BIT-om u C-i, i nikada ne razmišljamo o operatorima koji se spominju u samoj naredbi. Ja sam ih detaljno opisao sve čisto da onaj tko hoće može shvatiti o čemu se točno radi. Inače ja sam dugo godina radio COPY/PASTE ove 3 linije i samo ubacivao ono što želim napraviti. Tek nakon par godina programiranja to postane automatizirano, pa onda koristite gore 3 navedene linije za COPY/PASTE kada upravljate jednim bitnom u C-u.

Kako bi digli 4 bit MATO registra u logički 1:
MATO |= (1<<4);

Kako bi spustili PD6 u logički 0?
PORTD &=~(1<<PD6);

Kako bi u 32 bitnom računalu okrenuli stanje 24-tog bit u registu NEDO_BOG:
NEDO_BOG ^= (1<<24);

U pisanju programa više nećemo ovako upravljati LED-om, jer zamislite da imam 10 LED-ica različitih boja? Tko može pamtiti sve registe i sve bitove, zaboga meni se neda pamtiti niti stanje kojim palim LED, niti me zanima jeli na portu logička nula ili logička jedinica. Ja samo želim upaliti LED, i tu dolazimo do već dugo spominjanog termina MACRO naredba. Prvu stvar koju moramo uvesti u naš program je naša MACRO naredba za upravljanjem LED-om, i tada će neke komplicirane stvari izgledati totalno jednostavne.

HINT:

Code: Select all

#define LedOn()        PORTC &=~ (1<<PC0)
To be continued...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" - Blink_LED Release

Prvi program je gotov na forumu i evo i zapakiran RAR.

Kod vašeg AVR studia potrebno je podesiti još jednu stvar, a to je tabulator. Default postavke AVR Studia su "4", ali ja programiram na "3". Kada mi je širina TAB-a "4" onda vrlo brzo napunim monitor i sve mi je nekako rastegnuto.

Širinu TAB-a podešavamo ovako:
1. Kliknete na Tools/Options
2. Kliknete na "Editor"
3. Za Tabwidth upišete "3"
4. Kliknete na OK

Tek kada to napravite file main.c će se pravilno posložiti.

To be continued...
Attachments
Tartufi.rar
(10.27 KiB) Downloaded 425 times
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" - DoStA SrAnJa

Budem li pisao tako sporo velika je vjerojatnost da ću prije umrijeti od starosti nego završiti LED_BLINK pa onda dosta sranja i malo po gasu...

Novi program koji postavljam konačan je program za blinkanje led-om i u njemu ne treba ništa dodati, a razlike između prošlog objavljenog programa su sljedeće:

1. Blinkanje LED-om je jedna zatvorena cjelina onoga što MCU trenutno radi i odvojio sam ga u zasebne 2 datoteke led_blink.c i led_blink.h

2. Provjeru t_led varijable namjerno sam napravio kao service koji poziva main, jer ne želim to imati u ISR-u.

3. Varijabla t_led postala je volatile zbog jednog neopisanog BUG-a u tartufima (Bio je opisan u nekim postovima, ali sam ih izbrisao jer ništa nije bilo povezano s nicim :) )

4. Prilikom provjere t_led varijable u main programu gasim globalne prekide kako bih izbjegao drugi neopisan BUG. (također nekad bio opisan).

5. Blinkanje LED-om više nije u ISR-u nego ga odrađuje servisna funkcija koja se mora stalno pozivati iz glavne loop petlje.
Attachments
Tartufi.rar
(12.82 KiB) Downloaded 449 times
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" - U govnima za sekundu

Blinkanje LED-om zvanično je završeno, zadnji program objavljen. U programiranju nema kraja da možemo reći kako je sve 100% dobro. Poznavajući sebe, u istom tom programu još bi dodao 100 stvari. Gotovo je!

Nekada sam mislio kako bih trebao opširnije pisati o mogućnostima MCU-a pa paralelno napasti protokole komunikacije, UART, Hardwar, periferiju, načine programiranja, strukturiranje programa... To je toliko opširno područne da je najbolje ne ulaziti u sve.

Nužno ne moram napasti ništa više nego što je već opisano u tartufima da bi svi zajedno došli u govna preko glave. (Osim mene naravno :) )

Sljedeći projekt je opet LED, malo zajebaniji, a pravila su sljedeća:

Ubaciti tipkalo kojim upravljamo radom LED-a.
Svaki pritisak tipkala na duže od 1 sekundu upaliti će ili ugasiti blinkanje LED-a.

Sljedeće pravilo je problem:
Broj impulsa na tipci treba odrediti broj impulsa LED-a u jednoj sekundi.

Dakle radimo uređaj koji uključuje ili isključuje blinkanje leda u slučaju da korisnik drži tipku stisnutu duže od 1S, a u slučaju da recimo 20 puta stisne tipku LED mora blinkati 20 puta u jednoj sekundi...
Ako je pak stisne 10 puta LED mora blinkati 10 puta u sekundi...

Vratimo se na korisnika:
On ne zna što mi programiramo, ali ipak zna kada drži dugo tipku i kada tipka neke impulse.
Sve što on želi je jedna tipka s kojom može upravljati statusom LED-a ON/OFF i podesiti brzinu blinkanja u jednoj sekundi.

Request:
Pulse > 1S -> Uključuje ili isključuje blinkanje
Impulsi < 1S podešavaju brzinu blinkanja LED-a u jednoj sekundi...
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" -On/Off Flag

Prije nego li se upustimo u pisanje bilo kojeg programa za bilo što trebali bi naučiti još jednu bitnu stvar u programiranju -> "Razmišljaj globalnoj ali piši lokalno". Svaki program koji pišemo u glavi moramo imati već napisan. Samo tipkanje programa svodi se na 10%-20% ukupnog vremena razvoja bilo kojeg uređaja. I prije nego što sam počeo pisati blinkanje LED-a znao sam kako će izgledati konačan program, davno prije već sam znao da ću podesiti 8 bitni TIMER da mi generira ISR svakih 1mS, davno prije sam znao da ću blink odraditi iz servisne funkcije main loop-a.

Što sam ja onda radio? Samo sam prepisao ideju iz glave u program! Suludo je programirati a da već ne znamo konačan ishod programa jer na taj način možemo 50 puta pisati program i na kraju ga opet krpati i moliti boga da radi.

Globalno razmišljanje blinkanja LED-om:
Moram podesiti TIMER koji zove ISR svakih 1mS da bih mogao pratiti vrijeme
Moram napisati Service koji prati vrijeme i okreće stanje LED-a

Lokalno razmišljanje blinkanja LED-om.
Moram izračunati prescaler za TIMER da overflow ne bude manji od 1mS
Moram ugasiti globalne prekide u main() funkciji kad čitam varijablu koju povećava ISR
Moram odrediti način rada TIMER-a, podesiti CS bitove, WGM bitove, pronaći točne registre...
Moram izračunati TOP vrijednost TIMER-a za 1mS overflow, upisati je u točan registar.
...
...
...

Globalno razmišljanje puno je bitnije od lokalnog jer ako odemo u krivom smjeru možemo napisati brdo programa koji ne valja jer je naša ideja rješenja bila loša. Jedina stvar koju moramo znati kod globalnog razmišljanja su mogućnosti mikrokontrolera. Nemoguće je globalno reći kako srediti neki problem, ako ne znamo što naš mikrokontroler uopće može napraviti i odraditi.

Lokalno razmišljanje čista je bauštela u kojoj odrađujemo segment po segment onoga što smo zamislili globalnim gledanjem na pojedini problem.

U novom projektu tartufa ocito trebamo najprije globalno ramisliti o onome što radimo i kako to možemo napraviti pa evo nekih stvari o čemu ja globalno razmišljam:

1. Moramo moći ugasiti i u paliti LED, a to ćemo napraviti FLAG varijablom u RunLedBlink() funkciji.
2. Moramo napraviti debounce na tipci, po praksi MIN 50 mS, a to ćemo napraviti tako da nam onaj ISR mjeri vrijeme stisnute tipke
3. Moramo napraviti funkciju koja vraća broj pritisnutih impulsa ili impuls duži od 1S.
....
....
....
... I po 400-ti puta ne smijemo koristiti DELAY!!!

Prije nego uopće otvorim AVR Studio već točno znam što ću i kako napraviti. Uvijek, ali isključivo uvijek moramo znati konačan program prije nego li krenemo tipkati code. Ne mislim sad konačan u onom smislu da sve znamo što trebamo napisati, nego moramo znati u kojem smjeru pišemo i imati problem rastavljen na niz jednostavnih segmenata. Kada pogledamo zadatak "na prvu" čini se malo kompliciran, ali razradimo li ga na globalnoj razini onda su stvari toliko jednostavne da nisu vrijedne spomena.

Lokalno dakle, prvo ću napraviti FLAG da mogu ugasiti ili upaliti LED, što je banalno jednostavno, a nakon toga srediti neki sljedeći lokalni segment koji je također banalan ako samo o njemu razmišljamo.
InTheStillOfTheNight
User avatar
InTheStillOfTheNight
Odlično uznapredovao
Odlično uznapredovao
Posts: 938
Joined: 01-06-2006, 17:54
Location: Zagreb

Re: U potrazi za tartufima AVR-a

Post by InTheStillOfTheNight »

"U potrazi za tartufima AVR-a" -On/Off Flag

Da bi mogli uključiti ili isključiti blinkanje LED-a naprosto moramo znati blinka li LED ili ne. Prvo što radimo deklariramo varijablu u RAM-u imena recimo led_status. Za svaku varijablu u našem programu moramo znati gdje se ona nalazi u RAM memoriji. Ne mislim sad da ju moramo nabadati vilicom na točnu adresu, nego znati gdje će je compiler smjestiti. Na sreću u globalnom pogledu na RAM postoje samo 2 pozicije na kojima se može nalaziti naša varijabla. Biti statična u RAM-u, ili biti na stacku u RAM-u.

Code: Select all

unsigned char led_status;          /* Blink Led status (0-OFF, 1-On)    */
Svaka varijabla deklarirana izvan funkcije je statična i compiler ih slaže od početka RAM-a prema kraju RAM-a.
Svaku varijablu deklariranu u funkciji (ako nije navedeno static) compiler uvijek stavlja na stack (zadnji dio RAM-a). Ružno je reći da nije isto, nego različito je u PM... :azdaja:

No dobro da zaključimo, naša varijabla sigurno je statična jer je izvan funkcije, nalazi se pri dnu RAM memorije, postojati će uvijek, biti će velika "char", dakle 1 byte i sadržavati će "unsigned" iliti samo pozitivne brojeve. Range varijable je [0-255].

Još jedno malo sitno pravilo:
Svaka statična varijabla na početku programa je 0 ukoliko nije drugačije navedeno. Compiler mora prije nego dođe do main funkcije sve statične varijable postaviti na 0. To znači da će naše stanje LED-a prilikom pokretanja programa sigurno biti 0, tj isključeno. Ako baš želimo da LED blinka prilikom uključivanja uređaja onda samo compileru kažemo ovo:

Code: Select all

unsigned char led_status = 1;      /* Blink Led status (0-OFF, 1-On)    */
Sve što compiler napravi prije nego li dođe do main funkcije je podešavanje STACK pointer-a i podešavanje statičnih varijabla, tako da će on postaviti led_status na 1.

U prilogu je čitav program. Dodao sam još neke stvari da me @HEX ne lupa po nosu na inicijalizaciji TIME-a kao što je reset svega bitnog na 0. (TCCR0A=TCCR0B=TIMSK0=GTCCR=0;) i napravio sam MACRO za LED.

Program u prilogu ne smije blinkati jer je LED status = 0;
Attachments
Tartufi.rar
(13.56 KiB) Downloaded 443 times
InTheStillOfTheNight
Post Reply