v 1.11
21/02/2001
Ettore Maronese
Usare la porta seriale del PC è da molti ritenuta cosa ostica, questo perché spesso non si conosce
il reale funzionamento della periferica, o non si è in possesso delle conoscenze di base per gestire un
protocollo di comunicazione.
L'alone di mistero che circonda questa periferica è però del tutto ingiustificato, la seriale può
essere facilmente gestita con poche righe di codice, e Visual Basic ci viene incontro fornendo l'oggetto MSCOMM
con il quale è possibile controllare efficacemente la RS-232.
Questo articolo tratta solo delle connessioni tra dispositivi senza l'utilizzo di modem, ossia con connessione
diretta.
Introduzione alla comunicazione seriale:
Una comunicazione seriale è uno scambio di dati tra due dispositivi. Queste informazioni viaggiano sul
cavo sotto forma di 1 e di 0, in modo appunto seriale (sequenziale). Inviando per esempio il carattere esadecimale
26h, sulla seriale verrà scomposto in bit 00100110, e questi bit verranno inviati, uno alla volta, sul cavo
seriale. Il tempo che intercorre tra l'invio di un bit ed il seguente è appunto la velocità di trasmissione,
ossia il baud rate, e viene espresso in bit al secondo. La scomposizione dei caratteri in bit è trasparente
al sistema, chi invia i dati si limita a fornire i caratteri alla periferica, chi li riceve si limiterà
ad estrarre i caratteri ricevuti da una coda di ricezione.
La seriale dei Personal Computers è di tipo asincrona, ossia permette di trasmettere e ricevere contemporaneamente.
L' RS-232c è uno standard un po' particolare in quanto lascia molta libertà sulla modalità
di trasmissione dei caratteri.
Queste modalità di lavorare rappresentano la configurabilità dell'hardware della RS-232c.
Vediamo quali sono queste modalità:
Baud rate = è possibile indicare la velocità di trasmissione dei bits. Si parte da 300 Bit/Sec fino ad arrivare a 115.200 Bit/Sec, e con i nuovi chip, sia arriva fino a 921.600.
Numero di BIT = è possibile scegliere se inviare tutti gli 8 bit del carattere, o escludere l'ultimo inviandone quindi solo 7. Se si usa la modalità a 7 bit, non potranno essere inviati caratteri con valore superiore a 127 (7Fh). Se si tenta di inviare un carattere con valore maggiore a 127 usando la modalità a 7 BIT, il bit più significativo non verrà inviato (è possibile avere un numero di bit inferiore a 7, ma di fatto sono modalità che non vengono mai usate).
Parità = è possibile aggiungere ad ogni invio di carattere un bit che indica se il numero di bit a 1 del carattere è pari o dispari. Questo può sembrare assurdo, ma risulta utile per rilevare errori di trasmissione dovuti a disturbi sulla linea, in quanto il ricevente riesegue il conto e paragona il risultato al bit di parità ricevuto, se è diverso viene generato un errore di parità. La parità può anche essere di tipo mark o space, nel primo caso il bit di parità vale sempre 1, e nel secondo sempre 0.
Bits di stop = I bits di stop sono dei bit a 1 che vengono aggiunti in coda ad ogni invio di carattere. Questi servono per marcare la fine del treno di bit del carattere. E' possibile avere 1 o 2 Bits di stop (esiste anche uno stop di 1,5 bits ma di fatto non viene mai usato)
Ecco i valori impostabili.
BaudRate
(Bit/sec)Nr di BITs
Parità
Bits di stop
110
4
Nessuna (non usato)
1
300
5
Pari
1,5
600
6
Dispari
2
1200
7
Mark
2400
8
Spazio
4800
9600
19200
38400
57600
115200
A questo punto facciamo un esercizio che ci aiuta a capire quanti bits vengono usati per inviare un carattere e le velocità di trasmissione. Per far questo occorre ricordare che ogni invio di carattere viene preceduto da un bit di start (a 0), e seguito da un numero variabile di bits di stop (a 1) in funzione dell'impostazione dei Bits di stop.
Supponiamo di dover trasmettere un file di 10240 bytes, con la seriale configurata come 9600,n,8,1 ossia
Baudrate=9600, Parità=nessuna, Bits=8, Stop=1.
Ogni carattere ci porta via 1 bit di start (fisso) + 8 bit di dati + 1 di stop, in tutto 10 bit. Quindi i bits
totali da inviare sono 10240*10=102400 ed andando ad una velocità di 9600 bits al secondo ci impiegheremo
102400/9600 = 10,7 secondi.
Supponiamo ora di inviare sempre 10240 bytes, ma con la seriale configurata come 4800,e,8,2 ossia Baudrate=4800,
Parità=Pari, Bits=8, Stop=2.
Ogni carattere ci porta via 1 bit di start (fisso) + 8 bit di dati + 1 di parità 2 bits di stop, in tutto
12 bits. Quindi i bits totali da inviare sono 10240*12=122880 ed andando ad una velocità di 4800 bits al
secondo ci impiegheremo 122880/4800 = 25,6 secondi.
Qundo si instaura una comunicazione seriale è quindi indispensabile che la periferica (l'hardware)
sia impostata nel medesimo modo in entrambi i dispositivi. Questa enorme quantità di combinazioni nella
configurazione dell'hardware ha reso lo standard molto versatile, ma ha avuto anche l'effetto di non essere mai
"Plug and Play". Per esempio, non è possibile collegarsi ad una bilancia senza sapere come è
stata configurata la sua seriale.
Adesso vediamo quali segnali (elettrici) vengono usati dalla RS-232, ossia i segnali che troviamo sui connettori.
Iniziamo subito con una bella tabella di tutti i segnali gestibili.
Segnale
Nome segnale
Tipologia (IN/OUT)
RXD
Ricezione dati
IN
TXD
Trasmissione dati
OUT
RTS
Richiesta di trasmissione
(Request To Send)OUT
CTS
Consenso alla trasmissione
(Clear To Send)IN
DTR
Il PC è pronto ad instaurare una comunicazione
(Data Terminal Ready)OUT
DSR
Il dispositivo remoto è pronto ad instaurare una comunicazione
(Data Set Ready)IN
GND
Ground (massa) dei segnali
-
I segnali RXD e TXD sono rispettivamente la ricezione e la trasmissione dei dati, RTS, CTS, DTR e DSR sono usati
per l'handshake ossia per la sincronizzazione della comunicazione.
è da notare che i nomi assegnati ai segnali di handshake sono in funzione al loro utilizzo per la
connessione ad un modem, ma dato che in questa sede analiziamo la connessione diretta senza modem, il significato
dei nomi è irrilevante.
Handshake, la sincronizzazione delle comunicazioni:
In una comunicazione seriale è spesso necessario utilizzare metodologie di sincronizzazione.
La sincronizzazione serve per evitare la perdita di dati dovuta al fatto che la controparte non è pronta
a riceverli. Questa situazione può avvenire o perché il dispositivo che riceve non è operativo,
o perché il buffer di ricezione è pieno, e quindi non è più in grado di accettare altri
caratteri.
Un classico esempio di Buffer Overflow lo danno le stampanti, dove la velocità di ricezione dei dati è
superiore alla loro velocità di stampa, e quindi, in presenza di grosse moli di stampa, hanno bisogno di
segnalare al PC di interrompere la trasmissione quando il buffer di ricezione è saturo e di riprenderla
quando il buffer si svuota.
Esistono due tipi di sincronizzazione, DTR-DSR che si basa su segnali hardware e XON-XOFF che si basa su un protocollo
software.
DTR-DSR
Per spiegare come funziona l'handshake DTR-DSR è necessario sapere che il DTR è un'uscita ed il DSR
è un'ingresso, e che il DTR del primo dispositivo è connesso al DSR del secondo, ed il DTR del secondo
dispositivo è connesso al DSR del primo. Per sincronizzare la comunicazione chi riceve deve dare il consenso
a chi trasmette, ossia alza il segnale di DTR (lo pone a 1) cosicchè chi deve trasmettere sa che può
farlo consultando lo stato del proprio DSR.
Quando un dispositivo apre la porta di comunicazione, come prima cosa deve alzare il segnale DTR (segnale in uscita)
per informare la controparte che è pronta a ricevere, e lo abbassa solo se il buffer di ricezione si satura.
Di conseguenza il DSR (segnale in ingresso) è sempre alto, ossia con il consenso a trasmettere, e risulta
basso solo se il ricevente è spento o non connesso, o perché il buffer di ricezione del ricevente
è saturo.
XON-XOFF
La sincronizzazione tramite XON-XOFF è concettualmente simile alla precedente, però questa volta
il consenso a trasmettere viene dato inviando sulla seriale un carattere XON (11h), mentre per fare interrompere
la trasmissione viene invio un XOFF (13h).
Questo tipo di sincronizzazione è molto comodo perché permette di usare cavi con soli tre fili TXD,
RXD e GND, (mentre con la sincronizzazione DTR-DSR servono 5 fili), ma ha due controindicazioni:
- Obbliga l'utilizzo di protocolli di comunicazione che non usino i caratteri XON (11h) e XOFF (13h), ossia protocolli
ASCII, escludendo quindi tutti i protocolli binari (i protocolli binari usano l'intera gamma di caratteri disponibile
da 0 a 255, mentre i protocolli ASCII usano i caratteri dal 32 al 127 riservando i caratteri dallo 0 al 31 per
la gestione del protocollo).
- Può essere usato solo per connessioni spot, tipo scarico file, e non da connessioni vive, in quanto se
per qualche motivo, tipo disturbi o reset di un dispositivo, non viene ricevuto l'XON o l'XOFF, si ha nel primo
caso un blocco delle comunicazioni (non avendo ricevuto l'XON il trasmittente non trasmette più) e nel secondo
caso la perdita di dati (il buffer è pieno ma il trasmittente continua ad inviare i dati).
RTS-CTS
I segnali RTS-CTS generalmente vengono usati unicamente col modem per forzare l'abilitazione del segnale di portante
sul doppino telefonico. In pratica il PC deve alzare il segnale RTS ed attendere che il modem risponda alzano il
segnale CTS del PC.
Generalmente nelle connessioni tra dispositivi questi segnali vengono simulati creando un ponticello, a livello
del connettore del cavo seriale tra i pin dei segnali RTS-CTS, di modo che quando il dispositivo alza il segnale
RTS si ritrova automaticamente il CTS attivo.
Cablaggi, cavi, connettori e connessioni:
Prima di iniziare l'analisi software, è utile sapere come vanno effettuati i cablaggi.
Esistono due tipi di dispositivi seriali, il DTE (Data Terminal Equipment) ed il DCE (Data Communication Equipment)
o detto in altre parole, il DTE è il PC ed il DCE è il modem. Questi due dispositivi hanno un connettore
differente, ossia cambia la posizione dei segnali sui pin del connettore. La differenziazione tra DTE e DCE è
stata fatta per utilizzare cavi seriali pin-to-pin, infatti, per esempio, il segnale TXD (trasmissione dati) del
PC è sullo stesso pin del RXD (ricezione dati) del modem.
Se però si connettono due PC tra loro, non è possibile usare un cavo pin-to-pin, ma occorre fare
un cavo particolare (incrociato), infatti se si usasse un cavo pin-to-pin il segnale TXD del primo si connetterebbe
al TXD del secondo, e la cosa è ovviamente errata.
L'importante è sapere che i computers sono sempre dei DTE e che le periferiche, tipo modem, stampanti, bilance
ecc.., sono sempre dei DCE.
Detto questo c'è anche da dire che esistono due standard di connettori, quello a 25pin e quello a 9 pin.
Vediamo in dettaglio tutti i tipi di connettore:
Connettori a 25 poli
PIN |
SEGNALE |
NOME SEGNALE |
TIPO (IN/OUT) |
2 |
TXD |
Trasmissione Dati |
OUT |
3 |
RXD |
Ricezione Dati |
IN |
4 |
RTS |
Request To Send |
OUT |
5 |
CTS |
Clear To Send |
IN |
6 |
DSR |
Data Set Ready |
IN |
7 |
GND |
Ground (Massa segnali) |
- |
20 |
DTR |
Data Terminal Ready |
OUT |
PIN |
SEGNALE |
NOME SEGNALE |
TIPO (IN/OUT) |
2 |
RXD |
Ricezione Dati |
IN |
3 |
TXD |
Trasmissione Dati |
OUT |
4 |
CTS |
Clear To Send |
IN |
5 |
RTS |
Request To Send |
OUT |
6 |
DTR |
Data Terminal Ready |
OUT |
7 |
GND |
Ground (Massa segnali) |
- |
20 |
DSR |
Data Set Ready |
IN |
Connettori a 9 poli
PIN |
SEGNALE |
NOME SEGNALE |
TIPO (IN/OUT) |
2 |
RXD |
Ricezione Dati |
IN |
3 |
TXD |
Trasmissione Dati |
OUT |
4 |
DTR |
Data Terminal Ready |
OUT |
5 |
GND |
Ground (Massa segnali) |
- |
6 |
DSR |
Data Set Ready |
IN |
7 |
RTS |
Request To Send |
OUT |
8 |
CTS |
Clear To Send |
IN |
PIN |
SEGNALE |
NOME SEGNALE |
TIPO (IN/OUT) |
2 |
TXD |
Trasmissione Dati |
OUT |
3 |
RXD |
Ricezione Dati |
IN |
4 |
DSR |
Data Set Ready |
IN |
5 |
GND |
Ground (Massa segnali) |
- |
6 |
DTR |
Data Terminal Ready |
OUT |
7 |
CTS |
Clear To Send |
IN |
8 |
RTS |
Request To Send |
OUT |
Se si desidera avere una panoramica di come fare i cavi di connessione, fate un salto nella sezione Cablaggi
Seriali.
Primi passi con il controllo MSCOMM:
Con Visual Basic viene fornito un componente per la gestione della seriale, l' MSCOMM.OCX.
L'MSCOMM dispone di diverse proprietà, di un solo evento ( OnComm() ) e nessun metodo.
Iniziamo col creare un nuovo progetto, quindi rendiamo disponibile il controllo MSCOMM andando sul menù
Progetto->Componenti, e selezionando "Microsoft comm control x.x".
Aggiungete quindi il controllo, ora presente nella barra degli strumenti, nel form (Form1) dando vita al controllo
MSComm1.
Per questioni didattiche preferisco configurare il controllo a run-time, quindi nell'evento Form_Load() inserite
quanto segue:
MSComm1.CommPort = 1 ' Selezioniamo la COM1 MSComm1.Settings = "9600,n,8,1" ' Le impostazioni della seriale MSComm1.PortOpen = True ' Apriamo la porta.
Impostando la proprietà PortOpen a True si richiede al sistema il permesso d' utilizzo
della porta scelta (COM1). Se la porta è disponibile il sistema operativo la assegnerà a noi, diversamente
se la porta è già utilizzata da un'altro processo verrà generato un errore intercettabile
con l'istruzione ON ERROR.
Per chiudere la porta e rilasciarne il controllo, è sufficiente impostare PortOpen a False.
A questo punto siamo già in grado di inviare dati sulla seriale, per esempio un classico "Ciao Mondo".
MSComm1.Output = "Ciao Mondo"
Se avete due PC ed un cavo seriale DTE-DTE (pin 2/3 girati) potete provare a lanciare sul primo PC l'HyperTerminal e vedere comparire la scritta Ciao Mondo al lancio del programma dimostrativo. (è possibile anche con un solo PC, basta far usare all'HyperTerminal la COM2, e connettere il cavo tra COM1 e COM2).
Il codice si può migliorare eseguendo un controllo sugli errori.
MSComm1.CommPort = 1 ' Selezioniamo la COM1 MSComm1.Settings = "9600,n,8,1" ' Le impostazioni della seriale On Error Resume Next ' Abilito l'intercettazione degli errori MSComm1.PortOpen = True ' Apriamo la porta. If Err Then ' se è accaduto un errore lo notifico all'utente MsgBox "Impossibile aprire la COM" & MSComm1.CommPort & vbCrLf & Error$ End If On Error GoTo 0
L'evento OnComm() e la proprietà CommEvent:
Il controllo MSCOMM è asincrono, ossia non occorre che il programma dedichi tutto il suo tempo a controllare
ciò che accade sulla seriale, è il controllo MSCOMM che si preoccupa di informare il programma di
ciò che accade tramite il richiamo dell'evento OnComm() e l'impostazione della proprietà CommEvent.
Quindi l'intera intelligenza della comunicazione è da implementare nell'evento OnComm().
Quando viene richiamato l'evento OnComm(), Nella proprietà CommEvents è riportato l'evento
accaduto. Esso può assumere uno dei seguenti valori (vedi l'help in linea del VB):
Costanti relative agli errori |
||
comEventBreak | 1001 | Ricezione di un segnale di interruzione. |
comEventFrame | 1004 | Errore di frame. L'hardware ha individuato un errore di frame. |
comEventOverrun | 1006 | Overrun della porta. L'hardware non ha letto un carattere prima dell'arrivo del successivo e il carattere è andato perduto. |
comEventRxOver | 1008 | Overflow del buffer di ricezione. Spazio esaurito nel buffer di ricezione. |
comEventRxParity | 1009 | Errore di parità. È stato rilevato un errore di parità. |
comEventTxFull | 1010 | Buffer di trasmissione pieno. Spazio esaurito nel buffer di trasmissione durante il tentativo di inserimento di un carattere. |
comEventDCB | 1011 | Errore imprevisto durante il recupero del DCB (Device Control Block) della porta. |
Costanti relative agli eventi di seriale |
||
comEvSend | 1 | Nel buffer di trasmissione è presente un numero di caratteri inferiore a quello definito dalla proprietà Sthreshold. |
comEvReceive | 2 | Numero di caratteri Rthreshold ricevuti. Questo evento viene generato fino a quando non si utilizza la proprietà Input per rimuovere i dati dal buffer di ricezione. |
comEvCTS | 3 | Modifica nella linea CTS (Clear To Send). |
comEvDSR | 4 | Modifica nella linea DSR (Data Set Ready). Questo evento viene generato solo quando DSR cambia da 1 a 0. |
comEvCD | 5 | Modifica nella linea CD (Carrier Detect). |
comEvRing | 6 | Individuato segnale telefonico. È possibile che alcuni UART (universal asynchronous receiver-transmitters) non supportino questo evento. |
comEvEOF | 7 | Ricevuto carattere indicatore di fine file (carattere ASCII 26). |
Non è comunque indispensabile la consultazione della proprietà CommEvent che personalmente uso solo nelle comunicazioni seriali complesse; vediamo dunque come implementare una semplice ricezione dati dalla seriale.
Come prima cosa è necessario dire al controllo MSCOMM che desideriamo essere informati della ricezione dei caratteri, impostando la proprietà Rthreshold a 1. Quindi l'apertura della porta la modifichiamo come segue.
Private Sub Form_Load() MSComm1.CommPort = 1 ' Selezioniamo la COM1 MSComm1.Settings = "9600,n,8,1" ' Le impostazioni della seriale MSComm1.RThreshold = 1 ' voglio essere informato della ricezione di ogni singolo carattere MSComm1.PortOpen = True ' Apriamo la porta. End Sub
Aggiungete al Form un controllo testo Text1 (fatelo bello grande) ed impostate la proprietà MultiLine
a True.
A questo punto aggiungete il seguente codice:
Private Sub MSComm1_OnComm() Dim Rx$ Rx$ = MSComm1.Input ' Leggo il contenuto del buffer di ricezione (e svuoto .Input) If Len(Rx$) Then ' Se ho ricevuto qualcosa lo scrivo nella TextBox Text1.Text = Text1.Text & Rx$ End If End Sub
Nella propriatà Input vengono riportati tutti i caratteri ricevuti. è da notare che la
proprietà Input la si può leggere una sola volta in quanto dopo averla letta (ed in
questo caso assegnata alla variabile Rx$) viene automaticamente azzerata (="").
Per esempio il seguente codice NON FUNZIONA!
If Len(MSComm1.Input) Then Text1.Text = Text1.Text &
MSComm1.Input
perche l' IF svuota il contenuto di Input ed alla text box viene appesa una stringa vuota.
Impostando la proprietà Rthreshold a 1 imponiamo al controllo MSCOMM di richiamare l'evento OnComm()
alla ricezione di ogni singolo carattere. In ogni caso, visto che la segnalazione non viene data in Interrupt-Time,
lo scatenarsi dell'evento OnComm() può avvenire in ritardo se il PC è impegnato in qualche
elaborazione, ed è quindi molto probabile che nella proprietà Input sia contenuto più
di un carattere.
Per poter anche trasmettere, aggiungete il seguente codice:
Private Sub Text1_KeyPress(KeyAscii As Integer) MSComm1.Output = Chr$(KeyAscii) End Sub
Abbiamo così costruito con la TextBox un semplice terminale ASCII.
L'Handshake DTR-DSR con l'MSCOMM:
L'MSCOMM non supporta l'handshake DTR-DSR, quindi occorre gestirlo a mano.
Supponiamo di dover inviare un grosso file (50Kbytes) ad una stampante seriale ad aghi. Se ci limitassimo ad aprire
la COMx ed inviare l'intero file, la stampante sarebbe costretta a porre tutti i dati ricevuti nel buffer di ricezione,
in quanto la sua velocità di stampa è nettamente inferiore alla velocità di ricezione dei
dati. Se quindi la stampante non ha un buffer sufficientemente grande, perderebbe i dati in eccesso. Contate che
una stampante seriale raramente ha un buffer di ricezione più grande di 16Kbytes.
La stampante, per risolvere il problema, quando è accesa e pronta a ricevere dati alza il proprio segnale
DTR (DSR del PC), e lo abbassa quando vuole interrompere il flusso di dati in ingresso.
Quello che noi dobbiamo fare è monitorare il segnale DSR ed interrompere il flusso di trasmissione quando
il segnale si abbassa, e riprenderlo quando il segnale si alza. Vediamo un esempio:
Const BLOCCO% = 448 ' Qta massima di bytes mantenuta nel buffer di trasmissione Dim BufFile$ ' Buffer contenente i bytes da inviare alla stampante Private Sub Form_Load() MSComm1.Handshaking = comNone ' nessun handshake, xchè lo gestisco a mano MSComm1.CommPort = 1 ' COM1 MSComm1.OutBufferSize = BLOCCO% + 64 ' 512 MSComm1.RThreshold = 1 ' in verità non serve, non dobbiamo ricevere alcunchè :-)) MSComm1.SThreshold = BLOCCO% - 64 ' Generare un evento di trasmissione ogni volta che sono stati trasmessi 64 bytes MSComm1.Settings = "9600,n,8,1" MSComm1.PortOpen = True BufFile$ = ... .. . ' supponiamo un file da 50Kbytes MSComm1.Output = Left$(BufFile$, 1) ' Innesco il SEND. Per fermarlo è sufficiente fare Buffer$="" Buffer$ = Mid$(BufFile$, 2) ' elimino dal buffer il byte ormai inviato sulla seriale End Sub
La costante BLOCCO% rappresenta la quantità di bytes che viene costantamente mantenuta nel buffer di
trasmissione. Tale costante è stata impostata su 448 il che suppone che la stampante alzi il segnale di
DTR quando il suo buffer non è ancora completamente pieno, ma è in grado di accettare almeno altri
448 bytes (il che dovrebbe essere sempre vero, al limite basta abbassare questo valore).
Notare che l'impostazione dell'handshake è comNone in quanto l'handshake lo gestiamo noi a mano, che ho
limitato la dimensione del buffer di trasmissione a 512 ossia lo stretto indispensabile, anzi, 64 bytes in più
(non si sa mai :-), e che ho impostato la soglia dell'evento di trasmissione a BLOCCO% - 64 di modo da generarlo
quando sono stati trasmessi 64 bytes (anche se in verità, causa ritardi di processo, quando verrà
generato l'evento di trasmissione spesso saranno stati trasmessi più di 64 bytes).
Per innescare la trasmissione viene inviato sulla seriale il primo byte del file da trasmettere (BufFile$)
in quanto questo genererà subito un evento di soglia di trasmissione raggiunta (CommEvent=comEvSend;
la soglia l'abbiamo impostata a BLOCCO% - 64). E' la funzione posta nell'evento OnComm() che provvederà
al resto.
Vediamo quindi cosa scrivere nell'evento OnComm().
Private Sub MSComm1_OnComm() Dim C% ' Controllo se ho dati da inviare If Len(BufFile$) Then ' controllo se la stampate è pronta If MSComm1.DSRHolding = True Then ' mi assicuro che il buffer non sia già pieno If MSComm1.OutBufferCount < BLOCCO% Then ' invio altri bytes fino a riempire il Buffer in TX con BLOCCO% bytes C% = BLOCCO% - MSComm1.OutBufferCount If C% > Len(BufFile$) Then C% = Len(BufFile$) MSComm1.Output = Left$(BufFile$, C%) BufFile$ = Mid$(BufFile$, C% + 1) End If End If If Len(BufFile$) = 0 Then MsgBox "File trasmesso con successo" End If End Sub
Per come abbiamo configurato l'MSCOMM esso genererà l'evento di OnComm() ogni volta che la quantità
di bytes presenti nel buffer di trasmissione scende sotto BLOCCO%-64, ed ogni volta che la linea DSR passa da bassa
ad alta (quando la linea passa da alta a bassa non viene generato alcun evento).
In questo modo se la linea DSR è sempre attiva, la trasmissione è continuamente alimentata dall'evento
soglia di trasmissione raggiunto (CommEvent=comEvSend), e viene interrotta se la linea DSR viene
abbassata. Quando viene rialzata viene generato un evento di DSR (CommEvent=comEvDSR) e quindi la trasmissione
riparte.
Se si volesse sapere se la stampante è accesa e pronta a ricevere dati, è sufficiente controllare
lo stato della linea DSR subito dopo l'apertura della COMx, in questo modo:
... . . MSComm1.Settings = "9600,n,8,1" MSComm1.PortOpen = True If MSComm1.DSRHolding = False Then MsgBox "Stampante non pronta!" MSComm1.PortOpen = False Exit Sub End If .. . .
Questo era un esempio di come tenere sotto controllo una trasmissione in funzione dello stato del DSR. Per quanto
riguarda il DTR, ossia quando è il nostro programma che desidera interrompere il flusso di dati in ricezione,
è sufficiente lavorare sulla proprietà DTREnable, ponendola a True quando siamo pronti
a ricevere dati, e su False quando si desidera interrompere il flusso di dati in ingresso. Se un controllo
sul DTR non fosse necessario, è buona norma tenere sempre il segnale DTR alzato DTREnable=True.
L'Handshake XON-XOFF e RTS-CTS con l'MSCOMM:
Questi due handshake sono gestibili direttamente dal controllo MSCOMM semplicemente impostando la proprietà
Handshaking rispettivamente su comXOnXOff per l'XON-XOFF e comRTS per RTS-CTS, si possono
abilitare anche entrambi con comRTSXOnXOff.
Comunicazioni a pacchetto con l'MSCOMM:
Spesso capita di dover instaurare una comunicazione viva tra PC ed altri dispositivi. In questi casi vengono
usati veri e propri protocolli di comunicazione più o meno complessi.
Un protocollo di comunicazione standard non esiste, infatti fino ad oggi non ho mai incontrato due dispositivi
diversi usare lo stesso protocollo, quindi in questa sede mi limiterò a dei protocolli immaginari. Sta a
voi recuperare la documentazione sul protocollo usato dal dispositivo a cui vi dovete connettere, che se non fornita
insieme al dispositivo, potrà essere reperita con una e-mail/telefonata al produttore.
Cos'è un pacchetto dati:
Tutte le comunicazioni sono basate sul concetto di pacchetto (alias record, alias datagram,
alias frame), ossia le informazioni vengono inviate e ricevute in modo da distinguere l'inizio e la fine
delle stesse. Le dimensioni dei pacchetti dipendono dal protocollo di comunicazione, comunque in genere non superano
i 1024 byte.
Possiamo ipotizzare di dover comunicare con uno strumento di misura, chessò, una bilancia, e di usare un
protocollo ASCII che adotta come carattere di Start del pacchetto un "(" e come carattere di Stop
un ")". E' ovviamente importante che i caratteri di Start e di Stop non siano usati
all'interno dei pacchetti.
Se ricevessimo dalla seriale la stringa "(12.76)" potrebbe voler dire che la bilancia sta pesando un
oggetto di 12.76 Kg.
Isolare i pacchetti ricevuti:
Quando siamo in ascolto sulla seriale, riceviamo un carattere alla volta, quindi sta a noi riuscire ad isolare
i vari pacchetti in modo corretto.
Per far questo occorre far uso di un buffer temporaneo nel quale vengono accodati tutti i dati in ingresso, e ricercati
ed isolati i pacchetti in esso contenuti.
Qui sotto è riportato un esempio di funzione OnComm() che isola nel modo più corretto i singoli
pacchetti dati. Per capirne il funzionamento leggere attentamente i commenti al codice.
Dim RxBuffer$ Private Sub MSComm1_OnComm() Dim Pos As Integer Dim Rx$ Dim Pacchetto$ ' Estraggo i dati arrivati Rx$ = MSComm1.Input If Len(Rx$) = 0 Then Exit Sub ' Accodo i dati arrivati al buffer RxBuffer$ = RxBuffer$ & Rx$ ' Mi assicuro di avere il buffer allineato ai pacchetti If Left$(RxBuffer$, 1) <> "(" Then ' Scarto tutto ciò che sta prima del primo carattere di START ' Questo capita quando si ricevono dei caratteri dovuti a disturbi sulla ' linea seriale, tutt'altro che rari! es: "ÿs2ÿÿ(12.7" ' oppure quando ho aperto la seriale (ed iniziato a bufferizzare i dati) ' a metà di un pacchetto dati in ricezione. es: "2.76)(13.16)" Pos = InStr(RxBuffer$, "(") RxBuffer$ = Mid$(RxBuffer$, Pos) If Len(RxBuffer$) = 0 Then ' se il buffer è vuoto, tanto vale uscire Exit Sub End If End If ' A questo punto controllo se sono arrivati dei pacchetti ' completi, li estraggo (tutti) e li elaboro Do Pos = InStr(RxBuffer$, ")") If Pos = 0 Then Exit Do ' nessun'altro pacchetto completo, esco dal loop ' Estraggo il pacchetto, togliendo i caratteri di START e di STOP Pacchetto$ = Mid$(RxBuffer$, 2, Pos - 2) ' Elimino il pacchetto dal buffer RxBuffer$ = Mid$(RxBuffer$, Pos + 1) ' a questo punto se il buffer conteneva "(12.76)(13.16)", ' Pacchetto$="12.76" e RxBuffer$="(13,16)" ' Controllo se ci sono altri caratteri di START nel pacchetto. ' Infatti potrebbe capitare che un disturbo simuli un carattere ' di START ritrovando nel buffer qualcosa del tipo "(ÿ(12.75)" ' oppure un disturbo potrebbe aver eliminato un carattere di STOP, ' e quindi ritrovarci quancosa del tipo "(12.75ÿ(13.16) dove la "ÿ" ' sarebbe dovuta essere un ")" (in questo caso il primo pacchetto è perso) Pos = InStr(Pacchetto$, "(") If Pos Then ' esempio pacchetto contiene "12.75ÿ(13.16" Pacchetto$ = Mid$(Pacchetto$, Pos + 1) ' ora Pacchetto$="13.16" End If ' Il lavoro dello strato ISO/OSI di linea è compiuto, ' passo il pacchetto ricevuto al prossimo strato, che si preoccuperà di ' interpretare il significato dei dati ricevuti Call ElaboraPacchetto(Pacchetto$) Loop End Sub
Verificare la correttezza dei dati tramite i caratteri di controllo
(CHECKSUM, XOR, CRC):
In tutte le connessioni (anche quelle Ethernet) esistono disturbi sulla linea che possono danneggiare i dati in
transito.
Per riuscire ad identificare ed eliminare i pacchetti danneggiati vengono attuate diverse strategie, alcune delle
quali, le più sofisticate, riescono perfino a ricostruire le parti danneggiate.
Nel nostro caso, ci viene in aiuto il Bit di Parità che ci segnala se un dato carattere ricevuto
è stato danneggiato. Ma la parità riesce a distinguere un errore solo nel 50% dei casi, senza contare
che non è detto che il dispositivo sia configurato per usare la parità.
Quindi il metodo utilizzato per scovare i pacchetti danneggiati è quello di inserire nel pacchetto stesso
uno o più caratteri calcolati in funzione dei caratteri dei dati del pacchetto stesso. In questo modo quando
il ricevente riceve il pacchetto, riesegue il calcolo basandosi sui dati ricevuti, e paragona il proprio risultato
con quello arrivato insieme al pacchetto; se sono uguali il pacchetto è _quasi_ certamente integro, se sono
diversi, il pacchetto è corrotto e quindi da scartare.
I metodi base per calcolare i caratteri di controllo sono tre: CHECKSUM, XOR, CRC.
Supponiamo quindi di inserire un carattere di controllo nel pacchetto dati, in posizione fissa, per esempio in coda ai dati del pacchetto: "(12.75XX)" dove la "XX" verrà sostituita con i caratteri di controllo.
Ecco un esempio semplice di implementazione della funzione ElaboraPacchetto().
Function ElaboraPacchetto(Pacchetto$) Dim Dati$ Dim chk$ ' Separo i dati dal carattere di controllo ' (i caratteri di controllo sono gli utimi 2 della stringa) Dati$ = Left$(Pacchetto$, Len(Pacchetto$) - 2) chk$ = Mid$(Pacchetto$, Len(Pacchetto$) - 2, 2) ' Controllo se il pacchetto è corrotto If chk$ <> CalcolaChecksum(Dati$) Then ' Errore, il pacchetto è corrotto Exit Sub End If ' Visualizzo il peso Label1.Caption = Dati$ End Function
Ed ecco degli esempi di calcolo dei caratteri di controllo:
Function CalcolaChecksum(Dati$) As String Dim ix As Integer Dim Checksum As Integer Checksum = 0 ' (sarebbe superfluo) For ix = 1 To Len(Dati$) Checksum = (Checksum + Asc(Mid$(Dati$, ix, 1))) Mod 256 Next ix ' Torna il valore esadecimale del carattere di controllo CalcolaChecksum = Right$("00" & Hex$(Checksum), 2) End Function Function CalcolaXor(Dati$) As String Dim ix As Integer Dim Checksum As Integer Checksum = 0 ' (sarebbe superfluo) For ix = 1 To Len(Dati$) Checksum = (Checksum Xor Asc(Mid$(Dati$, ix, 1))) Next ix ' Torna il valore esadecimale del carattere di controllo CalcolaXor = Right$("00" & Hex$(Checksum), 2) End Function
Per quanto riguarda il CRC, rimando all'esempio presente nella sezione Sorgenti del Sito Comune del NG it.comp.lang.visual-basic http://www.murialdo.it/it_lang_vb.
Inviare un pacchetto:
Inviare un pacchetto è molto più semplice rispetto al riceverlo. Di seguito c'è
un esempio di come può essere fatta una funzione di invio dati.
Sub InviaDati(Dati$) Dim Pacchetto$ Pacchetto$ = "(" & Dati$ & CalcolaChecksum(Dati$) & ")" MSComm1.Output = Pacchetto$ End Sub
Problemi riscontrabili con l'MSCOMM:
Il controllo MSCOMM ha un difetto sull'evento OnComm() riscontrabile quando si sta facendo del
DEBUG del proprio codice. Se ad esempio si mette in pausa l'esecuzione del programma, e durante l'attesa giungono
dei caratteri alla seriale, al riavvio del programma non verrà generato alcun evento per i caratteri già
ricevuti.
Una scappatoia per ovviare al problema è quella di aggiungere un TIMER, con una cadenza dell'ordine
dei 250-400 mSec, che si limita a richiamare la funzione MSComm1_OnComm(), compensando così alla
mancata generazione (in debug) dell'evento OnComm().
Esempio:
Private Sub Timer1_Timer() Call MSComm1_OnComm End Function
Non tutte le proprietà dell'oggetto MSCOMM sono state affrontate, per il semplice motivo che il
VB ha un ottimo help in linea dal quale poter facilmente reperire e capire tutte le altre caratteristiche di questo
oggetto.
Spero che questo articolo vi sia servito a comprendere l'utilizzo della seriale, abbattendo i timori nell'affrontare
il mondo delle comunicazioni.
A questo punto la palla è vostra, provate a mettere in pratica i suggerimenti dati, e vedrete che usare
la seriale non è affatto cosa complessa.
Ettore Maronese By -MES-
wm-mes@maronese.com
http://www.maronese.com/mes