最全的基于c++的serialport.cpp与serialport.h类文件(解析版)
备注
参考原文件为:serialport.cpp与serialport.h类文件源代码
作者:c344935
链接:https://blog.csdn.net/c344935/article/details/81133308
工作内容
因为工作需要,要基于C++利用MFC编写西门子PLC的上位机控制软件,开始使用的是CnComm.h类进行编写,但是由于该类封装太好,难于学习,因此选择使用serialport类进行串口通讯编写。由于基础薄弱,因此在读该类时对所有函数,结构体功能进行了详细注释,并利用该类实现了串口通讯。现将本人笔记版上传,希望能够帮助到初学者。因为本人不是科班出身,因此对某些定义有失偏颇,希望大家多多指正。
Serialport.h文件
#pragma once
#ifndef __SERIALPORT_H__
#define __SERIALPORT_H__
#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 20 //最大能够访问的串口个数,不是串口号。
#define IsReceiveString 0 //采用何种方式接收:ReceiveString 1多字符串接收(对应响应函数为Wm_SerialPort_RXSTR),ReceiveString 0一个字符一个字符接收(对应响应函数为Wm_SerialPort_RXCHAR)
#include "stdio.h"
#include "stdafx.h"
#include<windows.h>
//自定义串口状态结构体,包含 portNr串口号,bytesRead读取到的字节数
struct serialPortInfo
{
UINT portNr;//串口号
DWORD bytesRead;//读取的字节数
};
class CSerialPort
{
public:
// contruction and destruction
int GetPortNO();
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);//判断线程是否挂起,hThread为要判断的线程
DWORD GetWriteBufferSize();//获取写缓冲大小
DWORD GetCommEvents();//获取事件
DCB GetDCB();//获取DCB
//写数据到串口
void WriteToPort(char* string, size_t n); //
void WriteToPort(BYTE* Buffer, size_t n); //
void ClosetoPort(); //
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); //
static void WriteChar(CSerialPort* port);
thread
HANDLE m_Thread; //监视线程句柄
BOOL m_bIsSuspened; //thread监视线程是否挂起
synchronisation objects///
CRITICAL_SECTION m_csCommunicationSync; //临界资源
BOOL m_bThreadAlive; //监视线程运行标志,1线程运行,0线程挂起
/// 定义handles///
HANDLE m_hShutdownEvent; //关闭事件响应 句柄
HANDLE m_hComm; //绑定的串口句柄
HANDLE m_hWriteEvent; //写事件句柄,如果利用SetEvent(m_hWriteEvent)调用,则表示开始串口写入。
HANDLE m_hEventArray[3]; //设定事件句柄数组,用于定义优先级
///定义结构体/
OVERLAPPED m_ov; //声明异步I/O结构体OVERLAPPED,之后利用m_ov调用底层异步处理程序
/*OVERLAPPED结构体详解
typedef struct _OVERLAPPED {
DWORD Internal; //预留给操作系统使用。它指定一个独立于系统的状态,当GetOverlappedResult函数返回时没有设置扩展错误信息ERROR_IO_PENDING时有效。
DWORD InternalHigh; //预留给操作系统使用。它指定长度的数据转移,当GetOverlappedResult函数返回TRUE时有效。
DWORD Offset; //该文件的位置是从文件起始处的字节偏移量。调用进程设置这个成员之前调用ReadFile或WriteFile函数。当读取或写入命名管道和通信设备时这个成员被忽略设为零。
DWORD OffsetHigh; //指定文件传送的字节偏移量的高位字。当读取或写入命名管道和通信设备时这个成员被忽略设为零。
HANDLE hEvent; //在转移完成时该事件设置为有信号状态。调用进程集这个成员在调用ReadFile、 WriteFile、TransactNamedPipe、 ConnectNamedPipe函数之前。
} OVERLAPPED*/
COMMTIMEOUTS m_SerialPortTimeouts; //超时设置
/*COMMTIMEOUTS 结构体详解
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout; //两字符之间最大的延时,当读取串口数据时,一旦两个字符传输的时间差超过该时间,读取函数将返回现有的数据。
DWORD ReadTotalTimeoutMultiplier; //读取每字符间的超时。 指定以毫秒为单位的累积值。用于计算读操作时的超时总数。对于每次读操作,该值与所要读的字节数相乘。
DWORD ReadTotalTimeoutConstant; //一次读取串口数据的固定超时。
DWORD WriteTotalTimeoutMultiplier; //写入每字符间的超时
DWORD WriteTotalTimeoutConstant; //一次写入串口数据的固定超时。
} COMMTIMEOUTS, *LPCOMMTIMEOUTS;*/
DCB m_dcb; //设备控制块。串口通信基本设置结构体
/*DCB结构体定义
typedef struct _DCB {
DWORD DCBlength; //DCB结构大小,即sizeof(DCB),在调用SetCommState来更新DCB前必须作设置
DWORD BaudRate; //指定当前采用的波特率,应与所连接的通讯设备相匹配
DWORD fBinary : 1; //指定是否允许二进制模式。Win32 API不支持非二进制模式传输,应设置为true
DWORD fParity : 1; //指定奇偶校验是否允许,在为true时具体采用何种校验看Parity 设置
DWORD fOutxCtsFlow : 1; //是否监控CTS(clear-to-send)信号来做输出流控。当设置为true时: 若CTS为低电平,则数据发送将被挂起,直至CTS变为高。
DWORD fOutxDsrFlow : 1; //
DWORD fDtrControl : 2; //
DWORD fDsrSensitivity : 1; //
DWORD fTXContinueOnXoff : 1; //
DWORD fOutX : 1; // XON/XOFF 流量控制在发送时是否可用。
DWORD fInX : 1; //XON/XOFF 流量控制在接收时是否可用。
DWORD fErrorChar : 1; //该值为TRUE,则用ErrorChar指定的字符代替奇偶校验错误的接收字符
DWORD fNull : 1; //为TRUE时,接收时自动去掉空(0值)字节
DWORD fRtsControl : 2; //
DWORD fAbortOnError : 1; //读写操作发生错误时是否取消操作。若设置为true,则当发生读写错误时,将取消所有读写操作
DWORD fDummy2 : 17; //
WORD wReserved; //未启用,必须设置为0
WORD XonLim; //在XON字符发送前接收缓冲区内可允许的最小字节数
WORD XoffLim; //
BYTE ByteSize; //
BYTE Parity; //指定端口数据传输的校验方法。
BYTE StopBits; //指定端口当前使用的停止位数,可取值
char XonChar; //指定XON字符
char XoffChar; //指定XOFF字符
char ErrorChar; //指定ErrorChar字符(代替接收到的奇偶校验发生错误时的字节)
char EofChar; //指定用于标示数据结束的字符
char EvtChar; //
WORD wReserved1; //保留,未启用
} DCB;*/
// owner window//
HWND m_pOwner; //串口绑定的窗口句柄
// misc
UINT m_nPortNr; //串口号
PBYTE m_szWriteBuffer; //写缓冲区指针。
DWORD m_dwCommEvents; 定义串口事件
DWORD m_nWriteBufferSize; //写缓冲大小
size_t m_nWriteSize; //写入字节数
};
#endif __SERIALPORT_H__
SerialPort.cpp
#include "stdafx.h"
#include "SerialPort.h"
#include <assert.h>
int m_nComArray[20];//存放活跃的串口号
int CSerialPort::GetPortNO()
{
return m_nPortNr;
}
//定义结构体
CSerialPort::CSerialPort() //初始化设置
{
m_hComm = NULL; //初始化串口句柄
// initialize overlapped structure members to zero PS:overlapped为串口通信的重叠模式,在createfile内为 FILE_FLAG_OVERLAPPED,区别分于同步模式的NULL
///初始化异步结构体
m_ov.Offset = 0; //初始化传输文件的位置
m_ov.OffsetHigh = 0; //文件起始处的字节偏移量的高字位,PS:初始化指定传输文件的高度
// create events
m_ov.hEvent = NULL; //初始化 传送完成时的事件响应函数,一般在Readfile(),Writefile()之前。
m_hWriteEvent = NULL; //初始化写串口事件
m_hShutdownEvent = NULL;
m_szWriteBuffer = NULL;
m_bThreadAlive = FALSE;
m_nWriteSize = 1;
m_bIsSuspened = FALSE;
}
//SerialPort析构函数用于Delete dynamic memory
CSerialPort::~CSerialPort()
{
MSG message; //传递MSG结构体,建立消息对象,类似于线程消息队列
/*MSG结构体详解
背景:windows是通过监视各种输入设备,把发生的事件转化为消息的,并将消息保存在消息队列中。最后当前的应用程序从自己的消息队列中按顺序检索消息,并把每一个消息发送到所对应的窗口消息处理函数中去
typedef struct tagMSG{
HWND hwnd; //消息所指向的窗口的句柄。
UINT message; //消息标识符
WPARAM wparam; //32位的“消息参数”,该参数的含义和取值取决于具体的消息
LPARAM lparam; //另外一个32位的“消息参数”,该参数的含义和取值同样取决于具体的消息
DWORD time; //消息进入消息队列的时间
POINT pt; //消息进入消息中的鼠标指针的位置坐标。
}MSG,*PMSG;
*/
//增加线程挂起判断,解决由于线程挂起导致串口关闭死锁的问题
if (IsThreadSuspend(m_Thread))
{
ResumeThread(m_Thread);
}
//若串口句柄无效,释放句柄
if (m_hComm == INVALID_HANDLE_VALUE)
{
CloseHandle(m_hComm); //返回值Long型,!0 表示成功,零表示失败。会设置GetLastError
m_hComm = NULL;
return;
}
do //do{} while{}循环
{
SetEvent(m_hShutdownEvent); //将 关闭事件 变为有信号状态,防止死锁
if (::PeekMessage(&message, m_pOwner, 0, 0, PM_REMOVE)) //PeekMessage用于读响应消息(消息队列地址的指针,窗口指向:该窗口消息被检索,消息下界,消息上界:NULL>检索所有消息,检索后如何处理:PM_REMOVE>处理后清除消息PM_NOREMOVE>处理后不清除消息)
{
::TranslateMessage(&message); //将串口消息转换为字符消息,并存到MSG消息对象中(利用指针形式传递)。 //
//若消息被转换,则返回WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, 或 WM_SYSKEYUP。若未转换,返回0.
::DispatchMessage(&message); //该函数调度含有消息的MSG指针指向的消息传递给窗口程序
}
} while (m_bThreadAlive); //如果监视线程状态位为打开,则执行do{}内容
// if the port is still opened: close it
if (m_hComm != NULL)
{
CloseHandle(m_hComm);
m_hComm = NULL;
}
// Close Handles
//关闭句柄
if (m_hShutdownEvent != NULL) //关闭ShutdownEvent事件
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) //若写缓冲区不为NULL,则清空
{
delete[] m_szWriteBuffer; //释放写缓冲区
m_szWriteBuffer = NULL;
}
}
//
// Initialize the port. This can be port 1 to MaxSerialPortNum.
//初始化串口。只能是1到MaxSerialPortNum
//初始化串口函数
BOOL CSerialPort::InitPort(HWND pPortOwner, // 绑定串口响应消息MSG对应窗口的句柄,the owner (CWnd) of the port (receives message)
UINT portnr, // 串口号 (1到MaxSerialPortNum)
UINT baud, // 波特率
TCHAR parity, // 校验位,不区分大小写 parity n=none,e=even,o=odd,m=mark,s=space
UINT databits, // 数据位 databits 5,6,7,8
UINT stopbits, // 停止位 stopbits 1,1.5,2
DWORD dwCommEvents, //串口响应事件,有EV_RXCHAR,EV_CTS, EV_DSR ,EV_RING等等。
// EV_RXCHAR:设备返回数据时,第一个字符达到缓冲区时触发
UINT writebuffersize, // size of the writebuffer
DWORD ReadIntervalTimeout, //读间隔超时
DWORD ReadTotalTimeoutMultiplier,//读时间系数
DWORD ReadTotalTimeoutConstant,//读时间常量
DWORD WriteTotalTimeoutMultiplier,//写时间系数
DWORD WriteTotalTimeoutConstant)//写时间常量
{
assert(portnr > 0 && portnr < MaxSerialPortNum); //断言:串口号必须是大于0小于MaxSerialPortNum
assert(pPortOwner != NULL); //断言:必须要为串口响应消息绑定一个窗口句柄
MSG message; //见前文
//增加线程挂起判断,解决由于线程挂起导致串口关闭死锁的问题
if (IsThreadSuspend(m_Thread)) //若挂起
{
ResumeThread(m_Thread); //恢复线程
}
//若线程运行,则挂起
// if the thread is alive: Kill
if (m_bThreadAlive)
{
do
{
SetEvent(m_hShutdownEvent); //将 关闭事件 变为有信号状态
//防止死锁,同上
if (::PeekMessage(&message, m_pOwner, 0, 0, PM_REMOVE))
{
::TranslateMessage(&message);
::DispatchMessage(&message);
}
} while (m_bThreadAlive);
//TRACE("Thread ended\n");
Sleep(50);//此处的延时很重要,因为如果串口开着,发送关闭指令到彻底关闭需要一定的时间,这个延时应该跟电脑的性能相关
}
// 重置 events
if (m_ov.hEvent != NULL)
ResetEvent(m_ov.hEvent); //将异步通信Event变为无信号状态
else //change by COMTOOL
m_ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //CreateEvent(安全属性:NULL,复位方式:TRUE>用ResetEvent复位FALSE被线程释放后自动复位,初始状态:TRUE>有信号FALSE》无信号,指定名字:NULL>无名事件对象)
if (m_hWriteEvent != NULL)
ResetEvent(m_hWriteEvent); //清除WriteEvent
else //change by COMTOOL
m_hWriteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (m_hShutdownEvent != NULL)
ResetEvent(m_hShutdownEvent); //清除ShutdownEvent
else //change by COMTOOL
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))