Servizi seriali in win32: concetti base Come per altri servizi di accesso all’HW, i progettisti di NT hanno cercato di astrarre quanto più possibile l’accesso ai dispositivi e anzi di limitarne l’uso. Ricordiamo in questa sede che alcuni degli obiettivi di NT (e quindi del "figlio" Win32):
Aggiungiamo che, in simmetria con la filosofia Unix "tutto è un file", anche sotto NT si è teso a uniformare l’accesso alla seriale all’accesso ad un file. Ed infatti nelle API le chiamate, come vedremo nel seguito, sono le stesse, sia che si tratti di un file, di una socket, di una mailslot o delle porte seriali. Citiamo per completezza il fatto che esistono ancora alcune chiamate, in effetti a 16 Bit, che utilizzano funzione dedicate. L’SDK ufficiale le sconsiglia perché obsolete e inutili: in ambiente win32 sono rimappate alle funzione "file oriented". La logica di funzionamento può essere così riassunta:
E’ anche possibile verificare lo stato della porta e della linea con opportune chiamate. All’interno di Windows è stata definita una apposita struttura atta a descrivere i parametri della connessione, il DCB (Device Control Block), blocco di controllo del dispositivo. Lo riportiamo per intero segnalando solo i campi "interessanti". Rimandiamo alla bibliografia [3] per i dettagli. typedef struct _DCB { DWORD DCBlength; DWORD BaudRate; DWORD fBinary :1; DWORD fParity :1; DWORD fOutxCtsFlow :1; DWORD fOutxDsrFlow :1; DWORD fDtrControl :2; DWORD fDsrSensitivity :1; DWORD fTXContinueOnXoff :1; DWORD fOutX :1; DWORD fInX :1; DWORD fErrorChar :1; DWORD fNull :1; DWORD fRtsControl :2; DWORD fAbortOnError :1; DWORD fDummy2 :17; WORD wReserved; WORD XonLim; WORD XoffLim; BYTE ByteSize; BYTE Parity; BYTE StopBits; char XonChar; char XoffChar; char ErrorChar; char EofChar; char EvtChar; WORD wReserved1; } DCB Segnaliamo: BaudRate la frequenza di trasmissione ByteSize numero di bit: 5,6,7 o 8 bit prima del fronte di salita del bit di stop Parity parità: pari, dispari, nessuna, mark o space. StopBits numero di bit di stop Esistono delle opportune define per tutti i possibili valori dei parametri. Ne riportiamo solo alcuni. #define NOPARITY 0 #define ODDPARITY 1 #define EVENPARITY 2 #define MARKPARITY 3 #define SPACEPARITY 4 #define ONESTOPBIT 0 #define ONE5STOPBITS 1 #define TWOSTOPBITS 2 Lo stesso dicasi per le velocità. Una variabile di tipo DCB può essere settata e passata ad una API per settare i parametri, ma è anche possibile passare un DCB ad una funzione di "stato" che provvederà a riempirla con le impostazioni correnti. Consigliamo fin d’ora di procedere in questo modo:
in tal modo non siamo obbligati a settare ogni campo del DCB.
API SERIALI win32 Analizziamo le principali funzioni nell’ordine sopra descritto. Apertura E’ sufficiente utilizzare:
dove: lpFileName è il nome della porta, pes. "COM1". DwDesiredAccess è il tipo di accesso: poiché la seriale è bidirezionale, vale: GENERIC_READ | GENERIC_WRITE dwShareMode è messo a 0. Ha senso solo per i file che possono essere sharati in R/W. lpSecurityAttributes è messo a NULL, non utilizzeremo attributi di sicurezza dwCreationDisposition specifica come creare il file. In questo caso: OPEN_EXISTING dwFlagsAndAttributes per i file può essere hidden, di sistema etc. Qui semplicemente 0. hTemplateFile NULL. Non usiamo template. La funzione, come usuale in win32, restitusce un Handle. NON testate se l’Handle ritornato vale NULL: va confrontato con INVALID_HANDLE_VALUE.
Settaggio parametri di comunicazione (se necessario) Come detto, svuotiamo un DCB e prima lo passiamo ad una funzione di "stato": BOOL GetCommState( HANDLE hFile, // handle to communications device LPDCB lpDCB // pointer to device-control block structure ); dopo aver ottenuto lo stato corrente, nella variabile di tipo DCB passata per indirizzo, la modifichiamo e la passiamo a: BOOL SetCommState( HANDLE hFile, // handle to communications device LPDCB lpDCB // pointer to device-control block structure );
(Si veda l’esempio di codice più avanti) Lettura / Scrittura E’ sufficiente utilizzare: BOOL WriteFile( HANDLE hFile, // handle to file to write to LPCVOID lpBuffer, // pointer to data to write to file DWORD nNumberOfBytesToWrite, // number of bytes to write LPDWORD lpNumberOfBytesWritten, // pointer to number of //bytes written LPOVERLAPPED lpOverlapped // pointer to structure for // overlapped I/O ); BOOL ReadFile( HANDLE hFile, // handle of file to read LPVOID lpBuffer, // pointer to buffer that receives data DWORD nNumberOfBytesToRead, // number of bytes to read LPDWORD lpNumberOfBytesRead, // pointer to number of bytes read LPOVERLAPPED lpOverlapped // pointer to structure for data ); Essendo le stesse funzioni usate per i file, non ci dilunghiamo a commentare i parametri [4]. Ricordiamo però che sia ReadFile che WriteFile sono bloccanti, ossia non ritornano "su" dalla syscall se non hanno rispettivamente ricevuto o inviato il numero di caratteri specificato. Ciò significa che il nostro programma sarà in wait se non ci sono caratteri da ricevere, ma anche che non risponderà all’input utente finché non avrà inviato lungo il canale fisico tutti i byte specifcati da nNumberOfBytesToWrite. Potrebbe essere quindi necessario threadare tali attività per non aver un programma né in polling (spreco di CPU) per ricevere qualche dato, né in blocco su una scrittura lenta, o viceversa. Inoltre se vogliamo sia leggere che scrivere contemporaneamente, dobbiamo inventarci un meccanismo di alternanza fra gestione dell’input utente, scrittura e lettura. In breve uno specie di scheduler che interrompa le 3 operazioni descritte. Non lo faremo: nel nostro breve esempio possiamo tollerare che le operazioni siano bloccanti. Le alternative sono due:
Chiusura Banalmente come per ogni risorsa di Windows: BOOL CloseHandle(HANDLE hObject); NB: non usate fclose! Non è un file!
Logica dell’esempio Vogliamo realizzare un breve programma, per semplicità Console, che:
Il comando ATI3 è un "classico" come tutti comandi modem comincia con AT, ossia ATTENTION, e va chiuso con un carattere di invio, come per tutti i comandi. L’esempio di codice Cominciamo a scrivere un programma Console stile "Hello Word" , e sia MySerial. (tralasciamo i passi necessari; li abbiamo già visti in altri articoli) Cominciamo ad aprire la connessione alla seriale: #include "stdafx.h" #include "windows.h" #include "conio.h" int main(int argc, char* argv[]) { HANDLE myPortH= CreateFile( "COM1", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0); if (myPortH == INVALID_HANDLE_VALUE) { printf("error opening\n"); // error opening port; abort return -1; } printf("COMM Open\n\n"); getch(); CloseHandle(myPortH); return 0; } Provate ad eseguirlo: a meno di PC senza seriale, dovrebbe partire. Lo strano #include "windows.h" è necessario perché #include "stdafx.h" non include tutti i tipi delle API. Leggiamo i parametri correnti: DCB myDCB; Notiamo che per parità e bit di stop, otteniamo le define di Windows. Per una visualizzazione completa, dovremmo creare degli switch. Facciamolo per i Bit di Stop. switch(myDCB.StopBits) { case ONESTOPBIT: printf("ONESTOPBIT\n"); break; case ONE5STOPBITS: printf("ONE5STOPBITS\n"); break; case TWOSTOPBITS: printf("TWOSTOPBITS\n"); break; } Settiamo ora la velocità a 9600, con opportuna define: myDCB.BaudRate = CBR_9600; Ok = SetCommState(myPortH, // handle to communications device &myDCB // pointer to device-control block structure ); Aggiungiamo la scrittura della stringa: char msg[] = "ATI3"; DWORD NumberOfBytesWritten=0; DWORD nNumberOfBytesToWrite = strlen(msg); Ok = WriteFile( myPortH,// handle to file to write to msg,// pointer to data to write nNumberOfBytesToWrite,// number of bytes to write &NumberOfBytesWritten, // ptr.to n.bytes written NULL// pointer to structure for ); printf("Written %d bytes \n", NumberOfBytesWritten); Il modem dovrebbe lampeggiare. Poiché le stringhe di comando vanno chiuse da un CR (ASCII 13), duplichiamo il codice e modifichiamo: char CR[] = {13, 0}; NumberOfBytesWritten=0; nNumberOfBytesToWrite = strlen(CR); Ok = WriteFile( myPortH, CR, nNumberOfBytesToWrite, &NumberOfBytesWritten, NULL ); Ora mettiamoci in polling e leggiamo. Interromperemo il ciclo con un tasto. Come tutti i veri programmatori "C" usiamo l’unico ciclo "vero", il for e leggiamo char by char: printf("\nreading...\n\n\n"); for(;;) { char c; DWORD NumberOfBytesRead; Ok = ReadFile(myPortH, &c, // pointer to buffer that receives data 1, // number of bytes to read &NumberOfBytesRead, // ptr. to n. of bytes read NULL ); if (NumberOfBytesRead > 0) printf("%c", c); int keydown = _kbhit(); if (keydown) break; } Notiamo che, come detto, la routine è bloccante: riuscite a interrompere con un tasto SOLO se arriva "qualcosa" da modem che fa ritornare al programma la ReadFile. La cosa era prevista.
Dialing on modem Vediamo ora se si riesce a far comporre i numeri al modem. La logica è la seguente:
Poiché le funzioni di invio sono simili e va sempre inviato l’a capo, scriviamo una funzione che invii una stringa completa di a capo. void write(HANDLE myPortH, char * msg) { DWORD NumberOfBytesWritten=0; DWORD nNumberOfBytesToWrite = strlen(msg); BOOL Ok; if(nNumberOfBytesToWrite) { Ok = WriteFile( myPortH,// handle to file to write to msg,// pointer to data to write to file nNumberOfBytesToWrite,// n. bytes to write &NumberOfBytesWritten,//ptr n.bytes written NULL ); printf("Written %s\n%d bytes\n\n", msg, numberOfBytesWritten); } char CR[] ={13, 10, 0}; NumberOfBytesWritten=0; nNumberOfBytesToWrite = strlen(CR); Ok = WriteFile( myPortH,// handle to file to write to &CR,// pointer to data to write to file nNumberOfBytesToWrite,//n.of bytes to write &NumberOfBytesWritten, NULL ); printf("CR %d bytes \n", NumberOfBytesWritten); } la relativa chiamata sarà: write(hComm, "ATDP12345678"); Sarà ora sufficiente inserire la chiamata alla funzione nel main. Tralasciamo la classica attività di editing: il lettore è un duro programmatore C…
IL LISTATO E diamo una visione d’insieme del listato, soprattutto per permettere un agile Copy&Paste. Dopo alcuni piccoli aggiustamenti soprattutto alla visibilità delle variabili, eccolo: // My_serial.cpp : Defines the entry point for the console application. // Conclusioni Sebbene la seriale sia sempre meno usata, è ancora fondamentale per connessioni a server, ad apparecchiature di rete (switch e router), ad appareccchiature industriali, a centralini e cosi’ via. Addirittura su server di Apple è tornata dopo 10 anni….
|