转载请注明出处
作者:小马
转自:http://blog.youkuaiyun.com/pony_maggie/article/details/6199453
自己写的一个的串口操作类
一 打开串口
打开串口用CreateFile, 关健是几个参数该怎么填. CreateFile有7个参数.
- 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之后出错. 所以,一般是用如下的方式设置这几个成员.
- 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,表示间隔超时不使用.
如果是下面这样的设置:
- CommTimeOuts.ReadIntervalTimeout = MAXWORD;
- CommTimeOuts.ReadTotalTimeoutMultiplier = 0;
- CommTimeOuts.ReadTotalTimeoutConstant = 0;
读一次缓冲区操作立即返回, 不管读到的是什么,读到多少个字节. 这样的设置还是比较常用的.
超时时间的设置比较灵活, 具体如何设置要根据实际应用, 比如有些应用中,用户在上层自己控制读和写的超时时间, 这种情况下,上面的设置意义就不大.
发送数据
往串口发送数据用writefile, 有如下几个参数:
- HANDLE hFile
- 之前用CreateFile打开的有效的串口句柄.
- LPCVOID lpBuffer
- 待发送的数据缓冲区. 把要发送的数据放在这里.
- DWORD nNumberOfBytesToWrite
- 这里指定要发送的字节数, 可以比发送缓冲区总字节数小,具体看实际应用中要发多少.
- LPDWORD lpNumberOfBytesWritten
- 该值由函数返回, 指明函数返回时,实际发送成功的字节数. 这个值很有用, 有些时候,函数返回TRUE, 但由于一些原因(比如超时), 实际发送的少于nNumberOfBytesToWrite, 这种情况要继续发送.
- LPOVERLAPPED lpOverlapped
- 这个参数指出当前发送数据,是用异步,还是用同步. 如果是前者, 传进一个指向OVERLAPPED变量的指针, 如果是后者,该值为NULL.
在同步模式下, 发送数据是比较简单的. 可以用类似下面的形式:
- If(!WriteFile(hComm, buffer, strlen(buffer), &dwBytesWritten, NULL))
- {
- //错误处理.
- }
一般情况下,WriteFile都会返回TRUE,数据就会按指定的长度发送成功. 如果想更安全一些,也可以加入类似下面这样的判断.
- If(dwBytesWritten != strlen(buffer))
- {
- //错误处理
- }
在同步模式下,WriteFile会一直等待指定的数据发送完毕,直到超时,如果未指定超超时间,数据发送完毕之前,程序会一直挂起.
异步模式下的处理稍复杂一些. 首先, 如果WriteFile返回FALSE,不一定表示发送失败, 可以用GetLastError获取错误码,如果错误码是ERROR_IO_PENDING,表示操作正在进行, 这种不是真正的失败. 在错误码是ERROR_IO_PENDING的情况下, 可以用WaitForSingleObject等待操作完成.
如果你做过windows下的多线程应用,应该比较熟悉这个函数. 通过串口发送数据时, 这个函数类似下面这样用:
- 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来判断. 这个函数有必要说明一下, 它有如下几个参数:
- 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就表示操作成功完成, 否则表示操作失败. 调用过程类似下面的形式:
- 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;
- }
这样,在异步模式下的写操作, 就可以用类似下面这样的语句来实现:
- 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