您的位置:首页 > 其它

Windows api 串口通信

2015-04-19 11:13 218 查看
转载请注明出处

作者:小马

转自:http://blog.csdn.net/pony_maggie/article/details/6199453

自己写的一个的串口操作类

一 打开串口

打开串口用CreateFile, 关健是几个参数该怎么填. CreateFile有7个参数.

[cpp]
view plaincopy





LPCTSTR lpFileName
这个填串口号, 比如你用com1, 传进来”com1”就可以了.这里有一点要注意,如果你的串口号是com1到com9, 按上面说的传参没有问题, 如果 com9以上,比如com10, 就要用下面这样的形式,” ////.//COM10 ”

DWORD dwDesiredAccess
访问方式, 读,写或可读可写, 一般我们用串口,既要发送数据又会接收据, 可以设置为

GENERIC_READ | GENERIC_WRITE.
DWORD dwShareMode
共享模式, 在串口操作中,这个值一般设置为0, 表示不能共享,独占. 这个道理很容易明白,串口是硬件的I/O操作,读写时肯定要独占.

LPSECURITY_ATTRIBUTES lpSecurityAttributes
这个值在串口应用中,一般不必关心,直接置为null.

DWORD dwCreationDisposition
这个表示如果文件存在或不存在时,createfile的动作, 很明显,在串口应用中,OPEN_EXISTING,表示打开已存在的串口. 如果串口不存在,不可能去创建一个吧.

DWORD dwFlagsAndAttributes
文件的属性和标志位, 在串口应用中, 有两种操作模式, 同步(Nonoverlapped)和异步(overlapped), 同步该值为0, 异步模式, 该值可设置为FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED或FILE_FLAG_OVERLAPPED.

HANDLE hTemplateFile
这个值直接置为null.

下面解释一下同步和异步的概念.

拿发送数据举个例子, 同步模式下,发送函数返回(可能成功或失败), 操作才完成,否则当前发送线程会阻塞,直到发送完成. 而异步模式通过事件通知来避免阻塞,你可以在发送数据的同时,做其它事情,然后检测事件来判断操作是否完成. 关于同步和异步,在进发送和接收数据时,还有更详细的描述.

该函数如果成功,返回一个有效的串口句柄,否则返回INVALID_HANDLE_VALUE, 注意不是FALSE.

二 串口参数设置

波特率,停止位,字节大小,奇偶校验

这几个参数都存在于一个叫DCB的结构体内, 我们只需给这几个变量赋值, 然后调用SetCommState就可以了. 这里有一点要注意, 因为DCB结构体中还有其它成员变量, 对于我们不需要改变的,要知道它们原来的值,以免调用SetCommState之后出错. 所以,一般是用如下的方式设置这几个成员.

[cpp]
view plaincopy





DCB dcb;
FillMemory(&dcb, sizeof(dcb), 0);//初始化为空
if (!GetCommState(hComm, &dcb)) // 取当前dcb设置
// Error in GetCommState
return FALSE;
// 更新自己要设置的值
dcb.BaudRate = CBR_9600 ;//波特率,9600
dcb.Parity = NOPARITY; //无奇偶校验

通过上面方法,就可以避免更改其它的成员.

超时时间包括写超时和读超时,都在COMMTIMEOUTS这个结构体中的成员中。

WriteTotalTimeoutMultiplier和WriteTotalTimeoutConstant是和写操作有关的超时时间.

一次写操作的超时时间为WriteTotalTimeoutMultiplier*(字符数)+WriteTotalTimeoutConstant.

比如要发送10个字节,

WriteTotalTimeoutMultiplier = 100,

WriteTotalTimeoutConstant = 2000.

那么写操作超超时间为100*10+2000 = 3000ms.

在同步模式下, 如果3000ms内没有发送完数据,WriteFile函数会超时返回,可通过函数返回的实际发送的字节数来判断发送是否完成.

在异步模式下, 情况比较复杂一点, 下面讲到发送数据的时候再详述.

如果WriteTotalTimeoutMultiplier和WriteTotalTimeoutConstant的值都为0,表示写操作无超时时间.

ReadIntervalTimeout, ReadTotalTimeoutMultiplier, ReadTotalTimeoutConstant三个变量是和读操作有关的超时时间. 后面两个变量用法与写操作类似. 第一个变量, 表示读数据时,两个字节之间的间隔时间,如果该值为0,表示间隔超时不使用.

如果是下面这样的设置:

[cpp]
view plaincopy





CommTimeOuts.ReadIntervalTimeout = MAXWORD;
CommTimeOuts.ReadTotalTimeoutMultiplier = 0;
CommTimeOuts.ReadTotalTimeoutConstant = 0;

读一次缓冲区操作立即返回, 不管读到的是什么,读到多少个字节. 这样的设置还是比较常用的.

超时时间的设置比较灵活, 具体如何设置要根据实际应用, 比如有些应用中,用户在上层自己控制读和写的超时时间, 这种情况下,上面的设置意义就不大.

发送数据

往串口发送数据用writefile, 有如下几个参数:

[cpp]
view plaincopy





HANDLE hFile
之前用CreateFile打开的有效的串口句柄.

LPCVOID lpBuffer
待发送的数据缓冲区. 把要发送的数据放在这里.

DWORD nNumberOfBytesToWrite
这里指定要发送的字节数, 可以比发送缓冲区总字节数小,具体看实际应用中要发多少.

LPDWORD lpNumberOfBytesWritten
该值由函数返回, 指明函数返回时,实际发送成功的字节数. 这个值很有用, 有些时候,函数返回TRUE, 但由于一些原因(比如超时), 实际发送的少于nNumberOfBytesToWrite, 这种情况要继续发送.

LPOVERLAPPED lpOverlapped
这个参数指出当前发送数据,是用异步,还是用同步. 如果是前者, 传进一个指向OVERLAPPED变量的指针, 如果是后者,该值为NULL.

在同步模式下, 发送数据是比较简单的. 可以用类似下面的形式:

[cpp]
view plaincopy





If(!WriteFile(hComm, buffer, strlen(buffer), &dwBytesWritten, NULL))
{
//错误处理.
}

一般情况下,WriteFile都会返回TRUE,数据就会按指定的长度发送成功. 如果想更安全一些,也可以加入类似下面这样的判断.

[cpp]
view plaincopy





If(dwBytesWritten != strlen(buffer))
{
//错误处理
}

在同步模式下,WriteFile会一直等待指定的数据发送完毕,直到超时,如果未指定超超时间,数据发送完毕之前,程序会一直挂起.

异步模式下的处理稍复杂一些. 首先, 如果WriteFile返回FALSE,不一定表示发送失败, 可以用GetLastError获取错误码,如果错误码是ERROR_IO_PENDING,表示操作正在进行, 这种不是真正的失败. 在错误码是ERROR_IO_PENDING的情况下, 可以用WaitForSingleObject等待操作完成.

如果你做过windows下的多线程应用,应该比较熟悉这个函数. 通过串口发送数据时, 这个函数类似下面这样用:

[cpp]
view plaincopy





dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);
switch(dwRes)
{
// OVERLAPPED structure's event has been signaled.
case WAIT_OBJECT_0:
//处理
break;

default:
//错误处理
break;
}

第一个参数osWrite.hEvent是要等待的事件, 第二个参数是超时时间. 要注意这个超时时间并不是前面讲到的读写超时时间. 它是WaitForSingleObject等待osWrite.hEvent变为有信号的时间.

举个例子,如果设置的写超超时间是5秒, WaitForSingleObject第二个参数用INFINITE, 如果5秒写操作还没有完成,这时出现写超时, 但WaitForSingleObject返回的是WAIT_OBJECT_0,而不是WAIT_TIMEOUT. 因为对WaitForSingleObject来说, 写操作已经完成了.为了避免混淆,建议WaitForSingleObject的第二个参数用INFINITE.

还有一点要注意, WaitForSingleObject返回WAIT_OBJECT_0, 并不表示发送是成功的, 只说明发送数据的操作完成了. 是否成功, 还要通过另一个函数GetOverlappedResult来判断. 这个函数有必要说明一下, 它有如下几个参数:

[cpp]
view plaincopy





HANDLE hFile
串口句柄

LPOVERLAPPED lpOverlapped
指向OVERLAPPED变量的指针

LPDWORD lpNumberOfBytesTransferred
这个值返回实际发送的字节数

BOOL bWait
如果该值是TRUE,这个函数会一直等待操作完成才返回, 如果是FALSE, 该函数立即返回, 如果这个时候操作还没有完成, 函数返回FALSE, 用GetLastError可以获取错误码是ERROR_IO_INCOMPLETE

如果你细心的话可能已经发现, GetOverlappedResult也要传进一个指向OVERLAPPED变量的指针, 加上对bWait参数的描述, 很明显,这个函数跟WaitForSingleObject功能基本相同, 是的,在串口应用中,它们两个的用法基本相同, 当bWait为TRUE时, 跟WaitForSingleObject的第二个参数用INFINITE效果是一样的.

但GetOverlappedResult可以判读操作是否成功, 方法就是当WaitForSingleObject返回WAIT_OBJECT_0时,调用GetOverlappedResult,如果它返回TRUE就表示操作成功完成, 否则表示操作失败. 调用过程类似下面的形式:

[cpp]
view plaincopy





dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);
switch(dwRes)
{

case WAIT_OBJECT_0:
if (!GetOverlappedResult(hComm, &osWrite, &dwWritten, FALSE))
//操作失败
else
//操作成功完成
break;

default:
// An error has occurred in WaitForSingleObject.
//错误处理

break;
}

这样,在异步模式下的写操作, 就可以用类似下面这样的语句来实现:

[cpp]
view plaincopy





if (!WriteFile(hComm, lpBuf, dwToWrite, &dwWritten, &osWrite)) {
if (GetLastError() != ERROR_IO_PENDING)
{
//真正的出错情况
fRes = FALSE;
}
else// 操作还未完成, 等待操作完成
dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);
switch(dwRes)
{
case WAIT_OBJECT_0:// 写操作完成, 但不确定是否成功?
if (!GetOverlappedResult(hComm, &osWrite, &dwWritten, FALSE))
{
//操作失败,错误处理
}
else
//操作成功
break;

default:
//错误处理
break;
}
}
}

有时候看到一些串口通信的代码并没有上面那样繁琐的判断. 有些只用了WaitForSingleObject,或者只用了GetOverlappedResult, 其实也是可以的, 有些简单的应用场合并没有必要做这么细致的流程设计, 上述流程是MSDN推荐的,应该算是比较完整和健壮的.

接收数据

接收数据的操作跟发送数据差别不大,在接收数据之前, 一般会先用ClearCommError读取接收缓冲区中有多少未读的数据(未用ReadFile读取), 然后读取这个长度的数据. 如果读到的数据长度小于自己想要读的长度,则继续读.

ClearCommError获取接收缓冲区未读数据原理是传出一个指向COMSTAT 的参数变量, COMSTAT中的cbInQ ue成员就是当前未被读出的接收到的数据长度.

这里有一个问题要讨论一下, 系统什么时候置cbInQue不为零呢, 答案是, 它跟前面讲到的读字节超时间隔有关系, 它的机制是几个byte超时时间内没有收到数据,认为这一次接收完成,然后用当前接收到的字节数置cbInQue.

举个例子, 设备返回给主机的字节一共有10个, 每隔100ms返回一个, 如果, ReadIntervalTimeout设置为10ms, 那么, 主机读到一个字节就会置cbInQue为1, 如果ReadIntervalTimeout设置大一些,比如100ms, 那么主机会读完10个字节才会置cbInQue为10.实际应用中,ReadIntervalTimeout应该设置大一些还是小一些要根据不同应用场合. 明白了原理,就可以灵活的应对各种应用

其实对于串口通信, 还有更复杂的高阶应用,在这些应用中会涉及到多线程, 窗体的消息传递与通知(与MFC结合)等, 如果有时间,后续我会接着写相关的文章. 但用上面讲到的那些,设计一个简单实用的串口通信类应该不成问题.

源码下载地址:
https://github.com/pony-maggie/LKE_lke2600_CardReader
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: