În acest mic articol vreau să povestesc cîte ceva despre întreruperi, ce sînt ele, la ce folosesc, şi cum pot ajuta la scrierea programelor pe PIC.
Eşti cu maşina pe un DN. Cer însorit, asfalt uscat, vreme frumoasă de primăvară. Dintr-o dată îţi sare în faţă un dulău la vro 30′ de metri. Scurt, te uiţi în oglindă dacă poţi frîna brusc, vezi că nu-i nimeni lipit de tine. Din contrasens vin maşini, deci nu -l poţi ocoli. Ambreiaj, frînă, claxon, fandare uşoară stînga. Cîinele se uită hipnotizat la maşină, parcă dîndu-şi seama de greşeală, şi sare înapoi pe acostament, salvîndu-se pe sine, şi salvîndu-ţi bara, masca, radiatorul, proiectoarele. Totul durează două secunde. Schimbi a patra pentru a accelera la viteza de croazieră la care erai, şi încercînd să prinzi din urmă gîndul pe care tocmai l-ai abandonat.
Ok, acum, ce treabă cu întreruperile. Să zicem că apariţia cîinelui este un eveniment. Ochiul este perifericul care detectează evenimentul, şi comandă o tratare corespunzătoare a acestuia. Putem spune că ochiul declanşează o întrerupere, astfel încît creierul se abate de la execuţia programului
Succesiunea de acţiuni pe care şoferul le execută aproape instictiv putem să o numim rutina de deservire a întreruperii. Prescurtat se mai numeşte ISR – Interrupt Service Routine.
Astfel, o întrerupere apare ori de cîte ori procesorul abandonează şirul firesc al instrucţiunilor, pentru a interpreta date provenite de la un dispozitiv periferic. Rutina de deservire a întreruperilor are rolul de a determina care dispozitiv a declanşat întreruperea şi a acţiona în consecinţă.
Un alt exemplu, mult mai complex, este PC-ul. Aici cîteva zeci de mii de întreruperi pe secundă. Majoritatea sînt întreruperi software, determinate de sistemul de operare şi de task-uri, dar mai apar şi întreruperi hardware, determinate de evenimente precum golirea bufferului plăcii audio, primirea unui pachet pe reţea, mişcarea mouse-ului, terminarea scrierii unui sector pe HDD, etc, etc. Oricare dintre acestea implică abandonarea task-ului executat la momentul respectiv şi intrarea în rutina de deservire. Într-un mod foarte simplificat, golirea bufferului plăcii audio determină o întrerupere care cere procesorului transferul unei zone de date din memoria RAM în bufferul audio, după care procesorul revine la execuţia task-ului care era rulat înainte de declanşarea întreruperii.
Să pornim de la bătrînul PIC16F84, şi să vedem cum putem utiliza întreruperile. O întrerupere poate fi declanşată de următoarele evenimente:
-
Întrerupere externă, fie un front pozitiv pe pinul RB0, fie Schimbarea stării oricărui pin de pe portul B. Poate veni de la un timer extern spre exemplu, sau orice altă sursă care poate să dea un front sănătos, fără oscilaţii.
-
Întrerupere dată de timerul intern TMR0. Acesta este pe 8 biţi, şi numără crescător. Cînd a ajuns la 255, următoarea incrementare îl resetează la 0, şi se declanşează o întrerupere. Se poate folosi pentru execuţia unor rutine la intervale bine determinate de timp.
-
Scrierea unei adrese de EEPROM, la sfîrşitul căreia se declanşează o întrerupere.
Pentru PIC16F84, un registru important pentru lucrul cu întreruperile este INTCON. Acesta controlează ce surse de întreruperi să se foloească, iar interogarea lui ne spune ce întrerupere s-a declanşat. Biţii registrului INTCON sînt:
bit 7: Dacă este 0, toate întreruperile sînt ignorate. Altfel, se iau în considerare întreruperile generate de sursele specificate în biţii următori
bit 6: Dacă este 1, declanşează întreruperea cînd s-a terminat de scris un byte în memoria EEPROM
bit 5: Dacă este 1, declanşează întreruperea cînd TMR0 a făcut o depăşire (Overflow)
bit 4: Dacă este 1, declanşează întreruperea cînd a primit un front pe pinul RB0.
Bit 3: Dacă este 1, declanşează întreruperea cînd s-a schimbat un bit pe portul B
Biţii 2,1 şi 0 se interoghează pentru a afla sursa care a declanşat întreruperea. Bitul corespunzător va fi setat la 1, şi va trebui resetat manual la 0. Aceştia se mai numesc şi flag-uri, sau indicatori.
bit 2: TMR0 a efectuat o depăşire
bit 1: S-a primit un front pe pinul RB0
bit 0: S-a schimbat starea portului B.
Deci, în program, în void main(void) trebuie setate sursele de la care să accepte întreruperi, prin biţii 7..3 ai registrului INTCON. În void interrupt (void) se analizează care flag şi-a schimbat starea din 2,1,0. Se va executa bucata de cod de deservire a întreruperii, în funcţie de bitul găsit setat. Apoi bitul respectiv se va reseta.
Astfel, la declanşarea unei întreruperi, procesorul va abandona programul curent şi va face un salt la ISR. În stiva de sistem se va stoca adresa la care rămăsese cu execuţia programului curent. Fizic, ISR-ul începe la adresa 0004, aşa că declanşarea unei întreruperi determină un salt necondiţionat la adresa 0004h. Dar acest lucru nu ne ineresează dacă programăm într-un limbaj de nivel înalt. După execuţia ISR-ului, procesorul extrage din stiva de sistem adresa unde rămăsese cu execuţia, şi va continua de unde a rămas. Dacă programăm în asm, trebuie să avem grijă că regiştrii nu se păstrează la revenirea în programul principal. În ISR, spre exemplu acumulatorul W poate fi schimbat, şi poate provoca erori la întoarcere. De aceea, acumulatorul ar trebui salvat la începutul ISR-ului într-o locaţie de RAM, şi preluat înapoi după execuţia acesteia.
Stiva de sistem la PIC16F84 nu poate fi folosită pentru stocarea regiştrilor, ci exclusiv pentru locaţiile de salt. Are o adîncime de 8 nivele. Probleme pot să apară cînd apare o întrerupere cînd deja procesorul execută ISR-ul. Astfel se va ocupa un nivel suplimentar în stivă. Dacă au loc succesiv 8 salturi fără nici o întoarcere, apare fenomenul de stack overflow. Procesorul nu va mai şti la ce locaţie să se întoarcă. Acest fenomen poate fi prevenit dacă se scriu ISR-uri scurte, care se execută mai repede decît frecvenţa de apariţie a întreruperilor. De asemenea nu recomand să se conecteze direct butoane sau dispozitive mecanice la pinii de întreruperi externe, deoarece acestea pot da un tren de impulsuri la comutare, nu unul singur. Astfel se apelează de un număr incontrolabil de ori rutina de ISR şi poate să apară stack overflow.
Să facem un exerciţiu de folosire a întreruperilor pe batrînul PIC16F84. Să facem un semnal dreptunghiular cu frecvenţa de 1KHz, fără a folosi delay-uri software, şi folosind doar întreruperi de la timerul 0. Avînd un cristal de 4MHz, calculăm ce divizor ar trebui folosit:
4.000.000 : 4 : 1000 = 1.000
Deci prescalerul înmulţit cu FFh-(valoarea din timer0) trebui să dea 1000. Vom folosi prescalerul setat pe :2, si asignat lui TMR0. Această setare se face din OPTION_REG.
Deci ultimii 4 biţi vor fi 0000, iar bitul 5 va fi pus 0 pentru a folosi ceasul intern ca sursă pentru TMR0. Restul biţilor nu contează.
INTCON se setează la valoarea binară 0b10100000, astfel că se activează doar întreruperile date de TMR0.
TMR0 numără crescător pînă la overflow. Overflow se produce la trecerea de la valoarea zecimală 255 la 0, deci pentru a număra pînă la 250, trebuie iniţializat la 6. (6+250=256=0).
Un program minimal, dar funcţional, care pe baza întreruperilor generază un semnal dreptunghiular de 1KHz, este descris mai jos:
#include <PIC16F84.h> void interrupt( void ) //Rutina ISR { if( intcon & (1<<T0IF) ) //Verifica daca intreruperea provine de la timer { tmr0=6;//Reinitializeaza registrul TMR0 intcon.T0IF=0; //Sterge flagul de intrerupere if (porta.0) porta.0=0; else porta.0=1; } } void main( void ) { trisa=0b11111110; //A0 ca ieşire, celelalte ca intrare option_reg=0b00000000; tmr0=81; intcon=0b10100000; while( 1 ) { } }
Programul se compilează cu Boost C++. În locul secvenţei while (1) {} se poate scrie orice secvenţă de instrucţiuni. De fapt, proecsorul este ocupat cu execuţia propriu-zisă un timp foarte scurt, cam 1%. În cazul de faţă el nu face nimic, în restul timpului, rulînd la nesfîrşit bucla. În locul acestei bucle se poate scrie orice cod. Orice ar fi, rutina ISR se rulează nestingherit de 2000 de ori pe secundă. Prin contrast, altă metodă de a obţine 1KHz ar fi fără întreruperi cu delay-uri software. Asta ar fi ţinut procesorul ocupat cu execuţia delayurilor, şi nu ar mai fi putut să facă altceva. Sau, adăugarea oricărei instrucţiuni în plus ar fi mărit delay-ul într-un mod greu controlabil.
Sper ca acest articol să fie util celor aflaţi la început de drum în microcontrollere. Aştept comentariile voastre.