接上一篇文章使用VC2010实现基于MSComm32控件的串口通讯,本篇文章介绍一下如何使用Windows API接口来实现串口通讯的功能。
注:串口通讯可以实现同步和异步两种方式,这里本人主要介绍的是串口的异步通讯。
和直接MSComm32控件的接口不同,若使用Windows API接口来实现串口通讯的话,若想深刻的认识其中的道理,则我们需要了解我们所需要的API接口的功能。读者除了借鉴本篇文章的介绍之外,还需要不断地百度和MSDN一下相关的知识,因为只有这样才能明白其中的奥秘。当然,若读者只是图个使用的话,那就没那个必要了。
在此,本人介绍一下实现串口通讯功能需要用到的一些API接口。
一、打开串口操作及设置串口
1、CreateFile() 该函是一个多功能的函数,除了可以打开和创建我们常用的文件之外,还可以用于对I/O设备的操作,在对串口进行操作之前,就需要先用该函数来打开串口。
volatile HANDLE m_hCom;
m_hCom = ::CreateFile(strCom, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
这里值得注意的是必须使用“OPEN_EXISTING”来打开,因为串口是已经存在的设备;
由于我们要实现异步操作串口,所以得加上“FILE_FLAG_OVERLAPPED”参数。
2、SetCommMask() 该函数用于设置我们要在串口上监听的事件,一般要设置“EV_RXCHAR|EV_TXEMPTY”,这个参数组合表示在串口 “输入缓冲区中已收到数据,即接收到一个字节并放入输入缓冲区”和“输出缓冲区中的数据已被完全送出”这两种情况下会触发串口事件。当然你也可以不使用此函数来设置串口监听事件,而在你觉得必要的时候再进行串口操作。
::SetCommMask(m_hCom, EV_RXCHAR|EV_TXEMPTY)
3、SetupComm() 该函数可以设置串口的输入和输出缓冲区的大小。
4、PurgeComm() 该函数可以用于终止当前的串口操作并清空串口缓冲区。
::SetupComm(m_hCom, MAX_BUFFER, MAX_BUFFER); ::PurgeComm(m_hCom, PURGE_TXCLEAR|PURGE_RXCLEAR|PURGE_RXABORT|PURGE_TXABORT);
5、SetCommTimeouts() 该函数用于设置串口操作超时,涉及到的参数为主要有结构体COMMTIMEOUTS
typedef struct _COMMTIMEOUTS { DWORD ReadIntervalTimeout; /* Maximum time between read chars. */ DWORD ReadTotalTimeoutMultiplier; /* Multiplier of characters. */ DWORD ReadTotalTimeoutConstant; /* Constant in milliseconds. */ DWORD WriteTotalTimeoutMultiplier; /* Multiplier of characters. */ DWORD WriteTotalTimeoutConstant; /* Constant in milliseconds. */ } COMMTIMEOUTS,*LPCOMMTIMEOUTS;
每个参的单位都为毫秒。
ReadIntervalTimeout 参数用于设置每读取两个字符之间的最大间隔,若该参数设置为0,则该参数不起作用。
ReadTotalTimeoutMultiplier 用于计算读操作总超时,对于每次读取操作,用该参数的值乘以所需要读取的字节数。
ReadTotalTimeoutConstant 用于计算读操作总超时,对于每次读取操作,用该参数加上上面参数计算出的结果就是总超时,即,总超时 = ReadTotalTimeoutMultiplier * 需要读取的字节数 + ReadTotalTimeoutConstant。
WriteTotalTimeoutMultiplier 用于计算写操作总超时,对于每次写操作,用该参数的值乘以所需要写的字节数。
WriteTotalTimeoutConstant 用于计算写操作总超时,对于每次写操作,用该参数加上上面参数计算出的结果就是总超时,即,总超时 = WriteTotalTimeoutMultiplier* 需要读取的字节数 + WriteTotalTimeoutConstant 。
以下链接是MSDN文档对该结构体的具体说明。
http://msdn.microsoft.com/en-us/library/windows/desktop/aa363190(v=vs.85).aspx
// 设置读写操作的超时 COMMTIMEOUTS timeout; timeout.ReadIntervalTimeout = 2;//COMMTIMEOUTS的几个参数要仔细设置,ReadIntervalTimeout可以设置为0,此时该参数被忽略,但是当使用ReadFile来读取串口数据时,若接受缓冲区里想要读取的字节数大于串口缓冲区的字节数是,将会一直等待 总超时 = ReadTotalTimeoutMultiplier * 要接收的字节数 + ReadTotalTimeoutConstant 这么多时间才会完成接收; 若ReadIntervalTimeout设置合理,比如为5,则会一接收完所有数据就完成. timeout.ReadTotalTimeoutMultiplier = 3; timeout.ReadTotalTimeoutConstant = 1000; timeout.WriteTotalTimeoutMultiplier = 3; timeout.WriteTotalTimeoutConstant = 1000;
6、SetCommState() 该函数用于配置串口功能的设备控制块, 涉及到的参数为主要有结构体DCB
typedef struct _DCB { DWORD DCBlength; /* sizeof(DCB) */ DWORD BaudRate; /* Baudrate at which running */ DWORD fBinary: 1; /* Binary Mode (skip EOF check) */ DWORD fParity: 1; /* Enable parity checking */ DWORD fOutxCtsFlow:1; /* CTS handshaking on output */ DWORD fOutxDsrFlow:1; /* DSR handshaking on output */ DWORD fDtrControl:2; /* DTR Flow control */ DWORD fDsrSensitivity:1; /* DSR Sensitivity */ DWORD fTXContinueOnXoff: 1; /* Continue TX when Xoff sent */ DWORD fOutX: 1; /* Enable output X-ON/X-OFF */ DWORD fInX: 1; /* Enable input X-ON/X-OFF */ DWORD fErrorChar: 1; /* Enable Err Replacement */ DWORD fNull: 1; /* Enable Null stripping */ DWORD fRtsControl:2; /* Rts Flow control */ DWORD fAbortOnError:1; /* Abort all reads and writes on Error */ DWORD fDummy2:17; /* Reserved */ WORD wReserved; /* Not currently used */ WORD XonLim; /* Transmit X-ON threshold */ WORD XoffLim; /* Transmit X-OFF threshold */ BYTE ByteSize; /* Number of bits/byte, 4-8 */ BYTE Parity; /* 0-4=None,Odd,Even,Mark,Space */ BYTE StopBits; /* 0,1,2 = 1, 1.5, 2 */ char XonChar; /* Tx and Rx X-ON character */ char XoffChar; /* Tx and Rx X-OFF character */ char ErrorChar; /* Error replacement char */ char EofChar; /* End of Input character */ char EvtChar; /* Received Event character */ WORD wReserved1; /* Fill for now. */ } DCB, *LPDCB;
一般来说,我们只需要设置里面的 BaudRate、ByteSize、Parity、StopBits、fBinary、fParity 这几个参数就可以了。
// 配置串口参数 DCB dcb; ::GetCommState(m_hCom, &dcb); dcb.BaudRate = BaudRate; dcb.ByteSize = ByteSize; dcb.Parity = Parity; dcb.StopBits = StopBits; dcb.fBinary = fBinary; dcb.fParity = fParity; BOOL bState = ::SetCommState(m_hCom, &dcb);
7、CreateEvent() 创建一个事件对象,我们可以用这个函数来创建一个具有重叠功能的对象。一般来说,我们要对串口进行读操作和写操作,所以一般会创建两个重叠对象。
OVERLAPPED m_olRead, m_olSend; m_olRead.hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL); m_olSend.hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
以上介绍的几个函数主要用于当我们打开一个串口时需要涉及到的函数。
二、写串口操作
1、WriteFile() 跟写文件操作一样,我们使用该函数对串口进行写操作,即往串口发送数据。
WriteFile( __in HANDLE hFile, __in_bcount_opt(nNumberOfBytesToWrite) LPCVOID lpBuffer, __in DWORD nNumberOfBytesToWrite, __out_opt LPDWORD lpNumberOfBytesWritten, __inout_opt LPOVERLAPPED lpOverlapped );
需要注意的是该函数的最后一个参数,因为我们要实现异步功能,所以需要给它传入一个指向 OVERLAPPED 结构的指针,即我们可以把上面创建的m_olSend对象的指针传进去。
2、GetOverlappedResult() 该函数很重要,它用于判断一个重叠操作当前的状态,读者若想深入了解的话,可以看下以下的链接,那里有对该函数比较详细的说明。
http://blog.sina.com.cn/s/blog_412ef3420100lprm.html
bState = ::WriteFile(m_hCom, pszData, strlen(pszData), &dwBytesWriten, &m_olSend);// 因为是异步操作串口,所以会立即返回,一般都是FALSE,然后在下面无限等待此次写串口结束 if(!bState) { if(::GetLastError() == ERROR_IO_PENDING) { // -----------无限等待重叠操作结果 bState = ::GetOverlappedResult(m_hCom, &m_olSend, &dwBytesWriten, TRUE); } }
三、读串口操作
1、ReadFile() 跟读文件操作一样,我们使用该函数对串口进行读操作,即从串口读取数据。
ReadFile( __in HANDLE hFile, __out_bcount_part_opt(nNumberOfBytesToRead, *lpNumberOfBytesRead) __out_data_source(FILE) LPVOID lpBuffer, __in DWORD nNumberOfBytesToRead, __out_opt LPDWORD lpNumberOfBytesRead, __inout_opt LPOVERLAPPED lpOverlapped );
需要注意的是该函数的最后一个参数,因为我们要实现异步功能,所以需要给它传入一个指向 OVERLAPPED 结构的指针,即我们可以把上面创建的m_olRead对象的指针传进去。
2、ClearCommError() 该函数用于清除硬件的通讯错误以及获取通讯设备的当前状态,涉及到的结构体有
typedef struct _COMSTAT { DWORD fCtsHold : 1; DWORD fDsrHold : 1; DWORD fRlsdHold : 1; DWORD fXoffHold : 1; DWORD fXoffSent : 1; DWORD fEof : 1; DWORD fTxim : 1; DWORD fReserved : 25; DWORD cbInQue; DWORD cbOutQue; } COMSTAT, *LPCOMSTAT;
在这里,我们基本上只要用到里面的 cbInQue 成员就可以了,即用于查看当前串口接收缓冲区里有多少字节数。
::ClearCommError(m_hCom, &dwErrorFlags, &ComStat);//清除串口的错误并返回此时串口的状态 dwLength = ComStat.cbInQue;//串口输入缓冲区的字节数 if(dwLength > 0) { if( dwLength >= MAX_BUFFER) dwLength = MAX_BUFFER - 1;//保留最后一位用于放置 ‘\0‘ bState = ::ReadFile(m_hCom, pszDataRecv, dwLength, &dwBytesRead, &m_olRead);// 因为是异步操作串口,所以会立即返回,一般都是FALSE,然后在下面无限等待此次读串口结束 if(!bState) { if(::GetLastError() == ERROR_IO_PENDING) { // -----------无限等待重叠操作结果 bState = ::GetOverlappedResult(m_hCom, &m_olRead, &dwBytesRead, TRUE); } if(!bState) AfxMessageBox(" 接收数据失败 !"); } }
四、关闭串口操作
1、CloseHandle() 该函数主要用于关闭一个内核对象,我们可以使用它来关闭和串口相关的对象。
if( m_hCom == INVALID_HANDLE_VALUE ) return; ::CloseHandle(m_hCom); m_hCom = (HANDLE)-1; ::CloseHandle(m_olSend.hEvent); ::CloseHandle(m_olRead.hEvent);
好了,以上基本上介绍了我们常用的串口操作功能,若读者觉得需要的话,可以从以下链接下载本人在学习过程中编写的一个串口操作实例;里面还有本人自己写的一个 串口类 ,读者可以根据需要自行修改,然后便可以将它移植到自己的项目中去。
http://download.csdn.net/detail/xbmoxia/7107603