113
U potrazi za tartufima AVR-a "U potrazi za tartufima AVR-a" Uvod - 1. dio Davno sam pročitao jedan članak iz svijeta elektronike "Regulacija temperature sa DS1820", no u tom delikventnom razdoblju dobro sam spajao otpornike, relaye i zaljevao cvijeće, no nažalost pojma nisam imao što je to DS1820, a još manje što je AT89C2051. Nakon godinu dana muke s tim pojmovima, nabavom programatora i ulaskom u to sve, vrlo brzo sam zaključio kako ništa ne znam, ni danas ništa ne znam, a vjerojatno nikad ništa neću niti znati... To razdoblje zivota bavio sam se isključivo natjecanjima u robotici, elektronici, a opet pojma nisam imao, ili sam znao relativno malo više od drugih. Svi ti programi koje sam nekad pisao bili su metoda razmišljanja bez znanja, no to se kasnije pokazalo kao apsolutna ograničenost u programiranju. No zašto ovo pišem? Uvijek se sjetim mojih početaka kad smo internet vukli na slamku kroz 56k modem, i mislili da smo osvojili svijet ako znamo napraviti *.bat file... Tako nadobudni klinci kupovali smo Atmelove procesore, kao mi smo "programeri"... Sam pojam izazivao je strahopoštovanje... no ništa mi nismo znali, a informacije su bile nedostupne, a i ono što je dostupno bilo je, jel, na materinjem engleskom koji smo svi govorili tečno još u maternici... I tako mi smo programirali... Upali, ugasi. Ako si upalio probaj ugasit, ako si ugasio, a "pero" stisnuo tipku, odmah upali, no ako je to bio "mato" čekaj malo, pa upali, pa ugasi, pa provjeri "peru", pa "matu" pa "juricu". Dvije omiljene naredbe su WAIT i GOTO... Valjda svima početnicima srcu prirasle. I naravno nekako smo mi sve programe navukli da rade, ali nismo naucili programirati! Ovu temu otvorio sam jer ću pisati, kao što ste već skužili, samo o programiranju. Razlog tome je jer nitko nikad nije pisao o tome kada je to meni trebalo, ili nije postojao meni razumljiv tekst koji bih shvatio...

U Potrazi Za Tartufima AVR

Embed Size (px)

Citation preview

Page 1: U Potrazi Za Tartufima AVR

U potrazi za tartufima AVR-a

"U potrazi za tartufima AVR-a" Uvod - 1. dio

Davno sam pročitao jedan članak iz svijeta elektronike "Regulacija temperature sa DS1820", no u tom delikventnom razdoblju dobro sam spajao otpornike, relaye i zaljevao cvijeće, no nažalost pojma nisam imao što je to DS1820, a još manje što je AT89C2051. Nakon godinu dana muke s tim pojmovima, nabavom programatora i ulaskom u to sve, vrlo brzo sam zaključio kako ništa ne znam, ni danas ništa ne znam, a vjerojatno nikad ništa neću niti znati...

To razdoblje zivota bavio sam se isključivo natjecanjima u robotici, elektronici, a opet pojma nisam imao, ili sam znao relativno malo više od drugih. Svi ti programi koje sam nekad pisao bili su metoda razmišljanja bez znanja, no to se kasnije pokazalo kao apsolutna ograničenost u programiranju.

No zašto ovo pišem?Uvijek se sjetim mojih početaka kad smo internet vukli na slamku kroz 56k modem, i mislili da smo osvojili svijet ako znamo napraviti *.bat file... Tako nadobudni klinci kupovali smo Atmelove procesore, kao mi smo "programeri"... Sam pojam izazivao je strahopoštovanje... no ništa mi nismo znali, a informacije su bile nedostupne, a i ono što je dostupno bilo je, jel, na materinjem engleskom koji smo svi govorili tečno još u maternici...

I tako mi smo programirali... Upali, ugasi. Ako si upalio probaj ugasit, ako si ugasio, a "pero" stisnuo tipku, odmah upali, no ako je to bio "mato" čekaj malo, pa upali, pa ugasi, pa provjeri "peru", pa "matu" pa "juricu". Dvije omiljene naredbe su WAIT i GOTO... Valjda svima početnicima srcu prirasle.I naravno nekako smo mi sve programe navukli da rade, ali nismo naucili programirati!

Ovu temu otvorio sam jer ću pisati, kao što ste već skužili, samo o programiranju. Razlog tome je jer nitko nikad nije pisao o tome kada je to meni trebalo, ili nije postojao meni razumljiv tekst koji bih shvatio... Davno sam postavljao početnička pitanja po forumu i često sam dobio profesionalne odgovore koje nisam shvaćao. On meni tamo piše o nekim "registrima" čudnih imena, boja i oblika, a ja ništa ne kužim, kao da mi na "Turskom" objašnjava "križni put 12 apostola", i onda tek izgubim volju za programiranjem. Dobar dio tih tekstova o programiranju često izaziva totalno kompleksan i neshvatljiv sadržaj za početnike, i često svi "normalni" jednostavno odustanu prije nego što uopće krenu.

Kod knjiga je opet problem jer su pisane dosta opširno, sa previše nevažnih pojmova, kao da se Einstein s nekim natječe "Tko će napisati opširniju znanstvenu knjigu!"... Naime, dobro je znati sve stvari iz knjiga, no teško ih je u potpunosti držati u glavi, pa stoga i najbolji programeri često imaju podeblju knjigu programskog jezika kraj svog stola... Čisto iz razloga jer se neki dijelovi programskog jezika koriste rijetko, pa se dobro podsjetiti iz knjige...

U ovom topicu pisati ću o programiranju, ipak o suštinskom, a ne znanstevnom. Svaki programerski pojam pokušat ću objasniti na što jednostavniji i razumljiviji način svojoj baki, a istovremeno gledano sa programerske strane nepobitno točno.

Najteži početnički problem je napraviti programator, odabrati procesor, programski jezik i compiler, jer na internetu ima toliko toga da me ponekad zaboli želudac i pomislim da bih se

Page 2: U Potrazi Za Tartufima AVR

mogao pristojno ispovraćati od muke, kao i nauke...

Zato ću odabrati gotovo početničko rješenje na kojem će svi moji primjeri programa i opisi raditi dobro. Sve što ću pisati biti će za istu platformu, isti procesor, isti editor i naravno isti compiler.Razlog tome je jer ne želim paliti i gasiti ledice na svim procesorima ovog svijeta, dosta je monotno i naporno...S druge strane, nemoguce je nauciti programirati na 100 procesora. Nauči samo jedan, znaš ih sve, no ako učiš sve, nikad nećeš naučiti niti jedan...

Postoji samo jedno parvilo:Molim vas ne komentirajte temu, sve što vas zanima šaljite na PM, ili otvorite drugi TOPIC.Razlog tome je jer ne želim zagušiti ideju da pišem postove o programiranju koji su zatrpani sa 1 000 000 pitanja i komentara...Tema ce biti uvijek čista sa mojim postovima, a sva pitanja slobodno posaljite na PM i dobiti cete odgovore...Sve ono što mi se učini nejasno potruditi ću napisati post koji objašnjava nedoumice AVR procesora i programskog jezika C.I naravno postove neću pisati svaki dan jer sam previše zauzet, i nemam dovoljno slobodnog vremena da bih pisao toliko često...

Prvi post, prvi dio ne treba puno teksta:

MCU : ATMEGA32 - "Izgleda ko' čip, usudim se reći "JAK" čip AVR-a"

Editor : AVR Studio - "Izgleda ko' program za pisanje. Eh, u njemu mi tipkamo program..."

Compiler : WINAVR - "Ne izgleda. Samo ga koristimo kao prevodilac na strojni jezik i koristimo njegove biblioteke, tj gotove programe da ih ne pisemo ponovno jer je to vec netko drugi i iskusniji napisao..."

Programski jezit: C/assembler - "Jezik kojim govorimo procesoru što da radi! Tražio sam neki Turski ili Kineski jezik, ali ima samo "C".

Što je mikroračunalo?To je procesor. E baš nije, svi ga tako zovemo i dalje ćemo ga tako zvati no postoji bitna razlika.

Mikroračunalo i procesor su dva odvojena pojma. Svi mi to miješamo, pa ću pojasniti ono što čini te drastične razlike između "na oko" istog pojma.Procesor ili CPU je "Centralna Procesorska Jedinica" što bi značilo da procesor samo izvršava programskeinstrukcije. Procesor je onaj vrag u kompjuteru koji izvršava instrukcije sa vašeg Hard Diska, a rezultateeventualno pohranjuje u RAM na vašem računalu. Ako dublje zabijete nos u matičnu ploču skužiti ćete da su sve te stvari odvojene i razbacane po matičnoj ploči. Procesor ne može biti samostalan jer nema nikakve memorije iz koje bi izvršavao instrukcije. Jer, recite vi meni u koji procesor je moguće strpati Windows XP? Takav ne postoji. Windows se na računalo učitava sa Hard disk-a, i odrađuje djelomično na Hard disk-u, a djelomično u RAM memoriji, a svim time upravlja procesor(CPU).

Page 3: U Potrazi Za Tartufima AVR

Mikroračunalo ili "Microcontroller"(MCU) je čitav mali kompjuter strpan u samo jedan čip (Naravno, bez monitora). Samom izjavom da je to čitavo računalo dolazimo i do tih osnovnih djelova mikro računala. Nemamo Hard Disk, no zato imamo FLASH memoriju kojoj je svrha da memorira program koji želimo izvršavati. Imamo vlastitu RAM memoriju, imamo vlastiti procesor ili jezgu (core), i imamo vlastitu periferiju mikroračunala. Najbolje od tog svega je činjanica da se sve nalazi u samo jednom malom čipu.

I dalje ćemo ga zvati procesor, no čisto da se zna, to nije procesor nego mikrokontroler...

Koja je onda uloga compilera, i programskog jezika?Da bih mogao napisati ulogu moram napisati suštinsku povijest nastanka, pa ovaj daljni dio teksta pišem samo kao primjer razmišljanja prilikom nastanka mikroračunala:

Davno prije kada je nastao procesor kao izvršna jedinica ljudske kreativnosti nastale su i prve naredbe:

(HEX FILE)AABF DC52 110C2135 12B6

Izmišljam naredbe, no odprilike tako se programiralo. Prvi parametar je bio što treba napraviti, a ostali parametri ako ih je naredba zahtjevala bili su "odakle i gdje"? I sami ti kreativci nisu bili toliki konji da ne vide osnovni problem ovog pristupa programiranju -> Nečitljivo, teško, nerazumljivo!!! I osmislili su najniži programerski jezik "assembler". Zapravo su samo brojeve zamjenili sa slovima bližim ljudskom umu, pa onda ovaj isti tekst može izgledati ovako:

(ASM FILE)SEI MOV R21,R20LDI R19,0xFF

Ovako je program izgledao dosta čitljivije. Assemblerska naredba SEI (Set Enable I) izvršava "Enable Global Interrupts", assemblerska naredba MOV registar R20 kopira u registar R21, a LDI (Load Immediate) konstantu 0xFF upisuje u registar r19.Assembler kao programerski jezik ima puno većih ograničenja od tog teksta -> Sve moramo raditi na bazi DIGITRONA. Možemo samo zbrajati, oduzimati uspoređivati... te rezultate pohranjivati na željenu lokaciju u RAM memoriji ili ostavljati rezultate u radim registrima mikroračunala. To je bilo ograničeno ubijanje mozga sa glupostima, glupostima savršenstva.

Assembler je savršen programerski jezik, baziran na upravljanju svakog ciklusa i svakog događaja na računalu, no isto tako je besmisleno ubijanje mozga programera koji mora paziti na svaki korak mikrokontrolera. Drugi problem je što assemblerski napisan program može raditi isključivo na mikrokontroleru za koji je pisan, i jako teško ga je preseliti na drugi tip mikrokontrolera jer su drugačije i same naredbe, arhitektura, adrese registara...

Iz tog razloga nastali su viši programerski jezici koji glase ovako:

Page 4: U Potrazi Za Tartufima AVR

Broj1 = Broj2 + Broj3;

No nažalost ovaj tekst ništa ne znači za mikroračunalo, njemu jednostavno treba dati one "Guraj-Vuci" komande i za to postoji "Compiler".Stoga "Compiler" je program koji će programerski jezik višeg i razumljivijeg nivoa prevesti na primitivni nivo jednog mikroračunala. (10100010110110111100101010001110000000110101)

Sve programe ću pisati u C-u (jednostavnijem i višem programskom jeziku), a WINAVR koristim da mi to prevede na strojni (HEX) file, tj da moj jednostavniji nivo razmišljanja prevede tom primitivnom mikroračunalu.

Zbunjuje me totalno platforma. Rekoh moram odlučiti, i stalno važem , no nikako da više odlučim za koji AVR pisati. Napisao sam u prvom postu kako će to biti MEGA32, no to baš i ne spada u početnički AVR s obzirom da je dosta opširan. Isto pitanje vratio sam si 5 godina unazad i postavio ga ponovno. Dragi Josipe, s kojim bi ti mikrokontrolerom AVR-a volio početi? I gle odgovor je tu: Definitivno ATMEGA88. Možda mi je taj AVR malo prirastao srcu, no ta serija ATMEGA48, ATMEGA88 i ATMEGA168 ipak je najbolji početak. Zapravo radi se o procesoru koji je "skoro" identičan kao ATMEGA8(najpopularniji), no jači je u tome jer ima 1-wire Debug.

Što je 1 wire debug? Većina programera najprije programiraju, onda to upucaju u MCU, i metodom palac-oko procjene jel radi, ili ne radi. Ako nešto ne radi onda bulje u program i po njemu traže grešku. Dakle, vide oni jako dobro da to ne radi kako treba, no ne znaju zašto? Što dakle da čine, osim da kopaju po svom programu i razmišljaju gdje su se zeznuli. Iz tog razloga je nastao pojam "On-Chip Debug". Zamislimo da imamo 20 000 linija programa, i nešto nekad, iz čudnog razloga poludi. Prva metoda kopanja po programu odmah pada u vodu, jer nemoguće je toliko linija programa držati u glavi, a kamoli iz toga svega pronaći razlog zbog kojeg se program srušio. On-Chip Debug nam dozvoljava da sa PC-a izvršavamo instrukciju po instrukciju programa, da analiziramo sve što se događa na mikrokontroleru, mijenjamo sve što želimo, zaustavljamo program kad želimo, kako želimo. Svaki puta kada program zaustavimo imamo detaljan uvid u stanje svih memorija, svih registara i svega što se nalazi u mikrokontroleru. Nemojte miješati ovaj DEBUG sa simulatorom. Simulator simulira ono što bi se trebalo dogoditi, a one wire debug govori ono što se točno događa. 1 Wire Debug se ne izvršava na PC-u, on se izvršava direktno na mikrokontroleru i prema PC-u šalje sve informacije iz svoje memorije, stoga je točan prikaz onoga što se događa na vašem hardware-u.

Ovaj dio i nije tako bitan za početnike, no upravo zbog toga "one wire debug-a" ću odabrati ATMEGA88 jer mogu izvršavati program direktno na njemu, te imati uvid u sve što se događa, a samim time mogu bolje i objasniti razloge nekih događaja koje u suštini često predvidimo, ili ih nemamo čime pogledati da bi ih shvatili... A opet na kraju svega odabrao sam AVR koji je kao naštiman za početnike. Nije toliko opširan, a ima gotovo sve što ima i svaki AVR mikrokontroler.

Platforma za koju ću pisati jer nju imam doma je ova: CPU 4U4I http://www.inovatic.hr/default.aspx?id=69Baš gledam, ima ih tu nekoliko, no ovu drugu odozgora imam doma tako da ću sve pisati za nju, a sad tko voli nek si prevodi

Page 5: U Potrazi Za Tartufima AVR

Registracija na www.atmel.com je obavezna... no AVR studio je besplatan i može se skinuti ovdje:http://www.atmel.com/dyn/products/tools_card.asp?tool_id=2725&category_id=163&family_id=607&subfamily_id=760AVR Studio 4.18 (build 684)AVR Studio 4.18 SP3 (b716)

I još trebam prevodilac, opisan nedavno (WINAVR):http://winavr.sourceforge.net/download.htmlSkidajte uvijek zadnju verziju, trenutno vidim da je neka: 20100110

Znači ono što bi trebalo instalirati je ovim redom: AVR Studio 4, AVR Studio Service Pack 3, WINAVR.

"U potrazi za tartufima AVR-a" Misija - 1. Upaliti LED

Ovo je valjda najjednostavnije što sam mogao smisliti. Nećemo za sada blinkati LED-icom, niti ćemo čitati tipke. Sve što treba napraviti je upaliti jednu LED diodu. Mislim da ovo svi početnici znaju, a isto tako mislim da niti jedan početnik ne razumije što je napravio.

On tamo u bascomu ili nekom drugom programu upiše 3 linije programa i LED svijetli, a samim time ograniči svoju kreativnost jer ne razumije što se u pozadini uopće događa.

Evo i naše omiljene knjige u kojoj ima sve što trebamo, te puno više od toga. Pročitao sam je nekoliko puta, ovisno što sam kada radio i koje poglavlje mi je kad trebalo. Ovo nije "Biblija" da je čitamo po redu. I često ćemo je pročitati, ali našim redosljedom onoga što nas u kojem trenutku zanima.http://www.atmel.com/dyn/resources/prod_documents/doc2545.pdf

Ovako izgleda mikroračunalo ATMEGA88:http://img340.imageshack.us/img340/5056/mega881.jpg

Vidim ima tu nekih puno čudnih kvadratića, pa ću ja u svrhu ovog palenja LED-a obrisati one koji me ne interesiraju trenutno: http://img847.imageshack.us/img847/9530/mega882.jpg

Ovako je već bolje Napisati ću osnovne stvari o svakom kvadratiću, pa prije nego što možemo upaliti LED-icu ovo bi morali razumjeti.

1. Power Supervision POR/BOD & RESETOvo služi da na neki način nadgleda MCU, osigurava dobar Start izvršavanja programa, te nekakve zaštite od kritičnih situacija sa naponom.

POR -> "Power On Reset" ima ulogu da svaki puta kada uključimo mikroračunalo on resetira program na početak, tj postavi to početno stanje iz kojeg uvijek kreće rad mikroračunala.

BOD -> "brown-out detection"procesor ne smije izvršiti niti jednu krivu instrukciju, a može ih izvršavati 20 000 000

Page 6: U Potrazi Za Tartufima AVR

u sekundi, dakle jako brzo i puno instrukcija, a sve moraju biti bez greške. Jedini pravi problem je u naponu napajanja, te vanjskim smetnjama. Ako napon napajanja padne na kratko procesor može poluditi, a da se to ne dogodi služi ovaj BOD. Njega možemo uključiti ili isključiti. BOD monitorira napon na procesoru, te u slučaju da on padne ispod dozvoljene razine BOD će resetirati MCU kako bi program koji se izvršava krenuo od početka. Ovisno o onome što radimo, sami odlučujemo dali želimo da BOD štiti program restartom ili ne.

RESET -> Bio externi, interni POR, ili BOD ovo će resetirati procesor te program pokrenuti ispočetka.

2. FLASHTu ide onaj HEX file, program, instrukcije ili zovite to kako hoćete. To slikovito možemo zamisliti kao XXXX instrukcija od kojih se prva uvijek nalazi na adresi 0, a svaka sljedeća ima veću adresu. U MCU postoji nešto što se zove PC "Program counter", a to govori procesoru ili jezgri koju instrukciju treba izvršiti. Ovo govorim iz razloga jer je povezano sa resetom. Dakle kad resetiramo mikroračunalo onaj RESET će uvijek postaviti PC (program counter) na adresu 0. Dakle prva instrukcija koju će AVR jezgra izvršiti uvijek se nalazi na adresi 0 u FLASH-u. Postoji još nešto što se zove BOOTLOADER, pa se ta adresa može postaviti i na lokaciju BOOTLOADER-a, no za sada svi početnici uvijek trebaju znati da je prva instrukcija na adresi 0 u FLASH-u. ATMEGA88 ima 8k programske memorije(točnije 8192 bytea), seljački rečeno ima mjesta za 8192 broja od 0-255. Ti brojevi su instrukcije za AVR jezgru.

3.SRAMTamo se sprema ono što se zove varijabla u programiranju. AVR jezgra ima svoje radne registre gdje možemo držati nekakve brojeve, no njih je premalo i ne bi mogli u to strpati puno podataka. Zato postoji RAM memorija u koju procesor prema instrukcijama pohranjuje podatke, vuče ih nazad u registre te opet po programu nešto izvršava s tim podacima. Što je najgore da upalimo ledicu u ovoj našoj misiji, RAM nam nije potrebam, jer program je toliko jednostavan da ništa nećemo pohranjivati u RAM, samo trebamo napisati instrukcije za AVR jezgru koja će upaliti LED-icu. ATMEGA88 ima 1 kB RAM-a, ili seljački rečeno tamo može pohraniti 1024 različitih slova ili brojeva od 0-255;

Najbitnija razlika između RAM-A i FLASHA je ta da FLASH programiramo i tamo instrukcije ostaju bez obzira jel procesor na napajanju ili nije. One su tamo trajno upisane kako bi mogli isključiti MCU, a da naredbe ostanu sigurne. RAM memorija uvijek je prazna, tj sve je "log 0", prilikom restarta procesora, te tamo procesor pristupa prema našem programu kako bi nešto privremeno pohranio.

4.AVR CPU E to je procesor, jezgra. Na svaki "clock" koji dođe sa oscilatora jezgra izvrši jednu assemblersku instrukciju iz FLASH-a. U sekundi može ih izvršiti po pravilima datasheeta maksimalno 20 000 000. Za one koji žele znati koliko je to brzo: 1/20 000 000 = 0,000 000 05 Sekundi ili 50 nSKao programeri prisiljeni smo razmišljati u takvom vremenu stoga stvarnosti "bye-bye".

Page 7: U Potrazi Za Tartufima AVR

5. Oscilator Circuits/Clock GenerationDio procesora koji generira CLOCK za AVR jezgu te periferiju o kojoj ćemo kasnije govoriti puno više.AVR možemo svakako konfigurirati. U sebi ima ugrađen interni oscilator koji možemo podesiti da radi na MAX 8 000 00 Mhz, a isto tako možemo podesiti da se clock uzima sa vanjskog kristala MAX 20 Mhz. Isto tako možemo biti i dovoljno ludi da ga konfiguriramo na "Vanjski impuls" preko X2 pina, spojimo obični prekidač te mu mi govorimo u realnom vremenu da izvršava jednu po jednu assemblersku instrukciju.Za sada o clocku treba znati da je to takt rada jezgre, te za palenje LED diode bilo kakav takt nam odgovara, stoga ovaj dio ću ostaviti kako je podešen na mojoj ploči. Svaki taj takt CPU jezgra izvrši jednu instrukciju iz FLASH-a.

6. PORTE tamo su spojeni pinovi čipa. Nakon RESET-a mikroračunala svi PORTOVI (PORT B, PORT D PORT C) konfigigurirani su kao ULAZI. Postoje tu joše nekakva default pravila prilikom isporuke novog MCU-a iz atmela, pa recimo RESET linija nikad nije konfigurirana kao PORT, nego kao RESET pin... neću ih sad sve nabrajati jer još nisam pisao ništa o FUSE, stoga to ću ostaviti za neka druga vremena.

Program koji pali LED diodu trebao bi izgledati ovako:

1.Konfiguriraj PORT na kojemu je spojena LED-ica da bude IZLAZ.2.Postavi Stanje PORT-a da bude na logičkom stanju koje pali LED.3.Nemoj više ništa raditi. (ako je moguće).

Ispravak i korekcija smislenih neistina...

Naravno, @Kizo me prvi isprvio jer decko zna previše , no to je pozitivno, svi koji možete i znate puno obavezno me ispravljajte, jer nitko ne zna sve i dobro je da se tekst može korigirati za buduće čitatelje...

Kako već pišem za početnike ponekad da pojednostavim stvari ne pišem apsolutnu istinu, nego istinu koju bi početnik možda trebao znati prije nego se upusti u totalno nizak podhvat : "Razumjevanje mikroračunala". Postove pišem javno, pa stoga javno mogu pisati i neistine, no u svakom slučaju ako u teksovima napišem nešto što baš i ne bi trebalo biti tako napisano jer je pretjerano daleko od prave istine obavezno mi pošaljite poruku kako bi na kraju dobili što jednostavnije i smislenije upute za učenje programiranja AVR-a.

Sve tekstove koje pišem ću tjekom vremena iznova ažurirati i dorađivati, stoga je sasvim uredu da u tekstu može biti eventualnih propusta i grešaka. Oni koji su čitali postove ranije primjetili su možda da ATMEGA88 radi na 16 000 000. No to nije sasvim točno jer u datasheetu piše da radi na 20 Mhz. Stoga na takve propuste upozoravajte jer većinu teksta pišem iz glave, a ne iz datasheeta s obzirom da bi mi trebalo puno više vremena da sve što napišem provjerim u datasheetu. Zapravo se trenutno borim sa nekim ARM procesorom koji je svoja vrsta kompleksne nauke, a njegov datasheet ima oko 900 stranica, stoga postoji mogućnost da pomješam sve te informacije u jednu trenutnu neistinu na forumu...

Page 8: U Potrazi Za Tartufima AVR

Hvala na uvaženim isprikama.

"U potrazi za tartufima AVR-a" Misija - 1. Upaliti LED

Dakle malo sam se igrao instrumentom oko platforme UI 4x4 i skužio da postoji spojena LED dioda i RELAY koji se nalaze na PINU 23 mikroračunala ATMEGA88. Pogledam u datasheet, onu knjigicu sa 300 stranica i vidim da se radi o PC0 (PORT C 0). Također sam primjetio da se ta LED-ica i RELAY pale sa logičkom nulom. (0V). Stoga program mora postaviti PC0 kao izlaz, te na njega upisati logičku nulu, i po mogućnosti više ništa ne raditi.

No moram malo odlutati od teme:

Znate li da moj laptop ima 32 bitni procesor? Vaš je možda 64 bitni, pa vas pitam koliko je to bitno?Bitno je, naravno da je bitno. Vaš je 64 bitan, a moj samo 32 bitan, stoga je vaš duplo bitniji nego moj? Što znače ti bitni brojevi 32,8,64?

Evo jedan 8 bit-ni broj: (BIN)11111111Evo jedan 32 bitni broj: (BIN)11111111 11111111 11111111 11111111 Po tome 64 bitni broj sastoji se od 64 znamenke ( 0 ili 1 ).

No zašto je to bitno?Zaključili smo kako procesor samo izvršava instrukcije: zbroji, oduzmi, usporedi, poguraj, povuci, skoči tamo-vamo. 32 bitni, 64 bitni ili 8 bitni procesor znači broj bitova s koliko on računa "nešto" u jednoj assemblerskoj instrukciji.ATMEGA88 je 8 bit-no računalo, pa mogu zaključiti kako je maksimalni broj s kojim on zna računati: BIN:11111111, HEX:FF ili DEC:255. Za prebacivanje brojeva iz DEC, u HEX ili BIN najjednostavnije je koristiti Windows Calculator. Dobro je za početnike uhvatiti se malo www.google.com i proučiti kako se računa u HEX, DEC i BIN jer to su osnove koji bi ipak trebali znati.

Vraćam se temi LED-ice na portu PC0. Kako 8 bit-no računalo uvijek računa isključivo sa 8 bit-ova zato sve u tom računalu uvijek izgleda kao 8 nekakvih stanja (0 ili 1). Pa tako i portovi imaju imena od recimo PB0 do PB7.

Kako to u AVR studio izgledaju ti registi PORT-a C?http://img857.imageshack.us/img857/7311/mega883.jpg

"U potrazi za tartufima AVR-a" Misija - 1. Upaliti LED

Ako se malo bolje zagledate u zadnju objavljenu sliku, skužiti ćete kako svaki PORT AVR-a ima 3 registra:DDRCPINCPORTC

Page 9: U Potrazi Za Tartufima AVR

Opisati ću važne stvari oko ova 3 registra (Barem važne kad želimo upaliti LED)...

DDRC -> Data Direction Register COvaj registar sadrži informacije o onoj nožici mikrokontrolera (Ulaz ili izlaz). (Samo za PORT C, 8 Bitno računalo, 8 linija, 8 svega)Procesor ili jezgra iz FLASH-a izvršava instukcije koje mogu upisati na ovaj registar 8 nula ili jedinica. A samim time naš program će sigurno tamo morati upisati "00000001" jer mi želimo izlaz na PC0, pošto želimo upaliti LED-icu koja se tamo nalazi. Ako bi imali spojenu LED-icu na PC7, onda bi u DDRC morali upisati ovo: 10000000 (BIN).

Prva nožica PORTA C, uvijek se zove PC0, a zadnja PC7.

Već sam naveo kako su svi PORT-ovi po default ULAZI, pa sada mogu napisati da logička "0" znači ulaz.

Kada se procesor uključi na napajanje te POR (Power on Reset) resetira CPU , te Program Counter bude postavljen na adresu 0 gdje se nalazi prva instrukcija FLASHA) svi registri PORT-ova biti će 0, tj ulaz. Lako je dakle zaključiti kako mi moramo na DDRC upisati "00000001" (BIN) kako bi postavili PCO kao izlaz.

Nemojte mješati PORT C sa PC0 -> Port C je čitav PORT (8 nožica mikroračunala), a PORTC0 je nulta nožica od tih 8... Dakle sve je 8 (PC0 - PC7).

PINC-> "Dvoličan registar" -> Može biti jedno, ali ako znaš malo više o njemu znaš, onda je sasvim drugo...Ovaj registar ovisi o tome kako smo konfigurirali smijer podataka (DDRC) registar. Za sada ćemo predpostaviti kako smo smijer (DDRC registar) odabrali kao izlaz zbog tog krajnjeg cilja palenja Led-a, onda PINC registar ima jednu ličnost...

Prva ličnost PINC registra....

Stavka 1: Ako ga čitamo, on će nam sigurno reći kakvo je stanje našeg izlaza -> Ovo može biti korisno ukoliko pri programiranju ne želimo pratiti kada smo izlaz postavili na LOG 1, te kada smo ga postavili na LOG 0. Jednostavno pročitamo PINC registar i tamo piše jeli izlazna linija na 0V ili na 5 V...

Stavka 2: Imena TOOGLE -> Također korisna stvar. Ako na ovaj registar upišemo logičku jedinicu, izlaz kakav god bio će promijeniti trenutno stanje. Dakle, ako je bio 0 postati će 1, a ako je bio 1 postati će 0. Korisno je ukoliko recimo želimo blinkati LED-icom, ali nas ne zanima jeli LED trenutno upaljen ili ugašen, stoga samo na PINC registar upišemo 1 (na poziciju PC0 -> 00000001), i sigurni smo da će taj PC0 promjeniti stanje izlaza...

O drugoj ličnosti ovog registra govoriti ćemo kada budemo nožicu mikroračunala koristili kao ULAZ.

PORTC -> Također "dvoličan" registar... Drugačije se ponaša kada je DDRC registar konfiguriran kao ulaz, a drugačije kad je izlaz. Trenutno ću opisati ličnost, samo kad je izlaz, jer to i je krajnji cilj ideje.Ako na PORTC upišemo log "0", nožica mikrokontrolera će biti 0V povučena sa

Page 10: U Potrazi Za Tartufima AVR

tranzistorom, stoga kroz ovu logičku "0" možemo vući struju za LED. Ako u PORTC registar upišemo LOG "1", tada će nožica mikrokontrolera biti 5V, no također 5V PNP zatvorenog tranzistora, stoga i iz ove situacije možemo također vuči struju...

Ovo je bitno ukoliko je netko radio sa 8051 arhitekturom, koja sve izlaze ima open-collector. Na 8051 postoji samo jedan tranzistor koji izlaz vuče na 0V te je 0V jedino iskoristivo za vući struju dovoljnu za palenje LED-a, dok AVR ima 2 PUSH-PULL tranzisotra koji mogu koristiti i 0V i 5V da bi povukli struju iz mikrokontrolera...

Kod 8051 bitno je kako okrenemo LED diodu, jer iskoristiva je jedino LOG 0 za dovoljnu struju koja može upaliti LED, dok kod AVR-a kako god je okrenemo, koristili LOG "0" ili LOG "1" mikrokontrolera, uvijek imamo osiguranu dovoljnu struju za LED.Za početnike dobro bi bilo proučiti pojmove: "Open Collector", "Push-Pull"Evo i neki link koji sam onako "odoka" metodom pronašao:http://www.ecircuitcenter.com/circuits/pushpull/pushpull.htm

Tranzistor kao sklopka ovisno o tipu PNP, ili NPN može liniju potrošača povlačiti na 0V (NPN), ili na 5V (PNP).Kod 8051 postoji samo jedan NPN tranzistor, stoga izlaznu liniju moramo vući na 0V ako želimo iskoristiti struju tog tranzistora. Kod AVR-a imamo 2 tranzistora, jedan je zadužen da može povući struju prema 0V, dok je drugi zadužen da je može povući prema 5V.

Dosta zbunjeno ovo zvuči, no vjerujem da će biti jasnije kada budem programirao, te sve ove primjere prezentirao...

Mislio sam slikati fotićem konfiguraciju i programiranje, ali kasnije mi je došla ideja zašto to da ne snimim i dignem na youtube?? Onda sam pogledao u monitor preko fotića, i skužio da se ništa živo ne vidi. Pa sad sam u potrazi za nekim programom koji može snimati ono što se događa na monitoru...

Ne znam postoji li takvo nešto, no mislim da bi moralo postojati, jer ne vidim razlog da ne postoji, stoga ako netko zna takav program neka pošalje PM. Iskreno, još nisam ni kopao o tome po www.google.com jer sam danas planirao napisati barem jedan post o ovim registrima, pa sad bih ipak trebao nekakav takav program da sve ovo o čemu serem mogu snimiti, kako bi to zajedno dobilo nekakav smisao teksta...

Ovo o čemu sam danas pisao nalazi se na stranici 71 datasheeta one knjigice koju ne čitamo kao "Biblija":13.2 Ports as General Digital I/O Page(71)

"U potrazi za tartufima AVR-a" Misija - 1. Upaliti LED

Hvala, @feko, program je odličan... Budem dizao na youtube, jer mislim da je tako najbolje i sigurno najpreciznije kako napraviti palenje LED-a.

Jedina obavezna funkcija u programskom jeziku C je int main(void); i to po standardu C-a znači početak programa. Netko bi mogao pomisliti kako se to nalazi na adresi 0 u FLASH-u, no ti nije sasvim točno. Compiler će za nas odraditi nešto drugo na adresi 0 u FLASHU, a funkcija main biti će nešto malo dalje od adrese nula FLASH-a. Ovo ću kasnije objasniti detaljno, no za sada moramo znati da je svaki početak programa u programskom jeziku C

Page 11: U Potrazi Za Tartufima AVR

uvijek funkcija "main", dok će se prevodilac pobrinuti da to dobro prevede na assemblerske naredbe.

Evo youtube link kako napisati program palenja LED-a.

http://www.youtube.com/watch?v=sbcrKH5aDco"Prebaciti u HD"

Code:Code: Select all

#include <avr\io.h>int main(void){    DDRC = 1;              while(1);       }

Evo nedoumice koje bi mogle biti nejasne...

Nisam pisao po PORTC registru? Zašto?Već sam do sad naveo kako su PORTC, PINC, te DDRC prilikom reseta mikrokontrolera uvijek logička nula, no naveo sam kako se LED na platformi UI4x4 pali sa logičkom nulom.Dakle nisam morao ići upisati 0 na PORTC 0 jer je taj registar po defaultu 0 nakon reseta, stoga sve što sam morao napraviti je ulaz konfigurirati kao izlaz. DDRC = 1; Nakon izvršavanja ove naredbe LED se pali.

#include <avr\io.h>Da bih nešto upisao u registar moram znati na kojoj se on adresi nalazi. Naveo sam kako ćemo koristiti biblioteke compilera WINAVR, stoga sam u svoj program ubacio njihovu gotovu biblioteku u kojoj se nalaze sve adrese ovih registara. Kada god hoćete pisati po port registrima morate napraviti ovaj include.

"{" "}" - otvorena zagrada znači početak glavne funkcije programa int main(void);int znači da funkcije može vratiti nekakav int parametar, no o tome ću pisati kada budem pisao o funkcijama. Void znači da funkcija nema atributa, što ću također pisati kada budem pisao o funkcijama.Za sada treba znati kako je ovo osnovna funkcija svih programa, i što god da radili u AVR studio, ona mora postojati i uvijek označava početak programa.

while(1); ili sve dok je jedan, no uvijek je 1 pa tako dobivam beskonačnu petlju. U jednom od postova napisao sam da procesor mora "ne raditi više ništa", no to se ne može baš tako jednostavno napraviti. Procesor je prisiljen na svaki clock izvršiti jednu instrukciju.

Stoga mu ja kažem da izvršava tu svoju jednu instrukciju, a ona glasi "sve dok je 1, izvršavaj isti sve dok je 1".

Dakle procesor će zaglaviti na toj naredbi, te je stalno izvršavati, a zapravo neće raditi ništa pametno. Time smo postigli da se program nikad ne završi ili izgubi, te da LED svijetli...Postoje niz načina kako zaglaviti procesor. while(1) je programska petlja koja se izvršava sve dok ovaj parametar 1 ne postane 0, a mi

Page 12: U Potrazi Za Tartufima AVR

smo ga u samoj naredbi postavili na 1, te on nikad neće postati 0.

klik na build/rebuild all prevodimo C napisan program na strojni HEX jezik, te time kreiramo hex datoteku koju treba upisati u FLASH mikrokontrolera.

Fileove neću dizati na forum, jer mi nije cilj da početnik ima gotov program, jer poanta priče nije imati gomilu programa, nego pisati sve sam i učiti se kako ovo programirati.

"U potrazi za tartufima AVR-a" - Juriš na assembler, bitka kod HEX-a

Ovaj naslov je i tako ispred svog vremena i davno sam odlučio da će takav biti. Prije ovoga mislio sam napisati post o "upucavanju" Hex file-a u ATMEGA88, no to ću napraviti kasnije.Razlog tome je naravno bucin topic o assembleru i drago mi je da se odlučio pisati takvo nešto. Cilj posta je svakako prezentacija odnosa snaga assemblera i C-a. Program palenja LED-a u C-u sam već napisao, no sa strane assemblera ovo izgleda potpuno drugačije. Bitka kod hex-a svodi se na najnižu razinu na koju se u programiranju možemo spustiti... "Dno-Dna" -> Sve ispod ovoga bi bilo dizajniranje vlastitiog računala...

Što je HEX file i kako izgleda?Samo ime HEX govori da je datoteka zapisana u Hexadecimalnom brojevnom sustavu: (0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F)

Evo jedan red HEX file-a koji ću rastaviti i objasniti značenje HEX file-a:(Ovo je prvi red iz HEX file-a za palenje LED-a i programa kojeg sam napisao u C-u)

:1000000019C020C01FC01EC01DC01CC01BC01AC00C

Ovako je to zapisano u HEX datoteci, no ubaciti ću nekoliko razmaka u taj red jer moram red drugačije formirati da bi razlikovali stvari koje se nalaze u jednom redu...

: 10 0000 00 19C0 20C0 1FC0 1EC0 1DC0 1CC0 1BC0 1AC0 0C

Hex rastavljen prema redosljedu logičkih cjelina:: - označava početak reda u HEX datoteci. (Početak HEX zapisa)

10 - označava broj data byteova u tom redu. Ne radi se o 10 byteova nego o 16. Hex je pisanu Hexadecimalnom sustavu, pa probajte kalkulatorom prebaciti HEX 10 u DEC sustav i to je 16Tih 16 znači da u jednom redu postoji 16 "data" byteova.

0000 - Adresa na koju se upisuje data zapis HEX filea. Kao što vidite prvi red u HEX datoteci zapalenje LED-a ima adresu 0000 jer to je prva naredba koju će izvršiti AVR jezgra.

00 - ovo označava tip zapisa hex filea. 00 označava "DATA" zapis. U INTEL HEX fileu postoji 6 različitih tipova zapisa, no kad piše "00" sigurno se radi u DATA zapisu koji u sebi sadrži 16 bitnu adresu.

Page 13: U Potrazi Za Tartufima AVR

Nakon descriptora jednog reda idu instrukcije: 19C0 20C0 1FC0 1EC0 1DC0 1CC0 1BC0 1AC0.Veličina svake instrukcije AVR jezgre je 2 byte-a. Postoje assemblerske instrukcije koje su 4 byte-a jer u 2 byte-a jednostavno je nemoguće napraviti sve instrukcije AVR-a. No to nam nije toliko bitno jer kada bi analizirali svaku naredbu skužili bi koja je 4 byte-a a koja 2 byte-a. Ovaj dio HEX filea sam izvukao iz palenja LED-a, pa također mogu zaključiti kako će AVR jezgra za prvu instrukciju sigurno izvršiti 19C0(HEX).Bez obzira što ovo nitko nikad ne mora koristiti bitno je zapamtiti da svaka assemblerska instrukcija za AVR jezgru može biti 2 ili 4 byte-a.

Ako znamo koliko je velika jedna instrukcija, jednostavno je izračunati koliko instrukcija assemblera može stati u ATMEGA88. Ako imao 8192 Bytea FLASH-a, onda sigurno u to može stati duplo manje assemblerskih instrukcija jer svaka instrukcija treba minimalno 2 byte-a.8192/2 = 4096 -> ATMEGA88 može pohraniti u FLASH 4096 assemblerskih instrukcija

Ovo su instrukcije koje se nalaze u jednom redu HEX file-a. Prvi byte u HEX fileu označava broj data byte-ova -> 10(HEX), 16(DEC).Dakle idam zbrojiti koliko ima tih byteova assemblerskih instrukcija...

19C0 - 2 Bytea20C0 - 2 Bytea1FC0 - 2 Bytea1EC0 - 2 Bytea1DC0 - 2 Bytea1CC0 - 2 Bytea1BC0 - 2 Bytea1AC0 - 2 Bytea________________Total = 16 bytes(DEC) (10HEX)

Moramo izvuči i zaključak ovoga -> U jednom redu HEX filea postoji max 8 assemblerskih naredbi, ili 16 data byteova za AVR jezgru.

Osatao je i famozni kraj HEX filea koji uvijek završava sa jednim byteom. U ovom slučaju piše -> "0C"Kontrolni byte jednog reda reda HEX file-a zove se "checksum"Dakle taj byte potvrđuje vjerodostojnost svih byteova u tom HEX redu i računa se na sljedeći način:

Ponovno mi treba čitav red HEX-a ->:1000000019C020C01FC01EC01DC01CC01BC01AC00C

Zadnji byte ću obrisati kako bi ga mogao ponovno izračunati.

:1000000019C020C01FC01EC01DC01CC01BC01AC0

Prvo što radim je to da razdvojim to na byteove ->

10 00 00 00 19 C0 20 C0 1F C0 1E C0 1D C0 1C C0 1B C0 1A C0

Page 14: U Potrazi Za Tartufima AVR

Pokrenem windows calculator, prebacim ga u HEX i računam ovo:10 + 00 + 00 + 00 + 19 + CO.... -> Zbrojim sve byteove u Hex redu. Osim zadnjeg kojeg smo obrisali jer njega računamo.Rezultat koji dobivan je 06F4. Broj koji sam dobio veći je od jednog byte-a pa uvijek uzimam niži byte -> "F4". U calculatoru računam "F4 XOR FF" i za relultat dobivam "0B".Dodam ovom "0B" rezultatu "01" i dobivam "OC". Dakle, OC mora biti na kraju ovog HEX reda. Evo univerzalna formula:Checksum HEX reda računam tako da od zbroja svih byteova uzimam onaj niži (manje važeči), te računam XOR sa 0xFF i uvećavam rezultat za 1. Bitno je primjetiti da sam izbrisao checksum prilikom njegovog računanja i taj zadnji byte ne ulazi u formulu za proračun checksuma.

Dakle ovaj zadnji byte je izračunat prema svim ostalim byteovima u istom redu HEX filea samo kako bi mogao dokazati vjerodostojnost podataka u istom redu. Ako promjenimo samo jedan BIT u čitavom HEX redu checksum neće odgovarati rezultatu checksuma i samim time naš HEX file nije dobro zapisan.

Koja je u tome najnižem nivou moć assemblera? Ako možemo sami pisati HEX te izračunati sve što je potrebno za FLASH MCU-a što mi onda radimo? Programiramo ili pišemo brojeve?

Hex file je u principu strojni assembler, ovi brojevi su assemblerske naredbe koje su nazvane imenima čisto da bi komande imale smisla. U notepadu to izgleda kao broj, no radi se o čistoj, od majke rođenoj assemblerskoj instrukciji.

Tu nastaje ta prva rupa između C-a i assemblera. U c-u mi ne možemo utjecati na HEX file jer ga generira compiler, i on odlučuje kako će napraviti HEX file, dok u assembleru nas zapravo boli "k" za sve, jer mi smo ti koji odlučujemo o svakoj instrukciji. Mi smo oni koji slažu instukciju za instrukcijom i nitko drugi osim nas ne može utjecati na redosljed instrukcija AVR jezgre.

Žalosna istina na kraju ovog dijela napada na HEX je sljedeća:Da bi mi upalili LED potebne su 3 assemblerske instrukcije (@buco vjerojatno zna i kraći način, no trenutno mi se ne kopa po assemblerskim instrukcijama da bih ovo napravio kraće)

Možda se pitate zašto HEX file koji je napravio compiler WINAVR iz C-a izgleda ovako:Code: Select all

:1000000019C020C01FC01EC01DC01CC01BC01AC00C:1000100019C018C017C016C015C014C013C012C034:1000200011C010C00FC00EC00DC00CC00BC00AC064:1000300009C008C011241FBECFEFD4E0DEBFCDBF82:1000400002D004C0DDCF81E087B9FFCFF894FFCFA5:00000001FF

dok Hex file koji sam kasnije napisao u assembleru izgleda ovako:Code: Select all

:0600000081E087B9FFCF8B:00000001FF

Vidite da je zadnji red HEX file-a identičan, i ako njega rastavimo po pravilima HEX file-a

Page 15: U Potrazi Za Tartufima AVR

dobijemo ovo00 0000 01 FF. 00 -> Broj byte-a je 00000 -> Adresa zapisa je 001 -> Tip zapisa više nije 00 nego je 01. Ovo u HEX-u znači kraj file-a. FF -> Onaj dosadni checksum -> Zbroj byteova je 01 -> 01 XOR FF = FE, FE + 1 = FF -> Dakle checksum mora biti FF

U svakoj HEX datoteci može postojati samo jedan kraj file-a i uvijek se označava sa tipom zapisa "01". Orginalna ideja (iz 1970) tog kraja je da u njemu postoji i adresa prve instrukcije koju izvršava jezgra, pa je u ovom našem HEX fileu dobro upisana adresa na 0000. No ovo u stvarnosti baš ne drži vodu jer ne bira HEX file odakle će jezgra startati, nego to bira FUSE AVR MCU-a o kojemu ćemo naravno kasnije.

Ovaj tekst oko HEX file-a nije potrebno znati, ali onaj tko hoće može si bez problema otvoriti HEX file i analizirati sve instrukcije AVR jezgre.

Zašto ja pišem ove gluposti o HEX-u? Postoji samo jedna razlika između pravih programera, i dakako onih koji to nisu. Što napraviti kad nastane problem??? Bascom, C i svi jezici koji nisu assembler u sebi imaju nešto što možemo nazvati RANDOM BUG, pa tu i tamo znaju popizditi i napraviti glupost. Te gluposti mogu biti ili neznanje programera da kaže compileru što želi, ili compiler jednostavno ni sam ne zna kako da napravi to što programer želi, pa napravi sranje od HEX file-a.

Snaga programera je samo u daljini koliko seže njegovo znanje te oružja s kojima raspolaže. Ako ja napadam "problem", koji ne želi odustati, sigurno da ću otići u najgoru moguću krajnost assembera i HEX-a, dok programer C-a ili BASCOM-a mora ostati na razini višeg jezika i problem ne može riješiti narednih 200 godina.

Povući ću jednu zanimljivost o HEX file-u. Dok ne znate što je to svi mislite da se upisuje u FLASH MCU-a, no ti nije istina. Kada pogledamo što sve postoji u HEX redu vidimo da postoje gluposti kao adresa, broj byte-ova u jednom redu, tip zapisa i checksum reda.Koji bi to konj napravio MCU i upisao u FLASH čitav hex? Ono što se stvarno upisuje u FLASH MCU-a su samo instrukcije, a sve ostalo što se nalazi u samom HEX-u potrebno je da bi se znalo kamo što treba upisati.Dakle u FLASH MCU-a ne ide HEX, nego samo instrukcije za jezgru. HEX je takav kakav je samo da bi protokol upisivanja FLASH-a znao kamo što upisati, pa mogu zaključiti kako za prvi red HEX-a u MCU FLASHA biti će upisane samo instrukcije: 19C0 20C0 1FC0 1EC0 1DC0 1CC0 1BC0 1AC0Istinski FLASH u AVR-u nije HEX, nego samo instrukcije poslagane upravo onako kao HEX file opisuje.

"U potrazi za tartufima AVR-a" Misija - 1. Upaliti LED (2. dio)

U C-u je nešto kompliciranije upravljati pojedinim izlazima na MCU. Jasno je kako se LED dioda nalazi na PC0, no postavlja se pitanje kako da upravljate sa PC0, a ne dirate ostale linije porta C?

Page 16: U Potrazi Za Tartufima AVR

Ako vi izjednačite registar smijera podataka sa nekim brojem "DDRC = 1;" upravo ste prepisali svih 8 bitova. DEC "1" je BIN "00000001" što bi značilo da je zadnji desni bit PC0.

Činjenica je da smo mi na točnu poziciju upisali "1", ali je isto tako činjenica da smo na sve ostale linije upisali ponovno "0", a to nismo željeli. Što ako je neki od njih bio "1" i morao ostati "1" ?

Možda se pitate zašto u C-u "AVR Studio, WINAVR" ne postoji ono PC1, PC2, PC3, pa da mi mozemo upisati samo jedan bit a ne svih 8?

Odgovor na to pitanje čisto je političke naravi: Programski jezik C je striktan sa puno pravila kojih se moramo držati u programiranju. Neki alati C-a tipa "codevision" imaju implementiranu mogućnost jednostavnog pristupa "bit-u", dok većina njih nema... No da se mi ne bavimo politikom, prava pravila C-a nemaju nikakve komande tipa PC0, PC1, PC2 ( na bazi jednog bita) i upravo zato AVR studio nije htio kršiti pravila C-a i napraviti nekakve svoje komande koje ne postoje u programskom jeziku C.

Jednom "bit-u" u AVR Studiu pristupamo operatorima "AND" i "OR"

1. Postavljanje prvog desnog bita (PC0) DDRC registra u logičku jedinicu radimo ovako:DDRC |= (1<<0);

ovo je kraći zapis i ja ga uvijek koristim, a duži zapis je ovo:DDRC = DDRC | (1<<0);

DDRC -> "Data direction register C" -> registar za smijer podataka porta C

| -> oznaka za "OR" operaciju u programskom jeziku C (ALT GR+W)

(1<<0) -> broj 1 pomičem u lijevo za 0 mjesta.

; -> kraj naredbe u C-u

Najveći prioritet u C-u su zagrade "()"- Program c-a uvijek računa sve s desne strane jednakosti i tada to upiše u ono što se nalazi na lijevoj strani. Ono što je u najvišem prioritetu je (1<<0), stoga se ovaj red izvršava sljedećim redosljedom:

"00000001" guram u lijevo za "0" mjesta i dobijem isto to: "0000 0001";

sada radim "ili" operaciju dobivenog broja sa DDRC registrom:(DDRC) 00000000 ILI(1<<0) 00000001________________(DDRC) 00000001

"ILI" operaciju sa 2 byte-a svi bi trebali znati. Ako je u bilo kojem byte-u na nekom mjestu logička jedinica rezultat će sigurno na istom mjestu imati logičku jedinicu. U našem slučaju radi se o PC0 koji je postao log "1", a samim time PC0 smo postavili kao izlaz.

Potrebno je i dokazati da smo promijenili stanje samo prvog bita, a to ćemo napraviti da zamislimo kako je DDRC registar trenutno "10101010" i mi na istom registu radimo istu

Page 17: U Potrazi Za Tartufima AVR

stvar:(DDRC) 10101010 ILI(1<<0) 00000001________________(DDRC) 10101011

Jasno da smo radeći "ILI" operaciju promijenili samo prvi bit (krajnji desni) u logićku jedinicu. Svi ostali su identični.

Ali zašto sam ja šiftao broj "1" za "0" mjesta u lijevo? Lijen sam, neda mi se razmišljati... Slučajno se pogodilo da se radi o PC0 "bit-u" pa mi je to bilo jednostavnije napisati ovako: DDRC |= 1;

No što ako se radi o PC7 bitu??? Idemo probati PC7 bit podići u logicku jedinicu:(DDRC) 00000000 ILI(------) 10000000________________(DDRC) 10000000

Vidimo da moramo raditi "ILI" operaciju sa brojem BIN: 10000000, HEX: 0x80, DEC 128Kao programerima neda nam se pisati u BIN jer ima puno nula i jedinica, ružno je i nečitljivo, HEX 0x80 i DEC 128 nekako je glupo pamtiti jer onda za 6-ti bit opet moramo pamtiti koji je to HEX, BIN ili DEC broj...

No ako smo već toliko ljeni pamtiti bin, hex i dex onda barem znamo izvesti trik:Pa zasto ja ne bi onda uzeo obicni broj "1" i samo ga pogurao za broj porta?Zadnji bit je sigurno PC7, pa ako njega dizem u log 1 najprije radim (1<<7), dakle gurnem broj "1" "7" puta u lijevo i znam da sam dobio ovo: 10000000. Ako želim bilo koji drugi PC0,1,2,3,4,5,6,7 samo poguram broj "1" toliko puta u lijevo. Ovo svi C compileri dobro razumiju i neće napraviti veći program zato jer mi imamo više teksta. Program je identičan u sva tri slučaja:DDRC |=(1<<7); // DEC TRIKDDRC |= 128; // Razmišljanje o DEC brojuDDRC |= 0x80; // Razmišljanje o HEX broju

Postoji još jedan dobar razlog zašto se radi (1<<XX). Ako sami računamo masku, tj taj broj s kojim radimo ili operaciju za jedan BIT onda imamo izbor ovo: HEX-> 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80Onako "BUM" iz glave vas sad pitam sta je maska za PC4 i svi ćete morati razmišljati koji je sad to po redu... Ali ako znamo da se radi o PC4 uvijek mozemo napisati ovo (1<<4) i sigurno smo 100% tocni... Taj jedan razlog zbog kojeg se ovo radi je smanjivanje ljudske greske u racunanju... Nama je i cilj da sve sto napisemo bude 100% tocno... Pisanjem ovog guranja broja "1" u lijevo bili bi kreteni da se zajebemo, dok računanjem maske tipa 0x10 ipak je veća mogučnost pogreške.

Dosta sam pročitao programa što su pisali programeri ATMEL-a i ovaj način koriste i oni za pristup jednom bit-u te podizanje log 0 u log 1 stoga gledam si, ako su u stanju napraviti takvo računalo moram biti prisiljen vjerovati da je ovo najbolji način.

Evo konfiguracija izlaza za čitav port C:

Page 18: U Potrazi Za Tartufima AVR

DDRC |= (1<<0); //Config PC0 as outputDDRC |= (1<<1); //Config PC1 as outputDDRC |= (1<<2); //Config PC2 as output DDRC |= (1<<3); //Config PC3 as outputDDRC |= (1<<4); //Config PC4 as outputDDRC |= (1<<5); //Config PC5 as outputDDRC |= (1<<6); //Config PC6 as outputDDRC |= (1<<7); //Config PC7 as output

Sve nakon "//" je komentar i tim načinom komentiramo ono što programiramo.

Ovaj način podizanja logičke nule nema veze sa DDRC registrom, to je standard C-a i dizanje bilo kakve nule u jedinicu u C-u na bazi jednog BIT-a uvijek se ovako radi. Ovo koristimo na varijablama i na svim registrima i općenito na svemu u C-u. Ako se radi o registru DR_MR_FR_GR koji ne postoji u AVR-u opet je ista stvar. Za prvi desni bit opet radimo:DR_MR_FR_GR |= (1<<0);

ako se radi o varijabli C-a u ram-u imena "sabahudin" za prvi desni bit radimo ovo:sabahudin |= (1<<0);

Dakle totalno univerzalna stvar za pristup jednom bit-u u C-u..

Čitav ovaj post govori o tome kako podići logičkI "0" u logičkI "1", no nisam spomenuo kako "1" vratiti nazad u "0".

Stvar je slične naravi, radi se sa "AND" operatorom i objasnim u sljedećem postu.

Za ovaj post dovoljno je zaključiti kako se izlaz za ledicu koja se nalazi na PC0 programira ovako:

DDRC |= (1<<0);

I kada to tako napišete to je jasno kao dan i meni, i svima koji programiraju u C-u. Pogotovo iz razloga jer programeri ATMEL-a rade identičnu stvar i svakako trebamo kopirati njihove trikove jer smo maleni za nešto pametnije.Ovaj post se ne tiče samo AVR-a nego programskog jezika C, tako da se ista stvar može primjeniti na svim mikrokontrolerima koje programiramo u C-u.

"U potrazi za tartufima AVR-a" Misija - 1. Upaliti LED (3. dio)

Spuštanje jednog BIT-a iz log "1" u log "0" nam ne treba da bi upalili LED na PC0, ali će nam sigurno trebati kasnije. Ove metode spuštanja i dizanja jednog BIT-a koristimo gotovo uvijek kada pristupamo registrima MCU-a pa tako da bi taj dio morao biti jasan.

Spuštanje PC0 BIT-a u logičku nulu:__________________________________kraći zapis:DDRC &= ~(1<<0);

Page 19: U Potrazi Za Tartufima AVR

duži zapis:DDRC = DDRC & ~(1<<0);__________________________________

DDRC -> "Data direction register C" -> registar za smijer podataka porta C

& -> oznaka za "AND" operaciju u programskom jeziku C (SHIFT+6)

~ -> "Ones Complement" operator za okretanje stanja svih bitova (ALT GR+1)

(1<<0) -> broj 1 pomičem u lijevo za 0 mjesta.

; -> kraj naredbe u C-u__________________________________

Opet krećemo od onog guranja broja "1" u lijevo (1<<0), dakle poguram broj 1 u lijevo za 0 mjesta i dobijem isto to BIN: 00000001

Sad ide ovaj čudni i nelogični znak "~", a on uvijek okreće stanje svih bitova. Uzmemo naš rezultat za (1<<0) i okrenemo mu sve bitove:

00000001 ~________11111110

i sada radimo "AND" operaciju sa DDRC registrom:(DDRC) 00000001 AND~(1<<0) 11111110___________________(DDRC) 00000000

AND operaciju bi svi trbali znati: Ako je bilo koji od BIT-ova logička "0", rezultat će za taj BIT uvijek biti logička "0".

Ovim načinom sigurno smo spustili jedan BIT (PC0 -> krajnji desni) u logički "0", no da bi dokazali da nismo promijenili ostale BIT-ove opet moramo uzeti slučaj kada oni nisu bili logička "0";

Dakle, zamislim da je DDRC registar trenutno BIN: 00110011, i samo želim spustiti PC0 u logičku nulu.

Prvo radim ovo: (1<<0) i rezultat je BIN: 00000001Nakon toga izvršavam "~" i time okrenem stanje svih bitova te dobijem rezultat 11111110Sada radim "AND" operaciju sa DDRC registrom:

(DDRC) 00110011 AND~(1<<0) 11111110___________________(DDRC) 00110010

Page 20: U Potrazi Za Tartufima AVR

Kada radimo maskiranje sa AND operatorom vidimo da naša maska BIN 11111110 propušta sve logičke jedinice osim prve, i zato je DDRC registar zadržao sve logičke jedinice nepromjenjenima osim naravo one koju smo i htjeli spustiti u logičku nulu.

Evo konfiguracija ulaza za čitav port C:DDRC &= ~(1<<0); //Config PC0 as inputDDRC &= ~(1<<1); //Config PC1 as inputDDRC &= ~(1<<2); //Config PC2 as inputDDRC &= ~(1<<3); //Config PC3 as inputDDRC &= ~(1<<4); //Config PC4 as inputDDRC &= ~(1<<5); //Config PC5 as inputDDRC &= ~(1<<6); //Config PC6 as inputDDRC &= ~(1<<7); //Config PC7 as input

U ovim postovima oko dizanja i spuštanja jednog BIT-a napisani su i objašnjeni primjeri za PC0, zbog ledice koju palimo, ali ovo su osnove koje moraju biti jasne da bi mogli bilo što raditi dalje. Bez ovoga ne možemo napisati niti jednu liniju programa i svakako bi svi oni koji ovo ne razumiju trebali uzeti olovku i papir i sami sebi dokazati zašto se to tako radi, i kako se računa. Programska osnova je jednostavna i imamo samo 2 kombinacije pisanja, pa evo na kraju u programu konfiguracija izlaza za čitav port C, te konfiguracija ulaza za isti PORTC, jer ako budete testirali moje linije vjerojatno ćete zaboraviti napraviti #include <avr\io.h>, a to je objašnjeno u prošlim postovima. Što se tiče razloga zašto opet guram broj "1" u lijevo identičan je kao i u prošlom postu.

Code: Select all#include <avr\io.h>

int main(void){    DDRC |= (1<<0); //Config PC0 as output    DDRC |= (1<<1); //Config PC1 as output    DDRC |= (1<<2); //Config PC2 as output    DDRC |= (1<<3); //Config PC3 as output    DDRC |= (1<<4); //Config PC4 as output    DDRC |= (1<<5); //Config PC5 as output    DDRC |= (1<<6); //Config PC6 as output    DDRC |= (1<<7); //Config PC7 as output

    DDRC &= ~(1<<0); //Config PC0 as input    DDRC &= ~(1<<1); //Config PC1 as input    DDRC &= ~(1<<2); //Config PC2 as input    DDRC &= ~(1<<3); //Config PC3 as input    DDRC &= ~(1<<4); //Config PC4 as input     DDRC &= ~(1<<5); //Config PC5 as input    DDRC &= ~(1<<6); //Config PC6 as input    DDRC &= ~(1<<7); //Config PC7 as input            while(1);  //Do Nothing}

"U potrazi za tartufima AVR-a" Zavjera sa vremenom

Svaki novi projekt koji krećemo programirati jednostavno mora imati redosljed. Kao

Page 21: U Potrazi Za Tartufima AVR

programeri ne možemo u glavi držati čitav program, nego ga moramo razbiti na 1000 jednostavnih stvari. Uvijek prvo konfiguriramo ulaze i izlaze (DDRB, DDRC, DDRD registri). Radimo identičnu stvar kao kad palimo LED na PC0. To je jednostavno napraviti jer imamo hardware i točno znamo na kojem PIN-u je što spojeno i samo prema tome radimo gore opisane konfiguracije smijera podataka.

Kad to napravimo dolazimo do najtežeg problema svih početnika, a to je "vrijeme". Bilo koji tečaj za početnike nije istina, lažu vam u osnovnoj školi pa onda u srednjoj. Kamo god se okretine lažu vam. Čitavo vrijeme nas uče krivo razmišljati da bi onda postavljali nemoguće misije programiranja...

Najbolji i najjednostavniji primjer je blinkanje LED-icom. Uče nas ovako:Code: Select all

#include <avr\io.h>#include <util\delay.h>

int main(void){

    DDRC |= (1<<0);    //Config PC0 as output

    while(1){ // do forever

        PORTC &= ~(1<<0); // Led_on

        _delay_ms(1000);  // wait 1S

        PORTC |= (1<<0);  // Led_off

        _delay_ms(1000);  // wait 1S    }}

Istina da se ovim načinom programiranja postiglo to da LED stvarno blinka, ali isto tako je istina da vi više ništa živo ne možete dopisati u ovakav program. Već sam naveo kako je snaga programiranja u našoj brzini izvršavanja instrukcija. (AVR može izvršiti jednu instrukciju za 0.000000050 S). Kada u programu napišete _delay_ms(1000); upravo ste uništili jedino ono što u programiranju imamo, a to je brzina. Upravo ste rekli mikrokontroleru da svoju dragocjenu brzinu potroši na čekanje jedne sekunde.

MCU ne radi ništa pametno čitavu sekundu, zbraja i oduzima jedan te isti broj, samo da bi prošlo vrijeme od jedne sekunde. Vi kao programeri u toj situaciji morate znati da nemate vremena bilo što raditi, jer ste upravo MCU natjerali da ne radi apsolutno ništa čitavu jednu sekundu.

Kako bi ja sad nekom drugom LED-icom blinkao svakih 0.010 sekundi? U ovom programu očito nikako jer nemam dovoljno vremena zbog velikog _delay_ms(1000);

Rekao sam i u prvom postu da ću pisati o suštinskom programiranju i ne želim vam govoriti kako se ovako programira. Od sada i na dalje zaboravite na _delay_ms naredbe i sve naredbe vezane za čekanje u programu. Programirali vi u bascomu, basicu, ili ne znam čemu naredba WAIT, DELAY, SLEEP za vas ne postoji u mikrokontrolerima... Da bi znali dobro primjeniti delay naredbu najprije moramo naučiti programirati bez nje...

Page 22: U Potrazi Za Tartufima AVR

Naš program nikada i nigdje ne smije stati, a opet moramo programirati u realnom vremenu. Moramo znati stvoriti 1 sekundu vremena, a da je ne čekamo. Mikrokontroler uvijek mora trčati po čitavom programu kao muha bez glave i nikada ne smije stati i čekati. (barem ne u realnom vremenu).

Naredba čekanja koristi se samo u malim dozama, pa često u pisanju komunikacijskih drivera sa vanjskim hardwareom moramo malo sačekati ako emuliramo protokol komunikacije. Taj delay uvijek je brz i radi se o svega nekoliko uS. Tu programsku žrtvu moramo raditi jer nemamo implementiran Hardware MCU-a za sve što spajamo na MCU, no dok god koristimo protokole komunikacije koji su hardwerski podržani na mikrokontroleru nećemo koristiti delay.Ovo je svakako uvod u nešto težu temu, a to je blinkanje LED diodom na PC0.

"U potrazi za tartufima AVR-a" Zavjera sa vremenom (2-dio)

Kao početnika uvijek me mučilo to kako netko može napisati 1000 linija programa. Koliko god se ja trudio uvijek sam se izgubio u 50 linija programa. U jednom segmntu čekam, a u drugom već kasnim jer odrađujem prvi segment... Muka po svim apostolima...

Vjerujem da većina onih koji čitaju moje postove misle da sam im odsjekao noge i ruke u programiranju ako sam rekao da se delay ne smije koristiti. Nije to samo pitanje ATMEL-a nego pitanje svih mikrokontrolera ovog svijeta.

Tu analizu programiranja vrlo je jednostavno testirati na svim do sad napisanim programima. Pokušajte uzeti bilo koji program sa delay naredbama i obrisati ih sve.... (bez obzira koji MCU programirali)Jedino što možete zaključiti da mikrokontroler "leti" po čitavom programu, ljudskom umu teško shvatljivom brzinom. Upravo takav program trebamo pisati, jer dok god naš program leti maksimalnom brzinom imamo neograničene mogućnosti programiranja.

Postavlja se pitanje: Kako možemo čekati 1 sekundu, ako naš program ne smije čekati?

Odgovor je u suštini jednostavan:Program nigdje neće stati, ali zato će svaki krug koji napravi pogledati u sat.

Svi mikrokontroleri imaju TIMER, a sve što mi moramo napraviti je da nam TIMER bude naš sat koji za nas prati vrijeme.

Ovo možda zvuči trivijalno, ali nije. Da bi TIMER podesili kako treba morati ćemo naučiti puno pojmova vezanih za mikrokontrolere.

Sve konfiguracije koje radim ponavljam svaki puta iznova. Datasheet Timera do sada sam pročitao barem 50 puta. I danas podešavam TIMER, a to radim zbog potrage tartufa opet iznova otvaram isti datasheet. Bitno je znati da ovo ne učimo napamet. Ono što bi trebali naučiti je čitanje tehničke dokumentacije. Bitno je poznavati logiku kojom radi TIMER, i nakon toga je nevažno koji MCU uzeli jer ako znamo čitati dokumentaciju, te ako razumijemo

Page 23: U Potrazi Za Tartufima AVR

kako radi brojač tog trenutka svi mikrokontoleri postaju identični upravo zato jer znamo kako im pristupiti.

dakle opet link na datasheet:http://www.atmel.com/dyn/resources/prod_documents/doc2545.pdf

Odabrati ću TIMER/COUNTER 0 koji za nas u narednim postovima mora postati SAT i osloboditi nas mučnih DELAY naredbi..

Timer 0 nalazi se na stranici 89 ovog datasheeta, no preskočiti ćemo uvod i otići na stranicu 90, te malo se zadržati na block diagramu TIMER-a 0:

Evo nekoliko pitanja i odgovora koje možemo zaključiti samo gledajući u block diagram...Prvo moramo razdvojiti pojmove TIMER i COUNTER. Ovo nije samo TIMER, a isto tako nije niti COUNTER. U navici nam je da ga zovemo TIMER, ali zapravo je TIMER/COUNTER. Stvar je samo u načinu konfiguracije. Ako pogledamo desni gornji kut digrama vidjeti ćemo jedan block koji se zove "Clock Select".Upravo on čini razliku između TIMER-a i COUNTER-a. U "Clock Select"-u postoje 2 ulaza. Jedan je "From prescaler" a drugi je "Tn"

Što je "From Prescaler"?Hardrerski implementiran Prescaler ili djeljitelj frekvencije radi upravo ono što i samo ime govori. Može djeliti frekvenciju oscilatora sa nekim brojem. Na ovoj slici nije nacrtan prescaler, ali sve što za sada o njemu moramo znati je da nam on dovodi impulse sa oscilatora. Ti impulsi koji dolaze na prescaler su točni, te neovisni o programu MCU-a. Pošto oscilator MCU-a uvijek oscilira na točnoj frekvenciji, onda su i ovi impulsi točni kao i oscilator. Kada TIMER/COUNTER konfiguriramo da broji impulse sa prescaler-a tj svog oscilatora onda se radi o TIMERU, a ne COUNTERU.

Što je "Tn"Tn je PIN MCU-a, a služi kako bi mogli koristiti TIMER/COUNTER da nam broji vanjske impulse. Ovo može biti korisno ako trebamo brojati nekakve impulse, a ne želimo to raditi analizom signala te programom. TIMER/COUNTER za nas može brojati vanjske impulse a mi u programu samo moramo pročitati koliko je on impulsa nabrojao u nekom vremenu. Ako brojimo impulse sa PIN-a Mikrokontrolera onda se definitivno radi o COUNTERU, a ne TIMERU.

COUNTER nas ne zanima trenutno, jer mi želimo brojati impulse našeg oscilatora kako bi mogli pratiti vrijeme.

Od ostalih registara za sada nam je bitan "TCNTn". Taj registar je brojač, i u njemu se povećava vrijenost na svaki clock koje uđe u TIMER.

Ako malo bolje pogledamo naziv timer-a vidjeti ćemo da piše 8 Bit TIMER/COUNTER 0, a ovaj "8-Bit" govori o tome koliko BIT-ova ima TIMER. Ako ih ima 8, kao i sve u tom MCU onda on može brojati do 255, a nakon toga dogodi se overflow ili preljev te TIMER počinje ponovno brojati od "0". Ovaj dio odrađuje "Control Logic" koji je vidljiv u diagramu."Control Logic" upravlja radom TIMER-a te se brine oko Gornje i Donje vrijednosti TIMER-a. U diagramu je vidljivo da postoji TOP i BOTTOM koji ulaze u "Control Logic",

Page 24: U Potrazi Za Tartufima AVR

a isto tako vidimo da može brojati napred, brojati nazad, te resetirati brojač. Kako će TIMER raditi na kraju krajeva ipak odlučujemo mi.

Trenutno ćemo predpostaviti da konfiguriramo TIMER samo da broji napred tj. da se na svakom impulsu koji dođe sa oscilatora (iz prescalera/1) poveća za "1".

Da bi koristili TIMER moramo znati izračunati koliko brzo on broji:Zamislimo situaciju da naš MCU radi na 8 Mhz, i također zamislimo da smo TIMER konfigurirali da broji impulse oscilatora. Malo se moramo pozabaviti proračunom i izračunati koliko TIMER-u treba vremena da dođe do vrijednosti 255, tj koliko mu treba vremena da napravi čitav krug (od 0 do 0)? Prvo nas zanima koliko traje jedan pulse sa oscilatora: To računamo tako da koristimo dobro znanu formulu koja povezuje period i frekvenciju T=1/f. Dakle "1" djelim sa 8 000 000 i dobivam 0,000 000 125 Sekundi. (clock je 8 Mhz)

TIMER zapravo ima 256 stanja, brojevi od (0-255) pa će jedan krug TIMER-a biti 256 impulsa, a ne 255.Ako brojimo impulse 8 Mhz oscilatora onda će se naš TIMER povećavati svakih 0, 000 000 125 Sekundi, a ako se mora povećati 256 puta da bi napravio čitav krug i ponovno završio na broju "0" onda će proći točno0, 000 000 125 * 256 = 0, 000 032 Sekundi.

Dakle naš timer će na frekvenciji 8Mhz napraviti čitav krug za 32 uS...Jasno je da 32 uS nema nikakve veze sa realnim vremenom, no morali smo ovo računati kako bi znali izračunati vrijeme koje mi želimo.

U programiranju već dugo koristim identičnu metodu konfiguracije TIMER-a, i to radim odmah nakon što konfiguriram ulaze i izlaze MCU-a. Opet ponavljam kako uvijek krećemo od DDRC, DDRD i DDRB registara, a kad to napravimo odmag idemo na računanje vrijednosti TIMERA.

U svim mikrokontrolerima koje programiram uvijek konfiguriram TIMER da mi stvori vrijeme od 1mS, dakle 0.001S

Ovo mi je najbolje zato jer ako vrijeme 1mS pomnožim sa 1000 dobivam ravno jednu sekundu. Kada konfiguriramo TIMER vidjeti ćemo načine kako se od 1mS jednostavno napravi sekunda vremena.

U ovom proračunu koji sam pisao vidljivo je da će TIMER napraviti krug svakih 32 uS, a nama treba 1 mS, tako da ćemo morati ubaciti još nekoliko pojmova kako da nam TIMER napravi krug svakih 1 mS, a ne 32 uS

Daleko smo još od pisanja programa, jer najprije moramo znati što želimo, a da bi to napisali u programu treba nam sigurno nekoliko programskih linija i svega 5 minuta vremena.

Analizirajući gornji proračun TIMER-a na frekvenciji 8 Mhz zaključili smo kako 8 Bit-ni TIMER 0 napravi čitav krug za 0.000032 S ili 32 uS. No to nama nikako ne odgovara. Najbolje bi bilo kada bi on napravio krug za točno 1mS, ali kako to već biva "Murphy" često gura nos u elektroniku i programiranje.

Page 25: U Potrazi Za Tartufima AVR

Ako je jedan krug 32 uS lako je zaključiti da TIMER broji previše brzo. Nekako ga moramo usporiti, a ne želimo mjenjati naš 8 Mhz kristal oscilator. Upravo zato i postoji prescaler u AVR-u. On nam omogućava da ulaznu frekvenciju oscilatora podijelimo s nekim brojem.

Dakle opet link na datasheet (Page 138)http://www.atmel.com/dyn/resources/prod_documents/doc2545.pdf

Opet promatramo samo sliku te idemo od početka, a to je "clk I/O" koji se nalazi gore lijevo na block diagramu. CLK I/O uvijek je frekvencija oscilatora, pa ako koristimo kristal 8 Mhz za pokretanje MCU-a onda je CLK I/O točno 8 Mhz. Ono što prvo moramo znati je tekst za koji piše CK/8 CK/64 CK/256 i CK/1024.

TIMER 0 i TIMER 1 koriste zajednički prescaler, no to nas trenutno ne treba zanimati, nego prvo idemo izračunati sve frekvencije koje možemo izvući sa prescalera te ih koristiti za TIMER.CK/8 = 8 Mhz / 8 = 1 MhzCK/64 = 8 Mhz / 64 = 125 khzCK/256 = 8 Mhz / 256 = 31,250 khzCK/1024 = 8 Mhz / 1024 = 7812,5 Hz

U prošlom postu sam računao vrijeme za krug TIMER-a kao da djelim 8 Mhz sa prescalerom 1, pa možda vam je nejasno zašto njega nema? Ima ga, naravno da ima, samo ne ide preko prescalera, nego ide odmah prema CS odabiru prescalera što je također vidljivo iz block diagrama.

Što mislite postoji li signal ove frekvencije na svim linijama koje smo računali u trenutku kada upalimo MCU? Odgovor je DA!

Broje li TIMERI po defaultu nakon palenja MCU-a ? Odgovor je NE, svi su zaustavljeni.

Zaključimo dakle kako prescaler odmah nakon palenja MCU-a na napajanje počinje generirati impulse ovih izračunatih frekvencija, a naš timer ipak stoji čvrsto na nuli. Razlog tome nalazi se opet na slici iste stranice 138 datasheet-a.

U ovoj priči tek sada dolazimo do onoga s čime možemo upravljati iz programa. Svi TIMER-i bilo kojeg AVR-a imaju registar imena TCCRx, ili ponekad više njih u slučaju da konfiguracija timera ne stane u jedan registar. Puni naziv AVR registra ili više njih je "Timer Counter Control Registar -TCCR". U jednom ili drugom registru ovog naziva upravo se nalazi odgovor kako pokrenuti TIMER. TCCR registar sigurno ima 3 BIT-A koji se zovu CS00, CS01 i CS02.

Vratiti ćemo se ponovno na istu sliku i pronaći CS na slici. CS ili puni naziv "Clock Select" na TIMER prespaja signale sa prescalera. Upravljanjem CS bitovima registra mi biramo što želimo spojiti na TIMER, pa ako se malo bolje zagledamo u sliku skužiti ćemo da za za TIMER0 možemo odabrati ovo:

Frekvencija 8Mhz koja ide direktno gore sa CLK IOFrekvencija CK/8 = 1MhzFrekvencija CK/64 = 125 khzFrekvencija CK/256 = 31,250 khz

Page 26: U Potrazi Za Tartufima AVR

Frekvencija CK/1024 = 7812,5 HzT0 - Frekvencija za vanjskog pina MCU-a

Za TIMER 1 možemo odabrati identičnu stvar, samo ako brojimo vanjske impulse onda se radi o drugom PINU koji se zove T1, a ne T0.

Reći ću vam jednu tajnu -> Ovo je ovako napravljeno kod AVR-a, ali osnovni model TIMER-a identičan je od samog nastanka TIMER-a. Bilo koji drugi MCU je slična stvar. Registri se drugačije zovu, slike izgledaju nešto drugačije, pa čak je i sam koncept drugačiji, ali kad svedemo na ono čemu TIMER služi onda zapravo zaključimo da je to sve isto. Čitajući datasheet na isti način možemo podešavati sve TIMER-e ovog svijeta.

"U potrazi za tartufima AVR-a" - Opet na mukama početnika

Mislite li da je ovo stvarno? Što je tu opipljivo, a što samo imaginacija? Govorim li ja o programskom jeziku C?

Programski jezik C zadnji puta sam spomenu kada sam govorio o laganju i DELAY naredbama, davno prije dosta postova. Kao sad ja govorim tu o nekom programiranju, a uopče ništa ne programiram? Pisanje programa uopće nema veze o ovome što govorim. Govorim li ja o imaginaciji? Imaginacija nema veze o ovome što govorim. Što dakle? Ovo je onda valjda ta zadnja gornja solucija nazvana "Stvarnost".

Neki to zovu Hardware pa ćemo i mi reći da je to hardware mikrokontrolera. Što mislite o čemu razmišljamo @buco - assembler, @noćni - C i @kizo - Bascom??? Razmišljamo li mi o linijama programa? Neee. Svi programeri bez obzira u kojem programskom jeziku pisali razmišljaju samo o jednoj te istoj stvari. HARDWARE MCU-a. Sve ovo o čemu pišem postoji u mikrokontoleru, doduše sitno, ali postoji.

Ponekad naletim na početnike koji mislie da je TIMER pojam kao recimo WINDOWS... TIMER nije pojam, on je stvaran kao i obični otpornik ili kondenzator. Moji zadnji postovi o TIMER-u nemaju nikakve veze sa programiranjem u C-u, programiranjem u BASCOM-u, Pascalu ili assembleru...

Kako onda kizo iz BASCOMA konfigurira TIMER??? Pa isto kao i ja jako dobro zna što je prescaler, što je CS, ali nekom BASCOM metodom napiše nešto tipa "CONFIG TIMER 0 As NEKI VRAG"

Kako buco konfigurira TIMER? Pa isto kao i ja, zna točno sve o čemu pišem i samo napiše assemblerske instrukcije koje podese registre TIMER-a

Kako ja konfiguriram TIMER? Pa isto kao i Buco i Kizo, samo iz C-a upišem u registre TIMER-a konfiguraciju kakvu želim.

Ono što želim reći je da svaki programer bez obzira u čemu programirao mora znati kako radi Hardware TIMER-a, a sada kako izgleda samo pisanje programa nas uopće ne interesira... Ne postoji niti jedan programski jezik u kojemu možemo programirati, a da ovo o čemu pišem ne znamo...

Page 27: U Potrazi Za Tartufima AVR

Svaki početnik trebao bi znati da ja ne govorim o C-u, Bascomu niti assembleru. Ja govorim trenutno o mikrokontroleru, o hardware-u a ne software-u, pa i ako mislite da ste dobri u recimo BASCOMU zapitajte se koliko znate o samom mikroračunalu kojeg programirate... Iz bascoma vrlo lako možemo programirati i nekoliko godina, a da uopće ne znamo s kakvim računalom radimo. Možemo živjeti u takvoj zabludi da mislimo kako postoji samo DELAY, FLASH i RAM, a sve ostalo što i je čudo tehnologije vrlo lako nam klizne pokraj očiju, i onda kao sljepac tapkamo u mraku misleći da smo programeri.

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

Do sada bi svakako trebali znati da TIMER broji isključivo impulse sa Prescalera ili vanjskog pina, isto tako moramo znati da prescaler uvijek i samo uvijek djeli frekvenciju sa "main" clocka.

Ovom pričom svakako zaobilazim jednu bitnu stvar AVR hardware-a imena FUSE. Uzeo sam zdravo za gotovo kako je AVR FUSE konfiguriran za "External Crystal Oscillator", te kako ga pogonim sa 8Mhz kristalom. AVR mikroračunala imaju implementiran i interni oscilator, pa ukoliko pogonimo MCU sa internog oscilatora moramo znati točnu frekvenciju rada kako bi mogli računati postavke TIMER-a. Princip računanja je identičan.Evo primjer za 4 Mhz vanjski kristal osilator, te krug TIMER-a 0 na prescaleru / 1Djelim frekvenciju sa prescalerom 1 i dobijem istu frekvenciju 4 Mhz.Računam periodu te frekvencije po "t[s]=1/f[Hz]" formuli i dobijem 1/4000000 = 0,00000025 SekundiMnožim vrijeme jednog impulsa sa 256 kako bi mi TIMER napravio puni krug: 0.00000025*256 = 0.000064S Dakle na 4 Mhz kristal oscilatoru 8 Bitni TIMER će napraviti jedan krug za 64 uS.

Iz ovog proračuna lako je zaključiti da smo smanjivanjem frekvencije MCU-a sa 8 Mhz na 4 Mhz upravo povećali vrijeme jednog kruga TIMER-a sa 32uS na 64 uS. Zdravo seljački rečeno: duplo smanji brzinu mikroračunala i TIMERU treba duplo više vremena da nabroji čitav krug.

Ponovno se vraćam na cilj konfiguracije TIMER-a 0 te 8 Mhz oscilatoru i svakako cilju konfiguracije TIMER-a da napravi krug svake 1 mS. Zaključili smo kako prescaler /1 (8 Mhz) pogoni TIMER prevelikom brzinom, pa ću ponovno napraviti PASTE frekvencija sa Prescalera, te pokušati pronaći neku frekvenciju koja mi najviše odgovara:

1. CK/1 = 8 Mhz / 1 = 8 Mhz -> Jedan krug 8 Bitnog TIMER-a: 32 uS2. CK/8 = 8 Mhz / 8 = 1 Mhz3. CK/64 = 8 Mhz / 64 = 125 khz4. CK/256 = 8 Mhz / 256 = 31,250 khz5. CK/1024 = 8 Mhz / 1024 = 7812,5 Hz

Računam za stavu "2" krug TIMER-a (1 Mhz):Frekvenciju 8 Mhz dijelim sa prescalerom / 8 i dobivam frekvenciju 1 Mhz koju odabirem za pokretanje TIMER-a. Računam period 1 Mhz frekvencije formulom T=1/f i dobivam 1/1000000 = 0.000001 SekundiMnožim vrijeme jednog impulsa sa 256 kako bi mi TIMER napravio puni krug: 0.000001 * 256 = 0.000256 S

Page 28: U Potrazi Za Tartufima AVR

Sad samo pogledam rezultat i vidim da je i ovo previše brzo. Timer će napraviti krug za 256 uS, a ja baš želim 1 mS. Svakako moram uzeti sljedeći prescaler i ponovno računati istu stvar za njega.

Računam za stavu "3" krug TIMER-a (125 khz):Frekvenciju 8 Mhz dijelim sa prescalerom / 64 i dobivam frekvenciju 125 khz koju odabirem za pokretanje TIMER-a. Računam period 125 khz frekvencije formulom T=1/f i dobivam 1/125000 = 0.000008 SekundiMnožim vrijeme jednog impulsa sa 256 kako bi mi TIMER napravio puni krug: 0.000008 * 256 = 0.002048 S

Upornog li TIMER-a, baš vraga ne možeš natjerati da napravi puni krug za 1 mS. Čitavo vrijeme vam govorim kako podešavamo TIMER na 1mS puni krug, ali ipak znam da je to nemoguće bez mjenjanja kristal oscilatora.

Ružno je reći da sam lagao, prikladiji izraz je: Namjerno nisam rekao istinu:Zapravo sam tražio samo vrijeme koje je veće od 1mS. Na frekvenciji 125 khz naš TIMER će napraviti krug svakih 2,048 mS i kada nađemo prvi prescaler koji je veći od 1 mS onda je to sigurno prescaler koji koristimo. Naš program morati će podesiti Hardware TIMER-a 0 na prescaler CK/64.

Fora je u tome što TIMER-u možemo smanjiti krug, ali ga ne možemo povećati jer krov timera ili TOP vrijednost je MAX 255 (8 Bitni TIMER). Taj krov je hardwerski maksimalna vrijednost i nikako ne može biti veća. U daljnjoj konfiguraciji TIMER-a moramo znati da TIMER ne mora brojati do 255. Postoji mogućnost da mu mi kažemo točan broj do kojeg on broji, samo mora biti manji od 256. Ako možemo smanjivati gornju vrijednost TIMER-a jednostavno je zaključiti da smanjivanjem gornje vrijednosti TIMER-a mi smanjujemo i vrijeme jednog kruga. Ako TIMER 0 napravi krug na nešto više od 2 mS, sve što mi moramo napraviti je natjerati TIMER da ne pravi čitav krug nego recimo pola kruga.

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

Zamislimo da želimo napraviti SAT kojemu je Tick svakih 100mS a ne 1 ms??? Što onda radimo?Identičnu stvar. Pogledamo kolika je frekvencija oscilatora, te krenemo računati prescalere. Kad najdemo prvi prescaler koji za krug timera ima vrijeme veće od 100 mS upravo smo izračunali prvi dio posla...

Opet se vraćam svojoj 1 mS, te onome što smo do sad mogli zaključiti. Računajući prescalere sa 8 Mhz kristala došli smo do onoga koji nama treba. Dakle CK/64 prescaler će sigurno napraviti krug Timera za 2.048 mS.

Kada sam opisivao prescaler spomenuo sam da on ima 3 bita imena CS0 CS1 CS2.Ono što ja prvo tražim u datasheetu je tablica prescalera. Atmelov datasheet pisan mi je na jako razumljiv način. I kada pronađem tablicu prescalera, znam da mi odmah iznad te stranice mora biti REGISTER te lokacija vezanih Bitova.

Page 29: U Potrazi Za Tartufima AVR

Dakle opet link na datasheet (Page 105)http://www.atmel.com/dyn/resources/prod_documents/doc2545.pdf

U tablici je jasno vidljivo da ona povezuje stanje CS bitova i prescaler Timera 0, pa onda samo pronađemo onaj koji želimo koristiti: "clkI/O/64 (from prescaler)"CS02 = 0CS01 = 1CS00 = 1

Ako ovako postavimo "CS" ili "Clock Select" bitove za Timer 0 onda moramo znati da će TIMER odmah početi brojati sa frekvencije 125 khz te će napraviti krug svakih 2.048mS

Odemo na stranicu 104 datasheta i evo naš registar koji sadrži bitove CSTCCR0B – Timer/counter control register BFOC0A FOC0B – – WGM02 CS02 CS01 CS00

Bez obrira na programski jezik u kojemu programiramo mi na prvi i drugi desni BIT, dakle CS00 i CS01 moramo upisati logičku jedinicu kako bi pokrenuli TIMER sa željenog prescalera "/64"

Još jednu stvar moram napomenuti a to je razlika između CS00,CS01,CS02,CS10,CS11,CS12,CS20,CS21,CS22 ????Odkud ih sad toliko??? Budete li kopali po datasheetu sigurno možete pronaći baš toliko CS bitova... Zašto toliko?Zato jer imamo 3 TIMER-a, Evo CS od TIMER-a 0: CS00, CS01, CS02 -> TCCR0B registerEvo CS od TIMER-a 1: CS10, CS11, CS12 -> TCCR1B registerEvo CS od TIMER-a 2: CS20, CS21, CS22 -> TCCR2B register

Dakle, samo ukazujem da ne smijemo mješati kruške i jabuke. Ako smo počeli podešavati TIMER 0, onda obavezno moramo pronaći sve točne tablice i diagrame za TIMER 0.

Sad se možda pitate kako to izgleda u programskom jeziku C?Podizanjem CS00 i CS01 bitova samo pokrećemo TIMER i to je 30% onoga što moramo napraviti da bi stvorili taj sat koji broji vrijeme za nas umjesto DELAY naredbe...

Code: Select all#include <avr\io.h>                                                int main(void){                                       TCCR0B |= (1<<CS00) | (1<<CS01);         while(1);                           }

"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.

Page 30: U Potrazi Za Tartufima AVR

Š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 0000000100000001 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<<CS00) 00000010 ILI___________________

Page 31: U Potrazi Za Tartufima AVR

(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

"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_documents/doc2545.pdf

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"Pošto sam već otvorio tablicu sa modom rada TIMER-a 0 odmah ću zapisati postavke WGM bitova da ne moram kasnije tražiti istu tablicu. Za CTC mod rada postavke iz tablice su sljedeće:WGM02 = 0WGM01 = 1

Page 32: U Potrazi Za Tartufima AVR

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.TCCR0A – Timer/counter control register ACOM0A1 COM0A0 COM0B1 COM0B0 – – WGM01 WGM00

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.

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

Page 33: U Potrazi Za Tartufima AVR

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-aTCCR0A -> 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.

"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.

Page 34: U Potrazi Za Tartufima AVR

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   

Page 35: U Potrazi Za Tartufima AVR

    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.

"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.

Page 36: U Potrazi Za Tartufima AVR

(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.

"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.

Page 37: U Potrazi Za Tartufima AVR

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_USERSEEPROM_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

Page 38: U Potrazi Za Tartufima AVR

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;

"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" poludi2. Resetira mi se, a sve sam dobro napisao3. 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čunalo2. 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 bitdouble : 32 bit

Puno nečega ali ću odmah odabrati tip varijable koji želim koristiti:"unsigned int" - 16 bitsunsigned - "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

Page 39: U Potrazi Za Tartufima AVR

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...

U svim knjigama o programiranju postoji toliko istine o svemu, čak i savršeno objašnjeno u nekih dobrih 100 000 stranica... No odgovor na moje pitanje nažalost ne možete pronaći niti u jednoj knjizi i nitko nikad nije napisao ova dva najveća problema svih programera u višim jezicima... Naravo da ih nije napisao, jer to i nisu problemi. Knjige savršeno idu u krajnost krajnosti, sve vam kažu u toliko puno teksta da od gomile govana i pojmova uopće ne vidite problem...

"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

Page 40: U Potrazi Za Tartufima AVR

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).

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:

Page 41: U Potrazi Za Tartufima AVR

1. ISR2. ISR_VECTOR 3. STACK4. STACK_POINTER5. I bit SREG-a6. 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...

"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

Page 42: U Potrazi Za Tartufima AVR

#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();

Page 43: U Potrazi Za Tartufima AVR

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...

"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 22 izvora što znači da ATMEGA88 može imati 22 prekidne rutine. Nakon reseta MCU-a svih 22 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 (21) 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

Page 44: U Potrazi Za Tartufima AVR

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 EnableOCIE0B - 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 registruKada 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    }     }     

Page 45: U Potrazi Za Tartufima AVR

To be continued...

"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.

Page 46: U Potrazi Za Tartufima AVR

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 Reset2 0x001 INT0 External Interrupt Request 03 0x002 INT1 External Interrupt Request 14 0x003 PCINT0 Pin Change Interrupt Request 05 0x004 PCINT1 Pin Change Interrupt Request 16 0x005 PCINT2 Pin Change Interrupt Request 27 0x006 WDT Watchdog Time-out Interrupt8 0x007 TIMER2 COMPA Timer/Counter2 Compare Match A9 0x008 TIMER2 COMPB Timer/Counter2 Compare Match B10 0x009 TIMER2 OVF Timer/Counter2 Overflow11 0x00A TIMER1 CAPT Timer/Counter1 Capture Event12 0x00B TIMER1 COMPA Timer/Counter1 Compare Match A13 0x00C TIMER1 COMPB Timer/Coutner1 Compare Match B14 0x00D TIMER1 OVF Timer/Counter1 Overflow15 0x00E TIMER0 COMPA Timer/Counter0 Compare Match A16 0x00F TIMER0 COMPB Timer/Counter0 Compare Match B17 0x010 TIMER0 OVF Timer/Counter0 Overflow18 0x011 SPI, STC SPI Serial Transfer Complete19 0x012 USART, RX USART Rx Complete20 0x013 USART, UDRE USART, Data Register Empty21 0x014 USART, TX USART, Tx Complete22 0x015 ADC ADC Conversion Complete23 0x016 EE READY EEPROM Ready24 0x017 ANALOG COMP Analog Comparator

Page 47: U Potrazi Za Tartufima AVR

25 0x018 TWI 2-wire Serial Interface26 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.

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.

"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:

Page 48: U Potrazi Za Tartufima AVR

http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html#ga751c22101f7e8f2fbe792c64a81f8dba

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

Page 49: U Potrazi Za Tartufima AVR

    }     } 

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){

Page 50: U Potrazi Za Tartufima AVR

}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}

Page 51: U Potrazi Za Tartufima AVR

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    }     } 

"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

Page 52: U Potrazi Za Tartufima AVR

    }     } 

"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.

Page 53: U Potrazi Za Tartufima AVR

Š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 ANa 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_POINTERSTACK 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.

Page 54: U Potrazi Za Tartufima AVR

"U potrazi za tartufima AVR-a" - Pomalo o ničemu

Temu oko samog STACK-a i njegove uloge ću malo preskočiti jer neke bitne stvari u samom tekstu nisam spomenuo...

Ako ja podešavam TIMER i ISR rutinu nikad neću imati nekakav određen redosljed razmišljanja. U tartufima sam ga podesio po nekoj proceduri jer naprosto pišem samo tekst i po tome ne postoje prioriteti. Jednostavno mi se može dogoditi da prije konfiguracije TIMER-a podešavam ISR rutinu. Pomalo je nelogično podešavati nešto što uopće nismo niti konfigurirali, ali programeri često imaju cilj podešavanja prije nego li napišu bilo koju instrukciju.

Nevažno je kojim redosljedom podešavamo TIMER, čak ne postoji niti pravilo . Najbitnije od toga je znati stavke koje trebamo podesiti, pa sad odakle ćemo mi krenuti nevažno je. Na samu varijaciju oko podešavanja TIMER-a koju sam pisao u tartufima mi možemo krenuti od bilo kojeg segmenta.

Ovaj dio pomalo zavlači nos i u iskustvo, jer ako znamo sve mogućnosti TIMER-a vrlo lako možemo napisati ISR rutinu prije nego što smo uopće podesili sve parametre TIMER-a. Oni koji često podešavaju TIMER imaju samo nekoliko stvari u glavi:Moram podesiti CSMoram podesiti WGMMoram uključiti globalne prekide ako koristim prekidMoram uključiti prekid na hardware-u,ako ga koristimMoram za svaki prekid povezati prekidni VECTOR sa adresom ISR-a

Ovo su neke osnovne stavke TIMER-a, njegovih mogućnosti, te ISR-a, no ne postoji redosljed kojim će netko razmišljati prilikom podešavanja. Realno mogao sam pisati i princip podešavanja od bilo kojeg segmenta i sa bilo koje strane samog programa.

Koliko se trudili misliti da postoji neka specifična procedura ona ne postoji. Nitko vama ne zabranjuje da pri podešavanju TIMER-a krenite sa bilo kojeg segmenta TIMER-a. Uostalom kad sve izračunate što trebate za TIMER i praćenje vremena raspored instrukcija je jednostavno podesiti.

Želim reći da i ovaj način na koji sam podešavao TIMER nije jedini na koji se to može napraviti.... TIMER se može podešavati sa bilo koje strane, a to hoćete li bi prvo računati WGM bitove, CS bitove, OCR0A nema uopće nikakve veze.

Početnike zna zbuniti taj redosljed razmišljanja, kao da moraju razmišljati po nekim utvrđenim pravilima podešavanja. Jedino ono što je bitno na samom hardware-u MCU-u podesiti sve parametre, nevažno s koje strane programa to radili i s koje strane programiranja razmišljali o tome.

U programu mogu krenuti pisati ISR i povezati vector koji znam da ću koristiti, te napraviti ISR rutinu za TIMER koji još nisam podesio. Poznavajući hardware samog MCU-a u situaciji kada nemamo niti jedne linije programa već moramo znati što želimo podesiti, a tad je nevažno s koje strane to idemo pisati.

Vezano za tu temu trenutno niti nemamo nekih teških konfiguracija TIMER-a, pa ću povući

Page 55: U Potrazi Za Tartufima AVR

jednu stvarnost. Možete sve instrukcije podešavanja napisati u svim mogućim varijantama i sve će i dalje raditi dobro, što je još jedna stavka kako konfiguracija TIMER-a ne mora biti striktno uvjetovana nekim redosljedom.

"U potrazi za tartufima AVR-a" - Kriva Drina

Ovo što sam pisao o računalu i njegovom načinu rada možemo olako zaobići. (Barem mislimo da možemo).Poanta priče je jednostavna: Svi koji kažu kako za programiranje nije potrebno znanje hardware-a računala i njegovog suštinskog rada ili lažu ili ne znaju pravu istinu.

U jednu ruku mislim da sam se udaljio od C-a, pogotovo onih stvari koje u C-u ne vidimo pa ću malo vratiti temu na smijer C-a. Jednu stvar iz toga treba zapamtiti: Kvaliteta bilo kojeg programa pisanog u višim jezicima je direktno vezana za poznavanje računala i assemblera.

Bez obzira što gotovo svi mislimo kako je C dovoljan za programiranje MCU-a to često nije dovoljno. Kada znamo assembler i suštinski rad računala onda nam se u C-u otvore oči. Poznavanjem rada računala, što assemblera, to i hardware-a MCU-a, iz viših jezika tipa C možemo pisati daleko bolje programe. To nam daje mogućnost da razmišljamo na način assemblera, program pišemo u C-u i povezivanjem tih stvari dobijemo optimalan program između HEX-a MCU-a i C-a kao jezika.

Ako će se uopće tko klati oko toga to ćemo biti buco i ja. Tu se nismo složili već 3 godine i nikada i nećemo, a istina toga je da smo obojica u pravu. Assembler kao assembler donosi nam apsolutnu kontolu nad računalom, dok nam C nosi apsolutnu jednostavnost programiranja.

Assembler je daleko najbrži, ali C je daleko bolji za komplicirane stvari. Programeri u C-u natipkaju 100 000 linija programa jednostavno i isprogramiraju "čuda", dok programeri u assembleru tome gotovo i ne mogu pristupiti. Programirati u assembleru je daleko kompliciranije nego u C-u, jer C kao jezik nosi puno programeskih prednosti koje ne postoje u assembleru.

Već dugo pišem ovo blinkanje LED-om i potrudio sam se koliko mogu prikazati (najvažnije od svega) način pristupa problemu i način razmišljanja prilikom rješavanja problema.

Nema smisla ići u komplikacije LED-a kao što sam mislio ranije, pa ću ovu temu završiti u narednih nekoliko postova i odabrati novi cilj. Sve ideje koje nekome padnu na pamet i misle da treba postojati na tartufima slobodno pošaljite u PM i raditi ćemo nešto od toga.

"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

Page 56: U Potrazi Za Tartufima AVR

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

Page 57: U Potrazi Za Tartufima AVR

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

Page 58: U Potrazi Za Tartufima AVR

(>=1000) te kako sam okrenuo stanje LED-a sa DDRC ^= (1<<PC0); Objasnim u sljedećem postu.

"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 02. varijabla t_led je 03. Dogodi se prvi prekid, što znači da je prošlo 1mS vremena4. Varijabla t_led se uvećava za 15. 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

Page 59: U Potrazi Za Tartufima AVR

ć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".

"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:Code: Select all

PORTC |=  (1<<PC0);         // Postavi PC0 u log 1PORTC &=~ (1<<PC0);         // Postavi PC0 u log 0

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 allPORTC |=  (1<<PC0);         // Postavi PC0 u log 1PORTC &=~ (1<<PC0);         // Postavi PC0 u log 0PORTC ^=  (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

Page 60: U Potrazi Za Tartufima AVR

  ULAZ         IZLAZA      B      A XOR B0      0          00      1          11      0          11      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

Page 61: U Potrazi Za Tartufima AVR

IME_REGISTRA |= (1<<BROJ_BITA); // Ova linija uvijek BROJ_BITA diže u logičku jedinicuIME_REGISTRA &=~(1<<BROJ_BITA); // Ova linija uvijek BROJ_BITA spušta u logičku nuluIME_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()        PCO &=~(1<<PC0)

"U potrazi za tartufima AVR-a" - MACRO

Prvo moramo znati da nismo dovoljno bisti da možemo programirati na ovakav način upravljanja portovima. Mislim možemo mi, ali se prije ili kasnije izgubimo u vlastitom programu i napravimo 1000 BUG-ova. U prošlom postu sam naveo primjer sa upravljanjem 10 LED-ica različitih boja. Programski kupus tog tipa bi izgledao ovako:

Code: Select allPORTC |=  (1<<PC3);PORTD |=  (1<<PD7);PORTD &=~ (1<<PD7);PORTC |=  (1<<PC4);PORTC &=~ (1<<PC0);PORTC |=  (1<<PC6);PORTC &=~ (1<<PC6);PORTC &=~ (1<<PC4);

Page 62: U Potrazi Za Tartufima AVR

Logično i nespretno od mene je postaviti jedno pitanje vama:Recite vi meni sa kojom instrukcijom sam upalio crvenu LED-icu?

Kao prvo vi u ovom programu ne znate pali li se crvena LED-ica sa logičkim 0 ili logičkim 1, a što je još veće zlo ne znate niti na kojem je portu spojena i jedino se možete hvatati za glavu i zaključiti da je ovaj program pisao loš programer.

Zamislite da imate 1000 linija programa ovako isprogramiranih??? Pa recimo ja takve programe ne želim niti čitati, niti gledati niti popravljati i dorađivati. Gospodin koji piše takve programe nek se sam snalazi u svojim, oprostite na izrazu, govnima.

Iz tog razloga moramo uvesti MACRO naredbe koje će riješiti problem crvene LED-ice.Sve MACRO naredbe su istovremeno i predprocesorske naredbe i u stvarnom pogledu na njih služe samo kao šminka programa. Nema ih puno pa ćemo većinu njih koristiti stalno. Predprocesorske naredbe ništa ne naređuju mikrokontroleru, nego predprocesoru. Neću ulaziti u sve detalje kako stvar radi jer bih se opet zavukao u krajnost nečega što ne moramo nizi znati. Mislim da će svaki primjer reći puno više o MACRO naredbama nego nekakvo nabadanje mušica na vilicu i pričanja o detaljima.

Sve predprocesorske naredbe u C-u uvijek počinju sa znakom:Code: Select all

#

Pa tako zaključujemo kako je i #include naredba zapravo predprocesorska u kojoj mi predprocesoru kažemo da ćemo u ovoj datoteci koristiti i recimo <avr\io> datoteku. U C-u postoji dosta pravila oko predprocesorskih naredbi a one koje trenutno ne mogu izbjeći ću i navesti: 1. Prije "#" u C programu nikad ne smije postojati razmak nego "#" mora biti prvi znak u redu2. Na kraju predprocesorske naredbe nikada se ne stavlja ";"3. direktiva "define" mora se pisati malim slovima

Da bih napisao MACRO za svoju LED-icu prvo moram pogledati hardware i zaključiti da se moj LED nalazi na PC0 i pali se sa logičkom nulom. Kada to izvučem sa samog hardware-a ovo napišem za nekoliko sekundi:Code: Select all

#define LedRedOn()         DDRC &=~(1<<PC0)    #define LedRedOff()        DDRC |= (1<<PC0)#define LedRedToggle()     DDRC ^= (1<<PC0)

Sada se svakako usudim ponovno postaviti pitanje o crvenom LED-u i pokazati program ponovno:Code: Select all

PORTC |=  (1<<PC3);PORTD |=  (1<<PD7);PORTD &=~ (1<<PD7);PORTC |=  (1<<PC4);LedRedOn();PORTC |=  (1<<PC6);PORTC &=~ (1<<PC6);PORTC &=~ (1<<PC4);

Page 63: U Potrazi Za Tartufima AVR

Kada za bilo koju stvar napišete MACRO naredbu istog trenutka možete zaboraviti na kojem se nešto portu nalazi, s čime se pali, s čime gasi. Nas kao programere to više ne interesira jer želimo razmišljati na višem nivou samog programa, a ne zamarati se sa guranjem broja 1 u lijevo i logičkim sklopovima.

Ovaj post govori o poanti i razlozima zašto se programira sa MACRO naredbama, ali prije nego li primjenimo MACRO na programu morati ćemo još malo detaljnije vidjeti što točno radi #define i sam predprocesor.

"U potrazi za tartufima AVR-a - Novi file

Danas sam čitao tartufe i iskreno sam se začudio koliko tu ima loše objašnjenih stvar, čak i netočnih, digao sam i neke slike u prošlim postovima, pa kao razmišljao o tome da filtriram i korigiram do sad napisan tekst. No to mi se stvarno neda. Gubljenje vremena je ispravljati neke krive drine u tekstu, no samo podešavanje TIMER-a mi se čini relativno razumljivo. Pošto ću iste pojmove koje sam do sad koristio morati koristiti uvijek u budućnosti neću se vraćati na tekstove koji su napisani nego ću se uhvatiti novog područja koje moramo shvatiti prilikom programiranja, a pogotovo MACRO naredbi.

Možda ste nekad čuli razgovor 2 programera koji se svađaju oko programerskog "layer-a" iliti programerskog sloja.

Kada govorimo o bilo kakvim slojevima mrkve, kupusa i tartufa te ih tako i složimo ne možemo pobjeći od činjenice da je neki sloj viši od drugog, a drugi niži od prvog. Upravo zato u programiranju govorimo o slojevima programa.

Programski jezik C ima savršeno postavljen način programiranja u slojevima i mislim da je vrijeme da napravimo prvi programerski sloj u kojemu pišemo MACRO naredbe.

Već sam rekao kako nas u aplikativnom razmišljanju programiranja ne zanima gdje je spojena LED-ica, a još manje nas zanima pali li se ona sa logičkim 0 ili logičkim 1. Nas ne zanima za koliko se broj 1 gura u koju stranu, niti nas zanima na kojem portu, u kojem registu se nalazi BIT koji upravlja LED-om.

Ma kvragu da završim misao: Ne želim u main.c datoteci uopće gledati govna koja upravljaju mojim registrima. Samo želim da ih koristim, ali da ih više nikad ne vidim i da mi se nekakvo "guranje broja 1" ne pokazuje na monitoru.

Što ja dakle želim?Treba mi programerski sloj ispod mog main.c file-a koji je niži od mog trenutnog razmišljanja i za mene će odraditi stvari koje ja ne želim gledati na monitoru.

Svečani naziv nove datoteke biti će "board_driver.h"Neka se ona zeza sa guranjem brojeva i registrima, a ja ću u svojoj main funkciji koristiti ono što piše u board_driver.h datoteci.

Page 64: U Potrazi Za Tartufima AVR

Kako to radimo:Pa prvo moramo otići u folder u kojemu se nalazi pokretački file AVR studia za ovaj projekt i upravo tamo gdje se nalazi main.c datoteka napravimo novi TXT file kojemu promijenimo ime i ekstenziju u ovo:board_driver.h

U tom trenutku upravo imamo 2 sloja u programiranju, prvi je main.c datoteka i on je viši sloj, a drugi je board_driver.h i on je niži sloj.

Pošto je moj laptop sa novim sustavom 7 X64 poludio i ne želi instalirati AVR Studio ovaj projekt sa 2 datoteke te palenjem LED-a dići ću na forum. Zapravo svaki program koji programiram u ovom topicu ću objaviti čisto da se niti jedan početnik ne mora mučiti sa stvaranjem samog projekta.

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

Prvi program je gotov na forumu i evo i zapakiran RAR.Napravio sam i board_driver.h datoteku da ne moram 100 puta dizati isti projekt za neku malu doradu.

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/Options2. 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...

"U potrazi za tartufima AVR-a" - Board_driver.h

Smiješno je kako nitko ne razmišlja o budućnosti i problemima s kojima se susrećemo tek nakon puno i puno programiranja. Ovo blinkanje LED-om je jednostavno i ima toliko malo teksta da je nevažno koristili mi MACRO ili ne. Stvarni projekti iz realnog života često nam napune sve portove MCU-a pa po tome možemo imati i 40 zauzetih portova, što ulaza tako i izlaza...(ovisno o MCU AVR-a) Bilo koji program koji s time upravlja ne smije se svoditi na:DDRC |= (1<<PC0);

Ako bi to tako radili očito bi imali 40 linija programa koji konfiguriraju samo DDR registre, a da ne govorim o programu koji upravlja s tim portovima. On se može sastojati od dobrih 10 ili 20 datoteka što *.c ili *.h datoteka, a svaka od njih može imati i po 1000 linija programa.

Isto tako svih 10 ili 20 datoteka u nekim segmentima programa mogu koristiti iste te portove i upravljati s njima sa 100 lokacija u programu...

Page 65: U Potrazi Za Tartufima AVR

Prvi problem je kada vi napravite novi hardware i sada samo PC0 trebate zamijeniti sa PD6.Ako imate 10 000 linija programa onda očito morate prekopati svih 10 000 linija da bi pronašli sve lokacije u kojima se spominje PC0 i PD6 te iz zamijeniti.

Drugi problem je taj jer bi direktnim upravljanjem registrima napravili puno grešaka jer naprosto zaboravite što se pali sa log 1, što sa log 0, a i zaboravite ime registra pa često možete umjesto PORTC napisati DDRC, ili nedo bog PORTD. S duge strane nemoguće je držati u glavi 40 PORT-ova, te njihovu konfiguraciju DDR registra kao ni logiku koji je ulaz, koji izlaz, pogotovo logiku što se s čime pali i gasi.

board_driver.h datoteka sređuje sve naše probleme. Njezine MACRO naredbe moraju imati sve konfiguraciju ulaza i izlaza i ta datoteka ima samo tu jedinstvenu svrhu. Ako imamo 20 drugih datoteka i sve one žele upravljati ulazima i izlazima svaka od njih mora pozvati board_driver.h i koristiti njene MACRO naredbe.Tim načinom programiranja vrlo lako je zamijeniti PC0 sa PD6 jer sve što mi trebamo zamjeniti je u board_driver.h datoteci, a čitav ostali program kojeg može biti 10 000 linija programa ne moramo niti pogledati jer on i tako koristi MACRO samo jedne i jedinstvene board_driver.h datoteke.

Prvu stvar koju u ovom programu treba napraviti je prebaciti sav program koji piše po portovima u MACRO naredbe board_driver.h datoteke.

"U potrazi za tartufima AVR-a"- Nevazan post

@buco je dobro skužio razliku između programa sa DELAY naredbama i bez njih i openito napisao zgodan post u temi komentara na tartufe... Pokušao sam objasniti poantu programiranja na najjednostavniji nacin na koji sam to mogao napraviti. Puno stvari pisao sam na način koji nije direktno stvaran, ali je najjednostavniji za shvatiti.

Danas imam drugačije mišljenje oko istog BUG-a na koji je naletio buco, a isto tako i ja. Programiranje kao programiranje nikoga ne zanima i svi žele bez i malo truda napraviti svoja čuda.

Buco je sigurno napravio grešku jer je htio pokazati čitateljima kako izgleda računalo, a ja sam pak napravio grešku jer sam istim tim čitateljima htio reći kako treba razmišljati.

Stvar ne može biti jednostavnija: Oni koji hoće naučiti moraju biti u stanju dobro zagrijati stolicu, a mi koji nešto znamo smo prošli taj period zagrijavanja stolice.

Meni nikad nije bilo zao napisati ono sto znam, bez obzira što iza toga stoji 10 godina programiranja, da bih nekoga uputio na pravi smijer, ali kad pogledam sve ono što je pisano mislim da to nema smisla...

Nevjerojatno je da je samo jedan forumas (@zigzag9) uspio iskoristiti moj tekst i podesio TIMER za sasvim drugi AVR, i sasvim drugi TIMER na sasvim drugom oscilatoru.

Forum cu i dalje komentirati, no ovo je zadnji post tartufa.

Page 66: U Potrazi Za Tartufima AVR

"U potrazi za tartufima AVR-a" - Tko je kriv?

Blinkanje LED-om naizgled je jednostavno, ali vrlo lako i u tom primitivnom programu napravimo BUG. To je onaj poznat osjećaj kad program uporno ne želi raditi onako kako bi trebao.

Mislim da nema programera koji se svakodnevno ne bori sa novim problemima, što u Hardware-u tako i u software-u, pa ću dodati još jednu malu funkcionalnost u već objavljen program...

Prva ideja programa je bila da treba blinkati LED diodom (PC0) svake sekunde, te da se ne smije koristiti delay. Jedina funkcionalnost koju dodajem programu je brza promjena stanja na PC5 portu koju ću odrađivati u glavnom loop-u. Sve što želim napraviti je da mi stari program i dalje blinka LED-om, ali da PC5 port okreće stanje svog izlaza najbržom brzinom kojom to AVR može raditi... @Buco znam, može sa sbi, cbi čisto da se razumijemo...

Prvo moram u inicijalizaciji postaviti PC5 kao izlaz jer to je PIN kojemu će MCU okretati stanje.DDRC |= (1<<PC5);

Nakon toga u glavom loop-u kada mi program nije u prekidu moram stalno okretati stanje PC5 porta, a to ću napraviti XOR operatorom na isti način kako okrećem i stanje LED-a:PORTC ^= (1<<PC5);

Konačan 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    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)    DDRC |= (1<<PC5);                  // Config data direction registar ( PC5 -> 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

Page 67: U Potrazi Za Tartufima AVR

    TIMSK0 |= (1<<OCIE0A);             // Enable Output Compare Match A Interrupt    sei();                             // Enable Global Interrupts

    while(1){                          // main loop        PORTC ^= (1<<PC5);             // Toggle my debug line    }     }

U principu program bi i dalje trebao blinkati LED-om na PC0 svake sekunde, i usput PORT PC5 mijenjati najbrže što može... Ta linija PC5 može se promatrati samo osciloskopom jer je AVR mijenja u nS, dakle dosta brzo...

Jedino logično pitanje koje mogu postaviti je: Zašto program ne radi onako kako bi trebao?Ovaj program nisam testirao, ali znam da će program imati simptome gripe, te ledica neće blinkati svake sekunde nego će ponekad preskočiti ciklus promjene stanja...

Drugo pitanje je:Zašto vam se ova greška pojavila sada?Odgovor:Zato da vam se ne bi pojavila kasnije!!! Jer tražiti ovakav BUG u 50 linija programa je kao tražiti iglu u plastu govana...

U prilogu je i source AVR Studio4Attachments

Tartufi_1.rar(10.59 KiB) Downloaded 1 time

"U potrazi za tartufima AVR-a" - "Omiljeni Assemblerski Bascom"

I mene splet okolnosti zadnja 2 dana natjerao na BASCOM, taj divan, prekrasan programski jezik. Brdo biblioteka koje rade, ali ne ono što bi trebale. Kupusast program, ogavan prijevod na strojni jezik, razbacivanje svim čarima programiranja obuhvaćenih u jednu veliku hrpu govana.

Zapravo, nisam izdržavo 2 dana, nego 1.5 radnih dana i shvatio koliko je Bascom ograničen, loš i nedorečen.

Prva globalna glupost koja mi pada na pamet je svakako ISR. Ne vjerujem Bascomu, pa uvijek pogledam kakav HEX napravi i prva stvar me ubode kao nožem PUSH i POP... Pa jbt ne može mi ISR za povećavanje jedne varijable gurati gotovo sve registre na stack, i onda ih vući nazad. Ajde ok, još se mogu pomiriti sa činjenicom da se bezobrazno razbacuje sa resursima MCU-a, no nikako ne mogu shvatiti da mora postojati samo jedna BASCOM "return" instrukcija??? U kojem to programskom jeziku return iz ISR-a nije asm("reti")?

Eh, trik je u tome ako u jednoj ISR rutini imaju 2 returna, onda ce jedan raditi POP sa stacka i pozvati asm(reti); , a drugi ce pozvati samo asm("ret"); Drugacije zapisano ne smijem imati 2 returna iz iste ISR rutine.

No ok, to prežvačem preko asm-a i pomirim se sa cinjenicom da je to tako, i odmah nakon

Page 68: U Potrazi Za Tartufima AVR

toga uputim se na "Config Servos" koji koristi ako se ne varam TIMER 0 ili TIMER 1.

Isuse Bože pa on emulira SERVO, tjera ISR svakih 10 uS, pusha opet proklete registre na stack, vuče ih nazad kao kreten. Na 8 Mhz barem 50% vremena provodi samo u tom ISR-u. Ako rezoluciju podignem sa 10 uS na više onda mi period toliko kasni da umijesto 20mS dobijem 40mS...

No ajd, ko ga hebe, bacim ja i 50 % vremena MCU jer to tako po bascomu mora biti i uputim se u kalkulaciju servo duty cicle PWM-a.

Opet čudo: Kurva računa duty cycle prema frekvenciji MCU-a i izračuna svakih koliko se mora pozvati konfigurirani ISR da bi emulirao Servo kanal... Sve je to super, samo što on radi update TIMER-a tek na kraju ISR rutine koja traje 50% MCU vremena, a to je trebao napraviti odmah kad je ušao u ISR. Po tome ispada kao da ima _delay_uS u logici servo pulsa, pa em što je emulirani PWM loš, MCU zaklan do 50 %, još je i netočan.

Jedina prednost Bascoma je ta što ga na sreću ne moram koristiti u životu, osim kada to posao nalaže...

"U potrazi za tartufima AVR-a" - Servo

Dosta o LED-u i njegovom blinkanju. Već je i meni dosadno pisati dosadni LED, pa očito moram smisliti nešto malo kompliciranije pa se sjetih servo motora i njihovog pokretanja. Da bi to bilo kako solidno napravili moramo se upoznati sa strukturiranjem C programa, pisanjem driver-a, pisanjem aplikacije i povezivanjem tih pojmova u nešto što se može nazvati uređaj. Programiranje dobrog i čitljivog programa podrazumjeva i različit način pisanja, više programerskih ličnosti i razmišljanja.

Glupo zvuči, ali ipak programiranje driver-a i programiranje aplikacije su 2 odvojene stvari i drugačije se rade. Dok pišemo driver na neki način smo programerski rob koji poslužuje "glavni" program, dok na drugoj strani razmišljanja smo programski gospodar kojeg poslužuju programski robovi... Kada bih to govorio sa strane programiranja ona bih govorio o programskim slojevima u kojim uvijek netko mora biti najviši, a netko najniži...

Kako god na to gledali moramo biti oboje i s obe strane konačnog programa da bi pravilno napisali driver i aplikaciju. Moja praksa programiranja mi govori da je obavezno prvo pisati drivere i dati gospodaru punu podršku i funkcionalnost, a tek ona pisati aplikaciju. Razlog tome opet jednostavan: Drivere bez aplikacije možemo testirati, ali aplikaciju bez drivera ne možemo nikako.

Kako uopće definirati što je aplikacija a što driver? Kako znati što radi driver, a što aplikacija?Najlakše je biti dvoličan i postaviti se u razmišljanje driver-a i aplikacije:

Razmišljanje aplikacije:" Mene boli k**** kako se servo motor pokreće, kakvi mu se impulsi daju, kada mu se daju? Uopće me ne interesira kako servo motor radi. Samo želim da mu kažem na koju poziciju da

Page 69: U Potrazi Za Tartufima AVR

ode i pitam ga na kojoj je poziciji."

Razmišljanje driver-a:" Mene boli k**** na koju ćeš ti mene poziciju postaviti i što ćeš ti pozicionirati, kako kada i zašto. Ja samo znam da ti to želiš, a ja moram znati napraviti, moram znati kako upravljati motorom, kakav mu pulse dati da bih ispunio tvoje želje."

Ovakvim načinom je jedino moguće timski programirati, jer driver i aplikaciju ne mora pisati ista osoba, i u svim projektima na kojim radi više programera posao se uvijek odvaja na driver-e, aplikaciju, dio driver-a ili dio aplikacije.

To Be Continued...

Poštovani prijatelju,

detaljno i pažljivo sam pročitao topic koji si napisao. Paraleleno sa čitanjem sam primjenjivao sve što si opisao vezano za TIMER na ARDUINO ploči. Sve mi je jasno, tutorial je odlično i jasno napisan.

Međutim, nikako mi ne uspijeva da LED diodu upalim iz main-a, pa bih te srdačno zamolio da mi daš par smjernica kako bih je upalio. Ovako bi izgledao moj pokušaj:) :

Code: Select all#include <avr/io.h>#include <avr/interrupt.h>

unsigned int t_led;

ISR (TIMER0_COMPA_vect){    t_led++;}

int main(){

    DDRD |=(1<5);    OCR0A= 78;    TCCR0A = TCCR0A | (1<<WGM01);    TCCR0B = TCCR0B | (1<<CS02);    TIMSK0=TIMSK0 | (1<<OCIE0A);    sei();

    while(1){        if(t_led >= 1000){             PORTD ^= (1<<5);             t_led = 0;        }        return 1;    }}

PS

Page 70: U Potrazi Za Tartufima AVR

Zanemari postavke TIMER-a, nije podešen na 1ms. Namjerno sam ih izmijenio da bih skontao princip rada TIMER-a, jer, ne možeš naučiti voziti bicikl dok ne probaš ! BTW. Kao što si primjetio potrudio sam se da ARDUINO programiram u pravom PJ.

Unaprijed zahvaljujem na odgovoru!

Pogledao sam ti program do prve greske koju sam na srecu uocio u nekoliko sekundi, pa cu samo nju prijaviti i onda probaj...

Moj zakljucak:Digao si globalni sa sei(); OKPokrenuo si TIMER, sa CS, nadam se na dobrom registru, pa valjda timer vozi. Predpostavljam OK.

U TIMSK si digao dobar FLAG za ISR TIMER-a kada nabroji do OCR0A, te postavio neki OCR0A. OK

Dakle, ono što sigurno nije dobro:

Varijabla brojača mora biti volatile i deklarira se ovako:volatile unsigned int t_led;

To je jedan od BUG-ova koje sam opisao, a problem je jer compiler povuce vrijednost RAM varijable u svoj radni registar i nikad je vise ne vuce iz RAM-a, a tvoj ISR povecava varijablu u RAM memoriji. Kada compileru kazes da je varijabla volatile tada on zna da ne smije koristiti staru vrijednost nego uvijek iznova ide citati RAM jer predpostavlja da netko (ISR) moze promjeniti tu varijablu...

Druga greska je:Obavezno ubijaj globalne interrupte kada citas varijablu t_led, te kada pises po njojNesto ovako:

cli();if(t_led >= 1000){ PORTD ^= (1<<5); t_led = 0;}sei();

Postoji razlog i za to, a to se u tartufima vodi pod drugim neopisanim BUG-om... Dakle napravio si oba BUG-a

Trik sa volatile varijablom je taj jer sam ja blinkao sa LED-om iz ISR-a, i znam da compiler mora povući novu vrijednost iz RAM-a. ( Svaki ISR vuče sviježu vrijednost iz RAM-a)

No ako si u main funkciji, compiler će optimizirati program i samo jedamput će povući t_led

Page 71: U Potrazi Za Tartufima AVR

u radni registar i nakon toga gledati samo u svoj registar (kojeg nitko ne mjenja), a tvoj ISR će uporno povećavati vrijednost u RAM memoriji, tako da ti u toj situaciji brojač povećava RAM, a ti gledas samo u registar koji je konstantno na 0.

//Bez volatile je ovo:Povuci jednom iz RAM-a u radni registar;while(1){Gledaj u registar;}

//A sa volatile je ovo:while(1){Povuci iz RAM-a u radni registar;Gledaj u radni registar;}

Nadalje, tek kad sam poslozio tvoj upit da izgleda kao program skuzio sam da iz while(1) pozivas return();, samo ne znam kamo se vraćaš? ... Ne možeš se iz main funckije na kontroleru vratiti u pozivalac main funkcije jer je pozivalac sam hardware. To treba obrisati

LP,Josip

Što se tiče drugog BUG-a kada se radi cli(); i sei(); stvar je nešto kompliciranija.Mnogi mješaju ova 2 BUG-a u isti BUG, a radi se o posve drugoj stvari. Sa "volatile" govorimo compileru da u glavnom programu uvijek vuče vrijednost iz RAM-a u registar svaki puta kada u C-u pristupamo varijabli. Netko bi pomislio da sve varijable nazove volatile pa da ne mora o ovome razmisljati, ali to je jos gluplje.

Evo jedan primjer:

Code: Select allunsigned char pero;int main(void){    while(1){        pero++;    }}

Ako globalnu varijablu "pero" nikad ne koristimo u ISR-u, to znači da je ISR sigurno ne može promjeniti i suludo je reći compileru da bez veze gubi vrijeme na povlačenju te varijable iz RAM-a u registar svaki puta iznova. Dovoljno je jedamput povući vrijednost iz rama i stalno povećavati registar. (O ovome se brine compiler i optimizator).

Da se vraim na BUG za koji sam gasio globalne prekide:Životna nesreća je ta jer je vaša varijabla u C-u "unsigned int", dakle 2 byte-a.No u assembleru su to 2 memorijske lokacije po 1 byte, jer je naše računalo 8-bitno, pa idemo postaviti jedan "unsigned int" u assembleru na 0:

Code: Select all

Page 72: U Potrazi Za Tartufima AVR

CLR r0               ; postavi r0 na 0STS 0x0101, r0       ; pohrani r0 na adresu 0x0101 (RAM) prvih 8 bitova t_led varijableSTS 0x0100. r0       ; pohrani r0 na adresu 0x0100 (RAM) drugih 8 bitova t_led varijable

Sve bi to bilo super da se ovo ne može dogoditi:CLR r0 ; postavi r0 na 0STS 0x0101, r0 ; pohrani r0 na adresu 0x0101 (RAM) prvih 8 bitova t_led varijable;TIMER tocno sad poziva prekid i skače na ISR vectorSTS 0x0100. r0 ; pohrani r0 na adresu 0x0100 (RAM) drugih 8 bitova t_led varijable

Postavili smo na "0" tek prvi dio integer varijable, a hardware pozvao ISR koji ide povećati istu memorijsku lokaciju, a to je naš unsigned int t_led. Ako ISR povećava nešto, vrlo lako se dogodi da ono što smo maloprije postavili na "0" ISR postavi na neku drugu vrijednost.

Kod čitanja je ista stvar, jer vi u assembleru morate pročitati 2 byte-a u 2 instrukcije, i ako to presječe prekid, tada vam se može dogoditi ovo:

Vrijednost RAMA za t_led je: 0000 0000 1111 1111Iz main-a čitate recimo LOW byte t_led varijable i u registar r0 pročitate 11111111Prekid se dogodi i poveća t_led za 1, te je nova vrijednost RAM varijable t_led: 0000 0001 0000 0000Iz main-a čitate HIGH byte i u registar r1 pročitate 0000 0001.

Vrijednost rama je : 0000 0001 0000 0000A vaši registri kažu da je : 0000 0001 1111 1111

To izgleda tako kad posvađate main program i prekidnu servisnu rutinu, pa stoga nemojte ih svađati i bilo kojom metodom zaobiđite bug.

Nužno, neizbježno i važno je znati da poziv svake prekidne rutine mora završiti kao da se rutina nikada nije izvršila gledano sa strane main programa. Hardware prekida na stack-u sačuvati će povratnu adresu main programa, ugasiti globalne prekide (I bit SREG-a), ušuljati se na vektor prekida, a assemblerski programer sa vectora skočiti na ISR rutinu, pohraniti sve registre na stack koji su važni za glavni program, pohraniti SREG, brzo odratiti posao prekidne rutine, vratiti sve registre na tocnu vrijednost (povući ih sa stacka) i pozvati RETI. Tada hardware ponovno uzima stvar u svoje ruke te uključuje globalne prekide (I bit SREG-a) i vraća vas u glavni main program kao da se prekid nikada nije niti dogodio.

Zato sam ja namjerno pozvao ISR TIMER-a u tocnom trenutku kada se dogodi BUG, a na to ipak moramo paziti u main-u. Najlakša metoda je ugasiti I bit SREG-a jer kada to napravimo svi prekidi koje se trebaju dogoditi su na čekanju, i dogoditi će se tek kada uključimo I bit SREG-a... Postoje i bolje metode, i o tome ću nadam se pisati nekada u budućnosti...

"U potrazi za tartufima AVR-a" - Servo

Funkcija int main(void); uvijek je ulazna točka u sve programe pisane u C-u i u svakom napisanom programu trudimo se ostaviti funkciju "čistom", jasno vidljivom i gotovo praznom.

Page 73: U Potrazi Za Tartufima AVR

Polazna točka C-a uvijek treba biti jasno prepoznatljiva i ne smije biti natrpana sa programom, driver-ima i aplikacijom.

Primjeri nekih mojih programa u c-u izgledaju čak i ovako:Code: Select all

#include "app_mato.h"#include "app_jurica.h"

int main(void){    InitAppMato();    InitAppJurica();        while(1){        RunAppMato();        RunAppJurica();    }}

Već sam napomenuo da AVR jezgra može izvršiti čitav FLASH program ATMEGA88 računala u samo 205 uS, a i za _delay_ms(x) valjda više ne moram ni napominjati (ne smijemo koristiti). Dakle svi moji programi brzo će se pojaviti u main programu.

Kako sam već pisao LED i njegovo blinkanje, onda ću to smatrati prvim programom u tartufima, koji će izvršavati MCU. Lako je zaključiti da sve što ima veze sa LED-om ne smije biti tako napisano u main-u, nego mora imati svoj kutak u projektu.

Kako bi mogli nastaviti programirati dalje, očito naš program mora izgledati ovako:

Code: Select all#include "blink_led\blink_led.h"

int main(void){        InitBlinkLed();

    while(1){        RunBlinkLed();    }}

Kasnije znam da ne moze izgledati tako, jer onaj TIMER sa 1mS tickom će nam sigurno trebati u svim ostalim programima jer naprosto svi programi koje god pišemo uvijek imaju neki vrag sa vremenom... No o tome ćemo razmišljati tek kada dođemo do toga.

Bitno je za sada odvojiti taj program negdje čisto da ga ne gledamo u main funckciji kao takav, te pripremiti strukturu programa za servo_driver.

Page 74: U Potrazi Za Tartufima AVR

"U potrazi za tartufima AVR-a" - *.c i *h

Svima je jasno što ide u C datoteke, pa i main.c je *.c datoteka i u njoj smo pisali program za blinkanje LED-om. No kada govorimo o *.h datotekama onda se lako zapletemo u priču zašto postoje i čemu uopće služe.

Često vidim i programe u kojima su funkcije pa i program napisani u *.h datoteci, a compiler to ipak zna prevesti... hm zna zato jer uvijek *.h datoteku doda iznad *.c datoteke, pa se ovakvo nešto i može napraviti, ali to nikako nije smisao ovih datoteka.

Prvenstveno ako blinkanje LED-om smatramo jednim djelom našeg ogromnog programa (koji još uopće ne postoji) tada je blinkanje LED-om jedan modul programa. On za glavni main.c file mora biti "black box", pa i sav program koji odrađuje mora biti nevidljiv te skriven u svim ostalim datotekama našeg projekta.

Za svaki takav modul koji nešto odrađuje najbolje je napraviti jedan *.h file preko kojeg viši sloj programa koristi niži sloj programa. (Ovo će nam biti jasnije kada bude više programa)

Ako inicijalizaciju TIMER-a, te ISR (prekidnu servisnu rutinu) prebacimo u zaseban file imena blink_led.c tada smo upravo napravili jedan niži sloj naše čitave aplikacije i za njega moramo napraviti jednu *.h datoteku.

Stoga, naš sljedeći program mora imati minimalno 3 datoteke:main.c led_blink.cled_blink.h

"U potrazi za tartufima AVR-a" - new led_blink

Prebacih to, pa ću pisati dio po dio da bude jasnije kojim redosljedom ovo radimo, te koja je svrha onog što radimo.

Kreiranje novih datoteka je lako... samo kliknete desnim na "Source Files" i odaberete stavku "Create new source file", te mu date ime kako god vas volja. Ja ga nazvah led_blink.c. Kod header datoteke je ista stvar, samo kliknete desnim na "Header Files", i opet ga nazovete kako hocete. Ja ga nazvah led.blink.h

Copy/Paste metodu još smo naučili kad smo čitali "Anu Karenjinu" kao lektiru srednje škole, pa prebacimo dio programa iz main.c u led_blink.c file

Malo općenito:Ono što je meni glupo u BASIC programima su pojmovi sub i function, delace sub i declare function.Po BASIC-u sub je podprogram koji ne može vratiti neku vrijednost pozivatelju, dok je funkcija također podprogram koji vraća neku vrijednost pozivatelju... Za mene u C-u to su iste stvari i uvijek govorim o funkciji koja vraća ili ne vraća neku vrijednost. U assemberu programer sam prenosi atribute preko registara ili stacka, te ih sam i vraća na ta 2 načina. C za nas to odradi bezbolno, no u asm ipak moramo točno znati kako želimo nekoj rutini dati

Page 75: U Potrazi Za Tartufima AVR

parametre i kako nam ona to vraća nazad.

Tu smo dakle kod prve funkcije koja nije int main(void);, nego prva naša koja nam treba a zove se:void InitLedBlink(void);. Možete je pronaći i u led_blink.c i u led_blink.h datoteci.

Funkcija je podprogram kojeg pozivamo da za naš viši sloj programa odradi neki posao. Svaki assemblerski programer bi ovu funkciju odmah deklarirao kao gubitak vremena MCU-a, a ona to u stvarnosti i je. Svaka funkcija u C-u ima svoju lokaciju u FLASH memoriji i pozivom bilo koje funkcije gubimo određeni dio FLASH programa jer smo mogli podesiti TIMER i u main-u i ne bi prisilili MCU da odlazi na neku novu adresu u FLASH-u.

Kasnije ćemo u detalje zašto funkcije postoje, a za sada trebamo sagledati novu funkciju sa više gledišta samog programa. Što znači InitLedBlink() u main.c, u led_blink.c i u led_blink.h datoteci???

led_blink.cStavimo se u poziciju roba, nižeg sloja, za gazdu nevažnog i zanemarivog jer u suštini to i jesmo. Naša funkcija u led_blink.c datoteci odrađuje bauštelu i posao za pozivatelja. Mi ne znamo tko može pozvati funkciju, čak nas niti ne zanima. Tko voli nek izvoli i nek zove. Prvenstevno u funkciji moramo znati što mi u kurac uopće radimo???? U novoj funkciji mi znamo da moramo sve pripremiti za blinkanje LED-a, a zato i funkcija u imenu ima "Init" kako bi gospodar znao da mi neki vrag pripremamo i pokrećemo, postavljamo u početno stanje. U toj funkciji mi radimo aktivan posao pripreme i to sigurno ide u FLASH, mi upravljamo registrima, pripremamo hardware za blinkanje LED-a, i kad odradimo posao vraćamo se u program pozivatelja, tj main.c datoteke.

main.cI dalje krov svega, glavna funkcija gdje sve počinje i završava, gazda svega koji zna da treba blinkati LED-om, ali ga uopće ne interesira kako radi TIMER. Čisto ga boli kurac za hardware, TIMER, registre i ne želi se s time uopće zamarati... Samo vikne " Blinkaj kurčevim LED-om". Problem je samo taj što on mora znati kome vikati???

led_blink.hPoveznica između main.c i led_blink.h datoteke. Gazdu svakako zanima kome viknuti, a to ide u *.h datoteku. Ona opisuje što rob u programu može raditi i tamo su uvijek prototipovi funkcija.Zato u main.c datoteci postoji #include "led_blink.h" jer glavni program time dolazi do nižeg sloja.Pogledamo li led_blink.h datoteku vidjeti ćemo da u njoj postoji samo prototip funkcije InitLedBlink();

led_blink.c i led_blink.h čine jedan modul programa u kojemu led_blink.c odrađuje čitav posao, dok je led_blink.h samo poslovođa koji govori što rob sve može napraviti... U našoj situaciji rob može samo inicijalizirati i pripremiti sve za blinkanje LED-a, ISR će odraditi sve ostalo i tu dolazimo do situacije da je main program i dalje slobodan i može galamiti na druge podprograme koji nešto drugo trebaju odraditi...

Pogledamo li iz main.c datoteke, ostale datoteke su za nas zatvoren sustav i ne zanima nas što

Page 76: U Potrazi Za Tartufima AVR

tamo piše... Eventualno nas zanima što piše u led_blink.h jer sve što je tamo mi možemo pozvati i koristiti.Attachments

Tartufi.rar(12.52 KiB) Not downloaded yet

bob4 wrote:Kladim se da polovina koristika tog programskog jezika nezna što su to push i pop naredbe, ret i reti, ISR pa ih zbog toga ne boli glava.

Ja se već podosta godina koristim s tim bascomom; (još sam i na 89c4051) i on za sad zadovoljava sve moje potrebe u elektro projektima; i stvarno zbog tog me ne boli glava (a pogotovo da razmišljam o mikrosekundama; ili manje).A ovdje ima dobri stručnjaka da mi pomognu kada zapne. HVALA

Moram se pozvati na ovaj komentar, jer prvenstevno svi problemi programiranja leže u tim uS i nS.

Uzeti ću paušalno da je jedinica vremena 1mS, i u bilo kojem programu zamisliti da mi glavna petlja zastane za 1mS vremena... Matematicki moram baciti okvirni proračun svega onog što MCU radi:

Jezga je uistinu u stanju čitav program FLASH memorije izvršiti u približno 200-300 uS, pa pogledajmo to iz drugog kuta;Ako glavna petlja radi koristan posao 300 uS, a čeka 1mS očito smo bacili 70% mogućnosti MCU-a... Možete to okrenuti na puno kombinacija: Ako smo u stanju na takav način bacati ono što MCU čini moćnim, onda možemo gledati da se MCU ne vrti na 8 ili 20 Mhz, nego 70% sporije... Realno, naš program sa tih 1mS čekanja i vrti se "imaginarno" toliko sporije, jer umjesto da odrađujemo koristan posao, ne radimo apsolutno ništa.

Izačunaj to u realnom vremenu:70% vremena MCU ne radi ništa30% vremena MCU izvrši čitav svoj program FLASH memorije

Dakle gledajući u periodu od jedne sekunde, mi zapravo ništa ne radimo... Zato _delay_ms ne postoji, jer kada čekamo u pravom smislu gubimo vrijeme... Najveći problem je taj što smo uvijek u beskonačnoj petlji, tako da vrijeme koje gubimo u pravom smislu gubimo iznova i iznova i to beskonačno dugo dok god se vrti naš glavni program sa delay_ms(1);

"U potrazi za tartufima AVR-a" - TCCR0A, TIMSK0, DDRC, SREG, sei i t_led

Što u programskom jeziku C znače gore navedeni pojmovi? Apsolutno ništa!Iako to često možemo pročitati u programima, tartufima, datasheetu to nema veze sa programskim jezikom C.

U prilogu je zadnji program sa malo drugačijom main.c datotekom koja sada izgleda ovako:Code: Select all

Page 77: U Potrazi Za Tartufima AVR

#include "led_blink.h"

unsigned char TCCR0A, TIMSK0, DDRC, SREG, sei;unsigned int t_led;

int main(void){    InitLedBlink();        TCCR0A = 0;    TIMSK0 = 0;    DDRC = 0;    SREG = 0;    sei = 0;    t_led = 0;

    while(1){    }}

Prvo logično pitanje je hoće li LED na PC0 prestati blinkati nakon postavljanja TCCR0A, TIMSK0, DDRC, SREG, sei i t_led na vrijednost 0? Drugo logično pitanje bilo bi što ja uopće postavljam na 0? Treće pitanje koje može zbuniti je što je sad na kraju TCCR0A, TIMSK0, DDRC, SREG, sei i t_led? Jesu li to registri MCU-a?

Dobro došli u C...Točan odgovor na ova pitanja je: "Zavisi s koje strane gledaš"main.c je glavna datoteka u programu, no ipak moramo znati što ona vidi od čitavog ostalog programa, te koje datoteke su #include. Ako pogledamo main.c vidimo da ona poziva jedino:Code: Select all

#include "led_blink.h"

Dakle, ona može pristupiti samo onome što piše u led_blink.h datoteci, no ni tamo se ne spominje nikakav TIMSK, SREG, ili t_led... Dakle, za main.c datoteku ovi pojmovi ne postoje i naravno da ja mogu upravo tako nazvati svoje besmislene varijable.Ako gledamo iz main.c datoteke na ove pojmove onda su to samo nove varijable i imati će posebno mjesto u RAM memoriji. Tim varijablama moguće je pristupiti samo iz main.c datoteke.

Ali ako pogledamo iz led_blink.c datoteke stvari su drastično drugačije. led_blink.c poziva 2 includea:Code: Select all

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

A upravo onaj avr\io.h sadrži MACRO naredbe svih registara mikrokontrolera, pa tako i TIMSK0, DDRC, TCCR0A... Ako iz led_blink.c pišemo po TIMSK, DDRC... mi pišemo po registrima, jer smo do njih došli preko #include <avr\io.h>

Nešto moramo i zaključiti:1. U ovakvom programu nemoguće je iz main.c datoteke pozvati #include <avr\io.h> jer bi se compiler morao buniti. Ne možemo imati isto ime registra iz biblioteke i željeti da se naša varijabla tako zove.

Page 78: U Potrazi Za Tartufima AVR

2. U ovakvom programu nemoguće je u led_blink.c datoteci deklarirati varijable TIMSK0, DDRC... jer preko poziva #include <avr\io.h> već postoje MACRO naredbe i compiler se opet mora buniti...

Možda vas muči što sa t_led varijablom?To su 2 različite varijable, na različitim adresama u RAM-u. main.c posjeduje jednu, a led_blink.c drugu. Varijable jesu statične u RAM-u, isto se zovu, no pristup njima ovisi samo o tome iz koje datoteke pristupamo.

C dozvoljava čuda sa varijablama, pa se tako može pristupiti i varijablama u različitim datotekama, no ovo su osnove od kojih je najbolje početi... Ne smijemo pisati programe koji se nalaze u jednoj datoteci, jer tada nam se sve pomješa, imamo 40 varijabla, a da ne znamo niti iz kojeg su programa, niti tko ih koristi. Ne smijemo pisati niti funkcije u istoj datoteci kojima tamo nije mjesto jer onda nam se i to sve smulja u nečitljivo sranje. A da ne govorim o tome kako ne smijemo pisati aplikaciju i drivere u istoj datoteci jer tada se više ne zna tko je glavni i tko koga poziva i čija je koja varijabla.

*.h datoteke trebale bi samo opisivati C datoteku i sadržavati prototip funkcija koje se nalaze u C datoteci, pa zato i imamo led_blink.c i led_blink.h gdje h datoteka ima samo jedan prototip jedine funkcionalnosti koju led_blink.c može izvršiti. Često u praksi nešto kompleksnije koristimo h datoteke, no u suštini to je njihova svrha.Attachments

Tartufi.rar(13.22 KiB) Downloaded 1 time

"U potrazi za tartufima AVR-a" - Zašto odvajati poslove?

Ne mogu to direktno napisati zato i zato jer bi morali doći u situaciju da shvatite zašto i zato... Nužno neizbježno mora se. Prije nekih možda 2 ili 3 godine pisao sam bazu podataka u C-u za external FLASH i kontrolu pristupa, pa čisto prolistajte datoteke i pogledajte što je u *.h i *.c datotekama... Svaka moja *.h datoteka ima samo prototipove funkcija i typedef struktura, a svaka *.c datoteka ima izvršni program. Danas bi ovo pisao drugačije i ima dosta grešaka u strukturi samog programa, no od suštine nije daleko...

Zamislite da sve to stoji u samoj jednoj datoteci... Možete probati i copy/paste u jednu datoteku... Cisto sumljam da se netko moze snaci u tome... Zato definitivno odvajajte funckionalnosti u datoteke.Attachments

Database.rar(63.6 KiB) Downloaded 1 time