Sunteți pe pagina 1din 94

Universitatea “Petru Maior” – Tg.

Mureş

DUKA Adrian-Vasile RUSU Marius Sebastian

ARHITECTURA CALCULATOARELOR
Îndrumător de laborator

2005
Cuprins:

Prefaţă 1

Cap. 1 – Bazele programării în C 2


Cap. 2 – Funcţii, vectori şi pointeri în C 11
Cap. 3 – LabWindows / CVI – Prezentare generală 21
Cap. 4 – LabWindows / CVI – Crearea şi utilizarea interfeţei grafice 28
Cap. 5 – Simularea circuitelor digitale 37
Cap. 6 – Modalităţi de temporizare 43
Cap. 7 – Portul paralel 51
Cap. 8 – Portul paralel – Modalităţi de citire a datelor 60
Cap. 9 – Portul serial 65
Cap. 10 – Programarea portului serial în LabWindows / CVI 76
Cap. 11 – Comunicaţii în reţea 82

Bibliografie 92
Prefaţă

Lucrarea de faţă se adresează studenţilor din anul III ai secţiilor de Automatică şi


Calculatoare, precum şi celor care doresc să se familiarizeze cu o parte din problemele
specifice Arhitecturii calculatoarelor sau cu mediul de programare LabWindows / CVI.
LabWindows / CVI este un mediu de dezvoltare vizual dedicat aplicaţiilor industriale,
de control, achiziţie de date, automatizări produs de National Instruments Corporation.
Limbajul de programare este compatibil cu standardul ANSI C, aşadar trecerea la
programarea LabWindows / CVI pentru o persoană care este familiarizată cu limbajul C este
foarte facilă.
Lucrarea este structurată pe 11 capitole. Primele două cuprind o sinteză selectivă a
principalelor elemente de programare în C (tipuri de date, instrucţiuni, funcţii etc.). Se
continuă apoi cu prezentarea mediului de dezvoltare LabWindows / CVI şi a modalităţii de
programare în acest mediu (funcţii Callback, programare vizuală). Pe parcursul următoarelor
capitole se tratează modalităţile de temporizare furnizate de mediul de programare C şi cele
prezente în LabWindows CVI, şi se simulează o serie de circuite digitale. Un loc aparte în
cadrul acestei lucrări este alocat studiului portului paralel şi a portului serial. În cadrul
capitolelor dedicate acestora se prezintă modalităţile de interfaţare şi comunicare pe care
cele două porturi le pun la dispoziţie. Ultimul capitol al lucrării se ocupă cu studiul
comunicaţiilor în reţea, în special cu implementarea aplicaţiilor TCP de tip client-server în
Labwindows / CVI.
În fiecare capitol al lucrării se prezintă numeroase linii de cod sursă pentru
exemplificarea diferitelor concepte prezentate.
Autorii îşi exprimă speranţa că această lucrare corespunde exigenţelor actuale care se
impun în pregătirea studenţilor de la secţiile de Automatică şi Calculatoare, şi va putea fi
utilă în pregătirea aplicaţiilor dezvoltate de aceştia.

Autorii

-1-
Cap. 1 - Bazele programării în C

Structura unui program C

În mod normal un program C conţine următoarele elemente


- Comenzi de preprocesor (#include, #define…);
- Declaraţii şi definiţii de tipuri de date;
- Prototipuri de functii - declaraţia tipului funcţiei şi a parametrilor;
- Funcţii (main(), printf, funcţii ale utilizatorului etc.);
- Variabile şi constante;
- Comentarii.

Funcţia main ( )

Fiecare program C trebuie să conţină cel puţin funcţia main( ). Aceasta va fi apelată
automat la executarea programului. Toate instrucţiunile din această funcţie vor fi executate.
Funcţiile vor fi executate doar dacă sunt apelate în interiorul lui main( ).

Variabile şi constante

O variabilă reprezintă o locaţie de memorie căreia i se atribuie un nume. Prin


folosirea numelui variabilei în program se face referirea la data stocată la acea locaţie de
memorie.
C pune la dispoziţia programatorului mai multe tipuri de variabile. Această gamă de
tipuri de variabile este necesară deoarece diferite valori numerice necesită cerinţe diferite de
stocare, iar dificultatea cu care se realizează calculele diferă de asemenea. Numerele mici
(ex. 15, -26, 110) necesită memorie mai puţină pentru stocare, iar calculatorul va putea să
efectueze operaţii matematice (adunări, împărţiri ş.a.) foarte rapid. Prin contrast, numerele
mari (long integer, virgula flotantă) (ex. 156000000 sau 0.0002359774) necesită resurse mai
multe, iar calculele durează mai mult. Prin folosirea tipurilor de date potrivite se asigură o
funcţionare eficientă a programelor create.

C pune furnizează următoarele tipuri de date:

-2-
Tipuri de variabile Cuvânt cheie Dimensiune memorie Limita valorilor interne
Character char 1 octet -128 la 127
2 octeţi – 4octeţi
Integer int -32768 la 32767
depinde de compilator
Short integer short 2 octeţi -32768 la 32767
Long integer long 4 octeţi -2147483648 la 2147483647
Unsigned character unsigned char 1 octet 0 la 255
Unsigned integer unsigned int 2 octeţi 0 la 65535
Unsigned short integer unsigned short 2 octeţi 0 la 65535
Unsigned long integer unsigned long 4 octeţi 0 la 4294967295
Floating-point float 4 octeţi 1.2E-38 la 3.4E38
Double precission
double 8 octeţi 2.2E-308 la 1.8E308
floating-point
Tabel 1.1 – Tipuri de date

Înainte de a putea folosi variabilele într-un program C, acestea trebuie declarate. Prin
declararea unei variabile compilatorul este informat despre numele şi tipul variabilei şi
opţional se iniţializează variabila cu o valoare specifică. Dacă programul încearcă să
folosească o variabilă care nu a fost declarată, compilatorul va genera un mesaj de eroare.
Declararea unei variabile are următoarea formă:

tip_variabilă nume_variabilă;

tip_variabilă specifică tipul variabilei şi trebuie să fie un tip predefinit de compilator


sau un tip definit de utilizator; nume_variabilă reprezintă numele variabilei şi trebuie să
respecte o serie de reguli.
Este permisă declararea mai multor variabile de acelaşi tip pe aceeaşi linie prin
separarea numelor prin virgulă.

Exemplu:
int i;
float a, b, c;
char ch;

Orice variabile declarate în afara funcţiilor sunt variabile globale. Acestea se vor
distruge la terminarea programului şi pot fi utilizate de toate funcţiile, dar vor fi văzute
implicit numai în fişierul în care au fost declarate. Dacă se utilizează variabila globală şi în
alte fişiere care vor fi legate împreună se va declara variabila respectivă şi în acest fişier
utilizând specificatorul de clase de stocare extern.

Exemplu:
extern int i;
float x;
char ch1, ch2;

-3-
main ()
{
}

Variabilele definite în interiorul funcţiilor poartă denumirea de variabile locale, fiind


accesibile doar funcţiei din care fac parte, şi vor fi distruse la terminarea funcţiei.
Utilizând specificatorul de clase de stocare static pentru variabile locale ele vor
deveni permanente. Ele nu vor fi cunoscute în afara funcţiei, dar îşi păstrează valoarea între
două apelări.

Exemplu:
static float x;

Atât variabilele locale cât şi cele globale pot fi iniţializate folosind operatorul =
pentru atribuirea valorii de iniţializare.

Exemplu:
float x = 0.1;
char ch = ‘A’;

Atribuirea valorii iniţiale variabilelor locale statice se va efectua o singură dată, nu de


fiecare dată când se intră în funcţie ca în cazul variabilelor locale obişnuite.

Ca şi în cazul variabilelor, constantele se referă tot la locaţii de memorie folosite de


program. Spre deosebire de variabile, valoarea stocată de o constantă nu poate fi modificată
în timpul execuţiei programului.
Declararea unei constante se face în mod asemănător cu declararea de variabile
folosind cuvântul cheie const, după cum se vede mai jos:

Exemplu:
int const a = 1;
sau
const int a =2;

O altă modalitate utilizată pentru definirea constantelor implică folosirea expresiei


#define:

Exemplu:
#define PI 3.1415
#define PORT 0x378;

-4-
Operatori

Operatorii sunt combinaţii de caractere speciale care specifică modul în care valorile
sunt transformate şi asignate. În C operatorii pot fi calsificaţi în următoarele categorii:
- operatorul de asignare;
- operatori matematici;
- operatori relationali;
- operatori logici;
- operatori la nivel de biţi.

Operatorul de asignare

Operatorul de asignare este reprezentat de semnul (=). În programare utilizarea lui


diferă de cea cunonscută în matematică. Expresia: x = y;dintr-un program C, nu înseamnă "x
este egal cu y", ci se traduce prin “atribuie valoarea lui y variabilei x", iar expresia va avea
valoarea lui x.

Operatori matematici

Pe lângă operatorii matematici clasici (+ - * /), C pune la dispoziţie doi operatori


matematici unari: operatorul de incrementare (++) şi operatorul de decrementare (--), care
sunt mai eficienţi decăt echivalenţii lor. (de exemplu: x++ este mai rapid decât x = x+1)

Operatorii matematici şi utilizarea lor sunt exemplificate în tabelul următor:

Operator Simbol Explicaţie Exemplu


Incrementare ++ Incrementează operandul cu unu ++x, x++
Decrementare -- Decrementează operandul cu unu --x, x--
Adunare + Adună doi operanzi x+y
Scadere - Scade al doilea operand din primul x-z
Înmulţire * Înmulţeşte doi operanzi x*y
Împărţire / Împarte primul operand la al doilea x/y
Modulo % Restul împărţirii primului operand la al doilea x%y
Tabel 1.2 – Operatorii matematici

Operatori relaţionali

Operatorii relaţionali din C se folosesc pentru a compara expresii, prin intermediul


unor întrebări de genul “Este x mai mare ca 10?” sau “Este y egal cu x?”. Expresiile care

-5-
conţin operatori relaţionali sunt evaluate ca fiind adevărate (1) sau false (0). Cei şase
operatori relaţionali din C sunt conţinuţi în tabelul următor.

Operator Simbol Întrebarea pusă Exemplu


Egalitate == Este operandul 1 egal cu operandul 2? x==y
Mai mare Este operandul 1 mai mare decât operandul
> x>y
2?
Mai mic Este operandul 1 mai mic decât operandul
< x<y
2?
Mai mare sau egal Este operandul 1 mai mare sau egal cu
>= x>=y
operandul 2?
Mai mic sau egal Este operandul 1 mai mic sau egal cu
<= x<=y
operandul 2?
Diferit != Este operandul 1 diferit de operandul 2? x!=y
Tabel 1.3 – Operatorii relaţionali

Operatori logici

Există situaţii în care o condiţie nu poate fi exprimată de o singură expresie


relaţională. De exemplu: “Daca x este mai mare decât 10 şi mai mic decât 100, atunci y este
100”. Operatorii logici din C permit combinarea a două sau mai multe expresii relaţionale
într-o singură expresie care se evaluează ca adevărată sau falsă. Cei trei operatori logici din
C sunt prezentaţi în tabelul următor.

Operator Simbol Exemplu


ŞI && Exp1 && Exp2
SAU || Exp1 || Exp2
NU ! !Exp1
Tabel 1.4 – Operatorii logici

Operatori la nivel de biţi

Operatorii la nivel de biţi realizează operaţiile logice: ŞI (&), SAU (|), SAU exclusiv
(^) şi operaţiile de deplasare ale primului operand la stânga (<<) sau la dreapta (>>) cu un
număr de biţi specificaţi de al doilea operand.

Exemplu:
int x = 0xca00;
int y = 0x120f;
int n;
1. n = x & y; //n = x ŞI y
2. n = x | y; // n = x SAU y

-6-
3. n = x ^ y; // n = x SAU exclusiv y
4. n = x << 3; // n = x deplasat la stânga cu 3 biţi
5. n = y >> 4; // n = y deplasat la dreapta cu 3 biţi

Rezultatele asignate lui n în exemplele de mai sus sunt următoarele: 1. n = 0x0200; 2.


n = 0xda0f; 3. n = 0xd80f; 4. n = 0x6000; 5. n = 0x0120.

Controlul execuţiei

Un program C va fi executat secvenţial începând cu prima instrucţiune a funcţiei


main şi continuând instrucţiune cu instrucţiune în ordinea în care acestea au fost scrise.
Această secţiune îşi propune să parcurgă diversele instrucţiuni pe care ANSI C le
pune la dispoziţie pentru controlul ordinii în care se execută un program. La fel ca şi în alte
limbaje de programare aceste instrucţiuni realizează bucle, selectează alte instrucţiuni pentru
a fi executate, transferă controlul programului etc.

Instrucţiunea if

În forma ei de bază, instrucţiunea if evaluează o expresie şi dirijează execuţia


programului în funcţie de rezultatul acestei evaluări. Sintaxa acestei instrucţiuni este
următoarea:

if (expresie)
instrucţiune_1;
else
instrucţiune_2;

Corpul unei instrucţiuni if este executat selectiv, în funcţie de valoarea expresiei.


Dacă expresia este adevărată (diferită de zero) se execută instrucţiune_1. Dacă este falsă se
execută instrucţiune_2 (care urmează cuvântului cheie else). Clauza else este o clauză
opţională. În cazul de mai sus, dacă expresie este evaluată ca fiind falsă se va executa
instrucţiune_2. instrucţiune_1 şi instrucţiune_2 pot fi atât instrucţiuni de sine stătătoare cât
şi blocuri de instrucţiuni (incluse între acolade) care pot conţine la rândul lor instrucţiuni if,
for, while etc.

Exemplu:
if (i<0 && a>0){
x = x + i;
a = a * x;
}
if (i>0)
b = b + i;

-7-
else x = i;

Instrucţiunea switch

Sintaxa:
switch (expresie) {
case element_1:
instrucţiune_1;
break;
case element_2:
instrucţiune_2;
break;
………………………………………………
case element_n:
instrucţiune_n;
break;
default:
instrucţiune;
break;
}

Instrucţiunea switch testează succesiv valoarea unei expresii faţă de o listă de expresii
constatnte (element_i), când ele sunt egale se execută instrucţiunea sau blocul de instrucţiuni
asociat acestei constante.
Instrucţiunea default se execută dacă nici o expresie constantă (element_i) din case-
uri nu este egală cu valoarea expresiei instrucţiunii switch. Instrucţiunea break este necesară
pentru a termina execuţia instrucţiunii switch.

Exemplu:
switch (litera){
case ‘A’:
x++;
break;
case ‘B’:
x--;
break;
default:
y = x;
break;
}

-8-
Instrucţiunea for

Instrucţiunea for permite execuţia unui bloc de una sau mai multe instrucţiuni de un
anumit număr de ori.
O instrucţiune for are următoarea structură:

for (iniţial; condiţie; increment)


instrucţiune;

iniţial, condiţie şi increment sunt toate expresii C, iar instrucţiune se referă la una sau
mai multe instrucţiuni (bloc de instrucţiuni).

Când în execuţia unui program se întâlneşte o instrucţiune for se parcurg următorii paşi:
1. expresia iniţial este evaluată;
2. expresia condiţie este evaluată;
3. dacă expresia condiţie este evaluată ca falsă, atunci instrucţiunea for se termină;
4. dacă expresia condiţie este evaluată ca adevărată, atunci se execută instrucţiunea
instrucţiune;
5. expresia increment este evaluată şi execuţia se reia de la pasul 2.

Exemplu:
for (i = 0; i<20; i++)
printf (“%d \n”, i); //afişează numerele de la 0 la 20

Instrucţiunea while

Sintaxa:

while (expresie)
instrucţiune;

Corpul instrucţiunii while este executat de zero sau mai multe ori, până când expresie
devine falsă. Dacă aceasta este falsă de la început, corpul while nu va fi executat niciodată şi
controlul programului este transferat instrucţiunii următoare din program. Dacă expresie este
adevărată (diferită de zero) se execută instrucţiunile din corpul while. Expresia expresie se
evaluează de fiecare dată înainte de executarea corpului instrucţiunii while. Executarea
corpului instrucţiunii while continuă atâta timp cât expresie este adevarată.

Exemplu:
while(condiţie_1==1 && condiţie_2==0){
printf(“%d”,a);
a++; }

-9-
Instrucţiunea do…while

Bucla do…while execută un bloc de instruţiuni atâta timp cât o condiţie este
adevărată. Instrucţiunea do…while testează condiţia la sfîrşitul buclei şi nu la început cum
se face în cazul instrucţiunilor for sau while.

Sintaxa:
do{
instrucţiuni;
}while(expresie);

Exemplu:
do{
y = f(x);
x--;
}while(x>0);

Temă

Să se realizeze un program în C care determină numerele prime dintr-un interval


specificat.

- 10 -
Cap. 2 – Funcţii, vectori şi pointeri în C

Funcţii

Funcţiile reprezintă un concept de bază al programării în C şi al modului în care sunt


concepute programele în C. O funcţie este o colecţie independentă de declaraţii şi
instrucţiuni concepută pentru a realiza o prelucrare specifică. Un program C are cel puţin o
funcţie main( ) dar poate avea şi alte funcţii.

Vom defini următoarele trei operaţii asupra funcţiilor:


- declaraţia funcţiei;
- definiţia funcţiei;
- apelul funcţiei.

Funcţiile folosite în programele C se împart în două categorii: funcţii predefinite şi


funcţii scrise de utilizator. Funcţiile predefinite sunt stocate în librăriile de funcţii, sunt
declarate în <stdio.h>, <string.h>, <math.h> etc. şi reprezintă esenţa standardului ANSI C.
Acestea acoperă toată funcţionalitatea de bază a programării în C, cum ar fi operaţiile de
intrare / ieşire, funcţiile matematice, lucrul cu fişierele etc.
În continuare vom analiza atât modul de utilizare al funcţiilor predefinite, cât şi
realizarea şi implementarea funcţiilor scrise de utilizator.
Exemplul următor indică modul de utilizare al unei funcţii predefinite (printf):
/* utilizarea unei funcţii predefinite */
#include <stdio.h> //conţine declaraţia funcţiei printf

void main ( )
{
printf(“Hello world!”); //apelul funcţiei printf
}

Este evident că acest program va afişa pe ecran textul “Hello world!”. Declaraţia
funcţiei printf a fost făcută în fişierul stdio.h, inclus la începutul fişierului sursă de directiva
#include. Fiind vorba de utilizarea unei funcţii predefinite, definiţia funcţiei este realizată în
biblioteca standard şi va fi combinată cu codul programului de editorul de legături. Al treilea
element, apelul funcţiei, se execută prin transmiterea unui argument, în cazul de faţă “Hello
world!”, funcţiei predefinite.

- 11 -
Funcţiile scrise de utilizator vor trebui să conţină şi definiţia funcţiei.
La fel ca şi în cazul variabilelor, şi aici înainte de folosirea unei funcţii aceasta trebuie
declarată. Prin declararea unei funcţii, compilatorul C este informat despre tipul pe care îl
returnează funcţia şi tipul parametrilor care trebuie transmişi funcţiei.
Pentru a declara prototipul unei funcţii trebuie specificat tipul returnat de funcţie,
numele funcţiei şi în paranteze lista tipurilor parametrilor care se transmit funcţiei în ordinea
în care apar în definiţia funcţiei.

tip_func nume_funcţie (tip_arg arg_1, tip_arg arg_2, …);

Tipul funcţiei (tip_func) specifică tipul de dată pe care funcţia îl returnează


programului care a apelat-o. Tipul returnat poate fi oricare din tipurile de date ale limbajului
C: char, int, float etc. Se pot de asemenea declara funcţii care nu returnează nici o valoare
(void).
nume_funcţie se referă la numele funcţiei şi trebuie să fie unic. Se recomandă
utilizarea unor nume de funcţii care să reflecte cât mai bine utilitatea funcţiei.
Multe funcţii folosesc argumente (arg_1, arg_2...), care sunt valorile care se transmit
funcţiei în momentul când aceasta este apelată. Pentru o funcţie trebuie să se specifice felul
argumentelor care îi vor fi transmise, şi anume tipul fiecărui argument (tip_arg: char, int,
float etc.)
Se recomandă declararea prototipului funcţiilor la începutul programelor.
În continuare se prezintă câteva exemple de declaraţii de funcţii:

Exemplu:
int func1(int x, float y);
void func2 (char a, int *b);
char func3 ( );

Pentru a defini (scrie) o funcţie se foloseşte următoarea sintaxă:

tip_func nume_funcţie (tip_arg arg_1, tip_arg arg_2, …) //antetul funcţiei


{
instrucţiuni;
return (valoare);
}

Corpul funcţiei este cuprins între acolade şi urmează imediat după antetul funcţiei.
Când o funcţie este apelată execuţia începe cu prima linie din corpul funcţiei şi se termină cu
instrucţiunea return sau când execuţia ajunge la acolada de închidere.
În mod normal corpul funcţiei va cuprinde declaraţii de variabile locale, diverse
instrucţiuni (nu există limitări asupra instrucţiunilor care pot fi conţinute într-o funcţie) şi o
valoare returnată. Când execuţia ajunge la instrucţiunea return expresia este evaluată şi
valoarea este transmisă funcţiei care a apelat funcţia în cauză.

- 12 -
Exemplu:
/*exemplu de definiţie al unei funcţii care realizează
conversia din grade kelvin in grade fahrenheit*/

float Fahrenheit( float Kelvin ) //antetul funcţiei


{
float temp; //variabilă locală
temp = 1.8 *( Kelvin - 273) + 32.0; // conversia
return( temp ); //valoarea returnată
}

Există două modalităţi de a realiza apelul unei funcţii. Orice funcţie poate fi apelată
într-o instrucţiune folosind numele funcţiei şi o lista de argumente ca şi în exemplul urmator.
Dacă funcţia are o valoare pe care o returnează, aceasta va fi ignorată.

nume_funcţie (arg_1, arg_2, …);

A doua modalitate se foloseşte pentru funcţiile care returnează o valoare şi se


realizează în felul următor:

Exemplu:
int valoare;
valoare = nume_funcţie (arg_1, arg_2, …);

În exemplul următor se prezintă modul de realizare şi utilizare a funcţiilor în limbajul C.

Exemplu:

/* exemplu de program funcţional pentru utilizarea


funcţiei Fahrenheit */

#include <stdio.h> /* utilizat pentru funcţia predefinită printf()*/

float Fahrenheit( float Kelvin ); //prototipul funcţiei

main( )
{
float a = 20; //declaraţii de variabile locale
float temp_F;

temp_F = Fahrenheit(a); // apelul funcţiei Fahrenheit

printf("Temp is: %f\n",temp_F); // afişare


}

- 13 -
float Fahrenheit( float Kelvin ) //definiţia funcţiei
{
float temp; //declaraţie de variabilă locală
temp = 1.8 *( Kelvin - 273) + 32.0; //instrucţiunea de conversie
return( temp ); //returnarea rezultatului
}

Vectori

Un vector (array) reprezintă o colecţie de locaţii de memorie având acelaşi tip de date
şi acelaşi nume. Locaţiile individuale de memorie poartă denumirea de elemente ale
vectorului. Motivul pentru care vectorii au fost introduşi în limabajul C este pentru a facilita
manipularea grupurilor mai mari de date.
De exemplu calculul unei medii exprimată matematic sub forma:
x1 + x2 + .... + xn
m=
n
este mult mai costisitor în programare decât calculul aceleiaşi medii exprimată sub forma:
n x
m=Σ i
i =1 n

Când se declară un vector compliatorul rezervă un spaţiu de memorie suficient de


mare pentru a cuprinde întregul vector (x cu n elemente).

De exemplu declaraţia următoare:


int date[500];
rezervă suficientă memorie pentru 500 de membrii ai vectorului numit date. Fiecare element
al acestui vector este de tip int şi poate fi accesat prin folosirea indexului corespunzător,
denumit şi offset. De exemplu dacă dorim să afişăm valoarea stocată de elementul 238 al
vectorului date putem folosi următoarea funcţie:
printf(“%d”, date[237]);

La fel ca şi în cazul variabilelor vectorii pot fi globali (declaraţi în afara funcţiei


main()) sau locali (declaraţi ca membri ai diferitelor funcţii). Când se declară un vector
elementele sale nu sunt iniţializate. Până când nu se atribuie o valoare elementelor
vectorului, acestea vor conţine informaţia reziduală conţinută la adresa de memorie
corespunzătoare.
În C numerotarea elementelor unui vector începe întotdeauna cu valoarea 0, iar
vectorul conţine atâtea elemente câte sunt specificate în declaraţia sa. Să considerăm
următoarea declaraţie pentru vectorul numit a, care constă din trei elemente:
int a[3];
Primul element: a[0]

- 14 -
Al doilea element: a[1]
Al treilea element: a[2]

În încheierea acestei secţiuni amintim pe scurt şi despre tablouri (multi-dimensional


arrays). În C o asemenea variabilă se declară în felul următor:

float tablou[nr_coloane][nr_linii];

Pointeri

Un pointer este o variabilă care conţine adresa din memorie a unei alte variabile.
Pointerii sunt foarte folosiţi în C pe de o parte datorită faptului că există situaţii în care ei
prezintă singura modalitate de a efectua o serie de operaţii, iar pe de altă parte întrucât
folosirea acestora duce la o formă mult mai compactă şi mai eficientă a codului rezultat.

În utilizarea poiterilor se întâlnesc doi operatori importanţi:


- Operatorul de adresă ‘&’, care furnizează adresa unei variabile. Astfel expresia
următoare:
p = &c
asignează adresa lui c variabilei p. Se spune că p indică spre c.
- Operatorul de indirectare ‘*’, care furnizează conţinutul unui obiect spre care indica
pointerul.

Să presupunem că x şi y sunt două variabile de tip int, iar ip este un pointer spre o
variabilă de tip int. Secvenţa din exemplul următor arată cum se declară un pointer şi cum se
folosesc cei doi operatori amintiţi mai sus.

Exemplu:
int x = 1, y = 2;
int *ip; /* ip este un pointer spre int */
ip = &x; /* ip conţine adresa lui x */
y = *ip; /* y este acum 1 */
x = ip; /* valoarea lui x este acum adresa lui x */
*ip = 3; /* x este acum 3 */

Pentru o cât mai bună înţelegere a modului în care funcţionează pointerii merită să
analizăm ce se întâmplă la nivel de maşină în memorie. Pentru exemplul anterior, în figura
următoare, să presupunem că variabila x se află la locaţia de memorie 100, y la 200, iar ip la
1000.

Notă: un pointer este şi el o variabilă şi de aceea valorile sale trebuie si ele stocate
undeva în memorie.

- 15 -
Figura 2.1 – Explicativă pentru pointeri

Pentru exemplul de mai sus, atribuirile x = 1 şi y = 2 în mod evident încarcă valorile


respective în variabile. ip este declarat ca fiind un pointer spre un integer şi îi este atribuită
adresa lui x (&x). Astfel în ip se încarcă valoarea 100.
În continuare lui y i se asignează conţinutul lui ip. În exemplul nostru, în acest
moment ip indică spre locaţia 100 (locaţia lui x). Deci lui y i se atribuie valoarea lui x, care
este 1. Urmează să se atribuie valoare lui ip lui x. Valoarea lui ip în acest moment este 100.
În cele din urmă atribuim o valoare conţinutului pointerului (*ip).

Pointeri şi funcţii

În continuare vom examina pe scurt relaţia dintre pointeri şi funcţiile C.


Deoarece în C argumentele funcţiilor se transmit prin valoare, nu există o modalitate
directă pentru funcţia apelată de a modifica o variabilă în funcţia apelantă. De exemplu, o
rutină de sortare ar putea avea nevoie să interschimbe argumentele prin intermediul unei
funcţii denumită swap.
Apelul swap(a, b) nu este util, funcţia swap fiind definită în felul următor:

void swap(int x, int y) /* GREŞIT */


{
int temp;
temp = x;
x = y;
y = temp;
}

- 16 -
Datorită apelului prin valoare, funcţia swap nu afectează în nici o măsură argumentele
a şi b din rutina care a apelat această funcţie.
Efectul dorit se obţine dacă rutina apelantă transmite pointeri spre valori ca
argumente funcţiei: swap(&a, &b). Deoarece operatorul & produce adresa unei variabile, &a
este un pointer spre a. În funcţia swap parametrii sunt declaraţi ca pointeri, şi operanzii sunt
accesaţi indirect prin intermediul lor.

void swap(int *px, int *py) /* interchange *px and *py */


{
int temp;
temp = *px;
*px = *py;
*py = temp;
}

Argumentele de tip pointer permit funcţiilor să acceseze şi să modifice obiecte în


funcţiile care le-au apelat.

Pointeri şi vectori

Pointerii sunt folositori atunci când se lucrează cu variabile simple, dar sunt mult mai
utili când se lucrează cu vectori (arrays). Pentru exemplificarea relaţiei dintre pointeri şi
vectori să considerăm următorul exemplu:

int a[10], x;
int *pa;
pa = &a[0]; /* pa pointer spre adresa elementului a[0] */
x = *pa; /* x = conţinutul lui pa (a[0] în acest caz) */

Figura 2.2 – Relaţia dintre pointeri şi vectori

După cum menţionam şi în secţiunea dedicată vectorilor, elementele unui vector


ocupă locaţii consecutive de memorie. Astfel pentru a ne “deplasa” într-un vector (Figura
2.2) folosind pointerii am putea folosi operaţia: pa + i care ar fi echivalentă cu a[i].
ANSI C în schimb este mult mai subtil în ceea ce priveşte legătura dintre pointeri şi
vectori. Putem folosi pa = a în loc de pa = &a[0], iar a[i] poate fi scris ca *(a+i).
Cu toate acestea este o diferenţă importantă între un vector şi un pointer, şi anume
aceea că un pointer este o variabilă, deci operaţiile de genul pa = a sau pa++ sunt corecte,
dar un vector nu este o variabilă, iar operaţiile de tipul a = pa şi a++ sunt incorecte.

- 17 -
Intrare şi ieşire

În majoritatea programelor pe care le veţi crea veţi fi nevoiţi să afişaţi informaţii pe


ecran sau să citiţi informaţii de la tastatură. Această secţiune îşi propune să analizeze două
dintre cele mai des folosite funcţii din C care permit realizarea operaţiilor de intrare şi
ieşire.

Ieşirea formatată. Funcţia printf

Funcţia de ieşire printf transformă valorile interne în caractere. Această funcţie a fost
folosită şi până acum pentru exemplele prezentate. În continuare această funcţie va fi tratată
mai detaliat.
Funcţia printf este definită după cum urmează:

int printf(char *format, arg1, arg2, ...);

şi converteşte, formatează şi afişează argumentele sale la ieşirea standard (stdout - ecranul)


în concordanţă cu string-ul de formatare format. Această funcţie returnează numărul de
caractere afişate.
Stringul de formatare se referă la două categorii de obiecte:
- Caractere obişnuite, care sunt copiate direct la ieşire;
- Specificatori de conversie (tabelul 1) notaţi prin % şi care determină conversia şi
afişarea argumentelor funcţiei.

Specificatori
Tipul Rezultat
format (%)
d, i int Număr zecimal
o int Număr în octal
x, X int Număr în hexazecimal (0, 1 …d, e, f) sau (0, 1 … D, E, F)
u int Număr natural
c char Caracter
s char * String de caractere
f double [-]m.dddddd unde d se referă la precizie
e, E double [-]m.dddddde+/-xx sau [-]m.ddddddE+/-xx
g, G double Varianta mai compactă dintre %e sau %f
% Afişează caracterul %
Tabel 2.1 - Formatarea

Între % şi caracterul de formatare putem folosi următoarele:


- semnul minus: aranjare la stânga a argumentului convertit;
- un număr care specifică lăţimea câmpului;
- un punct care separă lăţimea câmpului de precizie;

- 18 -
- un număr care specifică precizia – numărul maxim de caractere care să fie afişate
dintr-un string sau numărul de zecimale după virgulă.

În continuare sunt prezentate câteva exemple de utilizare a funcţiei printf:

Exemplu:
//afişează pe un rând nou ‘TVA = 19%’
printf (“\nTVA = 19%%”);

// afişează ‘Valoarea lui x este 5’


int x=5;
printf (“Valoarea lui x este %d”, x);

//afişează ‘Hello World!’


char *str;
str = “Hello World !”
printf (“\t%s”,str);

//afişează 17.235 pe rând nou


printf(“\n %-2.3f”,17.23478);

Intrarea formatată. Funcţia scanf

Funcţia scanf este funcţia analoagă lui printf pentru intrare, oferind aceleaşi facilităţi
de conversie, dar în cealaltă “direcţie”.
Funcţia scanf este definită după cum urmează:

int scanf(char *format, ...)

şi citeşte caractere de la intrarea standard (stdin - tastatura), pe care le interpretează în


funcţie de stringul de formatare format. Rezultatele sunt stocate la adresele transmise prin
argumentele funcţiei.
Stringul de formatare conţine specificaţiile de conversie care sunt folosite pentru
controlul conversiei intrării şi este foarte asemănător cu cel folosit la funcţia printf. Celelalte
argumente, care trebuie să fie pointeri, indică locul unde intrarea convertită corespunzătoare
trebuie să fie stocată.
Funcţia scanf se opreşte când stringul de formatare a epuizat sau când intrările nu se
mai potrivesc cu specificaţiile de control. Valoarea returnată reprezintă numărul intrărilor
potrivite şi atribuite care au reuşit să fie citite.

Notă: funcţia scanf ia ca parametru de intrare adresa unei variabile sau pointer spre o
adresă, după cum se poate vedea mai jos:

- 19 -
scanf(“%d”,&i);

Exemplu:
/* program de exemplificare a utilizării funcţiilor de intrare/ieşire formatată
suma a două numere introduse de la tastatură */

#include <stdio.h>

void main()
{
float x, y, suma;
printf(“introduceti doua numere pentru a calcula suma lor”);
scanf(“%d”,&x);
scanf(“%d”,&y);
suma = x +y;
printf(“suma = %d”,suma);
}

Temă

Să se realizeze un program în C care compară trei algoritmi de sortare din punct de


vedere al timpului de rulare. Fiecare algoritm va fi implementat într-o funcţie proprie care
primeşte ca argument un tablou şi returnează timpul de rulare.

- 20 -
Cap. 3 - LabWindows / CVI – Prezentare generală

LabWindows / CVI este un mediu de dezvoltare vizual dedicat aplicaţiilor industriale,


de control, achiziţie de date, automatizări produs de National Instruments Corporation. Site-
ul web al companiei este http://www.ni.com.
Limbajul de programare este compatibil cu standardul ANSI C, aşadar trecerea la
programarea LabWindows / CVI pentru o persoană care este familiarizată cu limbajul C este
foarte facilă.
Componentele LabWindows / CVI sunt:
- editoarele pentru interfaţa grafică, codul sursă, panouri de funcţii, etc.
- set de librării pentru cele mai utilizate funcţii şi standarde;
- drivere pentru diferite instrumente;
- mediul integrat de gestiune a proiectelor, editare, compilare şi depanare a codului sursă.
În cele ce urmează vom detalia aceste componente ale mediului, prezentând
caracteristicile lor de bază.

Setul de editoare

LabWindows / CVI dispune de un set de editoare care facilitează lucrul cu


componentele unui proiect.

Editorul de cod sursă

Fiind un limbaj de programare construit în jurul standardului ANSI C, elementul de


bază al scrierii programelor este codul sursă. Acesta poate fi editat foarte elegant prin
intermediul editorului de cod.
Trebuie menţionat că acest editor, pe lângă funcţiile de bază ale oricărui editor de
text, este sensibil la sintaxă (syntax coloring), cuvintele cu semnificaţii speciale fiind afişate
în culori diferite, fiind astfel mult mai uşor de urmărit. În versiunea predefinită, cuvintele
cheie de limbaj sunt afişate în albastru, directivele de preprocesare în roz, comentariile în
verde, etc. Bineînţeles, aceste culori, tipul de font, aranjamentul pe toolbar şi încă alte
opţiuni pot fi configurate după dorinţă prin intermediul meniului Options.
Funcţiile standard ale editorului de cod sursă includ: operaţiunile cu fişierul, meniul
File (Open, Close, Save, Print), operaţiuni de editare standard, meniul Edit (Copy, Paste,
Undo, Redo, Fiind, Replace).

- 21 -
Figura 3.1 – Editorul de cod sursă

Mai sunt accesibile opţiuni de inserare automată a structurilor de bază C (structuri


decizionale, structuri repetitive) în Insert Construct (meniul Edit).
De asemenea, meniul Windows pune la dispoziţia programatorului posibilitatea
rescalării şi rearanjării ferestrelor.

Editorul pentru interfaţa grafică

Acest editor uşurează enorm munca de dezvoltare a aplicaţiei, fiind responsabil cu


desenarea interfeţei grafice. Astfel, programatorul LabWindows / CVI este scutit de dificila
sarcină de a construi interfaţa grafică a aplicaţiei prin intermediul codului sursă.
La fel ca la editorul de cod sursă, editorul pentru interfaţa grafică prezintă aceleaşi
operaţiuni de bază cu fişierele, de editare şi cu ferestrele.
Pe lângă acestea se adaugă funcţiile specifice de creare şi aranjare a elementelor
interfeţei grafice.
Crearea elementelor interfeţei grafice se realizează prin intermediul meniului Create.
Acest meniu poate fi accesat atât prin intermediul barei de meniuri, cât şi prin intermediul
meniului flotant.
Pentru aranjarea elementelor interfeţei grafice meniul Arrange pune la dispoziţie
funcţii de poziţionare relativă a elementelor, precum şi de distribuire a lor pe interfaţă.

- 22 -
Figura 3.2 – Editorul pentru interfaţa grafică

Elementul primar al interfeţei grafice este panoul (Panel). Acesta este echivalentul
LabWindows / CVI al ferestrei de aplicaţie Windows.
Fiind destinat aplicaţiilor tehnice, LabWindows / CVI pune la dispoziţia
programatorului un set de controale grafice foarte expresive de tipul bazinelor, butoanelor
rotative, osciloscoape etc. Acestea sunt împărţite în următoarele categorii:
- Controale numerice: permit introducerea de valori numerice (edit box numeric,
slider, buton rotativ, etc.);
- Controale text: permit introducerea şi afişarea valorilor text (string, text box);
- Butoane cu revenire: pentru apelarea anumitor funcţii ale aplicaţiei (command
button);
- Butoane cu memorare: pentru alegerea unor opţiuni binare (toggle button);
- LED-uri: pentru semnalizarea unor evenimente binare (LED);
- Întrerupător binar: pentru acţionări binare (binary switch);
- Controale de selecţie: pentru selectarea anumitor valori dintr-o mulţime (ring);
- Lista: permite selectarea unui element dintr-o listă (listbox);
- Decoraţii: nu sunt controale de interacţiune propriu-zisă cu programul, ci au doar un
caracter artistic (decorations);
- Grafic: permit afişare informaţiei sub formă grafică, cronologică (graph);
- Imagine: pentru afişarea unei imagini (picture)
- Temporizator: pentru semnalizarea scurgerii unui interval fixat de timp (timer);
- Suprafaţa de afişare: permite afişarea informaţiei în format propriu aplicaţiei
(canvas);

O descriere mai amănunţită a acestor elemente de interfaţă este dată în help-ul


LabWindows / CVI.

- 23 -
Editorul de panouri de funcţii

Acesta permite crearea panourilor ce asigură o apelare, testare uşoară a funcţiilor din
codul sursă. Odată documentată această parte a unui instrument, programatorul poate insera
uşor funcţiile în codul propriu.

Figura 3.3 – Editorul de panouri de funcţii

Acest editor permite construcţia interfeţei funcţiei prin precizarea parametrilor, a


tipului acestora, a valorii returnate. Toate aceste aspecte pot fi bine documentate prin
folosirea editorului de help al funcţiei.
De asemenea, în cadrul editorului de panou de funcţii mai este posibilă declararea
variabilelor globale necesare în cadrul funcţiei.

Editorul de help

Acest editor este destinat documentării instrumentelor, funcţiilor şi parametrilor


funcţiilor. Este foarte important de tratat această problemă a documentării interfeţelor pentru
a ajuta programatorul în folosirea codului.

- 24 -
Setul de librării

Pentru uşurarea dezvoltării aplicaţiilor industriale, LabWindows / CVI include un set


de librării de funcţii de bază.
Astfel, întâlnim următoarele librării:
- User Interface: crearea şi administrarea interfeţei grafice a aplicaţiei;
- Advanced Analysis: prelucrare numerică de semnal (DSP), algebră vectorială şi
matricială, operaţii cu numere complexe, statistică, interpolare, curbe, etc.
- Easy I/O for DAQ: operaţii de bază pentru achiziţia de date: intrare / ieşire
analogică, intrare / ieşire digitală, numărătoare, temporizatoare, etc.
- Data Acquisition: achiziţie de date;
- VXI: interacţiunea cu echipamente compatibile VXI, standard industrial dezvoltat de
National Instruments (VME eXtension for Instrumentation);
- GPIB / GPIB 488.2: operarea cu protocolul de magistrală GPIB (General Purpose
Interface Bus) (IEEE 488 şi IEEE 488.2);
- RS-232: operarea cu portul serial RS-232, precum şi cu modem-urile seriale;
- VISA: interfaţă unică de programarea pentru standarde de genul GPIB, VXI, RS-232,
sau alte astfel de standarde (Virtual Instrument Software Architecture);
- TCP: setul de funcţii pentru construirea unui client / server în arhitectura TCP-IP,
pentru realizarea aplicaţiilor de reţea şi distribuite;
- DDE: setul de funcţii pentru utilizarea DDE în cadrul aplicaţiei (Dynamic Data
Exchange);
- Formating and I/O: set de funcţii pentru formatarea informaţiei, a fişierelor, şirurilor
de caractere, a datelor generice;
- Utility: set de funcţii pentru lucrul cu diverse resurse cum ar fi întreruperile, tastatura,
fişierele, porturile de intrare / ieşire, accesul la memoria fizică, etc.
- ANSI C: set de funcţii diverse standardizate în ANSI C: matematice, şiruri de
caractere, dată / oră, etc.

Mediul integrat de dezvoltare

LabWindows / CVI integrează în editoarele descrise mai sus compilatorul C,


editorul de legături (link-editor) şi depanatorul (debugger).
Prezente în toate ferestrele de editare în bara de meniuri, opţiunile de compilare,
executare şi depanare oferă programatorului toată gama de ustensile necesare dezvoltării
aplicaţiilor industriale.
Prin intermediul depanatorului, se pot stabili punctele de oprire (breakpoints), se
poate rula programul funcţie cu funcţie (Step Over, Step Into), se pot urmări valorile
variabilelor (watches), se poate vizualiza memoria dinamică, stiva de apel de funcţii, etc.
Toate aceste funcţii sunt accesibile prin meniul Run.
În timpul depanării programului, variabilele urmărite sunt afişate atât ca valoare
curentă cât şi ca tip şi mod de definiţie (static, etc.).

- 25 -
Figura 3.4 – Vizualizarea variabilelor

Meniul Build pune la dispoziţie operaţiile de compilare, construire şi editare de


legături, precum şi posibilităţile de configurare a acestora.
Gestiunea proiectului se realizează din fereastra de proiect, având acces la includerea,
excluderea fişierelor din proiect. Tot în editorul de proiect avem la îndemână informaţii
referitoare la fişierele incluse: dacă este deschis în fereastra de editare, dacă nu este compilat
(C), dacă este modificat, dar nu este salvat (S), dacă reprezintă un driver de instrument.

Figura 3.5 – Editorul de proiect

Tot din editorul de proiect pot fi setate configuraţiile generare a codului (executabil,
bibliotecă cu legare dinamică, bibliotecă cu legare statică). Mediul LabWindows / CVI pune

- 26 -
la dispoziţie şi posibilitatea de a crea un kit de distribuţie al aplicaţiei. Acest kit va include
automat toate librăriile dinamice CVI necesare aplicaţiei.

Temă

Studiaţi mediul LabWindows / CVI, în special a editoarelor incluse. Să se realizeze


un program minimal în care să vă reamintiţi noţiunile de bază ale limbajului ANSI C.

- 27 -
Cap. 4 - LabWindows/CVI – Crearea şi utilizarea
interfeţei grafice

Mediul LabWindows / CVI vine în ajutorul programatorului cu un model foarte


simplu de gestiune a interfeţei grafice. Vom înţelege prin interfaţa grafică toate elementele
informaţionale afişate pe ecran pentru notificarea utilizatorului asupra stării de funcţionare a
programului.
După cum am văzut în paginile anterioare, mediul LabWindows / CVI pune la
dispoziţia utilizatorului un editor de interfaţă grafică foarte intuitiv şi care salvează foarte
mult timp preţios în implementarea programului.
Trebuie menţionat că interfaţa grafică poate fi construită şi direct din codul sursă,
având la dispoziţie o serie de funcţii dedicate. Totuşi, nu vom intra în amănunte, editorul
grafic fiind suficient de complex pentru cerinţele acestei lucrări. Cititorul interesat este
invitat să studieze documentaţia mediului LabWindows / CVI.
Toate elementele de interfaţă grafică pe care le vom discuta în continuare pot fi create
şi aranjate cu editorul grafic.

Noţiuni generale

Toate elementele interfeţei grafice sunt construite ca nişte resurse şi salvate în fişierul
cu extensia .uir ataşat. Elementele care pot alcătui interfaţa grafică (panourile, meniurile,
butoanele, afişajele, etc.) sunt entităţi logice cu anumite atribute sau proprietăţi. Spre
exemplu, un buton are un titlu, dimensiuni, culoare, etc. Toate acestea sunt atribute ale
butonului. Cele mai importante atribute ale unui control se pot configura prin intermediul
dialogului de editare a atributelor. De asemenea, atributele pot fi configurate direct din codul
sursă. A se vedea subcapitolul Controale.
Pentru a putea asigura conexiunea logică dintre resursele din fişier şi partea de cod C
a programului, se atribuie fiecărui element de interfaţă un identificator unic şi, opţional, o
funcţie ce va fi apelată în cazul producerii unui eveniment legat de elementul respectiv după
mecanismul de tip Callback. Aceste două elemente de conexiune între interfaţă şi codul C
sunt configurabile prin intermediul dialogului de editare a atributelor elementului de
interfaţă la secţiunea Source Code Connection. Identificatorul unic se va genera pe baza
câmpului Constant Name, iar numele funcţiei Callback va fi precizat în câmpul Callback
function. Definirea constantei şi prototipul funcţiei Callback sunt automat inserate în fişierul
header (.h) ce poartă acelaşi nume cu fişierul în care este salvată interfaţa grafică (.uir).
Atenţie, este recomandat să nu se modifice aceste inregistrări din acest fişier, întrucât ele
asigură legătura între resurse şi codul C.

- 28 -
O observaţie foarte importantă: numele simbolic al identificatorului unui control se
obţine din numele simbolic al panoului în care se află controlul, caracterul underscore şi
numele constantei precizat la control. Spre exemplu, un element cu constanta BUTTON aflat
în panoul cu constanta PANEL va avea asociat identificatorul unic PANEL_BUTTON.

Figura 4.1 – Dialogul de editare a atributelor

Mecanismul Callback este similar celui din Windows. Aplicaţia propriuzisă


reprezintă o buclă de aşteptare, care va primi mesaje de la sistemul de operare. Mesajele vor
fi stocate în coada de aşteptare şi vor fi prelucrate în ordinea în care au sosit. Orice
eveniment în sistemul de operare, sau eveniment asociat unui obiect din interfaţa grafică va
genera un mesaj iar bucla de prelucrare va apela funcţia Callback asociată, dacă aceasta
există. Toate evenimentele ataşate unui obiect din interfaţă vor produce aceeaşi funcţie, iar
în corpul acestei funcţii trebuie analizat evenimentul şi în funcţie de acesta se va acţiona
după dorinţă. La finalul capitolului este dat tabelul de evenimente pentru interfaţa grafică.
Spre exemplu, codul asociat butonului de ieşire din program poate arăta în felul
următor:

int CVICALLBACK OnExitButton (int panel, int control, int event,


void *callbackData, int eventData1, int eventData2)
{
switch (event) {
case EVENT_COMMIT:
QuitUserInterface (0);
break;
}
return 0;
}

- 29 -
Elementele grafice pot fi controlate cu ajutorul funcţiilor declarate în userint.h.
Trebuie menţionat că toate funcţiile returnează în caz de eroare un cod de eroare negativ.

Elementul fundamental al interfeţei grafice va fi aşa-numitul panou. Odată construit


panoul, acesta poate găzdui un număr mare de controale.

Panoul

Aşa cum s-a menţionat, panoul este elementul fundamental al unei interfeţe grafice
LabWindows / CVI. Acesta poate fi considerat corespondentul ferestrei de aplicaţie
Windows. Aşadar, el reprezintă suportul controalelor care vor fi adăugate ulterior pe
interfaţă. O aplicaţie poate avea mai multe panouri, care pot fi încărcate simultan sau
interactiv, fiecare panou va avea un fisier de resurse ataşat.

Figura 4.2 - Panoul

După ce este creat cu ajutorul editorului grafic, panoul poate fi configurat în fereastra
de editare a atributelor (figura 4.3).
În secţiunea Panel Settings se pot configura diversele atribute de afişare ale panoului
dintre care: titlul ferestrei, bara de meniu, controlul de ieşire, coordonatele ferestrei, barele
de derulare, etc.
Secţiunea Attributes for Child Panels se referă la setările ce vor fi aplicate în cazul
folosirii unor panouri de tip copil.
Secţiunea Source Code Connection găzduieşte cele 2 setări esenţiale pentru legarea
panoului creat de programul propriu-zis. Astfel, aici se setează numele constantei care va

- 30 -
desemna panoul în codul C pe care îl vom scrie (Constant Name) şi numele funcţiei care va
fi apelată la fiecare eveniment creat de panoul (Callback Function).

Figura 4.3 – Atributele panoului

În program, panoul poate fi încărcat cu funcţia LoadPanel() al cărei prototip este


următorul:

int LoadPanel (int parentPanelHandle, char filename[], int panelResourceID);

unde:
- parentPanelHandle este panoul părinte, dacă panoul se leagă de un alt panou
(părinte), sau 0 dacă nu există un panou părinte;
- filename este numele fişierului de resurse;
- panelResourceID este identificatorul panoului care va fi încărcat;

Această funcţie returnează un handle pentru panoul încărcat, sau un număr negativ în
caz de eroare.
După ce a fost încărcat, panoul trebuie afişat folosind funcţia DisplayPanel():

int DisplayPanel (int panelHandle);

Bineînteles, panelHandle reprezintă handle-ul panoului care trebuie afişat. Dacă


funcţia nu se execută corect, se returnează un cod de eroare negativ.
În fine, la sfârşitul executării programului sau în alt moment în care se doreşte acest
lucru, panoul poate fi distrus (şters de pe ecran şi dezalocat din memorie) cu ajutorul funcţiei
DiscardPanel():

- 31 -
int DiscardPanel (int panelHandle);

La fel ca la DisplayPanel(), panelHandle reprezintă handle-ul panoului care trebuie


distrus

Controalele

Câteva exemple de elemente de interfaţă ar fi: butoanele cu revenire (command


buttons), butoanele cu menţinerea stării (toggle buttons), afişaje numerice de diferite forme
şi dimensiuni, afişaje grafice, afişaje de şiruri de caractere, afişaje şi întrerupătoare binare,
liste, imagini, etc.
Controalele sunt elementele grafice cele mai spectaculoase ale interfeţei grafice
construite cu LabWindows / CVI. Acest lucru se datorează şi multitudinii de atribute care
pot fi setate. Fiecare control are setări specifice pe care invităm cititorul să le descopere
singur prin intermediul help-ului LabWindows / CVI.
Pentru a avea acces din codul sursă la aceste atribute avem la dispoziţie două funcţii:

int GetCtrlAttribute (int panelHandle, int controlID, int controlAttribute,


void *attributeValue);

int SetCtrlAttribute (int panelHandle, int controlID, int controlAttribute, ...);

unde:
- panelHandle este handle-ul panoului în care se află controlul;
- controlID este identificatorul unic al controlului;
- controlAttribute este atributul vizat;
- attributeValue este variabila în care se va citi sau din care se va scrie valoarea
atributului.
Atenţie la identificatorul unic.
Valoarea de utilitate efectivă a controlului poate fi accesată prin intermediul
funcţiilor:

int GetCtrlVal (int panelHandle, int controlID, void *value);

int SetCtrlVal (int panelHandle, int controlID, ...);

unde:
- panelHandle este handle-ul panoului în care se află controlul;
- controlID este identificatorul unic al controlului;
- value este variabila în care se va citi sau din care se va scrie valoarea atributului.

- 32 -
Meniul

Pentru a construi un meniu care se va ataşa unui panou avem la dispoziţie editorul de
meniuri care funcţionează foarte intuitiv şi permite declararea unui meniu complex, cu
submeniuri. Poate fi accesat prin intermediul editorului de interfaţă, meniul Create, opţiunea
Menu Bar. Mai târziu meniul creat poate fi modificat tot prin intermediul editorului grafic,
meniul Edit, opţiunea Menu Bars.

Figura 4.4 – Editorul de meniu

La fel ca şi în cazul controalelor, fiecare element al meniului are asignat un


identificator unic (Constant Name) şi o funcţie Callback (Callback Function). Aşadar, este
foarte simplu de implementat câte o funcţie pentru fiecare dintre opţiunile meniului.

Generarea codului

După „desenarea” interfeţei grafice se poate genera scheletul programului bazat pe


informaţiile introduse până acum. Pentru a realiza aceast lucru, în fereastra editorului grafic,
din meniul Code se alege Generate, respectiv All Code.
Un exemplu de program generat este cel de mai jos:

Exemplu:
#include <cvirte.h> /* Needed if linking in external compiler; harmless otherwise */
#include <userint.h>
#include "SimpleProject.h"

- 33 -
static int panelHandle;

int main (int argc, char *argv[])


{
if (InitCVIRTE (0, argv, 0) == 0) /* Needed if linking in external compiler;
harmless otherwise */
return -1; /* out of memory */
if ((panelHandle = LoadPanel (0, "SimpleProject.uir", PANEL)) < 0)
return -1;
DisplayPanel (panelHandle);
RunUserInterface ();
return 0;
}

int CVICALLBACK OnExitButton (int panel, int control, int event,


void *callbackData, int eventData1, int eventData2)
{
switch (event) {
case EVENT_COMMIT:
QuitUserInterface (0);
break;
}
return 0;
}

int CVICALLBACK OnMaxLevel (int panel, int control, int event,


void *callbackData, int eventData1, int eventData2)
{
switch (event) {
case EVENT_COMMIT:

break;
}
return 0;
}

int CVICALLBACK OnOnOff (int panel, int control, int event,


void *callbackData, int eventData1, int eventData2)
{
switch (event) {
case EVENT_COMMIT:

break;
}
return 0;
}

- 34 -
int CVICALLBACK OnTimer (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
switch (event) {
case EVENT_TIMER_TICK:

break;
}
return 0;
}

Dacă după generarea codului complet se mai adaugă elemente grafice şi se doreşte
generarea codului pentru noile elemente, se poate face acest lucru tot prin intermediul
meniului Code, submeniul Generate. Din acest submeniu se vor alege tipurile de Callback
care se vor genera.
Generatorul de cod poate genera următoarele elemente în program:
- funcţia main() cu încărcarea panoului;
- structura funcţiilor Callback;
- evenimentele ce vor fi incluse în funcţii;
- funcţia de ieşire.

Evenimentele
Tabelul de mai jos conţine evenimentele care vor fi generate pentru controale.

Tipul obiectului Identificatorul evenimentului Observaţii


Controale şi meniuri EVENT_COMMIT Activarea unui control
Controale EVENT_VAL_CHANGED Modificarea unei valori
EVENT LEFT CLICK Click buton stânga
EVENT LEFT DOUBLE CLI Click dublu buton stânga
EVENT RIGHT CLICK Click buton dreapta
Controale şi panouri EVENT RIGHT DOUBLE CL Click dublu buton dreapta
EVENT KEYPRESSED Apăsarea unei taste
EVENT GOT FOCUS Selectarea unui control
EVENT LOST FOCUS Părăsirea controlului selectat
EVENT_DISCARD Renunţare modificare
Timer EVENT_TIMER_TICK Expirarea unei temporizări
Panouri EVENT CLOSE Închiderea panoului
EVENT_PANEL_SIZE Modificarea dimensiunii panoului
Tabel 4.1 – Evenimente

- 35 -
Temă

Să se realizeze un program în care să studiaţi construcţia interfeţei grafice şi legarea


acesteia cu codul sursă C. De asemenea, realizaţi modificări de atribute şi valori ale
controalelor la apariţia unor evenimente alese de dvs.

- 36 -
Cap. 5 - Simularea circuitelor digitale

Scopul acestei lucrări îl reprezintă înţelegerea modului de efectuare a operaţiilor


binare şi simularea unor circuite digitale care implementează funcţii combinaţionale.
Circuitele care vor fi simulate sunt: decodificatorul BCD / zecimal cu 4 intrări şi 10 ieşiri şi
multiplexorul cu 3 intrări de selecţie şi 8 intrări de date.
Întrucât în acest mediu de programare accesul la valoarea unui singur bit nu se poate
face direct, decât prin intermediul unui octet (byte) care îl conţine, este important de analizat
modul în care se realizează testarea, setarea unui bit sau a unui grup de biţi din cadrul unui
octet pe 1 sau pe 0. La fel de importantă este şi cunoaşterea modului în care se face
deplasarea biţilor spre stânga sau spre dreapta (a se vedea capitolul 1 al acestei lucrări).
În C (LabWindows) tipurile de date cu dimensiunea de un octet sunt:
- char: cu domeniul -128 la 127 (10000000 la 01111111)
- unsigned char: cu domeniul 0 la 255 (00000000 la 11111111)

Testarea unui bit se realizează prin mascarea restului. Pentru testarea bitului 3 se
foloseşte funcţia ŞI între valoarea citită şi o mască avînd un singur bit pe 1 pe poziţia testată.
Valoarea rezultată va fi diferită de 0 dacă valoarea bitului este 1.
xxxxxxxx valoare citită
ŞI
00001000 mască
0000x000 rezultat

Pentru poziţionarea unei linii de ieşire pe 0, fără a modifica restul, se foloseşte funcţia
ŞI între valoarea curentă a ieşirii şi un modificator. Modificatorul va avea toţi biţii pe 1 mai
puţin bitul ce urmează a fi resetat.
xxxxxxxx valoare curentă
ŞI
11111011 modificator
xxxxx0xx ieşire

Pentru a seta o linie de ieşire se utilizează funcţia SAU între valoarea curentă şi
modificatorul avînd toţi biţii pe 0 în afară de cel setat.
xxxxxxxx valoarea curentă
SAU
00100000 modificator
xx1xxxxx ieşire

- 37 -
Simularea unui decodificator binar

Funcţionarea unui decodificator BCD / zecimal este prezentată mai jos prin
intermediul tabelului de adevăr. Se poate remarca faptul că ieşirile sunt active pe 0 logic, iar
pentru intrări invalide ieşirile rămân pe 1.

Intrări BCD Ieşiri zecimale


Nr.
D C B A D0 D1 D2 D3 D4 D5 D6 D7 D8 D9
0 0 0 0 0 0 1 1 1 1 1 1 1 1 1
1 0 0 0 1 1 0 1 1 1 1 1 1 1 1
2 0 0 1 0 1 1 0 1 1 1 1 1 1 1
... ... ...........
8 1 0 0 0 1 1 1 1 1 1 1 1 0 1
9 1 0 0 1 1 1 1 1 1 1 1 1 1 0
1 0 1 0 1 1 1 1 1 1 1 1 1 1
1 0 1 1 1 1 1 1 1 1 1 1 1 1
Invalid

1 1 0 0 1 1 1 1 1 1 1 1 1 1
1 1 0 1 1 1 1 1 1 1 1 1 1 1
1 1 1 0 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1
Tabel 5.1 – Decodificator BCD/zecimal

Pentru citirea intrărilor se va folosi un control numeric care permite şi vizualizarea


datelor sub formă binară. Funcţia care se va utiliza pentru citirea valorii introduse este
funcţia Get Control Value:

int status = GetCtrlVal (int panelHandle, int controlID, void *value);

care se poate găsi în Library Æ User Interface Library Æ Controls/Graphs/Strip Charts Æ


General Functions

Figura 5.1 – Alegerea controlului numeric

- 38 -
Figura 5.2 – Modificarea proprietaţilor pentru controlul numeric

Vom considera cei 4 biţi (D B C A) care reprezintă intrarea decodificatorului biţii 5,


4, 3 şi 2 ai octetului (unsigned char) de intrare.

x x D B C A x x
Figura 5.3 – Octetul de intrare

Obţinerea valorii de intrare pentru decodificator se face introducând de la tastatură în


controlul numeric un octet (unsigned char) în domeniul 0 la 255 şi mascând (masca =
00111100) biţii neutilizaţi, valoarea obţinută astfel utilizându-se pentru a determina poziţia
bitului de ieşire care va fi setat.
Pentru punerea bitului de ieşire corespunzător pe 0 se deplasează un 1 cu n poziţii la
stânga, n fiind egal cu intrarea decodificatorului determinată anterior, iar apoi se realizează
un SAU exclisv cu valoarea 1023 = (1111111111)2. Valoarea obţinută se afişează într-un
control numeric cu proprietăţi asemănătoare cu ale primului folosind funcţia Set Control
Value

int status = SetCtrlVal (int panelHandle, int controlID, void *value);

care se poate găsi în Library Æ User Interface Library Æ Controls/Graphs/Strip Charts Æ


General Functions.

- 39 -
Decodificarea va fi declanşată de apăsarea unui buton (Command Button), deci
pentru acest buton va trebui sa avem o funcţie Callback.
Codul ataşat acestei funcţii Callback este prezentat generic mai jos:

Exemplu:
masca = 60; // (00111100)2 = (60)10
GetCtrVal(... intrare ...);
n = ( intrare & masca ) >>2;
iesire =1023 ^ ( 1<<n);
SetCtrVal(... iesire ...);

În continuare, se recomandă afişarea rezultatului decodificării pe un set de 10 LED-


uri conţinute pe interfaţa realizată.

Figura 5.4 – Sugestie de interfaţă

În exemplul prezentat în figura de mai sus se poate remarca cu uşurinţă, analizând


octetul de intrare, faptul că DBCA = (1010)2 = (6)10, ceea ce duce la setarea LED-ului
numărul 6 şi la o valoare de ieşire egală cu (1111011111)2.

Simularea unui multiplexor

Funcţionarea multiplexorului cu 3 intrări de selecţie, 8 intrări de date şi o ieşire este


prezentată mai jos prin intermediul tabelului de adevăr.

W = ABC D0 + ABCD1 + ABC D1 + ... + ABC D6 + ABCD7

- 40 -
A B C W
0 0 0 D0
0 0 1 D1
0 1 0 D2
……… …
1 1 1 D7

Tabel 5.2 - Multiplexor

La fel ca şi în cazul anterior pentru octetul care reprezintă intrările de date şi pentru
cel care reprezintă intrările de selecţie se va folosi câte un control numeric care ne permite
vizualizarea sub formă binară. Proprietăţile pentru aceste controale numerice vor fi
asemănătoare cu cele folosite la exemplul anterior. Valorile care vor fi utilizate se vor
iniţializa aleator în domeniul 0 la 255.
Vom considera cei 3 biţi (A B C) care reprezintă intrările de selecţie ale
multiplexorului biţii 5, 3 şi 1 ai unui octet.

x x A x B x C x
Figura 5.5 – Octetul de intrare

Funcţia care se va utiliza pentru citirea valorilor introduse pentru intările de date şi
pentru intrările de selecţie este functia Get Control Value:

int status = GetCtrlVal (int panelHandle, int controlID, void *value);

care se poate găsi în Library Æ User Interface Library Æ Controls/Graphs/Strip Charts Æ


General Functions

Starea ieşirii ( W ) va fi semnalizată prin intermediul unui LED pe interfaţa realizată.

Pentru obţinerea ieşirii multiplexorului se citesc intrările de selecţie, se maschează


biţii neutilizaţi şi se efectuează operaţiile logice şi de deplasare pe bit corespunzătoare pentru
a obţine o mască pentru intrările de date.

Declanşarea operaţiei de multiplexare va fi făcută prin intermediul unui buton de


comandă. Codul sursă ataşat funcţiei Callback pentru butonul respectiv este prezentat mai
jos.

Exemplu:
unsigned char sel_byte, data_byte, iesire;
unsigned char masca1, masca2, masca3;
unsigned char a_byte, b_byte, c_byte;

- 41 -
masca1 = 2; // (00000010)2 = (2)10
masca2 = 8; // (00001000)2 = (8)10
masca3 = 32; // (00100000)2 = (32)10

c_byte = (sel_byte & masca1)>>1;


b_byte = (sel_byte & masca2)>>2;
a_byte = (sel_byte & masca3)>>3;

sel_byte = a_byte | b_byte | c_byte;

iesire = data_byte & (1<<sel_byte);


iesire = iesire>>sel_byte;
SetCtrVal(... iesire ...);

Figura 5.6 – Sugestie de interfaţă

În exemplul din figura de mai sus, putem remarca faptul că ABC = (101)2 =(5)10, ceea
ce duce la selectarea bitului D5 al octetului de intrare, care în cazul de faţă fiind 1 va seta
LED-ul utilizat pentru indicarea ieşirii multiplexorului

Temă

Să se realizeze în LabWindows / CVI câte un program care simulează circuitele


digitale prezentate mai sus.

- 42 -
Cap. 6 - Modalităţi de temporizare

Unele sisteme, procese sau algoritmi necesită să fie anunţaţi în cazul scurgerii unui
interval de timp precizat. Spre exemplu, pentru a realiza un ceas cu alarmă, este nevoie de o
posibilitate de a primi un semnal în momentul în care trebuie pornită alarma; un alt exemplu,
pentru a citi valoarea unui senzor la fiecare 2 secunde, trebuie să existe o modalitate prin
care suntem informaţi că s-au scurs cele 2 secunde.
Există multe modalităţi de a temporiza un sistem, dar noi ne vom concentra atenţia
asupra celor realizabile cu un calculator personal.

Timer-ul programabil

În calculatoarele PC (şi nu numai) există un circuit de temporizare 8253 (respectiv,


8254) care realizează divizarea frecvenţei de bază a sistemului. Acesta conţine trei
numărătoare programabile de 16 biţi. La circuitul 8254 stările numărătoarelor şi a ieşirilor
pot fi stocate şi citite. Frecvenţa de intrare este de 1,19 MHz şi va fi divizată pe cele 3
canale.
Astfel, canalul 0 furnizează frecvenţa de 18,2 Hz ce va fi folosită în temporizările
sistemului de operare. Această frecvenţă generează întreruperea hardware IRQ 0, cu vectorul
de întrerupere 08h (0x08), care apelează la rândul ei întreruperea software 1Ch (0x1C).
Canalul 1 generează un semnal cu perioada de 15 μsec. Acest semnal este utilizat la
împrospătarea memoriilor dinamice.
Canalul 2 este cel ce comandă difuzorul.
Canalele sunt programate în modul 3, adică vor diviza frecvenţa cu valoarea
prestabilită prin numărare în jos. Astfel, când numărătorul ajunge la 0, valoarea prestabilită
se va reîncărca automat.
Pentru citirea sau programarea timer-ului se va folosi portul 40h pentru canalul 0,
respectiv 42h pentru canalul 2. Octetul ce va fi trimis portului are structura:

Canal selectat Comandă scriere/citire Mod de lucru 1 = BCD

Comenzile sunt:
00 – citire;
01 – numai octetul cel mai semnificativ (MSB);
10 – numai octetul cel mai puţin semnificativ (LSB);
11 – LSB urmat de MSB.

- 43 -
Modurile de lucru sunt:
000 – Mod 0;
001 – Mod 1;
X01 – Mod 2;
X11 – Mod 3;
100 – Mod 4;
101 – Mod 5.

Dacă aplicaţia are nevoie de o precizie ridicată se va reprograma canalul 0 şi se va


intercepta întreruperea 08h, fără a uita să se apeleze vechea întrerupere la fiecare 55 msec
pentru a nu perturba actualizarea corectă a ceasului sistem.
Spre exemplu, dacă se doreşte generarea unui sunet de o anumită frecvenţă, se va
programa corespunzător canalul 2, se va valida canalul prin bitul 0 al portului 61h şi se va
activa difuzorul prin bitul 1 al portului 61h. Modificarea periodică a bitului de validare a
canalului conduce la generarea unui sunet.

Ceasul de timp real (RTC)

Ceasul de timp real (Real Timer Clock) reprezintă o altă modalitate de temporizare
în sistemele PC. Acesta este construit în jurul circuitului integrat MC146818 ce conţine un
oscilator, divizoare de frecvenţă şi o memorie CMOS de câţiva zeci de octeţi. Ceasul este
alimentat de la un acumulator, astfel funcţionând şi după întreruperea alimentării sistemului.
Ceasul de timp real generează întreruperea hardware IRQ 8 cu vectorul de întrerupere
70h. Pentru a accesa memoria CMOS se va scrie adresa locaţiei de memorie la adresa 70h, şi
se va citi sau scrie informaţia prin portul 71h.
Structura memoriei CMOS a ceasului de timp real este dată în tabelul de mai jos:

Adresă Conţinut
0 Secunde
1 Secunde alarmă
2 Minute
3 Minute alarmă
4 Ore (în modul 12 ore, bitul 7 indică post-meridian)
5 Ore alarmă (în modul 12 ore, bitul 7 indică post-meridian)
6 Ziua săptămânii
7 Ziua
8 Luna
9 Anul
A Registrul de stare A
B Registrul de control B
C Registrul de stare C
Tabel 6.1 – Memoria RTC

- 44 -
Registrul de stare A are structura următoare:
Bitul 7 – reîmprospătarea ceasului în curs
Bitul 6 – dezactivarea ceasului
Biţii 4-5 – valoarea divizorului
00 – 400000h
01 – 100000h
10 – 8000h
Biţii 0-3 – rata de întrerupere
0000 dezact. 1000 128
0001 128 1001 256
0010 256 1010 512
0011 4 1011 1024
0100 8 1100 2048
0101 16 1101 4096
0110 32 1110 8192
0111 64 1111 16384

Registru de comandă B are structura:


Bitul 7 – validarea modificării ceasului
Bitul 6 – validarea întreruperii periodice
Bitul 5 – validarea întreruperii pentru alarmă
Bitul 4 – validarea întreruperii la terminarea reîmprospătării ceasului (1 sec)
Bitul 3 – generarea impulsurilor dreptunghiulare
Bitul 2 – formatul datelor (0 = BCD, 1 = binar)
Bitul 1 – formatul orelor (0 = 24 ore, 1 = 12 ore)
Bitul 0 – schimbarea orei de vară

Registrul de stare C poate fi doar citit şi prezintă starea întreruperilor:


Bitul 7 – cerere de întrerupere
Bitul 6 – întrerupere periodică
Bitul 5 – alarmă
Bitul 4 – întrerupere la terminarea reîmprospătării
Restul – neutilizaţi

Dacă a fost setată alarma şi validată întreruperea corespunzătoare, în momentul în


care ora curentă va fi egală cu cea de alarmare se generează întreruperea hardware 70h care
va apela întreruperea software 4Ah. Aceasta poate fi interceptată de utilizator şi se va putea
trata corespunzător. Pentru a primi această întrerupere, trebuie verificat dacă întreruperea
IRQ 8 este activată în registrul mască al controllerului de întreruperi.
O altă modalitate de programare a ceasului de timp real este posibilă prin întreruperea
soft 1Ah. Selectarea subfuncţiilor se face prin înscrierea în registrul AH a valorii dorite.

- 45 -
Semnificaţia subfuncţiilor este:
0 - Citeşte valoarea curentă a numărătorului de la ultima resetare, fiind incrementat la
fiecare 55 msec. Valoarea returnată va fi în regiştrii CX şi DX;
1 - Setează numărătorul cu valoarea dată în regiştrii CX şi DX;
2 - Citeşte ceasul din CMOS în format BCD. Registrul CH va conţine orele, DH
minutele, DL secundele;
3 - Setează ceasul din CMOS cu valorile regiştrilor CH, DH, DL;
4 - Citeşte data din CMOS în format BCD. Registrul CX va conţine anul, DH luna, DL
ziua;
5 - Setează ceasul din CMOS cu valorile regiştrilor CX, DH, DL;
6 - Setează alarma la valoarea dată de regiştrii CH, CL, DH. La un moment dat poate fi
activă o singură alarmă. Dacă o alarmă care nu a fost deservită este încă activă, mai
întâi se va şterge această alarmă şi apoi va fi reprogramată
7 - Şterge alarma precedentă
În cazul în care oricare dintre funcţii nu a fost efectuată corect se va seta flag-ul CARRY.

Timer-ele în LabWindows / CVI

Dacă până acum am discutat despre temporizări ce implică programarea efectivă a


echipamentelor electronice corespunzătoare, în acest subcapitol vom aborda perspectiva
LabWindows / CVI asupra temporizărilor.
În LabWindows / CVI temporizările sunt posibile prin folosirea obiectului abstract
denumit Timer. Acesta poate fi adăugat prin intermediul editorului de interfaţă grafică, fiind
de altfel aproape identic cu un element de interfaţă grafică. Trebuie menţionat că timer-ul nu
va fi afişat pe interfaţă în timpul rulării programului. El va funcţiona „în spate”, efectele lui
făcându-se simţite indirect.

Figura 6.1 – Timer-ul

Prin intermediul editorului se pot configura atributele timer-ului. Spre exemplu,


intervalul la care se va genera semnalul de temporizare se introduce în câmpul Interval
(seconds). Trebuie menţionat că acest interval se măsoară în secunde şi are tipul double.

- 46 -
Figura 6.2 – Atributele timer-ului

Principiul de funcţionare al timer-ului este foarte simplu, el fiind aproape identic cu


cel al unui element de interfaţă grafică. Altfel spus, obiectul timer generează periodic un
semnal care se transmite funcţiei Callback asociate. Funcţia Callback asociată se specifică în
câmpul Callback Function şi se generează automat ca orice funcţie Callback de interfaţă.
La scurgerea intervalului setat se apelează funcţia Callback şi i se transmite parametrul
eveniment EVENT_TIMER_TICK.
Aşadar, în switch-ul de tratare al evenimentelor vom completa codul dorit pentru
mesajul EVENT_TIMER_TICK. Spre exemplu, dacă vrem să incrementăm o variabilă x din
100 în 100 de milisecunde, completăm intervalul timer-ului cu valoarea 0.1 şi scriem funcţia
callback sub forma:
int CVICALLBACK OnTimer (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
switch (event) {
case EVENT_TIMER_TICK:
x++;
break;
}
return 0;
}

- 47 -
Un timer poate fi activat sau dezactivat prin setarea, respectiv resetarea atributului
ATTR_ENABLED.
De asemenea, din codul sursă C se poate modifica perioada unui timer prin
modificarea atributului ATTR_INTERVAL. Acest atribut este dat în secunde şi este de tip
double. Înainte de modificarea valorii atributului de interval se recomandă dezactivarea
timer-ului, iar după modificare reactivarea lui.
O altă funcţie aplicabilă timer-ului este ResetTimer():

int ResetTimer (int panelHandle, int controlID);

Aceasta realizează resetarea timer-ului specificat de constanta controlID. Operaţia de


resetare se referă la reînceperea numărării intervalului timer-ului. Spre exemplu, un timer
setat să semnaleze scurgerea a x secunde, şi care este resetat după y secunde de la ultima
semnalizare va mai semnaliza doar peste x secunde, adică momentul actual devine
momentul din care se contorizează cele x secunde, următoarea semnalizare apărând în
realitate după x+y secunde de la ultima.
Trebuie reţinut că, în caz de nevoie, se pot folosi mai multe timer-e (timer-e
multiple). Pentru această se pot „desena” în editorul grafic timer-ele de care avem nevoie.
Controlul simultan al tuturor timer-elor se poate realiza cu ajutorul funcţiilor:

int SuspendTimerCallbacks (void);

int ResumeTimerCallbacks (void);

Funcţia SuspendTimerCallbacks(), aşa cum îi spune şi numele, realizează


suspendarea funcţiilor Callback. Aceasta nu înseamnă că timer-ele nu mai sunt active, ci
doar că funcţiile asignate nu mai sunt apelate, altfel spus sunt inhibate. Activarea funcţiilor
Callback se face prin funcţia ResumeTimerCallbacks(). De menţionat că cele două funcţii
se aplică la toate timer-ele.
După cum am arătat anterior se pot folosi mai multe timer-e în aceeaşi aplicaţie.
Totuşi, utilizarea timer-elor multiple ridică unele probleme care trebuie avute în vedere.
La folosirea timer-elor multiple, fiecare timer are propriul interval şi propria funcţie
Callback care se va executa independent de celelalte. Acest mecanism funcţionează fără
probleme, atâta timp cât timer-ele nu trebuie să fie în fază, adică să apeleze funcţiile
Callback simultan. Trebuie reţinut că oricum apar unele „încetiniri” ale timer-elor datorită
unei conjuncturi de evenimente (întârzieri cauzate de sistemul de operare, alte programe,
etc.), chiar şi de faptul că se execută o altă funcţie Callback. Spre exemplu, într-o aplicaţie
de control, un timer poate realiza citirea unui senzor la intervale fixate, pe când un alt timer
se ocupă de comanda unui element de execuţie.
Dacă este nevoie de temporizări multiple sincronizate, se recomandă folosirea
divizării unui singur timer. Această tehnică foloseşte un singur timer al cărui interval este în
aşa fel ales încât temporizările care vor avea la bază acest timer să fie multipli ai acestui
interval. Se vor contoriza tacturile pentru fiecare temporizare în parte şi la scurgerea fiecărui

- 48 -
interval se vor executa operaţiile corespunzătoare. Spre exemplu, dacă trebuie realizat un
ceas special care să afişeze sutimile de secundă şi la fiecare secundă să emită un semnal
sonor se va face astfel: se realizează un timer de 0.1 secunde; la fiecare apel al funcţiei
Callback se va afişa sutimea de secundă şi se va decrementa o variabilă iniţializată cu 9. De
asemenea, se va verifica de fiecare dată dacă variabila decrementată este 0, şi în caz
afirmativ se va emite un sunet şi se va reiniţializa variabila cu 9.
Un alt aspect important al proiectării aplicaţiilor cu timer-e LabWindows / CVI este
acela referitor la valoarea perioada minimă a timer-ului pentru a garanta funcţionarea
corectă. Cu alte cuvinte, deşi teoretic este posibil, în practică nu se recomandă folosirea
perioadelor foarte mici. Dacă intervalul de timp este foarte mic (apropiat de 0), calculatorul
va dedica, aproape în întregime, procesorul tratării rutinei de timer. În acest caz, nu mai
rămâne timp pentru procesarea celorlalte aplicaţii, chiar nici pentru celelalte elemente ale
aplicaţiei ce conţine timer-ul vinovat, fiind astfel posibil să nu se mai poată opri acest timer.
Într-o astfel de situaţie există o mare probabilitate ca sistemul să se „blocheze”, eveniment
cu consecinţe imprevizibile, ducând chiar la necesitatea repornirii calculatorului.
De asemenea, în cazul folosirii timer-elor cu perioadă foarte mică, precizia
temporizării scade semnificativ, datorită atât intervenţiei celorlalte aplicaţii, cât şi propriei
aplicaţii.
Un alt element ce trebuie avut în vedere este durata execuţiei funcţiei Callback a
timer-ului. Dacă această durată este comparabilă sau mai mare decât perioada timer-ului,
funcţia nu va putea fi apelată cu frecvenţa dorită.
Aşadar, o aplicaţie care necesită temporizări trebuie analizată şi proiectată cu foarte
mare atenţie.

Alte modalităţi de temporizare

În cazul în care se doreşte suspendarea execuţiei programului (sau a thread-ului


curent) pe o durată cunoscută se poate apela funcţia delay().
În LabWindows / CVI ea are forma:
void Delay (double seconds);

iar în Borland C:
void delay (unsigned milliseconds);

După cum se poate observa în varianta LabWindows / CVI parametrul de tip double
reprezintă secunde, iar în varianta Borland C parametru de tip unsigned reprezintă
milisecunde.

- 49 -
Temă

Realizaţi un program LabWindows / CVI care aprinde succesiv 8 LED-uri virtuale şi


foloseşte un timer. Se va putea modifica perioada timer-ului şi se va putea porni / opri timer-
ul prin intermediul interfeţei grafice.

- 50 -
Cap. 7 - Portul paralel

Una dintre cele mai simple modalităţi de interfaţare a unui calculator personal cu un
dispozitiv periferic implică utilizarea portului paralel. Denumirea de port paralel vine de la
modalitatea de transmitere a datelor.
Portul paralel a fost până nu demult folosit cel mai intens pentru conectarea
imprimantelor, motiv pentru care, după cum se va vedea în continuare; multe specificaţii
tehnice au fost descrise în vederea conectării acestui tip de periferic.

Descrierea portului

Standardizat în anul 1994 sub codul IEEE 1284, portul paralel poate funcţiona în 5
moduri:
1. Modul de compatibilitate (Compatibility Mode)
2. Modul nibble (Nibble Mode)
3. Modul octet (Byte Mode)
4. Modul EPP (Enhanced Parallel Port)
5. Modul ECP (Extended Capabilities Port)
Primele 3 moduri de lucru reprezintă elementele funcţionării SPP (Standard Parallel
Port). Modurile EPP şi ECP, fiind introduse ulterior, prezintă unele modificări hardware
pentru viteze ridicate, dar suportă şi modul SPP. Majoritatea implemetărilor portului paralel
prezente pe piaţă în prezent suportă toate cele 3 moduri.
Lucrarea de faţă se va concentra asupra funcţionării SPP.

Specificaţii mecanice

Standardul IEEE 1284 specifică 3 tipuri de conectori mecanici:


- 1284 Type A este un conector D cu 25 pini. Acesta se foloseşte preponderent pe partea
PC a conexiunii.
- 1284 Type B este denumit şi conector Centronics şi prezintă 34 pini. Acest tip de
conector se foloseşte preponderent pe partea de imprimantă a conexiunii.
- 1284 Type C este o versiune mai mică a conectorului Centronics, cu performanţe
îmbunătăţite şi cu 2 pini suplimentari pentru verificarea prezenţei tensiunii de alimentare
la perifericul conectat

- 51 -
Pin (D- Pin Direcţie Inversat
Semnal SPP Registru
Type 25) (Centronics) In/Out hardware
1 1 nStrobe In/Out Control Da
2 2 Data 0 Out Date
3 3 Data 1 Out Date
4 4 Data 2 Out Date
5 5 Data 3 Out Date
6 6 Data 4 Out Date
7 7 Data 5 Out Date
8 8 Data 6 Out Date
9 9 Data 7 Out Date
10 10 nAck In Stare
11 11 Busy In Stare Da
Paper-Out /
12 12 In Stare
Paper-End
13 13 Select In Stare
nAuto-
14 14 In/Out Control Da
Linefeed
nError /
15 32 In Stare
nFault
16 31 nInitialize In/Out Control
nSelect-
17 36 Printer / In/Out Control Da
nSelect-In
18 - 25 19-30 Ground Gnd
Tabel 7.1 – Semnificaţiile pinilor

În tabelul anterior, semnalele precedate de „n” sunt active pe nivelul low. Semnalele
care sunt indicate ca „Inversat hardware” reflectă în regiştri inversul valorii reale la pin.
Ieşirea portului paralel este pe nivel TTL. Majoritatea implementărilor portului paralel oferă
un curent de ieşire sau de intrare de maxim 12mA. Se recomandă folosirea unor circuite de
putere pentru a asigura un curent mai mare dacă este necesar şi pentru a proteja portul
paralel.

Figura 7.1 - Tipurile de conectori IEEE 1284

- 52 -
Adresele portului paralel

La PC, portul paralel are 3 adrese folosite în mod uzual. Acestea sunt date în tabelul
de mai jos, la arhitecturile noi ele pot fi setate din BIOS.

Domeniul de adrese Observaţii


3BCh - 3BFh Iniţial folosite pe plăcile video pe care era încorporat. Nu suportă ECP.
378h - 37Fh Adresa uzuală pentru LPT1
278h - 27Fh Adresa uzuală pentru LPT2
Tabel 7.2 - Adresele portului paralel

În continuare vom numi prima adresă din domeniu adresa de bază sau baza.
Se obişnuieşte atribuirea unor denumiri generice portului. Spre exemplu, portul
paralel care are adresa de baza 378h este denumit de obicei LPT1 (Line PrinTer), iar cel care
are adresa de bază 178h, LPT2. Totuşi, în funcţie de decizia producătorului plăcii de bază
sau a BIOS-ului, aceste adrese pot fi diferite.
O altă tabelă BIOS stochează adresele de bază ale porturilor după cum reiese din
tabelul de mai jos.

Adresa de început Funcţia


0000:0408 Adresa de bază a LPT1
0000:040A Adresa de bază a LPT2
0000:040C Adresa de bază a LPT3
Tabel 7.3 - Adresele de bază LPT în zona de date a BIOS-ului

Modul SPP (Standard Parallel Port)

Acest mod de utilizare a portului paralel este cel mai simplu şi preferat pentru
aplicaţiile industriale care nu necesită viteză de transmisie ridicată a datelor. De aceea,
lucrarea de faţă se concentrează asupra acestui mod.
O menţiune importantă trebuie făcută vis-a-vis de liniile de date. La începuturile
dezvoltării SPP, liniile de date erau dedicate exclusiv transmiterii. Cu timpul însă acestea au
fost schimbate pentru a suporta şi recepţionarea semnalelor. Deoarece majoritatea
implementărilor din ziua de azi suportă trafic bidirecţional de informaţie pe liniile de date,
vom considera subînţeles acest lucru.

Regiştrii SPP

Utilizarea portului paralel presupune folosirea unor regiştri speciali dedicaţi. În mod
SPP se vor folosi 3 regiştri, alocaţi începând cu adresa de bază a portului şi adăugând 0, 1,
respectiv 2, pentru a-i identifica în program.

- 53 -
Bit Semnificaţie
Bit 7 Data 7
Bit 6 Data 6
Bit 5 Data 5
Bit 4 Data 4
Bit 3 Data 3
Bit 2 Data 2
Bit 1 Data 1
Bit 0 Data 0
Tabel 7.4 - Registrul de date - Baza+0

Aşa cum am mai spus anterior, registrul de date îl vom considera bidirecţional, adică
putem scrie date în el la fel de bine cum putem citi date din registru. Datele scrise în acest
registru, în modul de scriere, vor fi reflectate la pinii de date (2-9), iar datele ce se citesc din
acest registru, în mod de citire, reprezintă starea pinilor de date.

Registrul de stare este doar pentru citire (read-only) şi conţine biţii de control a
comunicaţiei paralele. Este accesibil la adresa de bază + 1. Orice încercare de a scrie în acest
registru se va ignora.

Bit Semnificaţie
Bit 7 Busy
Bit 6 Ack
Bit 5 Paper End
Bit 4 Select Out
Bit 3 Error
Bit 2 IRQ (negat)
Bit 1 Rezervat
Bit 0 Rezervat
Tabel 7.5 - Registrul de stare - Baza+1

Acest registru are 5 biţi de intrare conectaţi la pinii 10-13 şi 15, un indicator al stării
IRQ şi 2 biţi rezervaţi. Semnalul Busy este inversat hardware, adică dacă bitul Busy este 0
înseamnă că la pinul corespunzător semnalul este de +5V. La fel, IRQ este negat, adică
valoarea lui este 0 dacă s-a produs întreruperea. În cazul conectării imprimantei, semnalul
Busy indică faptul că aceasta este ocupată şi nu poate accepta date, Ack semnalizează că
imprimanta a preluat date de la calculator, Paper End indică terminarea hârtiei din
imprimantă, Select Out arată că imprimanta este selectată, Error indică detectarea unei
condiţii de eroare la imprimantă, iar IRQ indică starea întreruperii.

- 54 -
Bit Semnificaţie
Bit 7 Nefolosit
Bit 6 Nefolosit
Bit 5 Activare port bidirecţional
Bit 4 Activare IRQ prin linia Ack
Bit 3 Select In
Bit 2 Iniţializare imprimantă (Init)
Bit 1 Auto Linefeed
Bit 0 Strobe
Tabel 7.6 - Registrul de control - Baza+2

Registrul de control se află la adresa de bază + 2 şi este şi de citire şi de scriere.


Semnalele Strobe, Auto Linefeed şi Select In sunt inversate hardware. Biţii acestui registru
configurează imprimanta şi portul paralel al calculatorului.
Select In arată că imprimanta a fost selectată, Auto Linefeed comandă imprimantei
saltul automat la următoarea linie când linia de text a fost tipărită, iar Strobe notifică
imprimanta că un octet valid este gata de citit.

Scrierea şi citirea informaţiei de pe liniile de date

Aşa cum am mai spus, portul paralel are 8 linii dedicate transferului bidirecţional de
date. Acestea sunt accesibile în program prin registru de date (adresa de bază + 0) şi reflectă
starea fizică a liniilor electronice.
Deoarece registrul de date este bidirecţional, selectarea sensului se face prin
intermediul bitului 5 din registrul de control.
Prin înscrierea valorii 1 în acest bit, pinii de date (2-9) vor intra în starea de
impedanţă ridicată. În acest moment se pot pune date valide pe liniile 2-9 şi se pot citi în
registrul de date. În mod citire, valorile de pe pinii de date trebuie să respecte specificaţiile
TTL.
Pentru dezactivarea modului bidirecţional, se va înscrie valoarea 0 la bitul 5 al
registrului de control, putându-se apoi scrie în registrul de date biţii ce vor apărea pe pinii de
date.
În mod de scriere, valorile biţilor din port se vor reflecta în valorile tensiunii TTL pe
pinii corespunzători (2-9). Dacă, spre exemplu, registrul este înscris cu valoarea 0xEE, ceea
ce în binar înseamnă 11101110, pinii 9, 8, 7, 5, 4, 3 vor fi la aproximativ +5V, pe când pinii,
6 şi 2 vor fi la 0V.
Mai multe detalii despre acest modul bidirecţional se găsesc în capitolul următor.

- 55 -
Posibilităţi de interconectare

În modul SPP portul paralel poate fi conectat în foarte multe feluri, în funcţie de
protocolul de comunicaţie dorit. Cel mai cunoscut mod de conectare este cel standard între
PC şi imprimantă.

Nr pin Data
0
2 1
3 2
4 3
5 4
6 5
7 6
8 7
9 IMPRIMANTÃ
PC STROBE
1 AUTO LF XT
14 INIT
16 SLCT IN
17
ACK
10 BUSY
11 PAPER END
12 SLCT OUT
13 ERROR
15

Figura 7.2 - Conectarea standard PC – Imprimantă

În acest mod, datele circulă într-un singur sens dinspre PC spre imprimantă, iar
semnalizările apar pe liniile dedicate.
Varianta cea mai simplă de transmitere de date propusă de Centronics este redată în
figura de mai jos.

Figura 7.3 - Protocolul Centronics simplificat

- 56 -
Dacă aplicaţia necesită doar scrirea unui octet sau a maxim 8 biţi fără alte modalităţi
de validare, se recomandă folosirea registrului de date, respectiv a pinilor 2-9. Atenţie la
curentul maxim absorbit din port.

Utilizarea întreruperii (IRQ)

În unele aplicaţii este de preferat ca programul de pe PC să fie informat de apariţia


unor evenimente semnificative (ex: perifericul doreşte să transmită informaţii PC-ului).
Această metodă presupune întreruperea executării programului principal în momentul
producerii evenimentului semnificativ, în contrast cu metoda „clasică” în care se verifică
periodic starea portului şi a comunicaţiei.
Aşadar, portul paralel în mod standard poate genera întrerupere. În mod normal
cererea de întrerupere a portului paralel este IRQ5 sau IRQ7, dar în cazul în care acestea
sunt ocupate poate primi şi altă valoare.
Pentru activarea întreruperii se va seta bitul 4 al registrului de control (Activare IRQ
prin linia Ack) la valoarea 1, iar pentru dezactivarea întreruperii se va seta bitul pe 0.
Funcţionarea mecanismului de întrerupere este extrem de simplă. La apariţia unui
front crescător (tranziţie din 0 în 1, sau din 0V în +5V) în semnalul nAck, se generează o
întrerupere. Trebuie menţionat că există unele implementări ale portului paralel care
generează întreruperea pe frontul descrescător. Pentru tratarea întreruperii se va suprascrie
rutina de tratare a întreruperii asociată. Modalitatea de programare este dată în capitolul
următor.

Programarea portului paralel

În esenţă programarea portului paralel se reduce la scrierea şi citirea valorilor de la


porturi. După cum am văzut anterior, interfaţa dintre program şi echipamentul fizic al
portului este realizată de 3 regiştri. Aşadar, modificările din aceşti regiştri sunt reflectate în
funcţionarea portului paralel.

Programarea în Borland C sub MS-DOS

Pentru a scrie la portul dorit se va folosit funcţia:

outportb (unsigned int adresa_port, char octet_de_scris);

Pentru a citi portul se foloseşte:

char inportb (unsigned int adresa_port);

- 57 -
Spre exemplu dacă se doreşte scrierea valorii 0xEE (11101110) pe liniile de date ale
LPT1 cu adresa de baza 0x378, se va folosi sintaxa:

Exemplu:
outportb (0x378,0xEE)

Pentru a putea rula codul dorit în cazul apariţiei întreruperii, se va folosi o secvenţă de
cod asemănătoare cu cea care urmează:

Exemplu:
int intno = numar_intrerupere;

void interrupt (*oldhandler)();

void interrupt newhandler()


{
/* cod de tratare a întreruperii */
...
}

...
voi main (void)
{
...
oldhandler = getvect(intno); /* salvare vector vechi */
setvect(intno, newhandler); /* setare vector nou */
outportb(0x21, inportb(0x21)& 0x7F); /* activarea liniei IRQ7 in registrul masca */
outportb(0x378+2, inportb(0x378+2)| 0x10); /* activarea intreruperii in
registrul de control */

...
outportb(0x378+2, inportb(0x378+2)& 0xEF); /* deactivarea intreruperii in
registrul de control */
outportb(0x21, inportb(0x21)| 0x80); /* deactivarea liniei IRQ7 in registrul masca */
setvect(intno, oldhandler); /* restaurare vector vechi inainte de iesire */
}

Programarea în LabWindows / CVI

Mediul LabWindows / CVI pune la dispoziţia programatorului două funcţii similare


celor din Borland C pentru MS-DOS şi anume:

char inp (int adresa_port);

- 58 -
pentru citirea porturilor şi

char outp (int adresa_port, char octet_de_scris);

pentru scrierea lor.

În rest, programarea LabWindows / CVI se va aborda identic cu cea Borland C, adică


respectând cu rigoare specificaţiile registrilor portului paralel. Trebuie menţionat că folosirea
întreruperii în modul prezenatat mai sus sub LabWindows / CVI este imposibilă.

Temă

Realizaţi schema electronică minimală prezentată mai jos. Aceasta constă într-un
număr de 8 LED-uri de 3mm conectate la o mufă D-25 prin intermediul a 8 rezistenţe de 1
kΩ.

Figura 7.4 – schema electronică

Echipamentul astfel realizat se va conecta la portul paralel al calculatorului. Se cere


realizarea unui program LabWindows / CVI care să implementeze o lumină dinamică
folosind cele 8 LED-uri.

- 59 -
Cap. 8 - Portul Paralel – Modalităţi de citire a datelor

Aşa cum am văzut în capitolul anterior, portul paralel în mod standard (SPP, Standard
Parallel Port) suportă mai multe feluri de funcţionare.
Cel mai simplu sistem de comunicare realizabil cu portul paralel este cel în care una
sau mai multe linii de date (registrul de date biţii D7-D0, pinii 9-2) sunt conectate la
periferic şi se transmit date exclusiv către periferic. Adică, această configuraţie oferă
comunicaţie unidirecţională (de la PC la periferic) pe liniile de date.
Totuşi, majoritatea aplicaţiilor industriale ce folosesc portul paralel au nevoie de mai
mult decât o comunicaţie unidirecţională şi anume o comunicaţie bidirecţională, în care
informaţia este transmisă şi de la PC la periferic, şi invers. În continuare se vor prezenta
câteva modalităţi de rezolvare a acestei probleme.

Registrul de date bidirecţional

O metodă simplă de comunicaţie bidirecţională semiduplex este cea a folosirii liniilor


de date bidirecţionale.

Figura 8.1 - Implementarea bidirecţională a liniilor de date

- 60 -
După cum spuneam în paginile anterioare, iniţial liniile de date ale portului paralel
erau folosite exclusiv pentru transmiterea informaţiei către periferic (placa video,
imprimantă, etc.). Cu timpul însă a apărut implementarea bidirecţională a liniilor de date
În versiunea iniţială unidirecţională a portului paralel se folosea pentru registrul de
date un circuit integrat 74LS374, alături de electronica auxiliară, bineînţeles. Semnalul de
activare a ieşirii OE era legat permanent la masă, circuitul fiind practic transparent.
Pentru a realiza bidirecţionalitatea registrului de date, s-a introdus un al doilea buffer
74LS244 care se foloseşte la citirea datelor. Semnalul OE a fost „legat” la bitul 5 din
registrul de control, tipul de operaţine (scriere/citire) fiind selectat cu ajutorul acestui bit.
Când bitul 5 al registrului de control are valoarea 1, liniile registrului de date intră în
stare de impedanţă ridicată, putând astfel să fie citite datele de la periferic. Dacă se încearcă
scrierea la port în acest moment, valoarea este stocată de registru, dar ieşirile nu se
conformează cu noua valoare.
Pentru a transmite informaţia trebuie să se revină la modul iniţial. Când bitul 5 al
registrului de control are valoarea 0, registrul de date se comportă ca registru de transmisie.
Aşadar, modul acesta este bidirecţional, dar semiduplex, adică datele se pot şi trimite şi
recepţiona, dar nu simultan, ci pe rând. Mai trebuie menţionat că unele implementări ale
portului bidirecţional folosesc alţi biţi de selecţie a modului, fiind recomandată consultarea
documentaţiei de firmă la folosirea portului paralel.

Utilizarea portului paralel pentru citirea a 8 biţi

Deşi foarte rar, se mai întâlnesc şi în ziua de azi implementări ale portului paralel care
nu suportă modul bidirecţional. De asemenea, pot apărea cerinţe de proiectare a comunicaţiei
care elimină posibilitatea folosirii modului bidirecţional. Pentru asemenea situaţii, există şi
alte soluţii, dintre care cea mai simplă e prezentată în rândurile următoare.
Problema cere de a găsi o modalitate de citire a biţilor transmişi de la periferic la port,
fără a folosi liniile de date. După cum am arătat în capitolul anterior, pe lângă liniile de date,
portul paralel mai dispune de semnale speciale, care vehiculează infomaţia de la periferic la
PC: Busy , nAck, Paper End, Select Out (în registrul de stare), nSelectIn , nInit,
nAutoLinefeed , nStrobe (în registrul de control).
După cum se poate vedea în figura următoare, folosind cele 8 semnale speciale, se
poate alcătui un octet, care să fie folosit ca port de intrare. Altfel spus, prin modificarea
destinaţiei iniţiale a celor 8 linii, şi anume, prin conectarea lor într-un mod corespunzător la
liniile de transmisie ale perifericului, informaţia poate circula şi dinspre periferic spre PC.
Se impune folosirea circuitelor cu colector deschis aşa cum arată şi figura pentru
prevenirea conflictelor de acces în cazul în care intrarea este în impedanţă ridicată.

- 61 -
Figura 8.2 - Realizarea unui octet de recepţie

Se observă aranjarea convenabilă a jumătăţii superioare a octetului de la semnalul


nBusy (bitul 7 în registrul de stare) până la semnalul Select Out (bitul 4 în registrul de stare),
urmată de jumătatea inferioară în aceeaşi idee: de la nSelect In (bitul 3 în registrul de
control) până la nStrobe (bitul 0 în registrul de control).
Aşadar, citirea octetului de date se va face pe două căi diferite, urmând ca asamblarea
celor două părţi să fie făcută în program.
În primul rând trebuie puse liniile în stare de înaltă impedanţă pentru a putea citi
datele. Aceasta se rezolvă în Borland C cu linia de mai jos:

outportb (baza+2, inport(baza+2) & 0xF0 | 0x04);

Apoi vom proceda la citirea părţii celei mai semnificative a octetului, astfel:

octet = (inport(baza+1) & 0xF0);

Urmează citirea părţii inferioare a octetului din registrul de control şi reasamblarea:

a = a | (inport(baza+2) & 0x0F);

Din modul de funcţionare a acestei scheme biţii 2 şi 7 sunt inversaţi. Pentru a-i aduce la
forma corectă vom folosi operaţia XOR pe biţi:

a = a ^ 0x84;

- 62 -
Modul Nibble

În unele aplicaţii, nici una dintre metodele de mai sus nu este agreată. Bineînteles,
există o soluţie şi în acest caz. Şi anume, modul nibble, sau modul multiplexat.
Acesta pleacă de la ideea că un octet de intrare poate fi citit în doi paşi folosind doar 4
linii de intrare. Prima dată se va citi partea superioară a octetului, apoi cea inferioară şi la
sfârşit se asamblează.
Pentru implementarea acestei soluţii este nevoie de un circuit integrat extern de tipul
multiplexor 2 la 1. Acesta va fi conectat la port prin liniile Busy , nAck, Paper End, Select
Out şi nStrobe , respectiv pe cele 8 linii de date de la periferic. Practic, cu ajutorul semnalului
nStrobe se va selecta partea de octet ce va fi citită.

Figura 8.3 - 8 intrări folosind 74LS157

Pentru selectarea părţii inferioare a octetului, vom seta bitul 0 al registrului de control
pentru a avea valoarea 0 pe pinul 1:

outportb (baza+2, inport(baza+2) | 0x01);

Apoi, citim registrul de stare ce conţine partea inferioară:

a = (inport(baza+1) & 0xF0);

Se deplasează partea în poziţia dorită (deplasare la dreapta cu 4 poziţii):

a = a >> 4;

- 63 -
Pentru selectarea părţii superioare a octetului, vom reseta bitul 0 al registrului de
control pentru a avea valoarea 1 pe pinul 1:

outportb (baza+2, inport(baza+2) | 0xFE);

Citim şi asamblăm şi partea superioară:


a = a | (inport(baza+1) & 0xF0);

În sfârşit, vom inversa biţii citiţi pe linia nBusy, deoarece e inversată:

a = a ^ 0x88;

S-ar putea să fie nevoie de introducerea unor întârzieri suplimentare dacă valorile
citite devin eronate.
Totuşi, dezavantajele principale ale acestei metode sunt viteza mai scăzută de
comunicare, datorată dublării timpului de citire a datelor (2 paşi), şi necesitatea utilizării
unui circuit integrat suplimentar.

Temă

Să se realizeze un program de tip chat prin portul paralel care implementează


bidirecţional protocolul Handshake cu cele trei semnale Ready, Strobe şi Acknowledge.

- 64 -
Cap. 9 - Portul serial

Comunicaţiile seriale sunt folosite pentru transmiterea de date pe distanţe lungi, biţii
de date fiind transmişi succesiv pe o singură linie. Datele seriale recepţionate de un modem
sau de un alt echipament trebuie transformate în date paralele pentru a putea fi transferate pe
magistrala calculatorului.
Echipamentele de comunicaţii seriale pot fi clasificate în funcţie de sensul de
transmisie în echipamente simplex (într-un singur sens), semiduplex (alternativ în ambele
sensuri) sau duplex (simultan în ambele sensuri).
Există două modalităţi de a transmite datele seriale: sincron sau asincron. În
transmisia sincronă datele se transmit în bloc, emiţătorul şi receptorul sincronizându-se cu
ajutorul unuia sau a mai multor caractere speciale denumite caractere de sincronizare.
Portul serial al calculatoarelor PC este un dispozitiv asincron, iar în cele ce urmează
va fi descris acest tip de sistem. În cazul transmisiei asincrone fiecărui caracter i se adaugă
informaţia de cadru, care constă dintr-un bit de start şi unul sau doi biţi de stop. Bitul de start
indică receptorului începutul procesului de asamblare a unui caracter din fluxul serial de biţi
care urmează, precum şi sincronizarea cu transmiţătorul. După bitul de start se transmite
caracterul care constă din 7 sau 8 biţi începând cu bitul cel mai nesemnificativ.
Sincronizarea are loc pe durata transmisiei unui singur caracter

Descrierea portului

Specificaţiile electrice ale portului serial sunt conţinute în standardul EIA (Electronics
Industry Association) RS232C. Acest standard a fost conceput în anii 1960 pentru a permite
comunicarea dintre Echipamentele Terminale pentru Date – DTE (PC-ul în acest caz) şi
Echipamentele de Comunicaţii Date – DCE (de obicei modem).
În standardul menţionat mai sus se specifică o mulţime de parametri care
caracterizează portul serial, din care amintim: nivelele de tensiune între –3V şi –15V pentru
‘1’ logic şi între +3V şi +15V pentru ‘0’ logic. (Tensiunile cel mai des utilizate sunt ±12V);
tensiunea maximă de ieşire în gol nu trebuie să depăşească 25V (faţă de masă, GND);
curentul de scurtcircuit nu trebuie să depăşească 500mA; capacitatea liniei; vitezele de
transmisie a datelor etc.
Interfaţarea portului serial se realizează prin intermediul a două tipuri de conectori, un
conector D (rack) de 25 de pini şi unul D de 9 pini, de tip tată, amplasaţi în spatele
calculatorului. Cele două tipuri de conectori şi funcţiile pinilor sunt prezentate mai jos.

- 65 -
Pin Pin
Nume Dir Descriere
(PC9) (PC25)
3 2 TD Æ Transmit Data - Ieşire Serială de date
2 3 RD Å Receive Data - Intrare Recepţie Date
7 4 RTS Æ Request to Send - UART-ul este gata să transmită
8 5 CTS Å Clear to Send - Modemul este gata pentru transfer
Data Set Ready - Informează UART-ul că modemul
6 6 DSR Å
este gata să stabilească o legătură
5 7 SG --- System Ground - Masa
1 8 CD Å Carrier Detect - semnalul de purtătoare prezent
Data Terminal Ready - UART-ul informează modemul
4 20 DTR Æ
că e gata de comunicare (invers DSR)
Ring Indicator - Se activează când modemul
9 22 RI Å
detectează un semnal de apel de la PSTN
Notă: Direcţia este DTE (Computer) relativ la DCE (Modem).
Tabel 9.1 – Semnificaţiile pinilor

Serial (PC 9) - 9 PIN D-SUB MALE Serial (PC 25) - 25 PIN D-SUB MALE

Figura 9.1 – Tipurile de conectori

TD – Acest semnal este activ când se transmit date de la DTE (Computer) la DCE (Modem).
Când nu se transmit date linia este pe ‘1’ logic – tensiune negativă.

RD – Acest semnal este activ când DTE primeşte date de la DCE. Când nu se transmit date
linia este pe ‘1’ logic – tensiune negativă.

RTS – Semnalul este pus pe ‘0’ logic – tensiune pozitivă pentru a pregăti modemul să
accepte date transmise de calculator. Când DCE este gata să primească date acesta răspunde
prin intermediul liniei Clear to Send (CTS).

CTS – Semnalul este pus pe ‘0’ logic – tensiune pozitivă de către DCE (Modem) pentru a
informa DTE (Computer) că transmisia poate să înceapă.

DSR – Când acest semnal provine de la modem, linia este pusă pe ‘0’ logic – tensiune
pozitivă dacă modemul este gata să stabiliească o legătură (modemul este conectat la o linie
de telefon activă; modemul este pus in starea de a transmite date; modemul a terminat de
realizat apelul pentru conectare).

- 66 -
SG – Toate semnalele sunt determinate faţă de o masă comună reprezentată de această linie.

CD – Semnal logic '0' – tensiune pozitivă când se detectează o “purtătoare”.

DTR – Semnalul este pus pe ‘0’ logic – tensiune pozitivă de către DTE (Computer) când
acesta doreşte să deschidă un canal de comunicare. Dacă DCE este un modem, acesta se
pregăteşte pentru a se conecta la linia telefonică şi după conectare se menţine legătura. Când
DTE este pus ‘1’ logic – tensiune negativă legătura se întrerupe.

RI – Când DCE este un modem această linie este pusă pe ‘0’ logic – tensiune pozitivă când
se recepţionează un semnal de apel pe linie telefonică. Acest semnal îşi păstrează starea pe
durata unui apel, fiind dezactivat (pus pe 1) între apeluri.

Conectarea Null modem

Una dintre cele mai ieftine şi simple modalităţi de a conecta două dispozitive DTE
(calculatoare) implică utilizarea unui Null modem pentru transferul datelor. În figura de mai
jos se prezintă modalitatea de a realiza conexiunea Null modem.

Figura 9.2 – Cablajul Null modem

Principiul pe care se bazează utilizarea unei configuraţii Null modem pentru


conectarea serială este de a face calculatorul să creadă că acesta conversează cu un modem
decât cu un alt calculator. Orice dată transmisă de la primul calculator trebuie să fie
recepţionată de al doilea astfel că TD este legat cu RD. Al doilea calculator trebuie să aibă
aceeaşi configuraţie astfel că RD este conectat le TD. Masa trebuie să fie acceaşi pentru
ambele calculatoare astfel încât se leagă ambele SG.
Data Terminal Ready (DTR) se leagă la DSR şi CD la ambele calculatoare. Când
DTR este activat, atunci imediat DSR şi CD devin active. În acest moment calculatorul
consideră că “modemul virtual” la care este conectat este pregătit şi a detectat purtătoarea
unui alt modem.
Când un calculator doreşte să transmită date va pune RTS pe 1 (high) şi cum acesta
este legat cu CTS, va primi răspuns că poate să transmită. RI nu este conectat la nimic.
Această linie se foloseşte pentru a informa calculatorul că există un semnal de apel pe linia

- 67 -
de telefon. Deoarece noi nu avem un modem conectat la linia telefonică acest semnal este
lăsat liber.

Conector de buclare

Această modalitate de a interfaţa portul serial este foarte utilă când se testează
programe pentru comunicaţia serială. Având liniile de recepţie şi de transmisie conectate
împreună, tot ceea ce se transmite va fi recepţionat imediat de acelaşi port.

Figura 9.3 – Cablajul pentru buclare

Circuitul UART

După cum menţionam anterior orice dispozitiv conectat la portul serial trebuie să
transforme înapoi în formă paralelă datele pentru a le putea utiliza. Acest lucru se poate face
utilizând un circuit UART (Universal Asynchronous Receiver / Transmitter). Practic fiecare
calculator conţine un circuit UART pentru a gestiona porturile seriale.
UART-ul este un circuit integrat folosit pentru comunicaţiile seriale care conţine un
transmiţător (convertor paralel-serial) şi un receptor (convertor serial-paralel) cu impulsuri
de tact separate. Partea paralelă a circuitului UART este de obicei conectată la magistrala
calculatorului. Când un calculator scrie un octet la registrul pentru transmiterea datelor
(TDR) al UART-ului, circuitul va începe să-l transmită sub formă serială pe linie. Registrul
de status conţine un bit pe care calculatorul poate să îl citească pentru a vedea dacă UART-ul
este pregătit să transmită un nou octet. Un alt bit din registrul de status indică dacă s-a
recepţionat un octet pe linia serială, caz în care calculatorul ar trebui să îl citească din
registrul pentru date primite (RDR).
Circuitul UART permite şi realizarea formatării datelor seriale (paritate, lungime
cuvânt) precum şi fixarea vitezelor de transmisie (biţi pe secundă). Erorile care pot apărea în
comunicarea serială (eroare de încadrare, eroare de paritate, eroare de suprarecepţie) sunt şi
ele capturate de circuitul UART prin intermediul registrului stare linie (LSR).
UART-ul a fost implementat în calculatoarele personale prin seria de circuite 8250, care
include circuitele UART 16450, 16550, 16650 şi 16750.

- 68 -
Regiştrii portului serial (la PC)

Adresele standard pentru portul serial şi IRQ-urile sunt prezentate în tabelul de mai
jos. Aceastea sunt valabile la cele mai multe PC-uri.

Nume Adresă IRQ


COM1 3F8h 4
COM2 2F8h 3
COM3 3E8h 4
COM4 2E8h 3
Tabel 9.2 – Adrese şi IRQ pentru portil serial

În continuare se vor prezenta sumar registrele care intră în componenţa portului serial.

Adr. bază DLAB Scriere/Citire Presc. Nume registru


=0 Scriere - Buffer transmisie
+0 =0 Citire - Buffer recepţie
=1 Scriere/Citire - Octet inferior divizor (viteza)
=0 Scriere/Citire IER Registru validare întreruperi
+1
=1 Scriere/Citire - Octet superior divizor (viteza)
- Citire IIR Registru identificare întrerupere
+2
- Scriere FCR Registru control FIFO
+3 - Scriere/Citire LCR Registru control linie
+4 - Scriere/Citire MCR Registru control modem
+5 - Citire LSR Registru stare linie
+6 - Citire MSR Registru stare modem
+7 - Scriere/Citire Registru suplimentar (Scratch)
Tabel 9.3 – Regiştrii portului serial

După cum se poate remarca din tabelul de mai sus, prin intermediul bitului DLAB
(Divisor Latch Access Bit), funcţionalitatea anumitor registre ale portului serial se schimbă.
Astfel UART-ul va avea 12 regiştri adresabili prin 8 adrese (porturi).
DLAB vine de la Divisor Latch Access Bit (Bit Acces Latch Divizor). Când DLAB
este pus pe '1' prin intermediul registrului de control linie (LCR, baza+3), devin accesibile
două registre (la adresele baza+0 şi baza+1) prin care se poate seta viteza de comunicaţie
măsurată în biţi pe secundă (bps).
În continuare este prezentat un tabel cu vitezele comune de transmisie utilizate şi
octeţii ce trebuie înscrişi în divizorul programabil.

Viteza Octet superior Octet inferior


(bps) (baza+1) (baza+0)
50 09h 00h
300 01h 80h
600 00h C0h

- 69 -
2400 00h 30h
4800 00h 18h
9600 00h 0Ch
19200 00h 06h
38400 00h 03h
57600 00h 02h
115200 00h 01h
Tabel 9.4 – Rata de transfer şi octeţii pentru setarea acesteia

Registrul Validare Întreruperi (IER)

La adresa baza+1, prin punerea bitului DLAB pe ‘0’ se poate configura registrul de
validare întreruperi (Interrupt Enable Register - IER).

Bit Semnificaţie
Bit 7 Neutilizat
Bit 6 Neutilizat
Bit 5 Neutilizat
Bit 4 Neutilizat
Validarea întreruperii generate de modificarea stării liniei de comunicare
Bit 3
(Stare Modem)
Bit 2 Validarea întreruperii generate de modificarea Registrului de Stare Linie
Bit 1 Validarea întreruperii generate de golirea Registrului de Transmisie
Bit 0 Validarea întreruperii generate de recepţia unui caracter
Tabel 9.5 – Registrul validare întreruperi – Baza+1

După cum se poate vedea cu uşurinţă, punerea bitului corespunzător pe ‘1’ sau pe ‘0’
va activa (valida) / dezactiva întreruperea respectivă.
Portul serial suportă 4 surse de întrerupere, dar acestea generează o singură cerere de
întrerupere. De exemplu dacă portul serial are adresa de bază la 3F8h (COM1) se va activa
IRQ4.

Registrul Identificare Întreruperi (IIR)

Acest registru se află la adresa baza+2 şi este un registru numai pentru citire.
Semnificaţia biţilor acestui registru este prezentată mai jos.
Biţii 6 şi 7 ai registrului de identificare a sursei întreruperii (Interrupt Identification
Register - IIR) permit citirea stării stivei FIFO. Biţii 4 şi 5 sunt rezervaţi. Bitul 3 arată starea
întreruperii generată de timpul depăşit (time-out) la 16550 sau mai mare. Bitul 0 ne indică
dacă a apărut o cerere de întrerupere, iar sursa întreruperii este indicată de biţii 1 şi 2.

- 70 -
Bit Semnificaţie
Bit 6 Bi t7
0 0 FIFO invalidă (singura opţiune pt 8250 sau 16450)
Biţii 6:7
0 1 FIFO validată dar neutilizabilă
1 1 FIFO validată şi operaţională
Bit 5 Validare FIFO 64 octeţi (numai pentru 16750)
Bit 4 Rezervat
0 Rezervat la 8250 şi 16450
Bit 3
1 Time-out întrerupere(16550)
Bit 2 Bit 1
0 0 Întrerupere stare modem
Biţii 1:2 0 1 Golire registru de transmisie
1 0 Dată recepţionată disponibilă
1 1 Întrerupere generată de stare linie comunicare
0 Nu există cerere întrerupere
Bit 0
1 Există cerere întrerupere
Tabel 9.6 - Registrul identificare întreruperi – Baza+2

Aceste întreruperi lucrează într-o anumită ordine de prioritate. Întreruperea generată


de Starea liniei are prioritatea maximă, urmată de Dată disponibilă apoi de Golirea
registrului de transmisie şi cu cea mai mică prioritate întreruperea stării modemului.

Registrul de control stivă FIFO (FCR)

Acest registru se află la adresa baza+2, este un registru numai pentru scriere şi se
găseşte la circuitele UART 16550 sau mai mari care au implementată stiva FIFO.

Bit Semnificaţie
Bit7 Bit6 Nivel generare întrerupere pt. 1, 4, 8 sau 14 octeţi
0 0 1 octet
Biţii 6:7 0 1 4 octeţi
1 0 8 octeţi
1 1 14 octeţi
Bit5 Validare FIFO 64 octeţi (numai la 16750)
Bit4 Rezervat
Bit3 Selectare mod DMA.
Bit 2 Ştergere FIFO transmisie
Bit1 Ştergere FIFO recepţie
Bit0 Validare operaţii cu FIFO
Tabel 9.7 - Registrul de control stivă FIFO – Baza+2

Bitul 0 validează operaţiile cu stivele FIFO de transmisie şi recepţie. Înscriind un '0'


aici vom invalida (bloca) stivele FIFO de recepţie şi transmisie şi se vor pierde toate datele

- 71 -
existente în aceste stive. Biţii 1 şi 2 controlează ştergerea stivei FIFO de transmisie sau
recepţie. Punând aceşti biţi pe 1 vom şterge conţinutul stivelor FIFO Aceşti doi biţi sunt cu
autoreset astfel că nu trebuie puşi pe '0' când aţi terminat. Bitul 3 validează modul DMA
care se găseşte pe 16550 sau mai mare. Biţii 4 şi 5 sunt rezervaţi. Biţii 6 şi 7 sunt destinaţi
pentru a seta nivelul de declanşare al stivei FIFO de recepţie. De exemplu dacă bitul 7 a fost
setat pe '1' şi bitul 6 pe '0' atunci nivelul de declanşare este de 8 octeţi. Când sunt 8 octeţi în
stivă atunci se generează întrerupere de Dată disponibilă.

Registrul Control Linie (LCR)

La adresa baza+3 se găseşte registrul de control linie (Line Control Register - LCR)
prin intermediul căruia se setează parametrii de bază pentru comunicaţie, precum şi bitul
DLAB (Divisor Latch Access Bit) despre care am vorbit anterior şi care permite accesarea
celor două registre prin care se fixează viteza comunicaţiei.

Bit Semnificaţie
1 DLAB = 1 acces octet inferior şi superior divizor
Bit 7 DLAB = 0 acces registru recepţie, transmisie şi
0
validare întreruperi
Bit 6 Validare regim Break
Bit 5 Bit 4 Bit 3 Selecţie paritate
X X 0 Fără paritate
0 0 1 Paritate impară
Biţii 3:5
0 1 1 Paritate pară
1 0 1 Paritate pe '1' (high) fixă
1 1 1 Paritate pe '0' (low) fixă
Număr biţi Stop
0 Un bit Stop
Bit 2
Doi biţi de stop pentru cuvinte de 6, 7 sau 8 biţi sau
1
1,5 biţi pentru cuvinte de 5 biţi
Bit 1 Bit 0 Lungime cuvânt
0 0 5 Biţi
Biţi 0:1 0 1 6 Biţi
1 0 7 Biţi
1 1 8 Biţi
Tabel 9.8 – Registrul control linie – Baza+3

Bitul 7 este Bitul de Acces la Latch-ul Divizorului sau DLAB pe scurt.


Un ‘1’ înscris la bitul 6 întrerupe transmisia în curs.
Biţii 5, 4 şi 3 selectează paritatea. Paritatea impară poate fi '1' sau '0' în funcţie de
faptul că avem sau nu un număr impar de '1' în cuvântul transmis. Paritatea pară atunci este
determinată de numărul par de '1' din cuvânt. Paritatea fixă pe '1' (high) utilizează un '1'
pentru bitul de paritate în timp ce paritatea opusă foloseşte bitul pe '0'

- 72 -
Bitul 2 setează numărul de biţi de stop.
Biţii 1 şi 0 setează lungimea cuvântului. Cel mai adesea se utilizează un cuvânt de 8
biţi.

Registrul Control Modem (MCR)

Registrul de Control Modem (Modem Control Register - MCR) este pentru citire şi
scriere şi se găseşte la adresa baza+4.

Bit Semnificaţie
Bit 7 Rezervat
Bit 6 Rezervat
Bit 5 Rezervat
Bit 4 Mod cu lucru în buclă închisă
Bit 3 Ieşire Aux 2
Bit 2 Ieşire Aux 1
Bit 1 (Forţare) Request to Send
Bit 0 (Forţare) Data Terminal Ready
Tabel 9.9 – Registrul control modem – Baza+4

Biţii 7, 6 şi 5 sunt rezervaţi.


Bitul 4 activează modul de lucru în buclă închisă. În acest mod orice dată care este
pusă în registrul de transmisie pentru ieşire este recepţionată de receptorul de pe acelaşi chip
şi este disponibilă în bufferul de recepţie. Acest lucru poate fi utilizat pentru testarea
funcţionării circuitului UART.
Ieşirile Aux 2 şi Aux 1 (biţii 3 şi 2) pot fi folosite pentru conectarea de circuite
externe.
Biţii 0 şi 1 modifică direct liniile Request to Send (RTS) şi Data Terminal Ready
(DTR). Un ‘1’ înscris la aceste poziţii activează liniile respective.

Registrul Stare Linie (LSR)

Registrul stare linie (Line Status Register - LSR) este un registru doar pentru citire şi
se află la adresa baza+5.
Un ‘1’ citit de la o anumită poziţie indică realizarea situaţiei menţionate în
înregistrările din tabelul următor.

- 73 -
Bit Semnificaţie
Bit 7 Eroare la stiva FIFO recepţie (cel puţin un octet este afectat de eroare)
Bit 6 Registrul de serializare şi cel de transmisie este gol
Bit 5 Registrul pentru menţinerea datelor transmise este gol
Bit 4 Recepţie semnal întrerupere transmisie (break)
Bit 3 Eroare de încadrare (ultimul bit nu este bit de stop)
Bit 2 Eroare de paritate
Bit 1 Eroare de suprarecepţie (nu s-a citit caracterul şi a sosit altul)
Bit 0 Dată disponibilă pentru citire în registrul de recepţie
Tablel 9.10 – Registrul stare linie – Baza+5

Registrul Stare Modem (MSR)

Registrul stare modem (Modem Status Register - MSR) este un registru pentru citire,
aflat la adresa baza+6, care indică o schimbare a semnalului Clear To Send (CTS) de la
ultima citire a registrului (bit 0), schimbarea stării biţilor Data Set Ready (DSR) şi Carrier
Detect (CD) – (biţii 1 şi 3), trecerea din ‘0’ în ‘1’ a liniei Ring Indicator (RI) – (bitul 2). Biţii
4 până la 7 indică starea curentă a liniilor corespunzătoare.

Bit Semnificaţie
Bit 7 Detecţie purtătoare
Bit 6 Indicator apel (RI)
Bit 5 Data Set Ready
Bit 4 Clear To Send
Bit 3 Delta Data Carrier Detect (variaţie DCD)
Bit 2 Front apariţie apel (trecere 0Æ1)
Bit 1 Delta Data Set Ready (variaţie DSR)
Bit 0 Delta Clear To Send (variaţie CTS)
Tabel 9.11 – Registrul stare modem – Baza+6

Registrul suplimentar (Scratch Register)

Acest registru aflat la adresa baza+7 este pentru scriere/citire, nu este folosit pentru
comunicaţie, fiind utilizat pentru păstrarea unui octet de date.

Programarea Portului Serial

În continuare se prezintă pe scurt modul în care se realizează programarea portului


serial în C. Pentru testare se poate folosi un conector de buclare.

- 74 -
Exemplu:
/* iniţializarea portului serial
viteza = 4800 bps
fără paritate
caracter de 8 biţi
1 bit de stop */

#define baza 0x3F8

outportb (baza + 3, 0x80); //setare DLAB

outportb (baza + 0, 0x18); //setare viteza – octet inferior divizor (vezi tabel 4)
outportb (baza + 1, 0x00); //setare viteza – octet superior divizor

outportb (baza + 3, 0x03 ); //setare parametri comunicaţie

/* citire octet */

do{
test = inportb (baza + 5) & 1;
} while (test==0); /* aşteptăm recepţia unui octet prin verificarea bitului 1 din
registrul LSR (vezi tabel 10) */

caracter_citit = inportb (baza);

/*scriere octet*/

do{
test = inportb (baza + 5) & 0x20;
}while (test==0); /* aştept până registrul de transmisie este gol pentru a putea
transmite un nou octet */
outportb(baza, caracter_scris);

În LabWindows / CVI se poate folosi aceeaşi modalitate de a programa portul serial,


cu menţiunea că în locul funcţiilor inportb şi outportb se folosesc funcţiile inp respectiv
outp. În plus acest mediu de programare ne pune la dispoziţie o bibliotecă de funcţii
suplimentară dedicată în exclusivitate lucrului cu portul serial după cum se va putea vedea în
capitolul următor.

Temă

Să se realizeze un program în C care implementează o comunicaţie prin portul serial.

- 75 -
Cap. 10 - Programarea portului serial în
LabWindows / CVI

În mediul LabWindows / CVI comunicarea serială se implementează folosind


biblioteca de funcţii RS-232. Această bibliotecă conţine o gamă de funcţii care se ocupă de
toate aspectele comunicării seriale. Cele mai importante funcţii sunt funcţiile de deschidere,
inchidere, scriere şi citire. Aceste funcţii vor fi descrise în cele ce urmează.

Deschiderea şi închiderea porturilor seriale

Înainte de a putea utiliza funcţiile corespunzătoare pentru a comunica cu


echipamentul serial este necesar să se deschidă o sesiune de comunicare cu portul COM
folosind funcţia OpenComConfig (Library Æ RS-232 Æ Open/Close Æ Open COM and
Configure).

int OpenComConfig(int COM_Port, char Device_Name[],long Baud_Rate,


int Parity, int Data_Bits,
int Stop_Bits, int Input_Queue_Size,
int Output_Queue_Size);

Această funcţie va selecta portul serial şi va stabili rata de transfer (baud rate - bps),
paritatea, numărul biţilor de stop şi dimensiunea bufferelor de citire şi scriere. CVI ne
permite să specificăm un buffer în memorie care va reţine conţinutul stivelor FIFO.
Dimensiunea bufferelor reale va depinde de tipul sistemului de operare precum şi de
interfaţa serială. Dacă funcţia vrea să deschidă un port care este deschis, îl va închide şi
redeschide cu noii parametri. Nu este întotdeauna necesar să se utilizeze funcţia
OpenComConfig, prin folosirea funcţiei OpenCom (int COMPort, char deviceName[]); se
vor utiliza parametrii impliciţi ai configuraţiei portului.
Introducerea şi identificarea argumentelor funcţiei OpenComConfig se face cu
uşurinţă prin intermediul interfeţei care permite fixarea acestora.

- 76 -
Figura 10.1 – OpenComConfig

Funcţia CloseCom (int COMPort) se va folosi pentru eliberarea resurselor utilizate în


timpul sesiunii de comunicaţie cu portul serial. Pentru a garanta că toţi biţii au fost scrişi
înainte de închiderea portului se poate monitoriza stiva cu funcţia GetOutQLen (int
COMPort). Când marimea stivei este egală cu zero închiderea portului este sigură.

Scrierea la portul serial

Pentru a transmite date dintr-un buffer unui echipament serial se foloseşte funcţia
ComWrt (Library Æ RS-232 Æ Input/Output Æ Write Buffer).

int ComWrt (int COM_Port, char Buffer[], int Count);

ComWrt scrie date din memorie la stiva de ieşire a portului serial determinat. Count
specifică numărul octeţilor care trebuie scrişi. Mărimea bufferului se poate determina
folosind funcţia strlen (ANSI C Library)

Pentru transmiterea unui singur caracter prin portul serial se poate utiliza funcţia
Write Byte, care scrie la portul serial octetul inferior al integer-ului.

int ComWrtByte (int COM_Port, int byte);

- 77 -
Citirea portului serial

Datele transmise de un echipament serial şi recepţionate de PC pot fi citite cu ajutorul


funcţiei ComRd (Library Æ RS-232 Æ Input/Output Æ Read Buffer).

int ComRd (int COM_Port, char Buffer[], int Count);

Funcţia ComRd citeşte numărul specificat de octeţi de la portul serial determinat.


Folosiţi funcţia GetInQLen (int COMPort) pentru a obţine numărul de caractere recepţionate
în stiva de intrare.

La fel ca şi în cazul anterior se poate realiza şi citirea unui singur caracter de la portul
serial prin folosirea funcţiei Read Byte, octetul citit fiind octetul inferior al întregului
returnat de funcţie

int byte = ComRdByte (int COMPort);

Folosirea funcţiilor Callback

După deschiderea portului şi setarea parametrilor auxiliari este indicat să instalăm o


funcţie Callback ce va primi şi va trata evenimentele de pe interfaţa serială. Instalarea unei
asemenea funcţii este utilă pentru a nu se ocupa resursele calculatorului în aşteptarea datelor
utilizând funcţia ComRd. Funcţia Callback ne va scuti de aşteptarea datelor, permiţându-ne
citirea portului de fiecare dată când vom fi anunţaţi că există date prezente.
Funcţia InstallComCallback (Library Æ RS-232 Æ Callbacks… Æ Install COM
Callback) este cea care ne permite instalarea unei funcţii Callback pentru un anumit port
serial.

int InstallComCallback (int COM_Port, int Event_Mask, int Notify_Count,


int Event_Character,
ComCallbackPtr Callback_Function,
void *Callback_Data);

Funcţia va fi apelată pentru toate evenimentele enumerate în Event_Mask. Pentru


fiecare port serial poate fi instalată o singură funcţie Callback. Dacă Event_Mask-ul este 0 se
va dezactiva funcţia Callback. Funcţia poate trata multiple evenimente, dar nu simultan.
Pentru a evita pierderea unor evenimente este de dorit ca funcţia să fie cît mai scurtă, sau să
fie selectate numai evenimentele ce sînt necesare din punct de vedere al aplicaţiei.

Evenimentele specificate prin intermediul parametrului Event_Mask la care poate


răspunde funcţia Callback sunt prezentate în tabelul următor.

- 78 -
Valoare Numele
Bit Evenimentul
hexa evenimentului
0 0x0001 Orice caracter recepţionat LWRS_RXCHAR
1 0x0002 Recepţionarea unui anumit caracter LWRS_RXFLAG
2 0x0004 Listă de transmisie goală LWRS_TXEMPTY
3 0x0008 CTS (Clear To Send) şi-a schimbat starea LWRS_CTS
4 0x0010 DSR (Data Set Ready) şi-a schimbat starea LWRS_DSR
5 0x0020 RLSD (Receive Line Signal Detect) şi-a schimbat starea LWRS_RLSD
6 0x0040 Recepţia caracterului BREAK LWRS_BREAK
Eroare de linie. Erorile pot fi CE_FRAME,
7 0x0080 LWRS_ERR
CE_OVERRUN şi CE_RXPARITY.
8 0x0100 Semnal Ring LWRS_RING
S-a recepţionat numărul de caractere setat prin
15 0x8000 LWRS_RECEIVE
Notify_Count
Tabel 10.1 – Event_Mask

Variabila Notify_Count reprezintă numărul de caractere ce trebuie recepţionate dacă


s-a selectat evenimentul LWRS_RECEIVE. În cazul nostru este egal cu numărul de
caractere cerute, şi este o soluţie de a reduce numărul de evenimente faţă de recepţionarea
individuală a lor. Numărul de caractere cerute nu poate depăşi dimensiunea bufferului de
intrare.

Variabila Event_Character va indica un caracter specific care va genera un eveniment


la recepţie.

Urmează numele funcţiei Callback ca parametru precum şi un pointer către o zonă de


date de 4 octeţi ce pot fi transferaţi ca parametru funcţiei Callback. Funcţia Callback va avea
totdeauna următoarea formă: void CallbackFunctionName (int COM_Port, int Event_Mask,
void *Callback_Data) şi va fi definită de utilizator.
Citirea, transferul caracterelor se va realiza cu funcţii explicite (o parte din ele au fost
prezentate mai sus) în funcţia Callback. Aceasta nu va efectua automat aceste operaţii. Tot
aici se poate testa şi starea portului serial prin funcţii de interogare, dar setarea
corespunzătoare a evenimentelor poate elimina aceste testări.

Programarea portului serial

În continuare se prezintă modalitatea în care se realizează iniţializarea portului serial,


instalarea şi definirea unei funcţii Callback pentru a răspunde la o serie de evenimente.

Exemplu:
/* Iniţializarea portului serial
Viteza:9600bps
Fără paritate

- 79 -
8 biţi
1 bit de stop
Mărimea listelor de transmisie şi recepţie: 50 */

comport = 1;

OpenComConfig (comport, "", 9600, 0, 8, 1, 50, 50);

/* Instalarea funcţiei Callback */

Notify_Count = 50; /* Aşteptăm cel puţin 50 de octeţi în stivă */

Event_Char = ‘#’; /* Aşteapta caracterul ‘#’ */

/*
Evenimentele: Recepţionarea unui anumit caracter (LWRS_RXFLAG) sau
Listă de transmisie goală (LWRS_TXEMPTY) sau
Numărul de caractere cerut prin Notify_Count s-a recepţionat
(LWRS_RECEIVE)
*/

Event_Mask = LWRS_RXFLAG | LWRS_TXEMPTY | LWRS_RECEIVE;

InstallComCallback (comport, Event_Mask, Notify_Count,


EventChar, Functia_Callback, NULL);

...

/* Definirea funcţiei Callback */

void Functia_Callback(int portNo, int Event_Mask, void *data)

if (Event_Mask & LWRS_RXFLAG)

printf("S-a receptionat caracterul specificat\n");

if (Event_Mask & LWRS_TXEMPTY)


{

/* când lista de transmisie este goala, afişez un mesaj şi trimit caracterul ‘k’ la port */

printf("Lista de transmisie este goala\n");

- 80 -
ComWrtByte (comport, ‘k’);
}

if (Event_Mask & LWRS_RECEIVE)

printf("S-au primit 50 de caractere\n");

Temă

Să se realizeze în LabWindows / CVI un program care foloseşte funcţiile din


biblioteca RS-232 şi care implementează transmisia şi recepţia unui caracter (respectiv a
unui şir de caractere) cu următorii parametri de comunicaţie: rata de transmisie 4800bps,
paritate pară, 7 caractere pe cuvânt, 1 bit de stop. Se vor implementa funcţiile Callback
corespunzătoare, iar pentru testare se va folosi o conexiune Null modem între două
calculatoare sau o conexiune în buclă pentru testarea pe acelaşi calculator.

- 81 -
Cap. 11 - Comunicaţii în reţea

TCP (Transport Communication Protocol – protocol de comunicaţie de nivel


transport) a fost proiectat pentru a asigura un flux de date sigur de la un capăt al conexiunii
la celălalt într-o reţea. În principiu acest protocol guvernează modul în care se transmit
datele între calculatoarele aflate în reţea, peste care se va implementa doar nivelul de
aplicaţie.

Aplicaţii bazate pe TCP

Comunicaţiile în reţea care folosesc TCP implică utilizarea unei arhitecturi client-
server. Clientul şi serverul pot fi orice dispozitiv din reţea care poate fi identificat printr-o
adresa IP. Înainte de realizarea conexiunii TCP aplicaţia server trebuie să se înregistreze în
reţea. Prin înregistrare se stabileşte un port prin intermediul căruia clientul va putea accesa
serverul. După înregistrare aplicaţia server va asculta cererile clienţilor. Pentru a transmite o
cerere la server, aplicaţia client trebuie să ştie adresa calculatorului gazdă pe care rulează
serverul şi numărul de port al acestuia. Când serverul primeşte o cerere de la client, acesta o
procesează şi va transmite clientului răspunsul.
Figura următoare indică interacţiunea dintre aplicaţiile server şi client. Întrucât TCP
este un protocol orientat pe conexiune, serverul şi clientul trebuie să fie conectaţi înainte de a
realiza schimbul de date. Transferul de date este bidirecţional atât pentru server care trimite
şi recepţionează date cât şi pentru client care cere şi recepţionează datele.

Figura 11.1 – Comunicaţia TCP

- 82 -
Programarea aplicaţiilor TCP folosind biblioteca de funcţii TCP

Biblioteca de funcţii TCP pusă la dispoziţie de LabWindows / CVI furnizează


funcţiile necesare pentru crearea aplicaţiilor client şi server. Pentru a putea utiliza această
bibliotecă trebuie să existe în sistem fişierul WINSOCK.DLL. Biblioteca TCP din
LabWindows / CVI utilizează Socket-urile Windows (Winsock). Winsock furnizează acces
direct la servciile nivelului de transport folosind protocolul TCP/IP. TCP stabileşte o
conexiune pentru transmiterea datelor, iar IP defineşte modul în care se transmit pachetele de
date.

Funcţiile Callback oferă mecanismul prin care se primesc notificările referitoare la


stabilirea conexiunii, recepţionarea datelor şi terminarea conexiunii. Aplicaţiile client şi
server vor avea fiecare propriile lor funcţii Callback. Clienţii vor declanşa evenimente în
funcţia Callback a serverului şi vice versa.
Funcţiile Callback se apelează în momentul în care se produc următoarele evenimente:
- TCP_CONNECT – Se întâmplă când un client realizează o cerere de conexiune.
Acest eveniment se produce numai la aplicaţia server.
- TCP_DATAREADY – Se întămplă când sunt date disponibile pentru citire.
- TCP_DISCONNECT – Se întămplă când serverul sau clientul termină o conexiune.

În figura următoare se prezintă modul de utilizare a funcţiilor TCP

Figura 11.2 – Funcţiile TCP

- 83 -
Comunicaţia începe în momentul în care o aplicaţie se înregistrează ca un server
valid. Clientul se conectează apoi la server folosind numărul de port specificat în funcţia de
înregistrare a serverului. Când clientul se conectează la server, aplicaţia server recepţionează
evenimentul TCP_CONNECT. Odată stabilită conexiunea ambele aplicaţii (serverul şi
clientul) pot primi evenimentul TCP_DATAREADY, care le indică existenţa unor date
disponibile pentru citire, urmând a se apela funcţiile de citire corespunzătoare. Când serverul
se deconectează aplicaţia client va recepţiona evenimentul TCP_DISCONNECT. În mod
similar când clientul se deconectează de la server, acesta va recepţiona evenimentul
TCP_DISCONNECT. Aplicaţiile client şi server mai pot recepţiona evenimentul
TCP_DISCONNECT în cazul în care conexiunea se termină datorită unei erori.

Aplicaţia server

Pentru a înregistra un server şi a permite altor aplicaţii să se conecteze la el trebuie să


se apeleze funcţia RegisterTCPServer (Library Æ TCP Æ Server Functions Æ Register TCP
Server).

int status = RegisterTCPServer (unsigned int Port_Number,


tcpFuncPtr Server_Callback_Function,
void *Callback_Data);

unde prin Port_Number se specifică numărul de port care va identifica serverul,


Server_Callback_Function reprezintă numele funcţiei Callback care va procesa cererile de la
clienţi (în funcţie de evenimentele amintite), iar parametrul Callback_Data este un pointer
către o zonă de date ce poate fi transferată ca parametru funcţiei Callback.
Deoarece protocolul TCP/IP permite înregistrarea mai multor aplicaţii server pe
acelaşi sistem ele vor fi identificate printr-un număr de port unic (Port_Number) pentru
fiecare aplicaţie. Numărul este ales de programator, dar numerele mai mici de 2000 sunt
rezervate pentru sistem. Nu se pot înregistra două aplicaţii pe acelaşi port.

Funcţia Callback va avea întotdeauna următoarea formă:

int (*tcpFuncPtr) Server_Callback_Function


(int handle, int xType,
int errCode,
void *Callback_Data)

unde: handle identifică conexiunea; xType se referă la evnimentul produs şi poate avea
valorile: TCP_CONNECT, TCP_DISCONNECT sau TCP_DATATREADY. Codul de eroare,
errCode va diferenţia cauzele erorii şi va lua valori negative. Funcţia Callback va utiliza
adresa Callback_Data pentru a diferenţia aplicaţia server pe care o deserveşte sau pentru a

- 84 -
transfera date aplicaţiei fără a utiliza variabile globale. Poate avea valoarea 0 dacă nu se
utilizează.
Funcţia Callback trebuie să fie cît mai scurtă deoarece nu este reentrantă.

La terminarea aplicaţiei serverul trebuie deregistrat folosind funcţia:


UnregisterTCPServer (Library Æ TCP Æ Server Functions Æ Unregister TCP Server)

int status = UnregisterTCPServer (unsigned int Port_Number);

Citirea datelor de către server ar trebui realizată la apariţia evenimentului


TCP_DATATREADY, folosind funcţia ServerTCPRead (Library Æ TCP Æ Server Functions
Æ Server TCP Read), prelucrarea lor nefiind indicată în funcţia Callback.

int ServerTCPRead (unsigned int Conversation_Handle,


void *Data_Buffer, unsigned int Data_Size,
unsigned int Time_Out);

Parametrii acestei funcţii specifică identificatorul conexiunii (Conversation_Handle),


a zonei în care se vor prelua date (Data_Buffer), a lungimii acesteia (Data_Size) precum şi a
valorii timeoutului (Time_Out) în ms.
Transmisia datelor se realizează folosind o funcţie asemănătoare, a cărei parametrii au
aceaşi semnificaţie:

int ServerTCPWrite (unsigned int Conversation_Handle,


void *Data_Pointer, unsigned int Data_Size,
unsigned int Time_Out);

Prin folosirea funcţiei DisconnectTCPClient(unsigned int Conversation_Handle)


(Library Æ TCP Æ Server Functions Æ Disconnect TCP Client) serverul are posibilitatea
de a deconecta un anumit client prin specificarea ca parametru al acestei funcţii al
identificatorului conexiunii (Conversation_Handle) respective.

În continuare se prezintă un exemplu de utilizare a funcţiilor menţionate mai sus

Exemplu:
port = 3333;

RegisterTCPServer (port, Functia_Callback, 0);


int CVICALLBACK Functia_Callback (unsigned handle, int xType,


int errCode, void *callbackData)
{

- 85 -
char date_in[100]; //buffer pentru datele primite
char date_out[100]; //buffer pentru datele transmise

switch (xType) {
case TCP_CONNECT:

/* un nou client s-a conectat la server */

break;

case TCP_DISCONNECT:

/* un client s-a deconectat de la server */

break;

case TCP_DATAREADY:

/* citesc datele primite */


ServerTCPRead(handle, date_in, 100, 0);

/* transmit niste date clientului*/


date_out = “mesaj de test”;
ServerTCPWrite (handle, date_out, 100, 0);

break;
}

return 0;

Aplicaţia client

Pentru a se putea conecta la server clientul trebuie să poată găsi serverul în reţea.
Pentru aceasta el va trebui să cunoască adresa IP a calculatorului gazdă pe care rulează
aplicaţia server şi numărul de port folosit de server la înregistrare. Odată realizată
conexiunea, legătura dintre client şi server va fi permanentă şi pot fi schimbate date până
când acesta se va deconecta sau va fi deconectat de server.

Stabilirea conexiunii cu serverul (identificat prin adresa IP şi port) se face utilizând


funcţia ConnectToTCPServer (Library Æ TCP Æ Client Functions Æ Connect to TCP
Server)

- 86 -
int ConnectToTCPServer (unsigned int *Conversation_Handle,
unsigned int Port_Number,
char Server_Host_Name[],
tcpFuncPtr Client_Callback_Function,
void *Callback_Data,
unsigned int Time_Out);

La conectare clientul va primi un handle ce va fi utilizat pentru identificarea


conexiunii (Conversation_Handle). Port_Number specifică portul care identifică în mod
unic un server aflat pe un anumit calculator. Server_Host_Name reprezintă o adresa IP (ex.
192.168.0.5) sau un nume de calculator gazdă (ex. aaa.bbb.ccc) pe care se află serverul.
Următorul parametru specifică numele funcţiei Callback a clientului
(Client_Callback_Function). Callback_Data este un pointer către o zonă de date ce poate fi
transferată ca parametru funcţiei Callback, iar Time_Out se referă la timpul după care expiră
funcţia în ms.
La fel ca şi la aplicaţia server funcţia Callback a clientului
(Client_Callback_Function) este o funcţie definită de utilizator având forma precizată la
paragraful anterior, singura diferenţă fiind că la aplicaţia client nu se mai recepţionează
evenimentul TCP_CONNECT

Datele vor fi citite sau transmise cu funcţiile ClientTCPRead, respectiv


ClientTCPWrite, care au aceeaşi structură ca şi funcţiile omoloage de la server.

Deconectarea de la server se realizează cu funcţia DisconnectFromTCPServer


(unsigned int Conversation_Handle).

În continuare se prezintă un exemplu de utilizare a funcţiilor TCP pentru aplicaţia


client.

Exemplu:
unsigned int idconex; //identificator conexiune
port = 3333;
ip = “localhost”;

ConnectToTCPServer (&idconex, port, ip, Functia_Callback, 0, 0);

int CVICALLBACK Functia_Callback (unsigned handle, int xType,


int errCode, void *callbackData)
{

char date_in[100]; //buffer pentru datele primite

- 87 -
char date_out[100]; //buffer pentru datele transmise

switch (xType) {

case TCP_DISCONNECT:

/* serverul a fost oprit sau clientul a fost deconectat de server


cu funcţia DisconnectTCPClient*/

break;

case TCP_DATAREADY:

/* citesc datele primite */


ClientTCPRead(handle, date_in, 100, 0);

/* transmit niste date clientului*/


date_out = “mesaj de test”;
ClientTCPWrite (handle, date_out, 100, 0);

break;
}

return 0;

Implementarea comunicaţiei TCP/IP este mult simplificată în mediul Labwindows /


CVI permiţind crearea rapidă a aplicaţiilor de tip client - server. Protocolul la nivelul
aplicaţiei este simplu deoarece serverul va transmite date la orice cerere explicită a
clientului, iar orice eroare de conexiune va fi semnalată atât pe partea de client cât şi pe
partea de server.

Conectarea mai multor clienţi la server

Deoarece mediul LabWindows / CVI nu permite crearea aplicaţiilor pe mai multe fire
de execuţie realizarea unei aplicaţii propriu-zise de tip client server cu mai mulţi clienţi nu
este posibilă. Totuşi există posibilitatea de a realiza mai multe conexiuni la server
identificate printr-un handle, toate aceste conexiuni având ataşată o singură funcţie Callback.
Problema care se iveşte este că la apariţia simultană a mai multor cereri acestea pot fi
pierdute mai ales dacă funcţiile Callback nu sunt suficient de scurte.
Parametrul unsigned handle prin intermediul căruia se poate identifica în mod unic
fiecare conexiune deschisă se întâlneşte la partea de server la funcţia Callback, la funcţiile de
comunicaţie ServerTCPRead şi ServerTCPWrite precum şi la funcţia DisconnectTCPClient.

- 88 -
La aplicaţia client parametrul de identificare a conexiunii (handle) se regăseşte în
toate funcţiile TCP.
După cum precizam şi mai sus, la înregistrarea unui server pe un anumit port, toate
evenimentele TCP pe care acest server le va recepţiona, ca urmare a conectării, deconectării
sau a recepţiei de date, vor fi capturate de o singură funcţie Callback (cea care apare ca
parametru pentru funcţia RegisterTCPServer).
Indiferent de numărul conexiunilor deschise, la apelul funcţiei Callback, cei doi
parametri unsigned handle şi int xType permit identificarea conexiunii (clientului) şi a
evenimentului apărut pe acea conexiune.
Astfel, pentru a se putea realiza corect comunicaţiile apare necesitatea de a efectua o
gestiune a conexiunilor deschise utilizând parametrul unsigned handle.
În continuare vom preciza modul de atribuire al acestor identificatori de conexiune.
Având un server înregistrat pe un anumit port, primul client conectat va primi un handle egal
cu 0. Fiecare nou client conectat va primi un handle cu o valoare consecutivă. (ex. Al doilea
client conectat va primi handle egal cu 1, al treilea va primi handle egal cu 2 ş.a.m.d.). Dacă
între timp unul din clienţii conectaţi se deconectează, handle-ul pe care acesta îl avea este
din nou disponibil şi va fi atribuit următorului client care se va conecta. Dacă la un moment
dat sunt disponibile mai multe handle, primul care se va atribui unui nou client este cel cu
valoarea cea mai mică.
Pentru exemplificare să presupunem că la un moment dat avem conectaţi la un server
5 clienţi, pentru care valorile parametrului handle sunt: 0, 1, 2, 3, 4. Dacă se deconectează
clienţii cu handle egal 1 şi handle egal cu 3, următorul client conectat va primi pentru handle
valoarea 1.
Cu toate că la aplicaţia client, utilizarea parametrului handle nu pare atât de necesară,
facem precizarea că odată stabilită conexiunea unui client oarecare cu serverul acesta va
primi, pe partea de client, un handle egal cu 0. Valoarea handle-ului alocat acestui client, pe
partea de server, va respecta ordinea de conectare după cum s-a menţionat mai sus.
Pentru a putea ţine evidenţa conexiunilor deschise şi implicit pentru a putea identifica
clientul conectat, la aplicaţia server, putem reţine într-un vector (array) toate handle-urile
alocate la un moment dat, cu precizarea că de fiecare dată când un client se conectează sau
se deconectează acest vector (array) trebuie refăcut.
În continuare, în porţiunea de cod următoare se prezintă o modalitate de a reţine
identificatorii conexiunilor deschise şi de a reface această listă ca urmare a deconectărilor.

Exemplu:
/* array global care va retine prin elementele
sale handle-rurile pentru conexiunile deschise */

unsigned lista[20];

/* variabila globala care va retine numarul clientilor


conectati la un moment dat la server */

int nr_clienti=0;

- 89 -
....

int CVICALLBACK ServerCallback(unsigned handle, int xType,


int errCode, void *callbackData)
{
/* functia Callback a serverului */

/* array ajutator pentru realizarea update-ului la lista */


unsigned lista_noua[20];
int i,j;
j=0;

switch (xType)
{ // evenimentele

case TCP_CONNECT:

/* un nou client conectat


adaugam handle-rul acestuia la lista noastra */
lista[nr_clienti] = handle;
nr_clienti=nr_clienti+1;

break;

case TCP_DISCONNECT:

/* client deconectat
refacem lista conexiunilor deschise */

for (i=0;i<nr_clienti;i++)
{
if(lista[i]!=handle)
{
lista_noua[j] = lista[i];
j=j+1;
}
}

lista = lista_noua;
nr_clienti=nr_clienti-1;

break;

- 90 -
case TCP_DATAREADY:

/* date disponibile */

break;

return 0;

Transmiterea şi citirea unor mesaje de la un anumit client se va realiza prin utilizarea


parametrului handle în funcţiile TCP corespunzătoare. Selectarea unui anumit client se poate
face cu uşurinţă prin parcurgerea vectorului.

Temă

Să se realizeze în LabWindows / CVI un program de tip chat care foloseşte funcţiile


din biblioteca TCP şi care permite conectarea la un server a unuia sau a mai multor clienţi
care vor schimba mesaje.

Figura 11.3 – Sugestie de interfaţă pentru aplicaţia chat client / server

- 91 -
Bibliografie:

[1] 54LS42 / DM54LS42 / DM74LS42 BCD/Decimal Decoders – National


Semiconductor datasheet
[2] Aitken, Peter G.; Jones, Bradley L. – “Teach Yourself C Programming in 21
Days”, Sams, 1995
[3] Engdahl, Tomi - Parallel port interfacing made easy: Simple circuits and programs
to show how to use PC parallel port output capabilities
(http://www.epanorama.net/circuits/parallel_output.html)
[4] Haller, Piroska – “Arhitectura calculatoarelor. Îndrumător de laborator”,
Universitatea “Petru Maior” – Tg. Mureş 1996
[5] Haller, Piroska – “Implementarea aplicaţiilor în timp real. Îndrumător de
laborator”, Universitatea “Petru Maior” – Tg. Mureş 1998
[6] Kernighan, Brian W.; Ritchie, Dennis M. - “The C Programming Language.
Second Edition”, Prentice Hall Software Series
[7] LabWindows / CVI Basics, National Instruments Corporation, 1995
[8] Ögren, Joakim - The Hardware Book (WinHelp32), ver. 1.3, 1997
[9] Peacock, Craig - Interfacing the Standard Parallel Port
(http://www.beyondlogic.org/spp/parallel.htm)
[10] Peacock, Craig - Interfacing the Enhanced Parallel Port
(http://www.beyondlogic.org/epp/epp.htm)
[11] Peacock, Craig - Interfacing the Extended Capabilities Port
(http://www.beyondlogic.org/ecp/ecp.htm)
[12] Peacock , Craig - Interfacing the Serial / RS232 Port V5.0
(http://www.beyondlogic.org/serial/serial.htm)
[13] Petrovici, Vasile; Goicea, Florin – “Programarea în limbajul C”, Editura Tehnică,
Bucureşti 1993
[14] Wick, Kurt - C & LabWindows Fundamentals, Phys 4051, 2000

- 92 -

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