storysnail的Windows串口编程笔记
作者 | HeYiJun – storysnailgmail.com | ||||||||
团队 | ls | ||||||||
版权 | 转载请保留本声明! 本文档包含的原创代码根据GeneralPublic License,v3 发布 本文档根据GNUFree Documentation License 1.3发布 文中所引用的软件版权详见各软件版权具体声明,文中所提及的所有商标均为各自商标所有人的财产。 | ||||||||
更新 |
|
前言:
这半个月因肺部感染而不得不暂时终止那令人生厌的中石油巡检工作,闭门在家安静的
修养。整月的工钱自然是泡汤了,可却得来了极其珍贵的个人闲暇时光,让我能淋漓尽致的
做软件方面的研究,虽是粗茶淡饭,针剂苦药,但可静心埋头于书房,却比天堂还甜美!
恍惚已至月末,工作单位来了音讯,让我一下子从甜美的梦中惊醒,从哪里来,回哪里
去,这种如"主体思想"一样可怕的思维是我挥之不去的梦魇,无奈、不知所措、病弱的身体
却不由自主的向那发声的地方靠去!
好了,还是不再发牢骚了,只是个人觉得这种臃肿低效的国企能够存在,本身就是对“
国富论”绝佳的嘲讽,我只能用世界是多元的来啊Q一下了!
切入正题,这段时间做GSM/GPRS和GPS的小东西,需要通过串口发送AT指令来控制,以前
调试一直在用串口助手和minicom之类的现成软件,可是一点都不爽,为什么不自己写个操作
串口的软件,就像在ARM和stm32上一样!
这文章其实只是我的一个笔记,分为两篇,一篇是《storysnail的Windows串口编程笔记》,
另一姊妹篇是《storysnail的Linux串口编程笔记》,由于网上已经有非常多的类似文章,有些长篇
大论,有些短小精悍,连我自己都思考过是否有必要再写一篇,但在Ling的鼓动下还是写了!
本篇是Windows串口编程笔记,详细介绍了串口通信会用到的api函数,并提供了两个示例程序,
这两个示例程序是在64位Windows7系统上用Code::Blocks上编写测试的。
一:写串口程序用到的函数
1:windows与Linux串口设备文件名对照
操作系统 | 串口1 | 串口2 | USB/RS-232转换器 |
Windows | COM1 | COM2 | COMX(我的系统上X=4) |
Linux | /dev/ttyS0 | /dev/ttyS1 | /dev/ttyUSB0 |
2:写串口程序用到的函数
串行通讯函数定义在winbase.h头文件中,所以需要包含该文件。下面是要介绍的函数列表
函数名 | 功能 |
CreateFile | 打开串口 |
CloseHandle | 关闭串口 |
GetCommState | 取得串口当前状态 |
SetCommState | 设置串口状态 |
SetupComm | 设置串口用的I/O缓冲区的大小 |
GetCommTimeouts | 检测通信超时设置 |
SetCommTimeouts | 设置通信超时参数 |
BuildCommDCB | 用字符串中的值来填充设备控制块 |
WriteFile | 发送数据 |
ReadFile | 接收数据 |
GetOverlappedResult | 返回最后异步操作结果 |
ClearCommError | 更新串口状态结构体,并清除所有串口硬件错误 |
PurgeComm | 清空串口缓冲区,退出所有相关操作 |
CreateEvent | 创建一个被监控事件 |
SetCommMask | 设置监控串口通信事件 |
WaitCommEvent | 等待被监控事件发生 |
WaitForSingleObject | 等待一个被监测对象的结果 |
WaitForMultipleObjects | 等待多个被监测对象的结果 |
2.1CreateFile()
用途:
打开串口
原型:
HANDLE CreateFile(LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDistribution,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
参数说明:
lpFileName: 要打开的文件名称。对串口通信来说就是COMX(X为、1、2、3等)
dwDesiredAccess: 读写模式设置
dwShareMode: 串口共享模式
lpSecurityAttributes: 串口的安全属性
dwCreationDistribution:创建文件的性质
dwFlagsAndAttributes: 属性及相关标志
hTemplateFile: 此处为0 操作说明:串口被成功打开时,会返回其句柄,以后对串口的操作都使用该句柄。否则返回INVALID_HANDLE_VALUE即-1。
特别说明:
ReadFile和WriteFile的行为不仅受打开串口时是否使用异步i/o的影响,还受通信超时设置的影响。串行口读写的同步、异步方式是在打开端口的同时给dwFlagsAndAttributes参数传入适当的值而设定的。在同步方式下,调用ReadFile和WriteFile后,当实际读写操作完成或发生超时时才返回调用程序。而异步方式函数在启动接收或发送过程后立即返回,程序继续向下执行。程序在调用ReadFile和 WriteFile时必须提供一个OVERLAPPED数据结构指针,该结构中包含一个手动的事件同步对象,其后的程序必须借助于该事件同步对象,完成数据的接收和发送过程。通信端口的超时设置对读写的处理方式也会产生影响,如果调用读写函数时发生端口超时,则读写函数立即返回并返回已传输的数据字节数。
举例:
HANDLE hComm; //hComm为函数返回的串口1的句柄
hComm=CreateFile("COM1", //串口号
GENERIC_READ|GENERIC_WRITE, //允许读写
0,//通讯设备不允许其他应用程序共享,必须以独占方式打开
NULL, //无安全属性,表示该串口不可被子程序继承
OPEN_EXISTING, //通讯设备已存在
FILE_FLAG_OVERLAPPED, //使用异步方式打开,即异步I/O
0); //通讯设备不能用模板打开
2.2CloseHandle()
用途:
关闭串口
原型:
BOOL CloseHandle(HANDLE hObjedt)
参数说明:
hObjedt:串口句柄
操作说明:
成功关闭串口时返回true,否则返回false
举例:
CloseHandle(hComm);
2.3GetCommState()
用途:
取得串口当前状态
原型:
BOOL GetCommState(HANDLE hFile,LPDCB lpDCB);
参数说明:
hFile:串口句柄
lpDCB:设备控制块(DeviceControlBlock)结构地址。此结构中含有和设备相关的参数。此处是与串口相关的参数。由于参数非常多,当需要设置串口参数时,通常是先取得串口的参数结构,修改部分参数后再将参数结构写入。在此仅介绍少数的几个常用的参数:
DWORDBaudRate: | 串口波特率 | #define CBR_110 110 #define CBR_14400 14400 |
DWORDfParity | 为1的话激活奇偶校验检查 | |
DWORDParity | 校验方式 | #define NOPARITY 0 //无校验 |
DWORDByteSize | 一个字节的数据位个数,范围是5~8 | |
DWORDStopBits | 停止位个数 | #define ONESTOPBIT 0 //1位停止位 |
操作举例:
DCB ComDCB; //串口设备控制块
if(GetCommState(hComm,&ComDCB)) { //获取串口当前状态
//成功执行
}
else {
//错误处理
}
2.4SetCommState()
用途:
设置串口状态,包括常用的更改串口号、波特率、奇偶校验方式、数据位数等
原型:
BOOL SetCommState(HANDLE hFile,LPDCB lpDCB);
参数说明:
hFile: 串口句柄
lpDCB:设备控制块(DeviceControl Block)结构地址。要更改的串口参数包含在此结构中。
操作举例:
DCB ComDCB;
GetCommState(hComm,&ComDCB); //取得当前串口状态
ComDCB.BaudRate=9600; //更改为9600bps,该值即为你要修改后的波特率
if(SetCommState(hComm,&ComDCB)) { //将更改后的参数写入串口
//成功执行
}
else {
//错误处理
}
2.5SetupComm()
用途:
设置串口用的I/O缓冲区的大小。Windows用I/O缓冲区来暂存串口输入和输出的数据。如果通信的速率较高,则应该设置较大的缓冲区。调用SetupComm函数可以设置串行口的输入和输出缓冲区的大小。
原型:
BOOL SetupComm(HANDLE hFile,DWORD dwInQueue,DWORD dwOutQueue);
参数说明:
hFile: 串口句柄
dwInQueue: 输入缓冲区的大小(字节数)
dwOutQueue: 输出缓冲区的大小(字节数)
操作举例:
if(SetupComm(hComm,1024,1024)) {
//成功执行
}
else {
//错误处理
}
2.6GetCommTimeouts ()
用途:
检测通信超时设置
原型:
BOOL GetCommTimeouts(HANDLE hFile,LPCOMMTIMEOUTS lpctmo);
参数说明:
详见SetCommTimeouts()函数
2.7SetCommTimeouts ()
用途:
设置通信超时参数
原型:
BOOL SetCommTimeouts(HANDLE hFile,LPCOMMTIMEOUTS lpctmo);
参数说明:
lpctmo: 指向包含新的超时参数的commtimeouts结构。commtimeouts结构定义如下:
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout;
DWORD ReadTotalTimeoutMultiplier;
DWORD ReadTotalTimeoutConstant;
DWORD WriteTotalTimeoutMultiplier;
DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
ReadIntervalTimeout: 以毫秒为单位指定通信线上两个字符到达之间的最大时间。在ReadFile操作其间,收到第一个字符时开始计算时间。若任意两个字符到达之间的间隔超过这个最大值,ReadFile操作完成,返回缓冲数据。0值表示不用间隔限时。若该成员为MAXDWORD,且ReadTotalTimeoutConstant和ReadTotalTimeoutMultiplier成员为零,则指出读操作要立即返回已接收到的字符,即使未收到字符,读操作也要立即返回。在用异步方式读写串口时,虽然ReadFile和WriteFile在完成操作以前就可能返回,但超时仍然是起作用的。在这种情况下,超时规定的是操作的完成时间,而不是ReadFile和WriteFile的返回时间。
ReadTotalTimeoutMultiplier:以毫秒为单位指定一个乘数,该乘数用来计算读操作的总限时时间。
ReadTotalTimeoutConstant:以毫秒为单位指定一个常数,用于计算读操作的总限时时间。读操作的总限时时间的计算公式是:
读操作的总限时时间=ReadTotalTimeoutMultiplier×要求读/写的字符数+ReadTotalTimeoutConstant
例如,要读入10个字符,那么读操作的总限时时间的计算公式为:
读操作的总限时时间=ReadTotalTimeoutMultiplier×10+ReadTotalTimeoutConstant
ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant成员的值为0表示读操作不使用限时时间。
WriteTotalTimeoutMultiplier:和ReadTotalTimeoutMultiplier类似
WriteTotalTimeoutConstant:和ReadTotalTimeoutConstant相似
操作举例:
COMMTIMEOUTS m_commtimeouts;
m_commtimeouts.ReadIntervalTimeout = 1000;
m_commtimeouts.ReadTotalTimeoutMultiplier = 1000;
m_commtimeouts.ReadTotalTimeoutConstant = 1000;
m_commtimeouts.WriteTotalTimeoutMultiplier = 1000;
m_commtimeouts.WriteTotalTimeoutConstant = 1000;
//串口超时参数设置
if(setcommtimeouts(m_hcomm,&m_commtimeouts)) {
//成功执行
}
else {
//错误处理
}
2.8 BuildCommDCB()
用途:
用字符串中的值来填充设备控制块
原型:
BOOL WINAPI BuildCommDCBA(LPCSTR lpszdef,LPDCB lpdcb);
参数说明:
lpszdef: 指向一个以null结束的字符串,该字符串指定串口的控制信息。比如,“1200,n,8,1”指定波特率为1200,无奇偶校验位,有8个数据位和1个停止位。
lpdcb: 指向被填充的dcb结构。
操作举例:
DCB ComDCB;
if(buildcommdcb("1200,n,8,1”, &ComDCB)){ //建立串口设备控制块
//成功执行
}
else {
//错误处理
}
2.9WriteFile()
用途:
向串口写数据
原型:
BOOL WriteFile(HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped);
参数说明:
hFile: 串口句柄
lpBuffer: 待写入数据的首地址
nNumberOfBytesToWrite:待写入数据的字节数长度
lpNumberOfBytesWritten:函数返回的实际写入串口的数据个数的地址,利用此变量可判断实际写入的字节数和准备写入的字节数是否相同。
lpOverlapped: 异步I/O结构的指针
操作举例:
DWORD BytesSent=0;
unsigned char SendBytes[5]={1,2,3,4,5};
OVERLAPPED ov_Write;
ov_Write.Offset=0;
ov_Write.OffsetHigh=0;
WriteFile(hComm, //调用成功返回非零,失败返回零
SendBytes, //输出缓冲区
5, //准备发送的字符长度
&BytesSent, //实际发出的字符数可以使用该值来检查是否将所有的数据成功写入串口
&ov_Write); //异步结构
2.10ReadFile()
用途:
读串口数据
原型:
BOOL ReadFile(HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
lpNumberOfBytesRead,
lpOverlapped);
参数说明:
hFile:串口句柄
lpBuffer:存储被读出数据的首地址
nNumberOfBytesToRead:准备读出的字节个数
NumberOfBytesRead:实际读出的字节个数
lpOverlapped:异步I/O结构,
操作举例:
unsigned char ucRxBuff[20];
COMSTAT ComStat;
DWORD dwError=0;
DWORD BytesRead=0;
OVERLAPPED ov_Read;
ov_Read.hEvent=CreateEvent(NULL, true, false, NULL); //必须创建有效事件
//检查串口接收缓冲区中的数据个数.假如当前串口中有5个字节数据的话,
//那么执行完ClearCommError()函数后,ComStat结构中的ComStat.cbInQue将被填充为5,
//此值在ReadFile函数中可被直接利用。
ClearCommError(hComm,&dwError,&ComStat);
bResult=ReadFile(hComm, //串口句柄
ucRxBuff, //输入缓冲区地址
ComStat.cbInQue, //想读入的字符数
&BytesRead, //实际读出的字节数的变量指针
&ov_Read); //异步结构指针
2.11GetOverlappedResult()
用途:
返回最后异步操作结果
原型:
BOOL GetOverlappedResult(HANDLE hFile,
LPOVERLAPPED lpoverlapped,
PDWORD lpnumberofbytestransferred,
BOOL bwait);
参数说明:
hfile: 串口句柄
lpoverlapped: 指向一个在启动异步操作时指定的OVERLAPPED结构. 如果采用异步方式,则在调用ReadFile或WriteFile函数时必需指定一个OVERLAPPED结构,调用后程序可继续执行其它操作,在合适的地方再调用函数GetOverlappedResult判断异步操作是否完成。OVERLAPPED结构最重要的成员是hEvent。hEvent是读写事件。当串口使用异步通讯时,函数返回时操作可能还没有完成,程序可以通过检查该事件得知是否读写完毕。当调用ReadFile,WriteFile函数的时候,该成员会自动被置为无信号状态;当异步操作完成后,该成员变量会自动被置为有信号状态。 OVERLAPPED结构定义如下:
typedef struct _OVERLAPPED {
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED,*POVERLAPPED,*LPOVERLAPPED;
lpnumberofbytestransferred: 指向一个32位变量,该变量的值返回实际读写操作传输的字节数。
bwait: 该参数用于指定函数是否一直等到异步操作结束。如果该参数为TRUE,函数直到操作结束才返回。如果该参数为FALSE,函数直接返回,这时如果操作没有完成,通过调用GetLastError()函数会返回ERROR_IO_INCOMPLETE。
函数返回结果:
通过判断OVERLAPPED结构中的hEvent是否被置位来判断异步操作是否完成。
2.12ClearCommError()
用途:
清除串口错误标志以便继续输入、输出操作或者读取串口现在的状态和通信误码的值
原型:
BOOL ClearCommError(HANDLE hFile,
LPDWORD lpErrors,
LPCOMATAT lpStat);
参数说明:
hFile: 串口句柄
lpErrors: 返回错误码的值,错误常数如下:
1-CE_BREAK: 检测到中断信号。意思是说检测到某个字节数据缺少合法的停止位。
2-CE_FRAME: 硬件检测到帧错误。
3-CE_IOE: 通信设备发生输入/输出错误。
4-CE_MODE: 设置模式错误,或是hFile值错误。
5-CE_OVERRUN: 溢出错误,缓冲区容量不足,数据将丢失。
6-CE_RXOVER: 溢出错误。
7-CE_RXPARITY: 硬件检查到校验位错误。
8-CE_TXFULL: 发送缓冲区已满。
lpStat: 指向通信端口状态的结构变量,该结构中对我们很重要的只有cbInQue和cbOutQue。COMSTAT原型如下:
typedef struct _COMSTAT {
DWORD fCtsHold:1; //Tx waiting for CTS signal
DWORD fDsrHold:1; //Tx waiting for DSR signal
DWORD fRlsdHold:1; //Tx waiting for RLSD signal
DWORD fXoffHold:1; //Tx waiting, XOFF char rec''d
DWORD fXoffSent:1; //Tx waiting, XOFF char sent
DWORD fEof:1; //EOF character sent
DWORD fTxim:1; //character waiting for Tx
DWORD fReserved:25; //reserved
DWORD cbInQue; //bytes in input buffer 输入缓冲区中的字节数
DWORD cbOutQue; //bytes in output buffer 输出缓冲区中的字节数
} COMSTAT,*LPCOMSTAT;
操作举例:
COMSTAT ComStat;
DWORD dwError=0;
//ClearCommError会将串口中已接收到的数据字节个数填充到ComStat.cbInQue,
//利用此数值就可以用ReadFile()函数去读串口中的数据了。
ClearCommError(hComm,&dwError,&ComStat);
2.13PurgeComm()
用途:
清除串口缓冲区
原型:
BOOL PurgeComm(HANDLE hFile,DWORD dwFlags);
参数说明:
hFile: 串口句柄
dwFlags:指定串口执行的动作,由以下参数组成:
PURGE_TXABORT:停止目前所有的传输工作立即返回不管是否完成传输动作。
PURGE_RXABORT:停止目前所有的读取工作立即返回不管是否完成读取动作。
PURGE_TXCLEAR:清除发送缓冲区的所有数据。
PURGE_RXCLEAR:清除接收缓冲区的所有数据。
操作举例:
//清除串口的所有操作。
PurgeComm(hComm, PURGE_RXCLEAR|PURGE_TXCLEAR|PURGE_RXABORT|PURGE_TXABORT);
2.14CreateEvent
用途:
创建一个被监控事件
原型:
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCSTR lpName);
参数说明:
lpEventAttributes: 安全属性,NULL表示使用默认属性
bManualReset: 为FALSE,表示该事件变为激发态后会自动重置为非激发态。为TRUE,则表示不会自动重置,如果想让事件由激发态变为非激发态,需要调用ResetEvent()来重置
bInitialState: TRUE,表示创建的事件一开始就是激发态,FALSE,表示创建的事件一开始就是非激发态
lpName: 事件对象的名字,任何线程或进程都可以通过该名字使用该事件对象
返回值:
成功返回事件句柄,并且调用GetLastError返回0,如果lpName已经存在,则会返回已存在的事件对象, GetLastError返回ERROR_ALREADY_EXISTS. 失败返回NULL
2.15SetCommMask()
用途:
设置监控串口通信事件。
原型:
BOOL SetCommMask(HANDLE hFile,DWORD dwEvtMask);
参数说明:
hFile: 串口句柄
dwEvtMask:准备监视的串口事件掩码,该参数有如下信息掩码位值:
EV_BREAK: 收到BREAK信号
EV_CTS: CTS(clear to send)线路发生变化
EV_DSR: DST(Data Set Ready)线路发生变化
EV_ERR: 线路状态错误,包括了CE_FRAME/CE_OVERRUN/CE_RXPARITY3钟错误。
EV_RING: 检测到振铃信号。
EV_RLSD: CD(Carrier Detect)线路信号发生变化。
EV_RXCHAR: 输入缓冲区中已收到数据。
EV_RXFLAG: 使用SetCommState()函数设置的DCB结构中的等待字符已被传入输入缓冲区中。
EV_TXEMPTY: 输出缓冲区中的数据已被完全送出。
操作举例:
//监视串口中有无数据和发送缓冲区中的数据是否全部发送完毕。
if(SetCommMask(hComm,EV_RXCHAR|EV_TXEMPTY)) {
//成功执行
}
else {
//错误处理
}
2.16WaitCommEvent()
用途:
用来判断用SetCommMask()函数设置的串口通信事件是否已发生。如果异步操作不能立即完成的话,函数会返回FALSE,这时应该调用GetLastError()函数得到错误原因,如果GetLastError()返回ERROR_IO_PENDING,表示异步操作正在后台进行.在这种情况下,函数返回之前系统设置OVERLAPPED结构中的事件为无信号状态,WaitCommEvent()函数会等待用SetCommMask()函数设置的串口事件发生,当有事件发生或产生错误时,WaitCommEvent()函数会把OVERLAPPED结构中的事件置为有信号状态,并将事件掩码填充到lpEvtMask参数中.
原型:
BOOL WaitCommEvent(HANDLE hFile,LPDWORD lpEvtMask,LPOVERLAPPED lpOverlapped);
参数说明:
hFile: 串口句柄
lpEvtMask: 函数执行完后如果检测到串口通信事件的话就将其写入该参数中。
lpOverlapped:异步结构,用来保存异步操作结果。
操作举例:
OVERLAPPED lpoverlapped;
DWORD dwMask,dwTrans,dwError=0,err;
memset(&lpoverlapped,0,sizeof(OVERLAPPED));
lpoverlapped.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
if(!WaitCommEvent(hComm,&dwMask,&lpoverlapped)){
if(GetLastError()==ERROR_IO_PENDING){
GetOverlappedResult(hComm,&lpoverlapped,&dwTrans,true);
switch(dwMask){
case EV_RXCHAR: //输入缓冲区中已收到数据。
//youcode
break;
case EV_TXEMPTY: //输出缓冲区中的数据已被完全送出。
//youcode
break;
case EV_BREAK: //收到BREAK信号
//youcode
break;
case EV_CTS: //CTS(clearto send)线路发生变化
//youcode
break;
case EV_DSR: //DST(DataSet Ready)线路发生变化
//youcode
break;
case EV_RING: //检测到振铃信号。
//youcode
break;
case EV_RLSD: //CD(CarrierDetect)线路信号发生变化。
//youcode
break;
case EV_RXFLAG: //使用SetCommState()函数设置的DCB结构中的等待字符已被传入输入缓冲区中。
//youcode
break;
case EV_ERR: //线路状态错误,包括了CE_FRAME/CE_OVERRUN/CE_RXPARITY3钟错误。
switch(dwError){
case CE_FRAME:
err=0;
break;
case CE_OVERRUN:
err=1;
break;
case CE_RXPARITY:
err=2;
break;
default:
break;
}
//youcode
break;
default:
break;
}
}
}
2.17WaitForSingleObject
用途:
等待一个被监测对象的结果
原型:
DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMiliseconds);
参数说明:
hHandle: 被监测对象的句柄
dwMiliseconds: 等待的最长时间.时间终了时,即便被监测对象没有激发,函数还是会立即返回.此值是0,代表立刻返回.此值是INFINITE,表示无穷等待
返回值:
失败返回WAIT_FAILED,这时应该调用GetLastError()取得更多信息,成功以下三种值
WAIT_OBJECT_0 等待的对象激发
WAIT_TIMEOUT 等待超时
WAIT_ABANDONED 一个拥有mutex的线程结束前没有释放mutex
2.18WaitForMultipleObjects
用途:
等待多个被监测对象的结果
原型:
DWORD WaitForMultipleObjects(DWORD nCount,
const HANDLE *lpHandle,
BOOL bWaitAll,
DWORD dwMiliseconds);
参数说明:
nCount: lpHandle中的句柄数量,最大值为MAXNUM_WAIT_OBJECTS
lpHandle: 由若干对象句柄组成的数组
bWaitAll: 如果为TRUE,表示所有句柄都必须激发,此函数才能返回 ;如果为FALSE,只要有一个句柄被激发就会立刻返回
dwMiliseconds:等待的最长时间.时间终了时,即便被监测对象没有激发,函数还是会立即返回。此值是0,代表立刻返回 ,此值是INFINITE,表示无穷等待
二: 示例程序
上面介绍了大部分的串口通信会用到的函数,一般大家在写串口通讯程序时多喜欢用事件
方式。即事先设置好需要监视的串口通信事件,然后创建一个线程监视是否有事件发生,如果
没有事件发生的话,该线程会一直默默的等待,当有事件发生时,再判断是何种事件,并通知
相关线程去处理,而处理的结果会通过事件或管道等通信方式通知主窗体线程来显示。
不过我写的程序特意将多线程部分移除了,这样是为了更清晰的展现如何操作串口,毕竟
进程管理、线程的调度和同步是个很大的话题,有兴趣的朋友可以看看jeffreyRichter的
《win32多线程程序设计》,这本书虽然很老,但却非常实用,核心的东西一般变化都非常小!
同步读写串口的简单程序
/********************************************************************
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *
main.c
DevelopTeam : ls
Programmer : He YiJun (storysnailgmail.com)
Programcomments : Ling Ying
License : GPLv3
LastUpdate : 2013-03-25
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *
功能说明:
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *
更 新:
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *
已知问题:
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *
*********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <tchar.h>
#define COM_MAX_BUFFER 512 //串口数据缓存的最大字节数
#define THREAD_QUIT 1
#define CMD_MAX_LEN 255
HANDLE hCom; //全局变量,串口句柄
/********************************************************************
函数名称:Com_Open()
函数功能:打开串口
函数说明:无
入口参数:无
出口参数:成功返回FALSE,失败返回TRUE
调用实例:无
*********************************************************************/
BOOL Com_Open()
{
hCom=CreateFile("COM4", //COMX口
GENERIC_READ|GENERIC_WRITE, //允许读和写
0, //独占方式
NULL,
OPEN_EXISTING, //打开而不是创建
0, //同步方式,异步方式用FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED
NULL);
if(hCom == INVALID_HANDLE_VALUE) {
printf("OpenCOM4 Fault!\n");
return FALSE;
}
return TRUE;
}
/********************************************************************
函数名称:Com_Setup()
函数功能:配置串口
函数说明:无
入口参数:无
出口参数:成功返回FALSE,失败返回TRUE
调用实例:无
*********************************************************************/
BOOL Com_Setup()
{
COMMTIMEOUTS TimeOuts;
DCB ComDCB;
//设置串口缓冲区
if(!SetupComm(hCom,COM_MAX_BUFFER,COM_MAX_BUFFER)) {
printf("SetupCOM4 Fault!\n");
return FALSE;
}
//设置超时
//在读一次输入缓冲区的内容后读操作就立即返回,而不管是否读入了要求的字符。
TimeOuts.ReadIntervalTimeout=MAXDWORD;
TimeOuts.ReadTotalTimeoutMultiplier=0;
TimeOuts.ReadTotalTimeoutConstant=0;
TimeOuts.WriteTotalTimeoutMultiplier=100;
TimeOuts.WriteTotalTimeoutConstant=500;
*/
//设定读超时
TimeOuts.ReadIntervalTimeout=1000;
TimeOuts.ReadTotalTimeoutMultiplier=500;
TimeOuts.ReadTotalTimeoutConstant=5000;
//设定写超时
TimeOuts.WriteTotalTimeoutMultiplier=500;
TimeOuts.WriteTotalTimeoutConstant=2000;
if(!SetCommTimeouts(hCom,&TimeOuts)) {
printf("SetupCOM4 Fault!\n");
return FALSE;
}
if(!GetCommState(hCom,&ComDCB)) { //获取串口当前状态
printf("SetupCOM4 Fault!\n");
return FALSE;
}
ComDCB.BaudRate = CBR_115200; //更改为115200bps,该值即为你要修改后的波特率
ComDCB.fParity = 0; //无奇偶效验
ComDCB.Parity = NOPARITY; //无校验
ComDCB.ByteSize = 8; //8数据位
ComDCB.StopBits = ONESTOPBIT; //1停止位
if(!SetCommState(hCom,&ComDCB)) { //将更改后的参数写入串口
printf("SetupCOM4 Fault!\n");
return FALSE;
}
//在读写串口之前,还要清空缓冲区
if(!PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR)) {
printf("SetupCOM4 Fault!\n");
return FALSE;
}
return TRUE;
}
/********************************************************************
函数名称:Com_Read()
函数功能:同步读串口,如果串口没有数据会一直阻塞
函数说明:无
入口参数:lpReadBuffer:将数据写入lpReadBuffer所指向的缓存区 ReadSize:预读取的字节数
出口参数:返回实际读到的字节数,失败返回0
调用实例:无
*********************************************************************/
DWORD Com_Read(char *lpReadBuffer)
{
DWORD rCount; //读取的字节数
BOOL bReadStat;
bReadStat=ReadFile(hCom,lpReadBuffer,COM_MAX_BUFFER,&rCount,NULL);
if(!bReadStat) {
printf("ReadCOM4 Fault!\n");
return 0;
}
printf("Readcom: %s\n",lpReadBuffer);
PurgeComm(hCom, PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
return rCount;
}
/********************************************************************
函数名称:Com_Write()
函数功能:同步写串口
函数说明:无
入口参数:lpReadBuffer:将lpWriteBuffer所指向的缓冲区中的数据写入串口 ReadSize:预写入的字节数
出口参数:返回实际写入的字节数,失败返回0
调用实例:无
*********************************************************************/
BOOL Com_Write(char *lpWriteBuffer,DWORD lpWriteBufferLen)
{
DWORD dwBytesWrite=0;
COMSTAT ComStat;
DWORD dwErrorFlags;
BOOL bWriteStat;
ClearCommError(hCom,&dwErrorFlags,&ComStat);
bWriteStat=WriteFile(hCom,lpWriteBuffer,lpWriteBufferLen,&dwBytesWrite,NULL);
if(!bWriteStat) {
printf("WriteCOM4 Fault!\n");
return FALSE;
}
PurgeComm(hCom, PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
printf("Wrotecom: %s\n",lpWriteBuffer);
return TRUE;
}
/********************************************************************
函数名称: main()
函数功能:main()
函数说明:main()
入口参数:无
出口参数:0
调用实例:无
*********************************************************************/
int main()
{
char lpReadBuffer[COM_MAX_BUFFER+1];
char lpWriteBuffer[COM_MAX_BUFFER+1];
char cmd[CMD_MAX_LEN+1];
DWORD rCount = 0;
if(!Com_Open()) {
return 0;
}
if(!Com_Setup()) {
CloseHandle(hCom);
return 0;
}
while (1) {
memset(cmd,'\0',CMD_MAX_LEN+1);
_tprintf (_T("%s"), _T("\nEnterCommand: "));
_fgetts (cmd, CMD_MAX_LEN, stdin);
/* Getrid of the new line at the end */
/* Messagesuse 8-bit characters */
cmd[strlen(cmd)-1] = '\0'; //至少能得到一个回车字符
if (strcmp (cmd, "$Quit") == 0)
break;
if (strncmp (cmd, "read",sizeof("read")) == 0) {
memset(lpReadBuffer,'\0',COM_MAX_BUFFER+1);
rCount = Com_Read(lpReadBuffer);
printf("Readcom char num: %d\n",(int)rCount);
}
if (strncmp (cmd, "write",sizeof("write")) == 0) {
memset(lpWriteBuffer,'\0',COM_MAX_BUFFER+1);
lpWriteBuffer[0] = 'A';
lpWriteBuffer[1] = 'T';
lpWriteBuffer[2] = 0x0d;
lpWriteBuffer[3] = '\0';
Com_Write(lpWriteBuffer,strlen(lpWriteBuffer));
memset(lpReadBuffer,'\0',COM_MAX_BUFFER+1);
rCount = Com_Read(lpReadBuffer);
printf("Readcom char num: %d\n",(int)rCount);
}
}
CloseHandle(hCom);
return 0;
}
异步读串口的示例代码:
/************************************************************************
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*
main.c
DevelopTeam : ls
Programmer : He YiJun (storysnailgmail.com)
Programcomments : Ling Ying
License : GPLv3
LastUpdate : 2013-03-25
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*
功能说明:
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*
更 新:
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*
已知问题:
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*
*************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <tchar.h>
#define COM_MAX_BUFFER 512 //串口数据缓存的最大字节数
#define THREAD_QUIT 1
#define CMD_MAX_LEN 255
HANDLE hCom; //全局变量,串口句柄
/************************************************************************
函数名称:Com_Open()
函数功能:打开串口,异步IO
函数说明:无
入口参数:无
出口参数:成功返回FALSE,失败返回TRUE
调用实例:无
*************************************************************************/
BOOL Com_Open()
{
hCom=CreateFile("COM4", //COMX口
GENERIC_READ|GENERIC_WRITE, //允许读和写
0, //独占方式
NULL,
OPEN_EXISTING, //打开而不是创建
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //异步方式
NULL);
if(hCom == INVALID_HANDLE_VALUE) {
printf("OpenCOM4 Fault!\n");
return FALSE;
}
return TRUE;
}
/************************************************************************
函数名称:Com_Setup()
函数功能:配置串口
函数说明:无
入口参数:无
出口参数:成功返回FALSE,失败返回TRUE
调用实例:无
*************************************************************************/
BOOL Com_Setup()
{
COMMTIMEOUTS TimeOuts;
DCB ComDCB;
//设置串口缓冲区
if(!SetupComm(hCom,COM_MAX_BUFFER,COM_MAX_BUFFER)) {
printf("SetupCOM4 Fault!\n");
return FALSE;
}
//设置超时
//设定读超时
TimeOuts.ReadIntervalTimeout=1000;
TimeOuts.ReadTotalTimeoutMultiplier=500;
TimeOuts.ReadTotalTimeoutConstant=5000;
//设定写超时
TimeOuts.WriteTotalTimeoutMultiplier=500;
TimeOuts.WriteTotalTimeoutConstant=2000;
if(!SetCommTimeouts(hCom,&TimeOuts)) {
printf("SetupCOM4 Fault!\n");
return FALSE;
}
if(!GetCommState(hCom,&ComDCB)) { //获取串口当前状态
printf("SetupCOM4 Fault!\n");
return FALSE;
}
ComDCB.BaudRate = CBR_115200; //更改为115200bps,该值即为你要修改后的波特率
ComDCB.fParity = 0; //无奇偶效验
ComDCB.Parity = NOPARITY; //无校验
ComDCB.ByteSize = 8; //8数据位
ComDCB.StopBits = ONESTOPBIT; //1停止位
if(!SetCommState(hCom,&ComDCB)) { //将更改后的参数写入串口
printf("SetupCOM4 Fault!\n");
return FALSE;
}
//在读写串口之前,还要清空缓冲区
if(!PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR)) {
printf("SetupCOM4 Fault!\n");
return FALSE;
}
return TRUE;
}
/************************************************************************
函数名称:Com_Read()
函数功能:读串口
函数说明:无
入口参数:lpReadBuffer:将数据写入lpReadBuffer所指向的缓存区 ReadSize:预读取的字节数
出口参数:返回实际读到的字节数,失败返回0
调用实例:无
*************************************************************************/
DWORD Com_Read(char *lpReadBuffer,DWORD ReadSize)
{
OVERLAPPED lpoverlapped;
COMSTAT ComStat;
DWORD dwErrorFlags;
DWORD rCount = 0; //读取的字节数
DWORD dwBytesRead = ReadSize;
BOOL bReadStat;
if(dwBytesRead > COM_MAX_BUFFER)
dwBytesRead = COM_MAX_BUFFER;
memset(&lpoverlapped,0,sizeof(OVERLAPPED));
lpoverlapped.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
//读取串口现在的状态和通信误码的值
//主要是为了得到输入缓冲区中的字节数
ClearCommError(hCom,&dwErrorFlags,&ComStat);
//dwBytesRead是预读取的字节数
dwBytesRead=min(dwBytesRead,(DWORD)ComStat.cbInQue);
if(!dwBytesRead)
return 0;
bReadStat=ReadFile(hCom,lpReadBuffer,dwBytesRead,&rCount,&lpoverlapped);
if(!bReadStat) {
if(GetLastError()==ERROR_IO_PENDING) { //返回ERROR_IO_PENDING,表明串口正在进行读操作
//GetOverlappedResult函数的最后一个参数设为TRUE时,
//函数会一直等待,直到读操作完成或由于错误而返回。
GetOverlappedResult(hCom,&lpoverlapped,&rCount,TRUE);
//使用WaitForSingleObject函数等待,直到读操作完成或延时已达到2秒钟
//当串口读操作进行完毕后,lpoverlapped.hEvent事件会变为有信号
//WaitForSingleObject(lpoverlapped.hEvent,2000);
PurgeComm(hCom, PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
printf("READIO PENDING %d!\n",(int)rCount);
return rCount;
}
printf("ReadCOM4 Fault!\n");
return 0;
}
PurgeComm(hCom, PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
return rCount;
}
/************************************************************************
函数名称:Com_Write()
函数功能:写串口
函数说明:无
入口参数:lpReadBuffer:将lpWriteBuffer所指向的缓冲区中的数据写入串口 ReadSize:预写入的字节数
出口参数:返回实际写入的字节数,失败返回0
调用实例:无
*************************************************************************/
DWORD Com_Write(char *lpWriteBuffer,DWORD WriteSize)
{
DWORD wCount = 0; //写入的实际字节数
DWORD dwBytesWrite=WriteSize;
OVERLAPPED lpoverlapped;
COMSTAT ComStat;
DWORD dwErrorFlags;
BOOL bWriteStat;
if((dwBytesWrite > COM_MAX_BUFFER) || (!dwBytesWrite))
return 0;
memset(&lpoverlapped,0,sizeof(OVERLAPPED));
lpoverlapped.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
//读取串口现在的状态和通信误码的值
//主要是为了得到输出缓冲区中的字节数
ClearCommError(hCom,&dwErrorFlags,&ComStat);
//如果输出缓冲区中有数据,那么返回0
if(ComStat.cbOutQue != 0) {
printf("Writebuffer was not empty!\n");
return 0;
}
bWriteStat=WriteFile(hCom,lpWriteBuffer,dwBytesWrite,&wCount,&lpoverlapped);
if(!bWriteStat) {
if(GetLastError()==ERROR_IO_PENDING) {
ClearCommError(hCom,&dwErrorFlags,&ComStat);
wCount = ComStat.cbOutQue;
WaitForSingleObject(lpoverlapped.hEvent,1000);
printf("WROTEIO PENDING %d!\n",(int)wCount);
return wCount;
}
printf("WriteCOM4 Fault!\n");
return 0;
}
PurgeComm(hCom, PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
return wCount;
}
/************************************************************************
函数名称: main()
函数功能:main()
函数说明:main()
入口参数:无
出口参数:0
调用实例:无
*************************************************************************/
int main()
{
char lpReadBuffer[COM_MAX_BUFFER+1];
char lpWriteBuffer[COM_MAX_BUFFER+1];
char cmd[CMD_MAX_LEN+1];
DWORD rCount = 0;
DWORD wCount = 0;
if(!Com_Open()) {
return 0;
}
if(!Com_Setup()) {
CloseHandle(hCom);
return 0;
}
while (1) {
memset(cmd,'\0',CMD_MAX_LEN+1);
_tprintf (_T("%s"), _T("\nEnterCommand: "));
_fgetts (cmd, CMD_MAX_LEN, stdin);
/* Getrid of the new line at the end */
/* Messagesuse 8-bit characters */
cmd[strlen(cmd)-1] = '\0';
if (strcmp (cmd, "$Quit") == 0)
break;
if (strncmp (cmd, "read",sizeof("read")) == 0) {
memset(lpReadBuffer,'\0',COM_MAX_BUFFER+1);
rCount = Com_Read(lpReadBuffer,COM_MAX_BUFFER);
if(rCount > 0) {
printf("Readcom: %s\n",lpReadBuffer);
printf("Readcom char num: %d\n",(int)rCount);
}
}
if (strncmp (cmd, "write",sizeof("write")) == 0) {
memset(lpWriteBuffer,'\0',COM_MAX_BUFFER+1);
lpWriteBuffer[0] = 'A';
lpWriteBuffer[1] = 'T';
lpWriteBuffer[2] = 0x0d;
lpWriteBuffer[3] = '\0';
wCount = Com_Write(lpWriteBuffer,strlen(lpWriteBuffer));
if(wCount > 0) {
printf("Wrotecom: %s\n",lpWriteBuffer);
printf("Wrotecom char num: %d\n",(int)wCount);
}
}
}
CloseHandle(hCom);
return 0;
}