Sunteți pe pagina 1din 10

Utilizarea perifericelor cu Microblaze

1. Perifericul de uz general pentru intrari-iesiri (GPIO)

Perifericul de uz general pentru intrari-iesiri (General Purpose Input/Output - GPIO) permite


accesul la pinii de intrare-iesire ai circuitului FPGA prin intermediul magistralei PLB. Folosind acest
periferic se pot defini grupuri de pini de lungime maxima de 32 de biti care sa poata fi cititi sau scrisi
prin intermediul software-ului. Acest periferic poate fi folosit la comanda LED-urilor, respectiv citirea
starii butoanelor sau a switch-urilor. In afara de comanda acestor periferice simple, modulul ofera
posibilitatea emularii din software a unor protocoale complexe, prin accesul direct la pini pe care ıl
ofera.
Perifericul GPIO se poate configura ca avand unul sau doua canale. Fiecare canal poate avea de la
1 la 32 de biti, numarul si directia (pin de intrare sau iesire) fiind configurabile individual pe canal,
respectiv bit. De asemenea, se pot configura valorile la reset pentru fiecare bit din ambele canale. O
alta facilitate oferita de acest periferic este capacitatea de a genera ıntreruperi la schimbarile valorilor
de intrare ale pinilor. Structura interna a perifericului GPIO este prezentata ın Fig.1.
Pentru a folosi portul ca intrare se vor seta toti bitii din registrul GPIO TRI ın 1 pentru a seta bufferul
tri-state ın starea de ınalta impedanta, iar valorile pinilor se vor regasi ın registrul READ REG IN.
In cazul folosirii portului ca unul de iesire bitii din registrul GPIO TRI se vor seta ın valoarea 0,
permitand scrierea datelor din registrul GPIO DATA catre pini. Ca o consecinta, daca portul este
setat ca unul de iesire, datele din READ REG IN vor coincide cu GPIO DATA.

Figura 1
Un exemplu minimal de sistem care foloseste perifericul GPIO pentru a comanda ledurile de
pe placa de dezvoltare este prezentat ın Fig. 2. Acest sistem contine un microprocesor cu memoria
aferenta, iar comunicarea dintre procesor si perifericul GPIO (LEDs 8Bit) este realizata prin
intermediul magistralei PLB.

Figura 2

Perifericul prezinta 4 registri catre exterior, prezentati ın Tabelul 2-1, cate doi pentru fiecare
canal.Registrii interni GPIO DATA si READ REG IN sunt accesati din exterior ca un singur registru,
citirea facandu-se din READ REG IN iar scrierea ın GPIO DATA. Marimea porturilor este de maxim
32 de biti, adica de 4 octeti. Spatiul de adresare este aliniat la 1 octet, motiv pentru care registrii
succesivi ai perifericului sunt aliniati la multipli de 4 octeti. In cazul configurarii perifericului ca avand
un singur canal, citirea registrilor GPIO DATA1 si GPIO TRI1 va avea ıntotdeauna ca rezultat
valoarea 0.

Urmatorul program prezinta un mod simplu de accesare a perifericului GPIO ai carui pini sunt
conectati la ledurile disponibile pe platforma de dezvoltare. Arhitectura minimala a sistemului pe
care ruleaza acest exemplu este cea din Fig.2

#include "xparameters.h"
//xparameters.h contine o serie de constante reprezentand parametri ai sistemului,
//inclusiv adresele de baza ale perifericelor componente
unsigned *led = (unsigned *)XPAR_LEDS_8BIT_BASEADDR; //pointer la adresa de baza

//a perifericului LEDs_8Bit


Un exemplu de citire a datelor de la perifericul GPIO conectat la switch-uri este prezentat mai jos:

#include "xparameters.h"
//pointer la adresa de baza a perifericului LEDs_8Bit:
unsigned *led = (unsigned *)XPAR_LEDS_8BIT_BASEADDR;
//pointer la adresa de baza a perifericului DIP_Switches_4Bit:
unsigned *switchuri = (unsigned *)XPAR_DIP_SWITCHES_4BIT_BASEADDR;
int main(void)
{
while(1)
{
//switchuri[0] contine valoarea switch-urilor
led[0] = switchuri[0]; //citeste starea switch-urilor si o afiseaza pe led-uri
}
return 1;
}

2. Utilizarea intreruperilor

Pentru a facilita lucrul cu ıntreruperile, perifericul GPIO contine hardware dedicat care
detecteaza orice modificare la oricare dintre pinii aferenti unui canal. Configuratia implicita a
perifericului nu ofera aceasta functionalitate, ea trebuind sa fie setata manual de catre utilizator.
Aceasta se realizeaza din System Assembly View printr-un dublu click pe numele perifericului GPIO,
apoi bifarea optiunii GPIO Supports Interrupts ın tab-ul User, meniul Common din fereastra nou
deschisa.
Registrii care controleaza functionarea cu ıntreruperi a GPIO sunt prezentati ın Tabelul 2-2.

In modul Toggle on Write (TOW), scrierea unui bit de 1 produce bascularea valorii bitului de
pe pozitia corespunzatoare din registru, realizandu-se practic o operatie de sau-exclusiv pe biti ıntre
cuvantul stocat ın registru si cuvantul scris. De exemplu, daca un registru contine valoarea 0110 ın
binar si este scris ın mod TOW cu valoarea 0011, rezultatul va fi 0101 (ultimii doi biti ısi schimba
valoarea).
Registrul GIER ofera activarea/dezactivarea primara a iesirii de ıntrerupere a perifericului. Activarea
se face prin setarea ın 1 a bitului 31 (MSB) a GIER (valoare 0 pentru dezactivare).
Activarea ıntreruperilor pentru fiecare canal este realizata cu ajutorul registrului IER, prin setarea ın
1 a bitului 0 (LSB) pentru canalul 1, respectiv a bitului 1 pentru canalul 2. Dezactivarea se realizeaza
prin setarea ın 0 a bitilor corespunzatori. Intreruperile de la pinii de intrare ai GPIO sunt semnalizate
prin intermediul registrului ISR. Valoarea 1 ınscrisa pe pozitia 0 (LSB), respectiv 1 din registru
semnaleaza o ıntrerupere primita pe canalul 1, respectiv 2. Acesti biti pot fi resetati ın modul TOW.
Un exemplu de citire a switchurilor folosind ıntreruperi este prezentat ın codul de mai jos. Programul
presupune ca perifericul GPIO DIP Switches 4Bit este singura sursa de ıntreruperi din sistem, nefiind
necesara adaugarea si configurarea unui controller de ıntreruperi. Pentru o astfel de functionare ın
ıntreruperi, este necesara realizarea unei conexiuni ın tab-ul System Assembly View -> Ports ıntre
portul IP2INTC Irpt al perifericului si portul Interrupt al procesorului MicroBlaze.

#include "xparameters.h"

#include "mb_interface.h"

unsigned *switchuri = (unsigned *)XPAR_DIP_SWITCHES_4BIT_BASEADDR;


unsigned *led = (unsigned *)XPAR_LEDS_8BIT_BASEADDR;
//periferic[adresa/4]
//impartire la 4 pentru ca fiecare registru este pe 32 biti
//iar pointerul la unsigned incrementeaza adresele din 4 in 4

void rutina_intrerupere(void *callBackHandler)


{
led[0]=switchuri[0]; //citeste starea switch-urilor si o afiseaza pe led-uri
//inainte de a iesi din rutina de tratare a intreruperilor se invalideaza
//toate sursele de intreruperi venite de la switchuri (vezi descrierea TOW)
switchuri[0x120/4]=switchuri[0x120/4];
}
int main(void)
{
//1. setarea intreruperilor pentru perifericul GPIO
//adresa Global Interrupt Enable Register = 0x11C
switchuri[0x11C/4] = 0x80000000; //bitul 31 in 1
// adresa IP Interrupt Enable Register = 0x128
switchuri[0x128/4] = 0xFFFFFFFF;
//intreruperea se declanseaza la modificarea oricarui switch
//2. setarea intreruperilor pentru procesorul Microblaze
// se defineste functia de tratare a intreruperilor pentru switchuri
microblaze_register_handler(
(XInterruptHandler)rutina_intrerupere,//pointer la functia de tratare a intreruperii
(void *)0
);
//activeaza intreruperile pentru procesor
microblaze_enable_interrupts();
led[0]=switchuri[0];
while(1);//bucla infinita care va fi intrerupta
return 1;
}

3. Instantierea perifericului

Perifericul GPIO se poate instantia utilizand Base System Builder sau, dupa generarea sistemului,
el poate fi instantiat din tab-ul IP Catalog, optiunea General Purpose IO -> General Purpose IO.
Dupa instantiere:
1. In tab-ul System Assembly View -> Bus Interfaces conectati portul SPLB al perifericului la
magistrala standard a sistemului mb plb.

2. In tab-ul System Assembly View -> Bus Interfaces dati dublu click pe numele perifericului. In
fereastra nou deschisa ın tab-ul User, meniul Channel 1 setati numarul de biti corespunzatori
aplicatiei la optiunea GPIO Data Channel Width.

3. In tab-ul System Assembly View -> Addresses specificati marimea si locatia spatiului de adrese
ın care se va mapa acest periferic sau apasati butonul Generate Addresses si ele vor fi configurate
automat.
4. In tab-ul System Assembly View -> Ports specificati semnalele GPIO IO I ın cazul folosirii
perifericului pentru intrari, respectiv GPIO IO O ın cazul folosirii perifericului pentru iesiri ca
fiind semnale externe.

5. In tab-ul Project deschideti fisierul UCF (dublu click pe data/system.ucf ) si adaugati


constrangerile care mapeaza semnalele externe la pinii FPGA-ului. Mai jos sunt prezentate
constrangerile necesare pentru folosirea butonului rotativ.

Net xps_gpio_0_GPIO_IO_I_pin<0> LOC=K18 | IOSTANDARD = LVCMOS33 | PULLUP; #ROT-A


Net xps_gpio_0_GPIO_IO_I_pin<1> LOC=G18 | IOSTANDARD = LVCMOS33 | PULLUP; #ROT-B

4. Folosirea butonului rotativ cu GPIO

Butonul rotativ codifica miscarea circulara folosind doua semnale A si B generate de doua
butoane. Aceste doua butoane, din constructia interna prezentata ın Fig.3a, sunt actionate de rotirea
unui disc descentrat. Miscarea de rotire cu un pas, ıntr-un sens sau altul, genereaza o succesiune
specifica de stari ale semnalelor A si B prezentate ın Fig.3b. La ınceputul rotirii cu un pas, depinzand
de sensul miscarii, unul din butoane va fi ın pozitie deschisa ınaintea celuilalt. De asemenea, la
sfarsitul efectuarii pasului, primul buton se va ınchide ınaintea celui de-al doilea.

Figura 3

Pentru folosirea butonului rotativ se vor urma pasii descrisi la Instantierea perifericului,
setand numarul de biti necesari, ın acest caz, la 2.

Programul prezentat mai jos realizeaza decodificarea semnalelor. Ideea de baza consta ın
modelarea unui automat de stare prin care se poate detecta parcurgerea succesiunii de semnale aferente
rotirii la dreapta sau la stanga.

unsigned *led = (unsigned *)0x81400000; //pointer la adresa de baza


//a perifericului LEDs_8Bit
unsigned *buton_rot = (unsigned *)0x81420000; //pointer la adresa de baza
//a perifericului But_Rot_2Bit
int main(void)
{
unsigned valoare_led = 0x80; //initializam cu un singur bit in 1
unsigned stare_buton; //starea butonului rotativ
unsigned stare; //starea curenta a automatului
unsigned stare_ant; //starea anterioara a automatului
while(1) //bucla infinita
{
led[0]=valoare_led; //afiseaza valoarea pe leduri
stare_buton=buton_rot[0]; //citeste starea butonului rotativ
//automatul de stare care determina directia rotirii
if(stare_buton==0x3) stare=0;
if(stare_buton==0x1 && stare==0) stare=1;
if(stare_buton==0x0 && stare==1) stare=2;
if(stare_buton==0x2 && stare==2) stare=3;
if(stare_buton==0x2 && stare==0) stare=4;
if(stare_buton==0x0 && stare==4) stare=5;
if(stare_buton==0x1 && stare==5) stare=6;
//rotirea valorii catre dreapta
if(stare==3 && stare_ant==2)
{
if(valoare_led == 1)
valoare_led = 0x80;
else valoare_led = valoare_led>>1;
}
//rotirea valorii catre stanga
if(stare==6 && stare_ant==5)
{
if(valoare_led == 0x80)
valoare_led = 1;
else valoare_led = valoare_led<<1;
}
stare_ant = stare; //starea actuala devine starea anterioara
}
return 1;
}

5. Perifericul timer

Modulul xps timer, care se ataseaza la bus-ul PLB, contine 2 timere, configurabile din punct
de vedere al numarului de biti (ıntre 8-32) si al valorii de la/pana la care numara. Fiecare timer
poate fi configurat pentru generare de semnale/ıntreruperi sau pentru a detecta evenimente (semnale
externe). Daca cele doua timere sunt vazute ca o pereche, se poate obtine o functionare ın mod PWM.
Un exemplu de structura a unui sistem care foloseste acest modul este prezentat ın Fig. 4.
Figura 4

Pentru o mai buna ıntelegere a organizarii si a modului de functionare a perifericului, prezentam


structura sa interna ın Fig.5. Fiecaruia din cele doua timere ıi este aferent cate un counter pe 32 de biti,
cate un registru de ıncarcare (Load Register) si de control/stare (Control/Status). Ambele timere pot
detecta semnale din exterior (CaptureTrig0, CaptureTrig1) si pot genera ın comun un semnal PWM.

Figura 5

6. Organizarea registrilor

Registrii perifericului timer, fiecare a cate 32 de biti, sunt prezentati ın Tabelul 2-22.
Semnificatia bitilor din registrii TCSRx este descrisa ın Tabelul 2-23. Registrul de ıncarcare contine
valoarea cu care se va ıncarca registrul de numarare. Incarcarea se face fie prin scrierea registrului
TCSRx cu bitul LOAD setat ın 1, fie automat la overflow (underflow) daca timerul este ın mod
autoreload.

Registrul de numarare contine valoarea curenta la care a ajuns numaratorul. Acest registru poate
sa fie doar citit, scrierea lui cu o valoare facandu-se prin intermediul registrului de ıncarcare.
7. Modul de generare

Prin setarea unui timer ın modul de generare (bitul MDT din TCSR ın 1) se poate masura timpul,
exprimat ın numar de tacte de ceas. Cel mai des scenariu de utilizare este implementarea unei ıntarzieri
(delay) cu valoare exacta (de exemplu 1 secunda). Terminarea numararii poate fi detectata prin citirea
repetata a registrului de numarare (polling) sau prin generarea unei ıntreruperi de catre periferic.
In functie de setarea bitului UDT din registrul de control/stare, timerul numara crescator sau
descrescator. Este important de stiut ca ın functie de cum este setata directia de numarare, intervalul
de timp va avea valori diferite. Daca timerul este setat sa numere descrescator:

Intervalul timp = (T LRx + 2) × P Lbclockperiod

Daca timerul este setat sa numere crescator:

Intervalul timp = (valoare maxima − T LRx + 2) × P Lbclockperiod

unde

valoare maxima = 2numar biti – 1

iar numarul de biti poate sa fie setat pentru fiecare timer (valoarea implicita este 32). PLBclockperiod
este perioada ceasului magistralei la care este conectat perifericul (ın general 20 ns).
Numararea dureaza pana cand se depaseste valoarea maxima (overflow -in cazul numararii
crescatoare) sau se scade sub 0 (underflow - ın cazul numararii descrescatoare). In functie de cum a
fost setat bitul ARHT, timerul reıncarca valoarea din TLRx si reıncepe numaratoarea, sau se opreste.
Daca timerul este setat sa genereze ıntreruperi, la terminarea numararii se genereaza o ıntrerupere, iar
bitul TINT din registrul TCSRx va fi ın 1 la citirea acestuia. In rutina de tratare a ıntreruperii,
ıntreruperea poate fi invalidata prin scrierea registrului TCSRx cu bitul TINT ın 1.
In cele ce urmeaza este prezentat un exemplu de folosire a perifericului ın mod de generare.

#include "xparameters.h"
unsigned *led=(unsigned *) XPAR_LEDS_8BIT_BASEADDR;
unsigned *timer=(unsigned *) XPAR_XPS_TIMER_0_BASEADDR;
int main(void)
{
unsigned valoare_led=0xFF;
//asignam o valoare registrului de incarcare
timer[0x04/4]=50000000;
while(1)
{
led[0]=valoare_led;
//incarcam registrul numarator prin scrierea bitului LOAD
timer[0]=0x020;
//pornim timerul
//urmatorii biti din registrul de stare/control sunt setati in 1
//bitul 1 (UDT0), timerul numara descrescator
//bitul 7 (ENT0), pentru a activa timerul
timer[0]=0x082;
while(timer[0x08/4]!=-1);
//in cazul numararii descrescatoare timerul se opreste dupa ce scade sub 0
valoare_led=valoare_led^0xFF;
}
return 1;
}

8. Modul de capturare

Prin folosirea acestui mod se poate masura intervalul de timp dintre doua fronturi (crescatoare sau
descrescatoare) ale unui semnal extern. Aceasta valoare este stocata ın registrul TLRx. Terminarea
masurarii este semnalata prin setarea bitului TINT, generandu-se o ıntrerupere (ın cazul ın care sunt
activate ıntreruperile). In acest mod, daca ARHT=0 si TINT=1, valoarea din registrul TLRx nu va fi
suprascrisa de valoarea numaratorului la detectarea unui front al semnalului extern. In toate celelalte
cazuri TLRx va fi suprascris la aparitia unui nou eveniment de captura. Semnalul extern este
esantionat de catre periferic cu frecventa ceasului magistralei PLB. Prin setarea
parametrului TRIGx Active Level (printr-un dublu click pe numele perifericului din System Assembly
View -> Bus Interfaces) se poate selecta tipul frontului care va fi detectat: prin setarea ın 1 se va
detecta un front crescator iar prin setarea ın 0 se va detecta un front descrescator al semnalului extern.
In cele ce urmeaza este prezentat un exemplu de cod C pentru folosirea perifericului ın mod de
capturare.

#include "xparameters.h"
unsigned *timer = (unsigned *)XPAR_XPS_TIMER_0_BASEADDR;
int aux = 0;
int main(void)
{
//configurarea registrului de stare/control
//ENALL activeaza timer-ul 0 si timer-ul 1
//ENT0 activeaza timer-ul 0
//ARTH0 suprascrierea valorii captate
//CAPT0 activeaza semnalul de captura
//MDT0 setarea timer-ului in mod captura
timer[0x00/4] = 0x499;
timer[0x04/4] = 0;
while(1) //bucla infinita
{
if (aux != timer[0x04/4])
{
//cand este apasat butonul "BtnWest" D18
//valoarea din counter este trecuta in registrul de incarcare
xil_printf("Valoarea veche: %d ",aux);
xil_printf("Valoarea noua: %d ",timer[1]);
aux = timer[0x04/4];
}
}
return 1;
}

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