Explorați Cărți electronice
Categorii
Explorați Cărți audio
Categorii
Explorați Reviste
Categorii
Explorați Documente
Categorii
Mureş
ARHITECTURA CALCULATOARELOR
Îndrumător de laborator
2005
Cuprins:
Prefaţă 1
Bibliografie 92
Prefaţă
Autorii
-1-
Cap. 1 - Bazele programării în C
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
-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ă;
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 ()
{
}
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’;
Exemplu:
int const a = 1;
sau
const int a =2;
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
Operatori matematici
Operatori relaţionali
-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.
Operatori logici
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
Controlul execuţiei
Instrucţiunea if
if (expresie)
instrucţiune_1;
else
instrucţiune_2;
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ă:
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ă
- 10 -
Cap. 2 – Funcţii, vectori şi pointeri în C
Funcţii
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.
Exemplu:
int func1(int x, float y);
void func2 (char a, int *b);
char func3 ( );
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*/
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ă.
Exemplu:
int valoare;
valoare = nume_funcţie (arg_1, arg_2, …);
Exemplu:
main( )
{
float a = 20; //declaraţii de variabile locale
float temp_F;
- 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
- 14 -
Al doilea element: a[1]
Al treilea element: a[2]
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.
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
Pointeri şi funcţii
- 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.
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) */
- 17 -
Intrare şi ieşire
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ă:
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
- 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ă.
Exemplu:
//afişează pe un rând nou ‘TVA = 19%’
printf (“\nTVA = 19%%”);
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ă:
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ă
- 20 -
Cap. 3 - LabWindows / CVI – Prezentare generală
Setul de editoare
- 21 -
Figura 3.1 – Editorul de cod sursă
- 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);
- 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.
Editorul de help
- 24 -
Setul de librării
- 25 -
Figura 3.4 – Vizualizarea variabilelor
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ă
- 27 -
Cap. 4 - LabWindows/CVI – Crearea şi utilizarea
interfeţei grafice
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.
- 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.
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.
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).
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():
- 31 -
int DiscardPanel (int panelHandle);
Controalele
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:
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.
Generarea codului
Exemplu:
#include <cvirte.h> /* Needed if linking in external compiler; harmless otherwise */
#include <userint.h>
#include "SimpleProject.h"
- 33 -
static int panelHandle;
break;
}
return 0;
}
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.
- 35 -
Temă
- 36 -
Cap. 5 - Simularea circuitelor digitale
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.
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
- 38 -
Figura 5.2 – Modificarea proprietaţilor pentru controlul numeric
x x D B C A x x
Figura 5.3 – Octetul de intrare
- 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 ...);
- 40 -
A B C W
0 0 0 D0
0 0 1 D1
0 1 0 D2
……… …
1 1 1 D7
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:
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
Î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ă
- 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
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.
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
- 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.
- 46 -
Figura 6.2 – Atributele timer-ului
- 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():
- 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.
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ă
- 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
- 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.
- 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.
Î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.
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
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
Î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.
- 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.
- 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;
...
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 */
}
- 58 -
pentru citirea porturilor şi
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Ω.
- 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.
- 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.
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
Apoi vom proceda la citirea părţii celei mai semnificative a octetului, astfel:
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ă.
Pentru selectarea părţii inferioare a octetului, vom seta bitul 0 al registrului de control
pentru a avea valoarea 0 pe pinul 1:
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:
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ă
- 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
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.
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.
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.
- 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.
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.
În continuare se vor prezenta sumar registrele care intră în componenţa 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.
- 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
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.
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
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
- 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ă.
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
- 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 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
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 (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
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.
- 74 -
Exemplu:
/* iniţializarea portului serial
viteza = 4800 bps
fără paritate
caracter de 8 biţi
1 bit de stop */
outportb (baza + 0, 0x18); //setare viteza – octet inferior divizor (vezi tabel 4)
outportb (baza + 1, 0x00); //setare viteza – octet superior divizor
/* 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) */
/*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);
Temă
- 75 -
Cap. 10 - Programarea portului serial în
LabWindows / CVI
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
Pentru a transmite date dintr-un buffer unui echipament serial se foloseşte funcţia
ComWrt (Library Æ RS-232 Æ Input/Output Æ Write Buffer).
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.
- 77 -
Citirea portului serial
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
- 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
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;
/*
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)
*/
...
/* când lista de transmisie este goala, afişez un mesaj şi trimit caracterul ‘k’ la port */
- 80 -
ComWrtByte (comport, ‘k’);
}
Temă
- 81 -
Cap. 11 - Comunicaţii în reţea
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.
- 82 -
Programarea aplicaţiilor TCP folosind biblioteca de funcţii 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
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ă.
Exemplu:
port = 3333;
- 85 -
char date_in[100]; //buffer pentru datele primite
char date_out[100]; //buffer pentru datele transmise
switch (xType) {
case TCP_CONNECT:
break;
case TCP_DISCONNECT:
break;
case TCP_DATAREADY:
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.
- 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);
Exemplu:
unsigned int idconex; //identificator conexiune
port = 3333;
ip = “localhost”;
- 87 -
char date_out[100]; //buffer pentru datele transmise
switch (xType) {
case TCP_DISCONNECT:
break;
case TCP_DATAREADY:
break;
}
return 0;
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];
int nr_clienti=0;
- 89 -
....
switch (xType)
{ // evenimentele
case TCP_CONNECT:
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;
Temă
- 91 -
Bibliografie:
- 92 -