CSerialPort类解析(一)
CserialPort类的功能及成员函数介绍
CserialPort类是免费提供的串口累,Codeguru是一个非常不错的源代码网站
CserialPort类支持线连接(非MODEM)的串口编程操作。
CserialPort类是基于多线程的,其工作流程如下:首先设置好串口参数,再开启串口检测工作线程,串口检测工作线程检测到串口接收到的数据、流控制事件或其他串口事件后,就以消息方式通知主程序,激发消息处理函数来进行数据处理,这是对接受数据而言的,发送数据可直接向串口发送。
CserialPort类定义的消息如表
消息名称 | 消息号 | 功能说明 |
WM_COMM_BREAK_DETECTED | WM_USER+1 | 检测到输入中断 |
WM_COMM_CTS_DETECTED | WM_USER+2 | 检测到CTS(清除发送)信号状态改变 |
WM_COMM_DSR_DETECTED | WM_USER+3 | 检测到DSR(数据设备准备就绪)信号状态改变 |
WM_COMM_ERR_DETECTED | WM_USER+4 | 发生线状态错误(包括CE_FRAME,CE_OVERRUN,和CE_RXPARITY) |
WM_COMM_RING_DETECTED | WM_USER+5 | 检测到响铃指示信号 |
WM_COMM_RLSD_DETECTED | WM_USER+6 | 检测到RLSD(接收线信号)状态改变 |
WM_COMM_RXCHAR | WM_USER+7 | 接收到一个字符并已放入接受缓冲区 |
WM_COMM_RXFLAG_DETECTED | WM_USER+8 | 检测到接受到字符(该字符已放入接受缓冲区)事件 |
WM_COMM_TXEMPTY_DETECTED | WM_USER+9 | 检测到发送缓冲区最后一个字符已经被发送 |
CSerialPort类解析(二)
介绍几个经常用到的函数:
1、串口初始化函数InitPort
BOOL CSerialPort::InitPort(CWnd *pPortOwner, // the owner (CWnd) of the port (receives message) UINT portnr, // portnumber (1..4) UINT baud, // baudrate char parity, // parity UINT databits, // databits UINT stopbits, // stopbits DWORD dwCommEvents, // EV_RXCHAR, EV_CTS etc UINT writebuffersize) // size to the writebuffer { assert(portnr > 0 && portnr < 5); assert(pPortOwner != NULL); // if the thread is alive: Kill if (m_bThreadAlive) { do { SetEvent(m_hShutdownEvent); } while (m_bThreadAlive); TRACE("Thread ended\n"); } // create events if (m_ov.hEvent != NULL) ResetEvent(m_ov.hEvent); m_ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (m_hWriteEvent != NULL) ResetEvent(m_hWriteEvent); m_hWriteEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (m_hShutdownEvent != NULL) ResetEvent(m_hShutdownEvent); m_hShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // initialize the event objects m_hEventArray[0] = m_hShutdownEvent; // highest priority m_hEventArray[1] = m_ov.hEvent; m_hEventArray[2] = m_hWriteEvent; // initialize critical section InitializeCriticalSection(&m_csCommunicationSync); // set buffersize for writing and save the owner m_pOwner = pPortOwner; if (m_szWriteBuffer != NULL) delete [] m_szWriteBuffer; m_szWriteBuffer = new char[writebuffersize]; m_nPortNr = portnr; m_nWriteBufferSize = writebuffersize; m_dwCommEvents = dwCommEvents; BOOL bResult = FALSE; char *szPort = new char[50]; char *szBaud = new char[50]; // now it critical! EnterCriticalSection(&m_csCommunicationSync); // if the port is already opened: close it if (m_hComm != NULL) { CloseHandle(m_hComm); m_hComm = NULL; } // prepare port strings sprintf(szPort, "COM%d", portnr); sprintf(szBaud, "baud=%d parity=%c data=%d stop=%d", baud, parity, databits, stopbits); // get a handle to the port m_hComm = CreateFile(szPort, // communication port string (COMX) GENERIC_READ | GENERIC_WRITE, // read/write types 0, // comm devices must be opened with exclusive access NULL, // no security attributes OPEN_EXISTING, // comm devices must use OPEN_EXISTING FILE_FLAG_OVERLAPPED, // Async I/O 0); // template must be 0 for comm devices if (m_hComm == INVALID_HANDLE_VALUE) { // port not found delete [] szPort; delete [] szBaud; return FALSE; } // set the timeout values m_CommTimeouts.ReadIntervalTimeout = 1000; m_CommTimeouts.ReadTotalTimeoutMultiplier = 1000; m_CommTimeouts.ReadTotalTimeoutConstant = 1000; m_CommTimeouts.WriteTotalTimeoutMultiplier = 1000; m_CommTimeouts.WriteTotalTimeoutConstant = 1000; // configure if (SetCommTimeouts(m_hComm, &m_CommTimeouts)) { if (SetCommMask(m_hComm, dwCommEvents)) { if (GetCommState(m_hComm, &m_dcb)) { m_dcb.fRtsControl = RTS_CONTROL_ENABLE; // set RTS bit high! if (BuildCommDCB(szBaud, &m_dcb)) { if (SetCommState(m_hComm, &m_dcb)) ; // normal operation... continue else ProcessErrorMessage("SetCommState()"); } else ProcessErrorMessage("BuildCommDCB()"); } else ProcessErrorMessage("GetCommState()"); } else ProcessErrorMessage("SetCommMask()"); } else ProcessErrorMessage("SetCommTimeouts()"); delete [] szPort; delete [] szBaud; // flush the port PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT); // release critical section LeaveCriticalSection(&m_csCommunicationSync); TRACE("Initialisation for communicationport %d completed.\nUse Startmonitor to communicate.\n", portnr); return TRUE; }
这个函数是用来初始化串口的,即设置串口的通信参数:需要打开的串口号、波特率、奇偶校验方式、数据位、停止位,这里还可 以用来进行事件的设定
如果串口初始化成功,就返回TRUE,若串口被其他设备占用、不存在或存在其他股占,就返回FALSE,编程者可以在这儿提示串口操作是否成功
如果在当前主串口调用这个函数,那么pPortOwner可用this指针表示,串口号在函数中做了限制,只能用1,2,3和4四个串口号,而事实上在编程时可能用到更多串口号,可以通过通过注释掉本函数中的“assert(portur>0&&portnr<5)”语句取消对串口号的限制
CSerialPort类解析(三)
2、启动串口通信监测线程函数StartMonitoring()
串口初始化成功后,就可以调用BOOL StartMonitoring()来启动串口检测线程,线程启动成功,发挥TRUE
BOOL CSerialPort::StartMonitoring()
{
if (!(m_Thread = AfxBeginThread(CommThread, this)))
return FALSE;
TRACE("Thread started\n");
return TRUE;
}
3、暂停或停止监测线程函数StopMonitoring()
该函数暂停或停止串口检测,要注意的是,调用该函数后,串口资源仍然被占用//
// Suspend the comm thread
//
BOOL CSerialPort::StopMonitoring()
{
TRACE("Thread suspended\n");
m_Thread->SuspendThread();
return TRUE;
}
4、关闭串口函数ClosePort()
该函数功能是关闭串口,释放串口资源,调用该函数后,如果要继续使用串口,还需要调用InitPort()函数
5、通过串口发送字符/写串口函数WriteToPort()
该函数完成写串口功能,即向串口发送字符。
//
// Write a string to the port
//
void CSerialPort::WriteToPort(char *string)
{
assert(m_hComm != 0);
memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
strcpy(m_szWriteBuffer, string);
// set event for write
SetEvent(m_hWriteEvent);
}
以上是常用的函数介绍,熟悉该类的使用后,可以仔细看看其他函数,对上面介绍的函数,在对串口资源的使用上要记住一下三点:
l 打开串口用调用InitPort()和StartMonitoring();关闭串口用StopMonitoring()和ClosePort()而且以上函数的调用顺序不能乱
l 通过串口发送字符调用函数WriteToPort()
l 接受串口收到的字符需要自己编写WM_COMM_RXCHAR消息处理函数,需要手工添加,
CSerialPort类解析(四)
操作:
首先,需要操作一个串口,所以只需要定义1个类对象就可以了,如要操作多个串口,则要为每个串口均定一个类对象,这可以通过数据方式来实现,这里定义的类对象为m_SerialPort,再定义一个布尔变量m_bSerialPortOpened用来标志串口是否打开。
在CserialPort类中有多个串口事件可以响应,在一般串口编程中,只需要处理WM_COMM_RXCHAR消息就可以了,该类所有的消息均需要人工添加消息处理函数,将处理函数名定义为OnComm(),首先在SerialPortTestDlg.h(头文件)中添加串口字符接受消息WM_COMM_RXCHAR(串口接受缓冲区内有一个字符)的响应函数声明:
//Generated message map funnctions
//{{AFX_MSG(CSCportTestView)
afx_msg long OnComm(WPARAM ch, LPARAM port);
//}}AFX_MSG
然后在,SerilPortTestDlg.cpp文件中进行WM_COMM_RXCHAR消息映射
BEGIN_MESSAE_MAP(CSerialPortTestDlg, CDialog)s
//{{AFX_MSG_MAP(CSerialPortTestDlg)
ON_MESSAGE(WM_COMM_RXCHAR, OnComm)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
接着,在SerialPortTestDlg.cpp文件中加入函数OnComm()的实现,并在其中完成对节诶受到的字符的处理,将接收到的字符显示在接受编辑框中:
long CSerialPortTestDlg::OnComm(WPARAM ch, LPARAM port)
{
m_strEditReceiveMsg += ch;
UpdateData(FLASE);//将接收到的字符显示在接受编辑框中
returne 0;
}
说明:WPARAM、LPARAM类型是多态数据类型,在WIN32中为32位,支持多种数据类型,根据需要自动适应,这样,程序有很强的适应性,再次,我们可以分贝理解为char和integer类型数据,每当串口接受缓冲区内有一个字符时,就会产生一个WM_COMM_RXCHAR消息,除法OnComm()函数,这时就可以在函数中进行数据处理,所以,这个消息就是整个程序的源头。
对CSerialPort类的改进
虽然CSerialPort类是一个非常好的类,但毕竟只是集中了作者一个人的智慧和经验,他也有许多缺陷,
n 原类只能发送字符(ASCII文本)不能处理二进制发送(也就是不能发送0X00)
n 该类不能很好的释放串口
n 存在内存泄漏
所以,可以进行如下改进
改进一、ASCII文本和二进制数据发送方式兼容
CSerialPort类中只有一个发送函数WriteToPort()
//
// Write a string to the port
//
void CSerialPort::WriteToPort(char *string)
{
assert(m_hComm != 0);
memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
strcpy(m_szWriteBuffer, string);
// set event for write
SetEvent(m_hWriteEvent);
}
调用上面的函数就只能用字符串方式,而c语言中,空字符(NULL,其中ASCII码值为0,即通常所说的十六禁止0x00字符),是串结束符,当检测到NULL字符后,就认为该字符串结束了,所以0x00字符以ASCII文本方式是不能从串口发送出去的,那么解决这一问题的方法就是用二进制发送,其实这里说的二进制,只不过是不以我们通常所说的“可见”或“能显示的字符”发送,比如,要发如下的一组值:
char chSend[5]={0x33,0x96,0x00,0x31,0xf1};
下面来对类做一些改进,解决这个问题,原理就是用字符数据来发送数据,并在发送时指定其长度,这样,数据没有发送完,发送过程就不会停止,CSerialPort类是用API函数编写的在,只要在WriteFile()函数中指定其实际要发送的长度,就可以将数据全部发送出去:
实现步骤如下:
1、在SerialPort.h文件中为CSerialPort类添加一个整形publicdn成员变量,:int m_nWriteSize;用于指定发送字符数据的长度
添加三个发送函数
CSerialPort类解析(五)——源代码
1、Win32下串口大致操作流程
(1)打开串口:CreateFile函数
(2)建立串口通信事件:CreateEvent函数
(3)初始化串口:SetCommState函数
(4)建立监视线程,即读写数据线程,因为我们不知道什么时候数据会到来,这里是一个异步事件
(5)写数据:WriteFile
(6)结束:关闭线程->停止WaitCommEvent->CloseHandle
2.SerialPort类的数据结构
大致了解操作流程后,先看一下SerialPort类,均在代码注释了
数据成员:
- public:
- int m_nWriteSize; //要写入串口的数据大小
- HANDLE m_hComm; //串口句柄
- protected:
- // thread监视线程
- CWinThread* m_Thread;
- // synchronisation objects
- //临界资源
- CRITICAL_SECTION m_csCommunicationSync;
- //监视线程运行标志
- BOOL m_bThreadAlive;
- // handles
- /*事件句柄*/
- HANDLE m_hWriteEvent;
- HANDLE m_hShutdownEvent;
- // There is a general shutdown when the port is closed.
- //事件数组,包括一个写事件,接收事件,关闭事件
- HANDLE m_hEventArray[3];
- // structures
- OVERLAPPED m_ov; //异步I/O模型
- COMMTIMEOUTS m_CommTimeouts; //超时设置
- DCB m_dcb; //设备控制块
- // owner window
- CWnd* m_pOwner;
- // misc
- UINT m_nPortNr;
- char* m_szWriteBuffer; //写缓冲区
- DWORD m_dwCommEvents; //
- DWORD m_nWriteBufferSize; //写缓冲大小
函数成员:
- public:
- /*******************Port Operation***********************/
- // port initialisation
- /*初始化串口*/
- BOOL InitPort(CWnd* pPortOwner,
- UINT portnr = 1,
- UINT baud = 19200,
- char parity = 'N',
- UINT databits = 8,
- UINT stopbits = 1,
- DWORD dwCommEvents = EV_RXCHAR,
- UINT writebuffersize = 1024);
- //关闭端口
- void ClosePort();
- // start/stop comm watching
- //控制串口监视线程
- BOOL StartMonitoring(); //开启
- BOOL RestartMonitoring(); //复位
- BOOL StopMonitoring(); //停止
- DWORD GetWriteBufferSize();//获取写缓冲大小
- DWORD GetCommEvents(); //获取事件
- DCB GetDCB(); //获取DCB
- //写数据到串口
- void WriteToPort(char* string);
- void WriteToPort(char* string,int n);
- void WriteToPort(LPCTSTR string);
- void WriteToPort(LPCTSTR string,int n);
- protected:
- /***************** protected memberfunctions **********************/
- void ProcessErrorMessage(char* ErrorText);
- //线程函数
- static UINT CommThread(LPVOID pParam);
- //接收字符
- static void ReceiveChar(CSerialPort* port, COMSTAT comstat);
- //写字符
- static void WriteChar(CSerialPort* port);
3.串口操作
(1)初始化串口
流程:检查参数-->检测线程-->创建事件(监视线程)-->打开端口-->设置异步IO结构参数,详细见代码:
- /*初始化串口*/
- BOOL CSerialPort::InitPort(CWnd* pPortOwner, // the owner (CWnd) of the port (receives message)
- UINT portnr, // portnumber (1..4)
- UINT baud, // baudrate
- char parity, // parity
- UINT databits, // databits
- UINT stopbits, // stopbits
- DWORD dwCommEvents, // EV_RXCHAR, EV_CTS etc
- UINT writebuffersize) // size to the writebuffer
- {
- assert(portnr > 0 && portnr < 5);
- assert(pPortOwner != NULL);
- // if the thread is alive: Kill
- //线程在的话关断它
- if (m_bThreadAlive)
- {
- do
- {
- SetEvent(m_hShutdownEvent);
- } while (m_bThreadAlive);
- TRACE("Thread ended/n");
- }
- // create events
- //创建事件
- if (m_ov.hEvent != NULL)
- ResetEvent(m_ov.hEvent);
- else
- m_ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
- if (m_hWriteEvent != NULL)
- ResetEvent(m_hWriteEvent);
- else
- m_hWriteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
- if (m_hShutdownEvent != NULL)
- ResetEvent(m_hShutdownEvent);
- else
- m_hShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
- // initialize the event objects
- //事件数组初始化,设定优先级别
- m_hEventArray[0] = m_hShutdownEvent; // highest priority
- m_hEventArray[1] = m_ov.hEvent;
- m_hEventArray[2] = m_hWriteEvent;
- // initialize critical section
- //初始化一个临界资源对象
- InitializeCriticalSection(&m_csCommunicationSync);
- // set buffersize for writing and save the owner
- m_pOwner = pPortOwner;
- if (m_szWriteBuffer != NULL)
- delete [] m_szWriteBuffer;
- m_szWriteBuffer = new char[writebuffersize];
- m_nPortNr = portnr;
- m_nWriteBufferSize = writebuffersize;
- m_dwCommEvents = dwCommEvents;
- BOOL bResult = FALSE;
- char *szPort = new char[50];
- char *szBaud = new char[50];
- // now it critical!
- /*********************************************
- 多个线程操作相同的数据时,一般是需要按顺序访问的,否则会引导数据错乱,
- 无法控制数据,变成随机变量。为解决这个问题,就需要引入互斥变量,让每个
- 线程都按顺序地访问变量。这样就需要使用EnterCriticalSection和LeaveCriticalSection函数。
- **********************************************************************/
- //进入临界区
- EnterCriticalSection(&m_csCommunicationSync);
- // if the port is already opened: close it
- //端口已经打开的就关闭它
- if (m_hComm != NULL)
- {
- CloseHandle(m_hComm);
- m_hComm = NULL;
- }
- // prepare port strings
- //串口参数
- sprintf(szPort, "COM%d", portnr);
- sprintf(szBaud, "baud=%d parity=%c data=%d stop=%d", baud, parity, databits, stopbits);
- // get a handle to the port
- /****************************************************************
- *通信程序在CreateFile处指定串口设备及相关的操作属性,再返回一个句柄,
- *该句柄将被用于后续的通信操作,并贯穿整个通信过程串口打开后,其属性
- *被设置为默认值,根据具体需要,通过调用GetCommState(hComm,&&dcb)读取
- *当前串口设备控制块DCB设置,修改后通过SetCommState(hComm,&&dcb)将其写
- *入。运用ReadFile()与WriteFile()这两个API函数实现串口读写操作,若为异
- *步通信方式,两函数中最后一个参数为指向OVERLAPPED结构的非空指针,在读
- *写函数返回值为FALSE的情况下,调用GetLastError()函数,返回值为ERROR_IO_PENDING,
- *表明I/O操作悬挂,即操作转入后台继续执行。此时,可以用WaitForSingleObject()
- *来等待结束信号并设置最长等待时间
- *****************************************************************/
- m_hComm = CreateFile(szPort, // communication port string (COMX)串口号
- GENERIC_READ | GENERIC_WRITE, // read/write types 可以读写
- 0, // comm devices must be opened with exclusive access独占方式打开串口
- NULL, // no security attributes 无安全属性
- OPEN_EXISTING, // comm devices must use OPEN_EXISTING
- FILE_FLAG_OVERLAPPED, // Async I/O 异步I/O
- 0); // template must be 0 for comm devices
- //如果创建不成功,错误处理
- if (m_hComm == INVALID_HANDLE_VALUE)
- {
- // port not found
- delete [] szPort;
- delete [] szBaud;
- return FALSE;
- }
- // set the timeout values
- //设置超时上限(异步IO)
- m_CommTimeouts.ReadIntervalTimeout = 1000;
- m_CommTimeouts.ReadTotalTimeoutMultiplier = 1000;
- m_CommTimeouts.ReadTotalTimeoutConstant = 1000;
- m_CommTimeouts.WriteTotalTimeoutMultiplier = 1000;
- m_CommTimeouts.WriteTotalTimeoutConstant = 1000;
- // configure
- /*分别调用Windows API设置串口参数*/
- if (SetCommTimeouts(m_hComm, &m_CommTimeouts)) //设置超时
- {
- /*******************************************************
- 若对端口数据的响应时间要求较严格,可采用事件驱动方式。
- 事件驱动方式通过设置事件通知,当所希望的事件发生时,Windows
- 发出该事件已发生的通知,这与DOS环境下的中断方式很相似。Windows
- 定义了9种串口通信事件,较常用的有以下三种:
- EV_RXCHAR:接收到一个字节,并放入输入缓冲区;
- EV_TXEMPTY:输出缓冲区中的最后一个字符,发送出去;
- EV_RXFLAG:接收到事件字符(DCB结构中EvtChar成员),放入输入缓冲区
- 在用SetCommMask()指定了有用的事件后,应用程序可调用WaitCommEvent()来等待事
- 件的发生。SetCommMask(hComm,0)可使WaitCommEvent()中止。
- **************************************************************/
- if (SetCommMask(m_hComm, dwCommEvents)) //设置通信事件
- {
- if (GetCommState(m_hComm, &m_dcb)) //获取当前DCB参数
- {
- m_dcb.EvtChar = 'q'; //设置字件字符
- m_dcb.fRtsControl = RTS_CONTROL_ENABLE; // set RTS bit high!
- if (BuildCommDCB(szBaud, &m_dcb)) //填写DCB结构
- {
- if (SetCommState(m_hComm, &m_dcb)) //配置DCB
- ; // normal operation... continue
- else
- ProcessErrorMessage("SetCommState()");
- }
- else
- ProcessErrorMessage("BuildCommDCB()");
- }
- else
- ProcessErrorMessage("GetCommState()");
- }
- else
- ProcessErrorMessage("SetCommMask()");
- }
- else
- ProcessErrorMessage("SetCommTimeouts()");
- delete [] szPort;
- delete [] szBaud;
- // flush the port
- //终止读写并清空接收和发送
- PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
- // release critical section
- //出临界区
- LeaveCriticalSection(&m_csCommunicationSync);
- TRACE("Initialisation for communicationport %d completed./nUse Startmonitor to communicate./n", portnr);
- return TRUE;
- }
(2)监视线程的控制
先看比较简单的线程控制吧,主要有开启线程,复位和停止
- //开始监视串口
- BOOL CSerialPort::StartMonitoring()
- {
- if (!(m_Thread = AfxBeginThread(CommThread, this)))
- return FALSE;
- TRACE("Thread started/n");
- return TRUE;
- }
- //
- // Restart the comm thread
- //
- //重启监视线程(挂起重启)
- BOOL CSerialPort::RestartMonitoring()
- {
- TRACE("Thread resumed/n");
- m_Thread->ResumeThread();
- return TRUE;
- }
- //
- // Suspend the comm thread
- //
- //挂起监视线程
- BOOL CSerialPort::StopMonitoring()
- {
- TRACE("Thread suspended/n");
- m_Thread->SuspendThread();
- return TRUE;
- }
(3)监视线程
我们把读写串口的操作全部交给监视线程,现在简单看一下监视线程的大致流程:
检查串口-->进入循环{WaitCommEvent(不阻塞询问)询问事件-->如果有事件来到-->到相应处理(关闭/读/写)}
详细代码如下:
- //监视串口函数
- UINT CSerialPort::CommThread(LPVOID pParam)
- {
- // Cast the void pointer passed to the thread back to
- // a pointer of CSerialPort class
- CSerialPort *port = (CSerialPort*)pParam;
- // Set the status variable in the dialog class to
- // TRUE to indicate the thread is running.
- port->m_bThreadAlive = TRUE;
- // Misc. variables
- DWORD BytesTransfered = 0;
- DWORD Event = 0;
- DWORD CommEvent = 0;
- DWORD dwError = 0;
- COMSTAT comstat;
- BOOL bResult = TRUE;
- // Clear comm buffers at startup
- //检查串口是否打开
- if (port->m_hComm) // check if the port is opened
- PurgeComm(port->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
- // begin forever loop. This loop will run as long as the thread is alive.
- //不断读取数据
- for (;;)
- {
- // Make a call to WaitCommEvent(). This call will return immediatly
- // because our port was created as an async port (FILE_FLAG_OVERLAPPED
- // and an m_OverlappedStructerlapped structure specified). This call will cause the
- // m_OverlappedStructerlapped element m_OverlappedStruct.hEvent, which is part of the m_hEventArray to
- // be placed in a non-signeled state if there are no bytes available to be read,
- // or to a signeled state if there are bytes available. If this event handle
- // is set to the non-signeled state, it will be set to signeled when a
- // character arrives at the port.
- // we do this for each port!
- /********************************************************************
- WaitCommEvent函数第3个参数1pOverlapped可以是一个OVERLAPPED结构的变量指针
- ,也可以是NULL,当用NULL时,表示该函数是同步的,否则表示该函数是异步的。
- 调用WaitCommEvent时,如果异步操作不能立即完成,会立即返回FALSE,系统在
- WaitCommEvent返回前将OVERLAPPED结构成员hEvent设为无信号状态,等到产生通信
- 事件时,系统将其置有信号
- ***********************************************************************/
- bResult = WaitCommEvent(port->m_hComm, &Event, &port->m_ov);//表示该函数是异步的
- if (!bResult)
- {
- //如果WaitCommEvent返回Error为NULL,则查询错误信息
- // If WaitCommEvent() returns FALSE, process the last error to determin
- // the reason..
- switch (dwError = GetLastError())
- {
- case ERROR_IO_PENDING: //正常情况,没有字符可读
- {
- // This is a normal return value if there are no bytes
- // to read at the port.
- // Do nothing and continue
- break;
- }
- case 87: //系统错误
- {
- // Under Windows NT, this value is returned for some reason.
- // I have not investigated why, but it is also a valid reply
- // Also do nothing and continue.
- break;
- }
- default: //发生其他错误
- {
- // All other error codes indicate a serious error has
- // occured. Process this error.
- port->ProcessErrorMessage("WaitCommEvent()");
- break;
- }
- }
- }
- else //WaitCommEvent()能正确返回
- {
- // If WaitCommEvent() returns TRUE, check to be sure there are
- // actually bytes in the buffer to read.
- //
- // If you are reading more than one byte at a time from the buffer
- // (which this program does not do) you will have the situation occur
- // where the first byte to arrive will cause the WaitForMultipleObjects()
- // function to stop waiting. The WaitForMultipleObjects() function
- // resets the event handle in m_OverlappedStruct.hEvent to the non-signelead state
- // as it returns.
- //
- // If in the time between the reset of this event and the call to
- // ReadFile() more bytes arrive, the m_OverlappedStruct.hEvent handle will be set again
- // to the signeled state. When the call to ReadFile() occurs, it will
- // read all of the bytes from the buffer, and the program will
- // loop back around to WaitCommEvent().
- //
- // At this point you will be in the situation where m_OverlappedStruct.hEvent is set,
- // but there are no bytes available to read. If you proceed and call
- // ReadFile(), it will return immediatly due to the async port setup, but
- // GetOverlappedResults() will not return until the next character arrives.
- //
- // It is not desirable for the GetOverlappedResults() function to be in
- // this state. The thread shutdown event (event 0) and the WriteFile()
- // event (Event2) will not work if the thread is blocked by GetOverlappedResults().
- //
- // The solution to this is to check the buffer with a call to ClearCommError().
- // This call will reset the event handle, and if there are no bytes to read
- // we can loop back through WaitCommEvent() again, then proceed.
- // If there are really bytes to read, do nothing and proceed.
- bResult = ClearCommError(port->m_hComm, &dwError, &comstat);
- if (comstat.cbInQue == 0)
- continue;
- } // end if bResult
- //主等待函数,会阻塞线程
- // Main wait function. This function will normally block the thread
- // until one of nine events occur that require action.
- //等待3个事件:关断/读/写,有一个事件发生就返回
- Event = WaitForMultipleObjects(3, //3个事件
- port->m_hEventArray,//事件数组
- FALSE, //有一个事件发生就返回
- INFINITE); //超时时间
- switch (Event)
- {
- case 0:
- {
- // Shutdown event. This is event zero so it will be
- // the higest priority and be serviced first.
- //关断事件,关闭串口
- CloseHandle(port->m_hComm);
- port->m_hComm=NULL;
- port->m_bThreadAlive = FALSE;
- // Kill this thread. break is not needed, but makes me feel better.
- AfxEndThread(100);
- break;
- }
- case 1: // read event 将定义的各种消息发送出去
- {
- //接收
- GetCommMask(port->m_hComm, &CommEvent);
- if (CommEvent & EV_RXCHAR)
- // Receive character event from port.
- ReceiveChar(port, comstat);
- if (CommEvent & EV_CTS)
- ::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_CTS_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);
- if (CommEvent & EV_BREAK)
- ::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_BREAK_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);
- if (CommEvent & EV_ERR)
- ::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_ERR_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);
- if (CommEvent & EV_RING)
- ::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_RING_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);
- if (CommEvent & EV_RXFLAG)
- ::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_RXFLAG_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);
- break;
- }
- case 2: // write event发送数据
- {
- // Write character event from port
- //写
- WriteChar(port);
- break;
- }
- } // end switch
- } // close forever loop
- return 0;
- }
(4)读取数据操作
读取数据是一个异步操作,当有数据发来时,会触发读事件m_ov.hEvent,监视线程捕捉到事件后并获知是读事件,进入相关读处理,这里调用函数ReceiveChar
,ReceiveChar中调用ReadFile函数将串口数据读到Buffer缓冲中,相关代码如下:
- //接收数据
- void CSerialPort::ReceiveChar(CSerialPort* port, COMSTAT comstat)
- {
- BOOL bRead = TRUE;
- BOOL bResult = TRUE;
- DWORD dwError = 0;
- DWORD BytesRead = 0;
- unsigned char RXBuff;
- for (;;)
- {
- // Gain ownership of the comm port critical section.
- // This process guarantees no other part of this program
- // is using the port object.
- EnterCriticalSection(&port->m_csCommunicationSync);
- // ClearCommError() will update the COMSTAT structure and
- // clear any other errors.
- //更新COMSTAT
- bResult = ClearCommError(port->m_hComm, &dwError, &comstat);
- LeaveCriticalSection(&port->m_csCommunicationSync);
- // start forever loop. I use this type of loop because I
- // do not know at runtime how many loops this will have to
- // run. My solution is to start a forever loop and to
- // break out of it when I have processed all of the
- // data available. Be careful with this approach and
- // be sure your loop will exit.
- // My reasons for this are not as clear in this sample
- // as it is in my production code, but I have found this
- // solutiion to be the most efficient way to do this.
- //所有字符均被读出,中断循环
- if (comstat.cbInQue == 0)
- {
- // break out when all bytes have been read
- break;
- }
- EnterCriticalSection(&port->m_csCommunicationSync);
- if (bRead)
- {
- //串口读出,读出缓冲区中字节
- bResult = ReadFile(port->m_hComm, // Handle to COMM port
- &RXBuff, // RX Buffer Pointer
- 1, // Read one byte
- &BytesRead, // Stores number of bytes read
- &port->m_ov); // pointer to the m_ov structure
- // deal with the error code
- //若返回错误,错误处理
- if (!bResult)
- {
- switch (dwError = GetLastError())
- {
- case ERROR_IO_PENDING:
- {
- // asynchronous i/o is still in progress
- // Proceed on to GetOverlappedResults();
- //异步IO仍在进行
- bRead = FALSE;
- break;
- }
- default:
- { //其他错误处理
- // Another error has occured. Process this error.
- port->ProcessErrorMessage("ReadFile()");
- break;
- }
- }
- }
- else
- { //ReadFile返回TRUE
- // ReadFile() returned complete. It is not necessary to call GetOverlappedResults()
- bRead = TRUE;
- }
- } // close if (bRead)
- //异步IO操作仍在进行,需要调用GetOverlappedResult查询
- if (!bRead)
- {
- bRead = TRUE;
- bResult = GetOverlappedResult(port->m_hComm, // Handle to COMM port
- &port->m_ov, // Overlapped structure
- &BytesRead, // Stores number of bytes read
- TRUE); // Wait flag
- // deal with the error code
- if (!bResult)
- {
- port->ProcessErrorMessage("GetOverlappedResults() in ReadFile()");
- }
- } // close if (!bRead)
- LeaveCriticalSection(&port->m_csCommunicationSync);
- // notify parent that a byte was received
- ::SendMessage((port->m_pOwner)->m_hWnd, WM_COMM_RXCHAR, (WPARAM) RXBuff, (LPARAM) port->m_nPortNr);
- } // end forever loop
- }
(5)写数据
也是由监视线程操作,不过触发事件交给主线程来触发,函数是WriteToPort
- //
- // Write a string to the port
- //
- void CSerialPort::WriteToPort(char* string)
- {
- assert(m_hComm != 0);
- //写进写缓冲区
- memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
- strcpy(m_szWriteBuffer, string);
- m_nWriteSize=strlen(string);
- // set event for write
- SetEvent(m_hWriteEvent);
- }
线程调用的函数WriteChar,把缓冲里的数据写到串口中,期间调用WriteFile
详细代码:
- //写数据
- void CSerialPort::WriteChar(CSerialPort* port)
- {
- BOOL bWrite = TRUE;
- BOOL bResult = TRUE;
- DWORD BytesSent = 0;
- ResetEvent(port->m_hWriteEvent); //复位写事件句柄
- // Gain ownership of the critical section
- EnterCriticalSection(&port->m_csCommunicationSync);
- if (bWrite)
- {
- // Initailize variables
- port->m_ov.Offset = 0;
- port->m_ov.OffsetHigh = 0;
- // Clear buffer
- PurgeComm(port->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
- //串口写入
- bResult = WriteFile(port->m_hComm, // Handle to COMM Port
- port->m_szWriteBuffer, // Pointer to message buffer in calling finction
- // strlen((char*)port->m_szWriteBuffer), // Length of message to send
- port->m_nWriteSize, // Length of message to send
- &BytesSent, // Where to store the number of bytes sent
- &port->m_ov); // Overlapped structure
- // deal with any error codes
- if (!bResult)
- {
- DWORD dwError = GetLastError();
- switch (dwError)
- {
- case ERROR_IO_PENDING:
- {
- // continue to GetOverlappedResults()
- BytesSent = 0;
- bWrite = FALSE;
- break;
- }
- default:
- {
- // all other error codes
- port->ProcessErrorMessage("WriteFile()");
- }
- }
- }
- else
- {
- LeaveCriticalSection(&port->m_csCommunicationSync);
- }
- } // end if(bWrite)
- if (!bWrite)
- {
- bWrite = TRUE;
- bResult = GetOverlappedResult(port->m_hComm, // Handle to COMM port
- &port->m_ov, // Overlapped structure
- &BytesSent, // Stores number of bytes sent
- TRUE); // Wait flag
- LeaveCriticalSection(&port->m_csCommunicationSync);
- // deal with the error code
- // if (!bResult)
- {
- // port->ProcessErrorMessage("GetOverlappedResults() in WriteFile()");
- }
- } // end if (!bWrite)
- //Verify that the data size send equals what we tried to send
- if (BytesSent != port->m_nWriteSize) // Length of message to send)
- {
- TRACE("WARNING: WriteFile() error.. Bytes Sent: %d; Message Length: %d/n", BytesSent, strlen((char*)port->m_szWriteBuffer));
- }
- // ::SendMessage((port->m_pOwner)->m_hWnd, WM_COMM_TXEMPTY_DETECTED, (WPARAM) RXBuff, (LPARAM) port->m_nPortNr);
- // ::SendMessage((port->m_pOwner)->m_hWnd, WM_COMM_TXEMPTY_DETECTED,0,(LPARAM) port->m_nPortNr);
- }
(6)其他操作
其他比如获取DCB,关闭等,比较简单,不做分析
代码如下:
- //
- // Return the device control block
- //
- DCB CSerialPort::GetDCB()
- {
- return m_dcb;
- }
- //
- // Return the communication event masks
- //
- DWORD CSerialPort::GetCommEvents()
- {
- return m_dwCommEvents;
- }
- //
- // Return the output buffer size
- //
- DWORD CSerialPort::GetWriteBufferSize()
- {
- return m_nWriteBufferSize;
- }
- void CSerialPort::ClosePort()
- {
- SetEvent(m_hShutdownEvent);
- }