C++串口操作以及串口模拟测试

串口一般分为同步操作和异步操作,串口编程一般分为以下几个步骤:打开串口,配置串口,串口读写和关闭串口。
(1)打开串口
Win32系统把文件的概念进行了扩展。无论是文件、通信设备、命名管道、邮件槽、磁盘、还是控制台,都是用API函数CreateFile来打开或创建的。该函数的原型为:
HANDLE CreateFile( LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDistribution,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
lpFileName:将要打开的串口逻辑名,如“COM1”;
dwDesiredAccess:指定串口访问的类型,可以是读取、写入或二者并列;
dwShareMode:指定共享属性,由于串口不能共享,该参数必须置为0;
lpSecurityAttributes:引用安全性属性结构,缺省值为NULL;
dwCreationDistribution:创建标志,对串口操作该参数必须置为OPEN_EXISTING;
dwFlagsAndAttributes:属性描述,用于指定该串口是否进行异步操作,该值为FILE_FLAG_OVERLAPPED,表示使用异步的I/O;该值为0,表示同步I/O操作;
hTemplateFile:对串口而言该参数必须置为NULL;

例如打开同步串口:
m_hComm = CreateFileA(szPort, /* 设备名,COM1,COM2等 /
GENERIC_READ | GENERIC_WRITE, /* 访问模式,可同时读写 /
0, /* 共享模式,0表示不共享 /
NULL, /* 安全性设置,一般使用NULL /
OPEN_EXISTING, /* 该参数表示设备必须存在,否则创建失败 /
0,
0);

NOTE:串口只能被打开一次。如果串口已经被别的设备或者串口调试软件打开占用,那么再次尝试在程序打开时,就会打开失败!

(2)配置串口
串口的参数配置:通过COM口的设备控制块DCB进行。在修改串口参数之前,应该先使用GetCommonState获取DCB的初始配置,修改完后,使用SetCommonState设置参数。
BOOL GetCommState(
HANDLE hFile, //标识通讯端口的句柄
LPDCB lpDCB //指向一个设备控制块(DCB结构)的指针
);
SetCommState函数设置COM口的设备控制块:
BOOL SetCommState(
HANDLE hFile,
LPDCB lpDCB
);
程序仍需要设置IO缓冲区的大小和IO超时。
IO缓冲区的设置:
BOOL SetupComm(
HANDLE hFile, // 通信设备的句柄
DWORD dwInQueue, // 输入缓冲区的大小(字节数)
DWORD dwOutQueue // 输出缓冲区的大小(字节数)
);
IO超时设置:通过GetCommTimeouts和SetCommTimeouts函数来操作结构体:COMMTIMEOUTS。
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout; //读间隔超时
DWORD ReadTotalTimeoutMultiplier; //读时间系数
DWORD ReadTotalTimeoutConstant; //读时间常量
DWORD WriteTotalTimeoutMultiplier; // 写时间系数
DWORD WriteTotalTimeoutConstant; //写时间常量
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
读总超时=ReadTotalTimeoutMultiplier×10+ReadTotalTimeoutConstant
几点说明:
● 如果所有写超时参数均为0,那么就不使用写超时。
● 如果ReadIntervalTimeout为0,那么就不使用读间隔超时。
● 如果ReadTotalTimeoutMultiplier 和 ReadTotalTimeoutConstant 都为0,则不使用读总超时。
● 如果读间隔超时被设置成MAXDWORD并且读时间系数和读时间常量都为0,那么在读一次输入缓冲区的内容后读操作就立即返回,而不管是否读入了要求的字符。

(3)串口读写
读串口:
BOOL ReadFile(
HANDLE hFile, //串口的句柄
LPVOID lpBuffer, // 读入的数据存储的地址
DWORD nNumberOfBytesToRead, // 要读入的数据的字节数
LPDWORD lpNumberOfBytesRead, // 返回读操作实际读入的字节数
LPOVERLAPPED lpOverlapped // 同步操作时,该参数为NULL。
);
写串口:
BOOL WriteFile(
HANDLE hFile, //串口的句柄
LPCVOID lpBuffer, // 写入的数据存储的地址,
DWORD nNumberOfBytesToWrite, //要写入的数据的字节数
LPDWORD lpNumberOfBytesWritten, // 返回实际写入的字节数
LPOVERLAPPED lpOverlapped // 同步操作时,该参数为NULL
);
CreateFile函数决定了ReadFile和WriteFile是同步还是异步操作。
在同步执行时,函数直到操作完成后才返回。
在异步执行时,即使操作还未完成,这两个函数也会立即返回,费时的I/O操作在后台进行。
ReadFile函数只要在串口输入缓冲区中读入指定数量的字符,就算完成操作。而WriteFile函数不但要把指定数量的字符拷入到输出缓冲区,而且要等这些字符从串行口送出去后才算完成操作。

重叠I/O非常灵活,它也可以实现阻塞(例如我们可以设置一定要读取到一个数据才能进行到下一步操作)。有两种方法可以等待操作完成:一种方法是用 WaitForSingleObject这样的等待函数来等待OVERLAPPED结构的hEvent成员;另一种方法是调用GetOverlappedResult函数等待。
OVERLAPPED结构
OVERLAPPED结构包含了重叠I/O的一些信息,定义如下:
typedef struct _OVERLAPPED { // o
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
   在使用ReadFile和WriteFile重叠操作时,线程需要创建OVERLAPPED结构以供这两个函数使用。线程通过OVERLAPPED结构获得当前的操作状态,该结构最重要的成员是hEvent。hEvent是读写事件。当串口使用异步通讯时,函数返回时操作可能还没有完成,程序可以通过检查该事件得知是否读写完毕。
  当调用ReadFile, WriteFile 函数的时候,该成员会自动被置为无信号状态;当重叠操作完成后,该成员变量会自动被置为有信号状态。
GetOverlappedResult函数
BOOL GetOverlappedResult(
HANDLE hFile, // 串口的句柄
LPOVERLAPPED lpOverlapped, // 指向重叠操作开始时指定的OVERLAPPED结构
LPDWORD lpNumberOfBytesTransferred, //返回实际读写操作传输的字节数。
BOOL bWait // 该参数用于指定函数是否一直等到重叠操作结束。
// 如果该参数为TRUE,函数直到操作结束才返回。
// 如果该参数为FALSE,函数直接返回,这时如果操作没有完成,
// 通过调用GetLastError()函数会返回ERROR_IO_INCOMPLETE。
);
该函数返回重叠操作的结果,用来判断异步操作是否完成,它是通过判断OVERLAPPED结构中的hEvent是否被置位来实现的。
(4)关闭串口
串口关闭直接调用CloseHandle:
BOOL CloseHandle(
HANDLE hObject; //handle to object to close
);
调试:
串口调试时,需要用串口线外接一个串口设备,有时候这不能立刻满足。此时,可以用软件模拟一个串口的连接,经过测试过,VSPD软件比较好用。我机器上只有COM1,在模拟的时候,模拟出两个虚拟串口,并将这两个串口连接(点击Add Pair即可)。
这里写图片描述
然后,可以在程序中打开COM2串口,再用串口调试助手打开COM3。比如自己的程序COM2读数据,那么就应该在串口调试助手中向打开的COM3发送数据。那么COM2就应该能收到COM3发送的数据。
这里写图片描述

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页