Curs 11.2
Curs 11.2
MASTER SRL
UTILIZAREA si PROGRAMAREA
MICROCONTROLERELOR
• Modulul de reset
Este un modul necesar functionarii corecte si sigure a procesorului si are ca functie,
stergerea tuturor registrilor si initializarea acestora inainte de inceperea unei sesiuni
de lucru. Unele procesoare contin si un modul atasat de intarziere (configurabil la
cerere) ce intarzie procesul de pornire pentru siguranta intrarii in regim normal de
functionare al tuturor modulelor periferice, cum ar fi stabilizarea frecventei de
oscilatie a oscilatorului, etc.
• Timer 0
In general un MCU contine mai multe TIMERE , care sunt de fapt numaratoare de 8
sau 16 biti (valoare maxima a contorului 256 sau 16384) a caror sursa de impulsuri
poate fi setata sa provina din interior sau exterior.
Ele pot fi setate sa contorizeze timpi (durata procese), sau sa genereze pulsuri la
intervale de timp constante.
Desi au destinatii preferentiale diferite, in aceasta lucrare von trata doar Timer0.
Acest contor este in general pe 8 biti , si poate fi legat la frecventa oscilatorului
intern divizata la 4 in mod direct, sau printr-un Prescaler (un divizor suplimentar
inseriat). Reamintim ca aceasta frecventa de lucru interna poate proveni din
functionarea oscilatorului pe componente interne (precizie1% ) sau externe, (pana la
0.00001% cu cristale Quartz de calitate).
De cate ori contorul atinge valoarea maxima, acesta genereaza un inpuls de
intrerupere catre procesor, care isi intrerupe activitatea curenta (retine intro memorie
speciala numita STIVA, locul unde se afla la momentul intreruperii) ,si trece la
executia unei subrutine, scrisa de utilizator, cu ceace trebuie sa faca procesorul in
aceasta situatie. De regula incrementarea unui contor cu valoarea REALA de timp
scursa de la intreruperea anterioara. Desi lungimea contorului este mica (uzual 256)
Utilizarea si programarea microcontrolerelor Pag 9
, in subrutina de intrerupere , se poate incrementa o variabila , pentru a se masura
cu precizie timpi de secunde, minute, ore ,C zileCetc.
Dupa terminarea subrutinei de intrerupere, procesorul reia locul de executie avut
anterior (memorat in Stiva) si isi continua activitatea curenta. De mentionat ca
Timer-ul nu pierde timp cu asteptarea procesorului sa-i execute subrutina de
intrerupere. El genereaza inpulsul de intrerupere (memorat in registrul de intreruperi)
si isi reia numaratoarea din nou. Procesorul dupa inceperea subrutinei de
intrerupere verifica motivul intreruperi , sterge cererea de intrerupere memorata in
registrul de intreruperi, si o executa.
In cazul in care mai multe periferice (blocuri interne ) genereaza cereri de
intreruperi , acestea se memoreaza in registrul de intreruperi, se face un test al
bitilor de intreruperi, pentru a se afla cine a solicitat intreruperile , dupa care se
trateaza fiecare pe rand intr-o ordine de prioritate stabilita de programator.
- Acestea pot fi Master Synchronous Serial Port (MSSP) cu SPI sau I2CTM
- Enhanced Universal Synchronous Asynchronous Receiver Transmitter
(EUSART)
- USB , etc
Ele sunt module ce pot comunica la cerere, cu alte periferice externe, folosind
diverse protocoale de comunicare, dupa numarul de fire, tensiunea pe liniile de
comunicare , viteza de transmisie , etc.
Astfel acestea pot diferi prin modul de adresare , un periferic sau mai multe
indexate, pe unul sau mai multe fire, sincron sau asincron.
Evident ca exista multe alte module aparute in special la MCU moderne, destinate
unor aplicatii de uz general (comanda LCD) sau specializate.
Anumiti pini din familia PIC de MCU au functie dubla. Ei sunt folositi atat pentru
programare cat si ca pini de uz general in timpul lucrului. Intrarea in regim de
programare se face fie prin aplicarea unei tensiuni mari (8-13V) pe pinul Vpp-MCLR,
fie prin aplicarea unei secvente speciale, daca la o scriere anterioara s-a activat in
“Configuration-world” “Low Voltage Programming”.
Scrierea se poate face si in montaj (dupa ce MCU a fost lipit pe placa ) nu doar
separat. Evident si in acest caz se foloseste tot interfata de scriere (programare).
Acest mod de scriere se numeste In-Circuit-Serial-Programming , sau pe scurt
Utilizarea si programarea microcontrolerelor Pag 10
ICSP. Insa pentru a beneficia de aceasta facilitate montajul trebuie sa respecte
cateva reguli ce vor fi expuse in continuare:
• Circuitul legat la pinul Vpp –MCLR trebuie sa suporte tensiuni mari (8-13V).
• Tensiunea de alimentare pentru MCU in timpul scrierii (Vdd) trebuie aplicata
numai lui, nu intregului montaj. In primul rand programatorul nu poate asigura
curentul de alimentare pentru tot montajul dar sunt situatii in care aplicarea
acestei tensiuni poate duce la arderea unor piese din circuit. Astfel daca
procesorul este alimentat din iesirea unui stabilizator (de exemplu 78L05)
varianta uzuala de altfel, acest stabilizator nu accepta tensiune de iesire mai
mare decat tensiunea la intrare.
• Cand este alimentat MCU el va avea tensiunea de Vdd la iesire, dar nimic pe
intrare si ca atare sint depasite valorile permise (prin urmare se distruge).In
figura de mai jos este exemplificat acest lucru cand Vdd>Va.
* Sau o alta varianta mai buna dar incomoda la scriere, (trebuie avut grija ca
in timpul scrierii Jumper-ul sa fie scos) este prin intreruperea alimentarii dintre
MCU si montaj cu ajutorul unui “jumper” ca in schema de mai jos:
Acest capitol isi propune doar sa treaca in revista cateva din notiunile de baza din
programarea in limbaj C+
Pentru a intelege avantajul major pe care-l are programarea intr-un limbaj avansat,
vom pune in paralel acelasi program scris in assambler, cel mai scazut nivel de
programare, care foloseste direct instructiunile microprocesorului si varianta
aceluiasi program scris in C+:
BANKSEL PORTA ;
CLRF PORTA ;Init PORTA
BANKSEL LATA ;Data Latch
CLRF LATA ;
BANKSEL ANSELA ;
CLRF ANSELA ;digital I/O
BANKSEL TRISA ;
MOVLW B'00111000' ;Set RA<5:3> as inputs
MOVWF TRISA ;and set RA<2:0> as
;outputs
in plus cel scris in limbaj avansat este mult mai usor de inteles
Variabile
Variabilele pot fi: - numere (ale caror valori variaza in timpul rularii programului)
- siruri de caractere (variabile text)
- Arii (matrici)
Variabilele numerice
Inainte de folosire in limbajul "C" variabilele trebuiesc declarate , definindu-se atat
tipul variabilei cat si valoarea de start.
Este interzisa folosirea de nume rezervate pentru a declara variabile, acestea
generand erori fatale;
astfel de nume : for, next, while, do, int, (o lista a numelor de variabile neutilizabile
,o puteti gasi in PIC Microcontrollers -Programming in C ; manual de referinta
pentru incepatorii in programare. Vezi tabelul de mai jos:
Variabilele numerice pot avea nume formate din literele a-z (preferabil literele mici)
si cifrele 0-9 , dar nu pot incepe cu o cifra.
Pot fi folosite si literele mari , dar se prefera ca acestea sa defineasca doar
constante.
Dupa tipul variabilei , aceasta poate ocupa un spatiu de memorie diferit in timpul
programului.
Pentru economisirea spatiului ocupat in RAM de variabile, se recomanda definirea
variabilelor cu o lungime minima in care ele se pot incadra. In microC for PIC aceste
tipuri pentru variabile numerice sunt:
Numere
Sistemul zecimal
In viata de zi cu zi am fost obisnuiti sa numaram si calculam in sistemul Zecimal,
altfel spus, in sistemul cu baza de numarare 10. Acesta contine cifrele de la 0 la 9.
Cu aceste cifre se pot forma numere ce contin mai multe cifre. Dupa locul ocupat de
cifre in numar , poarta numele de unitati , zeci, sute, mii ,etc. Valoarea numarului
1 0 1 1 0 0 1 1
1*2 + 0*2 +1*2 + 1*2 +0*2 +0*2 + 1*2 +1*20 =
7 6 5 4 3 2 1
Dupa numarul de cifre binare pe care il contine un numar binar, definim 2 variante
de baza:
- cu o singura cifra :"bit-ul" poate fi doar 0 sau 1
Utilizarea si programarea microcontrolerelor Pag 15
- Cu 8 cifre (8 biti) "Byt-ul" poate fi intre 0 si 11111111 ( 0si 255zecimal) se mai
numeste si "octet"
- Cu 16 cifre (practic 2 octeti ) . Acestia se vor defini ca LSB (Low Semnificative
Byt) si respectiv MSB (Most Semnificative Byt).
De mentionat ca toate elementele de calcul (microprocesoare si implicit
microcontrolere) lucreaza exclusiv in sistem binar,
Sistemul Hexazecimal
Pentru simplificarea citirii numerelor mari in informatica se foloseste adesea si
sistemul hexazecimal ( baza de numarare 16) avand ca cifre
0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F. Se remarca aici ca A,B,C,D,E,F sunt simbolurile
cifrelor cu valori zecimale mai mari ca 9.
Intrucat in sistemul binar numarul maxim pe patru biti este 15zecimal (1111binar) se
observa ca putem grupa cate 4 biti ce pot fi reprezentati printr-o singura cifra in
hexazecimal .
Exemplul anterior 10110011 in binar, devine B3 in hexazecimal.
Adesea in program folosim toate cele 3 variante de sisteme de numeratie. Pentru a
le distinge atat noi cat si programul de compilare numerele vor fi precedate de un
prefix dupa cum urmeaza:
nimic pentru zecimal : 179
0b pentru binar: 0b10110011
0x pentru hexazecimal : 0xB3
asadar 179= 0b10110011=0xB3
Variabilele indexate (pointer)
Atunci cand avem un numar de variabile (numerice sau siruri), scrise intr-o anumita
succesiune (lista) ele se pot apela dupa locul lor in lista .
Char *ziua[8] =luni,marti,miercuri,joi,vineri,sambata,duminica, ; // unde [8] lungimea
unui sir Astfel cand apelam ziua3 v-a rezulta miercuri
Pentru incepatori insa, se poate evita folosirea variabilelor indexate
Variabilele text (sau siruri )
In general sirurile sunt variabile text ce urmeaza a fi tiparite pe un ecran. De
mentionat ca spre imprimanta sau ecran LCD (etc) daca avem de afisat T=94g ,
vom trimete:
caracterul “T”, caracterul “=”, caracterul “9”, caracterul “4”, caracterul “g” ,
Dupa cum se observa 94 nu mai este un numar trimes, ci un sir de caractere
(simboluri).
Pentru a fi recunoscute drept siruri si nu nume de variabile sau cifre , valoarea
sirurilor va fi introdusa intre ghilimele : valoarea lui T=94g vafi scrisa asa : “T=94g”
Exemplu de sir trimes spre ecran:
lcd_out(1,1, "Tset=");
Daca ea este definita ca variabila, pentru a putea fi calculata anterior, va arata ceva
de genul :
char numesir [5] ; // numele variabilei este numesir si are o lungime de 5
caractere
CCCCCCCC.
numesir = “Tset=” // atribuirea valorii pentru variabila numesir
DDDDDDDDDD..
lcd_out(1,6, numesir); // afisarea pe ecran a valorii lui numesir
(pe ecran apare Tset=)
Utilizarea si programarea microcontrolerelor Pag 16
pentru mai multe detalii trebuie aprofundata folosirea sirurilor in limbajul C++
Functii logice
Atribuirea de valori :
Atribuirea de valori unei variabile se face uzual prin semnul de egalitate intre numele
variabilei si noua valoare directa sau operatia al carui rezultat ii va fi atribuita
variamilei. Cateva exemple aveti mai jos:
variabila =754 ; variabila cu numele “variabila”ia valoarea 754 in zecimal
variabila =0x02F2 ; variabila cu numele “variabila”ia valoarea 02F2(hexa) =754
in zecimal;
variabila =7+a ; variabila cu numele “variabila”ia valoarea variabilei a la care se
aduna 7;
variabila = variabila +a ; variabila cu numele “variabila”ia valoarea initiala
+valoarea lui “a”
Operatori uzuali:
Adunarea :
variabila =7+a ; variabila cu numele “variabila”ia valoarea variabilei “a” la care se
aduna 7;
Scaderea:
variabila =a-7 ; variabila cu numele “variabila”ia valoarea variabilei “a” din care
se scade 7;
Inmultirea:
Increment :
variabila ++; variabila cu numele “variabila”isi mareste valoarea cu o unitate
(adunarea cu 1 ) ;
Decrement :
variabila - - ; variabila cu numele “variabila”isi micsoreaza valoarea cu o unitate;
(scaderea cu 1 ) ;
Comparatia:
Testul de egalitate :
variabila ==754 ; functia este 1 (adevarata) daca variabila cu numele
“variabila”avea valoarea
754 in zecimal
Testul de inegalitate :
variabila <754 ; functia este 1 (adevarata) daca variabila cu numele “variabila”
avea valoarea mai mica decat 754 in zecimal.
variabila >754 ; functia este 1 (adevarata) daca variabila cu numele “variabila”
avea valoarea mai mare decat 754 in zecimal;
se pot folosi si functiile combinate >= mai mare sau egal ; <= mai mic sau egal ;
Conditionarea simpla :
if (conditie sau conditii ) {
linii de program de executat;
} // sfarsit pachet program conditionat
de cate ori conditia sau conditiile sunt realizate (rezultatul functiilor dintre paranteze
este adevarat) se va executa o singura data pachetul de program cuprins intre
acoalade { }
conditionarea alternativa
Uneori este necesara inserarea intr-un program scris in limbaj C++ de comenzi
preluate din assambler.
Ori de cate ori trebuie sa inseram o comanda in assambler vom scrie prefixul asm:
sau daca inseram o subrutina aceasta vafi precedata de prefixul asm si delimitata
de acoalade.
Utilizarea si programarea microcontrolerelor Pag 20
Asm {
BANKSEL PMADRL ; Select Bank for PMCON registers
MOVLW PROG_ADDR_LO ;
MOVWF PMADRL ; Store LSB of address
MOVLW PROG_ADDR_HI ;
MOVWF PMADRH ; Store MSB of address
BCF PMCON1,CFGS ; Do not select Configuration Space
BSF PMCON1,RD ; Initiate read
MOVF PMDATL,W ; Get LSB of word
MOVWF PROG_DATA_LO ; Store in user location
MOVF PMDATH,W ; Get MSB of word
MOVWF PROG_DATA_HI ; Store in user location
}
Anumite blocuri de program ce trebuiesc executate de mai multe ori , la cerere pot fi
definite ca “ FUNCTII” sau “ subrutine”, ce pot fi chemate la cerere ori de cate ori
este nevoie:
void display data ( ) {
liniile de executat pentru a fi afisate datele;
CCCCCCCCC..
} // sfarsit subrutina . de aici programul se intoarce de unde a plecat
CCCCCC..
linii de executat;
display data ( ) ; // chemare subrutina
CCCCCC.. // continuare dupa executie subrutina.
linii de executat;
Atentie : variabilele ce sunt folosite in mai multe subrutine poarta numele de
variabile globale si se vor defini inainte de aparitia lor initiala.
Comentariile:
Comentariile sunt inportante pentru programator , ele fiind destinate sa expliciteze
denumirea unor variabile, a unor functii, a unor subrutine,setari, etc.
Cu cat mai explicite sunt comentariile intr-un program, cu atat va fi inteles si
refolosit, modificat , adaptat , mai usor dupa o perioada de timp.
Sunt 2 moduri de a insera comentarii intr-un program scris in C+.
Comentariu scurt , este precedat de 2 bare inclinate spre dreapta (slash):
C. // comentariuCC
este valabil de la cele 2 bare (pot fi dupa o linie de program terminata in ; dupa o
acoalada ;sau la inceput de linie goala ) si se termina din oficiu la sfarsitul liniei de
program.
Comentariu lung : Incepe cu perechea /* continua cu orice continut pe orice
lungime, sau numar de linii , si se termina cu aceeasi pereche asezata in ordine
inversa : */
Atentie: Nu ezitati sa folositi cat mai multe comentarii inserate in program.
Descrieti numele date variabilelor, explicati semnificatia setarilor registrelor speciale
si a celor de configurare, semnificatia unor functii sau ale conditionarilor, etc.
Ofera protectie integrala , Condensatorul daca nu intarzie procese rapide este util in
a reduce varfurile de zgomot.
Este o protectie simpla , ieftina si asigura o protectie buna la Vdd dar la limita la
GND (Umin= –0.5V). Condensatorul daca nu intarzie procese rapide este util in a
reduce varfurile de zgomot.
Este cea mai buna solutie Umin = 0.1V (Uce saturat) iar max este dat chiar de Vdd
(poate fi si intern legat prin configurarea Weak Pull Up resistor ) Fata de situatia
anterioara circuitul de intrare poate avea masa separata, sau sa fie expus unui
mediu nociv.
Iesire de tensiune pozitiva (activ in 1 logic) curentul prin sarcina este de tip sursa
(source)
Este interzisa folosirea spre masa a unei iesiri de tensiune negativa (activ in 0 logic)
atunci cand sarcina este legata la o tensiune mai mare decat Uiesire (Vdd).
Atunci cand sarcina este legata la o tensiune mai mare decat Uiesire (Vdd) se va
folosi un tranzistor extern de separare / amplificare in tensiune pentru comanda
sarcinii la tensiune mare. Evident ca si aici este obligatoriu ca sa se suprime
tensiunea de autoinductie generata in sarcina la intrerupere.
Exemplul este similar cu cel prezentat anterior, doar ca s-a folosit un tranzistor de tip
MOSFET pentru amplificarea in tensiune (comanda sarcinii). Desi nu exista un
curent de grila (grila este izolata ), trebuie sa tinem cont ca exista o capacitate (un
condensator) intre Grila si Sursa ceea ce face sa apara un curent suplimentar la
ridicarea / coborarea tensiunii de iesire. R1 se dimensioneaza pentru limitarea
curentului de varf . R2 are valoare mare si se pune pentru descarcarea sarcinii din
Grila-Sursa si pentru mentinerea tranzistorului blocat atunci cand iesirea nu este
activa (in perioada de Reset si pana la configurare).
Din pacate Circuitul R1-Cgs este un circuit de temporizare care intarzie comanda.
Nici aici nu se permite folosirea spre masa a unei iesiri de tensiune negativa (activ
in 0 logic) atunci cand sarcina este legata la o tensiune mai mare decat Uiesire (Vdd).
Pasul 2- definirea schemei :Ne propunem sa setam portul A cu toti pinii in regim de
intrare si portul C cu toti pinii in regim de iesire . Ulterior vom atasa Trei LED-uri pe
RC0,RC1 si RC2.
Intrucat din Datasheet gasim ca fiecare iesire poate asigura un curent de 25mA pe
sarcina , ne vom limita pentru demonstratii sa alimentam LED-urile cu un curent de
10mA:
Vdd=5V;VfLED(rosu)=2V; Ur= Vdd-VfLED = 5V-2V; ILED= 10mA
Rled= (5-2)/0.01=300R ; alegem o rezistenta standard de 330R
void main() {
// nu avem nevoie de variabile la acest stadiu - aici era locul lor
//------------------ setarea registrelor-------------------
void main() {
short int led1=0; // variabila "led1" are valoarea alocata pt LED1
short int led2=0; // variabila "led2" are valoarea alocata pt LED2
short int led3=0; // variabila "led3" are valoarea alocata pt LED3
int tensiune =0;// in urma conversiei (citirii) tensiunii de la intrarea AN3
int i =0; // variabila curenta pentru bucla for
// situata pe RA4 va rezulta o valoare numerica pe 10 biti (max 1023)
//------------------ setarea registrelor-------------------
In acest exemplu s-a folosit modulul de tensiune de referinta interna FVR, utilizat in
special cand tensiunea de alimentare poate fi variabila (alimentarea din baterii).
Inrucat stabilitatea acestui modul este relativ modesta 4% , nu se recomanda
utilizarea lui atunci cand avem tensiuni de alimentare de calitate (obtinute din
stabilizatoare performante).
Acest program a fost scris sa supravegheze tensiunea a 2 baterii de acumulatoare
montate pe o masina . Atunci cand tensiunea pe bateria principala este >13.6V
(bateria se afla in regim de incarcare) , trebuie sa cupleze un releu (sau doua in
paralel, pentru cresterea curentului) si sa decupleze la o scadere sub 13.1V.
Acest “histerezis” este necesar pentru a nu apare oscilatii.
Tinand cont ca acest program are durata nedeterminata de lucru, (luni-ani) fara sa
fie intrerupt sau resetat , si ca acest montaj lucreaza in conditii cu posibilitati de
perturbatii , (paraziti, tensiuni de inductie ,etc) s-a activat modulul de supraveghere
WDT. In acest scop el a fost activat din Configuration-Word si a fost setat registrul
WDTCON corespunzator aplicatiei dorite , iar in program s-au introdus comenzi de
CLRWDT pentru resetarea periodica a registrului (contorului) WDT.
Nota: la urmarirea si intelegerea acestui program (ca de altfel la orice alt program)
incarcati si urmariti in paralel permanent datele din datasheet-ul corespunzator
procesorului utilizat ( in cazul de fata PIC12F1572)
Schema de utilizare este prezentata mai jos:
unsigned Get_ADC(){
ADCON0.F1 = 1; // start conversion
while (ADCON0.F1); // wait for conversion
return (ADRESH << 8) + ADRESL; // return value
}
void main() {
long int cont =0;
int factor=350 ;
int i =0 ;
int puls =0 ;
short int ron =0;
short int buz =0;
short int paralel =0;
int cntsw =0;
long int contsw =0;
unsigned int contbp =0;
short int ledon =0;
short int buzr =0; // lucru buz
int vbat1,vbat2 = 800 ; // 15k + 5k1 tensiune baterie 862 =13.6v
OSCCON =0b01110010; // 8 MHz INTERN
TRISA = 0b00011001 ; //GP1;2;5 out ; GP0;4- in Ana ; SWitch on GP 3
ANSELA = 0b00010001 ; // A0; A3 SUNT IN ana
FVRCON =0 ; // Vref = 5V ; Atentie CITIRE ANALOGICA !
WDTCON = 0b00010111 ; // 2sec (VALOARE DE RESET)
ODCONA = 0 ; // OUT-urile SUNT PUSH-POLL
WPUA =0 ; // WEAK PULL UP DISABLE
OPTION_REG =0x8F; // no WPU ; WDT prescaler 18ms x 128
ADCON1 =0b11010000; // f=1/16 ; Vref = Vdd
LATA =0x00 ;
buz=0;
while(1){
asm CLRWDT ;
// TEST SW PARALEL
if (PORTA.F3 ==0 && paralel ==0){
cntsw++ ;
//if (cntsw >251){
// cntsw =251 ;
}
else {
cntsw =0;}
if (cntsw >200 && paralel ==0){ // cuplare paralela la cerere pt 1 min
Utilizarea si programarea microcontrolerelor Pag 43
paralel =1 ;
ledon =1 ;
contsw =0 ;
ron =1 ;
}
if (contsw >60000 && paralel ==1){ // de testat pt 1 minut
paralel =0 ;
ledon =0 ;
ron =0 ;
Delay_us(100);
contsw =0 ;
}
cont++; // contorizeaza nr cicluri
contsw++; // contorizeaza nr cicluri
if (cont >1000) {
cont=0;
}
// ADCON0 = 0x81 ; // an0 on GP0 cu Vref =5v
// ADCON0 = 0x83 ; // Start AD pe AN0
Delay_us(300);
//--------------------------------------------------------------------------
asm CLRWDT ;
ADCON0 = 0b00001111 ; // Start AD pe AN3
Delay_us(100);
vbat1 = Get_ADC(); // read AN0
vbat1 = vbat1*1.18 ;
Delay_us(300);
asm CLRWDT ;
ADCON0 = 0x03 ; // Start AD pe AN0
Delay_us(100);
vbat2 = Get_ADC(); // read AN1
vbat2 = vbat2*1.18 ;
//--------------------------------------------------------------------------
if ( vbat1 >793 && paralel ==0){ // >13.6v //828
ron=1 ; // cupleaza incarcarea
ledon=0;
contbp =0;
buzr=0; // permite sa sune a doua oara
}
if ( vbat1 <753 && paralel ==0){ // <13.1 v //794
ron=0 ; // decupleaza incarcarea
}
// *********** subrutina beep ******************************
}
if (vbat2 >650 && vbat2 <681){ // 25 - 50%
puls = 300 ;
}
if (vbat2 >636 && vbat2 <650){ // 10 - 25%
puls = 750 ;
}
if (cont < puls && paralel ==0){
ledon =1;
}
if (vbat2 >626 && cont > puls && paralel ==0){
ledon =0;
}
De multe ori avem nevoie sa afisam numere compuse din cateva cifre (de exemplu un
voltmetru cu 3 digiti). Exista o multitudine de dispozitive de afisare , inclusiv lampi ce
folosesc tensiuni mari si care au chiar si cate un terminal pentru fiecare cifra (10 cifre + 1
comun ). O alta varianta des folosita , o reprezinta afisarea cu LED-uri (perfect vizibila in
medii intunecate ) ,si care pot forma orice cifra zecimala (uneori si Hexazecimala ) din 7
segmente plus terminalul de referinta (comun). Astfel vom avea doar 8 terminale in loc de
11, dar avem nevoie de un decodor Binar-zecimal si un codor (codificator) din zecimal in 7
segmente. Dea lungul timpului s-au creat o serie de circuite specializate ce fac direct
conversia binar-7segmente cu etaje finale capabile sa asigure curentii de lucru pentru LED-
urile din afisaj. Pentru simplificarea legaturii cu panoul de afisaj cifrele ce formeaza
numarul, pot fi afisate pe rand cu o frecventa mai mare de 50 de iluminari pe secunda ,
astfel ca imaginea obtinuta sa fie stabila, iar numarul de fire sa fie 7 (cate unul pentru
pentru fiecare segment), plus cate un fir pentru fiecare terminal de referinta (comun).
Astfel pentru un numar compus din trei cifre , in loc de 3x7+1=22 fire, vom avea 7+3=10
fire. Acest mod de folosire a afisajelor poarta numele de strobare. Un exemplu de afisaj
destinat acestui mod de utilizare il aveti in continuare.
Daca folosim Microcontrolere, atat afisarea pe rand a cifrelor , cat si decodarea –codarea
binar 7 segmente pot fi facute doar prin programare fara componente electronice
suplimentare. Daca folosim afisoare LED ce folosesc tehnologii superbright , si ne
incadram in valorile maxime de iesire ale unui MCU (25mA),putem sa folosim un astfel de
afisaj utilizand doar 7 rezistente externe corespunzatoare utilizarii LED-urilor si numai 10
terminale de MCU.
In continuare vom exemplifica pe un Voltmetru realizat cu microcontrolerul PIC16F1824.
Schema de conexiuni este data mai jos:
void Display_Data(){
for (i = 0; i<=10; i++) {
DG2 = 0; // Select sute Digit
DG3 = 1;
DG4 = 1;
if(DD2>63) SEG = 1; //segment g drivered by portA
PORTC = DD2;
delay_ms(5);
PORTC = 0; SEG = 0;
DG2 = 1; // Select Zeci Digit
DG3 = 0;
DG4 = 1;
if(DD3>63) SEG = 1;
PORTC= DD3;
delay_ms(5);
PORTC = 0;SEG = 0;
DG2 = 1; // Select unitati Digit
DG3 = 1;
DG4 = 0;
if(DD4>63) SEG = 1;
PORTC = DD4;
delay_ms(5);
DG4 = 1;
PORTC = 0; SEG = 0;
}
}
unsigned Get_ADC(){
ADCON0.F1 = 1; // start conversion
while (ADCON0.F1); // wait for conversion
return (ADRESH << 8) + ADRESL; // return value
}
void main() {
Utilizarea si programarea microcontrolerelor Pag 49
ANSELA = 0b00000001; // RA0 analog input
//ANSELB =0 ; // REST DIGITAL
ANSELC =0 ; // REST DIGITAL
TRISA = 0b00000001; // RA4, RA5 inputs
//TRISB = 0; //PORT B OUT
TRISC = 0x00;
OSCCON = 0b01110010 ; // CONFIG INTERNAL OSCILATOR PE 8 MHZ
DD2 = mask(0);
DD3 = mask(0);
DD4 = mask(0);
// Configure ADCON1
/* ADCON1.ADNREF = 0; // Vref- is connected to ground
ADCON1.ADCS0 = 0; // Use conversion clock, Fosc/16
ADCON1.ADCS1 = 0; // Fosc = 8MHZ
ADCON1.ADFM = 1; // result is right Justified */
ADCON1 =0b11010011 ; // VREF INTERN la FVR
/* // Configure ADCON0 for channel AN0
ADCON0.ADON = 1; // enable A/D converter */
ADCON0 = 0b00000001 ;
// Configure FVR to 2.048V
// FVRCON = 0b11000011 ; LA 16F1824
FVRCON = 0b11000010 ; // 1O PT 2048 mV
Display_Data(); // Display all zeros
do {
ADC_Value = Get_ADC();
DisplayVolt = ADC_Value * 98; // modificata scala : max 9.99
// DD1 = DisplayVolt/100000; // Extract DD1 "pentru 4 cifre"
// DD1 = mask(DD1);
DD2 = (DisplayVolt/10000)%10;
DD2 = mask(DD2);
DD3 = (DisplayVolt/1000)%10;
DD3 = mask(DD3);
DD4 = (DisplayVolt/100)%10;
DD4 = mask(DD4);
Display_Data();
} while(1); // end bucla do-while
} // end main
Cand avem de afisat mai multe cifre sau caractere alfanumerice (texte),etc este
recomandat sa folosim un afisaj LCD cu generator de caractere intern . Acestea pot
comunica pe 6 sau 10 fire cu un MCU .
Cel mai raspandit (datorita raportului calitate/pret) este display-ul xx1602 ce are o
capacitate de afisare de 2 randuri ori16 caractere . De remarcat ca acest afisaj permite si
predefinirea unor semne grafice proprii pe o matrice de 7x5 pixeli.
Pentru regimul de lucru in mediu intunecat acestea au un dispozitiv de iluminat cu LED-uri.
Mentionam ca astfel de afisaje se fac si cu un rand (1601 ; 0801;etc ) , sau cu mai multe
randuri , cum ar fi 2004 care are 4 randuri a 20 caractere.
Pentru utilizare trebuie :
- Respectarea tensiunii de lucru a LCD (Pot fi pe 3.3V, 5V , etc.)
- Alimentare Back-light daca este cazul
- Daca MCU are alta tensiune de alimentare decat LCD trebuiesc translatoare de
tensiune (adaptoare), corespunzatoare.
- Stabilirea tipului de comunicare pe linia de date a LCD (4 sau 8 date)
Dupa hotarirea modului “hard” de utilizare in program trebuiesc realizati urmatorii pasi:
- Defidirea pinilor de dialog
- Initializarea (reset) a LCD
- Modul de lucru pentru “cursor”
- Stergerea initiala a ecranului
- Transmiterea mesajelor la coordonate: rand,caracter, text
- intelegeti si verificati functionarea functiilor de conversie, din variabila numerica
in text: bytetostr; inttostr; si longtostr.
S-a folosit procesorul PIC16F1707, intrucat este unul dintre cele mai ieftine procesoare cu
20 pini, si care contine toate modulele tratate in acest curs.
// INSERT LCD
//lcd connction SE POT PERMUTA la desen PCB
sbit LCD_RS at LATC3_bit;
sbit LCD_EN at LATC2_bit;
sbit LCD_D4 at LATC4_bit;
sbit LCD_D5 at LATC5_bit;
sbit LCD_D6 at LATC6_bit;
sbit LCD_D7 at LATC7_bit;
void main() {
tensiune = Adc_Read(0);
delay_ms(10);
Dupa compilare se constata ca acest program utilizeaza doar 25% din RAM si 56% din
ROM in conditia in care de fapt biblioteca de dialog cu LCD ocupa cel mai mult.
Rezulta in fapt ca puteti scrie programe suficient de mari in limita a 2K-word (varianta demo
a MikroC pro for PIC) putand folosi afisase. Afisajele pot fi definitive in schema , prezentand
in timpul lucrului valori de interes pentru utilizator, cum ar fi tensiuni, frecvente, timp,
temperaturi, etc, sau pot fi inserate doar pentru testarea functionarii unui program “debug” ,
inserand in anumite locuri de interes comenzi de afisare valori variabile sau linia de
program , ori valori aflate in diverse registre, urmand ca programul final sa le ignore in
timpul lucrului, daca nu exista pe montajul final ,modulul LCD; sau sa rescrieti programul
,eliminand aceste linii de ajutor (programul va rula mai repede) si eventual chiar
transcrierea lui (de cele mai multe ori doar recompilarea) pe alt procesor din familia lui.
Astfel daca fara afisaj este suficient un procesor cu 14 pini la care adaugam 6 pini de dialog
cu LCD (total 20) vom folosi PIC16F1707 la probe cu LCD, de 2 sau 4 randuri (B1602 ;
[HD44780] sau B2004, [TWI 2004] pentru 4 randuri). Iar montajul final poate avea
microcontrolerul PIC16F1703 sau PIC16F1704 ,functie de momoria necesara.
Vezi datele preliminare pentru familia de microcontrolere PIC16F170x :
In cazul de fata Timer0 s-a folosit pentru a masura timpul corect fara a depinde de
viteza de parcurgere a liniilor de program.
Desi timpul de executie a liniilor de program se poate calcula cu precizie , intrucat in
programe exista frecvent linii conditionate ( if (conditie)C.), aceste linii vor fi
executate sau sarite la rularea programului, functie de starea conditiei si astfel
durata de executie a unei bucle , nu va mai fi constanta.
De mentionat ca pentru simplitate in exemplul de mai jos sa folosit oscilatorul intern
ce are o precizie de doar 1%. Pentru scop didactic si chiar pentru executii in timp
real care nu sunt deranjate de aceasta precizie scazuta (1minut la o ora si jumatate)
ar fi suficient.
Daca insa dorim timpi lungi cu precizie exceptionala (pana la secunde pe an) este
suficient sa avem 2 pini liberi ( OSC1 si OSC2 adica pini 2 si 3) pe care sa-I
conectam la un cristal de Quart de * MHz, inpreuna cu 2 condensatori de 22pF.
Mentionam ca se poate folosi orice alt cristal cu recalcularea timpilor de lucru.
In cazul nostru schema de baza , pentru care s-a scris programul a fost urmatoarea:
void interrupt() {
Utilizarea si programarea microcontrolerelor Pag 56
INTCON.F7 = 0; //stop interrupt GIE=0
// asm CLRWDT ;
if(INTCON.F2 ==1){ // TEST TMR0 overflow T0IF is on (functioneaza )
cntt0++ ; // Interrupt causes cnt to be incremented by 1
if (cntt0 > 200 ){ //puls scurt
ledb4 =0 ;
}
if (cntt0 >2000){ // de testat valoare pt 1 sec
ledb4 =1 ;
if (sec< 1 && activ ==1){ // scade minut
sec =60;
minute-- ;
if (minute<0){
minute=0 ;
}
}
if (activ ==1){
sec-- ; // scade 1 sec
beep++;
}
cntt0 = 0 ;
}
// TMR0 = 0; // Timer TMR0 is returned its initial value
INTCON = 0xE0; // Bit GIE set; T0IE is set, bit T0IF is cleared
} // end interrupt TMR0 overflow
} // end interrupt
void main() {
unsigned valtimp =1 ; // preset timp citire AN1
unsigned ptact =0 ; // preset timp tact AN4
// unsigned ctemp =0 ; // citire temperatura citire AN2
unsigned rtemp =0 ; // temperatura reala prescalata
unsigned ptemp =0 ; // preset temperatura citire AN0
// unsigned long dpmin,dpmax,dpa;
char dptemp[4], drtemp[4],dmin[4],dsec[4],dptact[4];// text de tiparit
OSCCON = 0X68 ; // PT 4MHz intern
INTCON =0xE0; // enable : GIE ; TMR0 overflow ;
OPTION_REG =0x00; // Prescaler (1/8) is assigned to timer TMR0 ; WPU enable
// si bit 6 in 0 pt RA2 cazator
TRISA = 0xFF; //Ra0-RA5 are IN; RA3 = MCLR
ANSELA = 0x07; // Ra0-RA2 are IN Ana Ra4-RA5 are configured as digital
// CMCON0 = 0x07 ; // Comparatoarele oprite (intrari digitale)
TRISB = 0; // All port B pins are configured as outputs
TRISC = 0x03; // RC0=AN4; RC1 in logic rest pins are configured as outputs
ANSELC = 0x01; // RC0=AN4
WPUA =0x38 ; // set pull up resistor on RA3 ;RA4 si RA5
ADCON1 = 0xD0; // f conv 1/16 pentru; CLK 4MHz ; RIGHT just Vref = Vdd
Lcd_Init(); // Initialize Lcd
Lcd_Cmd(_LCD_CLEAR); // Clear display
Lcd_Cmd(_LCD_CURSOR_OFF); // Cursor off
lcd_out(1,1, "MartinStefanAron");
delay_ms(200);
Utilizarea si programarea microcontrolerelor Pag 57
lcd_out(2,1, " == TERMOSTAT ==");
delay_ms(2000);
Lcd_Cmd(_LCD_CLEAR); // Clear display
lcd_out(2,12, dptact);
lcd_out(2,15, " s");
// sfarsit afisare PULS timp
} // end citiri setari
if (PORTA.F5 ==0 && activ ==0) { // start
Lcd_Cmd(_LCD_CLEAR); // Clear display
activ =1 ;
}
if (PORTA.F4 ==0 && activ ==1) { // stop
buzzer =1;
delay_ms(500);
buzzer =0;
activ =0 ;
}
if (minute==0 && sec==0 && activ ==1 ) {
activ =0 ;
for (i=0;i<3;i++){
buzzer =1;
delay_ms(500);
buzzer =0;
delay_ms(500);
}
}
delay_ms(1);
rtemp = Adc_Read(2);
delay_ms(1);
// ********** prescalare ************
ptemp= (30 + (ptemp/34));
rtemp = (20 +((1050-rtemp)*0.06)); // temp citita pe termistor NTC
// -----------------------------------
lcd_out(2,1,"Tact= " );
bytetostr(rtemp,drtemp);
lcd_out(2,6, drtemp);
bytetostr(minute,dmin);
dmin[(0)]= dmin[(1)] ; // shift text la stanga
dmin[(1)]= dmin[(2)] ; // shift text la stanga
lcd_out(1,15, dmin);
} //end main
Executa masuratori rapide (peste 1000 de evaluari pe secunda) ceea ce permite analiza
pulsurilor pe linii , cum ar fi variatiile pe bus-urile de date (functionare CAN , etc) sau a
pulsurilor de 12V pe echipamentele comandate in factor de umplere (PWM) cum ar fi
reglajul iluminatului de bord , stopuri modulate in intensitate, turatie aeroterme , etc.
Datorita voltmetrului atasat poate citi cu precizie de 0.1V media tensiunii la varful testerului
.
De mentionat ca aparatul are o rezistenta de intrare de 100Kohmi ceea ce confera o buna
stabilitate la paraziti , dar nu afecteaza functionarea corecta a masinii testate , indiferent in
ce loc se efectueaza masuratorile.
De asemeni o calitate deosebita fata de testerele curente, o reprezinta faptul ca acest
aparat face diferenta intre “NIMIC” si “MASA”. Firul liber , desi are tensiune 0 nu este
confirmat de ledul (verde) de stare "0".
Aparatul se bazeaza pe 2 microcontrolere, si anume:
- voltmetrul foloseste PIC 16F1704 si este pornit doar la cerere , (consum
aproximativ 4mA in afisare medie)
- Testerul logic foloseste PIC12F675 ;acesta este alimentat permanent , dar la
zece minute daca nu este folosit intra singur in regim economic de “sleep”,
asteptand comutatorul de mod sa-l readuca la activitate.
// =================================================//
/* "logicTest v5" derivat din tlogig 12f675 v3led3
selectie mod de lucru cu Vminim 0.45 sau 1V pentru beep
are si citire logica cu beep selectat din mod
In analogic cu Vref la Vdd=3V Autooprire daca este nefolosit >10 minute
F1= pullup in ANA; F2=Buzz; F4=L1; F5=L2; F4&F5=L3; F3 =in SW; F0=inANA
4MHz intern 12F675 test go to SLEEP wake up cu GP3
merge numai cu WDT si BOD dezactivat S-A ACTIVAT CODE PROTECT
2.5 Khz Beep cu 2 citiri intre beep-uri aprox 5000 citiri/sec
Used RAM= 18= 38% ; ROM 712 = 70% */
short int cnt =0;
short int msel =0;
short int md =0;
short int vmic =62;
int i =0; //bucla for
long int toff =0;
void beep () {
for (i=0;i<1000;i++) {
GPIO.F2 = 1;
delay_us(100);
GPIO.F2 = 0;
delay_us(400);}}
void sleep (){
beep();delay_ms(300); beep() ; //anunt ca intra in sleep
CMCON =0x07;
VRCON =0x00;
GPIO = 0x00; //stingere iesiri inainte de sleep
Utilizarea si programarea microcontrolerelor Pag 61
INTCON =0x08; // activeaza interrupt "vede tasta "
asm SLEEP;
toff =0;
delay_ms(10); }
void interrupt() {
INTCON =0x00; //dezactivare interrupt
GPIO = 0x00;
delay_ms(100);
cnt =0;
while (GPIO.F3) {
cnt++; // cronometrare timp apasare
delay_ms(100);}
if (cnt >20 ) sleep (); // test apasare lunga pentru sleep
else {
msel++; // la apasare scurta schimba modul (msel) 0;1;2;3;4;
if (msel>=5) msel =0;
}
beep ();
switch (msel) { //afiseaza modul 0.5 sec la pornire sau schimbare
case 0 : GPIO =0x02;md =0;break ;
case 1 : GPIO =0x12;md =1;vmic =40;break ;
case 2 : GPIO =0x12;md =1;vmic =65;
delay_ms(300);GPIO =0x02; beep ();GPIO =0x12;break ;
case 3 : GPIO =0x22;md =2;break ;
case 4 : GPIO =0x32;md =3;break ;
}
delay_ms(500);
GPIO =0x02;
ANSEL =0x11;
CMCON =0x07;
IOC =0x08;
INTCON =0x88; //set GIE =1 ; set GPIE =1; reactivare interrupt
}
void main() {
int vin; //vin definit ca marime intrare
short int vbep =0; //variabila activare beep
//int i =0; // variabila bucla FOR
// int td =2; // td timp de delay
INTCON = 0x88; //set GIE =1 ; set GPIE =1;
IOC =0x08; // set IOC interrupt on change pe GP3
TRISIO = 0x09; // GP0 =AN0 GP3 = IN logic GP 1,2,4, OUT GP5 out Beep
ANSEL =0x11; //oscilator conversie 1/8 (51h pt 1/16) , AN0 intrare
CMCON = 0x07; // comparator oprit
GPIO =0x02 ; // initializarea iesirilor in 0 cu pullup la in ANA
ADCON0 =0x81; // referinta interna si activat ADC
ADCON0 =0x83; // start AD conversion set bit 1
while (1){
vin = Adc_Read(0); // Read AN0 input
if (vin >64 && vin <184) { // intre 1 si 3V ledurile stinse
GPIO =0x02;
toff++ ; if (toff >1790000 ) {sleep();
beep (); // iesire din sleep cu beep si afisarea starii
switch (msel) { //afiseaza modul 0.5 sec la pornire sau schimbare
Utilizarea si programarea microcontrolerelor Pag 62
case 0 : GPIO =0x02;md =0;break ;
case 1 : GPIO =0x12;md =1;vmic =32;break ;
case 2 : GPIO =0x12;md =1;vmic =52;
delay_ms(300);GPIO =0x02; beep ();GPIO =0x12;break ;
case 3 : GPIO =0x22;md =2;break ;
case 4 : GPIO =0x32;md =3;break ;
}
delay_ms(500);
GPIO =0x02;
ANSEL =0x11;
CMCON =0x07;
IOC =0x08;
INTCON =0x88; //set GIE =1 ; set GPIE =1; reactivare interrupt
}} // sa execute subrutina de interrupt} // nefolosit >10 minute
if (vin > 550 ) { // peste 10V aprinde LED 3
if (md==3) // verifica daca trebuie cu Beep
vbep =1; // activeaza beep la sfarsit de bucla for
GPIO =0x32; // Aprinde led 3
toff =0;}
if (vin < vmic){ // pentru Vin < 0.5 sau 1V aprinde led 1
if (md==1) // verifica daca trebuie cu Beep
vbep =1; // activeaza beep la sfarsit de bucla for
toff =0; GPIO =0x12;} // Aprinde led 1
if (vin > 184 && vin < 307){ //intre 3 -5V aprinde Led 2
if (md==2) // verifica daca trebuie cu Beep
vbep =1; // activeaza beep la sfarsit de bucla for
toff =0; GPIO =0x22; }// aprinde led 2
vin = Adc_Read(0); // Read AN0 input
if (vin > 550 ) { // peste 10V aprinde LED 3
if (md==3) // verifica daca trebuie cu Beep
vbep =1; // activeaza beep la sfarsit de bucla for
GPIO =0x32; // Aprinde led 3
toff =0;}
if (vin < vmic){ // pentru Vin < 0.5 sau 1V aprinde led 1
if (md==1) // verifica daca trebuie cu Beep
vbep =1; // activeaza beep la sfarsit de bucla for
toff =0; GPIO =0x12;} // Aprinde led 1
if (vin > 184 && vin < 307){ //intre 3 -5V aprinde Led 2
if (md==2) // verifica daca trebuie cu Beep
vbep =1; // activeaza beep la sfarsit de bucla for
toff =0; GPIO =0x22; }// aprinde led 2
if (vbep ==1) { // verifica daca trebuie cu Beep
GPIO.F2 =1; delay_us(120); GPIO.F2 =0;} //puls pt beep activ 100usec
vbep =0;
} // end while
} // end main
// =================================================//
void Display_Data(){
for (i = 0; i<=10; i++) {
DG2 = 0; // Select sute Digit
DG3 = 1;
DG4 = 1;
if(DD2>63) SEG = 1; //segment g drivered by portA
PORTC = DD2;
delay_ms(5);
PORTC = 0; SEG = 0;
DG2 = 1; // Select Zeci Digit
DG3 = 0;
DG4 = 1;
if(DD3>63) SEG = 1;
PORTC= DD3;
delay_ms(5);
Utilizarea si programarea microcontrolerelor Pag 64
PORTC = 0;SEG = 0;
DG2 = 1; // Select unitati Digit
DG3 = 1;
DG4 = 0;
if(DD4>63) SEG = 1;
PORTC = DD4;
delay_ms(5);
DG4 = 1;
PORTC = 0; SEG = 0;
}
}
unsigned Get_ADC(){
ADCON0.F1 = 1; // start conversion
while (ADCON0.F1); // wait for conversion
return (ADRESH << 8) + ADRESL; // return value
}
void main() {
do {
ADC_Value = Get_ADC();
DisplayVolt = ADC_Value * 98; // modificata scala : max 9.99
DD2 = (DisplayVolt/10000)%10;
DD2 = mask(DD2);
DD3 = (DisplayVolt/1000)%10;
DD3 = mask(DD3);
DD4 = (DisplayVolt/100)%10;
DD4 = mask(DD4);
Display_Data();
} while(1); // end while
} // end main
Desen echipare
Pentru partea de voltmetru , alimentare si sunet desenele sunt prezentate mai jos,
montarea s-a facut intr- carcasa de tip Z-71/B (cod TME).
In orice moment la cerere (prin apasarea Sw de pe cutia cu afisaj numeric) se poate citi tensiunea la
tester. Cu testerul pornit dar varful in gol tensiunea trebuie sa fie de circa 1.2V +- 0.1V.
Cu testerul oprit si varful in gol tensiunea trebuie sa fie de circa 0V .
* Pentru cei ce doresc utilizarea aparatului si la 24V, cu indicare a LED-ului Rosu la peste
22 V (reamintim ca in lipsa acestei modificari aparatul poate fi utilizat la 24V cu indicare
insa a pragului superior la peste 10V) se poate adauga inca un "mod" de lucru suplimentar
switch (msel) unde msel=5 si confirmat la selectie cu 2 Beep ; 2Flash LED rosu .
Prezentam mai jos pozele unui astfel de programator realizat dupa documentatia amintita
si cu modificarile recomandate:
Pentru circuitele SMD de tip SO s-au confectionat 2 ZIF -SO , unul pentru 8-16 pini si
celalalt pentru 18-28 pini folosind conectorii de extensie de pe mother-board -ul unui PC .
Mentionam ca acesti conectori au pasul de 1.27 mm si au contacte aurite de foarte buna
calitate. Prin sectionare si utilizarea corespunzatoare (vezi pozele anexate) , puteti obtine
cu cateva ore de munca si fara investitii 2 socluri de calitate deosebita.
Placa de test (PIC Tester 01) cu Vdd 5V si Va=12v, 16LED-uri, 8 switch-uri si un driver
ULN2003.