C++串⼝通信编程(异步通信)
串⼝通信⼀般分为同步和异步两种⽅式,本博客主要讲述异步通信程序的编写,其编程步骤主要分为四步骤:
⼀、打开串⼝
Win32系统把⽂件的概念进⾏了扩展。⽆论是⽂件、通信设备、命名管道、邮件槽、磁盘、还是控制台,都是⽤API函数CreateFile来打开或创建的。本程序串⼝类中打开串⼝的函数定义如下:
bool My_Com::Open_Com(LPCTSTR Port)
{
hCom = CreateFile(
Port, //将要打开的串⼝逻辑名
GENERIC_READ | GENERIC_WRITE, //允许读和写
0, //指定共享属性,由于串⼝不能共享,该参数必须置为0,独占⽅式
NULL,//引⽤安全性属性结构,缺省值为NULL
OPEN_EXISTING, /创建标志,对串⼝操作该参数必须置为OPEN_EXISTING
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, //属性描述,此处指定该串⼝异步
NULL //对串⼝⽽⾔该参数必须置为NULL);
if (hCom == INVALID_HANDLE_VALUE)
{
printf ("打开串⼝失败!\n");
return FALSE;
}
el
{
printf("打开串⼝成功!\n");
}
return TRUE;
}
注意:FILE_ATTRIBUTE_NORMAL 和 FILE_FLAG_OVERLAPPED 均代表异步通信。也可以借⽤CreateFile函数加上for循环实现对外设驱动的扫描以⾃动找出插⼊的
GetCom()
{
MyComm.hCom = INVALID_HANDLE_VALUE;
BOOL ret = fal;
int j = 0;
//逐⼀进⾏COM 端⼝检测
printf("正在搜寻当前可⽤端⼝...\n");
for (int i = 1; i <= 10; i++)
{
string comname = "COM" + to_string(i);
MyComm.hCom = CreateFile(stringToLPCWSTR(comname), // 打开串⼝
GENERIC_READ | GENERIC_WRITE, //读写⽅式
0, //不能共享
NULL, //安全属性,⼀般不⽤设为NULL
OPEN_EXISTING, //打开已存在的设备
FILE_ATTRIBUTE_NORMAL, //普通⽂件属性
NULL); //⽆模板
if (MyComm.hCom != INVALID_HANDLE_VALUE)
{
j++;
printf("端⼝ COM %d 可⽤\n", i);
ret = CloHandle(MyComm.hCom);
if (!ret) printf("关闭串⼝失败!!");
}
}
if (!j)
{
printf("⽆可⽤端⼝!!\n");
return FALSE;
}
el return TRUE;
}
⼆、配置串⼝
ablaze
本部分主要是⼀些初始化配置:
1、DCB结构相关参数的配置(波特率、数据位数、奇偶校验和停⽌位数等信息),调⽤GetCommState函数获取串⼝的初始配置,然后
先修改DCB结构,再调⽤SetCommState函数设置串⼝。
2、COMMTIMEOUTS结构串⼝读写超时参数设置(单位:毫秒;若设置为0,表⽰该参数不起作⽤)
ReadIntervalTimeout:两字符之间最⼤的延时,当读取串⼝数据时,⼀旦两个字符传输的时间差超过该时间,读取函数将返回现有的数据。在ReadFile操作期间,时间周期从第⼀个字符接收到算起。如
果收到的两个字符之间的间隔超过该值,ReadFile操作完毕并返回所有缓冲数据。如果值为MAXDWORD,并且ReadTotalTimeoutConstant和ReadTotalTimeoutMultiplier两个值都为0, 则指定读操作携带已经收到的字符⽴即返回,即使没有收到任何字符。如果ReadIntervalTimeout为0,则该值不起作⽤。ReadTotalTimeoutMultiplier:读取每字符间的超时。 指定累积值,⽤于计算读操作时的超时总数。对于每次读操作,该值与所要读的字节数相乘。
ReadTotalTimeoutConstant:⼀次读取串⼝数据的固定超时。在⼀次读取串⼝的操作中,其超时为 ReadTotalTimeoutMultiplier乘以读取的字节数再加上 ReadTotalTimeoutConstant。将ReadIntervalTimeout设置为MAXDWORD,并将 ReadTotalTimeoutMultiplier 和ReadTotalTimeoutConstant设置为0,表⽰读取操作将⽴即返回存放在输⼊缓冲区的字符。
continue
WriteTotalTimeoutMultiplier:写⼊每字符间的超时。
WriteTotalTimeoutConstant:⼀次写⼊串⼝数据的固定超时。在⼀次写⼊串⼝的操作中,其超时为WriteTotalTimeoutMultiplier乘以写⼊的字节数再加上 WriteTotalTimeoutConstant。
间隔超时=ReadIntervalTimeout
总超时 = ReadTotalTimeoutMultiplier * 字节数 + ReadTotalTimeoutConstant
这⾥以串⼝读取事件为例进⾏详细的分析,其过程分两个阶段:
dear是什么意思
第⼀个阶段是:串⼝执⾏到ReadFile函数时,串⼝还没有开始传输数据,所以串⼝缓冲区的第⼀个字节是没有装数据的,这时候总超时起作⽤,如果在总超时时间内没有进⾏串⼝数据的传输,ReadFile函数就返回,当然 没有读取到任何数据。⽽且,间隔超时并没有起作⽤。
第⼆阶段:假设总超时为20秒,程序运⾏到ReadFile,总超时开始从0 计时,如果在计时到达10秒时,串⼝开始了数据的传输,那么从接收的第⼀个字节开始,间隔超时就开始计时,假如间隔超时为1ms,那么在读取完第⼀个字节后,串⼝开始等待1ms,如果1ms之内接收到了第⼆个字节,就读取第⼆个字节,间隔超时重置为0并计时,等待第三个字节的到来,如果第三个字节到来的时间超过了1ms,那么ReadFile函数⽴即返回,这时候总超时计时是没到20秒的。如果在20秒总计时时间结束之前,所有的数据都遵守数据间隔为1ms的约定并陆陆续续的到达串⼝缓冲区,那么就成功进⾏了⼀次串⼝传输和读取;如果20秒总计时时间到,串⼝还陆陆续续的有数据到达,即使遵守字节间隔为1ms的约定,ReadFile函数也会⽴即返回,这时候总超时就起作⽤了。
总结起来,总超时在两种情况下起作⽤:⼀是串⼝没进⾏数据传输,等待总超时时间那么长ReadFile才返回(即⾮正常数据传输);⼆是数据太长,总超时设置太短,数据还没读取完就返回了(读取的数据是不全的)。间隔超时触发的条件:在总超时时间内且串⼝进⾏了数据的传输。
3、利⽤SetUpComm、PurgeComm两个函数分别设置输出输出缓冲区的⼤⼩并进⾏清空处理
电脑培训课程SetupComm参数解释:dwInQueue指定输⼊缓冲区的⼤⼩(字节);dwOutQueue指定输出缓冲区的⼤⼩(字节)。
PurgeComm参数解释:PURGE_TXABORT 终⽌所有正在进⾏的字符输出操作,PURGE_RXABORT 终⽌所有正在进⾏的字符。输⼊操作PURGE_TXCLEAR 设备驱动程序清除输出缓冲,PURGE_RXCLEAR 设备驱动程序清除输⼊缓冲区。
4、利⽤CreateEvent函数创建读写及等待的操作事件,⽤于读写函数中做判断使⽤。
5、利⽤SetCommMask函数设置要监控的事件,⽽WaitCommEvent函数是等待串⼝通信事件的发⽣放在读函数中。
6、创建读取线程。
bool My_Com::Config_Com()
{
SetupComm(hCom, 1024, 1024); //输⼊缓冲区和输出缓冲区的⼤⼩都是1024
DCB dcb;
GetCommState(hCom, &dcb);
dcb.BaudRate = 9600; //波特率为9600
dcb.ByteSize = 8; //每个字节有8位
renadedcb.Parity = NOPARITY; //⽆奇偶校验位
dcb.StopBits = TWOSTOPBITS; //两个停⽌位
SetCommState(hCom, &dcb);
COMMTIMEOUTS TimeOuts; //设定读超时
TimeOuts.ReadIntervalTimeout = MAXDWORD;
TimeOuts.ReadTotalTimeoutMultiplier = 0;
TimeOuts.ReadTotalTimeoutConstant = 0; //设定写超时
TimeOuts.WriteTotalTimeoutMultiplier = 500;
TimeOuts.WriteTotalTimeoutConstant = 2000;
SetCommTimeouts(hCom, &TimeOuts); //设置超时
PurgeComm(hCom, PURGE_TXCLEAR | PURGE_RXCLEAR);
m_ovRead.hEvent = CreateEvent(NULL, fal, fal, NULL);
m_ovWrite.hEvent = CreateEvent(NULL, fal, fal, NULL);
m_ovWait.hEvent = CreateEvent(NULL, fal, fal, NULL);
//SetCommMask设置要监控的事件
//EV_RXCHAR:输⼊缓冲区中已收到数据,即接收到⼀个字节并放⼊输⼊缓冲区。
//EV_ERR:线路状态错误,包括了CE_FRAME / CE_OVERRUN / CE_RXPARITY 3种错误。
SetCommMask(hCom, EV_ERR | EV_RXCHAR);
/
/_beginThreadex创建读取线程
m_Thread = (HANDLE)_beginthreadex(NULL, 0, &My_Com::ComRecv, this, 0, NULL);
berkeleym_IsOpen = true;
return TRUE;
}
三、串⼝读写
写函数ComWrite(发送数据)中主要调⽤了WriteFile函数参数:HANDLE hFile⽂件句柄,LPCVOID lpBuffer数据缓存区指针DWORD nNumberOfBytesToWrite你要写的字节数,LPDWORD lpNumberOfBytesWritten⽤于保存实际写⼊字节数的存储区域的指针LPOVERLAPPED lpOverlappedOVERLAPPED结构体指针
bool My_Com::ComWrite(LPBYTE buf, int &len)
{
BOOL rtn = FALSE;
DWORD WriteSize = 0; //DWORD 代表 unsigned long
PurgeComm(hCom, PURGE_TXCLEAR | PURGE_TXABORT);
m_ovWait.Offt = 0;
rtn = WriteFile(hCom, buf, len, &WriteSize, &m_ovWrite);
if (FALSE == rtn && GetLastError() == ERROR_IO_PENDING)//后台读取
{
//等待数据写⼊完成
printf("已发送:");
for (int i = 0; i < len; i++)
printf("%d ", buf[i]);初学者怎样化妆
printf("\n");
}
return rtn != FALSE;
}
读函数ComRecv(接收数据)的解释已在代码区详细备注,其函数定义如下:
unsigned int __stdcall My_Com::ComRecv(void* LPParam)
{
My_Com *obj = static_cast<My_Com*>(LPParam);
DWORD WaitEvent = 0, Bytes = 0;zvezda
BOOL Status = FALSE;
BYTE ReadBuf[4096];
DWORD Error;
COMSTAT cs = { 0 };
int i;
while (obj->m_IsOpen)
{
WaitEvent = 0;
obj->m_ovWait.Offt = 0;
Status = WaitCommEvent(obj->hCom, &WaitEvent, &obj->m_ovWait);
/*
WaitCommEvent等待串⼝通信事件的发⽣
⽤途:⽤来判断⽤SetCommMask()函数设置的串⼝通信事件是否已发⽣。
原型:BOOL WaitCommEvent(HANDLE hFile,LPDWORD lpEvtMask,LPOVERLAPPED lpOverlapped);
参数说明:
-hFile:串⼝句柄
-lpEvtMask:函数执⾏完后如果检测到串⼝通信事件的话就将其写⼊该参数中。
-lpOverlapped:异步结构,⽤来保存异步操作结果。
*/
//GetLastError()函数返回ERROR_IO_PENDING,表明串⼝正在进⾏读操作
if (FALSE == Status && GetLastError() == ERROR_IO_PENDING)
{
// GetOverlappedResult函数的最后⼀个参数设为TRUE,函数会⼀直等待,直到读操作完成或由于错误⽽返回。 Status = GetOverlappedResult(obj->hCom, &obj->m_ovWait, &Bytes, TRUE);
}
//在使⽤ReadFile 函数进⾏读操作前,应先使⽤ClearCommError函数清除错误。
ClearCommError(obj->hCom, &Error, &cs);
if (TRUE == Status //等待事件成功
&& WaitEvent&EV_RXCHAR//缓存中有数据到达
insane是什么意思
&& cs.cbInQue > 0)//有数据
{
Bytes = 0;
obj->m_ovRead.Offt = 0;
memt(ReadBuf, 0, sizeof(ReadBuf));
/*
BOOL ReadFile(
HANDLE hFile, //串⼝的句柄
LPVOID lpBuffer,// 读⼊的数据存储的地址,即读⼊的数据将存储在以该指针的值为⾸地址的⼀⽚内存区
DWORD nNumberOfBytesToRead,// 要读⼊的数据的字节数
LPDWORD lpNumberOfBytesRead,// 指向⼀个DWORD数值,该数值返回读操作实际读⼊的字节数
LPOVERLAPPED lpOverlapped // 重叠操作时,该参数指向⼀个OVERLAPPED结构,同步操作时,该参数为NULL );
*/
Status = ReadFile(obj->hCom, &ReadBuf, 4096, &Bytes, &obj->m_ovRead);
if (Status != FALSE)
{
printf("收到:");
for (i = 0; i < Bytes; i++)
{
printf("%d ", ReadBuf[i]);
}
printf("\n");
proven是什么意思}
//PurgeComm函数清空串⼝的输⼊输出缓冲区
PurgeComm(obj->hCom, PURGE_RXCLEAR | PURGE_RXABORT);
}
}