Usare l' RS-232 da Visual Basic
v 1.11 21/02/2001 Ettore Maronese http://www.it-lang-vb.net/Documentazione/Articoli/MSCOMM.htm Indice: Introduzione Introduzione alla comunicazione seriale Lo standard poco standard I segnali usati dalla RS-232 Handshake, la sincronizzazione delle comunicazioni DTR-DSR XON-XOFF RTS-CTS Cablaggi, cavi, connettori e connessioni Esempi di cablaggi seriali Primi passi con il controllo MSCOMM L'evento OnComm() e la proprieta' CommEvent L'Handshake DTR-DSR con l'MSCOMM L'Handshake XON-XOFF e RTS-CTS con l'MSCOMM Comunicazioni a pacchetto con l'MSCOMM Cos'e' un pacchetto dati Isolare i pacchetti ricevuti Verificare la correttezza dei dati tramite i caratteri di controllo (CHECKSUM, XOR, CRC) Inviare un pacchetto Problemi riscontrabili con l'MSCOMM Conclusioni Introduzione: Usare la porta seriale del PC e' da molti ritenuta cosa ostica, questo perche' spesso non si conosce il reale funzionamento della periferica, o non si e' in possesso delle conoscenze di base per gestire un protocollo di comunicazione. L'alone di mistero che circonda questa periferica e' pero' del tutto ingiustificato, la seriale puo' essere facilmente gestita con poche righe di codice, e Visual Basic ci viene incontro fornendo l'oggetto MSCOMM con il quale e' 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 e' 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 verra' 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 e' appunto la velocita' di trasmissione, ossia il baud rate, e viene espresso in bit al secondo. La scomposizione dei caratteri in bit e' trasparente al sistema, chi invia i dati si limita a fornire i caratteri alla periferica, chi li riceve si limitera' ad estrarre i caratteri ricevuti da una coda di ricezione. La seriale dei Personal Computers e' di tipo asincrona, ossia permette di trasmettere e ricevere contemporaneamente. Lo standard poco standard: L' RS-232c e' uno standard un po' particolare in quanto lascia molta liberta' sulla modalita' di trasmissione dei caratteri. Queste modalita' di lavorare rappresentano la configurabilita' dell'hardware della RS-232c. Vediamo quali sono queste modalita': Baud rate = e' possibile indicare la velocita' 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 = e' possibile scegliere se inviare tutti gli 8 bit del carattere, o escludere l'ultimo inviandone quindi solo 7. Se si usa la modalita' 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 modalita' a 7 BIT, il bit piu' significativo non verra' inviato (e' possibile avere un numero di bit inferiore a 7, ma di fatto sono modalita' che non vengono mai usate). Parita' = e' possibile aggiungere ad ogni invio di carattere un bit che indica se il numero di bit a 1 del carattere e' pari o dispari. Questo puo' 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 parita' ricevuto, se e' diverso viene generato un errore di parita'. La parita' puo' anche essere di tipo mark o space, nel primo caso il bit di parita' 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 Parita' 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 velocita' 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, Parita'=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 velocita' 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, Parita'=Pari, Bits=8, Stop=2. Ogni carattere ci porta via 1 bit di start (fisso) + 8 bit di dati + 1 di parita' 2 bits di stop, in tutto 12 bits. Quindi i bits totali da inviare sono 10240*12=122880 ed andando ad una velocita' di 4800 bits al secondo ci impiegheremo 122880/4800 = 25,6 secondi. Qundo si instaura una comunicazione seriale e' quindi indispensabile che la periferica (l'hardware) sia impostata nel medesimo modo in entrambi i dispositivi. Questa enorme quantita' 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 e' possibile collegarsi ad una bilancia senza sapere come e' 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 e' pronto ad instaurare una comunicazione (Data Terminal Ready) OUT DSR Il dispositivo remoto e' 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. e' 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 e' irrilevante. Handshake, la sincronizzazione delle comunicazioni: In una comunicazione seriale e' spesso necessario utilizzare metodologie di sincronizzazione. La sincronizzazione serve per evitare la perdita di dati dovuta al fatto che la controparte non e' pronta a riceverli. Questa situazione puo' avvenire o perche' il dispositivo che riceve non e' operativo, o perche' il buffer di ricezione e' pieno, e quindi non e' piu' in grado di accettare altri caratteri. Un classico esempio di Buffer Overflow lo danno le stampanti, dove la velocita' di ricezione dei dati e' superiore alla loro velocita' 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 e' 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 e' necessario sapere che il DTR e' un'uscita ed il DSR e' un'ingresso, e che il DTR del primo dispositivo e' connesso al DSR del secondo, ed il DTR del secondo dispositivo e' 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) cosicche' chi deve trasmettere sa che puo' 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 e' pronta a ricevere, e lo abbassa solo se il buffer di ricezione si satura. Di conseguenza il DSR (segnale in ingresso) e' sempre alto, ossia con il consenso a trasmettere, e risulta basso solo se il ricevente e' spento o non connesso, o perche' il buffer di ricezione del ricevente e' saturo. XON-XOFF La sincronizzazione tramite XON-XOFF e' concettualmente simile alla precedente, pero' 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 e' molto comodo perche' 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). - Puo' 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 piu') e nel secondo caso la perdita di dati (il buffer e' 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, e' 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 e' il PC ed il DCE e' 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 e' stata fatta per utilizzare cavi seriali pin-to-pin, infatti, per esempio, il segnale TXD (trasmissione dati) del PC e' sullo stesso pin del RXD (ricezione dati) del modem. Se pero' si connettono due PC tra loro, non e' 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 e' ovviamente errata. L'importante e' sapere che i computers sono sempre dei DTE e che le periferiche, tipo modem, stampanti, bilance ecc.., sono sempre dei DCE. Detto questo c'e' 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 proprieta', di un solo evento ( OnComm() ) e nessun metodo. Iniziamo col creare un nuovo progetto, quindi rendiamo disponibile il controllo MSCOMM andando sul menu' 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 proprieta' PortOpen a True si richiede al sistema il permesso d' utilizzo della porta scelta (COM1). Se la porta e' disponibile il sistema operativo la assegnera' a noi, diversamente se la porta e' gia' utilizzata da un'altro processo verra' generato un errore intercettabile con l'istruzione ON ERROR. Per chiudere la porta e rilasciarne il controllo, e' sufficiente impostare PortOpen a False. A questo punto siamo gia' 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. (e' possibile anche con un solo PC, basta far usare all'HyperTerminal la COM2, e connettere il cavo tra COM1 e COM2). Il codice si puo' 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 e' 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 proprieta' CommEvent: Il controllo MSCOMM e' asincrono, ossia non occorre che il programma dedichi tutto il suo tempo a controllare cio' che accade sulla seriale, e' il controllo MSCOMM che si preoccupa di informare il programma di cio' che accade tramite il richiamo dell'evento OnComm() e l'impostazione della proprieta' CommEvent. Quindi l'intera intelligenza della comunicazione e' da implementare nell'evento OnComm(). Quando viene richiamato l'evento OnComm(), Nella proprieta' CommEvents e' riportato l'evento accaduto. Esso puo' 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 e' andato perduto. comEventRxOver 1008 Overflow del buffer di ricezione. Spazio esaurito nel buffer di ricezione. comEventRxParity 1009 Errore di parita'. È stato rilevato un errore di parita'. 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 e' presente un numero di caratteri inferiore a quello definito dalla proprieta' Sthreshold. comEvReceive 2 Numero di caratteri Rthreshold ricevuti. Questo evento viene generato fino a quando non si utilizza la proprieta' 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 e' comunque indispensabile la consultazione della proprieta' CommEvent che personalmente uso solo nelle comunicazioni seriali complesse; vediamo dunque come implementare una semplice ricezione dati dalla seriale. Come prima cosa e' necessario dire al controllo MSCOMM che desideriamo essere informati della ricezione dei caratteri, impostando la proprieta' 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 proprieta' 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 propriata' Input vengono riportati tutti i caratteri ricevuti. e' da notare che la proprieta' Input la si puo' 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 proprieta' 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() puo' avvenire in ritardo se il PC e' impegnato in qualche elaborazione, ed e' quindi molto probabile che nella proprieta' Input sia contenuto piu' 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 cosi' 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 velocita' di stampa e' nettamente inferiore alla velocita' 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 piu' grande di 16Kbytes. La stampante, per risolvere il problema, quando e' 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 e' 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, xche' lo gestisco a mano MSComm1.CommPort = 1 ' COM1 MSComm1.OutBufferSize = BLOCCO% + 64 ' 512 MSComm1.RThreshold = 1 ' in verita' non serve, non dobbiamo ricevere alcunche' :-)) 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 e' sufficiente fare Buffer$="" Buffer$ = Mid$(BufFile$, 2) ' elimino dal buffer il byte ormai inviato sulla seriale End Sub La costante BLOCCO% rappresenta la quantita' di bytes che viene costantamente mantenuta nel buffer di trasmissione. Tale costante e' stata impostata su 448 il che suppone che la stampante alzi il segnale di DTR quando il suo buffer non e' ancora completamente pieno, ma e' 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 e' 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 piu' (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 verita', causa ritardi di processo, quando verra' generato l'evento di trasmissione spesso saranno stati trasmessi piu' di 64 bytes). Per innescare la trasmissione viene inviato sulla seriale il primo byte del file da trasmettere (BufFile$) in quanto questo generera' 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 provvedera' 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 e' pronta If MSComm1.DSRHolding = True Then ' mi assicuro che il buffer non sia gia' 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 generera' l'evento di OnComm() ogni volta che la quantita' 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 e' sempre attiva, la trasmissione e' 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 e' accesa e pronta a ricevere dati, e' 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 e' il nostro programma che desidera interrompere il flusso di dati in ricezione, e' sufficiente lavorare sulla proprieta' 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, e' 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 proprieta' 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 piu' 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 limitero' 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, potra' essere reperita con una e-mail/telefonata al produttore. Cos'e' 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, chesso', 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 e' riportato un esempio di funzione OnComm() che isola nel modo piu' 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 cio' 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 meta' 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 e' 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 e' 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 e' compiuto, ' passo il pacchetto ricevuto al prossimo strato, che si preoccupera' 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 piu' sofisticate, riescono perfino a ricostruire le parti danneggiate. Nel nostro caso, ci viene in aiuto il Bit di Parita' che ci segnala se un dato carattere ricevuto e' stato danneggiato. Ma la parita' riesce a distinguere un errore solo nel 50% dei casi, senza contare che non e' detto che il dispositivo sia configurato per usare la parita'. Quindi il metodo utilizzato per scovare i pacchetti danneggiati e' quello di inserire nel pacchetto stesso uno o piu' 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 e' _quasi_ certamente integro, se sono diversi, il pacchetto e' 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" verra' 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 e' corrotto If chk$ <> CalcolaChecksum(Dati$) Then ' Errore, il pacchetto e' 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 e' molto piu' semplice rispetto al riceverlo. Di seguito c'e' un esempio di come puo' 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 verra' generato alcun evento per i caratteri gia' ricevuti. Una scappatoia per ovviare al problema e' quella di aggiungere un TIMER, con una cadenza dell'ordine dei 250-400 mSec, che si limita a richiamare la funzione MSComm1_OnComm(), compensando cosi' alla mancata generazione (in debug) dell'evento OnComm(). Esempio: Private Sub Timer1_Timer() Call MSComm1_OnComm End Function Conclusioni: Non tutte le proprieta' 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 e' vostra, provate a mettere in pratica i suggerimenti dati, e vedrete che usare la seriale non e' affatto cosa complessa. Ettore Maronese By -MES- wm-mes@maronese.com http://www.maronese.com/mes |