多线程,多接收模式串口类LsComm
作者:Liu_sir
描述:一个串口通讯类
应用平台:Windows
版本: v1.0
主要功能:设计了一个简洁易用的多线程串行通讯接口,可以切换查询和自动接收模式,进行对串口数据收发的类
接触VC,很不习惯mscomm等Active控件老让人去注册的方式,所以参照Delphi中的SpComm设计了一个类CComPort,对PJ
Naughter 的CSerialPort(http://www.vckbase.com/document/viewdoc.asp?id=612)进行了2次封装,主要目的是简化串口的使用.使其用简单的代码就可以完成串口通讯的过程.做了一个Demo程序演示了CComPort的使用,附图如下:

下面我从如何使用和类的设计两个方面说明一下:

考虑到使用过程尽可能简洁,实用,为了满足不同的使用要求设计4种接收模式, 前两种为手动接收方式,后两种为自动类回调方式,下面是使用代码
1.ManualReceiveByQuery 手动查询接收
#include "ComPort.h"
LsComm::CComPort m_ComPort; //LsComm is namespace in c++
m_ComPort.Open(2,LsComm::CComPort::AutoReceiveByquery);
//ReCeive Com Data: 接收语句
DWORD InBufferCount;
byte pbuffer[2048];
InBufferCount = m_ComPort.GetInBufferCount();
if(InBufferCount0)
{
m_ComPort.GetInput(pbuffer,InBufferCount);
}
//Write Com Data: 写串口数据
char a[10]="abcdefg";
this-m_ComPort.Output(a,sizeof(a));
2.ManualReceiveByConst(异步模式) 手动定常数接收模式
#include "ComPort.h"
LsComm::CComPort m_ComPort;//LsComm is namespace in c++
m_ComPort.Open(2,LsComm::CComPort::AutoReceiveByConst);
//ReCeive Com Data: //接收数据
DWORD InBufferCount=0;
byte pbuffer[2048];
InBufferCount=this-m_ComPort.GetInput(pbuffer,10,1000);
//上面我要在1000毫秒内接收10字节的数据,IbufferCount返回实际得到的数据
if(InBufferCount==0)
return;
CString c;
char a[4];
for(int i=0;i<(int)InBufferCount;i++)
{
::sprintf(a,"%2.2X",pbuffer[i]);
c+=a;
c+=" ";
}
c="接收(Receive):"+c;
写串口数据的过程同上
注意:第3,4种模式为自动接收模式,在用以前你必须先定义回调函数,然后,设置类的接收函数
/* 回调函数定义 */
void OnReceiveData(LPVOID pSender,void* pBuf,DWORD InBufferCount)
{
CString c;
byte a[100];
char b[4]="";
memcpy(a,pBuf,InBufferCount);
CLsCommDemoDlg* pDlg = (CLsCommDemoDlg*) pSender;
CListBox*pList =(CListBox*)pDlg-GetDlgItem(IDC_LIST1);
for(int i=0;i<(int)InBufferCount;i++)
{
::sprintf(b,"%2.2X",a[i]);
c+=" ";
c+=b;
}
c="接收(Receive):"+c;
pList-AddString(c);
}
3.AutoReceiveBySignal 自动信号接收模式
#include "ComPort.h"
LsComm::CComPort m_ComPort;//LsComm is namespace in c++
m_ComPort.Open(2,LsComm::CComPort::AutoReceiveBySignal );
m_ComPort.SetReceiveFunc((FOnReceiveData)OnReceiveData,this);
m_ComPort.SetBreakHandleFunc(OnComBreak);
//ReCeive Com Data:接收数据函数
in OnReceiveData(LPVOID pSender,void* pBuf,DWORD InBufferCount) //above
//write data
the same as the first mode;
4.AutoReceiveByBreak 中断接收模式#include "ComPort.h"
LsComm::CComPort m_ComPort;//LsComm is namespace in c++
m_ComPort.Open(2,LsComm::CComPort::AutoReceiveByBreak );
m_ComPort.SetReceiveFunc((FOnReceiveData)OnReceiveData,this);
//ReCeive Com Data:接收数据函数
in OnReceiveData(LPVOID pSender,void* pBuf,DWORD InBufferCount) //above
//write data
the same as the first mode;
另外说明2点:
(1)如果你需要处理中断事件,你可以在每种模式中设置中断接收事件:如下
//定义中断事件接收函数
void OnComBreak(LPVOID pSender,DWORD dwMask,COMSTAT stat)
{
//deal with the break of com here
}
m_ComPort.SetBreakHandleFunc(OnComBreak); //设置中断事件
(2)如何处理如,改变波特率,以及其它参数呢?
m_ComPort.GetSerialPort()可以获得一个CSerialPort类的指针,如何就可以操作各种com属性了.
DCB dcb;
this-m_ComPort.GetSerialPort()-GetState(dcb);

1. 类结构
为了说明一个大概的类构成,我用Rose画了一下类图:如下

LsComm::CComPort m_ComPort;//LsComm is namespace in c++
m_ComPort.Open(2,LsComm::CComPort::AutoReceiveBySignal );
m_ComPort.SetReceiveFunc(OnReceiveData,this);
m_ComPort.SetBreakHandleFunc(OnComBreak);
这些语句是怎么实现串口数据的发送和读取的呢?
2. 打开过程CComPort::Open()
void CComPort::Open(int nPort,ReceiveMode mode, DWORD dwBaud, Parity parity, BYTE DataBits,
StopBits stopbits,FlowControl fc)
{
//1.新建串口
this-m_pPort = new CSerialPort();
//2.判断收发模式
if(mode==ReceiveMode::ManualReceiveByQuery)
{
this-m_IsOverlapped = false;
}
else
{
this-m_IsOverlapped = true;
}
this-m_RecvMode = mode;
//3.转换参数,打开串口
int index;
index=parity-CComPort::EvenParity;
CSerialPort::Parity spParity=(CSerialPort::Parity)(CSerialPort::EvenParity+index);
…略去
this-m_pPort-Open(nPort,dwBaud,spParity,DataBits,spStopbits,spFC,m_IsOverlapped);
this-m_pPort-Setup(4096,4096);
this-m_pPort-Purge(PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
//it is important!!
COMMTIMEOUTS timeouts;
this-m_pPort-GetTimeouts(timeouts);
timeouts.ReadIntervalTimeout=100;
this-m_pPort-SetTimeouts(timeouts);
this-m_CurPortNum = nPort;
//创建关闭事件
this-m_hCloseEvent = CreateEvent(NULL,true,false,NULL);
ASSERT(this-m_hCloseEvent);
//4.创建线程类
this-m_pReadThread = new CReadComThread();
this-m_pReadThread-BandSerialPort(this);
this-m_pReadThread-Create();
this-m_pReadThread-Resume();
if(this-IsOverlapped())
{
this-m_hWriteEvent = ::CreateEvent(NULL,false,false,NULL);
}
}
主要做的工作是:
新建串口 this-m_pPort = new CSerialPort();
打开串口 this-m_pPort-Open
创建读取线程 this-m_pReadThread = new CReadComThread();
设立线程类与CComPort的关联关系this-m_pReadThread-BandSerialPort(this);
void CReadComThread::BandSerialPort(CComPort* pPort)
{
ASSERT(pPort);
this-m_pPort = pPort;
//创建异步读取事件
if(this-m_pPort-IsOverlapped())
{
this-m_ReadOverlapped.hEvent =::CreateEvent(NULL,false,false,NULL);
ASSERT(this-m_ReadOverlapped.hEvent);
this-m_BreakOverlapped.hEvent = ::CreateEvent(NULL,false,false,NULL);
ASSERT(this-m_BreakOverlapped.hEvent);
}
}
模式主要在线程执行的过程中发挥作用
3.串口的发送数据过程
DWORD CComPort::Output(void* pBuf,DWORD Count)
{
DWORD dwWriteBytes=0;
if(this-IsOverlapped())//异步模式
{
this-m_pPort-Write(pBuf,Count,this-m_WriteOverlapped);
if(WaitForSingleObject(this-m_WriteOverlapped.hEvent,INFINITE)==WAIT_OBJECT_0)
{
this-m_pPort-GetOverlappedResult(this-m_WriteOverlapped,dwWriteBytes,false);
}
}
else
dwWriteBytes= this-m_pPort-Write(pBuf,Count);
return dwWriteBytes;
}再看this-m_pPort-Write(pBuf,Count);
实际上是:调用DWORD CSerialPort::Write(const void* lpBuf, DWORD dwCount)
{
ASSERT(IsOpen());
ASSERT(!m_bOverlapped);
DWORD dwBytesWritten = 0;
if (!WriteFile(m_hComm, lpBuf, dwCount, &dwBytesWritten, NULL))
{
TRACE(_T("Failed in call to WriteFile\n"));
AfxThrowSerialException();
}
return dwBytesWritten;
}
或者是BOOL CSerialPort::Write(const void* lpBuf, DWORD dwCount, OVERLAPPED&
overlapped, DWORD* pBytesWritten) 异步写串口的过程
4.串口的读取过程
分两种:查询读取和线程自动读取
(1) 查询读取 DWORD CComPort::GetInput(void* pBuf,DWORD Count,DWORD dwMilliseconds)
{
//不能在自动模式下getinput
if(this-GetReceiveMode()==CComPort::AutoReceiveByBreak||
this-GetReceiveMode()==CComPort::AutoReceiveBySignal)
{
::AfxMessageBox("Can''t use GetInput methord in this mode!");
return 0;
}
if(this-IsOverlapped())
{
ASSERT(this-m_pReadThread);
DWORD dwBytes = this-m_pReadThread-ReadInput(pBuf,Count,dwMilliseconds);
this-m_pPort-TerminateOutstandingReads();
return dwBytes;
}
else
return this-m_pPort-Read(pBuf,Count);
}
主要是调用m_pPort-Read(pBuf,Count);然后调用API函数ReadFile
(2) 线程等待处理
主要过程:在线程CreadComThread的Execute中void CReadComThread::Execute()
{
if(this-m_pPort-GetReceiveMode()==CComPort::ManualReceiveByQuery)
{
this-ExecuteByManualQueryRecvMode();
}
else if(this-m_pPort-GetReceiveMode()==CComPort::ManualReceiveByConst)
{
this-ExecuteByManualConstRecvMode();
}
else if(this-m_pPort-GetReceiveMode()==CComPort::AutoReceiveBySignal)
{
this-ExecuteByAutoSignalRecvMode();
}
else//中断模式
{
this-ExecuteByAutoBreakRecvMode();
}
}
主要是选择模式然后执行:
下面看看this-ExecuteByAutoSignalRecvMode(); void CReadComThread::ExecuteByAutoSignalRecvMode()
{
DWORD dwMask=0;
HANDLE WaitHandles[3]; //监听事件数组
DWORD dwSignaledHandle;
WaitHandles[0] = this-m_pPort-GetCloseHandle();
WaitHandles[1] = this-m_ReadOverlapped.hEvent;
WaitHandles[2] = this-m_BreakOverlapped.hEvent;
this-m_pPort-GetSerialPort()-SetMask(EV_ERR | EV_RLSD | EV_RING );
if(!SetBreakEvent(dwMask))
goto EndThread;
//设置读事件
if(!SetReadEvent(this-m_ReadOverlapped))
goto EndThread;
//设置comEvent
for(;;)
{
dwSignaledHandle=::WaitForMultipleObjects(3,WaitHandles,false,INFINITE);
switch(dwSignaledHandle)
{
case WAIT_OBJECT_0:
goto EndThread;
break;
case WAIT_OBJECT_0+1:
if(!this-HandleReadEvent(this-m_ReadOverlapped))
goto EndThread;
if(!this-SetReadEvent(this-m_ReadOverlapped))
goto EndThread;
break;
case WAIT_OBJECT_0+2:
if(!this-HandleBreakEvent(dwMask))
goto EndThread;
if(!this-SetBreakEvent(dwMask))
goto EndThread;
break;
default:
//goto EndThread;
break;
}
}
EndThread:
this-m_pPort-GetSerialPort()-Purge(PURGE_RXABORT | PURGE_RXCLEAR);
::CloseHandle(this-m_ReadOverlapped.hEvent);
::CloseHandle(this-m_BreakOverlapped.hEvent);
return ;
}主要是一个等待事件发送然后调用,响应的过程,如果读取事件发生则调用this-HandleReadEvent(this-m_ReadOverlapped);
bool CReadComThread::HandleReadEvent(OVERLAPPED& overlapped)
{
if(this-m_pPort-GetSerialPort()-GetOverlappedResult(overlapped,this-m_InBufferCount,false))
{
return this-HandleData();
}
DWORD dwError = ::GetLastError();
if(dwError==ERROR_INVALID_HANDLE)
return false;
else
return true;
}如果查询有数据,则this-HandleData();bool CReadComThread::HandleData() //处理读取数据
{
if(this-m_InBufferCount0)
{
this-m_pBuffer = new byte[this-m_InBufferCount];
for(int i=0;i<(int)this-m_InBufferCount;i++)
{
this-m_pBuffer[i] = this-m_InputBuffer[i];
}
this-m_pPort-ReceiveData(this-m_pBuffer,this-m_InBufferCount);
delete[] this-m_pBuffer;
}
return true;
}
在这调用了this-m_pPort-ReceiveData(this-m_pBuffer,this-m_InBufferCount);即调用了你传入的函数.整个读取过程就这样了.如果还有不明白,请看我写的CComPort的类的代码.
当然,由于串行通讯各种情况综合在一起比较复杂,另外本人水平有限,所以一时很难考虑全面,这个版本暂时定为1.0,希望大家如果在使用过程中发现什么问题,请及时的告诉偶(E-Mail:Milo2002@sohu.com),有时间我做个升级什么的,当然,希望大家多多提出批评意见.