serialport.cpp
#include "stdafx.h"
#include "SerialPort.h"
#include <assert.h>
int m_nComArray[20];//存放活跃的串口号
//
// Constructor
//
CSerialPort::CSerialPort()
{
m_hComm = NULL;
// initialize overlapped structure members to zero
///初始化异步结构体
m_ov.Offset = 0;
m_ov.OffsetHigh = 0;
// create events
m_ov.hEvent = NULL;
m_hWriteEvent = NULL;
m_hShutdownEvent = NULL;
m_szWriteBuffer = NULL;
m_bThreadAlive = FALSE;
m_nWriteSize = 1;
m_bIsSuspened = FALSE;
}
//
// Delete dynamic memory
//
CSerialPort::~CSerialPort()
{
MSG message;
//增加线程挂起判断,解决由于线程挂起导致串口关闭死锁的问题 add by itas109 2016-06-29
if (IsThreadSuspend(m_Thread))
{
ResumeThread(m_Thread);
}
//串口句柄无效 add by itas109 2016-07-29
if (m_hComm == INVALID_HANDLE_VALUE)
{
CloseHandle(m_hComm);
m_hComm = NULL;
return;
}
do
{
SetEvent(m_hShutdownEvent);
//add by liquanhai 防止死锁 2011-11-06
if (::PeekMessage(&message, m_pOwner, 0, 0, PM_REMOVE))
{
::TranslateMessage(&message);
::DispatchMessage(&message);
}
} while (m_bThreadAlive);
// if the port is still opened: close it
if (m_hComm != NULL)
{
CloseHandle(m_hComm);
m_hComm = NULL;
}
// Close Handles
if (m_hShutdownEvent != NULL)
CloseHandle(m_hShutdownEvent);
if (m_ov.hEvent != NULL)
CloseHandle(m_ov.hEvent);
if (m_hWriteEvent != NULL)
CloseHandle(m_hWriteEvent);
//TRACE("Thread ended\n");
if (m_szWriteBuffer != NULL)
{
delete[] m_szWriteBuffer;
m_szWriteBuffer = NULL;
}
}
//
// Initialize the port. This can be port 1 to MaxSerialPortNum.
///初始化串口。只能是1-MaxSerialPortNum
//
//parity:
// n=none
// e=even
// o=odd
// m=mark
// s=space
//data:
// 5,6,7,8
//stop:
// 1,1.5,2
//
BOOL CSerialPort::InitPort(HWND pPortOwner, // the owner (CWnd) of the port (receives message)
UINT portnr, // 串口号 (1..MaxSerialPortNum)
UINT baud, // 速率
TCHAR parity, // parity
UINT databits, // databits
UINT stopbits, // stopbits
DWORD dwCommEvents, // EV_RXCHAR, EV_CTS etc
UINT writebuffersize,// size to the writebuffer
DWORD ReadIntervalTimeout,
DWORD ReadTotalTimeoutMultiplier,
DWORD ReadTotalTimeoutConstant,
DWORD WriteTotalTimeoutMultiplier,
DWORD WriteTotalTimeoutConstant)
{
assert(portnr > 0 && portnr < MaxSerialPortNum);
assert(pPortOwner != NULL);
MSG message;
//增加线程挂起判断,解决由于线程挂起导致串口关闭死锁的问题 add by itas109 2016-06-29
if (IsThreadSuspend(m_Thread))
{
ResumeThread(m_Thread);
}
// if the thread is alive: Kill
if (m_bThreadAlive)
{
do
{
SetEvent(m_hShutdownEvent);
//add by liquanhai 防止死锁 2011-11-06
if (::PeekMessage(&message, m_pOwner, 0, 0, PM_REMOVE))
{
::TranslateMessage(&message);
::DispatchMessage(&message);
}
} while (m_bThreadAlive);
//TRACE("Thread ended\n");
//此处的延时很重要,因为如果串口开着,发送关闭指令到彻底关闭需要一定的时间,这个延时应该跟电脑的性能相关
Sleep(50);//add by itas109 2016-08-02
}
// create events
if (m_ov.hEvent != NULL)
SerialPort.h
#ifndef __SERIALPORT_H__
#define __SERIALPORT_H__
#include "stdio.h"
#include<windows.h>
struct serialPortInfo
{
UINT portNr;//串口号
DWORD bytesRead;//读取的字节数
};
#ifndef Wm_SerialPort_MSG_BASE
#define Wm_SerialPort_MSG_BASE WM_USER + 617 //!< 消息编号的基点
#endif
#define Wm_SerialPort_BREAK_DETECTED Wm_SerialPort_MSG_BASE + 1 // A break was detected on input.
#define Wm_SerialPort_CTS_DETECTED Wm_SerialPort_MSG_BASE + 2 // The CTS (clear-to-send) signal changed state.
#define Wm_SerialPort_DSR_DETECTED Wm_SerialPort_MSG_BASE + 3 // The DSR (data-set-ready) signal changed state.
#define Wm_SerialPort_ERR_DETECTED Wm_SerialPort_MSG_BASE + 4 // A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.
#define Wm_SerialPort_RING_DETECTED Wm_SerialPort_MSG_BASE + 5 // A ring indicator was detected.
#define Wm_SerialPort_RLSD_DETECTED Wm_SerialPort_MSG_BASE + 6 // The RLSD (receive-line-signal-detect) signal changed state.
#define Wm_SerialPort_RXCHAR Wm_SerialPort_MSG_BASE + 7 // A character was received and placed in the input buffer.
#define Wm_SerialPort_RXFLAG_DETECTED Wm_SerialPort_MSG_BASE + 8 // The event character was received and placed in the input buffer.
#define Wm_SerialPort_TXEMPTY_DETECTED Wm_SerialPort_MSG_BASE + 9 // The last character in the output buffer was sent.
#define Wm_SerialPort_RXSTR Wm_SerialPort_MSG_BASE + 10 // Receive string
#define MaxSerialPortNum 200 ///有效的串口总个数,不是串口的号 //add by itas109 2014-01-09
#define IsReceiveString 0 //采用何种方式接收:ReceiveString 1多字符串接收(对应响应函数为Wm_SerialPort_RXSTR),ReceiveString 0一个字符一个字符接收(对应响应函数为Wm_SerialPort_RXCHAR)
class CSerialPort
{
public:
// contruction and destruction
CSerialPort();
virtual ~CSerialPort();
// port initialisation
// UINT stopsbits = ONESTOPBIT stop is index 0 = 1 1=1.5 2=2
// 切记:stopsbits = 1,不是停止位为1。
// by itas109 20160506
BOOL InitPort(HWND pPortOwner, UINT portnr = 1, UINT baud = 9600,
TCHAR parity = _T('N'), UINT databits = 8, UINT stopsbits = ONESTOPBIT,
DWORD dwCommEvents = EV_RXCHAR | EV_CTS, UINT nBufferSize = 512,
DWORD ReadIntervalTimeout = 1000,
DWORD ReadTotalTimeoutMultiplier = 1000,
DWORD ReadTotalTimeoutConstant = 1000,
DWORD WriteTotalTimeoutMultiplier = 1000,
DWORD WriteTotalTimeoutConstant = 1000);
// start/stop comm watching
///控制串口监视线程
BOOL StartMonitoring();//开始监听
BOOL ResumeMonitoring();//恢复监听
BOOL SuspendMonitoring();//挂起监听
BOOL IsThreadSuspend(HANDLE hThread);//判断线程是否挂起 //add by itas109 2016-06-29
DWORD GetWriteBufferSize();///获取写缓冲大小
DWORD GetCommEvents();///获取事件
DCB GetDCB();///获取DCB
///写数据到串口
void WriteToPort(char* string, size_t n); // add by mrlong 2007-12-25
void WriteToPort(PBYTE Buffer, size_t n);// add by mrlong
void ClosePort(); // add by mrlong 2007-12-2
BOOL IsOpen();
void QueryKey(HKEY hKey);///查询注册表的串口号,将值存于数组中
#ifdef _AFX
void Hkey2ComboBox(CComboBox& m_PortNO);///将QueryKey查询到的串口号添加到CComboBox控件中
#endif // _AFX
protected:
// protected memberfunctions
void ProcessErrorMessage(TCHAR* ErrorText);///错误处理
static DWORD WINAPI CommThread(LPVOID pParam);///线程函数
static void ReceiveChar(CSerialPort* port);
static void ReceiveStr(CSerialPort* port); //add by itas109 2016-06-22
static void WriteChar(CSerialPort* port);
// thread
HANDLE m_Thread;
BOOL m_bIsSuspened;///thread监视线程是否挂起
// synchronisation objects
CRITICAL_SECTION m_csCommunicationSync;///临界资源
BOOL m_bThreadAlive;///监视线程运行标志
// handles
HANDLE m_hShutdownEvent; //stop发生的事件
HANDLE m_hComm; // 串口句柄
HANDLE m_hWriteEvent; // write
OVERLAPPED m_ov;///异步I/O
COMMTIMEOUTS m_SerialPortTimeouts;///超时设置
DCB m_dcb;///设备控制块
// owner window
HWND m_pOwner;
// misc
UINT m_nPortNr; ///串口号
PBYTE m_szWriteBuffer;///写缓冲区
DWORD m_dwCommEvents;
DWORD m_nWriteBufferSize;///写缓冲大小
size_t m_nWriteSize;//写入字节数 //add by mrlong 2007-12-25
};
#endif __SERIALPORT_H__
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
//为避免有些串口设备无数据输入,但一直返回读事件,使监听线程阻塞,
//可以将读写放在两个线程中,或者修改读写事件优先级
//修改优先级有两个方案:
//方案一为监听线程中WaitCommEvent()后,添加如下两条语句:
//if (WAIT_OBJECT_O == WaitForSingleObject(port->m_hWriteEvent, 0))
// ResetEvent(port->m_ov.hEvent);
//方案二为初始化时即修改,即下面两条语句:
m_hEventArray[1] = m_hWriteEvent;
m_hEventArray[2] = m_ov.hEvent;
// 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 = NULL;
}
m_szWriteBuffer = new BYTE[writebuffersize];
m_nPortNr = portnr;
m_nWriteBufferSize = writebuffersize;
m_dwCommEvents = dwCommEvents;
BOOL bResult = FALSE;
TCHAR *szPort = new TCHAR[MAX_PATH];
TCHAR *szBaud = new TCHAR[MAX_PATH];
/*
多个线程操作相同的数据时,一般是需要按顺序访问的,否则会引导数据错乱,
无法控制数据,变成随机变量。为解决这个问题,就需要引入互斥变量,让每
个线程都按顺序地访问变量。这样就需要使用EnterCriticalSection和
LeaveCriticalSection函数。
*/
// 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
_stprintf_s(szPort, MAX_PATH, _T("\\\\.\\COM%d"), portnr);///可以显示COM10以上端口//add by itas109 2014-01-09
// stop is index 0 = 1 1=1.5 2=2
int mystop;
int myparity;
switch (stopbits)
{
case 0:
mystop = ONESTOPBIT;
break;
case 1:
mystop = ONE5STOPBITS;
break;
case 2:
mystop = TWOSTOPBITS;
break;
//增加默认情况,因为stopbits=1.5时,SetCommState会报错。
//一般的电脑串口不支持1.5停止位,这个1.5停止位似乎用在红外传输上的。
//by itas109 20160506
default:
mystop = ONESTOPBIT;
break;
}
myparity = 0;
parity = _totupper(parity);
switch (parity)
{
case _T('N'):
myparity = 0;
break;
case _T('O'):
myparity = 1;
break;
case _T('E'):
myparity = 2;
break;
case _T('M'):
myparity = 3;
break;
case _T('S'):
myparity = 4;
break;
//增加默认情况。
//by itas109 20160506
default:
myparity = 0;
break;
}
_stprintf_s(szBaud, MAX_PATH, _T("baud=%d parity=%c data=%d stop=%d"), baud, parity, databits, mystop);
// 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 po