CSerialCom - 一个用于在 Win-9X/2000 中实现串行通信的简单类






4.62/5 (59投票s)
2002年10月1日
9分钟阅读

514677

17531
CSerialCom - 一个用于在 Win-9X/2000 中实现串行通信的简单类。
引言
对于串行通信和 VC++ 世界的新手来说,串行数据传输似乎有点困难。很久以前,我在 codeguru.com 上搜索过一些关于串行数据传输的帮助,并获得了一些宝贵的信息。从那时起,开发一个用于实现串行数据传输的简单类就成了我的梦想。
在串行通信领域获得了七个月的实践经验后,我开发了一个使用 WinAPI 函数实现串行传输的简单类。在深入了解这个类的细节之前,有必要了解串行数据传输的基础知识。
串行数据传输基础知识
在串行数据传输中,数据以串行格式传输,其中要传输的字节的最低有效位 (LSB) 在数据位中首先移出。串行传输的通用格式是 起始位 + 数据位 + 奇偶校验位(可选) + 停止位。
奇偶校验位是可选的。它用于通信中的错误检查。您可以通过软件修改启用或禁用奇偶校验。此外,您可以通过软件指定要使用的奇偶校验类型,可以是“偶数”或“奇数”。
通过 PC 串行端口发送和接收数据需要执行的各种步骤如下所示:-
- 打开通信端口
- 通过设置波特率、奇偶校验、数据位数等来配置通信端口。
- 设置通信超时。
- 向端口写入数据。
- 从端口读取数据。
- 关闭端口。
打开串行端口
CreateFile()
函数打开一个通信端口。有两种方法可以调用 CreateFile()
来打开端口 - OVERLAPPED
和 NON-OVERLAPPED
。您可以为 OVERLAPPED
IO 操作和 NON-OVERLAPPED
IO 操作打开一个通信端口。CSerialCom
类是为 NON-OVERLAPPED
IO 操作编写的。有关 OVERLAPPED
和 NON-OVERLAPPED
IO 的更多详细信息,请参阅 MSDN 文档。
配置串行端口
串行通信编程中最关键的阶段是使用 DCB
结构配置端口设置。错误地初始化 DCB
结构是一个常见问题。当串行通信函数未产生预期结果时,DCB
结构可能存在错误。调用 CreateFile()
函数会以默认端口设置打开串行端口。通常,应用程序需要更改默认设置。您必须根据外部设备的要求,通过调用适当的 WinAPI 函数来设置通信波特率、奇偶校验功能、停止位数等。
配置超时
应用程序每次打开通信端口时,都必须使用 COMMTIMEOUTS
结构设置通信超时。如果未配置此结构,端口将使用驱动程序提供的默认超时,或来自先前通信应用程序的超时。如果应用程序假设特定的超时设置而实际设置不同,则读/写操作可能永远不会完成或完成得太频繁。您必须通过调用适当的 WinAPI 函数来配置读写超时。
写入串行端口
WriteFile()
函数通过串行连接将数据传输到另一个设备。在调用此函数之前,应用程序必须打开并配置一个串行端口。
从串行端口读取
应用程序调用 ReadFile()
函数以从串行连接另一端的设备接收数据。
关闭串行端口
串行传输后必须关闭通信端口,以使该端口可用于使用此资源的其他应用程序。只要您正在使用一个端口(即端口处于打开状态),其他线程或应用程序将无法访问该端口,直到您在 NON-OVERLAPPED
IO 操作中关闭该端口的句柄。调用 CloseHandle()
函数来关闭串行端口。CloseHandle()
有一个参数,它是打开端口的 CreateFile()
调用返回的句柄。
CSerialCom 类
CSerialCom
类使用六个成员函数来实现上述功能。它们是
BOOL CSerialCom::OpenPort(CString portname) { portname= "//./" + portname; hComm = CreateFile(portname, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0); if(hComm==INVALID_HANDLE_VALUE){ return false;} else return true; }
OpenPort()
成员函数打开一个通信端口进行数据传输。传递给此函数的参数是一个包含端口名称的字符串。例如,COM1 为“com1”,COM2 为“com2”等。如果函数成功,返回值为 true,否则为 false。
BOOL CSerialCom::ConfigurePort(DWORD BaudRate, BYTE ByteSize, DWORD fParity, BYTE Parity, BYTE StopBits) { if((m_bPortReady = GetCommState(hComm, &m_dcb))==0) { MessageBox("GetCommState Error","Error",MB_OK+MB_ICONERROR); CloseHandle(hComm); return false; } m_dcb.BaudRate =BaudRate; m_dcb.ByteSize = ByteSize; m_dcb.Parity =Parity ; m_dcb.StopBits =StopBits; m_dcb.fBinary=TRUE; m_dcb.fDsrSensitivity=false; m_dcb.fParity=fParity; m_dcb.fOutX=false; m_dcb.fInX=false; m_dcb.fNull=false; m_dcb.fAbortOnError=TRUE; m_dcb.fOutxCtsFlow=FALSE; m_dcb.fOutxDsrFlow=false; m_dcb.fDtrControl=DTR_CONTROL_DISABLE; m_dcb.fDsrSensitivity=false; m_dcb.fRtsControl=RTS_CONTROL_DISABLE; m_dcb.fOutxCtsFlow=false; m_dcb.fOutxCtsFlow=false; m_bPortReady = SetCommState(hComm, &m_dcb); if(m_bPortReady ==0) { MessageBox("SetCommState Error","Error",MB_OK+MB_ICONERROR); CloseHandle(hComm); return false; } return true; }
ConfigurePort()
成员函数配置一个通信端口进行数据传输。传递给此函数的参数如下。
DWORD 波特率
它表示外部设备支持的通信波特率。例如,您可以将此参数设置为 9600 或 CBR_9600
,表示波特率为 9600。PC 支持的可用标准波特率为 CBR_110、CBR_300、CBR_600、CBR_1200、CBR_2400、CBR_4800、CBR_9600、CBR_14400、CBR_19200、CBR_38400、CBR_56000、CBR_57600、CBR_115200、CBR_128000、CBR_256000
。
BYTE ByteSize
这表示传输和接收字节中的位数。标准值为 8 或 4。
DWORD fParity
指定是否启用奇偶校验。如果此参数为 TRUE
,则执行奇偶校验并报告错误。如果为 FALSE
,则不执行奇偶校验。
BYTE Parity
指定要使用的奇偶校验方案。此成员可以是以下值之一
偶数奇偶校验
标记奇偶校验
无奇偶校验
奇数奇偶校验
空格奇偶校验
BYTE StopBits
指定要使用的停止位数。此成员可以是以下值之一
一个停止位
一个半停止位
两个停止位
注意ConfigurePort()
函数的编写基于以下假设:通信流控制完全基于外部设备支持的协议。它在不检查 CTS/RTS 和 Xon/Xoff 硬件流控制的情况下传输和接收数据。您可以通过修改 SerialCom.cpp 中 ConfigurePort()
实现中负责流控制的 DCB
成员的值来根据您的要求进行修改。
ConfigurePort(CBR_9600, 8, true, EVENPARITY , ONESTOPBIT )
如果函数成功,返回值为 true,否则为 false。
BOOL CSerialCom::SetCommunicationTimeouts(DWORD ReadIntervalTimeout, DWORD ReadTotalTimeoutMultiplier, DWORD ReadTotalTimeoutConstant, DWORD WriteTotalTimeoutMultiplier, DWORD WriteTotalTimeoutConstant) { if((m_bPortReady = GetCommTimeouts (hComm, &m_CommTimeouts))==0) return false; m_CommTimeouts.ReadIntervalTimeout =ReadIntervalTimeout; m_CommTimeouts.ReadTotalTimeoutConstant =ReadTotalTimeoutConstant; m_CommTimeouts.ReadTotalTimeoutMultiplier =ReadTotalTimeoutMultiplier; m_CommTimeouts.WriteTotalTimeoutConstant = WriteTotalTimeoutConstant; m_CommTimeouts.WriteTotalTimeoutMultiplier =WriteTotalTimeoutMultiplier; m_bPortReady = SetCommTimeouts (hComm, &m_CommTimeouts); if(m_bPortReady ==0) { MessageBox("StCommTimeouts function failed", "Com Port Error",MB_OK+MB_ICONERROR); CloseHandle(hComm); return false; } return true; }
SetCommunicationTimeouts()
成员函数设置数据传输的写入和读取超时。传递给此函数的参数如下。
DWORD ReadIntervalTimeout
指定通信线路上两个字符到达之间允许经过的最大时间(以毫秒为单位)。在 ReadFile()
操作期间,时间段从接收到第一个字符时开始。如果任何两个字符到达之间的时间间隔超过此量,则 ReadFile
操作完成并返回任何缓冲数据。值为零表示不使用间隔超时。值为 MAXDWORD
,结合 ReadTotalTimeout
常量和 ReadTotalTimeoutMultiplier
成员的零值,指定读取操作应立即返回已接收到的字符,即使没有接收到任何字符。
ReadTotalTimeoutConstant
指定用于计算读取操作总超时期限的常量(以毫秒为单位)。对于每个读取操作,此值将添加到 ReadTotalTimeoutMultiplier
成员与请求的字节数之积中。ReadTotalTimeoutMultiplier
和 ReadTotalTimeoutConstant
成员都为零表示读取操作不使用总超时。
ReadTotalTimeoutMultiplier
指定用于计算读取操作总超时期限的乘数(以毫秒为单位)。对于每个读取操作,此值乘以请求读取的字节数。
WriteTotalTimeoutConstant
指定用于计算写入操作总超时期限的常量(以毫秒为单位)。对于每个写入操作,此值将添加到 WriteTotalTimeoutMultiplier
成员与要写入的字节数之积中。
WriteTotalTimeoutMultiplier
指定用于计算写入操作总超时期限的乘数(以毫秒为单位)。对于每个写入操作,此值乘以要写入的字节数。
WriteTotalTimeoutMultiplier
和 WriteTotalTimeoutConstant
成员都为零表示写入操作不使用总超时。
例如,如果您的设备传输的字符块在每个字符之间最大超时值为 500 毫秒,您可以将超时函数设置为 SetCommunicationTimeouts(0,500,0,0,0);
。如果函数成功,返回值为 true,否则为 false。
BOOL CSerialCom::WriteByte(BYTE bybyte) { iBytesWritten=0; if(WriteFile(hComm,&bybyte,1,&iBytesWritten,NULL)==0) return false; else return true; }
WriteByte()
成员函数将数据字节写入通信端口。传递给此函数的参数是要传输的字节。您可以在循环中重复调用此函数,并将要写入的数据放置在数组中。每次发送字符时,递增数组索引并调用 WriteByte()
,直到所有数据字节都传输完毕。
如果函数成功,返回值为 true,否则为 false。
BOOL CSerialCom::ReadByte(BYTE &resp) { BYTE rx; resp=0; DWORD dwBytesTransferred=0; if (ReadFile (hComm, &rx, 1, &dwBytesTransferred, 0)) { if (dwBytesTransferred == 1) { resp=rx; return true; } } return false; }
ReadByte()
成员函数从通信端口读取数据字节。传递给此函数的参数是存储接收到的数据字节的变量的地址。您可以在循环中重复调用此函数,并将接收到的数据移动到数组中。每次接收字符时,递增数组索引并调用 ReadByte()
,直到所有数据字节都接收完毕。如果您确切知道外部设备的响应字节数,您可以在循环中调用 ReadByte()
函数,直到接收到所有字符或发生超时。有时您可能无法预测外部设备的响应字节数。在这种情况下,重复调用 ReadByte
文件,直到发生超时,如果之前接收到的字符是您的协议格式中表示传输结束的字符,则通信过程成功完成。例如,对于遵循 3964 协议的设备,传输结束是“ETX”字符。因此,请根据您的外部设备支持的协议正确使用 ReadByte()
函数。
如果 ReadByte()
成功,返回值为 true,并且接收到的字节将存储在 ReadByte()
参数地址指向的位置。如果发生超时,返回值为 false。
void CSerialCom::ClosePort() { CloseHandle(hComm); return; }
ClosePort()
成员函数关闭已经处于打开状态的通信端口。
如何使用“CSerialCom”类
请按照以下步骤使用 CSerialCom
类
- 复制 SerialCom.h 和 SerialCom.cpp 文件并粘贴到您的项目目录中。
- 在您的 VC++ IDE 中,将文件添加到您的项目中
- 在对话框的头文件中添加
#include "SerialCom.h"
行 - 在对话框的头文件中创建
CSerialCom
类的一个实例。
您现在可以在需要与外部设备通信时调用 CSerialCom
的成员函数,如下所示。
在您的对话框的 .cpp 文件中
// Open Communication Port. Please check functions return value to ensure whether // Port opened successfully. port.OpenPort( ); // Configure Port for Communication. Please check functions return value to // ensure whether Port is configured successfully. port.ConfigurePort( ); // Set communication time outs. Please check functions return // value to ensure whether communication time outs configured // successfully. port.SetCommunicationTimeouts( ); // call this function in a loop till all bytes are written. Please check // functions return value to ensure whether Write operation completed // successfully. port.WriteByte(); // call this function in a loop till all bytes are received. Please check // functions return value to ensure whether Read operation completed // successfully or a time out occurred. port.ReadByte( ); // Call this function to close the handle to the port. // Process the received Data port.ClosePort();
注意
此代码已通过 RS-232 连接器测试,其 TXD 引脚和 RXD 引脚短接,连接到“com1”(例如,对于情况 1:要读取的数据字节数是预定义的或常量(在此情况下为 1)),以及一个支持 3964 协议的智能卡读卡器,波特率为 9600(例如,对于情况 2:要从外部设备读取的数据字节数未知,并且通过超时检测数据传输结束,其中接收到的最后一个字符是该协议中的传输结束字符(3964 协议的“ETX”字符))在 Win-98/2000 中,并且发现其工作正常。
本文中的一些解释来自 MSDN 库。