参考资料
https://github.com/licheedev/Android-SerialPort-API
GitHub - kongqw/AndroidSerialPort: Android串口通信示例
第一节 实现串口通讯的函数及串口编程简介
API函数不仅提供了打开和读写通讯端口的操作方法,还提供了名目繁多的函数以支持对串行通讯的各种操作。常用函数及作用下:
函数名 作用
CreateFile 打开串口
GetCommState 检测串口设置
SetCommState 设置串口
BuilderCommDCB 用字符串中的值来填充设备控制块
GetCommTimeouts 检测通信超时设置
SetCommTimeouts 设置通信超时参数
SetCommMask 设定被监控事件
WaitCommEvent 等待被监控事件发生
WaitForMultipleObjects 等待多个被监测对象的结果
WriteFile 发送数据
ReadFile 接收数据
GetOverlappedResult 返回最后重叠(异步)操作结果
PurgeComm 清空串口缓冲区,退出所有相关操作
ClearCommError 更新串口状态结构体,并清除所有串口硬件错误
CloseHandle 关闭串行口
用Windows API编写串口程序本身是有巨大优点的,因为控制能力会更强,效率也会更
高。
API编写串口,过程一般是这样的:
1、 创建串口句柄,用CreateFile;
2、 对串口的参数进行设置,其中比较重要的是波特率(BaudRate),数据宽度(BytesBits),奇偶校验(Parity),停止位(StopBits),当然,重要的还有端口号(Port);
3、 然后对串口进行相应的读写操作,这时候用到ReadFile和WriteFile函数;
4、 读写结束后,要关闭串口句柄,用CloseFile。
下面依次讲述各个步骤的过程。
第二节 创建串口句柄打开串口
从字面上去理解,大家也可以发现CreateFile实际上表明Windows是把串口当作一个文件来处理的,所以它也有文件那样的缓冲区、句柄、读写错误等,不同的是,这个文件名字只有固定的几个(一般为四个),而且始终存在(EXSITING),而且在调用CreateFile的时候请注意它的参数。CreateFile函数原型如下:
HANDLE CreateFile(LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile );
lpFileName:指向一个以NULL结束的字符串,该串指定了要创建、打开或截断的文件、管道、通信源、磁盘设备或控制台的名字。当用CreateFile打开串口时,这个参数可用“COM1”指定串口1,用“COM2”指定串口2,依此类推。
dwDesireAccess: 指定对文件访问的类型,该参数可以为GENERIC_READ(指定对该文件的读访问权)或GENERIC_WRITE(指定该文件的写访问权)两个值之一或同时为为这两个值。用ENERIC_READ|GENERIC_WRITE则指定可对串口进行读写;
dwShareMode:指定此文件可以怎样被共享。因为串行口不支持任何共享模式,所以dwShareMode必须设为0;
lpSecurityAttributes定义安全属性,一般不用,可设为NULL。Win 9x下该参数被忽略;
dwCreationDistribution定义文件创建方式, 对串口必须设为OPEN_EXISTING,表示打开已经存在的文件;
dwFlagsAndAttributes为该文件指定定义文件属性和标志,这个程序中设为FILE_FLAG_OVERLAPPED,表示异步通信方式;
hTemplateFile指向一个模板文件的句柄,串口无模板可言,设为NULL。在 Windows 9x下该参数必须为NULL。
串口被成功打开时,返回其句柄,否则返回INVALID_HANDLE_value(0XFFFFFFFF)。
上面说到了异步,那什么是异步呢?异步是相对同步这个概念而言的。异步,就是说,
在进行串口读写操作时,不用等到I/O操作完成后函数才返回,也就是说,异步可以更快得
响应用户操作;同步,相反,响应的I/O操作必须完成后函数才返回,否则阻塞线程。对于
一些很简单的通讯程序来说,可以选择同步,这样可以省去很多错误检查,但是对于复杂一点的应用程序,异步是最佳选择。
实例1:
/****************** example1.cpp ******************************************/
/* lishaoan 2009-06-29 *****************************************************/
/* ******************************************************/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
bool openport(char *portname)//打开串口
{
HANDLE hComm;
hComm = CreateFile(portname, //串口号
GENERIC_READ | GENERIC_WRITE, //允许读写
0, //通讯设备必须以独占方式打开
0, //无安全属性
OPEN_EXISTING, //通讯设备已存在
FILE_FLAG_OVERLAPPED, //异步I/O
0); //通讯设备不能用模板打开
if (hComm == INVALID_HANDLE_VALUE)
{
CloseHandle(hComm);
return FALSE;
}
else
return true;
}
void main()
{
bool open;
open=openport("com2");
if(open)
printf("open comport success");
system("pause") ;
}
/************************** program end***************************************/
实例2:
/****************** example2.cpp ******************************************/
/* lishaoan 2009-06-29 *****************************************************/
/* ******************************************************/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
bool openport(char *portname)//打开串口
{
HANDLE hComm;
hComm = CreateFile(portname, //串口号
GENERIC_READ | GENERIC_WRITE, //允许读写
0, //通讯设备必须以独占方式打开
0, //无安全属性
OPEN_EXISTING, //通讯设备已存在
0, //同步I/O
0); //通讯设备不能用模板打开
if (hComm == INVALID_HANDLE_VALUE)
{
CloseHandle(hComm);
return FALSE;
}
else
return true;
}
void main()
{
bool open;
open=openport("com2");
if(open)
printf("open comport success");
system("pause") ;
}
/************************** program end***************************************/
第三节 设置串口
在打开通信设备句柄后,常常需要对串行口进行一些初始化工作。这需要通过一个DCB结构来进行。DCB结构包含了诸如波特率、每个字符的数据位数、奇偶校验和停止位数等信息。在查询或配置串口的属性时,都要用DCB结构来作为缓冲区。
第一次打开串口时,串口设置为系统默认值,函数GetCommState和SetCommState可用于检索和设定端口设置的DCB(设备控制块)结构,该结构中BaudRate、ByteSize、StopBits和Parity字段含有串口波特率、数据位数、停止位和奇偶校验控制等信息。
程序中用DCB进行串口设置时,应先调用API函数GetCommState,来获得串口的设置信息:
GetCommState()
用途:取得串口当前状态
原型:BOOL GetCommState(HANDLE hFile, LPDCB lpDCB);
参数说明:
-hFile:串口句柄
-lpDCB:设备控制块(Device Control Block)结构地址。此结构中含有和设备相关的参数。此处是与串口相关的参数。由于参数非常多,当需要设置串口参数时,通常是先取得串口的参数结构,修改部分参数后再将参数结构写入。
然后在需要设置的地方对dcb进行设置。串口有很多的属性,上面也已经介绍了一些最重要的参数。这里介绍数据结构DCB:
typedef struct _DCB { // dcb
DWORD DCBlength; //DCB结构体大小
DWORD BaudRate; //波特率
DWORD fBinary: 1; //是否是二进制,一般设置为TRUE
DWORD fParity: 1;//是否进行奇偶校验
DWORD fOutxCtsFlow:1; //CTS线上的硬件握手
DWORD fOutxDsrFlow:1; //DSR线上的硬件握手
DWORD fDtrControl:2; //DTR控制
DWORD fDsrSensitivity:1;
DWORD fTXContinueOnXoff:1;
DWORD fOutX: 1; //是否使用XON/XOFF协议
DWORD fInX: 1; //是否使用XON/XOFF协议
DWORD fErrorChar: 1; //发送错误协议
DWORD fNull: 1;
DWORD fRtsControl:2;
DWORD fAbortOnError:1;
DWORD fDummy2:17;
WORD wReserved;
WORD XonLim; //设置在XON字符发送之前inbuf中允许的最少字节数
WORD XoffLim; //在发送XOFF字符之前outbuf中允许的最多字节数
BYTE ByteSize; //数据宽度,一般为8,有时候为7
BYTE Parity; //奇偶校验
BYTE StopBits; //停止位数
char XonChar; //设置表示XON字符的字符,一般是采用0x11这个数值
char XoffChar; //设置表示XOFF字符的字符,一般是采用0x13这个数值
char ErrorChar;
char EofChar;
char EvtChar;
WORD wReserved1;
} DCB;
我们真正在串口编程中用到的数据成员没有几个,在此仅介绍少数的几个常用的参数:
DWORD BaudRate:串口波特率
DWORD fParity:为1的话激活奇偶校验检查
DWORD Parity:校验方式,值0~4分别对应无校验、奇校验、偶校验、校验置位、校验清零
DWORD ByteSize:一个字节的数据位个数,范围是5~8
DWORD StopBits:停止位个数,0~2分别对应1位、1.5位、2位停止位
然后再末尾调用SetCommState就可以了,还是比较方便的。这样可不必构造一个完整的DCB结构。
SetCommState()
用途:设置串口状态,包括常用的更改串口号、波特率、奇偶校验方式、数据位数等
原型:BOOL SetCommState(HANDLE hFile, LPDCB lpDCB);
参数说明:
-hFile:串口句柄
-lpDCB:设备控制块(Device Control Block)结构地址。要更改的串口参数包含在此结构中。
然后调用SetCommMask,用来指定程序接收特定的串口事件,调用SetupComm函数,设置串口缓冲区大小:
SetCommMask()说明:
用途:设置串口通信事件。
原型: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_RXPARITY 3钟错误。
EV_RING:检测到振铃信号。
EV_RLSD:CD(Carrier Detect)线路信号发生变化。
EV_RXCHAR:输入缓冲区中已收到数据。
EV_RXFLAG:使用SetCommState()函数设置的DCB结构中的等待字符已被传入输入缓冲区中。
EV_TXEMPTY:输出缓冲区中的数据已被完全送出。
还有,串口因为是I/O操作,可能会产生错误,这时候需要用SetCommTimeouts()设置超时限制,以避免阻塞现象。设置超时设置需要一个结构体COMMTIMEOUTS。
SetCommTimeouts()
BOOL SetCommTimeouts( hCommDev, 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成员为零,则指
出读操作要立即返回已接收到的字符,即使未收到字符,读操作也要返回。
ReadTotalTimeoutMultiplier:以毫秒为单位指定一个乘数,该乘数用来计算读操作的总限时时间。每个读操作的总限时时间等于读操作所需的字节数与该值的乘积。
ReadTotalTimeoutConstant:以毫秒为单位指定一个常数,用于计算读操作的总限时时间。每个操作的总限时时间等于ReadTotalTimeoutMultiplier成员乘以读操作所需字节数再加上该值的和。ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant成员的值为0表示读操作不使用限时时间。
WriteTotalTimeoutMultiplier和WriteTotalTimeoutconstant的意义和作用分别与
ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant相似,不再重复。
举例:
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout=MAXDWORD;
timeouts.ReadTotalTimeoutConstant=0;
timeouts.ReadTotalTimeoutMultiplier=0;
timeouts.WriteTotalTimeoutConstant=50;
timeouts.WriteTotalTimeoutMultiplier=2000;
SetCommTimeouts(m_hCom, &timeouts);
这里将ReadIntervalTimeout设置为最大字节数,.ReadTotalTimeoutConstant和
ReadTotalTimeoutMultiplier都设置为0,表示不设置读操作超时,也就是说读操作瞬间完
成,不进行等待。
调用PurgeComm函数可以终止正在进行的读写操作,该函数还会清除输入或输出缓冲区中的内容。
PurgeComm()说明:
功能:终止目前正在进行的读或写的动作
函数原型:BOOL PurgeComm(
HANDLE hFile, // handle of communications resource
DWORD dwFlags // action to perform
);
参数说明:
HANDLE hFile,//串口名称字符串
dwFlags共有四种 flags:
PURGE_TXABORT:终止目前正在进行的(背景)写入动作
PURGE_RXABORT:终正目前正在进行的(背景)读取动作
PURGE_TXCLEAR: flush写入的 buffer
PURGE_TXCLEAR: flush读取的 buffer
实例3:
/****************** example3.cpp ******************************************/
/* lishaoan 2009-06-29 *****************************************************/
/* ******************************************************/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
bool openport(char *portname)//打开串口
{
HANDLE hComm;
hComm = CreateFile(portname, //串口号
GENERIC_READ | GENERIC_WRITE, //允许读写
0, //通讯设备必须以独占方式打开
0, //无安全属性
OPEN_EXISTING, //通讯设备已存在
0, //同步I/O
0); //通讯设备不能用模板打开
if (hComm == INVALID_HANDLE_VALUE)
{
CloseHandle(hComm);
return FALSE;
}
else
return true;
}
bool setupdcb(int rate_arg)//设置DCB
{
DCB dcb;
int rate= rate_arg;
memset(&dcb,0,sizeof(dcb));
if(!GetCommState(hComm,&dcb))//获取当前DCB配置
return FALSE;
// set DCB to configure the serial port
dcb.DCBlength = sizeof(dcb);
/* ---------- Serial Port Config ------- */
dcb.BaudRate = rate;
dcb.Parity = NOPARITY;
dcb.fParity = 0;
dcb.StopBits = ONESTOPBIT;
dcb.ByteSize = 8;
dcb.fOutxCtsFlow = 0;
dcb.fOutxDsrFlow = 0;
dcb.fDtrControl = DTR_CONTROL_DISABLE;
dcb.fDsrSensitivity = 0;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fOutX = 0;
dcb.fInX = 0;
/* ----------------- misc parameters ----- */
dcb.fErrorChar = 0;
dcb.fBinary = 1;
dcb.fNull = 0;
dcb.fAbortOnError = 0;
dcb.wReserved = 0;
dcb.XonLim = 2;
dcb.XoffLim = 4;
dcb.XonChar = 0x13;
dcb.XoffChar = 0x19;
dcb.EvtChar = 0;
// set DCB
if(!SetCommState(hComm,&dcb))
return false;
else
return true;
}
bool setuptimeout(DWORD ReadInterval,DWORD ReadTotalMultiplier,DWORD ReadTotalconstant,DWORD WriteTotalMultiplier,DWORD WriteTotalconstant)
{
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout=ReadInterval;
timeouts.ReadTotalTimeoutConstant=ReadTotalconstant;
timeouts.ReadTotalTimeoutMultiplier=ReadTotalMultiplier;
timeouts.WriteTotalTimeoutConstant=WriteTotalconstant;
timeouts.WriteTotalTimeoutMultiplier=WriteTotalMultiplier;
if(!SetCommTimeouts(hComm, &timeouts))
return false;
else
return true;
}
void main()
{
bool open;
open=openport("com2");
if(open)
printf("open comport success");
if(setupdcb(9600))
printf("setupDCB success\n");
if(setuptimeout(0,0,0,0,0))
printf("setuptimeout success\n");
SetCommMask(hComm, EV_RXCHAR); //当有字符在inbuf中时产生这个事件
//清除串口的所有操作
PurgeComm(hComm,PURGE_RXCLEAR|PURGE_TXCLEAR|PURGE_RXABORT|PURGE_TXABORT);
system("pause") ;
}
/************* program end***************************************/
第四节 读写串口数据及关闭串口
Win32API函数ReadFile和WriteFile支持对串行口的读写操作。在调用ReadFile和WriteFile之前,线程应该调用ClearCommError函数清除错误标志。
该函数负责报告指定的错误和设备的当前状态。
ClearCommError()
用途:清除串口错误或者读取串口现在的状态
原型: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:指向通信端口状态的结构变量,原型如下:
typedef struct _COMSTAT{
...
...
DWORD cbInQue; //输入缓冲区中的字节数
DWORD cbOutQue;//输出缓冲区中的字节数
}COMSTAT,*LPCOMSTAT;
该结构中对我们很重要的只有上面两个参数,其他的我们可以不用管。
假如当前串口中有5个字节数据的话,那么执行完ClearCommError()函数后,ComStat结构中的ComStat.cbInQue将被填充为5,此值在ReadFile函数中可被直接利用。
例如:
COMSTAT ComStat;
DWORD dwError=0;
ClearCommError(hComm,&dwError,&ComStat);
上式执行完后,ComStat.cbInQue就是串口中当前含有的数据字节个数,我们利用此
数值就可以用ReadFile()函数去读串口中的数据了。
函数ReadFile和WriteFile的行为还受是否使用异步I/O(Overlapped)及通信超时设置的影响。串行口读写的同步、异步方式是在打开端口的同时给dwGlagsAndAttributes参数传入适当的值而设定的。
WriteFile()
用途:向串口写数据
原型:BOOL WriteFile(HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped);
参数说明:
-hFile:串口句柄
-lpBuffer:待写入数据的首地址
-nNumberOfBytesToWrite:待写入数据的字节数长度
-lpNumberOfBytesWritten:函数返回的实际写入串口的数据个数的地址,利用此变量可判断实际写入的字节数和准备写入的字节数是否相同。
-lpOverlapped:重叠I/O结构的指针
ReadFile()
用途:读串口数据
原型:BOOL ReadFile(HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
lpNumberOfBytesRead,
lpOverlapped);
参数说明:
-hFile:串口句柄
-lpBuffer:存储被读出数据的首地址
-nNumberOfBytesToRead:准备读出的字节个数
-NumberOfBytesRead:实际读出的字节个数
-lpOverlapped:异步I/O结构
在同步方式下,调用ReadFile或WriteFile后,当实际读写操作完成或发生超时时才返回调用程序。而异步方式函数在启动接收或发送过程后立即返回,程序继续向下执行,程序在调用ReadFile和WriteFile时必须提供一个Overlapped数据结构指针,该结构中包含一个手动事件同步对象,其后的程序必须借助于该事件同步对象,完成数据的接收和发送过程。
通信端口的超时设置对读写的处理方式也会产生影响,如果调用读写函数时发生端口超时,
则读写函数立即返回并返回已传输的数据字节数。
ReadFile函数只要在串行口输入缓冲区中读入指定数量的字符,就算完成操作。
而WriteFile函数不但要把指定数量的字符拷入到输出缓冲中,而且要等这些字符从串行口送出去后才算完成操作。
如果不再使用某一端口,须将该端口关闭,以便其他程序可以使用该端口。如果不显式关闭某端口,当程序退出时打开的端口也将被自动关闭。但为了安全起见,最好是显式的关闭它。
关闭串口的语句为CloseHandle()。
CloseHandle()
用途:关闭串口
原型:BOOL CloseHandle(HANDLE hObjedt)
说明:
-hObjedt:串口句柄
操作说明:成功关闭串口时返回true,否则返回false
当ReadFile和WriteFile返回FALSE时,不一定就是操作失败,线程应该调用GetLastError函数分析返回的结果。例如,在重叠操作时如果操作还未完成函数就返回,那么函数就返回FALSE,而且GetLastError函数返回ERROR_IO_PENDING。如果GetLastError函数返回ERROR_IO_PENDING,则说明重叠操作还未完成,线程可以等待操作完成。
有两种等待办法:一种办法是用象WaitForSingleObject这样的等待函数来等待OVERLAPPED结构的hEvent成员,可以规定等待的时间,在等待函数返回后,调用GetOverlappedResult。
另一种办法是调用GetOverlappedResult函数等待,如果指定该函数的bWait参数为TRUE,那么该函数将等待OVERLAPPED结构的hEvent事件。GetOverlappedResult可以返回一个OVERLAPPED结构来报告包括实际传输字节在内的重叠操作结果。
如果规定了读/写操作的超时,那么当超过规定时间后,hEvent成员会变成有信号的。因此,在超时发生后,WaitForSingleObject和GetOverlappedResult都会结束等待。WaitForSingleObject的dwMilliseconds参数会规定一个等待超时,该函数实际等待的时间是两个超时的最小值。注意GetOverlappedResult不能设置等待的时限,因此如果hEvent成员无信号,则该函数将一直等待下去
GetOverlappedResult函数调用方法如下:
BOOL GetOverlappedResult(
HANDLE hFile, //用CreateFile获得的文件句柄
LPOVERLAPPED lpOverlapped, //指向一个在启动重叠操作时指定的OVERLAPPED结构(即
//读写函数中指定的OverLapped结构)
LPDWORD lpNumberOfBytesTransferred,//实际传输的字节数
BOOL bWait, //是否等待悬挂的重叠操作完成,若为TRUE,则此函数直到操作完成后才//返回。
);
OVERLAPPED结构定义如下:
typedef struct _OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
如果采用异步方式,则在调用ReadFile或WriteFile函数时必需指定一个Overlapped结构,调用后程序可继续执行其它操作,在合适的地方再调用函数GetOverlappedResult判断异步重叠操作是否完成(判断OVERLAPPED结构中的hEvent是否被置位)。
WaitCommEvent()
用途:用来判断用SetCommMask()函数设置的串口通信事件是否已发生。
原型:BOOL WaitCommEvent(HANDLE hFile,
LPDWORD lpEvtMask,
LPOVERLAPPED lpOverlapped
);
参数说明:
-hFile:串口句柄
-lpEvtMask:函数执行完后如果检测到串口通信事件的话就将其写入该参数中。
-lpOverlapped:异步结构,用来保存异步操作结果。
当由SetCommMask函数所指定的事件产生时这个函数将返回TRUE。
注:在用api函数撰写串口通信函数时大体上有两种方法,一种是查寻法,另外一种是事件通知法。
这两种方法的区别在于收串口数据时,前一种方法是主动的周期性的查询串口中当前有没有数据;后一种方法是事先设置好需要监视的串口通信事件,然后依靠单独开设的辅助线程进行监视该事件是否已发生,如果没有发生的话该线程就一直不停的等待直到该事件发生后,将该串口事件以消息的方式通知主窗体,然后主窗体收到该消息后依据不同的事件性质进行处理。比如说当主窗体收到监视线程发来的RX_CHAR(串口中有数据)的消息后,就可以用ReadFile()函数去读串口。
实例4:
/****************** example4.cpp ******************************************/
/* lishaoan 2009-07-10 *****************************************************/
/* ******************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
HANDLE hComm;
OVERLAPPED m_ov;
COMSTAT comstat;
DWORD m_dwCommEvents;
bool openport(char *portname)//打开一个串口
{
hComm = CreateFile(portname,
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
0);
if (hComm == INVALID_HANDLE_VALUE)
return FALSE;
else
return true;
}
bool setupdcb(int rate_arg)
{
DCB dcb;
int rate= rate_arg;
memset(&dcb,0,sizeof(dcb));
if(!GetCommState(hComm,&dcb))//获取当前DCB配置
{
return FALSE;
}
/* -------------------------------------------------------------------- */
// set DCB to configure the serial port
dcb.DCBlength = sizeof(dcb);
/* ---------- Serial Port Config ------- */
dcb.BaudRate = rate;
dcb.Parity = NOPARITY;
dcb.fParity = 0;
dcb.StopBits = ONESTOPBIT;
dcb.ByteSize = 8;
dcb.fOutxCtsFlow = 0;
dcb.fOutxDsrFlow = 0;
dcb.fDtrControl = DTR_CONTROL_DISABLE;
dcb.fDsrSensitivity = 0;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fOutX = 0;
dcb.fInX = 0;
/* ----------------- misc parameters ----- */
dcb.fErrorChar = 0;
dcb.fBinary = 1;
dcb.fNull = 0;
dcb.fAbortOnError = 0;
dcb.wReserved = 0;
dcb.XonLim = 2;
dcb.XoffLim = 4;
dcb.XonChar = 0x13;
dcb.XoffChar = 0x19;
dcb.EvtChar = 0;
/* -------------------------------------------------------------------- */
// set DCB
if(!SetCommState(hComm,&dcb))
{
return false;
}
else
return true;
}
bool setuptimeout(DWORD ReadInterval,DWORD ReadTotalMultiplier,DWORD ReadTotalconstant,DWORD WriteTotalMultiplier,DWORD WriteTotalconstant)
{
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout=ReadInterval;
timeouts.ReadTotalTimeoutConstant=ReadTotalconstant;
timeouts.ReadTotalTimeoutMultiplier=ReadTotalMultiplier;
timeouts.WriteTotalTimeoutConstant=WriteTotalconstant;
timeouts.WriteTotalTimeoutMultiplier=WriteTotalMultiplier;
if(!SetCommTimeouts(hComm, &timeouts))
{
return false;
}
else
return true;
}
ReceiveChar( )
{
BOOL bRead = TRUE;
BOOL bResult = TRUE;
DWORD dwError = 0;
DWORD BytesRead = 0;
char RXBuff;
for (;;)
{
bResult = ClearCommError(hComm, &dwError, &comstat);
if (comstat.cbInQue == 0)
continue;
if (bRead)
{
bResult = ReadFile(hComm, // Handle to COMM port
&RXBuff, // RX Buffer Pointer
1, // Read one byte
&BytesRead, // Stores number of bytes read
&m_ov); // pointer to the m_ov structure
printf("%c",RXBuff);
if (!bResult)
{
switch (dwError = GetLastError())
{
case ERROR_IO_PENDING:
{
bRead = FALSE;
break;
}
default:
{
break;
}
}
}
else
{
bRead = TRUE;
}
} // close if (bRead)
if (!bRead)
{
bRead = TRUE;
bResult = GetOverlappedResult(hComm, // Handle to COMM port
&m_ov, // Overlapped structure
&BytesRead, // Stores number of bytes read
TRUE); // Wait flag
}
}
}
WriteChar(BYTE* m_szWriteBuffer,DWORD m_nToSend)
{
BOOL bWrite = TRUE;
BOOL bResult = TRUE;
DWORD BytesSent = 0;
HANDLE m_hWriteEvent;
ResetEvent(m_hWriteEvent);
if (bWrite)
{
m_ov.Offset = 0;
m_ov.OffsetHigh = 0;
// Clear buffer
bResult = WriteFile(hComm, // Handle to COMM Port
m_szWriteBuffer, // Pointer to message buffer in calling finction
m_nToSend, // Length of message to send
&BytesSent, // Where to store the number of bytes sent
&m_ov ); // Overlapped structure
if (!bResult)
{
DWORD dwError = GetLastError();
switch (dwError)
{
case ERROR_IO_PENDING:
{
// continue to GetOverlappedResults()
BytesSent = 0;
bWrite = FALSE;
break;
}
default:
{
// all other error codes
break;
}
}
}
} // end if(bWrite)
if (!bWrite)
{
bWrite = TRUE;
bResult = GetOverlappedResult(hComm, // Handle to COMM port
&m_ov, // Overlapped structure
&BytesSent, // Stores number of bytes sent
TRUE); // Wait flag
// deal with the error code
if (!bResult)
{
printf("GetOverlappedResults() in WriteFile()");
}
} // end if (!bWrite)
// Verify that the data size send equals what we tried to send
if (BytesSent != m_nToSend)
{
printf("WARNING: WriteFile() error.. Bytes Sent: %d; Message Length: %d\n", BytesSent, strlen((char*)m_szWriteBuffer));
}
return true;
}
void main()
{
if(openport("com2"))
printf("open comport success\n");
if(setupdcb(9600))
printf("setupDCB success\n");
if(setuptimeout(0,0,0,0,0))
printf("setuptimeout success\n");
PurgeComm(hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
WriteChar("please send data now",20);
printf("received data:\n");
ReceiveChar( );
system("pause");
}
/************************** program end***************************************/
第五节 多线程串口通信及其它
进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭。
线程是进程内部的一个执行单元。系统创建好进程后,实际上就启动执行了该进程的主执行线程,主执行线程以函数地址形式,比如说main或WinMain函数,将程序的启动点提供给Windows系统。主执行线程终止了,进程也就随之终止。
每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也较为广泛。
多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。要说明的一点是,目前大多数的计算机都是单处理器(CPU)的,为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。由此可见,如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,反而会降低系统的性能。这一点在多线程编程时应该注意。
Win32 SDK函数支持进行多线程的程序设计,并提供了操作系统原理中的各种同步、互斥和临界区等操作。
Win32提供了一系列的API函数来完成线程的创建、挂起、恢复、终结以及通信等工作。下面将选取其中的一些重要函数进行说明。
由于创建线程所使用的函数CreateThread()是windows API函数,所以,必须包含头文件windows.h。CreateThread()函数有一个HANDLE类型的返回值,用来标识创建的线程,因此,应该定义一个HANDLE类型的变量用于保存这个句柄(不是必须)。线程创建完成之后,如果不需要使用这个句柄变量,应当将其关闭,以释放系统资源。关闭句柄的方法是调用CloseHandle()函数。
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId);
该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄,其中各参数说明如下:
lpThreadAttributes:指向一个 SECURITY_ATTRIBUTES结构的指针,该结构决定了线程的安全属性,一般置为 NULL;
dwStackSize:指定了线程的堆栈深度,一般都设置为0;
lpStartAddress:表示新线程开始执行时代码所在函数的地址,即线程的起始地址。一般情况为(LPTHREAD_START_ROUTINE)ThreadFunc,ThreadFunc是线程函数名;
lpParameter:指定了线程执行时传送给线程的32位参数,即线程函数的参数;
dwCreationFlags:控制线程创建的附加标志,可以取两种值。如果该参数为0,线程在被创建后就会立即开始执行;如果该参数为CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态,并不马上执行,直至函数ResumeThread被调用;
lpThreadId:该参数返回所创建线程的ID;
如果创建成功则返回线程的句柄,否则返回NULL。
DWORD WINAPI ThreadFunc( LPVOID lpParam )为线程函数,lpParam为线程函数的参数。
DWORD SuspendThread(HANDLE hThread);该函数用于挂起指定的线程,如果函数执行成功,则线程的执行被终止。
DWORD ResumeThread(HANDLE hThread);该函数用于结束线程的挂起状态,执行线程。
VOID ExitThread(DWORD dwExitCode);该函数用于线程终结自身的执行,主要在线程的执行函数中被调用。其中参数dwExitCode用来设置线程的退出码。
BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);一般情况下,线程运行结束之后,线程函数正常返回,但是应用程序可以调用TerminateThread强行终止某一线程的执行。各参数含义如下:
hThread:将被终结的线程的句柄;
dwExitCode:用于指定线程的退出码。
使用TerminateThread()终止某个线程的执行是不安全的,可能会引起系统不稳定;虽然该函数立即终止线程的执行,但并不释放线程所占用的资源。因此,一般不建议使用该函数。
BOOL PostThreadMessage(DWORD idThread,
UINT Msg,
WPARAM wParam,
LPARAM lParam);
该函数将一条消息放入到指定线程的消息队列中,并且不等到消息被该线程处理时便返回。
idThread:将接收消息的线程的ID;
Msg:指定用来发送的消息;
wParam:同消息有关的字参数;
lParam:同消息有关的长参数;
调用该函数时,如果即将接收消息的线程没有创建消息循环,则该函数执行失败。
DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
hHandle为要监视的对象(一般为同步对象,也可以是线程)的句柄;
dwMilliseconds为hHandle对象所设置的超时值,单位为毫秒;
当在某一线程中调用该函数时,线程暂时挂起,系统监视hHandle所指向的对象的状态。如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。
DWORD WaitForMultipleObject(DWORD dwCount , CONST HANDLE* phObject, BOOL fWaitAll, DWORD dwMillisecinds);
dwCount参数 用于指明想要让函数查看的内核对象的数量。这个值必须在1与MAXIMUM_WAIT_OBJECTS(在Windows头文件中定义为64之间.
phObjects参数 是指向内核对象句柄的数组的指针。可以以两种不同的方式来使用WaitForMultipleObjects函数。一种方式是让线程进入等待状态,直到指定内核对象中的任何一个变为已通知状态。另一种方式是让线程进入等待状态,直到所有指定的内核对象都变为已通知状态。
fWaitAll参数 告诉该函数,你想要让它使用何种方式。如果为该参数传递TRUE,那么在所有对象变为已通知状态之前,该函数将不允许调用线程运行。
dwMil liseconds参数 该参数的作用与它在WaitForSingleObject中的作用完全相同。如果在等待的时候规定的时间到了,那么该函数无论如何都会返回。同样,通常为该参数传递INFINITE,但是在编写代码时应该小心,以避免出现死锁情况。
WaitForMultipleObjects函数的返回值告诉调用线程,为什么它会被重新调度。可能的返回值是WAIT_FAILED和WAIT_TIMEOUT,这两个值的作用是很清楚的。如果为f WaitAll参数传递TRUE,同时所有对象均变为已通知状态,那么返回值是WAIT_OBJECT_0。如果为fWaitAll传递FALSE,那么一旦任何一个对象变为已通知状态,该函数便返回。在这种情况下,你可能想要知道哪个对象变为已通知状态。返回值是WAIT_OBJECT_0与(WAIT_OBJECT_0 + dwCount-1)之间的一个值。换句话说,如果返回值不是WAIT_TIMEOUT,也不是WAIT_FAILED,那么应该从返回值中减去WAIT_OBJECT_0。产生的数字是作为第二个参数传递给WaitForMultipleObjects的句柄数组中的索引。该索引说明哪个对象变为已通知状态。
通常,在写WINDOWS程序的时候我们会用 GetLastError()来获得错误代号,进而想要知道具体出错原因(文本描述),我们可以用 FormatMessage函数来得到。
DWORD FormatMessage(
DWORD dwFlags,
LPCVOID lpSource,
DWORD dwMessageId,
DWORD dwLanguageId,
LPTSTR lpBuffer,
DWORD nSize,
va_list* Arguments
);
dwFlags: FORMAT_MESSAGE_ALLOCATE_BUFFER //此函数会分配内存以包含描述字串。
FORMAT_MESSAGE_FROM_SYSTEM, //在系统的id映射表中寻找描述字串
FORMAT_MESSAGE_FROM_HMODULE //在其他资源模块中寻找描述字串
FORMAT_MESSAGE_FROM_STRING //消息ID是个字串,不是个DWORD通常为:
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM。
lpSource:指定了FORMAT_MESSAGE_FROM_HMODULE的话,此参数表示模块的HANDLE指定了FORMAT_MESSAGE_FROM_STRING的话,此参数表示id字串通常为:NULL。
dwMessageId:消息ID;如果指定FORMAT_MESSAGE_FROM_STRING,将被忽略。
dwLanguageId:消息描述所用的语言通常为:0表示自动选择。
lpBuffer:如果未指定FORMAT_MESSAGE_ALLOCATE_BUFFER,则为自己提供的缓冲区否则为系统LocalAlloc分配,需要被用户LocalFree。
nSize:如果未指定FORMAT_MESSAGE_ALLOCATE_BUFFER,则为自己提供的缓冲区大小否则为系统LocalAlloc分配之最小缓冲区大小。
Arguments:通常不使用。
如果异步操作不能立即完成的话,函数返回FALSE,并且调用GetLastError()函数分析错误原因后返回ERROR_IO_PENDING,指示异步操作正在后台进行.这种情况下,在函数返回之前系统设置OVERLAPPED结构中的事件为无信号状态,该函数等待用SetCommMask()函数设置的串口事件发生,共有9种事件可被监视:EV_BREAK,EV_CTS,EV_DSR,EV_ERR,EV_RING,EV_RLSD,EV_RXCHAR, EV_RXFLAG,EV_TXEMPTY;当其中一个事件发生或错误发生时,函数将 OVERLAPPED结构中的事件置为有信号状态,并将事件掩码填充到dwMask参数中。
操作举例1:OVERLAPPED os;
DWORD dwMask,dwTrans,dwError=0,err;
memset(&os,0,sizeof(OVERLAPPED));
os.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
if(!WaitCommEvent(hComm,&dwMask,&os)){
GetOverlappedResult(hComm,&os,&dwTrans,true);
switch(dwMask){
case EV_RXCHAR:
PostMessage(Parent,WM_COMM_RXCHAR,0,0);
break;
case EV_TXEMPTY:
PostMessage(Parent,WM_COMM_TXEMPTY,0,0);
break;
case EV_ERR:
switch(dwError){
case CE_FRAME:
err=0;
break;
case CE_OVERRUN:
err=1;
break;
case CE_RXPARITY:
err=2;
break;
default:break;
}
PostMessage(Parent,WM_COMM_ERR,(WPARAM)0,(LPARAM)err);
break;
case EV_BREAK:
PostMessage(Parent,WM_COMM_BREAK,0,0);
break;
case ...://其他用SetCommMask()函数设置的被监视的串口通信事件。
... ...
break;
default:break;
}
}
操作举例2:
COMSTAT comStat;
DWORD dwErrors;
BOOL fOOP, fOVERRUN, fPTO, fRXOVER, fRXPARITY, fTXFULL;
BOOL fBREAK, fDNS, fFRAME, fIOE, fMODE;
// Get and clear current errors on the port.
if (!ClearCommError(hComm, &dwErrors, &comStat))
// Report error in ClearCommError.
return false;
// Get error flags.
fDNS = dwErrors & CE_DNS;
fIOE = dwErrors & CE_IOE;
fOOP = dwErrors & CE_OOP;
fPTO = dwErrors & CE_PTO;
fMODE = dwErrors & CE_MODE;
fBREAK = dwErrors & CE_BREAK;
fFRAME = dwErrors & CE_FRAME;
fRXOVER = dwErrors & CE_RXOVER;
fTXFULL = dwErrors & CE_TXFULL;
fOVERRUN = dwErrors & CE_OVERRUN;
fRXPARITY = dwErrors & CE_RXPARITY;
// COMSTAT structure contains information regarding
// communications status.
if (comStat.fCtsHold)
// Tx waiting for CTS signal
if (comStat.fDsrHold)
// Tx waiting for DSR signal
if (comStat.fRlsdHold)
// Tx waiting for RLSD signal
if (comStat.fXoffHold)
// Tx waiting, XOFF char rec'd
if (comStat.fXoffSent)
// Tx waiting, XOFF char sent
if (comStat.fEof)
// EOF character received
if (comStat.fTxim)
// Character waiting for Tx; char queued with TransmitCommChar
if (comStat.cbInQue)
// comStat.cbInQue bytes have been received, but not read
if (comStat.cbOutQue)
// comStat.cbOutQue bytes are awaiting transfer
return true;
memset函数详细说明:
void *memset(void *s,int c,size_t n)总的作用:将已开辟内存空间 s的首 n个字节的值设为值 c。常用于内存空间初始化。
CreateEvent函数详细说明:
函数功能描述:创建或打开一个命名的或无名的事件对象
函数原型:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, //安全属性
BOOL bManualReset, //复位方式
BOOL bInitialState, //初始状态
LPCTSTR lpName //对象名称
);
参数:
lpEventAttributes:
[输入]一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承。
Windows NT/2000:lpEventAttributes的结构中的成员为新的事件指定了一个安全符。如果lpEventAttributes是NULL,事件将获得一个默认的安全符。
bManualReset:
[输入]指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当事件被一个等待线程释放以后,系统将会自动将事件状态复原为无信号状态。
bInitialState:
[输入]指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态。
lpName:
[输入]指定事件的对象的名称,是一个以0结束的字符串指针。名称的字符格式限定在MAX_PATH之内。名字是对大小写敏感的。
如果lpName指定的名字,与一个存在的命名的事件对象的名称相同,函数将请求EVENT_ALL_ACCESS来访问存在的对象。这时候,由于bManualReset和bInitialState参数已经在创建事件的进程中设置,这两个参数将被忽略。如果lpEventAttributes是参数不是NULL,它将确定此句柄是否可以被继承,但是其安全描述符成员将被忽略。
如果lpName为NULL,将创建一个无名的事件对象。
如果lpName的和一个存在的信号、互斥、等待计时器、作业或者是文件映射对象名称相同,函数将会失败,在GetLastError函数中将返回ERROR_INVALID_HANDLE。造成这种现象的原因是这些对象共享同一个命名空间。
终端服务(Terminal Services):名称中可以加入"Global\"或是"Local\"的前缀,这样可以明确的将对象创建在全局的或事务的命名空间。名称的其它部分除了反斜杠(\),可以使用任意字符。详细内容可参考Kernel Object Name Spaces。
Windows 2000:在Windows 2000系统中,没有终端服务运行,"Global\"和"Local\"前缀将被忽略。名称的其它部分除了反斜杠(\),可以使用任意字符。
Windows NT 4.0以及早期版本, Windows 95/98:名称中除了反斜杠(\),可以使用任意字符。
返回值:
如果函数调用成功,函数返回事件对象的句柄。如果对于命名的对象,在函数调用前已经被创建,函数将返回存在的事件对象的句柄,而且在GetLastError函数中返回ERROR_ALREADY_EXISTS。
如果函数失败,函数返回值为NULL,如果需要获得详细的错误信息,需要调用GetLastError。
备注:
调用CreateEvent函数返回的句柄,该句柄具有EVENT_ALL_ACCESS权限去访问新的事件对象,同时它可以在任何有此事件对象句柄的函数中使用。
在调用的过程中,所有线程都可以在一个等待函数中指定事件对象句柄。当指定的对象的状态被置为有信号状态时,单对象等待函数将返回。
对于多对象等待函数,可以指定为任意或所有指定的对象被置为有信号状态。当等待函数返回时,等待线程将被释放去继续运行。
初始状态在bInitialState参数中进行设置。使用SetEvent函数将事件对象的状态置为有信号状态。使用ResetEvent函数将事件对象的状态置为无信号状态。
当一个手动复原的事件对象的状态被置为有信号状态时,该对象状态将一直保持有信号状态,直至明确调用ResetEvent函数将其置为无符号状态。
当事件的对象被置为有信号状态时,任意数量的等待中线程,以及随后开始等待的线程均会被释放。
当一个自动复原的事件对象的状态被置为有信号状态时,该对象状态将一直保持有信号状态,直至一个等待线程被释放;系统将自动将此函数置为无符号状态。如果没有等待线程正在等待,事件对象的状态将保持有信号状态。
多个进程可持有同一个事件对象的多个句柄,可以通过使用此对象来实现进程间的同步。下面的对象共享机制是可行的:
·在CreateEvent函数中,lpEventAttributes参数指定句柄可被继承时,通过CreateProcess函数创建的子进程继承的事件对象句柄。
·一个进程可以在DuplicateHandle函数中指定事件对象句柄,从而获得一个复制的句柄,此句柄可以被其它进程使用。
·一个进程可以在OpenEvent或CreateEvent函数中指定一个名字,从而获得一个有名的事件对象句柄。
使用CloseHandle函数关闭句柄。当进程停止时,系统将自动关闭句柄。当最后一个句柄被关闭后,事件对象将被销毁。
使用环境:
Windows NT/2000:需要3.1或更高版本
Windows 95/98:需要Windows 95或更高版本
头文件:定义在Winbase.h;需要包含 Windows.h。
导入库:user32.lib
Unicode:在Windows NT/2000中,以 Unicode和 ANSI执行
一个Event被创建以后,可以用OpenEvent()API来获得它的Handle,用CloseHandle()
来关闭它,用SetEvent()或PulseEvent()来设置它使其有信号,用ResetEvent()
来使其无信号,用WaitForSingleObject()或WaitForMultipleObjects()来等待
其变为有信号.
PulseEvent()是一个比较有意思的使用方法,正如这个API的名字,它使一个Event
对象的状态发生一次脉冲变化,从无信号变成有信号再变成无信号,而整个操作是原子的.
对自动复位的Event对象,它仅释放第一个等到该事件的thread(如果有),而对于
人工复位的Event对象,它释放所有等待的thread.
实例5:
/****************** example4.cpp ******************************************/
/* lishaoan 2009-07-10 *****************************************************/
/* ******************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <conio.h>
HANDLE hComm;
OVERLAPPED m_ov;
COMSTAT comstat;
ProcessErrorMessage(char* ErrorText)
{
char *Temp = new char[200];
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL
);
sprintf(Temp, "WARNING: %s Failed with the following error: \n%s\nPort: %d\n", (char*)ErrorText, lpMsgBuf, "com2");
MessageBox(NULL, Temp, "Application Error", MB_ICONSTOP);
LocalFree(lpMsgBuf);
delete[] Temp;
return true;
}
bool openport(char *portname)//打开一个串口
{
hComm = CreateFile(portname,
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
0);
if (hComm == INVALID_HANDLE_VALUE)
return FALSE;
else
return true;
}
bool setupdcb(int rate_arg)
{
DCB dcb;
int rate= rate_arg;
memset(&dcb,0,sizeof(dcb));
if(!GetCommState(hComm,&dcb))//获取当前DCB配置
{
ProcessErrorMessage("GetCommState()");
return FALSE;
}
/* -------------------------------------------------------------------- */
// set DCB to configure the serial port
dcb.DCBlength = sizeof(dcb);
/* ---------- Serial Port Config ------- */
dcb.BaudRate = rate;
dcb.Parity = NOPARITY;
dcb.fParity = 0;
dcb.StopBits = ONESTOPBIT;
dcb.ByteSize = 8;
dcb.fOutxCtsFlow = 0;
dcb.fOutxDsrFlow = 0;
dcb.fDtrControl = DTR_CONTROL_DISABLE;
dcb.fDsrSensitivity = 0;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fOutX = 0;
dcb.fInX = 0;
/* ----------------- misc parameters ----- */
dcb.fErrorChar = 0;
dcb.fBinary = 1;
dcb.fNull = 0;
dcb.fAbortOnError = 0;
dcb.wReserved = 0;
dcb.XonLim = 2;
dcb.XoffLim = 4;
dcb.XonChar = 0x13;
dcb.XoffChar = 0x19;
dcb.EvtChar = 0;
/* -------------------------------------------------------------------- */
// set DCB
if(!SetCommState(hComm,&dcb))
{
ProcessErrorMessage("SetCommState()");
return false;
}
else
return true;
}
bool setuptimeout(DWORD ReadInterval,DWORD ReadTotalMultiplier,DWORD ReadTotalconstant,DWORD WriteTotalMultiplier,DWORD WriteTotalconstant)
{
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout=ReadInterval;
timeouts.ReadTotalTimeoutConstant=ReadTotalconstant;
timeouts.ReadTotalTimeoutMultiplier=ReadTotalMultiplier;
timeouts.WriteTotalTimeoutConstant=WriteTotalconstant;
timeouts.WriteTotalTimeoutMultiplier=WriteTotalMultiplier;
if(!SetCommTimeouts(hComm, &timeouts))
{
ProcessErrorMessage("SetCommTimeouts()");
return false;
}
else
return true;
}
ReceiveChar( )
{
BOOL bRead = TRUE;
BOOL bResult = TRUE;
DWORD dwError = 0;
DWORD BytesRead = 0;
char RXBuff;
for (;;)
{
bResult = ClearCommError(hComm, &dwError, &comstat);
if (comstat.cbInQue == 0)
continue;
if (bRead)
{
bResult = ReadFile(hComm, // Handle to COMM port
&RXBuff, // RX Buffer Pointer
1, // Read one byte
&BytesRead, // Stores number of bytes read
&m_ov); // pointer to the m_ov structure
printf("%c",RXBuff);
if (!bResult)
{
switch (dwError = GetLastError())
{
case ERROR_IO_PENDING:
{
bRead = FALSE;
break;
}
default:
{
break;
}
}
}
else
{
bRead = TRUE;
}
} // close if (bRead)
if (!bRead)
{
bRead = TRUE;
bResult = GetOverlappedResult(hComm, // Handle to COMM port
&m_ov, // Overlapped structure
&BytesRead, // Stores number of bytes read
TRUE); // Wait flag
}
}
}
WriteChar(BYTE* m_szWriteBuffer,DWORD m_nToSend)
{
BOOL bWrite = TRUE;
BOOL bResult = TRUE;
DWORD BytesSent = 0;
HANDLE m_hWriteEvent;
ResetEvent(m_hWriteEvent);
if (bWrite)
{
m_ov.Offset = 0;
m_ov.OffsetHigh = 0;
// Clear buffer
bResult = WriteFile(hComm, // Handle to COMM Port
m_szWriteBuffer, // Pointer to message buffer in calling finction
m_nToSend, // Length of message to send
&BytesSent, // Where to store the number of bytes sent
&m_ov ); // Overlapped structure
if (!bResult)
{
DWORD dwError = GetLastError();
switch (dwError)
{
case ERROR_IO_PENDING:
{
// continue to GetOverlappedResults()
BytesSent = 0;
bWrite = FALSE;
break;
}
default:
{
// all other error codes
ProcessErrorMessage("WriteFile()");
}
}
}
} // end if(bWrite)
if (!bWrite)
{
bWrite = TRUE;
bResult = GetOverlappedResult(hComm, // Handle to COMM port
&m_ov, // Overlapped structure
&BytesSent, // Stores number of bytes sent
TRUE); // Wait flag
// deal with the error code
if (!bResult)
{
ProcessErrorMessage("GetOverlappedResults() in WriteFile()");
}
} // end if (!bWrite)
// Verify that the data size send equals what we tried to send
if (BytesSent != m_nToSend)
{
printf("WARNING: WriteFile() error.. Bytes Sent: %d; Message Length: %d\n", BytesSent, strlen((char*)m_szWriteBuffer));
}
return true;
}
DWORD WINAPI MyThread1(LPVOID pParam)
{
ReceiveChar();
return 0;
}
DWORD WINAPI MyThread2(LPVOID pParam)
{
while(hComm!=INVALID_HANDLE_VALUE) //串口已被成功打开
{
unsigned char d;
d=getch();
printf("%c",d);
WriteChar(&d,1);
}
return 0;
}
void main()
{
if(openport("com2"))
printf("open comport success\n");
if(setupdcb(9600))
printf("setupDCB success\n");
if(setuptimeout(0,0,0,0,0))
printf("setuptimeout success\n");
PurgeComm(hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
HANDLE hThread1=CreateThread(NULL, 0, MyThread1, 0, 0, NULL); //读线程
HANDLE hThread2=CreateThread(NULL, 0, MyThread2, 0, 0, NULL); //写线程
CloseHandle(hThread1);
CloseHandle(hThread2);
system("pause");
}