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.