Programmazione Seriale in C/C++

Transcript

Programmazione Seriale in C/C++
Programmazione Seriale in C/C++
Traduzione a cura Vlad Silter - 5 B Inf anno scolastico 2010/2011
Articolo di Allen Denver - MSDN Microsoft
Novmbre 24, 2009
Sommario
Traduzione dall'inglese dell'articolo di Allend Denver sulla programmazione della porta seriale
in C/C++ con l'ausilio delle API di Windows per sistemi WIN32
1 Sommario
Le comunicazioni seriali in Microsoft Win32 sono signicativamente differenti dalle comunicazioni seriali in Microsoft Windows a 16 bit. Questo
articolo presuppone una familiarità con i fondamenti di multi threading e di
sincronizzazione in Win32. In piú, conoscere l'uso dell'heap delle funzioni
Win32 é utile per comprendere completamente i metodi dell'amministrazione
di memoria impiegati dagli esempi che si vedranno.
2 Descrizione
Le comunicazioni seriali in Microsoft Win32 sono signicativamente diverse dalle comunicazioni seriali in Microsoft Windows a 16 bit. Coloro che
sono abituati alle funzioni di comunicazione seriale a 16 bit dovranno imparare di nuovo molte parti del sistema per poter programmare propriamente
le comunicazioni seriali. Questo articolo vi aiuterà a farlo. Per coloro che
non conoscono le comunicazioni seriali, questo articolo sarà un'utile guida
per iniziare a programmare. In questo articolo si suppone che il lettore conosca i fondamenti del multi threading e la sincronizzazione in Win32. Inoltre,
una conoscenza di base delle funzioni heap di Win32 è utile per comprendere
appieno i metodi di gestione della memoria utilizzati dal esempio, MTTTY, incluso in questo articolo. Per ulteriori informazioni riguardanti queste
funzioni, consultare la documentazione del Platform SDK, le conoscenze di
base del SDK di Microsoft Win32 (Knowledge Base), o il Microsoft Developer Network Library. Le Application Programming Interface (API) che
controllano le caratteristiche dell'interfaccia utente riguardanti le nestre di
1
Programmazione seriale
2
dialogo, anche se non discusse qui, sono utili per comprendere pienamente
gli esempi forniti con questo articolo. I lettori che non hanno familiarità con
le pratiche generali della programmazione in Windows dovrebbero imparare
alcuni dei fondamenti della programmazione in Windows prima di arontare
le comunicazioni seriali.
3 Introduzione
Questo articolo si concentra sulle interfacce di programmazione delle applicazioni (API) e dei metodi che sono compatibili con Microsoft Windows
NT e Windows 95; quindi sono arontate solo le API supportate su entrambe
le piattaforme. Windows 95 supporta le Win32 API di Telefonia (TAPI) ma
Windows NT 3.x non le supporta; pertanto, questa discussione non includerà
le TAPI. Le Le TAPI vanno menzionate, tuttavia, in quanto implementano
bene l'interfaccia modem e il controllo delle chiamate. Un'applicazione di
produzione che lavora con i modem ed eettua chiamate telefoniche dovrebbe implementare queste funzioni utilizzando l'interfaccia TAPI. In questo
modo viene consentita una perfetta integrazione con le altre applicazioni che
utilizzano le TAPI che un utente potrebbe avere. Inoltre, questo articolo non
aronta alcune delle funzioni di congurazione in Win32, come ad esempio
GetCommProperties.L'articolo è suddiviso nelle seguenti sezioni: apertura
di una porta, lettura e scrittura (overlapped e non-overlapped), stato seriale
(eventi ed errori), e le impostazioni seriali (DCB, controllo di usso, time-out
di comunicazione).L'esempio incluso in questo articolo, MTTTY: Multithreaded TTY, implementa molte delle caratteristiche discusse qui. Utilizza
tre thread nella sua implementazione: un thread di interfaccia utente che si
occupa della gestione di memoria, un thread che controlla la scrittura, e un
thread di lettura di stato che legge i dati e gestisce le variazioni di stato sulla
porta. L'esempio utilizza alcuni diversi heap di dati per la gestione della
memoria. Si fa anche ampio uso di metodi di sincronizzazione per facilitare
la comunicazione tra i thread.
4 Apertura della Porta
La funzione CreateFile apre una porta di comunicazione. Ci sono due
modi per chiamare la funzione CreateFile per aprire la porta di comunicazione: overlapped e non-overlapped. L'esempio seguente è il modo corretto
di aprire una risorsa di comunicazione per operare in modo overlapped:
HANDLE hComm;
hComm = CreateFile( gszPort,
GENERIC_READ | GENERIC_WRITE,
0,
Programmazione seriale
3
0,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
0);
if (hComm == INVALID_HANDLE_VALUE)
// error opening port; abort
La rimozione del ag FILE_FLAG_OVERLAPPED dalla chiamata
a CreateFile specica un'operazione non overlapped. La sezione successiva
illustra l'operazione overlapped e non-overlapped. La documentazione del
Platform SDK aerma che, quando si apre una porta di comunicazione, la
chiamata a CreateFile deve avere i seguenti requisiti:
• fdwShareMode deve essere zero. Le porte di comunicazione non posso-
no essere condivise allo stesso modo in cui sono condivisi i le. Le applicazioni che utilizzano TAPI possono utilizzare le funzioni TAPI per
facilitare la condivisione delle risorse tra le applicazioni. Per le applicazioni Win32 che non utilizzano TAPI, l'ereditarietà o la duplicazione
degli handle è necessaria per condividere la porta di comunicazione.
La duplicazione degli handle è oltre la portata di questo articolo; si
prega di consultare la documentazione di Platform SDK per ulteriori
informazioni.
• il fdwCreate deve specicare la OPEN_EXISTING ag.
• il parametro hTemplateFile deve essere NULLO.
Una cosa da notare circa i nomi delle porte è che tradizionalmente sono
stati COM1, COM2, COM3 o COM4. L'API di Win32 non fornisce alcun
meccanismo per determinare quali porte esistono in un sistema. Windows
NT e Windows 95 tengono traccia delle porte installate in modo diverso l'uno dall'altro, di conseguenza nessun metodo sarebbe portabile su tutte le
piattaforme Win32. Alcuni sistemi hanno anche più porte rispetto al valore
tradizionale di al massimo quattro. I produttori di hardware e gli writer
serial-device-driver sono liberi di assegnare qualsiasi nome alle porte. Per
questo motivo, è meglio che gli utenti abbiano la possibilità di specicare il
nome della porta che desiderano utilizzare. Se una porta non esiste, si vericherà un errore (ERROR_FILE_NOT_FOUND) dopo il tentativo di aprire
la porta, e l'utente dovrebbe essere noticato che la porta non è disponibile.
5 Scrittura e lettura
La lettura e la scrittura dalle porte di comunicazione in Win32 è molto
simile ai le di input/output (I/O) in Win32. In eetti, le funzioni che svolgono operazioni I/O sui le sono le stesse funzioni usate per l'input/output
Programmazione seriale
4
seriale. Le operazioni di I/O in Win32 si possono fare in due modi: overlapped o non-overlapped. La documentazione di Platform SDK utilizza i termini
asincrona e sincrona per connotare questi tipi di operazioni I/O. Questo articolo, tuttavia, usa i termini overlapped e non-overlapped. L'input/output
non overlapped è conosciuto dalla maggior parte degli sviluppatori perché
questa è la forma tradizionale di I/O, dove è richiesta una operazione e viene
considerata completa quando la funzione ritorna. Nel caso di I/O overlapped, il sistema potrebbe tornare immediatamente al chiamante anche quando
un'operazione non è nita e avviserà il chiamante quando l'operazione sarà
completata. Il programma può utilizzare il tempo tra la richiesta di I/O e
il suo completamento per eseguire alcuni lavori in background. Leggere e
scrivere in Win32 è signicativamente diverso dal leggere e scrivere porte
di comunicazione seriale in Windows a 16 bit. Windows a 16 bit ha solo
le funzioni ReadComm e WriteComm. Le operazioni lettura e scrittura in
Win32 possono coinvolgere molte altre funzioni e scelte. Questi problemi
sono discussi di seguito.
5.1 I/O non overlapped
L' I/O non-overlapped è molto semplice, anche se ha dei limiti. Un'operazione avviene mentre il thread chiamante viene bloccato. Una volta
completata l'operazione, la funzione ritorna e il thread può continuare il suo
lavoro. Questo tipo di I/O è utile per le applicazioni multi-thread perché
mentre un thread è bloccato su una operazione di I/O, gli altri thread possono ancora lavorare. E' compito dell'applicazione serializzare l'accesso alla
porta in modo corretto. Se un thread è bloccato in attesa che l'operazione
di I/O sia completata, tutti gli altri thread che chiamano una API di comunicazione saranno bloccati no a quando l'operazione originale non verrà
completata. Per esempio, se un thread é in attesa che una funzione ReadFile
termini, qualsiasi altro thread che ha eettuato una funzione WriteFile sarebbe bloccato. Uno dei molti fattori da considerare quando si sceglie tra le
operazioni overlapped e non-overlapped è la portabilità. L'operazione overlapped non è una buona scelta, perché la maggior parte dei sistemi operativi
non lo supportano. Però la maggior parte dei sistemi operativi supportano
una qualche forma di multithreading, quindi l'I/O in modo multithreaded
non-overlapped può essere la scelta migliore per ragioni di portabilità.
5.2 I/O overlapped
L' I/O overlapped non è così semplice come l'I/O non-overlapped, ma
consente una maggiore essibilità ed ecienza. Una porta aperta per operazioni overlapped permette a più thread di fare operazioni di I/O allo stesso
tempo ed eseguire altri lavori mentre le operazioni sono in sospeso. Inoltre, il comportamento delle operazioni overlapped consente a un singolo th-
Programmazione seriale
5
read di rilasciare molte richieste diverse e lavorare in background mentre le
operazioni sono in sospeso. In entrambe le applicazioni single-threaded e
multi-threaded, deve avvenire la sincronizzazione tra l'emissione di richieste
e l'elaborazione dei risultati. Un thread dovrà essere bloccato no a quando il risultato di un'operazione sarà disponibile. Il vantaggio è che l'I/O
overlapped consente a un thread di fare alcuni lavori tra il momento della
richiesta e il suo completamento. Se nessun lavoro può essere fatto, allora l'unico motivo valido per l'I/O overlapped è che garantisce una migliore
reattività. I/O overlapped è il tipo di operazione che l'esempio MTTTY usa.
Si crea un thread che è responsabile per la lettura dei dati dalla porta e la
lettura dello stato della porta. Svolge inoltre attività periodiche di background. Il programma crea un altro thread esclusivamente per la scrittura
di dati attraverso la porta. 1
Un'operazione I/O overlapped ha due
parti: la creazione dell'operazione e l'individuazione del suo completamento.
Creare l'operazione implica la creazione di una struttura OVERLAPPED,
la creazione di un evento a reset manuale per la sincronizzazione, e invocare
la funzione opportuna (ReadFile o WriteFile). L'operazione di I/O potrebbe essere completata immediatamente oppure no. È un errore per un'applicazione dare per scontato che una richiesta di un'operazione overlapped
produca sempre un'operazione overlapped. Se l'operazione viene completata
immediatamente, l'applicazione deve essere pronta a proseguire l'elaborazione normalmente. La seconda parte di un'operazione overlapped è di rilevare
il suo completamento.Il rilevamento del completamento dell'operazione comporta l'attesa per l'handle dell'evento, il controllo del risultato overlapped,
e quindi la gestione dei dati. Il motivo per cui c'è più lavoro da fare quando
si lavora con un'operazione overlapped è che ci sono più possibilità di errori. Se un'operazione non-overlapped fallisce, la funzione restituisce solo un
codice di errore di ritorno. Se un'operazione overlapped fallisce, può fallire
nella creazione dell'operazione o mentre l'operazione è in sospeso. Si può
anche avere un time-out dell'operazione o un time-out in attesa del segnale
che indica che l'operazione è stata completata.
1
A volte le applicazioni abusano dei sistemi multithreading creando troppi thread.
Sebbene l'utilizzo di thread multipli può risolvere molti problemi dicili, creare troppi
thread non è il modo più eciente di utilizzarli in un'applicazione. I thread sforzano
di meno il sistema rispetto ai processi, ma necessitano comunque di risorse di sistema
come il tempo di CPU e memoria. Un'applicazione che crea troppi thread può inuire
negativamente sulle prestazioni dell'intero sistema. Un migliore utilizzo dei thread è quello
di creare una coda di richieste diversa per ogni tipo di lavoro e di avere un thread di
lavoro che rilascia una richiesta I/O per ogni voce nella coda di richieste. Questo metodo
è utilizzato dall'esempio MTTTY fornito con questo articolo.
Programmazione seriale
6
6 Lettura
La funzione ReadFile esegue un'operazione di lettura. Anche ReadFileEx
rilascia un'operazione di lettura, ma poiché non è disponibile in Windows
95, non viene spiegato in questo articolo. Ecco un frammento di codice che
descrive il modo di emettere una richiesta di lettura. Si noti che la funzione chiama una funzione per elaborare i dati se ReadFile restituisce TRUE.
Questa è la stessa funzione chiamata se l'operazione diviene overlapped. Da
notare il ag fWaitingOnRead che è denito dal codice; indica se un'operazione di lettura è overlapped. Usato per prevenire la creazione di una nuova
operazione di lettura se una è in sospeso.
DWORD dwRead;
BOOL fWaitingOnRead = FALSE;
OVERLAPPED osReader = {0};
// Create the overlapped event. Must be closed before exiting
// to avoid a handle leak.
osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osReader.hEvent == NULL)
// Error creating overlapped event; abort.
if (!fWaitingOnRead)
{
// Issue read operation.
if (!ReadFile(hComm, lpBuf, READ_BUF_SIZE, &dwRead, &osReader))
{
if (GetLastError() != ERROR_IO_PENDING)
// read not delayed?
// Error in communications; report it.
else fWaitingOnRead = TRUE;
} else {
// read completed immediately
HandleASuccessfulRead(lpBuf, dwRead);
}
}
La seconda parte dell'operazione overlapped è la rivelazione del suo completamento. L'Handle dell'evento nella struttura OVERLAPPED è passato
alla funzione WaitForSingleObject, che attenderà no a quando l'oggetto
sarà segnalato. Una volta che l'evento viene segnalato, l'operazione è completa. Questo non signica che essa è stata completata con successo, ma
solo che è stata completata. La funzione GetOverLappedResult riporta il risultato dell'operazione. In caso di errore, GetOverLappedResult restituisce
Programmazione seriale
7
FALSE e GetLastError restituisce il codice di errore. Se l'operazione è stata
completata con successo, GetOverLappedResult restituisce TRUE. 2 Ecco
un frammento di codice che mostra un modo per rilevare il completamento
di un'operazione di lettura overlapped. Si noti che il codice chiama la stessa funzione per elaborare i dati che è stata chiamata quando l'operazione
è stata completata immediatamente. Da notare anche l'uso del ag fWaitingOnRead che qui controlla l'ingresso nel codice di rilevamento, in quanto
dovrebbe essere chiamato solo quando un'operazione è in sospeso.
#define READ_TIMEOUT 500 // milliseconds
DWORD dwRes;
if (fWaitingOnRead)
{
dwRes = WaitForSingleObject(osReader.hEvent, READ_TIMEOUT);
switch(dwRes)
{
// Read completed.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(hComm, &osReader, &dwRead, FALSE))
// Error in communications; report it.
else
// Read completed successfully.
HandleASuccessfulRead(lpBuf, dwRead);
// Reset flag so that another opertion can be issued.
fWaitingOnRead = FALSE;
break;
case WAIT_TIMEOUT:
// Operation isn't complete yet. fWaitingOnRead flag isn't
// changed since I'll loop back around, and I don't want
// to issue another read until the first one finishes.
//
// This is a good time to do some background work.
break;
default:
2
GetOverLappedResult è in grado di rilevare il completamento dell'operazione, e di
restituire lo stato del fallimento dell'operazione. GetOverLappedResult restituisce FALSE e GetLastError restituisce ERROR_IO_INCOMPLETE quando l'operazione non è
completata. Inoltre, GetOverLappedResult può essere utilizzata per bloccare no al completamento dell'operazione. Questo trasforma eettivamente l'operazione overlapped in
una operazione non-overlapped e si ottiene facendo passare il parametro bWait come
TRUE.
Programmazione seriale
}
}
8
// Error in the WaitForSingleObject; abort.
// This indicates a problem with the OVERLAPPED structure's
// event handle.
break;
7 Scrittura
La trasmissione dei dati attraverso la porta di comunicazione è molto simile
alla lettura in quanto utilizza molte delle stesse API. Il frammento di codice
seguente dimostra come rilasciare e attendere che un'operazione di scrittura
sia stata completata.
BOOL WriteABuffer(char * lpBuf, DWORD dwToWrite)
{
OVERLAPPED osWrite = {0};
DWORD dwWritten;
DWORD dwRes;
BOOL fRes;
// Create this write operation's OVERLAPPED structure's hEvent.
osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osWrite.hEvent == NULL)
// error creating overlapped event handle
return FALSE;
// Issue write.
if (!WriteFile(hComm, lpBuf, dwToWrite, &dwWritten, &osWrite))
{
if (GetLastError() != ERROR_IO_PENDING)
{
// WriteFile failed, but isn't delayed. Report error and abort.
fRes = FALSE;
}
else
// Write is pending.
dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);
switch(dwRes)
{
// OVERLAPPED structure's event has been signaled.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(hComm, &osWrite, &dwWritten, FALSE))
fRes = FALSE;
Programmazione seriale
9
else
// Write operation completed successfully.
fRes = TRUE;
break;
default:
// An error has occurred in WaitForSingleObject.
// This usually indicates a problem with the
// OVERLAPPED structure's event handle.
fRes = FALSE;
break;
}
} else
// WriteFile completed immediately.
fRes = TRUE;
}
CloseHandle(osWrite.hEvent);
return fRes;
Si noti che il codice utilizza la funzione WaitForSingleObject con un valore di timeout INFINITO. Questo fa sì che la funzione WaitForSingleObject
aspetti per sempre no a quando l'operazione viene completata; questo potrebbe dare l'impressione che il thread o il programma sia appeso mentre, in
realtà, l'operazione di scrittura sta semplicemente richiedendo molto tempo
per essere completata o il controllo del usso ha bloccato la trasmissione.
Il controllo dello stato, arontato più avanti, è in grado di rilevare questa
condizione, ma non di far restituire un valore a WaitForSingleObject. Tre
cose possono migliorare questo problema:
• Inserire il codice in un thread separato. Questo permette ad altri
thread di eseguire tutte le funzioni che essi desiderano, mentre il nostro
thread di scrittura aspetta che la scrittura sia stata completata. Questo
è ciò che l'esempio MTTTY fa.
• Usare COMMTIMEOUTS per far completare la scrittura dopo un de-
terminato periodo di tempo. Questo è discusso più a fondo nella sezione Time-out di Comunicazioni più avanti in questo articolo. Questo é
anche ciò che l'esempio MTTTY consente di fare.
• Cambia la chiamata a WaitForSingleObject per includere un valore
reale di timeout. Questo provoca più problemi, perché se il programma
genera un'altra operazione mentre un'operazione vecchia è ancora in
sospeso, nuove strutture overlapped ed eventi a queste associate devono
essere assegnati. Questo tipo di progettazione è dicile, in particolare
Programmazione seriale
10
rispetto all'utilizzo di una coda di lavoro per le operazioni. Il metodo
di coda di lavoro è utilizzata nell'esempio MTTTY.
Poiché la funzione WaitForSingleObject nel frammento di codice precedente utilizza un timeout INFINITO, essa è equivalente all'utilizzo di
GetOverLappedResult con TRUE per il parametro fWait. Ecco il codice
equivalente in forma molto semplicata:
3
BOOL WriteABuffer(char * lpBuf, DWORD dwToWrite)
{
OVERLAPPED osWrite = {0};
DWORD dwWritten;
BOOL fRes;
// Create this writes OVERLAPPED structure hEvent.
osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osWrite.hEvent == NULL)
// Error creating overlapped event handle.
return FALSE;
// Issue write.
if (!WriteFile(hComm, lpBuf, dwToWrite, &dwWritten, &osWrite))
{
if (GetLastError() != ERROR_IO_PENDING)
{
// WriteFile failed, but it isn't delayed. Report error and abort.
fRes = FALSE;
} else {
// Write is pending.
if (!GetOverlappedResult(hComm, &osWrite, &dwWritten, TRUE))
fRes = FALSE;
else
// Write operation completed successfully.
fRes = TRUE;
}
} else
// WriteFile completed immediately.
fRes = TRUE;
3
i valori di timeout nelle funzioni di sincronizzazione non sono time-out di comunicazione. I time-out di sincronizzazione fanno in modo che le funzioni WaitForSingleObject o
WaitForMultipleObjects restituiscano WAIT_TIMEOUT. Questo non è come il time-out
di un'operazione di I/O. I time-out di comunicazione vengono descritti più avanti in questo
articolo.
Programmazione seriale
}
11
CloseHandle(osWrite.hEvent);
return fRes;
GetOverLappedResult non è sempre il modo migliore per aspettare
che una operazione overlapped sia completata. Ad esempio, se un'applicazione deve attendere un altro handle di un evento, il primo pezzo di codice
è un esempio migliore rispetto al secondo. La chiamata a WaitForSingleObject è facile da cambiare in WaitForMultipleObjects per includere gli handle
supplementari da attendere. Questo è ciò che fa l'applicazione MTTTY. Un
errore comune in I/O overlapped è di riutilizzare una struttura OVERLAPPED prima che la precedente operazione overlapped sia stata completata.
Se una nuova operazione overlapped viene emessa prima che una precedente operazione sia stata completata, una nuova struttura OVERLAPPED
deve essere allocata. Deve essere creato anche un nuovo evento a reset manuale per il membro hEvent della struttura OVERLAPPED. Una volta che
un'operazione overlapped è completa, la struttura OVERLAPPED ed il suo
evento sono disponibili per essere riutilizzati. L'unico membro della struttura OVERLAPPED che ha bisogno di modiche per la comunicazione seriale
è il membro hEvent. Gli altri membri della struttura OVERLAPPED dovrebbero essere inizializzati a zero e lasciati così. Modicare gli altri membri
della struttura OVERLAPPED non è necessario per i dispositivi di comunicazione seriale. La documentazione per ReadFile e WriteFile aerma che
i membri Oset e OsetHigh della struttura OVERLAPPED devono essere
aggiornati attraverso l'applicazione, oppure i risultati saranno imprevedibili.
Questa linea guida dovrebbe essere applicata a strutture SOVRAPPOSTE
utilizzate per altri tipi di risorse, come i le.
8 Stato della porta seriale
Esistono due metodi per recuperare lo stato di una porta di comunicazione.
Il primo è quello di denire una maschera di evento che causa la notica
dell'applicazione, quando gli eventi desiderati si vericano. La funzione SetCommMask imposta questa maschera evento, e la funzione WaitCommEvent
attende che gli eventi desiderati si verichino. Queste funzioni sono simili alle
funzioni a 16-bit SetCommEventMask e EnableCommNotication, salvo che
le funzioni Win32 non inviano messaggi WM_COMMNOTIFY. In realtà, il
messaggio WM_COMMNOTIFY non fa nemmeno parte della API Win32.
Il secondo metodo per il recupero dello stato della porta di comunicazione è
quello di chiamare periodicamente alcune funzioni a diversi stati. Il Polling
non è, ovviamente, né eciente, né raccomandato.
Programmazione seriale
12
8.1 Eventi di comunicazione
Gli eventi di comunicazione possono avvenire in qualsiasi momento nel corso
dell'utilizzo di una porta di comunicazione. I due passi necessari per ricevere
la notica degli eventi di comunicazione sono i seguenti:
1. SetCommMask imposta gli eventi desiderati che causano una notica.
2. WaitCommEvent emette un controllo dello stato. Il controllo di stato
può essere un'operazione overlapped o non-overlapped, come la lettura
e la scrittura.4
Di seguito un esempio della istruzione SetCommMask:
DWORD dwStoredFlags;
dwStoredFlags = EV_BREAK | EV_CTS | EV_DSR | EV_ERR | EV_RING |\
EV_RLSD | EV_RXCHAR | EV_RXFLAG | EV_TXEMPTY ;
if (!SetCommMask(hComm, dwStoredFlags))
// error setting communications mask
4
La parola evento in questo contesto riguarda solo gli eventi generati durante la comunicazione seriale. Non ha nulla a che vedere con l'oggetto evento presente nel sysop usato
per la sincronizzazione
Programmazione seriale
EV_BREAK
EV_CTS
EV_DSR
EV_ERR
EV_RING
EV_RLSD
EV_RXCHAR
EV_RXFLAG
EV_TXEMPTY
13
Flag degli eventi di comunicazione
Una pausa è stata rilevata in input.
Il segnale CTS (Clear-to-send) ha cambiato stato. Per conoscere lo stato attuale della linea CTS, chiamare la funzione
GetCommModemStatus.
Il segnale DSR (data-set-ready) ha cambiato stato. Per conoscere lo stato attuale della linea DSR, chiamare la funzione
GetCommModemStatus.
Si è vericato un errore di stato della linea. Gli errori
di stato della linea sono CE_FRAME, CE_OVERRUN e
CE_RXPARITY. Per trovare la causa dell'errore, chiamare
la funzione ClearCommError.
E' stata rilevata una chiamata.
Il segnale RLSD (receive-line-signal-detect) ha cambiato stato. Per conoscere lo stato attuale della linea RLSD, chiamare la funzione GetCommModemStatus. Si noti che viene
comunemente indicata come linea CD (Carrier Detect).
Un nuovo carattere è stato ricevuto e inserito nel buer di input. Vedere la sezione Avvertenze per ulteriori informazioni
su questo ag.
Il carattere di evento è stato ricevuto e inserito nel buer di
input. Il carattere di evento è specicato nel membro EvtChar della struttura DCB arontata più avanti. La sezione
Avvertenze seguente si applica anche a questa bandiera.
L'ultimo carattere nel buer di output è stato inviato al dispositivo di porta seriale. Se un buer hardware viene utilizzato,
questo ag indica soltanto che tutti i dati sono stati inviati
all'hardware. Non c'è alcun modo di rilevare quando il buf
fer hardware é vuoto senza interrogare direttamente lhardware
con un driver di periferica.
Dopo aver specicato la maschera degli eventi, la funzione WaitCommEvent rileva il vericarsi degli eventi. Se la porta é aperta per il funzionamento nonoverlapped, allora la funzione WaitCommEvent non contiene
alcuna struttura overlapped. La funzione blocca il thread chiamante no al
vericarsi di uno degli eventi. Se non si verica un evento, il thread si può
bloccare a tempo indeterminato.
Di seguito un esempio che mostra come il processo aspetta un evento
EV_RING quando la porta é congurata per operazioni non overlapped
DWORD dwCommEvent;
if (!SetCommMask(hComm, EV_RING))
// Error setting communications mask
return FALSE;
if (!WaitCommEvent(hComm, &dwCommEvent, NULL))
// An error occurred waiting for the event.
Programmazione seriale
14
return FALSE;
else
// Event has occurred.
return TRUE;
Come si puó notare, il codice rimane bloccato se un evento non si verica.
Una soluzione migliore si ha congurando la porta per operazioni overlapped
e si mette in attesa per un evento sullo stato della porta
#define STATUS_CHECK_TIMEOUT
500
// Milliseconds
DWORD
dwRes;
DWORD
dwCommEvent;
DWORD
dwStoredFlags;
BOOL
fWaitingOnStat = FALSE;
OVERLAPPED osStatus = {0};
dwStoredFlags = EV_BREAK | EV_CTS | EV_DSR | EV_ERR | EV_RING | EV_RLSD |\
EV_RXCHAR | EV_RXFLAG | EV_TXEMPTY;
if (!SetCommMask(comHandle, dwStoredFlags))
// error setting communications mask; abort
return 0;
osStatus.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osStatus.hEvent == NULL)
// error creating event; abort
return 0;
for ( ; ; )
{
// Issue a status event check if one hasn't been issued already.
if (!fWaitingOnStat)
{
if (!WaitCommEvent(hComm, &dwCommEvent, &osStatus))
{
if (GetLastError() == ERROR_IO_PENDING)
bWaitingOnStatusHandle = TRUE;
else
// error in WaitCommEvent; abort
break;
}
else
// WaitCommEvent returned immediately.
// Deal with status event as appropriate.
Programmazione seriale
15
ReportStatusEvent(dwCommEvent);
}
// Check on overlapped operation.
if (fWaitingOnStat)
{
// Wait a little while for an event to occur.
dwRes = WaitForSingleObject(osStatus.hEvent, STATUS_CHECK_TIMEOUT);
switch (dwRes)
{
// Event occurred.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(hComm, &osStatus, &dwOvRes, FALSE))
// An error occurred in the overlapped operation;
// call GetLastError to find out what it was
// and abort if it is fatal.
else
// Status event is stored in the event flag
// specified in the original WaitCommEvent call.
// Deal with the status event as appropriate.
ReportStatusEvent(dwCommEvent);
}
// Set fWaitingOnStat flag to indicate that a new
// WaitCommEvent is to be issued.
fWaitingOnStat = FALSE;
break;
case WAIT_TIMEOUT:
// Operation isn't complete yet. fWaitingOnStatusHandle flag
// isn't changed since I'll loop back around and I don't want
// to issue another WaitCommEvent until the first one finishes.
//
// This is a good time to do some background work.
DoBackgroundWork();
break;
default:
// Error in the WaitForSingleObject; abort
// This indicates a problem with the OVERLAPPED structure's
// event handle.
CloseHandle(osStatus.hEvent);
return 0;
}
}
CloseHandle(osStatus.hEvent);
Programmazione seriale
16
Il codice precedente usa molte delle struttura usate per la funzione Read
in congurazione overlapped. Ci sono due eetti collaterali interessanti in
SetCommMask e WaitCommEvent. In primo luogo, se la porta di comunicazione è aperta per il funzionamento nonoverlapped, WaitCommEvent sarà
bloccato no a quando si verica un evento. Se un altro thread chiama SetCommMask per impostare una nuova maschera di evento, quel thread viene
bloccato sulla chiamata a SetCommMask. La ragione è che la chiamata originale WaitCommEvent nel primo thread è ancora in esecuzione. La chiamata
a SetCommMask blocca il thread no a quando la funzione WaitCommEvent ritorna nel primo thread. Questo eetto collaterale è universale per le
porte aperte con I/O non OVERLAPPED. Se un thread è bloccato su una
qualsiasi funzione di comunicazione e un altro thread chiama una funzione
di comunicazione, il secondo thread resta bloccato no a quando la funzione
di comunicazione nisce nel primo thread. La seconda nota interessante di
queste funzioni è il loro uso su una porta aperta per il funzionamento sovrapposto OVERLAPPED. Se SetCommMask imposta una nuova maschera
evento, qualsiasi WaitCommEvent in attesa verrà completato con successo,
e la maschera degli eventi prodotta dall'operazione è NULL.
8.2 Avvertenze
Utilizzare il ag EV_RXCHAR noticherà il thread che un byte è giunto
alla porta. Questo evento, usato in combinazione con la funzione ReadFile, consente a un programma di leggere i dati solo quando sono nel buer di
ricezione, in contrasto con una lettura che aspetta i dati. Questo è particolarmente utile quando una porta è aperta per il funzionamento non sovrapposto
OVELAPPED perché il programma non ha bisogno di eseguire il polling per
i dati in arrivo; al programma viene noticato che i dati sono arrivati grazie
a EV_RXCHAR. I tentativi iniziali di codica di questa soluzione spesso
producono il seguente pseudocodice:
DWORD dwCommEvent;
DWORD dwRead;
char chRead;
if (!SetCommMask(hComm, EV_RXCHAR))
// Error setting communications event mask.
for ( ; ; )
{
if (WaitCommEvent(hComm, &dwCommEvent, NULL))
{
if (ReadFile(hComm, &chRead, 1, &dwRead, NULL))
// A byte has been read; process it.
else
Programmazione seriale
}
17
// An error occurred in the ReadFile call.
break;
}
else
// Error in WaitCommEvent.
break;
Il codice sopra non funziona correttamente senza impostare il corretto timeout. I time-out di comunicazione, arontati più avanti nell'articolo, inuenzano il comportamento dell'operazione ReadFile al ne di indurla a ritornare senza attendere l'arrivo di byte. Questo argomento viene arontato in
maniera più approfondita nella sezione Time-out di Comunicazione di questo articolo. L'avvertenza sopra riguardante EV_RXCHAR, vale anche per
EV_RXFLAG. Se i caratteri di ag arrivano in rapida successione, gli eventi EV_RXFLAG potrebbero non essere tutti serviti. Ancora una volta, la
soluzione migliore è quella di leggere tutti i byte. L'avvertimento vale anche per altri eventi non legati alla ricezione dei caratteri. Se gli altri eventi
avvengono in rapida successione alcune delle notiche verranno perse. Ad
esempio, se il voltaggio della linea CTS inizia alto, poi diventa basso, alto,
e di nuovo basso, si verica un evento EV_CTS. Non vi è alcuna garanzia
di quanti eventi EV_CTS saranno eettivamente rilevati con WaitCommEvent se i cambiamenti nella linea CTS accadono rapidamente. Per questo
motivo, WaitCommEvent non può essere utilizzata per monitorare lo stato
della linea. Questo argomento viene arontato nella sezione Stato Modem.
8.3 Gestione degli errori e stato della comunicazione
Uno dei ag di comunicazione specicato nella chiamata a SetCommMask
può essere EV_ERR. Il vericarsi dell'evento EV_ERR indica che si è vericata una condizione di errore nella porta di comunicazione. Possono anche
vericarsi degli errori che non attivano EV_ERR. In entrambi i casi, gli errori associati alla porta di comunicazione sospendono tutte le operazioni di I/O
no a quando non viene rimossa la condizione di errore. ClearCommError
è la funzione da chiamare per rilevare gli errori e rimuovere la condizione di
errore. ClearCommError fornisce anche lo stato di comunicazione, indicando
il motivo dell'arresto della trasmissione; indica anche il numero di byte in attesa nei buer di trasmissione e ricezione. La trasmissione può interrompersi
a causa di errori o del usso di controllo. Il controllo del usso viene arontato più avanti in questo articolo. Ecco il codice che illustra come chiamare
ClearCommError:
COMSTAT comStat;
DWORD dwErrors;
Programmazione seriale
BOOL
BOOL
18
fOOP, fOVERRUN, fPTO, fRXOVER, fRXPARITY, fTXFULL;
fBREAK, fDNS, fFRAME, fIOE, fMODE;
// Get and clear current errors on the port.
if (!ClearCommError(hComm, &dwErrors, &comStat))
// Report error in ClearCommError.
return;
// Get error flags.
fDNS = dwErrors & CE_DNS;
fIOE = dwErrors & CE_IOE;
fOOP = dwErrors & CE_OOP;
fPTO = dwErrors & CE_PTO;
fMODE = dwErrors & CE_MODE;
fBREAK = dwErrors & CE_BREAK;
fFRAME = dwErrors & CE_FRAME;
fRXOVER = dwErrors & CE_RXOVER;
fTXFULL = dwErrors & CE_TXFULL;
fOVERRUN = dwErrors & CE_OVERRUN;
fRXPARITY = dwErrors & CE_RXPARITY;
// COMSTAT structure contains information regarding
// communications status.
if (comStat.fCtsHold)
// Tx waiting for CTS signal
if (comStat.fDsrHold)
// Tx waiting for DSR signal
if (comStat.fRlsdHold)
// Tx waiting for RLSD signal
if (comStat.fXoffHold)
// Tx waiting, XOFF char rec'd
if (comStat.fXoffSent)
// Tx waiting, XOFF char sent
if (comStat.fEof)
// EOF character received
if (comStat.fTxim)
// Character waiting for Tx; char queued with TransmitCommChar
if (comStat.cbInQue)
// comStat.cbInQue bytes have been received, but not read
if (comStat.cbOutQue)
// comStat.cbOutQue bytes are awaiting transfer
Programmazione seriale
19
8.4 Stato del Modem
La chiamata a SetCommMask può comprendere i ag EV_CTS, EV_DSR,
EV_RING, e EV_RLSD. Questi ag indicano le variazioni di tensione sulla
linea di porta seriale. Non vi è alcuna indicazione dello stato attuale di queste
linee, ma solo che un cambiamento si è vericato. La funzione GetCommModemStatus recupera lo stato attuale di queste linee di stato restituendo
una maschera di bit che indica uno 0 a bassa o tensione mulla e 1 per l'alta
tensione per ciascuna delle linee. Si prega di notare che il termine RLSD
(Receive Line Signal Detect) è comunemente indicata come il CD (Carrier
Detect) line.
Il ag EV_RING non funziona in Windows 95 come indicato
in precedenza. La funzione GetCommModemStatus, tuttavia,
consente di rilevare lo stato della linea di RING.
Cambiamenti di stato in queste linee sono usati anche per generare un
evento di invio/ricezione dati. La funzione ClearCommError é usata anche
per rilevare se la trasmissione è stata sospesa a causa di un usso dati. Se
necessario, un thread può chiamare ClearCommError per rilevare se l'evento
é causato da un usso dati. Il controllo di usso é contemplato nel Flow Control più avanti in questo articolo. Ecco il codice che illustra come chiamare
GetCommModemStatus:
DWORD dwModemStatus;
BOOL fCTS, fDSR, fRING, fRLSD;
if (!GetCommModemStatus(hComm, &dwModemStatus))
// Error in GetCommModemStatus;
return;
fCTS = MS_CTS_ON &
fDSR = MS_DSR_ON &
fRING = MS_RING_ON
fRLSD = MS_RLSD_ON
dwModemStatus;
dwModemStatus;
& dwModemStatus;
& dwModemStatus;
// Do something with the flags.
8.5 Funzioni Estese
Il driver cambierá automaticamente lo stato delle linee di controllo se necessario. Di solito, é un driver a cambiare le linee di stato. Se un dispositivo
utilizza le linee di controllo della porta di comunicazione in modo diverso dallo standard RS-232, il driver standard di comunicazione seriale non
Programmazione seriale
20
controllerá il dispositivo, e sará necessario un driver di dispositivo personalizzato. Ci sono occasioni in cui le linee di controllo standard sono sotto il
controllo dell'applicazione invece che del driver di comunicazione seriale. Per
esempio, un'applicazione puó attuare un proprio controllo di usso. In questo caso, l'applicazione é responsabile del cambiamento dello stato delle linee
RTS e DTR. Un driver di comunicazione puó eettuare questo tipo di operazioni estese grazie alla funzione EscapeCommFunction. Con l'utilizzo di
questa funzione, il driver puó eseguire anche altre funzioni, come il settaggio
di una condizione di BREAK. Per ulteriori informazioni riguardanti questa
funzione, consultare la documentazione Platform SDK, il Knowledge Base
Microsoft Win32 SDK, o la libreria Microsoft Developer Network (MSDN).
8.6 Impostazione del DCB
L'aspetto cruciale in applicazioni per la comunicazione seriale, riguarda le
impostazioni della struttura Device-Control Block (DCB). Gli errori più comuni nella programmazione sono dovuti all'inizializzazione sbagliata della
struttura DCB. Quando la comunicazione seriale non avviene nella maniera
giusta, di solito è suciente esaminare la struttura DCB per trovare il problema. Ci sono 3 modalità per inizializzare la struttura. Il primo metodo
usa la funzione GetCommState, che restituisce la DCB in uso per la porta
delle comunicazioni. Il codice seguente mostra come va usata la funzione
GetCommState:
DCB dcb = {0};
if (!GetCommState(hComm, &dcb)) // Error getting current DCB settings
else// DCB is ready for use.
Il secondo metodo per inizializzare il DCB, utilizza la funzione BuildCommDCB. Questa funzione inserisce il baud, la parità, il numero degli
stop bits, e il numero dei data bit nella DCB. La funzione ssa i membri
controllori di usso a valori di default. Per dettagli sui valori di default usati, consultare la documentazione della funzione BuildCommDCB. Gli altri
membri della DCB non sono modicati da questa funzione. E' compito del
programma assicurarsi che gli altri membri della DCB non causeranno errori. La cosa più semplice da fare a riguardo è inizializzare la struttura DCB
con tutti i membri a 0, e in seguito ssare il membro della grandezza della
struttura in byte. Se l'inizializzazione a 0 della struttura DCB non avviene,
ci potrebbero essere valori diversi da 0 nei membri riservati; questo porterà
ad un errore quando si tenterà di usare la DCB. La seguente funzione mostra
come va usato correttamente questo metodo:
DCB dcb;
FillMemory(&dcb, sizeof(dcb), 0);
Programmazione seriale
21
dcb.DCBlength = sizeof(dcb);
if (!BuildCommDCB("9600,n,8,1", &dcb)) {
// Couldn't build the DCB. Usually a problem
// with the communications specification string.
return FALSE;
}
else
// DCB is ready for use.
Il terzo metodo per inizializzare la struttura DCB è di farlo manualmente.
Il programma alloca la struttura DCB e imposta ogni membro con qualsiasi
valore desiderato. Questo metodo non funzionerà bene con eventuali modiche alla DCB in implementazioni future di Win32 e non è raccomandato.
Di solito un'applicazione ha bisogno di impostare alcuni dei membri della DCB in maniera dierente dai valori di default, oppure deve modicare
le impostazioni durante l'esecuzione. Una volta eseguita l'inizializzazione
della DCB, è possibile modicare i valori di singoli membri. Le modiche
alla struttura DCB non hanno alcun eetto sul comportamento della porta
nché non viene eseguita la funzione SetCommState. Ecco un esempio di
codice che recupera la DCB attuale, modica il baud, e tenta di modicare
la congurazione:
DCB dcb;
FillMemory(&dcb, sizeof(dcb), 0);
if (!GetCommState(hComm, &dcb))
// get current DCB
// Error in GetCommState
return FALSE;
// Update DCB rate.
dcb.BaudRate = CBR_9600;
// Set new state.
if (!SetCommState(hComm, &dcb))
// Error in SetCommState. Possibly a problem with the communications
// port handle or a problem with the DCB structure itself.
Ecco la spiegazione di ciascuno dei membri della DCB e del modo in cui
agiscono su altre parti delle funzioni della comunicazione seriale. 5
5
La maggior parte di queste informazioni è presa dalla documentazione della Platform
SDK. Dato che la documentazione è la regola che determina cosa fanno esattamente i membri, questa tabella potrebbe non essere completamente adabile se sono state apportate
modiche al sistema operativo.
Programmazione seriale
22
Membri della struttura DCB
DCBlength
Grandezza, in byte, della struttura. Dovrebbe essere impostata prima di
chiamare la funzione SetCommState per aggiornare le impostazioni
Specica il valore del baud che il dispositivo di comunicazione usa per funzionare. Questo membro può essere un valore di baud reale, oppure un
indice di baud.
Specica se la modalità binaria è attivata. Win32 API non supporta trasferimenti di tipo non-binario, quindi questo membro dovrebbe essere TRUE.
Non funziona se il membro è FALSE.
Specica se la parità viene controllata. Se il membro è TRUE, il controllo
della parità viene eettuato e verranno segnalati eventuali errori. Non va
confuso con il membro della parità, che controlla il tipo di parità usato nella
comunicazione
Specica se il segnale CTS (clear-to-send) è monitorato per il controllo del
usso di output . Se questo membro è TRUE e il CTS è basso, l'output
viene sospeso nché il CTS non diventa di nuovo alto. Il segnale CTS viene
controllato dal DCE (di solito un modem), il DTE (di solito il PC) monitora
semplicemente lo stato del segnale, ma non lo cambia.
Specica se il segnale DSR (data-set-ready) è monitorato per il controllo del
usso di output. Se questo membro è TRUE e il CTS è basso, l'output viene
sospeso nché il DSR non diventa di nuovo alto. Come prima, il segnale è
sotto il controllo del DCE; il DTE monitora semplicemente questo segnale.
Specica il controllo di usso del DTR (data-terminal-ready). Questo
membro può assumere uno dei seguenti valori:
BaudRate
fBinary
fParity
fOutxCtsFlow
fOutxDsrFlow
fDtrControl
• DTR_CONTROL_DISABLE: Abbassa il livello della linea DTR
quando il dispositivo è acceso. L'applicazione può modicare lo stato
della linea con EscapeCommFunction.
• DTR_CONTROL_ENABLE:Alza il livello della linea DTR quando
il dispositivo è acceso. L'applicazione può modicare lo stato della
linea con EscapeCommFunction.
• DTR_CONTROL_HANDSHAKE: Abilita il handshaking del controllo di usso. Se questo valore è usato, è un errore per l'applicazione
aggiustare la linea usando EscapeCommFunction.
fDsrSensitivity
fTXContinueOnXo
Specica se il driver di comunicazione è sensibile allo stato del segnale DSR.
Se questo membro è TRUE, il driver ignora qualsiasi byte ricevuto, a meno
che la linea di input del modem DSR non sia alta.
Specica se la trasmissione si ferma quando il buer di input è pieno e
il driver ha trasmesso il carattere XOFF. Se questo membro è TRUE, la
trasmissione continua dopo che il carattere XOFF è stato inviato. Se questo
membro è FALSO, la trasmissione non continua nché il buer di input non
si trova quasi vuoto (secondo i limiti indicati da XonLim) e il driver ha
trasmesso il carattere XON.
Membri della struttura DCB - continua
Programmazione seriale
fOutX
fInX
fErrorChar
fErrorChar
fNull
fRtsControl
23
Specica se il controllo di usso XON/XOFF viene usato durante la trasmissione. Se questo membro è TRUE, la trasmissione si ferma quando
il carattere XOFF viene ricevuto e ricomincia quando il carattere XON è
ricevuto.
Specica se il controllo di usso XON/XOFF viene usato durante la trasmissione. Se questo membro è TRUE, il carattere XOFF è inviato quando
il buer è quasi pieno (entro i limiti indicati da XoLim), e il carattere XON
è inviato quando il buer è quasi vuoto (entro i limiti indicati da XonLim).
Specica quali bit ricevuti con errori di parità sono sostituiti con il carattere
specicato dal membro ErrorChar. Se questo membro è TRUE e il membro
fParity è TRUE, avviene la sostituzione.
Specica quali bit ricevuti con errori di parità sono sostituiti con il carattere
specicato dal membro ErrorChar. Se questo membro è TRUE e il membro
fParity è TRUE, avviene la sostituzione.
Specica se i byte nulli vengono scartati. Se questo membro è TRUE, i byte
nulli vengono scartati quando sono ricevuti.
Specica il controllo di usso di input RTS (request-to-send). Se questo
valore è 0, la default è RTS_CONTROL_HANDSHAKE. Questo membro
può avere uno dei seguenti valori:
• RTS_CONTROL_DISABLE Abbassa il livello della linea RTS
quando il dispositivo è acceso.
L'applicazione può usare
EscapeCommFunction per cambiare lo stato della linea.
• RTS_CONTROL_ENABLE Alza il livello della linea RTS quando il
dispositivo è acceso. L'applicazione può usare EscapeCommFunction
per cambiare lo stato della linea.
• RTS_CONTROL_HANDSHAKE Abilita il handshaking del controllo di usso RTS. Il driver alza lo stato della linea RTS, abilitando la
DCE a inviare, quando il buer di input ha abbastanza spazio per
ricevere dati. Il driver abbassa la linea RTS, in modo che la DCE non
può inviare, quando il buer di input non ha abbastanza spazio per ricevere dati. Se questo valore viene usato, è un errore dell'applicazione
se usa EscapeCommFunction per aggiustare la linea.
• RTS_CONTROL_TOGGLE Specica che la linea RTS sarà alta se
i byte sono disponibili per la trasmissione. Dopo che tutti i byte del
buer sono stati inviati, la linea RTS sarà bassa. Set viene impostato
questo valore è un errore dell'applicazione se usa EscapeCommFunction per aggiustare la linea. Questo valore viene ignorato in Windows95, perché induce il driver a comportarsi come se fosse specicato
RTS_CONTROL_ENABLE.
fAbortOnError
Determina se le operazione lettura/scrittura vengono terminate se avviene
un errore. Se questo membro è TRUE, il driver termina tutte le operazioni lettura e scrittura con uno stato di errore (ERROR_IO_ABORTED)
se avviene un errore. Il driver non accetterà altre comunicazioni nché l'applicazione non avrà riconosciuto l'errore chiamando la funzione
ClearCommError.
Membri della struttura DCB - continua
Programmazione seriale
fDummy2
wReserved
XonLim
XoLim
Parity
24
Riservata; Da non usare.
Non usata; deve essere impostata a zero.
Specica il numero minimo di byte ammessi nel buer di input prima che
il carattere XON venga inviato.
Specica il numero massimo di byte ammessi nel buer di input prima che
il carattere XOFF venga inviato. Il numero massimo di byte ammessi è
calcolato sottraendo questo valore, in byte, dalla dimensione del buer di
input.
Specica lo schema della parità che verrà usato. Questo membro può usare
uno dei seguenti valori:
• EVENPARITY - Pari
• MARKPARITY - Mark
• NOPARITY - Nessuna parità
• ODDPARITY - Dispari
StopBits
Specica il numero di bit di Stop che verranno usati. Questo membro può
avere uno dei seguenti valori:
• ONESTOPBIT - 1 Stop bit
• ONE5STOPBITS - 1,5 Stop bit
• TWOSTOPBITS - 2 Stop bits
XonChar
XoChar
ErrorChar
EofChar
EvtChar
wReserved1
Specica il valore del carattere XON sia per la trasmissione che per la
ricezione.
Specica il valore del carattere XOFF sia per la trasmissione che per la
ricezione.
Specica il valore del carattere che verrà usato per sostituire i byte ricevuti
con errore di parità.
Specica il valore del carattere che verrà usato per segnalare la ne dei dati.
Specica il valore del carattere che verrà usato per causare l'evento
EV_RXFLAG. Questa impostazione non fa accadere nulla senza l'uso di EV_RXFLAG nella funzione SetCommMask, e senza l'uso di
WaitCommEvent.
Riservata; Da non usare.
8.7 Controllo del usso
Il controllo del usso nelle comunicazioni seriali ore un meccanismo per
sospendere le comunicazioni mentre uno dei dispositivi è occupato, oppure
non può comunicare per un altro motivo. Tradizionalmente ci sono 2 tipi di
controllo del usso: hardware e software. Un problema frequente con le comunicazioni seriali riguarda l'operazione di Write virtuale che in realtà non
scrive i dati sul dispositivo. Spesso il problema è legato al fatto che l'applicazione non ha stabilito un protocollo per il controllo del usso dati. Una
attenta verica della struttura DCB rivela che uno o più dei seguenti membri
Programmazione seriale
25
potrebbero essere TRUE: fOutxCtsFlow, fOutxDsrFlow, oppure fOutX. Un
altro meccanismo che rivela che il controllo del usso è abilitato è chiamare
la funzione ClearCommError ed esaminare la struttura COMSTAT. Così si
rivela quando la trasmissione è sospesa a causa del controllo del usso. Prima
di parlare dei tipi di controllo di usso, è necessaria una buona comprensione
di alcuni termini. Le comunicazioni seriali avvengono tra 2 dispositivi. Tradizionalmente, c'è un PC e un modem o una stampante. Il PC è chiamato
DTE (Data Terminal Equipment). La DTE a volte viene chiamato ospite.
Il modem, la stampante o un'altra periferica vengono chiamati DCE (Data
Communications Equipment). A volte la DCE viene chiamata dispositivo.
8.7.1 Controllo del usso hardware
La DTE e la DCE devono accordarsi sul tipo di controllo del usso usato per
una sessione di comunicazioni. Impostando la struttura DCB, abilitando il
controllo del usso, si congura semplicemente la DTE. La DCE ha anche
bisogno di una congurazione che assicura che la DCE e la DTE usano lo
stesso tipo di controllo del usso. Non esiste un meccanismo fornito da
Win32 per impostare il controllo del usso della DCE. Interruttori di tipo
DIP presenti sul dispositivo oppure comandi inviati ad esso stabiliscono di
solito la congurazione. La seguente tabella descrive le linee di controllo, la
direzione del controllo del usso, e l'eetto della linea sulla DTE e la DCE.
Programmazione seriale
CTS(Clear To
Send) Controllo
del usso output
DSR (Data Set
Ready) Controllo del usso output
DSR (Data Set
Ready) Controllo del usso input
RTS (Ready To
Send) Controllo
del usso input
DTR (Data Terminal
Ready)
Controllo
del
usso input
26
Linee del controllo del usso hardware
La DCE imposta la linea alta per indicare che può ricevere dati. La DCE
imposta la linea bassa per indicare che non può ricevere dati.Se il membro
fOutxCtsFlow della DCB è TRUE, allora la DTE non invierà dati se la linea
è bassa. Inizia ad inviare se la linea è altaSe il membro fOutxCtsFlow della
DCB è FALSE, allora lo stato della linea non inuenza la trasmissione.
La DCE imposta la linea alta per indicare che può ricevere dati. La DCE
imposta la linea bassa per indicare che non può ricevere dati.Se il membro
fOutxCtsFlow della DCB è TRUE, allora la DTE non invierà dati se la linea
è bassa. Inizia ad inviare se la linea è alta Se il membro fOutxCtsFlow della
DCB è FALSE, allora lo stato della linea non inuenza la trasmissione.
Se le linea DSR è bassa, allora i dati che arrivano alla porta vengono ignorati.Se le linea DSR è alta, allora i dati che arrivano alla porta vengono
ricevuti.Questo comportamento avviene se il membro fDsrSensitivity della DCB è impostato TRUE. Se è FALSE, allora lo stato della linea non
inuenza la ricezione.
La linea RTS è controllata dalla DTE.Se il membro fRtsControl della DCB
è impostato a RTS_CONTROL_HANDSHAKE, viene usato il seguente
controllo di usso:Se il buer di input ha abbastanza spazio per ricevere
dati (almeno metà del buer deve essere vuoto), il driver imposta la linea
RTS alta.Se il buer di input ha poco spazio per ricevere dati (meno di un
quarto del buer è vuoto), il driver imposta la linea RTS bassa.Se il membro fRtsControl della DCB è impostato a RTS_CONTROL_TOGGLE,
allora il driver imposta la linea RTS alta quando sono disponibili dati da inviare. Il driver imposta la linea bassa quando non ci sono dati da inviare disponibili.Windows95 ignora questo valore e lo tratta nella stessa maniera di RTS_CONTROL_ENABLE.Se il membro fRtsControl della DCB è impostato a RTS_CONTROL_ENABLE, oppure a
RTS_CONTROL_DISABLE, allora l'applicazione ha la libertà di cambiare lo stato della linea in base alle necessità. Da notare che in questo
caso lo stato della linea non inuenza la ricezione.La DCE sospenderà la
trasmissione dei dati quando la linea diventa bassa, e inizierà di nuovo la
trasmissione quando la linea diventa alta.
La linea DTR è controllata dalla DTE. Se il membro fDtrControl della DCB
è impostato a DTR_CONTROL_HANDSHAKE, viene usato il seguente
controllo di usso:Se il buer di input ha abbastanza spazio per ricevere
dati (almeno metà del buer è vuoto), il driver imposta la linea DTR alta.
Se il buer di input ha poco spazio per ricevere dati (meno di un quarto
del buer è vuoto), il driver imposta la linea DTR bassa.Se il membro fDtrControl della DCB è impostato a DTR_CONTROL_ENABLE, oppure
a DTR_CONTROL_DISABLE, allora l'applicazione ha la libertà di cambiare lo stato della linea in base alle necessità. In questo caso lo stato della
linea non inuenza la ricezione.La DCE sospenderà la trasmissione dei dati
quando la linea diventa bassa, e inizierà di nuovo la trasmissione quando la
linea diventa alta.
Si riconosce subito il bisogno del controllo del usso quando avviene l'errore di tipo CE_RXOVER. Questo errore indica un overow del buer che
riceve, e relativa perdita di dati. Se i dati arrivano alla porta più veloce
Programmazione seriale
27
di quanto possono essere letti, avviene l'errore CE_RXOVER. Se viene aumentata la capacità del buer di input l'errore avviene più raramente, ma
questa soluzione non risolve completamente il problema. Per attenuare denitivamente il problema questo problema è necessario ricorrere al controllo
del usso in input. Quando il driver scopre che il buer di input è quasi
pieno, abbasserà le linee del controllo del usso input. Questo induce la
DCE a fermare la trasmissione, e in questo modo la DTE avrà abbastanza
tempo per leggere i dati del buer di input. Quando il buer di input ha più
spazio a disposizione, il voltaggio delle linee del controllo del usso input è
impostato alto, e la DCE inizia di nuovo a inviare dati. Un errore simile è
CE_OVERRUN. Questo errore avviene quando arrivano nuovi dati prima
che l'hardware di comunicazione e il driver di comunicazione hanno nito di
ricevere i dati vecchi. Questo può accadere quando la velocità di trasmissione è troppo alta per il tipo di hardware di comunicazione o la CPU. Può
avvenire anche quando il sistema operativo non è libero di occuparsi dell'hardware di comunicazione. L'unico modo per aggiustare questo problema
è di trovare una combinazione adatta con i seguenti elementi: abbassare la
velocità di trasmissione, cambiare l'hardware di comunicazione e aumentare
la velocità della CPU. A volte i driver hardware di terze parti che non lavorano in maniera eciente con le risorse della CPU possono causare questo
errore. Il controllo del usso non può risolvere completamente i problemi che
causano l'errore CE_OVERRUN, ma potrebbe ridurre la frequenza con cui
si presenta.
8.7.2 Controllo del usso software
Il controllo del usso software usa i dati dello stream di comunicazione per
controllare la trasmissione e la ricezione dei dati. Dato che il controllo del
usso software utilizza due speciali caratteri, XOFF e XON, i trasferimenti
binari non possono usare il controllo del usso software; i caratteri XON e
XOFF potrebbero apparire tra i dati binari e potrebbero quindi interferire
con il trasferimento dei dati. Il controllo del usso software è adatto alle
comunicazioni a base di testo, oppure a trasferimenti di dati che non contengono i caratteri XON e XOFF. Per permettere il controllo del usso software,
i membri fOutX e fInX della DCB devono essere TRUE. Il membro fOutX
regola il controllo del usso output. Il membro fInX regola il controllo del
usso input. Da notare che la DCB permette al programma di assegnare
in maniera dinamica i valori che il sistema riconosce come caratteri per il
controllo del usso. Il membro XoChar della DCB detta il carattere XOFF
sia per il controllo del usso di input, che per quello di output. Il membro
XonChar della DCB fa lo stesso per quanto riguarda il carattere XON. Per
il controllo del usso di input, il membro XoLim della DCB specica la
quantità minima di spazio libero ammesso nel buer di input prima che il
carattere XOFF venga inviato. Se lo spazio disponibile nel buer di input
Programmazione seriale
28
scende sotto questa valore, il carattere XOFF viene inviato. Per il controllo
del usso di input, il membro XonLim della DCB specica il numero minimo
di byte ammessi nel buer di input prima che il carattere XON venga inviato. Se la quantità di dati nel buer di input scende sotto questa valore, il
carattere XON viene inviato. La tabella 4 specica il comportamento della
DTE quando viene usato il controllo del usso con i caratteri XOFF/XON.
Comportamento del controllo di usso software
XOFF ricevuto
dalla DTE
XON ricevuto
dalla DTE
XOFF inviato
dalla DTE
XON
inviato
dalla DTE
La trasmissione DTE viene sospesa nché il carattere XON viene ricevuto. La ricezione DTE continua. Il membro fOutX della DCB controlla il
comportamento.
Se la trasmissione è sospesa a causa della ricezione precedente di un carattere XOFF, la trasmissione DTE ricomincia.Il membro fOutX della DCB
controlla il comportamento.
Il carattere XOFF viene inviato automaticamente dalla DTE quando il buffer che riceve è quasi pieno. Il limite eettivo è dato dal membro XoLim
della DCB. Il membro fInX della DCB controlla questo comportamento. La
trasmissione DTE è controllata dal membro fTXContinueOnXo della DCB
nella maniera descritta sotto.
Il carattere XON viene inviato automaticamente dalla DTE quando il buer
che riceve è quasi vuoto. Il limite eettivo è dato dal membro XonLim della
DCB. Il membro fInX della DCB controlla questo comportamento.
Se il controllo del usso software è abilitato per il controllo input, allora il membro fTXContinueOnXo della DCB entra in azione. Il membro
fTXContinueOnXo controlla se la trasmissione viene interrotta dopo che il
carattere XOFF è inviato automaticamente dal sistema. Se fTXContinueOnXo è TRUE, allora la trasmissione continua dopo che XOFF viene inviato
quando il buer che riceve è pieno. Se fTXContinueOnXo è FALSE, la trasmissione viene sospesa nché il sistema invia automaticamente il carattere
XON. I dispositivi DCE che usano il controllo del usso software smetteranno di inviare dopo che il carattere XOFF viene ricevuto. Alcuni apparecchi
inizieranno di nuovo a inviare quando il carattere XON viene mandato dalla
DTE. Altri dispositivi DCE invece, inizieranno a trasmettere di nuovo dopo
che ricevono qualsiasi carattere. Il membro fTXContinueOnXo dovrebbe
essere impostato FALSE quando comunica con un dispositivo DCE che inizia
di nuovo a mandare dopo che ha ricevuto un carattere qualsiasi. Se la DTE
ha proseguito la trasmissione dopo che ha inviato automaticamente XOFF,
la ripresa della trasmissione porterà la DCE a continuare ad inviare, sovrastando il carattere XOFF. Non esiste un meccanismo disponibile in Win32
API che può portare la DTE a comportarsi nella stessa maniera di questi
dispositivi. La struttura DCB non contiene membri che specicano la ripresa
della trasmissione sospesa, quando viene ricevuto un carattere qualsiasi. Il
carattere XON è l'unico che può causare la ripresa della trasmissione. Un
altro aspetto interessante del controllo del usso software è che la ricezione
dei caratteri XON e XOFF induce le operazioni in sospeso a terminare con
Programmazione seriale
29
zero byte letti. I caratteri XON e XOFF non possono essere letti dall'applicazione, dato che non si trovano nel buer di input. Una moltitudine di
programmi disponibili, incluso il programma Terminal che viene distribuito
con Windows, danno all'utente tre scelte per il controllo del usso: Hardware, Software, oppure Nessuna. Il sistema operativo Windows, da solo, non
limita un'applicazione in questo modo. Le impostazioni della DCB permettono al controllo di usso Software e Hardware di agire contemporaneamente.
Infatti, è possibile congurare separatamente ciascun membro della DCB che
inuenza il controllo del usso, e quindi sono presenti alcune congurazioni
dierenti del controllo del usso. I limiti imposti sulle scelte del controllo
del usso hanno lo scopo di facilitare la comprensione per gli utenti. Questi limiti esistono anche perché alcuni dispositivi usati per la comunicazione
potrebbero non supportare tutti i tipi di controllo del usso.
8.8 Timeout delle comunicazioni
Un altro grande aspetto che può inuenzare le operazioni di lettura e scrittura è il Time-out. Questo inuisce sulle operazioni di lettura e scrittura
nel seguente modo. Se un'operazione richiede più tempo del time-out, allora l'operazione è completata. Non viene restituito alcun codice di errore
da ReadFile, WriteFile,GetOverlappedResult, o WaitForSingleObject. Tutti
gli indicatori che monitorano l'operazione indicano che è stata completata
con successo. L'unico modo di determinare se l'operazione é terminata con
time-out, è di controllare se il numero dei byte trasmessi è minore di quello
dei byte richiesti. Quindi, se ReadFile restituisce TRUE, ma sono stati letti
meno byte di quanti ne sono stati richiesti, allora l'operazione proviene da
un time-out. Se una funzione overlapped fa il time-out, l'evento overlapped viene segnalato e WaitForSingleObject restituisce WAIT_OBJECT_O.
GetOverlappedResult restituisce TRUE, ma dwBytesTransferred contiene il
numero di byte che sono stati trasferiti prima del time-out. Il codice seguente dimostra come gestire questa situazione in un'operazione di scrittura
overlapped:
BOOL WriteABuffer(char * lpBuf, DWORD dwToWrite)
{
OVERLAPPED osWrite = {0};
DWORD dwWritten;
DWORD dwRes;
BOOL fRes; // Create this write operation's OVERLAPPED structure hEvent.
osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osWrite.hEvent == NULL) // Error creating overlapped event handle.
return FALSE; // Issue write
if (!WriteFile(hComm, lpBuf, dwToWrite, &dwWritten, &osWrite))
Programmazione seriale
{
30
if (GetLastError() != ERROR_IO_PENDING)
{
// WriteFile failed, but it isn't delayed. Report error. fRes = FALSE;
} else // Write is pending.
dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);
switch(dwRes)
{
// Overlapped event has been signaled.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(hComm, &osWrite, &dwWritten, FALSE))
fRes = FALSE;
else
{
if (dwWritten != dwToWrite)
{
// The write operation timed out. I now need to
// decide if I want to abort or retry. If I retry,
// I need to send only the bytes that weren't sent.
// If I want to abort, I would just set fRes to
// FALSE and return.
fRes = FALSE;
}
else// Write operation completed successfully.
fRes = TRUE;
}
break;
default:
// An error has occurred in WaitForSingleObject. This usually
// indicates a problem with the overlapped event handle.
fRes = FALSE;
break;
}
}
}
else
{
// WriteFile completed immediately.
if (dwWritten != dwToWrite)
{
// The write operation timed out. I now need to
// decide if I want to abort or retry. If I retry,
// I need to send only the bytes that weren't sent.
// If I want to abort, then I would just set fRes to
Programmazione seriale
31
// FALSE and return.
fRes = FALSE;
}
else fRes = TRUE;
}
CloseHandle(osWrite.hEvent); return fRes;
}
La funzione SetCommTimeouts specica i time-out di comunicazione per
una porta. Per recuperare i time-out in vigore per una porta, il programma
chiama la funzione GetCommTimeouts. Un'applicazione dovrebbe recuperare i time-out di comunicazione prima di modicarli. Questo permette all'applicazione di impostare i time-out alle loro impostazioni originali quando
ha nito di utilizzare la porta. Il seguente esempio mostra come impostare
nuovi time-out usando SetCommTimeouts:
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout = 20;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.ReadTotalTimeoutConstant = 100;
timeouts.WriteTotalTimeoutMultiplier = 10;
timeouts.WriteTotalTimeoutConstant = 100;
if (!SetCommTimeouts(hComm, &timeouts))
// Error setting time-outs.
Impostando tutti i membri della struttura COMMTIMEOUTS a zero,
non avverrà alcun time-out. Le operazioni non overlapped saranno bloccate
nché tutti i byte richiesti non verranno trasferiti. La funzione ReadFile
viene bloccata nché tutti i caratteri richiesti non sono trasferiti. La funzione WriteFile viene bloccata no a quando tutti i caratteri richiesti non
sono inviati. D'altronde, un'operazione overlapped non nirà no a quando
tutti i caratteri non sono stati trasferiti, oppure nché non è stata abbandonata l'applicazione. Sono in vigore le seguenti condizioni no a quando
l'operazione non è stata completata:
6
• WaitForSingleObject restituisce sempre WAIT_TIMEOUT se viene
fornito un time-out di sincronizzazione. WaitForSingleObject bloccherà per sempre se viene usato un time-out di sincronizzazione INFINITE.
6
I time-out di comunicazione non sono i valori di time-out forniti in sincronizzazione.
Ad esempio, WaitForSingleObject usa un valore di time-out per aspettare che un oggetto
venga segnalato; questa non è la stessa cosa di un time-out di comunicazione.
Programmazione seriale
32
• GetOverlappedResult restituisce sempre FALSE e GetLastError resti-
tuisce ERROR_IO_INCOMPLETE se viene chiamata subito dopo la
chiamata a GetOverlappedResult
Impostare i membri della struttura COMMTIMEOUTS nella seguente maniera induce le operazioni di lettura a terminare immediatamente, senza
aspettare nuovi dati in arrivo:
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;
if (!SetCommTimeouts(hComm, &timeouts))
// Error setting time-outs.
Questa impostazioni sono necessarie in presenza di un evento di lettura
come quello descritto in precedenza. Per fare in modo che ReadFile restituisca 0 byte letti, il membro ReadIntervalTimeout della struttura COMMTIMEOUTS è impostato a MAXDWORD, e i membri ReadTimeoutMultiplier
e ReadTimeoutConstant sono entrambi impostati a zero. Un'applicazione
deve sempre impostare i time-out di comunicazione quando usa una porta
di comunicazione. Il comportamento delle operazioni di lettura e scrittura è
inuenzato dai time-out di comunicazione. Quando una porta viene aperta,
utilizza i time-out di default forniti dal driver, oppure i time-out lasciati da
una precedente applicazione di comunicazione. Se un'applicazione presume
che i time-out sono impostati in una certa maniera, mentre invece sono impostati dierentemente, le operazioni di lettura e scrittura potrebbero non
terminare mai, oppure potrebbero terminare troppo frequentemente.
8.9 Conclusione
Questo articolo serve per discutere di alcuni dei più comuni trabocchetti o
delle domande che si possono presentare quando viene sviluppata un'applicazione per la comunicazione seriale. L'esempio di Multithreaded TTY che
viene presentato con questo articolo è stato creato usando molte delle tecniche descritte qui. Scaricatelo e provatelo. Imparare come funziona porterà
a una conoscenza approfondita delle funzioni per la comunicazione seriale in
Win32.