65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.62/5 (59投票s)

2002年10月1日

9分钟阅读

viewsIcon

514677

downloadIcon

17531

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

引言

对于串行通信和 VC++ 世界的新手来说,串行数据传输似乎有点困难。很久以前,我在 codeguru.com 上搜索过一些关于串行数据传输的帮助,并获得了一些宝贵的信息。从那时起,开发一个用于实现串行数据传输的简单类就成了我的梦想。

在串行通信领域获得了七个月的实践经验后,我开发了一个使用 WinAPI 函数实现串行传输的简单类。在深入了解这个类的细节之前,有必要了解串行数据传输的基础知识。

串行数据传输基础知识

在串行数据传输中,数据以串行格式传输,其中要传输的字节的最低有效位 (LSB) 在数据位中首先移出。串行传输的通用格式是 起始位 + 数据位 + 奇偶校验位(可选) + 停止位

奇偶校验位是可选的。它用于通信中的错误检查。您可以通过软件修改启用或禁用奇偶校验。此外,您可以通过软件指定要使用的奇偶校验类型,可以是“偶数”或“奇数”。

通过 PC 串行端口发送和接收数据需要执行的各种步骤如下所示:-

  1. 打开通信端口
  2. 通过设置波特率、奇偶校验、数据位数等来配置通信端口。
  3. 设置通信超时。
  4. 向端口写入数据。
  5. 从端口读取数据。
  6. 关闭端口。

打开串行端口

CreateFile() 函数打开一个通信端口。有两种方法可以调用 CreateFile() 来打开端口 - OVERLAPPEDNON-OVERLAPPED。您可以为 OVERLAPPED IO 操作和 NON-OVERLAPPED IO 操作打开一个通信端口。CSerialCom 类是为 NON-OVERLAPPED IO 操作编写的。有关 OVERLAPPEDNON-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.cppConfigurePort() 实现中负责流控制的 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 成员与请求的字节数之积中。ReadTotalTimeoutMultiplierReadTotalTimeoutConstant 成员都为零表示读取操作不使用总超时。

ReadTotalTimeoutMultiplier
指定用于计算读取操作总超时期限的乘数(以毫秒为单位)。对于每个读取操作,此值乘以请求读取的字节数。

WriteTotalTimeoutConstant
指定用于计算写入操作总超时期限的常量(以毫秒为单位)。对于每个写入操作,此值将添加到 WriteTotalTimeoutMultiplier 成员与要写入的字节数之积中。

WriteTotalTimeoutMultiplier
指定用于计算写入操作总超时期限的乘数(以毫秒为单位)。对于每个写入操作,此值乘以要写入的字节数。

WriteTotalTimeoutMultiplierWriteTotalTimeoutConstant 成员都为零表示写入操作不使用总超时。

例如,如果您的设备传输的字符块在每个字符之间最大超时值为 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

  1. 复制 SerialCom.hSerialCom.cpp 文件并粘贴到您的项目目录中。
  2. 在您的 VC++ IDE 中,将文件添加到您的项目中
  3. 在对话框的头文件中添加 #include "SerialCom.h"
  4. 在对话框的头文件中创建 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 库。

© . All rights reserved.