Programmazione della porta seriale




In questa lezione vedremo come realizzare un semplice programma in grado di inviare/ricevere dati attraverso la porta seriale. 

Tutto ciò che occorre è:

 

Il collegamento Null Modem 

Nella figura è raffigurato lo schema del collegamento Null Modem usato in laboratorio

Nella tabella è riportato il collegamento dei pin  dei due connettori. 

  DTE 1 DTE 2  
Receive Data 2 3 Transmit Data
Transmit Data 3 2 Receive Data
DTR 4 6+1 DSR + CD
System Ground 5 5 System Ground
DSR + CD 6+1 4 DTR
RTS 7 8 CTS
CTS 8 7 RTS
Nota: DSR e CD sono jumperati per simulare un DCE sempre in linea

L'API Communication di Windows

L'API per le comunicazioni di Win32 è un una libreria di funzioni(incluse nella libreria run-time di Windows) che fa da interfaccia software fra il programma utente e il driver di un dispositivo I/O, come l'UART, il controller per la parallela, la scheda di rete. 

In figura è rappresentata l'architettura di comunicazione

La trasmissione dei dati avviene con la seguente modalità:

 

Analogamente la ricezione:

 

Il driver di Windows permette di effettuare in maniera relativamente semplice le operazioni tipiche della comunicazione:

 

Il controllo di flusso viene effettuato o da hardware utilizzando i segnali RTS/CTS o da software. Nella seconda modalità, si utilizza un protocollo chiamato Xon/Xoff, dal nome dei due caratteri ASCII usati:  Xon =17 e Xoff= 19.

Il protocollo opera nel seguente modo:

Sono previsti due tipi di timeout: quello che intercorre fra la ricezione di due caratteri e quello relativo alla lettura/scrittura di un blocco di n bytes.

 

Programmazione

  1. Apertura della porta
  2. Impostazione dei parametri della comunicazione
  3. Impostazione dei timeouts
  4. Impostazione degli eventi da monitorare
  5. Implementazione delle procedure di ricezione/invio dati
  6. Chiusura della porta

 

Apertura della porta

Si effettua chiamando la funzione CreateFile:

Createfile

HANDLE CreateFile
        LPCTRSTR  lpFileName ,
         DWORD  dwDesideredAccess,  
         DWORD dwShareMode,
         LPSECURTITY_ATTRIBUTES lpSecurityAttributes,
         DWORD ndwCreation,

         DWORD dwFlagsAndAttribute,
         HANDLE  hTemplateFile

)

 

L’attributo programma l’interfaccia nella modalità Overlapped I/O. 

 

Impostazione dei parametri della comunicazione

I parametri della comunicazione sono numerosi. Per ora ci interessano i seguenti:

Ad essi si accede tramite una struct chiamata DCB (Device Control Block).

DCB

typedef struct _DCB{

  DWORD BaudRate

  BYTE ByteSize

  BYTE Parity

  BYTE StopBits

  <altri campi>

} DCB

 

CBR_110 CBR_19200
CBR_300 CBR_38400
CBR_600 CBR_56000
CBR_1200 CBR_57600
CBR_2400 CBR_115200
CBR_4800 CBR_128000
CBR_9600 CBR_256000
CBR_14400

 

ONESTOPBIT 1  bit di stop
ONE5STOPBITS 1.5  bit di stop
TWOSTOPBITS 2 bit di stop

Per leggere la configurazione corrente della porta si usa GetCommState, per impostare una nuova configurazione  SetCommState. Entrambe le funzioni hanno gli stessi parametri

 

SetCommState

BOOL SetCommState(

  HANDLE hcom

  LPDCB lpDCB

)

 

Esempio di uso GetCommState/SetCommState

DBC dbc;

HANDLE hcom2;

hcom2=CreateFile(.........);

GetCommState(hcom2, &dbc);

  dbc.BaudRate = CBR_9600;

  dbc.ByteSize = 8;

  dbc.Parity = NOPARITY;

  dbc.StopBits = ONESTOPBIT;

  resultset=SetCommState(hcom2,&dbc);

 

Implementazione delle procedure di ricezione/invio dati

Per ricevere dati dalla porta seriale si usa la funzione ReadFile, per inviare dati si usa WriteFile.

Le due funzioni hanno gli stessi parametri.

ReadFile 

BOOL ReadFile(

   HANDLE hFile,

   LPVOID lpBuffer,

   DWORD NumerodiBytesDaLeggere,

  LPDWORD lpNumerodiBytesLetti,

  LPOVERLAPPED lpOverlapped

}  

 

Notare che il puntatore lpbuffer è di tipo void. Questo significa che si può passare qualunque tipo di dato. Per es. se si deve leggere una struct del tipo: 

struct prova{ 

    tipo1 campo1;

   tipo2 campo2;

}

si crea un puntatore prova *punt= new prova oppure una variabile prova pv; 

nel primo caso:

ReadFile(hcom2, (prova *)punt, sizeof(prova),&nletti,NULL)

nel secondo caso:

ReadFile(hcom2, (prova *)&pv, sizeof(prova),&nletti,NULL);

E’ importante tenere presente il comportamento di ReadFile (WriteFile). La funzione ritorna solo quando il numero di bytes da leggere (scrivere) richiesti è stato prelevato dal buffer (scritto nel buffer).

Un 'operazione di lettura infatti potrebbe tenere occupato il thread per un tempo lungo sei dati tardano ad arrivare nel buffer.

Un modo per risolvere il problema è usare la funzione ClearCommError. Questa funzione restituisce come parametro una variabile di tipo COMSTAT, una struct che contiene una serie di campi che danno informazioni sullo stato corrente della comunicazione, in particolare:

DWORD cbInQue      //bytes presenti nel buffer di input  

DWORD cbOutQue;       // bytes presenti nel buffer di output.

 

Esempio: lettura in polling di N bytes 

#define N = 10;

HANDLE hcom2;

  char BufferIn

//la funzione ritorna true se ci sono bytes da leggere nel   buffer

BOOL LeggiNBytes(DWORD *numletti)

  DWORDerrors;

  COMSTAT stc;

  BOOL result;

  ClearCommError(hcom2,&errors,&StatComm);

  if (errors>0){ 

   //gestione errori 

   return FALSE;

  }

  if (StatComm.cbInQue>0){ 

   ReadFile(hcom2,BufferIn,StatComm.cbInQue,numletti,NULL))

   return TRUE;

  }

  return FALSE;

}

void main(){

DWORD nletti=0;  //è obbligatorio inizializzare a 0

..............

..............

while (LeggiNBytes(&nletti)); 

    ...............

}

 

Impostazione dei timeouts

I timeout vengono impostati riempiendo la stuttura COMMTIMEOUTS con la funzione SetCommTimeouts.

La COMMTIMEOUTS è definita così:

COMMTIMEOUTS

typedef struct _COMMTIMEOUTS{  

  DWORD ReadIntervalTimeout;

  DWORD ReadTotalTimeoutMultiplier;

  DWORD ReadTotalTimeoutConstant;

  DWORD WriteTotalTimeoutMultiplier;

  DWORD WriteTotalTimeoutConstant;

} COMMTIMEOUTS,*LPCOMMTIMEOUTS;

 

Il campo RTTM (WTTM) è un moltiplicatore usato  per adattare il timeout totale al numero di caratteri. 

Generalmente si una questa formula: 

TimeoutTotale = (Moltiplicatore * numero_di bytes)+ COSTANTE

in cui Moltiplicatore o Costante possono essere posti a 0

Nella tabella seguente è riassunto il comportamento delle operazioni di scrittura o lettura rispetto ai timeouts

Totale

Intervallo

Modalità di ritorno di ReadFile o WriteFile

0 0  Quando il buffer è completamente riempito.  I Timeouts non sono usati
T 0 Quando il buffer è completamente riempito o sono trascorsi T ms   dall'inizio dell'operazione
0 Y Quando il buffer è completamente riempito o sono trascorsi Y ms fra la ricezione di due caratteri consecutivi.
T Y Quando il buffer è completamente riempito o uno dei timeouts è scaduto .

 

Per impostare i timeouts si chiama SetCommTimeouts

 

SetCommTimeouts

BOOL SetCommTimeouts(

  HANDLE hfile 

  LPCOMMTIMEOUTS lpCommTimeouts 

)