📄 serialport.cpp
字号:
/*
** 文件名称: CSerialPort.cpp
**
** 描述: 这个类能读,写和监视一个端口
** 当端口发生事件时他能给所有者发送消息
** 这个类创建一个线程来读和写,因此,主程序不会阻塞
**
** 作者: 李洋
*/
#include "stdafx.h"
#include "SerialPort.h"
#include <assert.h> // 当结果有错误时,输出此错误的诊断信息
//
// Constructor
// 构造函数
//
CSerialPort::CSerialPort()
{
m_hComm = NULL; // com口句柄
// 初始化overlapped结构成员变量为0
m_ov.Offset = 0;
m_ov.OffsetHigh = 0;
// 初始化事件句柄
m_ov.hEvent = NULL;
m_hWriteEvent = NULL;
m_hShutdownEvent = NULL;
m_szWriteBuffer = NULL;
m_bThreadAlive = FALSE; // TRUE 表示线程正在运行
}
//
// Delete dynamic memory
// 析构函数,删除动态存储器
//
CSerialPort::~CSerialPort()
{
do
{
SetEvent(m_hShutdownEvent);
} while (m_bThreadAlive); // TRUE 表示线程正在运行
TRACE("Thread ended\n"); // printf
delete [] m_szWriteBuffer;
}
//
//初始化端口,你能初始化 1 到4
//
BOOL CSerialPort::InitPort(CWnd* pPortOwner, // 父窗口(接收消息)
UINT portnr, // 串口号(1-4)
UINT baud, // 波特率
char parity, // 校验位
UINT databits, // 数据位
UINT stopbits, // 停止位
DWORD dwCommEvents, // 串口状态事件
UINT writebuffersize) // 输出缓冲区尺寸
{
assert(portnr > 0 && portnr < 9); // 当结果有错误时,输出此错误的诊断信息
assert(pPortOwner != NULL); // 当结果有错误时,输出此错误的诊断信息
//如果线程在运行,结束线程
//TRUE 表示线程正在运行
if (m_bThreadAlive)
{
do
{
SetEvent(m_hShutdownEvent); // 设置事件为关闭
} while (m_bThreadAlive);
TRACE("Thread ended\n");
}
// 创建事件
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);
// 初始化监视事件对象
m_hEventArray[0] = m_hShutdownEvent; // highest priority 最高优先权
m_hEventArray[1] = m_ov.hEvent; // 写事件还是读事件在 overlapped结构体中
m_hEventArray[2] = m_hWriteEvent;
// 初始化临界区
InitializeCriticalSection(&m_csCommunicationSync);
// 保存父窗口,串口号
m_pOwner = pPortOwner;
m_nPortNr = portnr;
// 创建输出缓冲区
if (m_szWriteBuffer != NULL)
delete [] m_szWriteBuffer;
m_szWriteBuffer = new char[writebuffersize];
m_nWriteBufferSize = writebuffersize; // m_nWriteBuffersize为 返回输出缓冲区的大小
// 保存串口状态事件
m_dwCommEvents = dwCommEvents; // 事件代码
BOOL bResult = FALSE;
char *szPort = new char[50]; // 动态分配空间
char *szBaud = new char[50]; // 动态分配空间
// 进入临界区
//try
//{
EnterCriticalSection(&m_csCommunicationSync);
//如果端口已打开,则先关闭
if (m_hComm != NULL)
{
CloseHandle(m_hComm);
m_hComm = NULL;
}
// 准备串口设置
sprintf(szPort, "COM%d", portnr);
sprintf(szBaud, "baud=%d parity=%c data=%d stop=%d", baud, parity, databits, stopbits);
// 打开串口
m_hComm = CreateFile(szPort, // 串口名称字符串,如:com1
GENERIC_READ | GENERIC_WRITE, // 读写
0, // 以独占方式打开
NULL, // 未设置安全属性
OPEN_EXISTING, // 串口设备必须设置OPEN_EXISTING
FILE_FLAG_OVERLAPPED, // 异步 I/O 方式
0); // 串口设备该参数必须设为0
if (m_hComm == INVALID_HANDLE_VALUE)
{
//端口没有找到
delete [] szPort;
delete [] szBaud;
return FALSE;
}
// 设置超时时间
m_CommTimeouts.ReadIntervalTimeout =MAXDWORD;
m_CommTimeouts.ReadTotalTimeoutMultiplier =1000;
m_CommTimeouts.ReadTotalTimeoutConstant = 1000;
m_CommTimeouts.WriteTotalTimeoutMultiplier = 19200;
m_CommTimeouts.WriteTotalTimeoutConstant = 1000;
// 配置串口
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;
// 此函数能清除输入输出缓冲区的所有字节,并能停止没有读或写完的操作
PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
// 离开临界区
LeaveCriticalSection(&m_csCommunicationSync);
TRACE("Initialisation for communicationport %d completed.\nUse Startmonitor to communicate.\n", portnr);
return TRUE;
//}
//catch(CException ce)
//{
// assert(portnr > 0 && portnr < 5); // 当结果有错误时,输出此错误的诊断信息
// AfxMessageBox("yun");
//return FALSE;
//}
}
//
// The CommThread Function.
// 线程函数
//
UINT CSerialPort::CommThread(LPVOID pParam)
{
// Cast the void pointer passed to the thread back to
// a pointer of CSerialPort class
CSerialPort *port = (CSerialPort*)pParam;
// TRUE 表示线程正在运行
port->m_bThreadAlive = TRUE;
// 其他的变量
DWORD BytesTransfered = 0;
DWORD Event = 0;
DWORD CommEvent = 0;
DWORD dwError = 0; // 错误信息
COMSTAT comstat; // COMSTAT 结构体类型
BOOL bResult = TRUE;
// 在启动时,清空串口缓冲区
if (port->m_hComm) // 检查端口是否是打开的
PurgeComm(port->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
// 开始无限循环。当线程alive时这个循环就一直进行
for (;;)
{
//检查线路状态,等待SetCommMask指定事件之一发生,
//事实上因为CSerialPort以异步方式操作串口,所以改调用将会立即返回
bResult = WaitCommEvent(port->m_hComm, &Event, &port->m_ov);
if (!bResult)
{
// 如果WaitCommEvent()返回FALSE,处理最后确定的错误
// 原因是..
switch (dwError = GetLastError())
{
case ERROR_IO_PENDING:
{
//串口没有数据时什么也不做正常返回
break;
}
case 87:
{
// 在 Windows NT下,该值将会返回,原因不明,但它是正常的返回值
break;
}
default:
{
// 其他返回指标时有错误发生
port->ProcessErrorMessage("WaitCommEvent()");
break;
}
}
}
else
{
//WaitCommEvent以后,要用ClearCommError清除事件的Flag,以便进行下一轮
//WaitCommEvent,同时这个API可以获得更祥细的事件信息
bResult = ClearCommError(port->m_hComm, &dwError, &comstat);
//确认串口中无字符则无须进入主监视过程,重新开始循环
if (comstat.cbInQue == 0)
continue;
} // end if bResult
// 主监视函数,该函数将阻塞本线程直至等待的某一事件发生
Event = WaitForMultipleObjects(3, port->m_hEventArray, FALSE, INFINITE);
switch (Event) //循环监视事件
{
case 0: // Shutdown事件,将关闭本线程(最高级)
{
port->m_bThreadAlive = FALSE;
AfxEndThread(100); //结束线程100为返回代码,此函数只能在线程内调用
break;
}
case 1: // 处理串口状态事件
{
GetCommMask(port->m_hComm, &CommEvent); // 获取指定设备的事件代码
if (CommEvent & EV_CTS)
::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_CTS_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);
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_RXCHAR)
// 从端口收到字符
ReceiveChar(port, comstat);
break;
}
case 2: // 处理写事件
{
// 向端口写字符
WriteChar(port);
break;
}
} // end switch
} // close forever loop
return 0;
}
//
// 开始监视线程
//
BOOL CSerialPort::StartMonitoring()
{
if (!(m_Thread = AfxBeginThread(CommThread, this)))
return FALSE;
TRACE("Thread started\n");
return TRUE;
}
//
// 重启线程
//
BOOL CSerialPort::RestartMonitoring()
{
TRACE("Thread resumed\n");
m_Thread->ResumeThread();
return TRUE;
}
//
// 挂起线程
//
BOOL CSerialPort::StopMonitoring()
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -