结合CSerialPort类,实现完整的串口收发功能
最近的的项目软件,需要增加串口的收发,因需要统一到同一个通讯通讯框架,(之前已有USB,读卡器等)从API上看,我最希望其能简化成如下的简洁形式:
bool UartRcvData(LPCBYTE data,DWORD len)
bool UartSendData(LPCBYTE data,DWORD len)
为了达到次目的,吃苦不少。一开始采用了MSCOMM的控件,该控件将win32的串口功能封装的太厉害,我完全不了解串口收发的过程,只能靠一些神奇的sleep,去完成数据收发事件的触发,可移植性、操作性都非常差。而且有的电脑还需注册该控件,麻烦的很~就像很多人反应的,它其实不是很好用。
于是开始研究串口的的win32 API的直接实现。并发现了一个不错的封装类CSerialPort:
http://www.naughter.com/serialport.html
把这个看懂,基本上WIN32 串口的API怎么使用,也就没问题了。
我的实现几个关键点:
1.采用I/O交叠方式而不是阻塞;
2.一开始用设立 EV_RXCHAR事件,作为接收的触发;
3.如何避免error 87的错误。该错误是在I/O交叠方式下,WaitCommEvent会立即返回,若GetLastError返回错误不是IO_PENDING,则下一次再执行WaitCommEvent时,将返回error 87L。在这里,我参考了这位童鞋的方法:
http://blog.csdn.net/nwpu053883/article/details/6643306
4.WaitForSingleObject时,用超时等待方式,而不是INFINITE,等待串口接收事件的受信态。这样再串口数据发送方过快(如BPR 115200)时,很有可能阻塞住,事件永远不会受信;
5.最关键的,即便采用超时等待,程序依然可能不会按想象的那样先让事件受信,然后再去读。我猜测,在WaitForSingleObject返回之前(也就是把事件从已受信重置为未受信前),新的数据又进了缓冲区,但I/O端不会再引起事件受信,因此,下一次WaitForSingleObject必然超时,需要在WAIT_TIMEOUT的case中来接收已有缓冲区的数据。这样才不会遗漏任何数据,很关键。
具体的实现代码如下,细节和难点都在注释中:
bool UartRcvData(LPCBYTE data,DWORD len)
{
DWORD dwMask,dwError;
DWORD dwReaded,dwReceived,dwlen;
BOOL bWaitPending = FALSE;
BOOL state = FALSE;
COMSTAT ComStat;
//overlapp way
OVERLAPPED osCOMM = {0};
OVERLAPPED osRead = {0};
osCOMM.hEvent = ::CreateEvent(NULL,TRUE,FALSE,NULL);
ASSERT(osCOMM.hEvent);
osRead.hEvent = ::CreateEvent(NULL,TRUE,FALSE,NULL);
ASSERT(osRead.hEvent);
dwlen = 0;
dwReaded = 0;
dwReceived = 0;
m_SerialPort.GetMask(dwMask);
dwMask |= EV_RXCHAR;
m_SerialPort.SetMask(dwMask);
do
{
dwReceived = 0;
//I/O交叠方式,waitcommevent立即返回,若lasterror不是IO_PENDING,再次调用时将报错error 87
//因此加上bwaitpending变量
if(!bWaitPending)
{
TRACE("wait EV_RXCHAR event\n");
state = WaitCommEvent(HANDLE(m_SerialPort), &dwMask, &osCOMM);
if (!state)
{
DWORD dwError = GetLastError();
if (dwError == ERROR_IO_PENDING)
{
TRACE("wait event,I/O overlapping\r\n");
//m_SerialPort.GetOverlappedResult(osCOMM, dwReaded, TRUE);
bWaitPending = TRUE ;
//learCommError(HANDLE(m_SerialPort),&dwError,&ComStat);
}
else
{
TRACE(_T("WaitEvent Error %d\n"), dwError);
}
}
}
//若处于I/O处理状态,则等待事件受信
if (bWaitPending)
{
//这里采用超时500ms,而不是INFINITE,后者会直接阻塞
state = WaitForSingleObject(osCOMM.hEvent,500);
switch(state)
{
//若事件osCOMM的event处于已受信,说明缓冲区有数据了,则准备去缓冲区读数据
case WAIT_OBJECT_0:
ClearCommError(HANDLE(m_SerialPort),&dwError,&ComStat);
//即读COMSTAT的cbInQue;
dwReceived = m_SerialPort.BytesWaiting();
//有可能出现COMSTAT的cbInQue的长度为0。
if (dwReceived == 0)
{
TRACE("nothing in Inbuffer!\r\n");
continue;
}
TRACE("%d bytes in buffer now\r\n",dwReceived);
//读数据
try
{
m_SerialPort.Read((BYTE*)data + dwlen, dwReceived, osRead,&dwReaded);
}
catch(CSerialException* pEx)
{
//等待读动作完毕
if (pEx->m_dwError == ERROR_IO_PENDING)
{
m_SerialPort.GetOverlappedResult(osRead, dwReaded, TRUE);
//WaitForSingleObject(overlapped.hEvent,INFINITE);
pEx->Delete();
}
else
{
DWORD dwError = pEx->m_dwError;
pEx->Delete();
CSerialPort::ThrowSerialException(dwError);
}
}
ASSERT(dwReaded == dwReceived);
dwlen += dwReceived;
TRACE(" total %d data readed\n",dwlen);
bWaitPending = FALSE;
break;
case WAIT_TIMEOUT:
//难点在这里:有可能新的数据又来了,
//但这一次不会激活下一次循环的WaitForSingleObject函数,因此需要在这查看
//COMSTAT的cbInQue,看是否有数据已读要缓冲区。
TRACE("waitsingleobject time out,but data maystill in buffer\n");
dwReceived = m_SerialPort.BytesWaiting();
//如果有数据,读出来
if (dwReceived)
{
try
{
m_SerialPort.Read((BYTE*)data + dwlen, dwReceived, osRead,&dwReaded);
}
catch(CSerialException* pEx)
{
if (pEx->m_dwError == ERROR_IO_PENDING)
{
m_SerialPort.GetOverlappedResult(osRead, dwReaded, TRUE);
//WaitForSingleObject(overlapped.hEvent,INFINITE);
pEx->Delete();
}
else
{
DWORD dwError = pEx->m_dwError;
pEx->Delete();
CSerialPort::ThrowSerialException(dwError);
}
}
ASSERT(dwReaded == dwReceived);
dwlen += dwReceived;
TRACE(" total %d data readed\n",dwlen);
}
break;
default:
//do nothing!
break;
}
}
} while (dwlen != len);
m_SerialPort.ClearReadBuffer();
m_SerialPort.ClearWriteBuffer();
::CloseHandle(osRead.hEvent);
::CloseHandle(osCOMM.hEvent);
TRACE("read done\r\n");
return 0;
}
bool UartSendData(LPCBYTE data,DWORD len)
{
DWORD dwWrited = 0;
OVERLAPPED osWrite = {0};
osWrite.hEvent = ::CreateEvent(NULL,TRUE,FALSE,NULL);
ASSERT(osWrite.hEvent);
try
{
m_SerialPort.Write(data, len, osWrite,&dwWrited);
}
catch(CSerialException* pEx)
{
if (pEx->m_dwError == ERROR_IO_PENDING)
{
m_SerialPort.GetOverlappedResult(osWrite, dwWrited, TRUE);
pEx->Delete();
}
else
{
DWORD dwError = pEx->m_dwError;
pEx->Delete();
CSerialPort::ThrowSerialException(dwError);
}
}
ASSERT(dwWrited == len);
TRACE("%d data sended\r\n",dwWrited);
::CloseHandle(osWrite.hEvent);
return 0;
}
浙公网安备 33010602011771号