Sunteți pe pagina 1din 13

Microcontrollerul PIC24FJ128GA010

Timere și întreruperi

Pentru ca un sistem să prezinte un comportament stabil și riguros definit, este necesar ca


unele operații să fie executate cu cadență fixă. De asemenea, frecvența unor operații trebuie să
fie mai mare decât a altora. Un exemplu ar fi cazul în care un sistem achiziționează prin
intermediul unor senzori parametri din mediu. Parametrii vor fi afișați pe un ecran, iar, pe baza
lor, sistemul ia decizii legate de controlul procesului monitorizat. Este evident că afișarea se
poate face cu o viteză mică, deoarece nu putem sesiza modificările dacă acestea apar cu o
frecvență mai mare de 50Hz. În același timp, achiziția parametrilor și operațiile de control
trebuie realizate cu o viteză cât mai ridicată. Sunt necesare filtrări software ale semnalelor și
reacții cât mai rapide la modificări.
Aceste motive stau la baza integrării de module timer în microcontrolere și a folosirii
întreruperilor. Microcontrolerul PIC24FJ128GA010 pune la dispoziția utilizatorilor 7 nivele de
prioritate pentru întreruperi, 5 pini care pot fi utilizați ca surse de întreruperi externe și 5 timere
care pot genera întreruperi. De asemenea, oferă posibilitatea generării de întreruperi software.
Totodată, diferitele module integrate pot genera întreruperi, dacă sunt configurate în acest sens.
În cele ce urmează, ne propunem realizarea unei aplicații care să regleze luminozitatea
și frecvența de clipire a LED-urilor. Pentru aceasta este necesar să cunoaștem modul de
funcționare a timerelor integrate și modul de configurare și tratare a întreruperilor.

1. Timerul Timer1
Structura timerului Timer1 este dată în Figura 1:

Figura 1. Structura timerului Timer1

Observăm în primul rând posibilitatea selectării sursei de ceas pentru funcționarea


numărătorului pe 16 biți. Astfel, se poate conecta un oscilator extern pe pinii S0SCO și S0SCI
sau se poate folosi ceasul sistemului prin selectarea biților TGATE și TCS din registru T1CON.
De asemenea, timerul poate fi dezactivat prin setarea în ”0” a bitului TON din același registru.
Este prezentă de asemenea opțiunea de sincronizare prin setarea TSYNC. Prescalerul poate fi
1
setat prin biții de configurare TCKPS1:TCKPS0, și are rolul de a diviza și mai mult frecvența,
dacă divizarea din timer nu este suficientă. Valorile factorului de divizare sunt date în datasheet
în funcție de setarile celor doi pini. [2]. Placa de dezvoltare are conectat un oscilator cu quartz
extern între pinii SOSCO, respectiv SOSCI. Oscilatorul are o frecvență de 32768Hz și poate fi
utilizat pentru măsurarea precisă a timpului.
O caracteristică mai deosebită, care aduce un plus de flexibilitate este dată de modul de
reset a numărătorului pe 16 biți. Astfel, valoarea din registrul TMR1 este comparată în mod
continuu cu valoarea registrului PR1, iar dacă s-a depășit această a doua valoare, timerul este
resetat și se generează o întrerupere. Acest principiu de funcționare este diferit de cel al altor
microcontrolere (ex: seria PIC18 de la Microchip [6]). Structura unui timer prezent pe
microcontrolerele PIC18 este dată în Figura 2:

Figura 2. Blocul TIMER0 pentru microcontrolerele PIC18

În acest caz, timerul generează o întreurpere la overflow. Pentru obținerea unui anumit
factor de divizare sunt necesare mai multe calcule în proiectare, decât în cazul în care valoarea
factorului de divizare ar fi încărcat direct într-un registru și comparat cu valoarea curentă a
timerului (caz posibil pe un microcontroller PIC24).
Factorul de divizare menționat mai sus stă la baza obținerii unor eșantionări sau
execuții de rutine cu o frecvență specifică. Practic, prin divizarea cu un anumit factor a
frecvenței de lucru a sistemului sau a unei frecvențe de referință externe, se poate obține o plajă
largă de frecvențe.
Valoarea factorului de divizare se poate calcula cu formula:

Unde:
 este valoarea frecvenței de referință (32Mhz dacă divizăm frecvența de
lucru a sistemului)
 reprezintă frecvența de execuție proiectată a unui anumit task.

După cum s-a amintit, divizarea se face în cazul de față foarte simplu, încărcând
valoarea numărului natural în registrul PR1. Când valoarea acestui prag este atinsă,
numărătorul se resetează și este generată o întrerupere. Este prezentă și varianta mai puțin
comodă din PIC18 care constă în încărcarea, la reset, în registrul TMR1 a unei valori:

N’=Nmax-
Unde:
 este valoarea calculată anterior
 Nmax este numărul maxim de valori pe care le poate lua TMR1, în cazul de față 216
Prezența celor două abordări aduce, după cum am spus, un plus de flexibilitate
comparativ cu alte familii de microcontrolere.

2
Factorul de divizare global se obține prin înmulțirea factorului de divizare realizat de
timer cu factorul dat de prescaler. În proiectare trebuie luată în considerare o variantă combinată
a lor, mai ales când este necesar un factor de divizare foarte mare. Formula finală a factorului de
divizare, dacă se utilizează prescalerul cu factorul de divizare :

Frecvența de ieșire rezultată va fi, în acest caz:

Configurarea timerului se realizează prin setarea biților din registrul T1CON. Pentru
alte timere, numele regiștrilor sunt similare și se găsesc în datasheet. Un exemplu de configurare
al acestui registru este:

T1CON = 0b1000000000010000;

Cel mai semnificativ bit, setat în ”1” semnifică activarea timerului. Biții 5 și 6 sunt
TCKPS1:TCKPS0, iar setarea lor în ”01” este echivalentă cu selectarea unui prescaler de 1:8.
Toate celelalte setări posibile ale acestora, precum și semnificația tuturor biților din registrul de
configurare T1CON sunt date în datasheet. [2]

2. Rutine de tratare a întreruperilor


Întreruperile sunt semnale care opresc executia codului principal, pentru a executa
secvențe de cod prioritare. Secvențele de cod executate sunt așa-numitele “rutine de tratare a
întreruperilor”. Sursele de întreruperi pot fi interne sau externe. Majoritatea perifericelor
integrate în microcontroller pot genera întreruperi dacă sunt configurate în acest sens. Exemple
importante de periferice care pot genera întreruperi sunt timerele si convertoarele analog-
digitale. Întreruperile externe sunt generate de alte dispozitive conectate la microcontroller.
Conectarea la microcontroller se face prin intermediul unor pini care au această destinație. În
cazul PIC24FJ128GA010, există patru pini de întrerupere: INT0-INT3 (vezi [2]). Un exemplu de
utilizare a întreruperilor externe ar fi detecția unui semnal, în urma căruia microcontrollerul intră
în sleep. Un alt exemplu este detecția trecerilor prin 0 a unei tensiuni alternative.
Structura generală a unei rutine de tratare a întreruperilor este:

void __attribute__ (( interrupt)) _Nume_Functie ( void)


{
// cod de tratare a întreruperii...
} // end _Nume_Functie

Numele funcției nu se alege arbitrar, ci se va folosi un nume predefinit, legat de modulul


hard care generează întreruperile tratate. Astfel, în cazul întreruperilor generate de timerul
Timer1, acest nume predefinit este _T1Interrupt. Secvența
__attribute__ (( interrupt)) este utilizată de compilator pentru a specifica unele
atribute speciale, cum ar fi extensia limbajului C. Pentru a evita această modalitate destul de
complicate de a defini o rutină de tratare a întreruperilor, compilatorul C30 are predefinite o serie
de macro-uri (vezi [7], pg. 88). Acestea sunt definite în fișierul header asociat tipului de
microcontroller utilizat. Macroul de interes pentru noi este:

#define _ISR __attribute__((interrupt))

3
Utilizând acest macro, forma generală a unei funcții de tratare a întreruperilor, este:

void _ISR _Nume_Functie (void)


{
// cod de tratare a întreruperii...
} // end _Nume_Functie

Trebuie remarcat faptul că în interiorul unei astfel de funcții, codul trebuie să fie cât mai
simplu și mai utilizat cu putință. Se impune acest lucru deoarece scopul este ca rutina de
întrerupere să fie executată într-un timp cât mai scurt. Un timp scurt de execuție permite un
număr mare de execuții, adică o frecvență ridicată a întreruperilor. Uneori, se preferă chiar
utilizarea unor porțiuni de cod scrise în limbaj de asamblare, pentru atingerea acestui deziderat.

3. Modulația impulsurilor în durată (PWM)


Modularea impulsurilor în durată, Pulse Width Modulation (PWM) în engleză, constă în
generarea unui semnal dreptunghiular de frecvență fixă. Pe o perioadă a acestui semnal, raportul
dintre intervalul de timp în care semnalul are valoarea maxima și perioada semnalului variază.
Acest raport se numește factor de umplere (en: duty cycle). În figurile 3, 4 și 5 sunt prezentate
trei semnale PWM cu factor de umplere de 25, 50 respectiv 75%.

Figura 3. Semnal PWM cu factor de umplere de 25%

4
Figura 4. Semnal PWM cu factor de umplere de 50%

Figura 5. Semnal PWM cu factor de umplere de 75%

Utilizând notațiile din figurile amintite, putem scrie formula factorului de umplere D, ca
fiind raportul dintre timpii și :

În cazul în care se lucrează cu un sistem digital, putem considera cei doi timpi din
formulă ca fiind multiplii întregi ai unei cuante de timp :

Unde și sunt factori de multiplicare ai perioadei minime de timp, . Perioada


poate fi dată, spre exemplu ca inversul frecvenței întreruperilor generate de un timer:
5
Astfel, se poate observa legătura cu paragrafele anterioare. Având în vedere relațiile de
mai sus, putem rescrie formula factorului de divizare astfel:

Semnalul generat în acest fel poate fi utilizat pentru a transporta informația (există
senzori cu ieșire PWM) sau pentru a controla puterea. Controlul puterii cu PWM se bazează pe
faptul că prin varierea factorului de umplere, modificăm și puterea efectivă a semnalului. Puterea
efectivă a unui semnal PWM pe o perioadă poate fi calculată prin:

Putem observa că o variație liniară a factorului de umplere va duce la o variație liniară a


puterii disipate. Formulele de mai sus sunt valabile pentru un control al puterii în curent
continuu. De asemenea, tensiunea inferioară, se consideră 0V. Puterea maximă se
obține pentru un factor de umplere unitar.
Utilizând formulele din secțiunea 1 și 2, frecvența semnalului PWM rezultat este:

Această frecvență se alege în funcție de cerințele aplicației. În cazul controlului


luminozității cu LED-uri, este necesar, spre exemplu, să avem o frecvență suficient de mare (mai
mare de 50Hz) astfel încât să nu observăm „clipirea” acestora. Numărul de valori disponibile
pentru factorul de umplere se alege pentru a obține precizia necesară aplicației. În același timp,
trebuie făcut un compromis, deoarece un număr mare de astfel de nivele este echivalent cu un

număr mare, iar cum și este dictată de aplicație, vom obține o perioadă
mică. O perioadă mică este echivalentă cu o frecvență mare a întreruperilor generate de timer.
O frecvență mare de execuție a întreruperilor va duce la o execuție inacceptabil de lentă a
porțiunilor de cod mai puțin prioritare. Dacă frecvența setată este mult prea mare, putem ajunge
în cazul în care sistemul se blochează din lipsă de resurse de procesor.

4. Programul demonstrativ

În cele ce urmează va fi descris un program care pune în evidență modul de utilizare al


întreruperilor. Programul este construit ca o dezvoltare a aplicațiilor cu LED-uri descrise în
capitolele anterioare. Astfel, ne propunem să putem modifica frecvența de clipire și factorul de
umplere al semnalelor care controlează LED-urile. Pentru modificarea acestor doi parametri vom
utiliza patru butoane. Verificarea acestor butoane se va face cu o frecvența fixă, utilizând
întreruperi generate de un timer. Diagrama de stare a programului este dată în Figura6:

6
Start

Configurare Întrerupere
TIMER 1 TIMER 1

Configurare DA
TIMER 2 Resetare flag
TIMER1
Iniţializare
butoane
NU Factor de DA
DA umplere atins
Buton 1
apăsat
Crește factorul de
NU umplere
LED aprins LED stins

Buton 2 DA
apăsat
Scade factorul de
NU umplere

DA Întrerupere
Buton 3
apăsat TIMER2
Crește frecvenţa
NU de clipire DA
Resetare flag
DA TIMER2
Buton 4
apăsat
Scade frecvenţa Verificare
NU de clipire butoane

Figura 6. Diagrama de stare a aplicației


Partea stângă a diagramei se referă la corpul principal al programului, în timp ce
partea dreaptă este rezervată întreruperilor. Primii pași ai programului principal (funcția
main) sunt configurarea sistemului și configurarea timerelor. Configurarea sistemului
este cea utilizată și în celelalte programe, astfel că nu va mai fi reluată. Porțiunile de cod
pentru configurarea timerelului Timer1 sunt date în Exemplul 1.

7
T1CON = 0b1000000000010000;
PR1 = timer1_reload;
IPC0bits.T1IP = 3;//setare prioritate intreruperi TIMER 1
_T1IF = 0; //reset flag de intrerupere TIMER 1
_T1IE = 1; //activare TIMER 1

Exemplul 1. Configurarea timerului Timer1

Prima linie de cod realizează configurarea inițială a timerului.Cel mai


semnificativ bit, setat în ”1” semnifică activarea timerului. Biții 5 și 6 sunt
TCKPS1:TCKPS0, iar setarea lor în ”01” este echivalentă cu selectarea unui prescaler de
1:8. Toate celelalte setări posibile ale acestora, precum și semnificația tuturor biților din
registrul de configurare T1CON sunt date în datasheet. [2]
Valoarea pragului la care timerul se resetează este setată inițial prin linia de cod:

PR1 = timer1_reload;

Următoarea linie de cod are rolul de a seta prioritatea întreruperii generate de


timer, în acest caz 3. Cu cât acest număr este mai mic, cu atât prioritatea porțiunii de cod
este mai mare. Biții de configurarea a priorităților se găsesc în registrul IPC0. După
setarea priorității, urmează operația de ștergere a flagului de întrerupere și activarea
întreruperilor generate de timer. De menționat că putem avea timerul activ prin setarea
corespunzătoare a registrului T1CON și totuși să nu avem activate întreruperile pentru
acest timer. În acest caz, rutinele de tratare a întreruperilor nu vor funcționa. Cele două
flaguri setate, T1IF și T1IE se găsesc în regiștrii IFS0 respectiv IEC0. Se poate opta
pentru scrierea valorilor direct în registru, dar, dat fiind numărul mare de variabile și de
regiștrii, se recomandă utilizarea numelor predefinite pentru fiecare bit de configurare.
Frecvența de apariție a întreruperilor generate de timerul Timer1 va fi frecvența
punctelor de aprindere/stingere LED-uri. Întreruperile generate de timerul Timer2, pe de
altă parte, vor fi utilizate pentru verificarea butoanelor. Putem considera că verificarea
butoanelor este mai puțin prioritară decât controlul de putere al LED-urilor, astfel încât s-
a optat pentru o prioritate 5 în cazul Timer2. Restul setărilor de configurare sunt similare
și sunt date în Exemplul 2:

T2CON = 0b1000000000010000;
PR2 = timer2_reload;
IPC0bits.T1IP = 5;
_T2IF = 0;
_T2IE = 1;

Exemplul 2. Configurarea timerului Timer2

După cum se poate observa în Figura, următorul pas este inițializarea


butoanelor. Pentru a putea descrie acest pas, trebuie mai întâi înțeles modul în care vor fi
utilizate butoanele în această aplicație. În primul rând, spre deosebire de aplicația

8
descrisă în lucrarea dedicată porturilor de intrare, vom utiliza un mod mai robust de
verificare și anume verificarea butoanelor cu o frecvență constantă. Vom utiliza patru
butoane, iar modul lor de definire este dat în Exemplul 3. Porțiunea de cod se afă în
fișierul header “buttons.h”. Primele linii definesc constante spre pinii utilizați ca input,
pentru o mai ușoară utilizare a acestora. Urmează declararea a două funcții utilizate: una
pentru inițializare, iar cea de-a doua pentru verificarea butoanelor. După acestea, se
declară o structură cu 4 membrii, corespunzătoare celor 4 butoane. În variabilele acestei
structuri se va memora starea curentă a butoanelor. Utilizarea unor variabile de tip char
permite memorarea mai multor informații despre butoane. Astfel, în cazul de față, odată
ce butonul a fost considerat apăsat, se va seta „1” în variabila corespunzătoare butonului.
Odată ce acțiunea corespunzătoare respectivului buton a fost îndeplinită, variabila va lua
valoarea „2”. În acest mod, vom evita cazul în care o apăsare prelungită a unui buton va
realiza modificări succesive nedorite.

#define BUTTON1 PORTDbits.RD6


#define BUTTON2 PORTDbits.RD7
#define BUTTON3 PORTAbits.RA7
#define BUTTON4 PORTDbits.RD13
//prototip functie de initializare butoane
void BtnInit(void);
//prototip functie de verificare butoane
void BtnProcessEvents(void);
//structura cu indicatorii de stare ai butoanelor
typedef struct tagButton {
unsigned char b1;
unsigned char b2;
unsigned char b3;
unsigned char b4;
}BUTTON;
extern BUTTON button_press;

Exemplul 3. Modul de definire al butoanelor și al structurii asociate

Inițializarea butoanelor constă în setarea în „0” a variabilelor corespunătoare din


structura descrisă (Exemplul 4). Funcția BtnInit se găsește în fișierul „buttons.c”.

void BtnInit(void)
{
b1_flag = 0;
b2_flag = 0;
b3_flag = 0;
b4_flag = 0;
}

Exemplul 4. Inițializara butoanelor

9
Cea mai importantă porțiune de cod legată de butoane este verificarea efectivă a
acestora. Verificarea se realizează în funcția BtnProcessEvents, din același fișier
„buttons.c”. Deoarece verificările sunt identice pentru cele patru butoane, a fost atașată
doar porțiunea de cod corespunzătoare unuia dintre ele. (Exemplul 5)

if (!BUTTON1) {//daca butonul 1 este in starea "0" logic, adica apas


at
if (b1_flag&&button_press.b1==0){//daca butonul a fost apasat
la momentul
//anterior,iar butonul este considerat in starea OFF
button_press.b1 = 1;
//seteaza indicatorul de buton apasat
}
b1_flag = 1; //seteaza flag
}
else {
b1_flag = 0;
//altfel, resesteaza flag
button_press.b1 = 0;
//reseteaza indicator, buton "OFF"
}

Exemplul 5. Verificarea primului buton

După cum se poate observa, în secvența de cod folosim pe lângă membrii


structurii definite anterior și variabile flag (b1_flag). Aceste variabile indică o
posibilitate ca butonul să fi fost apăsat. Ele vor fi setate pe valoarea „1” de fiecare dată
când la pinul corespunzător butonului, vom citi valoarea „0” logic. Aceste variabile sunt
folosite pentru a memora starea anterioară a butonului (la verificarea precedentă). Dacă
butonul a primit un impuls (!BUTTON1=1), butonul a fost apăsat și la verificarea
anterioară (b1_flag=1), iar butonul nu este considerat apăsat (button_press.b1==0),
îl vom considera apăsat, prin instrucțiunea button_press.b1 = 1. În cazul în care
valoarea logică de la intrarea butonului este „1” logic (butonul este conectat la VDD),
atât flag-ul, cât și indicatorul din structură vor fi setate pe valoarea „0”. Verificarea
acestor butoane se realizează prin intermediul rutinei de tratare a întreruperilor generate
de timerul Timer2.
Ultima porțiune din codul funcției main() constă în efectuarea acțiunilor
corespunzătoare apăsării fiecărui buton. Acestea au prioritate mică, astfel încât s-a optat
pentru implementarea lor în interiorul unei bucle infinite. Secvențele legate de primul
buton sunt date în Exemplul 6:

10
if(button_press.b1==1){
button_press.b1=2;
//scade factorul de umplere
//prin decrementarea valorii setate PWM
if(pwm_set>pwm_min)pwm_set-=pwm_step;
}

Exemplul 6. Operații declanșate de apăsarea primului buton


Primul lucru pe care îl observăm este faptul că odată considerat butonul apăsat,
valoarea din structura de stare a butoanelor este schimbată în „2”
(button_press.b1=2). Astfel se asigură efectuarea operației o singură dată pentru o
apăsare. Operația efectuată în cazul primului buton este scăderea valorii pwm_set cu
valoarea de pas definită inițial, pwm_step. Valoarea pwm_set este momentul
stingerii LED-ului într-o perioadă a semnalului PWM și conduce la valoarea factorului de
umplere. Valoarea pwm_set este echivalentă cu din subcapitolul dedicat modulației
PWM. În același timp, pwm_max este echivalentul lui (lungimea discretă a perioadei
semnalului PWM). Factorul de umplere presetat poate fi calculat astfel:

Pentru valorile inițiale ale variabilelor, avem:

Celelalte butoane au o funcționalitate similară. La apăsarea unuia dintre


butoanele 3 și 4, se va modifica valoarea de divizare a frecvenței (timer1_reload),
valoare încărcată în registrul PR1. Aceste modificări vor avea ca efect modificarea
frecvenței de refresh sau de clipire a LED-urilor, frecvență numeric egală cu inversul
perioadei semnalului PWM. Frecvența obținută poate fi calculată cu formula:

Putem observa că frecvența de clipire a LED-urilor se poate modifica prin


alterarea oricăror parametri dintre cei trei:
 Factorul de divizare din timer, , utilizat în program ca timer1_reload
 Factorul de prescalare, setat odată cu setarea inițială a timerului
 Factorul de divizare „soft”, , reprezentat în program ca pwm_max
Pentru setările inițiale din program, valoarea acestei frecvențe este:

11
Ultima parte a programului este dată de implementarea rutinelor de întrerupere.
Această parte este cea mai importantă din capitolul curent. Rutinele de tratare a
întreruperilor se găsesc în fișierul main.c. Rutina asociată timerului Timer1, are codul din
Exemplul 7.

void _ISR _T1Interrupt(void){


_T1IF=0; //resetare flag de intrerupere TIMER 1
if(pwm>=pwm_set)PORTA=0x00;//daca s-
a atins pragul setat, LED-ile se sting
if(pwm<pwm_max)pwm++; //incrementare contor PWM
else if(pwm_set>0){ //se aprind LED-urile doar daca pwm_set>0
pwm=0; //resetare contor PWM
PORTA=0x0F; //se aprind LED-urile (ultimele 4)
}
}

Exemplul 7. Rutina de tratare a întreruperilor generate de Timer1


După cum putem observa, primul pas în tratarea unei întreruperi este să resetăm
bitul de flag asociat. Prin aceasta evităm situația în care pentru o singură întrerupere
avem mai multe execuții succesive. Bitul de flag asociat întreruperilor generate de
Timer1 este T1IF.
Odată executată această operație, se trece la generarea semnalului PWM. Astfel,
utilizăm o variabilă contor (pwm) în care stocăm poziția curentă în perioada semnalului
PWM. Poziția este o valoare naturală, astfel că vorbim de o implementare discretă a
sistemului. Această valoare, pwm, este echivalentă valorii din formula de mai jos:

În această formulă, este durata de timp scursă de la începutul perioadei


curente a semnalului PWM. Astfel, factorul de umplere care s-ar obține dacă am opri
LED-urile la momentul curent, se poate scrie:

Tot ceea ce ne rămâne de făcut este să comparăm acest factor de umplere cu


valoarea setată (D), iar dacă am depășit această valoare să stingem LED-urile. Totuși,
dacă analizăm formulele de calcul ale celor doi factori de umplere, ajungem la concluzia
că este suficient să comparăm cele două valori întregi și . Această observație este
foarte importantă, deoarece pe sistemele embedded resursele de procesare sunt
importante. Astfel, se impune să lucrăm eficient, să utilizăm pe cât posibil variabile

12
întregi pe cel mai mic număr de biți posibil. De asemenea, operațiile aritmetice sunt mult
mai lente dacă se utilizează numere flotante. Compararea celor două valori mai sus
menționate se face prin linia de cod:
if(pwm>=pwm_set)PORTA=0x00;

Dacă se îndeplinește condiția de mai sus, LED-urile vor fi stinse prin scrierea
valorii 0x00 în registrul PORTA.

5. Aplicații și teme

Aplicațiile unui mod de control al puterii similar celui prezentat sunt multiple:
 Reglaje de putere în curent continuu și alternativ
 Generarea de sunete cu dispozitive buzzer
 Realizarea de jocuri de lumini

Pentru aprofundarea înțelegerii modului de funcționare a timerelor și a


întreruperilor, se propun următoarele teme:

1. Calculați frecvența de clipire a LED-urilor, respectiv frecvența de verificare


a stării butoanelor, pe baza biților de configurare a timerelor.
2. Propuneți o modalitate prin care atât timp cât un buton este apăsat, să se
execute în mod continuu funcția corespunzătoare (ex: creșterea factorului de
umplere). Care sunt problemele suplimentare care apar? Care ar fi avantajele
și dezavantajele fiecărei implementări?

13

S-ar putea să vă placă și