tutorial avr (03.06.2012)
TRANSCRIPT
Tutorial Introducere în programarea și utilizarea
microcontrollerelor Atmel AVR
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
2
Cuprins
Partea 1 - “Hello world!” ................................................................................................................. 3
Introducere ................................................................................................................................... 3
De ce ai nevoie? ........................................................................................................................... 4 Programarea uC-ului .................................................................................................................... 5 Cum scriu cod pentru uC? ............................................................................................................ 8 Cum transfer programul în uC? .................................................................................................... 9 Leduri „clipitoare” ...................................................................................................................... 10
Tema 1 ........................................................................................................................................ 11
Partea 2 – Push the button .............................................................................................................. 12
De ce ai nevoie? ......................................................................................................................... 12 Cum folosesc butoanele în cod? ................................................................................................. 13 Exemple ...................................................................................................................................... 13 Tema 2 ........................................................................................................................................ 14
Partea 3 – The clock is ticking ....................................................................................................... 15 Exemple ...................................................................................................................................... 16
Partea 4 – Fade to black ................................................................................................................. 18 Cum se folosește? ....................................................................................................................... 18 Exemple ...................................................................................................................................... 18
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
3
Partea 1 - “Hello world!”
Introducere
În această primă parte a tutorialului vei face cunoștință 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 începator sunt necesare cunoștințe cel puțin medii de programare în C.
În mare parte totul va fi explicat în limba română dar sunt necesare cunoștințe de limba engleză
pentru a înțelege anumite lucruri.
Ce este ATMega8? Este un microcontroller cu 8KB memorie Flash (e suficientă 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 intrări /
ieșiri. Cam așa arată:
La acest microcontroller vei conecta câteva leduri cu care ne vom juca mai târziu.
În cazul în care ai uitat cum arata un led și cum se conectează arunca o privire pe imaginea de
mai jos:
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
4
De ce ai nevoie?
Ca să te poți apuca de programarea propriu-zisă ai nevoie întâi de un circuit care să poată
îndeplini funcțiile implementate în program (program la care mă voi referi în continuare ca “soft”
sau “firmware” - FW). Pentru realizarea circuitului ai nevoie de următoarele:
Letcon, fludor, sacâz (colofoniu) – pentru lipirea componentelor (dacă
folosești breadboard nu sunt necesare). În cazul în care nu ai mai lipit până
acum componente uită-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 găsit prin casă cu tensiune DC mai mare de 7V.
ATMega8 – microcontrollerul folosit în acest tutorial
Fire de legatură
USBasp – programatorul folosit în acest tutorial. Poți folosi orice
programator cu programele adecvate.
Pe lângă resursele hardware mai ai nevoie și de niște programe cu care să scrii / compilezi
codul și cu care să transferi softul rezultat în microcontroller (cuvântul e destul de lung așa că în
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 instalează împreună cu AVR Studio 4.18. Îl găsești
aici:
http://www.atmel.com/dyn/resources/prod_documents/AVRStudio4.18
SP3.exe
WinAVR – compilatorul de C care se folosește împreună cu AVR Studio 4
(http://sourceforge.net/projects/winavr/files/WinAVR/20100110/WinA
VR-20100110-install.exe/download)
Khazama Programmer – programul cu care se transferă softul în uC
(http://khazama.com/project/programmer/)
Circuitul pe care îl vei folosi în această primă parte a tutorialului este cel din imaginea de
mai jos. Va trebui ca folosind uneltele descrise mai sus să conectezi componentele între ele ca în
schema. Recomandat este ca toate ledurile sa fie montate într-un singur rând pentru a înțelege
mai bine exemplele de cod prezentate.
Lângă leduri se vor monta (vertical - pentru a face economie de spațiu pe plăcuță;
orizontal în cazul în care spațiul nu e o problemă) rezistențele. Conexiunile până la uC se vor face
folosind fire de legătură. Poziția conectorului pentru programare nu este critică dar încearcă să
aranjezi componentele în așa fel încat să ocupi cât mai puțin spațiu. Pe măsura ce vei parcurge
tutorialul plăcuța se va umple cu componente!
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
5
Programarea uC-ului
Pentru a înțelege ceea ce urmează este necesar să citești din datasheet-ul uC-ului
(http://www.atmel.com/dyn/resources/prod_documents/doc2486.pdf) următoarele:
Pag. 2 – Pin configurations
Pag. 5 – Port B
Pag. 51 – I/O Ports până la Table 20 (Pag. 53)
Pag. 55 până 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-zisă. AVR
Studio 4 este programul în care vei dezvolta, compila și simula codul. Pentru crearea unui proiect
nou deschide AVR Studio 4.
În fereastra care îți apare selectează New Project apoi AVR GCC.
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
6
Dă un nume proiectului și selectează unde vrei sa fie salvat apoi apasă Next.
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
7
Selectează AVR Simulator iar în dreapta ATMega8 apoi apasă Finish.
În fereastra care îți apare vei scrie codul.
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
8
Cum scriu cod pentru uC?
Poate părea complicat la început însă este în mare același C pe care poate că l-ai facut la
informatică în liceu.
Ca și în liceu codul începe cu headerele necesare. Acum vei folosi doar #include
<avr/io.h> pentru accesarea pinilor. Urmează veșnicul void main() în care apare codul propriu-
zis.
Până să ajungi să scrii cod trebuie să știi câteva noțiuni mai puțin folosite în programarea
PC-ului:
Familia de uC AVR lucreaza pe 8biți. Asta înseamnă ca toți registri au
lungime de 8 biti putând stoca o valoare între 2550 xFFx 0000
111111110000000000 bb .
1 logic = VCC (în cazul de față 5V); 0 logic = GND (0V)
(1<<X) – înseamnă că reprezentarea lui 1 în baza doi (0b00000001) o
deplasez în stânga cu X poziții ( 71 X ). Pentru X=2 rezultatul este
0b00000100. Toată operația are ca rezultat setarea bitului X ca 1.
~(1<<X) – înseamnă că la toată operația descrisă cu un pas mai sus se
aplică complementul lui 1. Altfel spus: ce este 1 devine 0 si ce e 0 devine
1. Exemplu: ~(1<<3)=~(0b00001000)=0b1111011. Toată operația are ca
efect setarea bitului X ca 0 iar a celorlalți ca 1.
A|=B; - înseamnă A=A|B, adică A ia valoarea rezultată din aplicarea
operatiei logice SAU între A și B. La fel e pentru A&=B;
Înainte de a începe să scrii codul trebuie să te asiguri că proiectul are setările corecte.
Apasă butonul pentru a deschide fereastra de configurare. Pentru tutorialul acesta AVR
Studio este configurat ca în imaginea de mai jos:
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
9
Acum poți începe să scrii primul tău cod pentru uC. Exemplul urmator demonstrează cum poți
aprinde două din cele opt leduri conectate pe PORTB, respectiv ledul 0 și 7. Poți alege orice led
și oricâte leduri.
#include <avr/io.h> //header necesar pt a putea accesa pinii uC-ului void main() { DDRB|=(1<<DDB0)|(1<<DDB7); //setează pinul 0 și 7 ca ieșire PORTB|=(1<<PB0)|(1<<PB7); //setează pinul 0 și 7 ca 1 logic while(1); //bucla infinită }
Cum transfer programul în uC?
După ce ai terminat de scris codul va trebui sa-l compilezi apăsând tasta F7. Dacă totul a
decurs bine ar trebui să vezi ceva asemănător cu imaginea de mai jos:
Deschide programul Khazama Programmer și selectează Load FLASH File (marcat cu
roșu).
În fereastra care îți apare mergi la locația unde ai salvat proiectul, folderul default. Acolo găsești
fișierul .hex rezultat în urma compilării codului. Selectează fișierul, apoi Open.
Asigură-te că programatorul e conectat atât la calculator cât și la montaj iar montajul este
alimentat. Selectează Command ->Read Chip Signature. Dacă rezultatul este 0x1E9307 poți trece
la programarea propriu-zisă a uC-ului apasând butonul Write FLASH to Chip marcat cu albastru
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
10
în imaginea de mai sus. Presupunând că scrierea s-a făcut cu success acum ar trebui ca cele două
leduri despre care am vorbit mai sus să fie aprinse.
Leduri „clipitoare”
Hai acum să încercăm să facem ledurile să „clipească”. Deoarece uC-ul funcționeză la
frecvență foarte mare (1MHz în cazul celui folosit în acest tutorial) și execută o instrucțiune/ciclu
ledurile își vor schimba starea mult prea repede ca ochiul uman să sesizeze ceva. Avem nevoie de
o modalitate de a mări timpul cât ledul se află într-o stare. Pentru asta avem la dispozitie funcțiile
_delay_us(double __us) (întârzie execuția urmatoarei intrucțiuni cu numărul cerut de micro-
secunde) și _delay_ms(double __ms) (întârzie execuția următoarei instrucțiuni cu un anumit
număr de milisecunde) incluzând headerul util/delay.h.
Noi ne vom folosi de funcția _delay_ms() pentru a obține întârzieri vizibile iar
instrucțiunile necesare vor fi plasate în bucla infinită while(1) codul urmând a fi executat repetitiv
atât timp cât 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() { DDRB|=(1<<DDB0)|(1<<DDB7); //setează pinul 0 și 7 ca ieșire while(1)
{PORTB|=(1<<PB0); //aprind ledul 0 _delay_ms(500); //aștept 500ms PORTB&=~(1<<PB0); //sting ledul 0 PORTB|=(1<<PB7); //aprind ledul 7 _delay_ms(500); //aștept 500ms PORTB&=~(1<<PB7); //sting ledul 7 }
}
O modalitate de a scrie codul mai elegant ar fi sa definim operațiile cu biții din PORTB și
delay-ul cu câte 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() { DDRB|=(1<<DDB0)|(1<<DDB7); //setează pinul 0 și 7 ca ieșire while(1)
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
11
{LED0_ON(); //aprind ledul 0 DELAY(); //aștept 500ms LED0_OFF(); //sting ledul 0 LED7_ON(); //aprind ledul 7 DELAY(); //aștept 500ms LED7_OFF(); //sting ledul 7 }
}
Cele două coduri sunt echivalente. Diferența este doar de estetică. Poți testa codul
compilându-l și apoi scriindu-l în uC.
Tema 1
1. Folosind circuitul construit, implementează un numarator binar pe 8 biți.
2. Folosind circuitul construit, implementează un program care să deplaseze un led
aprins la stânga și la dreapta alternativ, lungimea deplasării fiind de 7 biți.
3. Implementează un program care să deplaseze ledul stins la stânga și la dreapta
alternativ, lungimea deplasarii fiind de 7 biți.
4. Combină programele create anterior (2 și 3) astfel încât pornind cu deplasarea ledului
aprins, după 3 deplasări complete se va trece la deplasarea ledului stins. După 3 astfel
de deplasări programul trece din nou la deplasarea ledului aprins, ciclul repetându-se
la nesfarșit.
5. Scrie un program care să conțină cel puțin 3 animații diferite și care se vor schimba
dupa un număr ales de cicluri.
6. Folosind 3 leduri consecutive pentru reprezentarea a 3 variabile binare a,b,c și unul
pentru f, implementează funcția cbacabf .
O variantă de rezolvare a temei se găsește în folderul Tema 1.
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
12
Partea 2 – Push the button
În prima parte ai făcut cunoștință cu microcontrollerele și ai învățat cum se folosește un
port ca output. În cele ce urmează vei învăța cum poți folosi butoane pentru a schimba
funcționarea montajului.
De ce ai nevoie?
Pentru a putea folosi butoanele întâi să achiziționezi (dacă nu ai deja) și să adaugi la
circuitul existent urmatoarele componente:
2 push butoane
2 rezistente de 5K6
2 condensatori 100nF
Circuitul arată cum se vede mai jos. Este același circuit pe care l-ai folosit până acum cu
butoanele conectate la pinii PD2 și PD3.
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
13
Cum folosesc butoanele în cod?
Înainte de a te apuca de scris codul trebuie să știi că aproape fiecare pin, pe lângă funcția
sa principală de input / output mai are și alte funcții secundare notate în paranteză în dreptul
pinului din schemă. Butoanele le-ai conectat pe pinii PD2 și PD3. În dreptul lor în schemă ai să
vezi scris INT0 respectiv INT1 care se referă la ÎNTreruperi externe.
Ce este o întrerupere? O întrerupere poate fi descrisă ca un eveniment care forțează
uC-ul să execute codul scris pentru acea întrerupere în locul codului principal. La terminarea
codului din întrerupere se revine unde a rămas în codul principal. Pentru a înțelege mai bine ce
este o întrerupere, cum se folosesc în general dar și cum vei folosi butoanele îți recomand sa
citești din datasheet-ul uC-ului următoarele:
Pag. 56 – Alternate Port Functions
Pag. 63 – Alternate Functions of Port D
Pag. 63-68 – External Interrupts
Pentru a te putea folosi efectiv de întreruperi va trebui să incluzi în cod headerul
avr/interrupt.h . Întreruperile sunt procesate de o funcție numită ISR (Interrupt Service Routine)
ce primește ca parametru numele vectorului de întrerupere (ex: ISR(INT0_vect)). Pentru butoane
vei folosi ISR(INT0_vect) și ISR(INT1_vect). În interiorul fiecărei funcții vei scrie ce anume vrei
să se întâmple când are loc întreruperea respectivă.
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 masă în cealaltă parte. Când apeși unul din butoane,
tensiunea pe pinul uC-ului va deveni 0V, deci ai o tranziție HIGH-LOW (falling edge). Atât timp
cât butonul este apăsat ai LOW LEVEL. Când dai drumul butonului vei avea din nou 5V, deci o
tranziție LOW-HIGH (rising edge). În funcție de cum ai configurat întreruperea (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. În exemplele de mai jos întreruperea va fi configurată să declanșeze la falling edge.
Pe lângă configurarea corectă a întreruperilor, pentru ca acestea să declanșeze trebuie
activate global folosind funcția sei(). Pentru a dezactiva global întreruperile se folosește funcția
cli().
Exemple
1. Unul dintre butoane incrementează valoarea lui PORTB cu o unitate la fiecare apasare iar
celalalt decrementează valoarea lui PORTB în același fel.
#include <avr/io.h> #include <avr/interrupt.h>
ISR(INT0_vect) {PORTB++;}
ISR(INT1_vect) {PORTB--;}
void main()
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
14
{DDRB=0xFF; //PortB ca iesire MCUCR|=(1<<ISC01)|(1<<ISC11); //declanseaza INT0 si INT1 pe tranzitie H-L
GIMSK|=(1<<INT0)|(1<<INT1); //activeaza intreruperile pe INT0 si INT1 sei(); while(1); }
2. Butonul conectat la INT0 intrementează valoarea lui PORTB atât timp cât e apăsat iar celălalt
împarte la doi valoarea la fiecare ridicare a butonului. #include <avr/io.h>
#include <avr/interrupt.h>
ISR(INT0_vect) {PORTB++;}
ISR(INT1_vect) {PORTB>>=1;}
void main() {DDRB=0xFF; //PortB ca iesire MCUCR|=(1<<ISC10)|(1<<ISC11); //declanseaza INT0 pe low level si INT1 pe
tranzitie //L-H GIMSK|=(1<<INT0)|(1<<INT1); //activeaza intreruperile pe INT0 si INT1
sei();
while(1);
}
Tema 2 – un exemplu de rezolvare se găsește în folderul Tema2
1. Folosind circuitul construit, implementează un numărător binar pe 8 biți cu incrementare
la apăsarea unuia dintre butoane și decrementare pe celalalt buton astfel încât valoarea
maxima să fie 255 chiar dacă se mai apasă butonul de incrementare după ce s-a ajuns la
această valoare. Analog pentru valoarea minimă 0.
2. Modifică codul problemei 5 de la Tema 1 în asa fel încât unul dintre butoane să schimbe
viteza animației iar celalalt să schimbe animația.
3. Scrie un program care la fiecare apăsare a butonului notat în schemă S1 să selecteze pe
rând unul din leduri. Apăsarea butonului S2 schimbă starea ledului selectat.
4. Scrie un program care să aprindă ledul 0 apoi la fiecare apăsare a butonului S1 va deplasa
ledul / ledurile aprinse cu o pozitie spre dreapta. Butonul S2 va decrementa valoarea
portului cu o unitate. INT0 va declanșa pe falling edge iar INT1 pe rising.
5. Scrie un program care sa implementeze un astabil, monostabil si bistabil folosind 2 leduri.
Butonul S1 va selecta modul de functionare iar S2 va actiona ca trigger când este cazul.
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
15
Partea 3 – The clock is ticking
Dacă până acum ai făcut programe în care pentru a întârzia execuția cu un anumit timp ai
folosit o metodă destul de ineficientă (funcția _delay_ms()) pentru delay-uri mari, acum vei
învăța cum poți folosi timerele de care dispune uC-ul pentru a executa la intervale de timp
regulate o anumită parte de cod, în același timp lăsând restul programului să funcționeze normal.
Pentru a înțelege cum funționează timerele și ce fac exemplele de mai jos, aruncă o
privire (atentă) în 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 în datasheet, timerele funționează independent de restul
programului, pentru a ști când timerul a ajuns la valoarea maximă, vei folosi o întrerupere într-un
mod asemănător cu cele pe care le-ai folosit pentru butoane. Întreruperea va fi
ISR(TIMER0_OVF_vect) care va declanșa de fiecare dată când timerul ajunge la valoarea
maximă. Acum probabil ai să te întrebi la ce te ajută toate chestiile astea? Hai să luăm un
exemplu concret în care uCf - frecventa la care lucrează uC-ul (în cazul de față 1MHz), Tf -
frecvența la care funcționează timerul.
HzfuC 1000000
Alegem un prescaler de 1/1024 =>
Hzff uCT 56.9761024/
Știind că timerul este pe 8 biți rezultă că ISR(TIMER0_OVF_vect) va declanșa de
976/256=3.81 ori pe secundă. Ce faci dacă vrei ca o anumită parte de cod să se execute mai rar de
3.81 ori într-o secundă? Răspunsul este destul de simplu: implementezi un timer software, care
asemenea celui hardware va avea un prescaler stabilit de tine.
Notă: În cele ce urmează voi folosi notații și expresii de genul „declanșează la 3.81Hz” =
declanșează de 3.81 ori pe sec.
În cazul de față dacă vrei sa obții execuția unui cod la aproximativ 1Hz poți face ca în
exemplul de mai jos:
ISR(TIMER0_OVF_vect) // declanseaza la 3.81Hz {if(++t==3) //la fiecare a treia întrerupere se va executa codul {t=0; //resetează timerul software ...
codul tau //do the monkey business ... }
}
Exemplul de cod de mai sus dă o frecvență de execuție a codului de aproximativ 1.27Hz.
ATENȚIE! Frecvența de 1.27Hz obținută mai sus este frecvența la care se va intra în if, respectiv
în branch. Frecvența de execuție a instrucțiunilor din branch este HzfuC 1000000 .
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
16
Exemple
Acum că ai văzut cam cum stă treaba cu calculele, hai să vedem și ceva exemple practice
de cod. Exemplele ce urmează se bazează pe exemplele anterioare pentru a înțelege mai bine cum
se pot combina noțiunile învățate până acum.
1. “Leduri clipitoare” – versiunea 2 #include <avr/io.h>
#include <avr/interrupt.h>
ISR(TIMER0_OVF_vect) {static uint8_t t=0; if(++t==3)
{t=0; if(PORTB) PORTB=0; else PORTB=0xFF; }
}
void main() {DDRB=0xFF;
TIMSK|=(1<<TOIE0); //Enable Timer0 overflow interrupt TCCR0|=(1<<CS02)|(1<<CS00); //Timer0 prescaler =f/1024
sei(); while(1);
}
2. “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 apăsării butoanelor care modifică
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) {if(++t==speed)
{t=0; if(dir) {if(!led) {if(i<=7) {i++; PORTB=(PORTB<<1)+1; } else {led=1; i=0;} }
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
17
else {if(i<=7) {i++; PORTB>>=1;} else {led=0; i=0;} } } else {if(!led) {if(i<=7) {i++; PORTB=(PORTB>>1)+0x80; } else {led=1; i=0;} } else {if(i<=7) {i++; PORTB<<=1;} else {led=0; i=0;} } } }
}
ISR(INT0_vect) {dir=!dir; PORTB=0; t=0; i=0; led=0; } ISR(INT1_vect) {if(++speed==4) speed=1; t=0; }
void main() {DDRB=0xFF;
MCUCR|=(1<<ISC01)|(1<<ISC11); //trigger on falling edge GIMSK|=(1<<INT0)|(1<<INT1); //enable INT0 and INT1 trigger interrupt TIMSK|=(1<<TOIE0); //Enable Timer0 overflow interrupt TCCR0|=(1<<CS02)|(1<<CS00); //Timer0 prescaler =f/1024 sei(); while(1);
}
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
18
Partea 4 – Fade to black
În această parte ne vom ocupa de PWM – Pulse Width Modulation. Spus mai „pe
românește” variezi timpul cât aplici 1 logic pe pinii care generează PWM. Astfel poti controla
intensitatea luminii ledurilor instalate pe placă, viteza motoarelor de curent continuu, etc, sau poți
încerca să îți creezi propria instalație pentru bradul de Crăciun.
Cum se folosește?
În datasheet este scris foarte detaliat (poate chiar prea detaliat) cum funcționează
generatoarele de PWM și care sunt modurile de lucru. La bază totul este legat de timere (Timer1
și Timer2) care au și alte funcții pe lângă cea de bază. Ca să înțelegi o idee mai bine îți recomand
să arunci o privire în datasheet peste următoarele:
Pag 59-60 – Alternate Port Functions – PortB: OC1A, OC1B,OC2
Pag 76-83 – Timer1
Pag 85-88 – Output Compare Units
Pag 88 – Modes of operation: Normal Mode, Fast PWM Mode
Pag 97-103 – Register Description (doar ce tine de PWM, fara Input
Capture)
Asemănător cu Timer1 se folosește și Timer2 cu câteva deosebiri: este pe 8 biți și are doar
un singur generator de PWM. Pentru a-ți fi mai ușor să folosești și Timer2 pentru PWM în
exemplele ce urmează vei folosi Timer1 configurat în așa fel încât să fie compatibil cu Timer2.
Ca și până acum configurarea corectă a hardware-ului este esențială în buna funcționare a
montajului. Câțiva pași care trebuiesc urmați în folosirea PWM-ului ar fi:
Setarea pinilor folosiți pentru PWM ca output.
Setarea modului de lucru al generatorului de PWM folosind tabelele din
datasheet
Inițializarea registrului OCR folosit cu o valoare dorită
Activarea Timerului corespunzător generatorului de PWM care se dorește
a fi folosit prin selectarea unei surse de ceas
Acești patru pași reprezintă de fapt patru linii de cod care pot fi scrise oriunde în cod atât
timp cât se respectă ordinea. De preferat este ca cele patru linii de cod sa fie scrise grupat pentru
a evita problemele.
Exemple
1. În acest exemplu un singur generator de PWM va fi folosit doar pentru a demonstra
implementarea în cod a celor patru pași descriși mai sus.
#include <avr/io.h> #include <util/delay.h> void main() {DDRB|=(1<<DDB1); //seteaza pinii de PWM ca output TCCR1A|=(1<<COM1A1)|(1<<WGM12)|(1<<WGM10);
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
19
// configureaza pwm: Fast PWM 8bit, Non-inverting
OCR1A=0; // initializeaza OCR1A cu 0 TCCR1B|=(1<<CS10); //seteaza sursa de ceas: clk/1 (no prescalling) while(1) //creeaza un effect de fade destul de “nice” {while(++OCR1A<255) _delay_ms(5); while(--OCR1A>0) _delay_ms(5); } }
2. Acest al doilea exemplu este puțin mai complex folosind atât Timer1 cât și Timer2 pentru
a genera trei semnale PWM. În acest fel înlocuind cele 3 leduri de pe PCB conectate la
generatoarele de PWM cu un singur led RGB s-ar putea obtine orice culoare existentă.
Scopul exemplului este însă altul: evidențierea modului de lucru asemanator al timerelor 1
și 2.
#include <avr/io.h> #include <util/delay.h> void main() { DDRB|=(1<<DDB1)|(1<<DDB2)|(1<<DDB3);//seteaza pinii de PWM ca output // configureaza Timer1: Fast PWM 8bit, Non-inverting TCCR1A|=(1<<COM1A1)|(1<<COM1B1)|(1<<WGM12)|(1<<WGM10); OCR1A=255; // initializeaza OCR1A cu 255,OCR1B cu 0 OCR1B=0; // configureaza Timer2: Fast PWM 8bit, Non-inverting TCCR2|=(1<<COM21)|(1<<WGM21)|(1<<WGM20); OCR2=0; // initializeaza OCR2 cu 0 //seteaza sursa de ceas Timer1: clk/1 (no prescalling) TCCR1B|=(1<<CS10); TCCR2|=(1<<CS20); //seteaza sursa de ceas Timer2: clk/1 (no prescalling) while(1) { while(--OCR1A>0) {OCR1B++; _delay_ms(5); } while(--OCR1B>0) {OCR2++; _delay_ms(5); } while(--OCR2>0) {OCR1A++; _delay_ms(5); } }}
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
20
Partea 5 – Convertorul Analogic – Digital (ADC)
Până acum am văzut cum se pot folosi diverse module ale uC-ului pentru a afișa anumite
valori sau evenimente pe cele 8 leduri. Dacă în partea 2 (Push the button) am văzut cum putem
detecta evenimente exterioare de scurtă durată (apăsarea unui buton) ce se prezintă sub forma
unor semnale digitale (1 – butonul nu este apăsat, 0 – buton apăsat), în această parte a tutorialului
vom vedea cum putem citi semnale continue în timp (tensiunea pe o rezistență în acest caz).
Pentru a putea realiza acest lucru trebuie modificat montajul realizat anterior prin conectarea lui
AGND la GND, AVCC la VCC și AREF printr-un condensator la GND ca în schema prezentată
mai jos.
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
21
Cum se folosește?
În datasheet funcționarea modulului de conversie analogic-digital este descrisă pe 13
pagini însă pentru a putea urma acest pas al tutorialului nu este necesară parcurgerea tuturor
paginilor. Totuși pentru o mai bună înțelegerea felului cum merg lucrurile îți recomand să citești
următoarele pagini:
Pag. 196-198 – Analog-to-Digital Converter - Features
Pag. 198 – Starting a Conversion, Prescaling and Conversion Timing
Pag. 200 – Changing Channel or Reference Selection
Pag. 201 – Voltage Reference
Pag. 205-208 - ADC Conversion Result, ADC Multiplexer Selection, ADC
Control and Status Register A, ADC Data Register
Acum voi explica pe rând ce înseamnă fiecare bloc despre care ai citit în paginile
enumerate mai sus:
Analog to Digital Converter (Convertor Analogic – Numeric): acest
dispozitiv are rolul de a converti valoarea unei tensiuni într-un număr.
Modul în care se face acest lucru diferă între tipurile de ADC. Pentru
aceasta are nevoie de o tensiune de referință, fixă, stabilă. În funcție de
tensiunea de referință și de rezoluția convertorului (numărul de biți pe care
lucrează), pentru aceeași valoare a tensiunii aplicate la intrare se pot obține
rezultate diferite. Așa cum ai citit la pag. 205 – ADC Conversion Result,
rezultatul (numărul) se obține după formula:
în cazul în
care se folosesc toți cei 10 biți ai convertorului.
Prescaler (pag. 198 – Prescalling and Conversion Timing) – ca orice
circuit digital, și ADC-ul are nevoie de un semnal de tact pentru a
funcționa. De frecvența acestui semnal depin timpul de conversie și
rezoluția maximă. Pentru a folosi ADC-ul la rezoluția maximă, Atmel
recomandă frecvențe între 50KHz și 200KHz. Timpul de conversie pentru
prima conversie este de 25 de perioade de tact (clock cycles) iar pentru
următoarele conversii este de 13 perioade.
ADC Multiplexer (pag. 200 – Changing Channel or Reference Selection):
Este posibil ca unui singur convertor să i se atribuie mai multe canale de
intrare, ca și în cazul uC-ului ATMega8 care are un singur ADC cu 5
canale. Acestea se conectează la convertor câte unul la un moment dat prin
intermediul unui multiplexor. Multiplexorul se comporta ca un comutator
cu mai multe pozitii. Poziția este dată de biții MUX3:0 din registrul
ADMUX. Astfel pentru canalul 0 (poziția 0) biții vor avea valorile 0000 (0
în zecimal) iar pentru canalul 5 (poziția 5) vor avea valorile 0101 (5 în
zecimal). Pentru celelalte combinații existente vezi Tabelul 75, pag. 206.
ADC Voltage Reference (Tensiunea de referință) – Așa cum am explicat
mai sus, de tensiunea de referință depinde în mod direct rezultatul
conversiei pentru aceeași tensiune aplicată la intrare. Convertorul existent
în ATMega8 oferă posibilitatea de a alege între 3 referințe: AREF
Tutorial – Introducere în programarea și utilizarea microcontrollerelor Atmel AVR
22
(tensiunea aplicată pe pinul AREF), AVCC (tensiunea de alimentare a
convertorului, 5V, aplicată pe pinul AVCC) sau referința internă de 2.56V.
Este recomandat ca tensiunea de referință să se aleagă cât mai apropiată de
valoarea maximă pe care o poate atinge semnalul de intrare însă sa nu fie
mai mică.
Exemple
1. Acest exemplu demonstrează cum se setează ADC-ul pentru a citi în mod continuu valoarea
tensiunii aplicată pe canalul 0 cu afișare pe cele 8 leduri.
#include <avr/io.h> void adc_init(void); uint8_t adc_read(uint8_t channel); int main(void) { DDRB=0xFF; //Portb devinde iesire adc_init(); //Initializeaza ADC while(1) //Bucla infinita { PORTB = adc_read(0); //Transmite pe PortB valoarea citita pe canalul 0 } } void adc_init() { ADMUX|=(1<<REFS0) //Referinta AVCC, | (1<<ADLAR);//Deplaseaza rezultatul la stanga (cei mai semnificativi 8 biti in ADHC) ADCSRA|=(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0) //Prescaler: F_CPU/128 | (1<<ADEN); //Activeaza ADCul } uint8_t adc_read(uint8_t channel) { ADMUX &= 0xE0; //Sterge vechiul canal ADMUX |= channel; //Seteaza noul canal ADCSRA|=(1<<ADSC); //Porneste conversia while(!(ADCSRA&(1<<ADIF))); //Asteapta sfarsitul conversiei return ADCH; //Returneaza rezultatul (pe 8 biti) }