TRASMISSIONE SERIAlE

Transcript

TRASMISSIONE SERIAlE
Relazione di Sistemi
Trasmissione
Seriale
Standard RS232/C
Protocollo Master/Slave con Eco
Sviluppato in Linguaggio C++
Andrea Asta
Autore del fascicolo
Nome
Sito Web
Email
Classe
Andrea Asta
www.andrea-asta.com
[email protected]
5A INFO
Sommario
Autore del fascicolo............................................................................................................................2
Sommario............................................................................................................................................2
Introduzione .......................................................................................................................................3
1.1
Titolo del progetto................................................................................................................3
1.2
Descrizione del progetto ......................................................................................................3
2. Progettazione generale del sistema...............................................................................................3
2.1
Strati di progettazione ..........................................................................................................3
2.2
Strato fisico ..........................................................................................................................4
2.3
Strato logico .........................................................................................................................4
2.4
Strato di programmazione....................................................................................................5
2.5
Riepilogo dei vari strati........................................................................................................6
3. Analisi approfondita dello strato fisico ........................................................................................7
3.1
La trasmissione in generale..................................................................................................7
3.2
La trasmissione seriale asincrona.........................................................................................8
3.3
Standard RS232/C..............................................................................................................13
3.5
Trasmissione RS232/C Null – Modem ..............................................................................15
3.6
Programmazione RS232/C.................................................................................................16
4. Analisi approfondita dello strato logico .....................................................................................19
4.1
Definizione del protocollo .................................................................................................19
4.2
Studio del protocollo..........................................................................................................20
4.3
Limitazioni note .................................................................................................................24
4.4
Algoritmo "Master Trasmette"...........................................................................................25
4.5
Algoritmo "Master Riceve" ...............................................................................................27
4.6
Unione degli algoritmi .......................................................................................................28
5. Programmazione ..........................................................................................................................29
5.1
Scelta del linguaggio di programmazione..........................................................................29
5.2
Routine di base del programma .........................................................................................29
5.3
Traduzione degli algoritmi.................................................................................................32
5.5
Software di supporto ..........................................................................................................33
5.6
Codice completo del programma .......................................................................................33
6. Conclusioni ...................................................................................................................................42
6.1
Conclusioni ........................................................................................................................42
6.2
Fonti ...................................................................................................................................42
7. Allegati ..........................................................................................................................................43
7.1
Documentazione _bios_serialcom()...................................................................................43
7.2
Documentazione clock() ....................................................................................................45
2
Introduzione
1.1 Titolo del progetto
Realizzazione di un sistema di comunicazione seriale RS232/C, tramite la definizione di un protocollo Master/Slave con eco.
1.2 Descrizione del progetto
L'intento prefissato è l'ideazione, progettazione e realizzazione di un
sistema di comunicazione tra sistemi a microprocessore, attraverso la
porta seriale RS232.
2. Progettazione generale del sistema
2.1 Strati di progettazione
Il progetto sarà analizzato e realizzato a diversi strati, che permetteranno un incremento graduale delle prestazioni del sistema. In altre parole, un primo livello di base permetterà la comunicazione fisica tra i
due microprocessori; un secondo strato prevederà la definizione del sistema logico di comunicazione (in che modo, in che ordine i due elaboratori comunicheranno); infine, lo strato più esterno sarà costituito
dal vero e proprio codice.
1. Strato fisico
2. Strato logico
3. Strato di programmazione
Figura 1: Strati di progettazione
Ogni strato, come si può osservare dall'immagine, è strettamente
dipendente dallo strato di livello inferiore: in particolare, possiamo dire che uno strato esterno eredita, amplia e dipende dalle caratteristiche
dello strato immediatamente più interno. Eliminando uno strato interno, anche tutti quelli immediatamente esterni perdono significatività.
3
Da questo si deduce come, prima di poter passare alla creazione di
uno strato superiore, sia vitale testare il perfetto funzionamento di tutti
gli strati più interni, in ogni sua parte. Infatti, come spiegato, ogni strato esterno è strettamente dipendente dagli strati interni, non può esistere senza di essi; ne consegue che un errore in uno strato interno compromette il funzionamento anche dello strato esterno.
2.2 Strato fisico
Lo strato fisico riguarda l'analisi e la gestione della comunicazione elementare tra due microprocessori. Per quanto stabilito, la trasmissione
dovrà essere di tipo seriale e si atterrà allo standard di comunicazione
RS232/C.
Lo strato fisico comprende quindi la gestione a livello hardware
della comunicazione tra due microprocessori, il modo utilizzato per la
sincronizzazione e i controlli basilari degli errori fisici di trasmissione.
Lo scopo finale dello strato fisico è quello di fornire algoritmi
chiari all'utente per la trasmissione e la ricezione di un carattere attraverso la porta seriale RS232/C del microprocessore.
2.3 Strato logico
Completato lo strato basilare del progetto, ossia la trasmissione e la ricezione elementare attraverso lo standard RS232/C, è necessario ampliare lo studio del progetto e aggiungere una serie di controlli aggiuntivi che definiscano un protocollo chiaro di comunicazione.
In altre parole, una volta che i due microprocessori hanno a disposizione gli strumenti necessari per comunicare (strato fisico) è necessario che si prendano degli accordi sul modo in cui essi dovranno scambiarsi i dati. Ad esempio, all'inizio dell'esecuzione, chi dovrà inviare
un dato e chi dovrà riceverlo? Come potranno i due microprocessori
sincronizzarsi? In che modo potremo verificare l'esattezza del dato ricevuto? Tutte queste domande troveranno risposta nel nostro protocollo.
In generale, un protocollo di comunicazione è un insieme di regole rigorose, chiare, il più possibili elementari e comunemente accettate
che vengono definite per instaurare una comunicazione corretta.
Con il termine rigorose si intende che esse devono essere definite e
non ambigue: in ogni istante della comunicazione deve essere chiaro in
modo univoco che cosa si vuole fare.
Con il termine chiare si intende invece il fatto che i metodi utilizzati da un protocollo per instaurare la comunicazione devono essere di
comprensione immediata, non ci devono essere incomprensioni tra i
sistemi che tentano di comunicare.
Inoltre, siccome in genere il protocollo utilizza lo strato fisico per
trasmettere parole speciali utilizzate come comandi, è necessario che
esse siano il più possibile semplici ed elementari: bisogna escludere
ogni comando superfluo, in quanto ogni trasmissione e ricezione in eccesso rallenta, anche se in minima parte, il tempo di esecuzione dell'intera trasmissione. Se, ad esempio, per trasmettere un singolo carattere,
fosse prima necessario inviare una decina di parole chiave, più una decina di parole alla fine, probabilmente il protocollo definito non sareb4
be elementare e, in generale, il tempo speso per la trasmissione dei
comandi sarebbe molto superiore rispetto a quello della trasmissione
del dato.
Infine, il protocollo definito deve essere comunemente noto e accettato, nel senso che esso deve essere rispettato da tutti i sistemi interessati nella comunicazione.
Va aggiunto che, generalmente, lo strato fisico fornisce le routine
per la gestione di un'unità elementare di dato: ad esempio, normalmente, con le routine RS232 non si potrà inviare più di un byte. Scopo del
protocollo che sarà definito è anche quello di permettere l'invio di una
quantità superiore di dati (generalmente una sequenza di dati elementari) e controllare, mediante appositi strumenti, la correttezza dell'intera sequenza inviata o ricevuta.
2.4 Strato di programmazione
In realtà lo strato di programmazione non è un vero e proprio strato di
progettazione, nel senso che esso dovrà, banalmente, tradurre lo strato
fisico in codice e lo strato logico in codice (che utilizzi quello dello
strato fisico): tuttavia, è bene ricordare che ogni linguaggio di programmazione, per sua natura, ha pregi e difetti, quindi è possibile che,
a seconda del linguaggio scelto, sia necessario applicare qualche piccola modifica, qualche piccolo cambiamento al progetto iniziale.
La gestione di input esterni, inoltre, porterà a controlli aggiuntivi,
pertanto possiamo dire che il codice è, seppur in forma ridotta, un
nuovo strato di progettazione, che deve essere curato come gli altri
due.
Lo strato di programmazione sarà quindi quello che, oltre a tradurre gli algoritmi degli strati fisico e logico, aggiungerà l'interattività finale con l'utente esterno: ad esempio, si occuperà di ricevere in input i
dati da trasmettere, o di fornire in output i dati ricevuti.
Sarà quindi suo compito principale quello di rendere la trasmissione trasparente all'utilizzatore, ricevendo in input solamente le informazioni strettamente necessarie e fornendo in output le informazioni più importanti. In altre parole, lo strado di programmazione dovrà
fornire un'interfaccia elementare e trasparente all'utente finale.
Il prodotto finale dello strato di programmazione risulterà quindi
un software di semplice utilizzo, che permetta a chiunque di scambiare
dati mediante la porta seriale.
L'utente finale non dovrà conoscere i dettagli del protocollo di comunicazione e, tanto meno, dovrà essere a conoscenza dello strato fisico del progetto, ossia dei metodi hardware utilizzati per la trasmissione. Tutto ciò che gli sarà richiesto sarà di connettere i due sistemi mediante un cavo Null – Modem apposito e di eseguire il software da noi
creato.
5
2.5 Riepilogo dei vari strati
La tabella seguente riassume in forma sintetica le caratteristiche ed i
compiti dei vari strati utilizzati per questo progetto.
Strato
Compiti
ƒ Gestione hardware della comunicazione
Fisico
ƒ Algoritmi efficienti per la comunicazione elementare
ƒ Definizione di un protocollo di comunicazione
Logico
ƒ Gestione degli errori di comunicazione
ƒ Creazione di un'interfaccia per l'utente finale
ƒ Definizione di un sistema di interaProgrammazione
zione con l'utente
ƒ Traduzione degli algoritmi fisici e
logici in programma
Caratteristiche
ƒ Efficiente
ƒ Rigoroso
ƒ Chiaro
ƒ Elementare
ƒ Comunemente accettato
ƒ Semplice
ƒ Intuitiva
ƒ Trasparente
ƒ Efficiente
6
3. Analisi approfondita dello strato fisico
3.1 La trasmissione in generale
Esistono diverse modalità di comunicazione tra due o più sistemi.
Quella più elementare è semplicemente quella in cui i vari sistemi sono fisicamente collegati per mezzo di una serie di connessioni elettriche collegate a delle porte di input e output del sistema.
Sistema
A
Port
X1
Sistema
B
Connessione fisica
Port
X2
Figura 2: Collegamento fisico tra due sistemi
Esistono altri tipi di trasmissione: ad esempio esiste la trasmissione
via linee telefoniche, la trasmissione via fibre ottiche, via raggi infrarossi, via bluetooth, via radio eccetera.
Nel nostro progetto ci occuperemo della trasmissione tramite collegamento fisico.
A questo proposito, esistono ancora molti tipi di trasmissione, raggruppabili in genere in due grandi famiglie:
ƒ Trasmissione seriale
ƒ Trasmissione parallela
In generale, la differenza tra questi due tipi di comunicazione sta
nel modo utilizzato per trasmettere (e, di conseguenza, ricevere) un dato: nel primo caso il dato è mandato bit per bit, quindi è necessaria una
sola linea dati per la trasmissione. Nel secondo caso, invece, tutto il
dato è trasmesso contemporaneamente, quindi saranno necessarie tante
linee quanti sono i bit da trasmettere.
Sistema
A
Parallel
#01
Sistema
B
n
Serial
#01
Parallel
#02
Serial
#02
Figura 3: Comunicazione seriale e parallela
Ogni porta di input o output, in genere, dispone anche di ulteriori
segnali di controllo, chiamati segnali di hand – shaking. Questi se7
gnali sono ovviamente diversi per la trasmissione seriale e per quella
parallela, in quando in ognuna delle due trasmissioni ci sono dei controlli differenti da effettuare.
Dalla descrizione, seppur sommaria, dei due tipi di trasmissione, si
possono dedurre i principali pregi e difetti di questi due tipi di comunicazione.
Trasmissione
Seriale
Parallela
Pregi
Difetti
ƒ E' sufficiente una sola linea
dati per la trasmissione dei
dati
ƒ Minor rischio di interferenze
ƒ Costi più ridotti
ƒ Fili più lunghi
ƒ Trasmissione più veloce
ƒ Trasmissione più lenta
ƒ Necessaria una linea dati per
ogni bit, quindi utilizzo di
cavi grossi ed ingombranti
ƒ Maggior rischio di interferenze
ƒ Costi più elevati
Va sottolineato inoltre che, negli ultimi anni, con l'introduzione
delle trasmissioni seriali USB e FireWire, la velocità di trasmissione è
cresciuta esponenzialmente, tanto da rendere queste due porte più veloci anche della trasmissione parallela.
Velocità di comunicazione
Parallela
150KB/s - 2.4MB/s
RS232
10,5 Kb/sec - 115,2 Kb/sec
USB
480 Mb/sec (USB 2.0)
FireWire
400 Mb/sec
3.2 La trasmissione seriale asincrona
La trasmissione seriale nasce essenzialmente per collegare il microprocessore ad un modem (modulatore demodulatore), in grado di
modulare i dati in frequenza, fase ed ampiezza e quindi trasferirli, per
mezzo della linea telefonica, ad un altro modem. Nella trasmissione
seriale si è soliti indicare il sistema microprocessore con la sigla DTE
(Data Terminal Equipment) e il modem con DCE (Data Communication Equipment).
Linea
telefonica
Seriale
Seriale
Figura 4: Comunicazione con modem
8
Il primo problema che è necessario risolvere in una trasmissione
seriale è il seguente: il microprocessore, per sua natura, lavora sempre
e solo con dati paralleli: i suoi bus, infatti, non sono, per motivi ovvi
legati alla velocità, di tipo seriale: ogni dato inviato in uscita sulle linee del Data Bus, quindi, sarà di tipo parallelo. L'unità elementare di
trasmissione è, generalmente, il byte.
C'è quindi la necessità di convertire il dato parallelo in seriale: in
termini tecnici questo problema è noto come serializzazione del dato.
Per questo scopo esiste una particolare porta, chiamata UART (Unità
Asincrona Ricezione Trasmissione) o USART (Unità Sincrona e Asincrona Ricezione Trasmissione), in grado di serializzare il dato e gestire
tutta la trasmissione seriale in generale.
8
uP
UART
8250
Figura 5: Interfaccia seriale
Le UART e le USART sono dette interfacce seriali e sono presenti sul mercato sotto forma di circuiti integrati interfacciabili con i diversi microprocessori: ad esempio, per la famiglia Intel 80XX sono disponibili gli integrati 8250 (UART) e 8251 (USART).
Recentemente il nome UART è caduto in disuso, in favore del più
recente ACE (Elementi di comunicazione asincrona).
Da quanto appena detto si deduce che esistono almeno due tipi di
comunicazione seriale:
ƒ Comunicazione sincrona
ƒ Comunicazione asincrona
Nella trasmissione asincrona il significato di ogni bit ricevuto dipende dalla sua collocazione nell'ambito temporale, mentre nella trasmissione sincrona è aggiunta una linea di clock che permette la sincronizzazione tra i due dispositivi.
La trasmissione sincrona è, nella pratica, poco utilizzata, in quanto
perdere il sincronismo tra due clock è decisamente semplice e, perso il
sincronismo di clock, la trasmissione è destinata ad avere esito negativo. Inoltre, tra due sistemi a microprocessore, l'aggiunta di una linea di
clock è del tutto superflua.
Nella trasmissione asincrona, in genere, i due sistemi si sincronizzano prima dell'invio di ogni singolo pacchetto di dati, in questo modo
la trasmissione risulta ancora più rallentata, ma decisamente più affidabile.
La trasmissione asincrona è caratterizzata dall'invio di un frame di
dati: con questo termine si intende il dato vero e proprio (serializzato),
più una serie di bit aggiuntivi utilizzati per il controllo del flusso di
trasmissione: in altre parole, sulla linea dati non viaggiano solamente i
veri e propri dati, ma anche alcuni segnali utilizzati per l'hand - shaking.
9
Per quanto appena detto, un esempio di frame potrebbe essere:
1. Segnale di inizio trasmissione
2. Dato serializzato
3. Segnale di fine trasmissione
La trasmissione asincrona, quindi, non necessita di una linea aggiuntiva per il clock: è intuibile tuttavia come sia necessaria una forma
di sincronismo tra i due dispositivi in comunicazione.
Generalmente il sincronismo è definito grazie ad un parametro della comunicazione, la velocità di trasmissione.
I due dispositivi, quindi, lavoreranno alla stessa velocità di trasmissione: una volta che è stato ricevuto il segnale di inizio trasmissione, il ricevitore controllerà la linea dati alla velocità impostata, segnalando il valore come bit del dato. Dopo 8 letture (se il dato è a 8
bit), si riceverà il bit di fine trasmissione e la comunicazione sarà terminata.
La velocità di trasmissione è misurabile mediante due unità di misura differenti: i bps (bit per second) o baud.
I bps indicano, come indica il nome, il numero di bit trasmessi nell'unità di tempo.
Il baud, invece, indica il numero di transizioni che avvengono sulla
linea nell'unità di tempo.
Se, ad esempio, la trasmissione avviene attraverso valori di tensione variabili tra 0V e 7V e ogni singolo Volt corrisponde ad una parola
diversa a 3 bit (0V = 000, 7V = 111), è logico pensare che, con una sola transizione, sarò in grado di trasmettere 3 bit: in altre parole, in un
caso del genere la velocità espressa in baudrate è un terzo di quella espressa in bps.
Se la trasmissione si serve di due soli valori logici '0' e '1', come
nella maggior parte dei casi, è logico che ogni transizione permetterà
di trasmettere un solo bit, quindi baudrate e bps coincideranno.
Riassumendo, se con una transizione si trasmettono 2n bit, si può
scrivere la seguente relazione:
1 baud =
1
bps
n
Volendo essere più generali, se con una transizione sono trasmessi
k bit, la relazione tra baud e bps diventa la seguente:
1 baud =
1
bps
log 2 k
In genere, la linea dato è a riposo sul valore logico '1'. Nel momento in cui il suo valore è spostato sul valore '0', la trasmissione ha inizio
e i due dispositivi inizieranno a trasferire i bit del dato.
In particolare, il trasmettitore avrà cura di cambiare la linea del bit
opportunamente, mentre il ricevitore si limiterà a leggere la linea dati
in relazione alla velocità di trasmissione impostata.
Terminato l'invio, la linea tornerà a riposo e la trasmissione del
frame sarà ultimata.
10
Dato
'1'
d0
d1
d2
Riposo
d3
d4
d5
d6
d7
Dato
S
Stop + Riposo
t
Start
Figura 6: Esempio di invio di un frame
Il primo problema che sorge quando si parla di sincronismo è l'istante in cui il dato deve essere campionato: se, ad esempio, la velocità
di trasmissione è impostata a 300b/s, il bit dovrà essere campionato
ogni
ds
ds
1b
1
v=
⇒ dt =
=
=
s
b 300
dt
v
300
s
Tuttavia, se si legge esattamente ogni trecentesimo di secondo, anche il più piccolo ritardo nel cambiamento della linea da parte del trasmettitore causerebbe un errore nella comunicazione (ad esempio, se il
ricevitore legge poco prima che il trasmettitore cambi la linea, si avrebbe una lettura errata). Generalmente le UART risolvono il problema campionando il bit a metà del periodo, in questo modo la probabilità che un piccolo ritardo generi errori di trasmissione è molto
bassa. In questo modo, quando la linea viene posta a '0', il ricevitore
lascerà passare la metà di un trecentesimo di secondo (trovandosi
quindi a metà del periodo di permanenza dello '0'), quindi inizierà a
campionare regolarmente ogni trecentesimo di secondo.
Dato
T/2
T
Campionamenti
'1'
d0
d1
d2
d3
d4
d5
d6
d7
S1
S2
t
Figura 7: Campionamento dei bit
Va notato che la fine della trasmissione è trattata esattamente come
un ulteriore bit: in altre parole, la linea portata allo stato di riposo è
nuovamente campionata. Il bit così rilevato è chiamato bit di stop. In
alcuni casi, per maggiore sicurezza durante la trasmissione, la linea è
11
mantenuta alta per un secondo ciclo, in questo caso la comunicazione
avrà due bit di stop.
Al frame di comunicazione, infine, è spesso aggiunto il bit di parità, che può essere pari o dispari1. In questo caso, il frame necessita di
un'ulteriore aggiunta, generalmente posta prima dei bit di stop.
Dato
'1'
d0
d1
Riposo
d2
d3
d4
d5
d6
d7
P
S2
t
Dato
Start
S1
Parità
Bit di Stop + Riposo
Figura 8: Frame completo
Va notato che il controllo sul bit di parità è totalmente affidato all'hardware, non al software. In altre parole, sarà la UART del ricevitore a valutare la correttezza del bit di parità e fornire segnali di hand –
shaking appropriati.
Dallo studio del frame si evince come, in effetti, la trasmissione seriale asincrona risulti da 10 (dato, start, stop) a 13 (dato, start, doppio
stop, parità) volte più lento di una trasmissione parallela. Come detto,
tuttavia, si hanno vantaggi legati all'affidabilità e alla lunghezza massima dei cavi di connessione.
Le trasmissioni seriali possono essere di tre tipi:
ƒ Simplex
ƒ Half – Duplex
ƒ Full – Duplex
Questa divisione indica in quale direzione è possibile comunicare:
nella comunicazione simplex, essa avviene "a senso unico", ossia in
una sola direzione. Nella trasmissione duplex, invece, è possibile avere
comunicazione bidirezionale, con una piccola differenza: nel caso della half – duplex, la trasmissione bidirezionale è possibile, ma non può
essere contemporanea, caratteristica permessa invece nelle comunicazioni full – duplex.
In conclusione, affinché una trasmissione seriale asincrona avvenga in modo corretto, i due dispositivi devono avere accordi precisi e
comuni riguardo a:
ƒ Numero di bit del dato
ƒ Numero di bit di stop
ƒ Numero di bit di parità e sua tipologia
ƒ Velocità di trasmissione
1
Il bit di parità pari è il numero di '1' da aggiungere al dato affinché il numero di '1' al suo interno sia pari. Ad esempio,
se il dato è 10101100, il bit di parità pari è '0', visto che il numero di '1' nel dato è 4, ossia pari. La parità dispari funziona allo stesso modo, ma indica il numero di '1' da aggiungere affinché il numero di '1' al suo interno sia dispari.
12
3.3 Standard RS232/C
Lo standard RS232 nacque nell'anno 1962 per opera della Electronic
Industries Association ed era orientato alla comunicazione tra i mainframe e i terminali attraverso la linea telefonica e un modem. Esso includeva le caratteristiche elettriche dei segnali, la struttura e temporizzazioni dei dati seriali, la definizione dei segnali e dei protocolli per il
controllo del flusso di dati seriali su un canale telefonico, il connettore
e la disposizione dei suoi pin ed infine il tipo e la lunghezza massima
dei possibili cavi di collegamento.
Nel corso degli anni lo standard fu più volte corretto e sistemato,
per poter essere accettato su scala sempre più vasta: nell'anno 1962 fu
definita la versione più famosa e ancora oggi più diffusa, la versione
RS232/C. Tuttavia, la sua diffusione si ebbe solo nel 1982, quando per
la prima volta l'IBM incluse in un suo computer una porta COM, la
prima porta seriale diffusa su scala mondiale.
Da allora seguirono altre versioni fino alla RS232/F, ma la maggior parte dei sistemi a microprocessore integra oggi solamente la versione C. C'è da aggiungere, inoltre, che la comunicazione seriale qui
descritta sta lentamente scomparendo dai moderni calcolatori, per dare
spazio alle più veloci tecnologie USB, FireWire, Ethernet e in fibra ottica.
Lo standard RS232/C è caratterizzato dalle seguenti proprietà:
ƒ La trasmissione è di tipo seriale asincrona
ƒ La trasmissione è di tipo full – duplex
ƒ E' possibile inserire 1 o 2 bit di stop (in alcuni casi anche 1 e ½)
ƒ Ogni transizione permette la trasmissione di un bit, quindi il baud
rate e i bps coincidono
ƒ Connettori a 9 pin (originariamente 25 di cui 10 significativi)
Lo standard, inoltre, si serve di particolari valori elettrici per definire i valori logici '0' e '1', diversi da quelli utilizzati, ad esempio, dalle
tecnologie TTL o CMOS.
Valore Logico
Tensione TTL
Tensione RS232/C
Denominazione
'0'
'1'
0V
5V
Da +3V a +25V
Da -3V a -25V
Space
Mark
La velocità di trasmissione è legata alla lunghezza massima dei cavi:
Baud Rate (baud)
Distanza (m)
50
110
300
600
1200
2400
3600
4800
7200
9600
19200
4800
2182
800
400
200
100
67
50
33
25
14
13
La tabella seguente illustra i nomi dei 9 segnali significativi per lo
standard.
Segnale
TD
RD
RTS
CTS
DCD
DTR
DSR
SGND
RI
Significato
Direzione
Transmitted Data
Received Data
Request To Send
Clear To Send
Data Carrier Detect
Data Terminal Ready
Data Set Ready
Signal Ground
Ring Indicator
DTE Æ DCE
DTE Å DCE
DTE Æ DCE
DTE Å DCE
DTE Å DCE
DTE Æ DCE
DTE Å DCE
--DTE Å DCE
Quando il sistema entra in funzione, il DTE attiva la linea Data
Terminal Ready. Il DCE, a sua volta, quando pronto attiverà la linea
Data Set Ready. Infine, il DCE controllerà se è disponibile un dispositivo esterno in ascolto (in altre parole, se esiste un altro modem pronto a ricevere, attraverso la linea telefonica, ciò che stiamo trasmettendo) e fornirà l'esito mediante il segnale Data Carrier Detect.
A questo punto, i due dispositivi sono pronti per la comunicazione
seriale.
Quando il DTE vuole trasmettere un dato serializzato, attiva la linea Request To Send e attende il "via libera" dal DCE mediante la linea Clear To Send: in generale, prima di attivarla, il DCE controllerà
la portante. A questo punto il DTE è pronto ad inviare un dato sulla linea Transmitted Data. Se, invece, è il DCE a dover trasmettere, la linea utilizzata sarà la Received Data.
Il modo più semplice e diffuso per connettere i due terminali è
quello di connettere semplicemente ogni segnale con l'omonimo nel
dispositivo di destinazione.
DTE
DCE
TD
RD
RTS
CTS
DCD
DTR
DSR
SGND
RI
TD
RD
RTS
CTS
DCD
DTR
DSR
SGND
RI
Figura 9: Connessione RS232/C tra DTE e DCE
Tuttavia, esistono altri diversi modi di connessione.
14
In alcuni casi, i cavi seriali incrociano alcuni segnali, in modo da
avere più segnali di hand – shaking anche quando è il DCE a dover
trasmettere un dato.
DTE
DCE
TD
RD
RTS
CTS
DCD
DTR
DSR
SGND
RI
TD
RD
RTS
CTS
DCD
DTR
DSR
SGND
RI
Figura 10: Collegamento con incroci
3.5 Trasmissione RS232/C Null – Modem
Spesso si ha la necessità, come nel nostro progetto, di connettere due
computer, ma non si ha invece bisogno di passare attraverso un modem: ad esempio, se i due terminali risiedono ad una breve distanza tra
loro, sarebbe superfluo passare per il modem.
Lo standard RS232/C può essere adattato anche per creare una
connessione, detta Null – Modem, che permette di collegare due terminali senza necessità di un modem.
Anche in questo caso, esistono differenti versioni di collegamento:
di seguito è riportata la più diffusa, nonché quella da noi utilizzata.
DTE1
DTE2
TD
RD
RTS
CTS
DCD
DTR
DSR
SGND
RI
TD
RD
RTS
CTS
DCD
DTR
DSR
SGND
RI
Figura 11: Connessione Null – Modem
Come si nota, alcuni segnali sono corto – circuitati, infatti l'assenza
di modem comporta il fatto che non ci sia bisogno di rilevare portanti
o verificare l'accensione di questo dispositivo. Allo stesso modo,
15
quando il primo terminale è pronto a trasmettere, può farlo senza aspettare conferme, visto che il modem non è presente.
3.6 Programmazione RS232/C
La porta RS232 è programmabile in tre modi diversi:
ƒ Accedendo direttamente alla porta
ƒ Attraverso gli interrupt BIOS
ƒ Attraverso gli interrupt DOS
Nel nostro progetto, sviluppato sotto ambiente Windows XP, ci
serviremo degli interrupt BIOS, che rappresentano un buon compromesso tra semplicità d'uso ed affidabilità (che sarebbe maggiore nel
caso di accesso diretto alla porta).
L'interrupt software BIOS 14h fornisce una serie di routine per la
gestione delle porte COM1, COM2, COM3, COM4.
Dei vari servizi a disposizione, ce ne sono quattro particolarmente
importanti:
Servizio 0: Inizializzazione porta seriale
Servizio 1: Invio di un carattere
Servizio 2: Ricezione di un carattere
Servizio 3: Stato della porta
Ognuno di questi servizi prevede dei parametri in ingresso, tra cui,
sempre, il numero di porta COM a cui ci si riferisce.
In generale, ogni porta utilizzata deve essere inizializzata, quindi è
possibile richiedere operazioni di invio e ricezione, controllando eventualmente lo stato della porta in caso di errori.
Inizializzazione della porta
Questa operazione è necessaria per programmare la porta seriale, impostando quindi i corretti valori per:
ƒ Velocità di trasferimento
ƒ Bit di parità
ƒ Lunghezza della parola
ƒ Numero di bit di stop
E' logico che la programmazione della porta seriale dovrà essere
identica su tutti e due i dispositivi interessati alla trasmissione.
La parola di programmazione è generalmente costituita da un byte,
che racchiude al suo interno le informazioni sopraccitate. In particolare
esso è così costituito:
b7
V2
b0
V1
V0
P1
P0
S
L1
L0
Figura 12: Programmazione della porta seriale
I tre bit più significativi servono ad impostare la velocità di trasferimento, secondo questi valori:
16
b7…b5
Baud Rate (baud)
000
001
010
011
100
101
110
111
110
150
300
600
1200
2400
4800
9600
I successivi due bit impostano la parità:
b4-b3
00
01
10
11
Parità
Nessun bit di parità
Parità dispari
Nessun bit di parità
Parità pari
Il bit successivo indica quanti bit di stop devono essere inclusi nel
frame:
b2
Stop
0
1
1 bit di stop
2 bit di stop
Gli ultimi due bit, infine, impostano la lunghezza delle parole da
trasferire:
b1-b0
00
01
10
11
Parità
Configurazione non ammessa
Configurazione non ammessa
Parole a 7 bit
Parole a 8 bit
Il risultato della programmazione è esattamente uguale al risultato
dell'invocazione dello stato della porta seriale e sarà quindi descritto in
seguito.
Utilizzo della porta
Una volta programmata, la porta è pronta ad essere utilizzata. In generale, le routine di invio e ricezione utilizzeranno una variabile a 7 o 8
bit (a seconda di come è stata programmata la porta) per l'invio o la ricezione di un carattere. Esisterà poi un flag che indicherà la corretta
esecuzione dell'operazione. In caso di errore, sarà possibile richiamare
la routine di status per maggiori informazioni.
Stato della porta
Il servizio di stato della porta seriale restituisce due byte, in cui il primo indica lo stato della linea, il secondo lo stato del modem.
Se, come nel nostro caso, si utilizza una connessione Null - Modem del tipo precedentemente descritto, è chiaro che lo stato del mo17
dem sarà irrilevante, visto che non vi è interconnessione tra i segnali di
hand – shaking di un dispositivo con quelli dell'altro.
Stato della linea
b7
b6
b5
b4
b3
b2
b1
b0
Significato
1
Dato pronto da ricevere
Errore di sovrapposizione (Overrun Error)
Errore di parità (Parity Error)
Errore di composizione (Framing Error)
Segnale di Break (Break Interrupt)
Registro di trasmissione THR vuoto
Registro di trasmissione TSR vuoto
Errore di timeout (Timeout Error)
b0
Significato
1
La linea CTS ha cambiato valore dopo l'ultima lettura
La linea DSR ha cambiato valore dopo l'ultima lettura
La linea RI ha cambiato valore dopo l'ultima lettura
La linea DCD ha cambiato valore dopo l'ultima lettura
Valore del segnale CTS
Valore del segnale DSR
Valore del segnale RI
Valore del segnale DCD
1
1
1
1
1
1
1
Stato del modem
b7
b6
b5
b4
b3
b2
b1
1
1
1
X
X
X
X
In generale, prima di una trasmissione bisognerà attendere che i
registri di trasmissione siano vuoti (tutto ciò che prima era presente è
stato trasmesso, i due registri vengono svuotati solo alla fine della trasmissione), mentre prima di una ricezione bisognerà verificare che sia
settato il bit di Data is Ready, che indica se è presente un nuovo dato
da leggere.
Nel nostro programma implementeremo una funzione con un timeout, in modo che il programma non rimanga all'infinito a provare la
trasmissione o la ricezione, ma solo per un tempo prefissato.
18
4. Analisi approfondita dello strato logico
4.1 Definizione del protocollo
Il protocollo che andremo a definire sarà di tipo Master – Slave con
eco.
Un protocollo è di tipo Master – Slave quando una delle due parti
in gioco ha il potere di decidere chi compierà una determinata operazione. Nel nostro caso, il Master deciderà, ad ogni iterazione, quale tra
i due dispositivi sarà in trasmissione e quale in ricezione. Lo Slave dovrà quindi attenersi alle disposizioni del Master.
Un protocollo è di tipo Con Eco quando ad ogni parola comando
inviata corrisponde un eco di conferma. Se, ad esempio, uno dei due
dispositivi manda il comando "Inizio Trasmissione", l'altro rimanderà
lo stesso comando "Inizio Trasmissione" per confermare di aver ben
inteso l'operazione che si dovrà eseguire. Questo tipo di controllo permette maggiore sicurezza nel flusso del programma.
Il nostro protocollo prevederà, inoltre, una seconda forma di controllo sulla completezza del dato inviato: si aggiungerà infatti un'ulteriore parola, la checksum del dato inviato.
Il trasmettitore manderà un segnale di inizio trasmissione, quindi
un pacchetto di dati di lunghezza variabile, ed un segnale di fine trasmissione, seguito da una parola di controllo degli errori.
Il ricevitore manderà l'eco per ogni comando e fornirà l'esito della
checksum II e, quindi, della comunicazione in generale.
Per la comunicazione, quindi, si seguirà una procedura di questo
tipo:
ƒ Il Master decide chi trasmetterà
ƒ Inizio della trasmissione
ƒ Invio del dato
ƒ Fine della trasmissione
ƒ Invio della checksum
ƒ Esito della trasmissione
Dovendoci basare unicamente sullo strato fisico, è chiaro che le
parole chiave saranno dei normali dati inviati.
Sarà inoltre definita una parola errore che, quando ricevuta, indicherà di riposizionarsi all'inizio dell'esecuzione e ricominciare la trasmissione dall'inizio.
Definiamo quindi i seguenti valori costanti come keywords:
Valore
0Bh
0Ch
01h
04h
00h
59h
4Eh
Descrizione
Il Master decide di trasmettere
Il Master decide che trasmetterà lo Slave
Inizio della trasmissione
Fine della trasmissione
Errore
Checksum corretta, trasmissione riuscita
Checksum errata, trasmissione fallita
II
La checksum è il bit di parità pari eseguito sull'intera serie dei dati. In altre parole, il bit bi della checksum altro non è
che il bit di parità dell'intera serie dei bit bi del pacchetto dati in questione.
19
4.2 Studio del protocollo
Una volta definito il protocollo, si esegue una serie di test per verificarne la validità.
I primi test sono quelli eseguiti in assenza di errori.
Test di correttezza in assenza di errori
Master in trasmissione
Master
Slave
Decisione
01
0B
Eco
0B
02
03
Inizio trasmissione
01
Eco
01
04
Dato
XX
Fine trasmissione
06
04
05
Eco
04
07
08
Checksum
YY
Esito
59 or 4E
09
Test di correttezza in assenza di errori
Master in ricezione
Master
Slave
Decisione
01
0C
Eco
0C
Inizio trasmissione
01
02
03
04
Eco
01
Dato
XX
Fine trasmissione
04
05
06
07
Eco
04
Checksum
YY
08
09
Esito
59 or 4E
A questo punto, l'analisi si sposta nello studio di ogni tipo di errore
che può avvenire nella trasmissione. Dobbiamo innanzitutto distinguere tra errori fisici (errori causati dalla trasmissione e ricezione RS232)
ed errori logici (eco sbagliato, checksum errata). A questo riguardo,
20
stabiliamo che, in ogni caso, sarà inviato il codice di errore e la trasmissione ricomincerà dall'inizio. E' logico pensare che il dato utilizzato per l'errore non potrà essere presente all'interno di alcun dato, visto
che, ad ogni istante, la ricezione di un codice di errore comporterà l'azzeramento della trasmissione.
Importante è notare la seguente questione: se è stato riportato un
errore fisico in trasmissione, chi ci assicura che il codice di errore inviato da una parte all'altra arrivi correttamente? In effetti nessuno. Tuttavia, la probabilità che fallisca anche questa seconda trasmissione è
piuttosto bassa e, comunque, non abbiamo altra scelta in merito.
Analizziamo ora il caso in cui avvenga un errore durante la trasmissione del Master.
Errore nel primo eco
Master in trasmissione
Master
Slave
Decisione
01
0B
Eco
0D
02
03
Errore
00
Esiste tuttavia un caso particolare che va ben tenuto presente: se lo
Slave capisce che sarà lui a trasmettere, non si mette in ascolto di un
comando, ma tenterà di inviare il codice di inizio trasmissione. In questo caso il Master dovrà attendere una lettura e quindi mandare il codice di errore come eco dell'inizio trasmissione.
Errore nel primo eco – Llo slave pensa di trasmettere
Master in trasmissione
Master
Slave
Decisione
01
0B
Eco
0C
Inizio trasmissione
01
02
03
04
Errore
00
Negli altri casi, invece, ci si limita ad inviare un codice di errore e
si ricomincia la trasmissione.
Errore nell'eco di inizio trasmissione
Master in trasmissione
Master
Slave
Decisione
01
0B
Eco
0B
02
03
Inizio trasmissione
21
01
Eco
02
04
05
Dato (Errore)
00
Errore nell'eco di fine trasmissione
Master in trasmissione
Master
Slave
Decisione
01
0B
Eco
0B
02
03
Inizio trasmissione
01
Eco
01
04
Dato
XX
Fine trasmissione
06
04
05
Eco
02
07
08
Checksum (Errore)
00
Esito
59 or 4E
09
Come si nota, anche questo caso appare particolare: infatti, se inviamo il codice di errore dopo l'eco di fine trasmissione, lo Slave lo interpreterà come una normale checksum. Tuttavia, come è facile immaginare, la checksum può valere esattamente zero. Tuttavia, il problema non si pone, per il seguente motivo: se lo Slave interpreta la fine
della trasmissione come un comando diverso, esso lo considererà ancora come dato, quindi sarà sufficiente inviare un codice di errore.
Nel caso in cui sia lo Slave a trasmettere, si ricorrerà, generalmente, agli stessi metodi. Ancora una volta, vanno tenuti presente i casi
particolari.
Errore nel primo eco
Master in ricezione
Master
Slave
Decisione
01
0C
Eco
0E
02
03
Inizio trasmissione (Errore)
00
Da qui si evince che, se lo Slave non pensa di dover trasmettere, si
posizionerà comunque in condizione di ricezione, attendendo il codice
di errore.
22
Errore nel primo eco – Lo Slave pensa di ricevere
Master in ricezione
Master
01
Decisione
0C
Eco
0B
02
03
Slave
Inizio trasmissione (Errore)
00
Errore nell'eco di inizio trasmissione
Master in ricezione
Master
Slave
Decisione
01
0C
Eco
0C
Inizio trasmissione
01
02
03
04
Eco
02
Dato (Errore)
00
05
Anche lo Slave, quindi, ha la possibilità di inviare un codice di errore, ma solamente nel caso dell'invio del dato.
Errore nell'eco di fine trasmissione
Master in trasmissione
Master
Slave
Decisione
01
0C
Eco
0C
Inizio trasmissione
01
02
03
04
Eco
01
Dato
XX
Fine trasmissione
04
05
06
07
08
Eco
05
Checksum (Errore)
0
Nel caso in cui si verifichino errori fisici nella trasmissione, si proverà comunque ad inviare il carattere di errore e ci si posizionerà all'inizio della nuova trasmissione.
L'ultimo studio importante riguarda i timeout: come già spiegato,
saranno implementati dei timeout nelle routine di trasmissione e ricezione, in modo che non si aspetti all'infinito. Tuttavia, esiste un caso in
23
cui la strategia adottata non è sufficiente: quando il Master invia il comando allo Slave che indica che sarà quest'ultimo a trasmettere, non è
possibile impostare un timeout nell'attesa del comando di inizio trasmissione. Infatti, solo dopo il comando del Master lo Slave potrà ricevere in input dall'esterno un dato da mandare, quindi l'utente potrebbe impiegare parecchio tempo per l'input. Non sarebbe formalmente
corretto imporre un timeout in questo caso, quindi sarà importante rimuoverlo.
L'ultima modifica apportata al protocollo è stata la seguente: dovendo essere il Master ad avere potere decisionale su ogni situazione,
abbiamo stabilito che esso confermerà anche il codice di errore ricevuto dallo Slave, prima di riposizionarsi all'inizio dell'esecuzione. Questa
ulteriore forma di controllo, seppur non strettamente necessaria, ci aiuta a mantenere coerenza con il fatto che è sempre il Master a prendere
le decisioni. Partendo da questa base, una possibile evoluzione futura
sarebbe che, ad esempio, quando il Master riceve un codice di errore,
ha la possibilità di eseguire altre operazioni, quindi mandare l'eco dell'errore allo Slave e poter quindi autorizzare il reset della comunicazione.
4.3 Limitazioni note
Esistono alcune limitazioni al nostro protocollo, riassunte di seguito.
1. Il dato non potrà contenere byte uguali al codice di errore
2. Il dato non potrà contenere byte uguali al codice di fine trasmissione
3. Si suppone che, in seguito ad un errore fisico o logico, l'invio
del codice di errore possa avvenire
4. Si suppone l'utilizzo di uno strato fisico RS232 con collegamento mediante cavo Null – Modem senza interconnessione tra
i segnali di handshaking dei due dispositivi (l'unica connessione è tra i segnali di ricezione e trasmissione, incrociati)
Ognuna di queste limitazioni è già stata motivata in precedenza: in
particolare, le limitazioni sulla tipologia del dato sono causate dal fatto
che le informazioni vietate (errore, fine trasmissione) sarebbero interpretate dal nostro algoritmo come comandi, quindi tutte le informazioni inviate di seguito risulterebbero inattese e genererebbero un errore.
La limitazione sull'invio del codice di errore, invece, è causata dal
fatto che è comunque necessario rendere noto ad entrambi i dispositivi
la presenza di un errore, quindi l'unico modo disponibile è appunto
quello di tentare una nuova trasmissione. Se anche questa trasmissione
dovesse fallire, è chiaro che il problema sarebbe di dimensioni troppo
vaste per poter essere gestito efficacemente e, presumibilmente, il sistema genererà spontaneamente un errore e terminerà l'esecuzione del
programma. Un'ulteriore ipotesi è che il programma risulti "bloccato"
e che l'utente termini forzatamente dall'esterno la sua esecuzione.
24
4.4 Algoritmo "Master Trasmette"
Il nostro progetto si interesserà della creazione del programma del
Master. Il primo punto è quindi quello di creare un algoritmo efficiente
nel caso in cui sia il Master a Trasmettere.
Algoritmo Master-Trasmette
1. Send 0Bh
2. X Å Ricevi()
3. error Å 0
4. Se X = 0Bh
1. Send 01h
2. Y Å Ricevi()
3. Se Y = 01h
1. Send dati
2. Send 04h
3. Z Å Ricevi()
4. Se Z = 04h
1. Send Checksum
2. CS Å Ricevi()
3. Se CS = 59h
1. Trasmissione corretta
4. Altrimenti
1. Trasmissione errata
5. Altrimenti
1. error Å 1
4. Altrimenti
1. error Å 1
5. Altrimenti
1. Se X = 0Ch
1. Ricevi()
2. error Å 1
6. Se error = 1
1. Send 00h
Questa versione dell'algoritmo si cura solamente degli errori logici:
il controllo sugli errori fisici verrà, per comodità, aggiunto solamente
in fase di stesura del codice; del resto, sarà sufficiente impostare a 1 la
variabile di errore se si è riscontrato un errore fisico.
Di seguito è riportato il diagramma a blocchi dell'algoritmo: alcune
parti sono state leggermente modificate rispetto all'algoritmo scritto
(che, per ragioni legate alla programmazione e allo stile, impone di
non usare determinate tecniche di salto), in modo da rendere più chiaro
il ragionamento.
25
Inizio
Invia
0Bh
Eco
=
0Bh
Eco
=
0Ch
Ricezione
a vuoto
Yes
Yes
Invia
01h
Eco
=
01h
Yes
Invia
Dati
Invia
04h
Eco
=
04h
Yes
Invia
Checksum
Invia
00h
Yes
Esito
=
59h
Trasmissione
corretta
Trasmissione
errata
Fine
26
4.5 Algoritmo "Master Riceve"
Il secondo punto da creare è quello riguardante il Master in ricezione.
Algoritmo Master-Riceve
1. Send 0Ch
2. X Å Ricevi()
3. error Å 0
4. Stringa s
5. i Å 0
6. Se X = 0Ch
1. Y Å Ricevi()
2. Se Y = 01h
1. Send 01h
2. Ripeti
1. C Å Ricevi()
2. Se ( C ≠ 00h && C ≠ 04h )
1. s[i] Å C
2. i Å i + 1
3. finché ( C ≠ 00h && C ≠ 04h )
4. Se C ≠ 00h
1. Z Å Ricevi()
2. Se Z = 04h
1. Send 04h
2. CS Å Ricevi()
3. CS2 Å CalcolaChecksum (str)
4. Se CS = CS2
1. Send 59h
2. Trasmissione corretta
5. Altrimenti
1. Send 4Eh
2. Trasmissione errata
3. Altrimenti
1. error Å 1
5. Altrimenti
1. error Å 1
3. Altrimenti
1. error Å 1
7. Altrimenti
1. error Å 1
8. Se error = 1
1. Send 00h
Questo algoritmo è simile a quello precedente: nella maggior parte
dei casi, la scelta adottata è quella di rendere possibile solo al Master
l'invio di un valore di errore: l'unico caso in cui lo Slave ha questa possibilità è durante il dato e, in ogni caso, dovrà attendere il codice di errore del master prima di riposizionarsi all'inizio.
Anche in questo caso, gli errori fisici porteranno sempre al codice
di errore.
27
4.6 Unione degli algoritmi
La parte finale è quella di unire gli algoritmi in precedenza progettati.
Inizio
Leggi
X
Yes
No
X = 'T'
Algoritmo
"MASTER TRASMETTE"
Algoritmo
"MASTER RICEVE"
Yes
Ancora?
No
Fine
28
5. Programmazione
5.1 Scelta del linguaggio di programmazione
La scelta del linguaggio di programmazione è critica, nel senso che da
essa dipendono alcune caratteristiche aggiuntive al progetto.
In generale, i linguaggi su cui scegliere sono stati il C++ e l'Assembly 8086. E' stato preferito il C++ per i seguenti motivi:
ƒ Dispone di ottime librerie per la gestione delle funzioni fisiche
RS232
ƒ E' un linguaggio strutturato, quindi il programma risulterà di
comprensione più immediata
ƒ Fornisce interessanti routine predisposte per la gestione dei timeout
ƒ Contiene routine di utilizzo immediato per l'input e l'output con
l'utente, sia per l'utilizzo della console video testuale che per la
gestione dei file
5.2 Routine di base del programma
Per prima cosa, utilizzando il C++, si definiscono alcune strutture dati
considerate essenziali per la semplificazione del programma.
Tipi di dati di base
// Definizione del dato booleano
enum bool { false = 0 , true };
// Definizione della word
union integer {
unsigned int word;
struct {
unsigned char low;
unsigned char high;
} byte;
};
// Definizione del dato byte
typedef unsigned char byte;
Nel caso del tipo di dato bool, è possibile che alcuni compilatori lo
implementino di default: se così non fosse, sarà necessario (e comodo)
definirlo.
Il tipo byte, invece, è stato creato per rendere più immediata la
comprensione del codice: del resto, questa nomenclatura è decisamente più efficace rispetto a "unsigned char".
Per quanto riguarda il tipo integer, esso permette di riferirsi ad una
locazione da 2 byte indifferentemente in modo totale oppure riferendosi ai singoli byte che lo compongono.
Il file di header bios.h include una funzione che permette la gestione delle porte seriali, che si serve degli interrupt hardware.
unsigned _bios_serialcom(unsigned cmd, unsingned serialport, unsigned data);
29
Utilizzando la documentazione della funzione, è possibile scrivere
le routine di base di inizializzazione, invio e ricezione.
Tuttavia, per prima cosa, è necessario definire una routine per la
gestione dello stato della porta seriale, che renda possibile anche la
gestione di un timeout. In particolare, ci interesserà controllare lo stato
finché non si ottiene uno stato ben definito (ad esempio, prima di ricevere, aspetteremo che ci sia un nuovo dato), ma ovviamente fino ad un
tempo massimo prefissato. Ci serviremo delle funzioni di clock della
libreria time.h per gestire il clock.
Funzione di stato
bool rs232_status (unsigned int mask, byte port, int secs)
// 1 = Ok, 0 = Errore
{
// secs = 0 -> Ci provo una sola volta
// secs < 0 -> Ci provo all'infinito (Attenzione, la routine ha il controllo
del programma)
clock_t start, end;
start = end = clock();
integer result;
bool esci = false;
while ( ((end-start)/CLK_TCK <= secs || secs < 0) && !esci)
{
result.word = _bios_serialcom (_COM_STATUS , port, 0);
// In ESCI ho il valore, devo fare la maschera
result.word &= mask;
if (result.word == mask)
esci = true;
end = clock();
}
return esci;
}
A questo punto si possono definire anche le funzioni di inizializzazione, invio e ricezione.
Inizializzazione porta seriale
void rs232_initialize (byte protocol, int port)
{
_bios_serialcom (_COM_INIT , port, protocol);
}
La funzione di inizializzazione può fornire solamente errori nella
linea del modem: utilizzando noi una connessione Null – Modem, è
logico pensare che non vi saranno errori legati a questa periferica. Il
valore di ritorno della funzione, pertanto, può essere ignorato.
Invio di un byte
bool rs232_send (byte x, byte port, unsigned int secs)
// 1 = Errori, 0 = Ok
{
// Aspetto che eventuali trasferimenti in corso siano terminati
bool r = rs232_status (0x6000, port, secs);
bool exit_value;
// Se c'è stato un errore di timeout esco
30
if (r)
{
// Invio il carattere
integer rs_res;
rs_res.word = _bios_serialcom (_COM_SEND , port, x);
// Maschero per controllare eventuali timeout fisici
int mask = rs_res.byte.high & 0x80;
if (mask)
exit_value = true;
else
exit_value = false;
}
else
exit_value = true; // C'è stato un errore
return exit_value;
}
Questa funzione attende che eventuali invii precedenti siano terminati, quindi tenta l'invio e restituisce l'esito dell'operazione.
Ricezione di un carattere
bool rs232_receive (byte* dato, byte port, int secs)
// 1 = Errori, 0 = Ok
{
integer result;
// Aspetto che ci sia un dato da leggere
bool s = rs232_status (0x0100,port,secs);
bool exit_value;
if (s)
{
// Non ci sono stati errori
result.word = _bios_serialcom (_COM_RECEIVE , port, 0);
// Maschero il risultato per vedere eventuali errori
int mask = result.byte.high & 0x8E;
if (mask)
exit_value = true;
else
exit_value = false;
}
else
exit_value = true;
// Imposto il dato ricevuto nel parametro di output
*dato = result.byte.low;
return exit_value;
}
In questa routine, si attende (nei limiti del timeout) che ci sia un
nuovo dato da leggere, quindi si procede con l'operazione e si restituisce il dato letto, più il controllo degli errori.
31
I controlli effettuati in queste routine sono legati al modo di operare della funzione di gestione della porta seriale (legata, a sua volta, agli
interrupt BIOS), quindi si rimanda alla documentazione della stessa
(allegata a questo documento) per maggiori dettagli.
Per semplificare il codice, infine, sono state definite delle costanti
che riassumono i valori necessari allo strato fisico e allo strato logico.
Costanti di sistema
// Secondi prima del timeout
#define RS232_TIMEOUT 5
// Definizione del protocollo di comunicazione utilizzato
#define RS232_PROTOCOL ( _COM_CHR8 | _COM_STOP1 | _COM_EVENPARITY | _COM_300 )
// Parole di comando
#define RS232_MASTER_TR
#define RS232_SLAVE_TR
#define RS232_START_TR
#define RS232_END_TR
#define RS232_CHECKSUM_OK
#define RS232_CHECKSUM_ERR
#define RS232_ERROR
0x0B
0x0C
0x01
0x04
0x59
0x4E
0x00
// Definizione della porta seriale
#define COM1 0
#define COM2 1
#define COM3 2
#define COM4 3
E' stata inoltre scritta una funzione di errore generico, che si occuperà di inviare allo Slave il codice di errore e di stampare a schermo
un messaggio di errore.
Routine di errore generico
void generic_error(char* msg, byte port)
{
// Manda uno 0 allo slave
byte command = 0;
rs232_send (command,port,0);
cout << "\a" << msg << "\a" << endl;
return;
}
Per quanto precedentemente assunto, si suppone che questa funzione possa sempre essere eseguita, quindi si tralascia il ritorno di una
variabile di controllo errori.
5.3 Traduzione degli algoritmi
Una volta predisposte tutte le routine di gestione della porta seriale, è
sufficiente tradurre in codice gli algoritmi precedentemente progettati.
Una serie di condizioni annidate permetterà di ottenere un unico punto
in cui gestire gli errori.
Dovendo gestire le stringhe come strutture statiche (l'utilizzo di
strutture dinamiche complicherebbe il codice più di quanto in realtà sia
necessario), stabiliamo che la lunghezza massima sia di 255 caratteri.
32
In questo modo, anche un eventuale programma che si serva di interrupt DOS per l'interazione esterna con l'utente potrà essere compatibile
con la struttura del programma.
Infine, è importante decidere come avverrà l'interazione con l'utente: per comodità stabiliamo che l'utente potrà interagire con il programma mediante la console. Riceverà messaggi a schermo e potrà inserire input da tastiera. Per la corretta gestione si utilizzeranno le funzioni di gestione della console previste in C++ dalla classe iostream.
5.5 Software di supporto
Per testare il funzionamento del programma ci siamo serviti anzitutto
del debugger integrato nel software Turbo C++, che ci ha permesso di
eseguire ogni routine passo per passo e verificare quindi l'esito di ogni
frammento di codice.
Ci siamo serviti inoltre di PROCOM, un software testato e funzionante per il trasferimento di dati attraverso la porta seriale. Una volta programmata la porta, il programma stamperà a schermo tutto ciò
che la porta riceve e invierà in output tutto ciò che viene digitato da tastiera. Il software gestisce la porta in modo diretto, andando a lavorare
con i registri interessati, quindi il suo funzionamento è decisamente sicuro e affidabile.
5.6 Codice completo del programma
Di seguito è riportato il codice intero del programma sviluppato.
File rs_master.cpp
#include
#include
#include
#include
#include
#include
#include
#include
<iostream.h>
<conio.h>
<dos.h>
<string.h>
<ctype.h>
<time.h>
<bios.h>
<stdio.h>
// Secondi prima del timeout
#define RS232_TIMEOUT 5
// Definizione del protocollo di comunicazione utilizzato
#define RS232_PROTOCOL ( _COM_CHR8 | _COM_STOP1 | _COM_EVENPARITY | _COM_300 )
// Parole di comando
#define RS232_MASTER_TR
#define RS232_SLAVE_TR
#define RS232_START_TR
#define RS232_END_TR
#define RS232_CHECKSUM_OK
#define RS232_CHECKSUM_ERR
#define RS232_ERROR
0x0B
0x0C
0x01
0x04
0x59
0x4E
0x00
// Definizione della porta seriale
#define COM1 0
#define COM2 1
#define COM3 2
#define COM4 3
33
// Definizione del dato booleano
enum bool { false = 0 , true };
// Definizione della word
union integer {
unsigned int word;
struct {
unsigned char low;
unsigned char high;
} byte;
};
// Definizione del dato byte
typedef unsigned char byte;
// FUNZIONI DI SISTEMA RS232
// Stato della porta
bool rs232_status (unsigned int mask, byte port, int secs = 0);
// Inizializzazione della porta
void rs232_initialize (byte protocol, int port);
// Invio di un byte
bool rs232_send (byte x, byte port, unsigned int secs);
// Ricezione di un byte
bool rs232_receive (byte* dato, byte port, int secs);
// Routine generica di invio errore allo slave
void generic_error(char* msg, byte port);
// Programma principale
int main ()
{
// Pulizia dello schermo
clrscr();
// Porta seriale utilizzata
int port = COM1;
// Protocollo utilizzato
byte protocol = RS232_PROTOCOL;
// Inizializzazione porta seriale
rs232_initialize(protocol,port);
// Carattere di comando e variabile di controllo
byte command;
// Carattere di eco e variabile di controllo
byte echo;
// Checksum e variabile di controllo
byte checksum;
// Esito della checksum e variabile di controllo
byte cs_return;
// Controllo del loop
34
int esci;
bool errors;
// Loop infinito
do {
// Trasmetto o ricevo?
char tr;
do {
cout << "[T]rasmettere o [R]icevere?";
cin >> tr;
tr = toupper (tr);
} while (tr != 'T' && tr != 'R');
if (tr == 'T')
{
char str[256]; // Massimo 255 caratteri
cout << "Dato da mandare: ";
gets(str);
int lung = strlen (str);
// Calcolo della checksum
checksum = 0;
for (register int i = 0; i < lung; ++i)
checksum ^= str[i];
// Voglio trasmettere
command = RS232_MASTER_TR;
if (!rs232_send(command,port,RS232_TIMEOUT))
{
// Attendo l'eco
if (!rs232_receive(&echo,port,RS232_TIMEOUT))
{
// Controllo l'eco
if (echo == RS232_MASTER_TR)
{
// Eco corretto
// Inizio trasmissione
command = RS232_START_TR;
if (!rs232_send(command,port,RS232_TIMEOUT))
{
// Attendo l'eco
if (!rs232_receive(&echo,port,RS232_TIMEOUT))
{
if (echo == RS232_START_TR)
{
// Eco corretto, mando il dato
for (register int j = 0; j < strlen (str) && error == false;
++j)
{
command = str[j];
if (rs232_send(command,port,RS232_TIMEOUT))
error = true;
}
if (error == false)
{
// Fine trasmissione
35
command = RS232_END_TR;
if (!rs232_send(command,port,RS232_TIMEOUT))
{
// Attendo l'eco
if (!rs232_receive(&echo,port,RS232_TIMEOUT))
{
if (echo == RS232_END_TR)
{
// Eco corretto
// Mando la checksum
if (!rs232_send(checksum,port,RS232_TIMEOUT))
{
if
(!rs232_receive(&cs_return,port,RS232_TIMEOUT))
{
if (cs_return == RS232_CHECKSUM_OK)
{
// Trasmissione corretta
cout << "Trasmissione corretta" << endl;
}
else
{
// Trasmissione errata
errors = true;
}
}
else
errors = true;
}
else
errors = true;
}
else
{
// Eco errato
errors = true;
}
}
else
errors = true;
}
else
errors = true;
}
}
else
{
// Eco errato
errors = true;
}
}
else
errors = true;
}
else
errors = true;
}
36
else
{
// Eco errato
if (echo == RS232_SLAVE_TR)
{
// Lo slave pensa di poter trasmettere!
// ...ma noi siamo più furbi!
// Ora facciamo un'imboscata allo slave...
// ... facciamo finta di accettare la sua trasmissione...
if (rs232_receive(&echo,port,-1))
{
// E ora possiamo impostare l'errore
errors = true;
}
}
else
errors = true;
}
else
errors = true;
}
else
errors = true;
}
else
{
// Master in ricezione
char str[256]; // Massimo 80 caratteri
int lung = 0; // Lunghezza della stringa ricevuta
// Calcolo della checksum
checksum = 0;
// Voglio ricevere
command = RS232_SLAVE_TR;
if (!rs232_send(command,port,RS232_TIMEOUT))
{
if (!rs232_receive(&echo,port,RS232_TIMEOUT))
{
// Controllo l'eco
if (echo == RS232_SLAVE_TR)
{
// Eco corretto, aspetto l'inizio della trasmissione
if (rs232_receive(&command,port,-1))
{
if (command == RS232_START_TR)
{
// Inizio trasmissione ricevuto, mando l'eco
echo = RS232_START_TR;
if (!rs232_send(echo,port,RS232_TIMEOUT))
{
// Echo mandato
// Ricevo il dato
byte data_rx;
do {
if (!rs232_receive(&data_rx,port,RS232_TIMEOUT))
{
37
// Vediamo se è fine trasmissione
if (data_rx != RS232_END_TR && data_rx != RS232_ERROR)
{
// E' un carattere
str[lung++] = data_rx;
checksum ^= data_rx;
}
else
{
if (data_rx == RS232_END_TR)
{
str[lung] = '\0';
}
}
}
else
errors = true;
} while (data_rx != RS232_END_TR && data_rx != RS232_ERROR
&& errors == false);
if (errors == false)
{
if (data_rx == RS232_END_TR)
{
// Trasmissione terminata, mando l'eco
echo = RS232_END_TR;
if (!rs232_send(echo,port,RS232_TIMEOUT))
{
// Ora ricevo la checksum
byte cs_received;
if (!rs232_receive(&cs_received,port,RS232_TIMEOUT))
{
if (cs_received == checksum)
cs_return = RS232_CHECKSUM_OK;
else
cs_return = RS232_CHECKSUM_ERR;
// Mando l'esito
if (!rs232_send(cs_return,port,RS232_TIMEOUT))
{
if (cs_return == RS232_CHECKSUM_OK)
{
// Trasmissione corretta
cout << "RICEVUTO: " << str << endl;
}
else
{
// Trasmissione errata
cout << "Trasmissione errata" << endl;
}
}
else
errors = true;
}
else
errors = true;
}
else
38
errors = true;
}
else
{
// C'Š stato un errore, bisogna tornare all'inizio
errors = true;
}
}
}
else
errors = true;
}
else
errors = true;
}
else
errors = true;
}
else
errors = true;
}
else
errors = true;
}
else
errors = true;
}
// Ci sono stati errori?
if (errors == true)
generic_error ("Errore nella comunicazione" , port);
cout << "Ancora? (S/N)";
char risp = getche();
if (risp == 's' || risp == 'S')
esci = 0;
else
esci = 1;
} while (!esci);
// Attesa del carattere
getch();
return 0;
}
bool rs232_status (unsigned int mask, byte port, int secs)
// 1 = Ok, 0 = Errore
{
// secs = 0 -> Ci provo una sola volta
// secs < 0 -> Ci provo all'infinito (Attenzione, la routine ah il controllo
del programma)
clock_t start, end;
start = end = clock();
integer result;
bool esci = false;
while ( ((end-start)/CLK_TCK <= secs || secs < 0) && !esci)
39
{
result.word = _bios_serialcom (_COM_STATUS , port, 0);
// In ESCI ho il valore, devo fare la maschera
result.word &= mask;
if (result.word == mask)
esci = true;
end = clock();
}
return esci;
}
void rs232_initialize (byte protocol, int port)
{
_bios_serialcom (_COM_INIT , port, protocol);
}
bool rs232_send (byte x, byte port, unsigned int secs)
// 1 = Errori, 0 = Ok
{
// Aspetto che eventuali trasferimenti in corso siano terminati
bool r = rs232_status (0x6000, port, secs);
bool exit_value;
// Se c'Š stato un errore di timeout esco
if (r)
{
// Invio il carattere
integer rs_res;
rs_res.word = _bios_serialcom (_COM_SEND , port, x);
// Maschero per controllare eventuali timeout fisici
int mask = rs_res.byte.high & 0x80;
if (mask)
exit_value = true;
else
exit_value = false;
}
else
exit_value = true; // C'Š stato un errore
return exit_value;
}
bool rs232_receive (byte* dato, byte port, int secs)
// 1 = Errori, 0 = Ok
{
integer result;
// Aspetto che ci sia un dato da leggere
bool s = rs232_status (0x0100,port,secs);
bool exit_value;
if (s)
{
// Non ci sono stati errori
result.word = _bios_serialcom (_COM_RECEIVE , port, 0);
// Maschero il risultato per vedere eventuali errori
40
int mask = result.byte.high & 0x8E;
if (mask)
exit_value = true;
else
exit_value = false;
}
else
exit_value = true;
// Imposto il dato ricevuto nel parametro di output
*dato = result.byte.low;
return exit_value;
}
void generic_error(char* msg, byte port)
{
// Manda uno 0 allo slave
byte command = 0;
rs232_send (command,port,0);
cout << "\a" << msg << "\a" << endl;
return;
}
41
6. Conclusioni
6.1 Conclusioni
La realizzazione di questo progetto si è rilevata ben più ostica di quanto ci si aspettasse: la porta seriale, gestita mediante gli interrupt BIOS
(come fanno le routine C++), è sicuramente di comprensione semplice,
ma il suo funzionamento non è costante e, addirittura, un programma
che funziona un giorno potrebbe non funzionare il giorno seguente.
Tuttavia, alla fine siamo riusciti a far funzionare adeguatamente il
programma in ogni situazione, cercando di eliminare le limitazioni superflue e tentando di prevedere il maggior numero possibile di errori.
Il software finale risulta nel complesso funzionante e testato.
Grazie a questa prova abbiamo appreso l'importanza del controllo
di un processo complesso quale la comunicazione tra due sistemi: ogni
operazione deve essere rigorosamente controllata e ogni comando, per
maggiore sicurezza, deve essere seguito da un eco. Trasmettendo un
pacchetto di dati, inoltre, è necessario aggiungere un controllo sull'intero pacchetto, controllo che noi abbiamo aggiunto sotto forma di checksum.
Inoltre abbiamo completato il lavoro relativo alla trasmissione dati
iniziata in precedenza con la trasmissione parallela: è da segnalare che
la trasmissione seriale è decisamente più complessa da realizzare, in
quanto i controlli da eseguire sono superiori rispetto a quelli per la trasmissione parallela.
In conclusione, il progetto risulta completo, testato e funzionante.
6.2 Fonti
Informazioni sulla trasmissione seriale
ƒ www.wikipedia.org
ƒ Corso di Sistemi Volume 3
ƒ Presentazione Power Point (professoressa Amaroli)
ƒ www.giobe2000.it
Informazioni e guida di riferimento funzioni C++
ƒ www.cplusplus.com
ƒ http://www.cplusplus.com/ref/ctime/clock.html (funzione clock)
ƒ http://www.delorie.com/djgpp/doc/libc/libc_68.html (funzione _bios_serialcom)
42
7. Allegati
7.1 Documentazione _bios_serialcom()
_bios_serialcom
Syntax
#include <bios.h>
unsigned _bios_serialcom(unsigned cmd, unsingned serialport,
unsigned data);
Description
The _bios_serialcom routine uses INT 0x14 to provide serial communications services. The serialport argument is set to 0 for COM1, to 1 for COM2, and so on. The cmd argument can be set to one
of the following manifest constants:
_COM_INIT
Initialize com port (data is the settings)
_COM_RECEIVE
Read a byte from port
_COM_SEND
Write a byte to port
_COM_STATUS
Get the port status
The data argument is ignored if cmd is set to _COM_RECEIVE or _COM_STATUS. The data argument for _COM_INIT is created by combining one or more of the following constants (with the
OR operator):
_COM_CHR7
_COM_CHR8
_COM_STOP1
_COM_STOP2
_COM_NOPARITY
_COM_EVENPARITY
_COM_ODDPARITY
_COM_110
_COM_150
_COM_300
_COM_600
_COM_1200
_COM_2400
_COM_4800
_COM_9600
7 bits/character
8 bits/character
1 stop bit
2 stop bits
no parity
even parity
odd parity
110 baud
150 baud
300 baud
600 baud
1200 baud
2400 baud
4800 baud
9600 baud
The default value of data is 1 stop bit, no parity, and 110 baud.
43
Return Value
The function returns a 16-bit integer whose high-order byte contains status bits. The meaning of the
low-order byte varies, depending on the cmd value. The high-order bits are as follows:
Bit
Meaning if Set
15
14
13
12
11
10
9
8
Timed out
Transmission-shift register empty
Transmission-hold register empty
Break detected
Framing error
Parity error
Overrun error
Data ready
When service is _COM_SEND, bit 15 is set if data cannot be sent.
When service is _COM_RECEIVE, the byte read is returned in the low-order bits if the call is successful. If an error occurs, any of the bits 9, 10, 11, or 15 is set.
When service is _COM_INIT or _COM_STATUS, the low-order bits are defined as follows:
Bit
Meaning if Set
7
6
5
4
3
2
1
0
Receive-line signal detected
Ring indicator
Data-set-ready
Clear-to-send
Change in receive-line signal detected
Trailing-edge ring indicator
Change in data-set-ready status
Change in clear-to-send status
Portability
ANSI/ISO C No
POSIX
No
Example
/* 9600 baud, no parity, one stop, 8 bits */
_bios_serialcom(_COM_INIT, 0,
_COM_9600|_COM_NOPARITY|_COM_STOP1|_COM_CHR8);
for(i=0; buf[i]; i++)
_bios_serialcom(_COM_SEND, 0, buf[i]);
44
7.2 Documentazione clock()
clock_t clock ( void );
Return number of clock ticks since process start.
Returns the number of clock ticks elapsed.
A macro constant called CLK_TCK defines the relation betwen clock tick and second (clock ticks
per second).
Parameters.
(none)
Return Value.
The number of clock ticks elapsed since start.
clock_t type is defined by default as long int by most compilers.
Portability.
Defined in ANSI-C.
45