1. 前言,最近接到一個很無語的需求,要求在一台電腦中,同時操作5個序列槽,本以為很簡單,那知道,中間遇到一些坑。在平時,通過Win api 操作序列槽都是操作一個序列槽的,而且習慣使用異步方式操作,以為多線程,同時這樣子操作,使用異步方式也會很友善,測試最終發現,還是會出現問題。最終使用同步方式操作,發現問題解決了。
序列槽通信一般分為四大步:打開序列槽->配置序列槽->讀寫序列槽->關閉序列槽,還可以在序列槽上監聽讀寫等事件。
1.打開序列槽,配置序列槽
//序列槽初始化函數
int UartInit(HANDLE *pUartHandle, int port, int baud)
{
char *szPort = NULL;
char szPortName[255];
HANDLE idComDev = INVALID_HANDLE_VALUE;
DWORD err, readdelay;
DCB dcb;
COMMTIMEOUTS CommTimeOuts;
if (port<0)
return DEVICE_ERROR_INVALID_DATA;
if(port == 100)
return DEVICE_ERROR_INVALID_DATA; //USB接口
switch(port)
{
case 0: szPort="COM1"; break;
case 1: szPort="COM2"; break;
case 2: szPort="COM3"; break;
case 3: szPort="COM4"; break;
case 4: szPort="COM5"; break;
case 5: szPort="COM6"; break;
case 6: szPort="COM7"; break;
case 7: szPort="COM8"; break;
case 8: szPort="COM9"; break;
default:
memset(szPortName, 0, sizeof(szPortName));
sprintf(szPortName, "\\\\.\\COM%d", port + 1);
szPort = szPortName;
break;
}
//打開序列槽檔案,同步方式操作
idComDev=
CreateFile( szPort,
GENERIC_READ|GENERIC_WRITE, //have right to read and write.
0, //exclusive access;
NULL, //no security attrs;
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, //FILE_FLAG_OVERLAPPED, // FILE_ATTRIBUTE_NORMAL ,not over lappped i/o;
NULL );
if (idComDev == INVALID_HANDLE_VALUE)
{
if ((err = GetLastError()) == 5) //ERROR_ALREADY_EXISTS
return DEVICE_ERROR_INVALID_HANDLE;
}
//設定序列槽緩存
SetupComm(idComDev,2124,2124);
//設定同步讀寫從逾時時間,1個位元組越50ms
CommTimeOuts.ReadIntervalTimeout = 100;
CommTimeOuts.ReadTotalTimeoutMultiplier = 25;
CommTimeOuts.ReadTotalTimeoutConstant = 25;
CommTimeOuts.WriteTotalTimeoutMultiplier = 200;
CommTimeOuts.WriteTotalTimeoutConstant = 200;
SetCommTimeouts(idComDev, &CommTimeOuts);
err = GetCommState(idComDev, &dcb);
if (!err)
{
CloseHandle(idComDev);
return DEVICE_ERROR_INVALID_DATA;
}
switch(baud)
{
case 1200: break;
case 9600: break;
case 14400: break;
case 19200: break;
case 28800: break;
case 38400: break;
case 57600: break;
case 115200: break;
default:
baud = 9600; break;
}
dcb.BaudRate = baud;
dcb.ByteSize = 8;
dcb.Parity = 0;
dcb.StopBits = ONESTOPBIT;
dcb.fOutX = 0;
dcb.fInX = 0;
dcb.fBinary = 1;
//設定序列槽通訊波特率
err = SetCommState(idComDev, &dcb);
if (!err)
{
CloseHandle(idComDev);
return DEVICE_ERROR_INVALID_DATA;
}
err = SetCommMask(idComDev, EV_TXEMPTY);
if (!err)
{
CloseHandle(idComDev);
return DEVICE_ERROR_INVALID_DATA;
}
//清空讀寫緩存
PurgeComm(idComDev, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR );
pUartHandle[0] = idComDev;
return DEVICE_ERROR_OK;
}
上面最主要的時,
①設定逾時
在調用ReadFile()和WriteFile()讀寫序列槽的時候,如果沒有指定異步操作的話,讀寫都會一直等待指定大小的資料,這時候我們可能想要設定一個讀寫的逾時時間。調用SetCommTimeouts()可以設定序列槽讀寫逾時時間,GetCommTimeouts()可以獲得目前的逾時設定,一般先利用GetCommTimeouts獲得目前逾時資訊到一個COMMTIMEOUTS結構,然後對這個結構自定義,再調用SetCommTimeouts()進行設定。
COMMTIMEOUTS結構如下:
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout;
DWORD ReadTotalTimeoutMultiplier;
DWORD ReadTotalTimeoutConstant;
DWORD WriteTotalTimeoutMultiplier;
DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
ReadIntervalTimeout為讀操作時兩個字元間的間隔逾時,如果兩個字元之間的間隔超過本限制則讀操作立即傳回。
ReadTotalTimeoutMultiplier為讀操作在讀取每個字元時的逾時。
ReadTotalTimeoutConstant為讀操作的固定逾時。
WriteTotalTimeoutMultiplier為寫操作在寫每個字元時的逾時。
WriteTotalTimeoutConstant為寫操作的固定逾時。
以上各個成員設為0表示未設定對應逾時。
逾時設定有兩種:間隔逾時和總逾時,間隔逾時就是ReadIntervalTimeout,總逾時= ReadTotalTimeoutConstant + ReadTotalTimeoutMultiplier*要讀寫的字元數。
可以看出:間隔逾時和總逾時的設定是不相關的,寫操作隻支援總逾時,而讀操作兩種逾時均支援。
比如:ReadTotalTimeoutMultiplier設為1000,其餘成員為0,如果ReadFile()想要讀取5個字元,則總的逾時時間為1*5=5秒;
ReadTotalTimeoutConstant設為5000,其餘為0,則總的逾時時間為5秒;
ReadTotalTimeoutMultiplier設為1000并且ReadTotalTimeoutConstant設為5000,其餘為0,如果ReadFile()想要讀取5個字元,則總的逾時間為1*5+5 =10秒。
如果将ReadIntervalTimeout設為MAXDWORD,ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant都為0,則讀操作會一次讀入緩沖區的内容後立即傳回,不管是否讀入了指定字元。
需要注意的是,用重疊方式讀寫序列槽時,SetCommTimeouts()仍然是起作用的,在這種情況下,逾時規定的是I/O操作的完成時間,而不是ReadFile和WriteFile的傳回時間。
參考《使用Windows API進行序列槽程式設計》
2. 位元組流方式讀序列槽,位元組流操作序列槽,就是每次讀1個位元組
//序列槽接收函數
int UartReceiveData(HANDLE uartHandle, int port, UCHAR *recData, int *recLength, DWORD maxLen, int timeout)
{
if(port < 0 || port > 128)
return DEVICE_ERROR_INVALID_DATA;
if(uartHandle == INVALID_HANDLE_VALUE)
return DEVICE_ERROR_NO_CONNECT;
DWORD dwread = 1;
unsigned char buffer[2048] = {0};
DWORD dwErrorFlags = 0;
COMSTAT ComStat;
memset(&osRead[port], 0, sizeof(OVERLAPPED));
osRead[port].hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
ClearCommError(uartHandle, &dwErrorFlags, &ComStat);
BOOL bReadStatus;
DWORD start;
int count = 0, offset = 0;
start = GetTickCount();
while(TRUE)
{
dwread = 1;
bReadStatus = ReadFile(uartHandle, buffer + offset, dwread, &dwread, &osRead[port]);
if(dwread == 1)
{
//有資料,儲存
count = 0;
offset++;
}
//50 ms 一次
count++;
if(count == timeout)
break;
}
int end = GetTickCount() - start;
if(!bReadStatus)
{
if(GetLastError() == ERROR_IO_PENDING)
{
DWORD waitReturn = WaitForSingleObject(osRead[port].hEvent, timeout);
if(waitReturn == WAIT_OBJECT_0)
{
if(maxLen < osRead[port].InternalHigh)
return DEVICE_ERROR_OUT_OF_SIZE;
memcpy(recData, buffer, osRead[port].InternalHigh);
recLength[0] = osRead[port].InternalHigh;
return DEVICE_ERROR_OK;
}
else
{
return DEVICE_ERROR_TIMEOUT;
}
}
}
if(maxLen < dwread)
return DEVICE_ERROR_OUT_OF_SIZE;
memcpy(recData, buffer, offset);
recLength[0] = offset;
return DEVICE_ERROR_OK;
}
3. 寫序列槽
//序列槽發送函數
int UartSendData(HANDLE uartHandle, int port, UCHAR *data, DWORD len)
{
if(port < 0 || port > 128)
return DEVICE_ERROR_INVALID_DATA;
if(uartHandle == INVALID_HANDLE_VALUE)
return DEVICE_ERROR_NO_CONNECT;
DWORD dwErrorFlag = 0;
COMSTAT comStat;
DWORD dwwrite = 0;
ClearCommError(uartHandle, &dwErrorFlag, &comStat);
PurgeComm(uartHandle, PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
memset(&osWrite[port], 0, sizeof(OVERLAPPED));
osWrite[port].hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
BOOL bWriteStat = WriteFile(uartHandle, data, len, &dwwrite, &osWrite[port]);
if(!bWriteStat)
{
if(GetLastError() == ERROR_IO_PENDING)
{
DWORD waitReturn = WaitForSingleObject(osWrite[port].hEvent, UART_TIME_OUT);
if(waitReturn == WAIT_OBJECT_0)
return DEVICE_ERROR_OK;
else
return DEVICE_ERROR_TIMEOUT;
}
}
return DEVICE_ERROR_OK;
}