结合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;
}

  

 

posted @ 2013-12-12 18:51  鸭卤丝  阅读(1700)  评论(0)    收藏  举报