Windows平台对串口进行操作的C++例程

很早以前就写好的代码,一时忘记要发了现在先补发下。

Windows串口通信差不多是对嵌入式系统开发的重要基础环节,也可以方便多机组网,所以在此请状况下,我就结合网上教程撸了一串代码供大家参考

C++真尼玛好玩!

一、代码核心函数

1.CreateFile()

HANDLE WINAPI CreateFile(
_In_ LPCTSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
);
如执行成功,则返回文件句柄。INVALID_HANDLE_VALUE表示出错,会设置GetLastError。即使函数成功,但若文件存在,且指定了CREATE_ALWAYS 或 OPEN_ALWAYS,GetLastError也会设为ERROR_ALREADY_EXISTS
参数类型及说明
函数声明HANDLE CreateFile(LPCTSTR lpFileName, //普通文件名或者设备文件名
DWORD dwDesiredAccess, //访问模式(写/读)
DWORD dwShareMode, //共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes, //指向安全属性的指针
DWORD dwCreationDisposition, //如何创建
DWORD dwFlagsAndAttributes, //文件属性
HANDLE hTemplateFile //用于复制文件句柄
);

参数说明:

lpFileName String要打开的文件的名或设备名。这个字符串的最大长度在ANSI版本中为MAX_PATH,在unicode版本中为32767。
dwDesiredAccess指定类型的访问对象。如果为 GENERIC_READ 表示允许对设备进行读访问;如果为 GENERIC_WRITE 表示允许对设备进行写访问(可组合使用);如果为零,表示只允许获取与一个设备有关的信息 。
另外,还可以指定下面的控制标志:
标准控制权限(16-23位掩码):
DELETE 删除对象的权限。
READ_CONTROL 从对象的安全描述符中读取信息的权限,但不包括SACL(系统访问控制列表)中的信息。
WRITE_DAC 修改对象安全描述符中的DACL(随机访问控制列表)的权限
WRITE_OWNER 修改对象安全描述符中的属主的权限
SYNCHRONIZE 同步化使用对象的权限,即可以创建一个线程等待信号量释放(但有些对象不支持这个权限)。
STANDARD_RIGHTS_REQUIRED 等价于前面四种权限的总合(通常这四种是必须具有的权限)。
STANDARD_RIGHTS_READ 一般等价于READ_CONTROL
STANDARD_RIGHTS_WRITE 一般等价于READ_CONTROL
STANDARD_RIGHTS_EXECUTE 一般等价于READ_CONTROL
STANDARD_RIGHTS_ALL 等价于前面五种权限的总合。
特殊控制权限(0-15位掩码):
SPECIFIC_RIGHTS_ALL
ACCESS_SYSTEM_SECURITY
MAXIMUM_ALLOWED
GENERIC_READ
GENERIC_WRITE
GENERIC_EXECUTE
GENERIC_ALL
注:实质上是通过ACCESS_MASK结构体的一个双字值来设置标准权限、特殊权限和一般权限的。
dwShareModeLong, 如果是零表示不共享; 如果是FILE_SHARE_DELETE表示随后打开操作对象会成功只要删除访问请求;如果是FILE_SHARE_READ随后打开操作对象会成功只有请求读访问;如果是FILE_SHARE_WRITE 随后打开操作对象会成功只有请求写访问。
lpSecurityAttributesSECURITY_ATTRIBUTES, 指向一个SECURITY_ATTRIBUTES结构的指针,定义了文件的安全特性(如果操作系统支持的话)
dwCreationDispositionLong,下述常数之一:
CREATE_NEW 创建文件;如文件存在则会出错
CREATE_ALWAYS 创建文件,会改写前一个文件
OPEN_EXISTING 文件必须已经存在。由设备提出要求
OPEN_ALWAYS 如文件不存在则创建它
TRUNCATE_EXISTING 将现有文件缩短为零长度
dwFlagsAndAttributesLong, 一个或多个下述常数
FILE_ATTRIBUTE_ARCHIVE 标记归档属性
FILE_ATTRIBUTE_COMPRESSED 将文件标记为已压缩,或者标记为文件在目录中的默认压缩方式
FILE_ATTRIBUTE_NORMAL 默认属性
FILE_ATTRIBUTE_HIDDEN 隐藏文件或目录
FILE_ATTRIBUTE_READONLY 文件为只读
FILE_ATTRIBUTE_SYSTEM 文件为系统文件
FILE_FLAG_WRITE_THROUGH 操作系统不得推迟对文件的写操作
FILE_FLAG_OVERLAPPED 允许对文件进行重叠操作
FILE_FLAG_NO_BUFFERING 禁止对文件进行缓冲处理。文件只能写入磁盘卷的扇区块
FILE_FLAG_RANDOM_ACCESS 针对随机访问对文件缓冲进行优化
FILE_FLAG_SEQUENTIAL_SCAN 针对连续访问对文件缓冲进行优化
FILE_FLAG_DELETE_ON_CLOSE 关闭了上一次打开的句柄后,将文件删除。特别适合临时文件
也可在Windows NT下组合使用下述常数标记:
SECURITY_ANONYMOUS, SECURITY_IDENTIFICATION, SECURITY_IMPERSONATION, SECURITY_DELEGATION, SECURITY_CONTEXT_TRACKING, SECURITY_EFFECTIVE_ONLY
hTemplateFile,hTemplateFile为一个文件或设备句柄,表示按这个参数给出的句柄为模板创建文件(就是将该句柄文件拷贝到lpFileName指定的路径,然后再打开)。它将指定该文件的属性扩展到新创建的文件上面,这个参数可用于将某个新文件的属性设置成与现有文件一样,并且这样会忽略dwAttrsAndFlags。通常这个参数设置为NULL,为空表示不使用模板,一般为空。
返回值
如执行成功,则返回文件句柄。
INVALID_HANDLE_VALUE表示出错,会设置GetLastError。即使函数成功,但若文件存在,且指定了CREATE_ALWAYS 或 OPEN_ALWAYS,GetLastError也会设为ERROR_ALREADY_EXISTS。

2. SetupComm

该函数初始化一个指定的通信设备的通信参数。
BOOL SetupComm(
HANDLE hFile,
DWORD dwInQueue,
DWORD dwOutQueue
);

参数说明

hFile 通讯设备句柄。
CreateFile函数返回此句柄。
dwInQueue 指定推荐的大小,以字节为单位,对设备的内部输入缓冲区。
dwOutQueue 指定推荐的大小,以字节为单位,对设备的内部输出缓冲区。
返回值非零表示成功。零表示失败。 要获得更多错误信息,调用GetLastError函数

3.GetCommTimeouts

BOOL GetCommTimeouts(
HANDLE hFile,
LPCOMMTIMEOUTS lpCommTimeouts
);
typedef struct COMMTIMEOUTS {
DWORD ReadIntervalTimeout; // 读间隔超时
DWORD ReadTotalTimeoutMultiplier; // 读时间系数
DWORD ReadTotalTimeoutConstant; // 读时间常量
DWORD WriteTotalTimeoutMultiplier; // 写时间系数
DWORD WriteTotalTimeoutConstant; // 写时间常量
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
COMMTIMEOUTS结构的成员都以毫秒为单位。
ReadIntervalTimeout:两字符之间最大的延时,当读取串口数据时,一旦两个字符传输的时间差超过该时间,读取函数将返回现有的数据。设置为0表示该参数不起作用。指定时间最大值(毫秒),允许接收的2个字节间有时间差。也就 是说,刚接收了一个字节后,等了ReadIntervalTimeout时间后还没有新的字节到达,就 认为本次读串口操作结束(后面的字节等下一次读取操作来处理)。即使你想读8个字节,但读第2个字节后,过了ReadIntervalTimeout时间后,第3个字节还没到。实际上就只读了2个字节。
ReadTotalTimeoutMultiplier:指定比例因子(毫秒),实际上是设置读取一个字节和等待下一个字节所需的时间,这样总的超时时间为读取的字节数乘以该值,同样一次读取操作到达这个时间后,也认为本次读操作己经结束。
ReadTotalTimeoutConstant:一次读取串口数据的固定超时。所以在一次读取串口的操作中,其超时为ReadTotalTimeoutMultiplier乘以读取的字节数再加上 ReadTotalTimeoutConstant。将ReadIntervalTimeout设置为MAXDWORD,并将ReadTotalTimeoutMultiplier 和ReadTotalTimeoutConstant设置为0,表示读取操作将立即返回存放在输入缓冲区的字符。可以理解为一个修正时间,实际上就是按ReadTotalTimeoutMultiplier计算出的超时时间再加上该时间才作为整个超时时间。
WriteTotalTimeoutMultiplier:写入每字符间的超时。
WriteTotalTimeoutConstant:一次写入串口数据的固定超时。所以在一次写入串口的操作中,其超时为WriteTotalTimeoutMultiplier乘以写入的字节数再加上 WriteTotalTimeoutConstant。
一般都会做以下设置:
TimeOuts.ReadIntervalTimeout=MAXDWORD;
// 把间隔超时设为最大,把总超时设为0将导致ReadFile立即返回并完成操作
TimeOuts.ReadTotalTimeoutMultiplier=0;
//读时间系数
TimeOuts.ReadTotalTimeoutConstant=0;
//读时间常量
TimeOuts.WriteTotalTimeoutMultiplier=50;
//总超时=时间系数*要求读/写的字符数+时间常量
TimeOuts.WriteTotalTimeoutConstant=2000;
//设置写超时以指定WriteComm成员函数中的
总超时的计算公式是:
总超时=时间系数×要求读/写的字符数 + 时间常量
例如,如果要读入10个字符,那么读操作的总超时的计算公式为:
读总超时=ReadTotalTimeoutMultiplier×10 + ReadTotalTimeoutConstant
可以看出,间隔超时和总超时的设置是不相关的,这可以方便通信程序灵活地设置各种超时。如果所有写超时参数均为0,那么就不使用写超时。如果ReadIntervalTimeout为0,那么就不使用读间隔超时,如果
ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant都为0,则不使用读总超时。如果读间隔超时被设置成MAXDWORD并且两个读总超时为0,那么在读一次输入缓冲区中的内容后读操作就立即完成,而不管是否读入了要求的字符。 在用重叠方式读写串行口时,虽然ReadFile和WriteFile在完成操作以前就可能返回,但超时仍然是起作用的。在这种情况下,超时规定的是操作的完成时间,而不是ReadFile和WriteFile的返回时间。

 

二、代码

#include "stdafx.h"
//#include "测试串口.h"
#include "stdio.h"
#include "windows.h"
HANDLE portfp;
static char* speed;
int _Open(char* com);
LPCWSTR _Change(char* arvg);
int _WriteDate(char* Date);
int _ReadDate();
int _SetCom(char* _nBaud);
int main(int argc, char* argv[])
{
 if (0==strcmp(argv[1], "help"))
 {
 printf("ZZF的串口工具帮助信息\n参数1.串口号\n参数2.波特率\n参数3.发送的字节\n");
 
 return 0;
 }
 _Open(argv[1]);
 _SetCom(argv[2]);
 _WriteDate(argv[3]);
 //Sleep(100);
 _ReadDate();
 
 CloseHandle(portfp);



// speed =argv[3];
 return 0;

}
int _Open(char* com) //打开文件OR串口
{
 portfp = CreateFile(_Change(com), // 普通文件名或者设备文件名
 GENERIC_WRITE | GENERIC_READ, // 访问模式 读写 
 0, // 不共享
 NULL, // 无安全指针
 OPEN_ALWAYS, // 始终创建
 FILE_ATTRIBUTE_NORMAL, // 文件属性
 NULL); // 无复制文件句柄
 if (portfp == INVALID_HANDLE_VALUE)//确认句柄是否无效
 {
 printf("CreateFile() error:%d", GetLastError());
 exit(0);
 }

return 1;
}
int _SetCom(char* _nBaud)
{
 //设置读写缓冲区大小
 static const int g_nZhenMax = 32768;
 if (!SetupComm(portfp, g_nZhenMax, g_nZhenMax))//初始化通信端口
 {
 printf("SetupComm() failed");
 CloseHandle(portfp);
 return false;
 }

//设置串口配置信息
 DCB dcb;//定义串口通讯中的DCB结构
 if (!GetCommState(portfp, &dcb))//读取串口设置(波特率,校验,停止位,数据位等),此处用于确认通信串口是否打开
 {
 printf("GetCommState() failed");
 CloseHandle(portfp);
 return false;
 }/*定义DCB*/
 dcb.BaudRate = atol(_nBaud);//波特率
 dcb.Parity = NOPARITY;//校验方式为无校验
 dcb.ByteSize = 8;//数据位为8位
 dcb.StopBits = ONESTOPBIT;//停止位为1位

if (!SetCommState(portfp, &dcb))//设置串口设置(波特率, 校验, 停止位, 数据位等).并确认是否成功设置
 {
 printf("SetCommState() failed");
 CloseHandle(portfp);
 return false;
 }
 COMMTIMEOUTS timeouts;//设置超时才 
 GetCommTimeouts(portfp, &timeouts);
 timeouts.ReadIntervalTimeout = 0;// 读间隔超时
 timeouts.ReadTotalTimeoutMultiplier =10;// 读时间系数
 timeouts.ReadTotalTimeoutConstant = 0; // 读时间常量
 timeouts.WriteTotalTimeoutMultiplier = 0; // 写时间系数
 timeouts.WriteTotalTimeoutConstant = 20; // 写时间常量
 SetCommTimeouts(portfp, &timeouts);
}
LPCWSTR _Change(char* argv)// LPCWSTR 转换子程序 ATL要勾选
{
 CString str = CString(argv);
 USES_CONVERSION;
 LPCWSTR wszClassName = new WCHAR[str.GetLength() + 1];
 wcscpy((LPTSTR)wszClassName, T2W((LPTSTR)str.GetBuffer(NULL)));
 str.ReleaseBuffer();
 return wszClassName;
}

int _WriteDate(char* Date) //写入数据
{
 BOOL bErrorFlag = FALSE;
 
 DWORD dwBytesToWrite = (DWORD)strlen(Date);
 DWORD dwBytesWritten = 0;
 bErrorFlag = WriteFile(
 portfp, //串口 
 Date, //写入数据
 dwBytesToWrite, //写的字节数
 &dwBytesWritten,//用于保存实际写入字节数的存储区域的指针
 NULL); 
 if (FALSE == bErrorFlag)
 {
 printf("Terminal failure: Unable to write to file.\n");
 }
 else
 {
 if (dwBytesWritten != dwBytesToWrite)
 {
 printf("Error: dwBytesWritten != dwBytesToWrite\n");
 }
 else
 {
 printf("Wrote \" %s \" successfully.\n", Date);
 }
 }

return 0;
}
 int _ReadDate()
{
 char str[51];
 DWORD wCount;//读取的字节数 
 BOOL bReadStat;
 memset(str, 0, sizeof(str));
 bReadStat = ReadFile(portfp, str, 50, &wCount, NULL);
 if (!bReadStat)
 {
 // AfxMessageBox("读串口失败!");
 return FALSE;
 }
 printf("%s\n", str);
}

三、结果分析

数据处理速度比不过一般串口软件,但是胜在有源码,可以改

最后生成的是命令行程序

串口工具帮助信息  参数1.串口号 2.波特率 3.发送的字节