Usare l' RS-232 da Visual Basic

v 1.11 21/02/2001
Ettore Maronese


Indice:

  1. Introduzione
  2. Introduzione alla comunicazione seriale
  3. Lo standard poco standard
  4. I segnali usati dalla RS-232
  5. Handshake, la sincronizzazione delle comunicazioni
  6. Cablaggi, cavi, connettori e connessioni
  7. Primi passi con il controllo MSCOMM
  8. L'evento OnComm() e la proprietà CommEvent
  9. L'Handshake DTR-DSR con l'MSCOMM
  10. L'Handshake XON-XOFF e RTS-CTS con l'MSCOMM
  11. Comunicazioni a pacchetto con l'MSCOMM
  12. Problemi riscontrabili con l'MSCOMM
  13. Conclusioni



Introduzione:

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.




Lo standard poco standard:

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.




I segnali usati dalla RS-232:

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

Connettore DTE 25 poli maschio (PC)

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

Connettore DCE 25 poli femmina (modem)

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

Connettore DTE 9 poli maschio (PC)

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

Connettore DCE 9 poli femmina (modem)

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




Conclusioni:

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