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