sâmbătă, 17 martie 2012

Microcontrolerul PIC18F4550 Microchip - lucrul cu timere si intreruperi

Un program pentru microcontrolerul PIC18F4550 poate executa diverse sarcini. Fiecare sarcina consuma din resursele microcontrolerului PIC18F4550. Astfel ca la un moment dat microcontrolerul este ocupat sa indeplineasca o anumita sarcina si nu poate face altceva. Sa ne imaginam ce se petrece in aplicatia cu LEDul care clipeste la fiecare secunda (vezi aici): schimb starea pinului 19 (RD0) in 1 logic, astept 1 secunda, apoi schimb starea pinului 19 in 0 logic, astept din nou 1 secunda samd. Deci cu alte cuvinte asteptarea de 1 secunda face ca microcontrolerul sa fie inutilizabil pentru alte sarcini, asteptarea il face inutilizabil.



Cateva cuvinte despre timpul necesar ca microcontrolerul PIC18F4550 sa execute instructiunile:

Timpul de executie este acelasi pentru majoritatea instructiunilor si dureaza 4 cicluri de ceas. Ceasul unui microcontroler Microchip in cazul meu este dat de un cristal de cuart de 20 MHz (mega hertz, da, ca cei de la frecventa radio). Asta inseamna ca folosind un cuart de 20MHz, fiecare instructiune dureaza 200nS, adica acest microcontroler executa 5 milioane de instructiuni pe secunda.

Deci schimbarea starii pinului din 1 in 0 si invers dureaza un timp foarte scurt, o instructiune - doar ca ceea ce se petrece cu pauza de 1 secunda este faptul care face ca microcontrolerul sa nu poata exexcuta nimic altceva pe langa. Practic microcontrolerul nu executa nici o operatie - instructiunea "nop"- este oarecum blocat, asteapta aiurea si nu mai putem face nimic cu el timp de 1 secunda. Executa 5 milionae de "nop".

Acest mod de programare face ca aprinderea si stingerea unui LED cu un microcontroler PIC18F4550 sa fie o aplicatie scumpa si inutila. Si totusi cum se face? Documentatia este de baza, deci sa citim.

In documentatia pentru PIC18F4550 sunt prezentate toate dispozitivele interne ale acestui microcontroler Microchip. Printre ele, sunt si cateva dispozitive care se numesc timere (TMR0, TMR1 etc), ele sunt de fapt niste numaratoare care numara la fiecare ciclu de ceas. Ele au si prescaler, adica in cazul in care dorim ca la 4 cicluri de ceas, timerul sa numere o singura data, punem presacalerul 1:4. Aceste timere mai pot fi pre-incarcate cu o valoare astfel incat sa numere de la valoarea respectiva in sus - este util pentru a obtine durate de timp bine definite. Cand timerul a numarat pana la valoarea maxima, apare un semnal care indica depasirea valorii maxime. In acel moment, punem timerul sa numere de la inceput.

Exemplu cu TMR1 in configuratia de timer - numara perioade de ceas - OSC / 4, cand apare depasirea, apare intrerupere si bitul TMR1IF este setat (adica devine 1 logic)


Astfel ca din calcule, pe baza valorii frecventei de ceas si a pre-incarcarii timerului, obtinem valori exacte de timp. De exemplu TMR1 este un timer pe 16 biti, adica poate numara de la 0x0000 pana la 0xFFFF (de la 0000 0000 0000 0000 la 1111 1111 1111 1111), in zecimal de la 0 pana la 65535. Deci daca avem un cuart de 20MHz (ceasul), atunci TMR1 numara o data la fiecare 200 nano secunde, deci vom folosi aceasta valoare ca referinta cand dorim sa calculam perioade exacte de timp. Cum spuneam in alte dati, accesul la Internet ne ofera detalii despre cum merge treaba cu timerele, deci cautati, cautati, cautati :), unele au si imagini sugestive; din nou cei de la Mikroelektronika exceleaza la acest capitol si ofera o prezentare grozava - vezi aici partea 3.5 TIMER TMR1.

Acum vine intrebarea: cum stim cand a aparut depasirea? Verificam din timp in timp (adica facem polling)? Dar daca ratam momentul? Samd. Raspunsul e simplu: PICul are intreruperi: cand apare un eveniment, apare o intrerupere care opreste microncontrolerul din activitatea lui curenta. Totul se realizeaza automat, se salveaza contextul de lucru si codul utilizatorului trateaza acel eveniment. Asa si aici, cand TMR1 ajunge de la 65535 la 0 (adica ajunge la 65536), apare depasirea care genereaza o intrerupere si astfel stim precis ca a trecut o perioada de timp definita de noi exact.

Din nou facem apel la documentatia microcontrolerul PIC18F4550 de la Microchip: pagina 131 - "12.0 TIMER1 MODULE" - aflam ca pentru lucrul cu TMR1 trebuie utilizat registrul T1CON.

Bitul 0 (TMR1ON) spune microcontrolerului sa foloseasca acest timer, deci il punem pe 1
Bitul 1 (TMR1CS) spune microcontrolerului sa foloseasca semnalul de ceas (clock-ul) de la cuartul de 20MHz, deci il lasam pe 0
Bitul 2 (T1SYNC) in combinatie cu TMR1CS = 0, este ignorat
Bitul 3 (T1OSCEN) il lasam pe 0
Bitul 4 si 5 seteaza prescalerul
Bitul 6 (T1RUN) ramane pe 0
Bitul 7 nu ne intereseaza, il lasam pe 0

Pe langa acesti biti, mai trebuie sa lucram cu partea de intreruperi: sa activam sistemul de intreruperi al microcontrolerului (bitul GIE (bitul 7) din regisrul INTCON), sa incarcam TMR1 cu o eventuala valoare presetata (TMR1L si TMR1H) daca e nevoie, sa activam intreruperea lui TMR1 (bitul TMR1IE - din registrul PIE1), sa punem flag-ul de intrerupere TMR1IF pe 0 (este bitul 0 din registrul PIR1).

Pentru a face mai interesanta programarea, sa calculam pre-incarcarea lui TMR1 astfel incat sa apara depasirea la fiecare 100 mili secunde, apoi sa numaram 10 depasiri si sa apindem sau sa stingem LEDul legat la RD0 la fiecare 100 depasiri, adica 100 * 10 mili secunde = 1 secunda.

Asadar avem un ceas de 20MHz (OSC), TMR1 numara la OSC/4, adica 20MHz / 4 = 5MHz, adica in timp t = 1/5MHz = 0,0000002 secunde = 200 nano secunde, adica 5 cicluri fac 1 micro secunda (5 * 200ns = 1000ns = 1us), iar 1 ms = 1000us, pt 10 milisecunde avem nevoie de 10000 de micro secunde, deci TMR1 trebuie sa numere 5 * 10000 = 50000 cicluri.

Daca TMR1 numara de la 0 la 65535, atunci TMR1 trebuie pre-incarcat astfel incat sa numere doar 50000 de cicluri. Deci 65535 - 50000 = 15535. Asadar daca TMR1 este deja pre-incarcat cu aceasta valoare, el va numara incepand de la 15535 si dupa 50000 de cicluri va ajunge la depasire. In acest fel am obtinut o durata de timp de 10 mili secunde.

Incarcarea TMR1 se face in 2 registre separate TMR1L (low) si TMR1H (high): 15535 zecimal = 0x3CAF hexazecimal, deci TMR1L = 0xAF si TMR1H = 0x3C. (pentru acest calcul si conversie am folosit calculatorul din mikroC).

Acum pentru partea cu intreruperea: in mikroC avem o functie speciala care se ocupa doar de intreruperi: cand in microcontroler apare o intrerupere, executia ajunge la aceasta functie si se executa secventa respectiva - verificam ce intrerupere ne-a adus acolo - pot fi mai multe intreruperi:

void interrupt() {

if (PIR1.TMR1IF == 1) { //did TMR1 overflow?

//task to do
cnt++; //increment cnt variable every 10 milli seconds

PIR1.TMR1IF = 0; // Reset bit TMR1IF
TMR1H = 0x3C; // preset for timer1 MSB register
TMR1L = 0xB0; // preset for timer1 LSB register
}
}


Variabila cnt va fi incrementata la fiecare 10 mili secunde, apoi in programul propriu zis verifcam cand variabila a ajuns la valoarea 100, cand a ajuns la 100, o resetam la 0 si schimbam starea LED-ului RD0. In acest fel putem sa facem 5 milioane de alte lucruri in timp ce TMR1 are grija sa numere cat timp a trecut :)

unsigned short cnt; // Define variable cnt

void interrupt() {

if(PIR1.TMR1IF){ //is the interrupt caused by TMR1 overflow?
cnt++ ; // Interrupt causes cnt to be incremented by 1
PIR1.TMR1IF = 0; // Reset bit TMR1IF
TMR1H = 0x3C; // preset for timer1 MSB register
TMR1L = 0xB0; // preset for timer1 LSB register
}
}

void main() {
ADCON1 |= 0x0F; // Configure AN pins as digital
CMCON |= 7; // Disable comparators

TRISD0_bit = 0; // set direction to be output
RD0_bit = 1; // Turn OFF LED on PORTD0


// Timer1 Registers:
// Prescaler=1:1; TMR1 Preset=60536; Freq=1.000,00Hz; Period=1,00 ms
T1CON.T1CKPS1 = 0;// bits 5-4 Prescaler Rate Select bits
T1CON.T1CKPS0 = 0;//
T1CON.T1OSCEN = 0;// bit 3 Timer1 Oscillator Enable Control: bit 1=on
T1CON.T1SYNC = 0;// bit 2 Timer1 External Clock Input Synchronization Control bit: 1=Do not synchronize external clock input
T1CON.TMR1CS = 0;// bit 1 Timer1 Clock Source Select bit: 0=Internal clock (FOSC/4) / 1 = External clock from pin T13CKI (on the rising edge)
T1CON.TMR1ON = 1;// bit 0 enables timer

// T1CON = 1; // Set timer TMR1
PIR1.TMR1IF = 0; // Reset bit TMR1IF
PIE1.TMR1IE = 1; // Enable interrupt on overflow

TMR1H = 0x3C; // preset for timer1 MSB register
TMR1L = 0xAF; // preset for timer1 LSB register

cnt = 0; // Reset variable cnt
INTCON = 0xC0; // Enable interrupt (bits GIE and PEIE)


do { // Endless loop
if (cnt == 100) { // Change port D0 state after 100 interrupts
RD0_bit = ~RD0_bit; // port D0 is inverted
cnt = 0; // Reset variable cnt
}
} while (1);
}


Pentru mai multe detalii despre intreruperi, consultati documentatia microcontrolerului PIC18F4550 de la Microchip. De asemenea se gasesc programe de calculator care ajuta la calcularea valorii cu care se pre-incarca TMR1L si TMR1H astfel incat sa fie obtinuta o anumita durata de timp in functie de oscilator.

Niciun comentariu:

Trimiteți un comentariu