|
Introducere in programarea și utilizarea microcontrollerelor Atmel AVR
In aceasta prima parte a tutorialului vei face cunoștința cu lumea microcontrolerelor folosind un Atmel ATMega8 și niște leduri. Cu toate ca ceea ce ține de microcontrollere este explicat la nivel de absolut incepator sunt necesare cunoștințe cel medii de programare in C. In mare parte totul va fi explicat in limba romana dar sunt necesare cunoștințe de limba engleza pentru a ințelege anumite lucruri.
Ce este ATMega8? Este un microcontroller cu 8KB memorie Flash (e suficienta pentru un program destul de complex), 1KB memorie RAM(acolo sunt allocate variabilele folosite in program), 512B EEPROM(aici poți stoca date; spațiul e cam mic) și mulți pini pentru intrari / ieșiri. Cam așa arata:
La acest microcontroller vei conecta cateva leduri cu care ne vom juca mai tarziu.
In cazul in care ai uitat cum arata un led și cum se conecteaza arunca o privire pe imaginea de mai jos:
Ca sa te poți apuca de programarea propriu-zisa ai nevoie intai de un circuit care sa poata indeplini funcțiile implementate in program (program la care ma voi referi in continuare ca "soft" sau "firmware"). Pentru realizarea circuitului ai nevoie de urmatoarele:
Letcon, fludor, sacaz - pentru lipirea componentelor (daca folosești breadboard nu sunt necesare). In cazul in care nu ai mai lipit pana acum componente uita-te la filmulețul din link-ul urmator: http://www.youtube.com/watch?v=AOdnGUMi7lQ
Cablaj de test sau breadboard
8 leduri
8 rezistențe (100ohm sunt ok)
7805 - regulator de tensiune pentru a putea alimenta circuitul de la cam orice alimentator gasit prin casa cu tensiune DC mai mare de 7V.
ATMega8 - microcontrollerul folosit in acest tutorial
Fire de legatura
USBasp - programatorul folosit in acest tutorial. Poți folosi orice programator cu programele adecvate.
Pe langa resursele hardware mai ai nevoie și de niște programe cu care sa scrii / compilezi codul și cu care sa transferi softul rezultat in microcontroller (cuvantul e destul de lung așa ca in continuare voi folosi notația uC). Programele necesare sunt urmatoarele:
AVR Studio 4.18 - mediul de dezvoltare a codului. Conține și un simulator foarte util. (http://en.stkshop.com/download--download_id-65.html)
AVR Studio SP3 - se instaleaza impreuna cu AVR Studio 4.18. Il gasești aici: http://www.atmel.com/dyn/resources/prod_documents/AVRStudio4.18SP3.exe
WinAVR - compilatorul de C care se folosește impreuna cu AVR Studio 4 (http://sourceforge.net/projects/winavr/files/WinAVR/20100110/WinAVR-20100110-install.exe/download)
Khazama Programmer - programul cu care se transfera softul in uC (http://khazama.com/project/programmer/)
Circuitul pe care il vei folosi in aceasta prima parte a tutorialului este cel din imaginea de mai jos. Va trebui ca folosind uneltele descrise mai sus sa conectezi componentele intre ele ca in schema. Recomandat este ca toate ledurile sa fie montate intr-un singur rand pentru a ințelege mai bine exemplele de cod prezentate.
Langa leduri se vor monta (vertical - pentru a face economie de spațiu pe placuța; orizontal in cazul in care spațiul nu e o problema) rezistențele. Conexiunile pana la uC se vor face folosind fire de legatura. Poziția conectorului pentru programare nu este critica dar incearca sa aranjezi componentele in așa fel incat sa ocupi cat mai puțin spațiu. Pe masura ce vei parcurge tutorialul placuța se va umple cu componente!
Pentru a ințelege ceea ce urmeaza este necesar sa citești din datasheet-ul uC-ului (http://www.atmel.com/dyn/resources/prod_documents/doc2486.pdf) urmatoarele:
Pag. 2 - Pin configurations
Pag. 5 - Port B
Pag. 51 - I/O Ports pana la Table 20 (Pag. 53)
Pag. 55 pana la Digital Input Enable and Sleep Modes
AVR Studio 4
Dupa ce ai citit ce este indicat mai sus te poți apuca de programarea propriu-zisa. AVR Studio 4 este programul in care vei dezvolta, compila și simula codul. Pentru crearea unui proiect nou deschide AVR Studio 4.
In fereastra care iți apare selecteaza New Project apoi AVR GCC.
Da un nume proiectului și selecteaza unde vrei sa fie salvat apoi apasa Next.
Selecteaza AVR Simulator iar in dreapta ATMega8 apoi apasa Finish.
In fereastra care iți apare vei scrie codul.
Poate parea complicat la inceput insa este in mare același C pe care poate ca l-ai facut la informatica in liceu.
Ca și in liceu codul incepe cu headerele necesare. Acum vei folosi doar #include <avr/io.h> pentru accesarea pinilor. Urmeaza veșnicul void main() in care apare codul propriu-zis.
Pana sa ajungi sa scrii cod trebuie sa știi cateva noțiuni mai puțin folosite in programarea PC-ului:
Familia de uC AVR lucreaza pe 8biți. Asta inseamna ca toți registri au lungime de 8 biti putand stoca o valoare intre .
1 logic = VCC (in cazul de fața 5V); 0 logic = GND (0V)
(1<<X) - inseamna ca reprezentarea lui 1 in baza doi (0b00000001) o deplasez in stanga cu X poziții (). Pentru X=2 rezultatul este 0b00000100. Toata operația are ca rezultat setarea bitului X ca 1.
~(1<<X) - inseamna ca la toata operația descrisa cu un pas mai sus se aplica complementul lui 1. Altfel spus: ce este 1 devine 0 si ce e 0 devine 1. Exemplu: ~(1<<3)=~(0b00001000)=0b1111011. Toata operația are ca efect setarea bitului X ca 0 iar a celorlalți ca 1.
A|=B; - inseamna A=A|B, adica A ia valoarea rezultata din aplicarea operatiei logice SAU intre A și B. La fel e pentru A&=B;
Inainte de a incepe sa scrii codul trebuie sa te asiguri ca proiectul are setarile corecte. Apasa butonul pentru a deschide fereastra de configurare. Pentru tutorialul acesta AVR Studio este configurat ca in imaginea de mai jos:
Acum poți incepe sa scrii primul tau cod pentru uC. Exemplul urmator demonstreaza cum poți aprinde doua din cele opt leduri conectate pe PORTB, respectiv ledul 0 și 7. Poți alege orice led și oricate leduri.
#include <avr/io.h> //header necesar pt a putea accesa pinii uC-ului
void main()
Dupa ce ai terminat de scris codul va trebui sa-l compilezi apasand tasta F7. Daca totul a decurs bine ar trebui sa vezi ceva asemanator cu imaginea de mai jos:
Deschide programul Khazama Programmer și selecteaza Load FLASH File (marcat cu roșu).
In fereastra care iți apare mergi la locația unde ai salvat proiectul, folderul default. Acolo gasești fișierul .hex rezultat in urma compilarii codului. Selecteaza fișierul, apoi Open.
Asigura-te ca programatorul e conectat atat la calculator cat și la montaj iar montajul este alimentat. Selecteaza Command ->Read Chip Signature. Daca rezultatul este 0x1E9307 poți trece la programarea propriu-zisa a uC-ului apasand butonul Write FLASH to Chip marcat cu albastru in imaginea de mai sus. Presupunand ca scrierea s-a facut cu success acum ar trebui ca cele doua leduri despre care am vorbit mai sus sa fie aprinse.
Hai acum sa incercam sa facem ledurile sa "clipeasca". Deoarece uC-ul funcționeza la frecvența foarte mare (1MHz in cazul celui folosit in acest tutorial) și executa o instrucțiune/ciclu ledurile iși vor schimba starea mult prea repede ca ochiul uman sa sesizeze ceva. Avem nevoie de o modalitate de a mari timpul cat ledul se afla intr-o stare. Pentru asta avem la dispozitie funcțiile _delay_us(double __us) (intarzie execuția urmatoarei intrucțiuni cu numarul cerut de micro-secunde) și _delay_ms(double __ms) (intarzie execuția urmatoarei instrucțiuni cu un anumit numar de milisecunde) incluzand headerul util/delay.h.
Noi ne vom folosi de funcția _delay_ms() pentru a obține intarzieri vizibile iar instrucțiunile necesare vor fi plasate in bucla infinita while(1) codul urmand a fi executat repetitiv atat timp cat montajul este alimentat.
#include <avr/io.h> //header necesar pt a putea accesa pinii uC-ului
#include <util/delay.h> //header necesar pentru delay
void main()
O modalitate de a scrie codul mai elegant ar fi sa definim operațiile cu biții din PORTB și delay-ul cu cate un nume. Exemplu:
#include <avr/io.h> //header necesar pt a putea accesa pinii uC-ului
#include <util/delay.h> //header necesar pentru delay
#define LED0_ON() PORTB|=(1<<PB0)
#define LED0_OFF() PORTB&=~(1<<PB0)
#define LED7_ON() PORTB|=(1<<PB7)
#define LED7_OFF() PORTB&=~(1<<PB7)
#define DELAY() _delay_ms(500)
void main()
Cele doua coduri sunt echivalente. Diferența este doar de estetica. Poți testa codul compilandu-l și apoi scriindu-l in uC.
1. Folosind circuitul construit, implementeaza un numarator binar pe 8 biți.
2. Folosind circuitul construit, implementeaza un program care sa deplaseze un led aprins la stanga și la dreapta alternativ, lungimea deplasarii fiind de 7 biți.
3. Implementeaza un program care sa deplaseze ledul stins la stanga și la dreapta alternativ, lungimea deplasarii fiind de 7 biți.
4. Combina programele create anterior (2 și 3) astfel incat pornind cu deplasarea ledului aprins, dupa 3 deplasari complete se va trece la deplasarea ledului stins. Dupa 3 astfel de deplasari programul trece din nou la deplasarea ledului aprins, ciclul repetandu-se la nesfarșit.
5. Scrie un program care sa conțina cel puțin 3 animații diferite și care se vor schimba dupa un numar ales de cicluri.
6. Folosind 3 leduri consecutive pentru reprezentarea a 3 variabile binare a,b,c și unul pentru f, implementeaza funcția .
O varianta de rezolvare a temei se gasește in folderul Tema 1.
In prima parte ai facut cunoștința cu microcontrollerele și ai invațat cum se folosește un port ca output. In cele ce urmeaza vei invața cum poți folosi butoane pentru a schimba funcționarea montajului.
Pentru a putea folosi butoanele intai sa achiziționezi (daca nu ai deja) și sa adaugi la circuitul existent urmatoarele componente:
2 push butoane
2 rezistente de 5K6
2 condensatori 100nF
Circuitul arata cum se vede mai jos. Este același circuit pe care l-ai folosit pana acum cu butoanele conectate la pinii PD2 și PD3.
Inainte de a te apuca de scris codul trebuie sa știi ca aproape fiecare pin, pe langa funcția sa principala de input / output mai are și alte funcții secundare notate in paranteza in dreptul pinului din schema. Butoanele le-ai conectat pe pinii PD2 și PD3. In dreptul lor in schema ai sa vezi scris INT0 respectiv INT1 care se refera la INTreruperi externe.
Ce este o intrerupere? O intrerupere poate fi descrisa ca un eveniment care forțeaza uC-ul sa execute codul scris pentru acea intrerupere in locul codului principal. La terminarea codului din intrerupere se revine unde a ramas in codul principal. Pentru a ințelege mai bine ce este o intrerupere, cum se folosesc in general dar și cum vei folosi butoanele iți recomand sa citești din datasheet-ul uC-ului urmatoarele:
Pag. 56 - Alternate Port Functions
Pag. 63 - Alternate Functions of Port D
Pag. 63-68 - External Interrupts
Pentru a te putea folosi efectiv de intreruperi va trebui sa incluzi in cod headerul avr/interrupt.h . Intreruperile sunt procesare de o funcție numita ISR (Interrupt Service Routine) ce primește ca parametru numele vectorului de intrerupere (ex: ISR(INT0_vect)). Pentru butoane vei folosi ISR(INT0_vect) si ISR(INT1_vect). In interiorul fiecarei funcții vei scrie ce anume vrei sa se intample cand are loc intreruperea respectiva.
Butoanele au rezistențe de pull-up (pe pinul respectiv al butonului ai 5V) care sunt conectate și la pinii uC-ului la un capat și masa in cealalta parte. Cand apeși unul din butoane, tensiunea pe pinul uC-ului va deveni 0V, deci ai o tranziție HIGH-LOW. Atat timp cat butonul este apasat ai LOW LEVEL. Cand dai drumul butonului vei avea din nou 5V, deci o tranziție LOW-HIGH. In funcție de cum ai configurat intreruperea (vezi tabelul 31 și tabelul 32 de la pagina 66-67 a datasheetului) ea se va declanșa la unul din evenimentele descrise mai sus. In exemplele de mai jos intreruperea va fi configurata sa declanșeze la o tranziție HIGH-LOW.
Pe langa configurarea corecta a intreruperilor, pentru ca acestea sa declanșeze trebuie activate global folosind funcția sei(). Pentru a dezactiva global intreruperile se folosește funcția cli().
1. Unul dintre butoane incrementeaza valoarea lui PORTB cu o unitate la fiecare apasare iar celalalt decrementeaza valoarea lui PORTB in același fel.
#include <avr/io.h>
#include <avr/interrupt.h>
ISR(INT0_vect)
ISR(INT1_vect)
void main()
2. Butonul conectat la INT0 intrementeaza valoarea lui PORTB atat timp cat e apasat iar celalalt imparte la doi valoarea la fiecare ridicare a butonului.
#include <avr/io.h>
#include <avr/interrupt.h>
ISR(INT0_vect)
ISR(INT1_vect)
void main()
Tema 2 - un exemplu de rezolvare se gasește in folderul Tema2
Daca pana acum ai facut programe in care pentru a intarzia execuția cu un anumit timp ai folosit o metoda destul de ineficienta (funcția _delay_ms()) pentru delay-uri mari, acum vei invața cum poți folosi timerele de care dispune uC-ul pentru a executa la intervale de timp regulate o anumita parte de cod, in același timp lasand restul programului sa funcționeze normal.
Pentru a ințelege cum funționeaza timerele și ce fac exemplele de mai jos, arunca o privire (atenta) in datasheet la:
Pag. 3 - Overview (doar primul paragraf)
Pag. 69 - 8-bit Timer/Counter0, Overview, Registers, Definitions
Pag. 70 - Timer/Counter Clock Sources, Operation
Pag. 71 - Timer/Counter Timig Diagrams
Pag. 72-73 - 8-bit Timer/Counter Register Description
Pag. 74 - Timer/Counter0 and Timer/Counter1 Prescalers, Internal Clock Source
Deoarece, așa cum ai citit in datasheet, timerele funționeaza independent de restul programului, pentru a ști cand timerul a ajuns la valoarea maxima, vei folosi o intrerupere intr-un mod asemanator cu cele pe care le-ai folosit pentru butoane. Intreruperea va fi ISR(TIMER0_OVF_vect) care va declanșa de fiecare data cand timerul ajunge la valoarea maxima. Acum probabil ai sa te intrebi la ce te ajuta toate chestiile astea? Hai sa luam un exemplu concret in care - frecventa la care lucreaza uC-ul (in cazul de fața 1MHz), - frecvența la care funcționeaza timerul.
Alegem un prescaler de 1/1024 =>
Știind ca timerul este pe 8 biți rezulta ca ISR(TIMER0_OVF_vect) va declanșa de 976/256=3.81 ori pe secunda. Ce faci daca vrei ca o anumita parte de cod sa se execute mai rar de 3.81 ori intr-o secunda? Raspunsul este destul de simplu: implementezi un timer software, care asemenea celui hardware va avea un prescaler stabilit de tine.
Nota: In cele ce urmeaza voi folosi notații și expresii de genul "declanșeaza la 3.81Hz" = declanșeaza de 3.81 ori pe sec.
In cazul de fața daca vrei sa obții execuția unui cod la aproximativ 1Hz poți face ca in exemplul de mai jos:
ISR(TIMER0_OVF_vect) // declanseaza la 3.81Hz
Exemplul de cod de mai sus da o frecvența de execuție a codului de aproximativ 1.27Hz.
ATENȚIE! Frecvența de 1.27Hz obținuta mai sus este frecvența la care se va intra in if, respectiv in branch-ul respectiv. Frecvența de execuție a instrucțiunilor din branch este .
Acum ca ai vazut cam cum sta treaba cu calculele, hai sa vedem și ceva exemple practice de cod. Exemplele ce urmeaza se bazeaza pe exemplele anterioare pentru a ințelege mai bine cum se pot combina noțiunile invațate pana acum.
Ex1: "Leduri clipitoare" - versiunea 2
#include <avr/io.h>
#include <avr/interrupt.h>
ISR(TIMER0_OVF_vect)
void main()
Ex2: "Moving LEDs" - aplicație folosește Timer0 pentru a obține delay-ul necesar aprinderii și stingerii ledurilor. INT0 și INT1 sunt folosite pentru detecția apasarii butoanelor care modifica viteza și direcția de deplasare.
#include <avr/io.h>
#include <avr/interrupt.h>
uint8_t speed=1,t=0,led=0,i=0, dir=0;
ISR(TIMER0_OVF_vect)
else
}
else
else
}
}
else
else
}
else
else
}
}
}
ISR(INT0_vect)
ISR(INT1_vect)
void main()