本文的重点在于讲解异步重叠操作串口的思想以及过程。
所有的IO设备的操作都遵循:创建、参数设置、读写、关闭三个步骤。
知识补充:
此文中需要使用到的几个结构体:
2 DWORD fCtsHold : 1;
3 DWORD fDsrHold : 1;
4 DWORD fRlsdHold : 1;
5 DWORD fXoffHold : 1;
6 DWORD fXoffSent : 1;
7 DWORD fEof : 1;
8 DWORD fTxim : 1;
9 DWORD fReserved : 25;
10 DWORD cbInQue; // 输入缓冲区的字节数
11 DWORD cbOutQue; // 输出缓冲区的字节数
12 } COMSTAT, *LPCOMSTAT;
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;
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;
首先介绍此文中需要用到的各个API函数。
CreateFile 打开串口
GetCommState 检测串口设置
SetCommState 设置串口
GetCommTimeouts 检测通信超时设置
SetCommTimeouts 设置通信超时参数
SetCommMask 设定被监控事件
WaitCommEvent 等待被监控事件发生
WaitForMultipleObjects 等待多个被监测对象的结果
WriteFile 发送数据
ReadFile 接收数据
GetOverlappedResult 返回最后重叠(异步)操作结果
PurgeComm 清空串口缓冲区,退出所有相关操作
ClearCommError 更新串口状态结构体,并清除所有串口硬件错误
CloseHandle 关闭串行口
CreateFile 打开串口
_In_z_ LPCTSTR lpFileName, // 文件名
_In_ DWORD dwDesiredAccess, // 访问模式(GENERIC_WRITE/GENERIC_READ)。0表示仅允许获得与一个设备有关的信息
_In_ DWORD dwShareMode, // 共享模式(FILE_SHARE_READ/FILE_SHARE_WRITE)。0表述独占
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 指向安全属性的指针
_In_ DWORD dwCreationDisposition, // 创建属性 CREATE_NEW 创建如果已经存在则冲突 CREATE_ALWAYS 总是创建,存在则改写 OPEN_EXISTING 打开现有,由设备提出要求 OPEN_ALWAYS 打开,不存在则创建 TRUNCATE_EXISTING 将现有文件缩短为0长度
_In_ DWORD dwFlagsAndAttributes, 文件属性
_In_opt_ HANDLE hTemplateFile // 用于复制文件句柄,文件模板。如果不为零,则新文件将从这个文件中复制扩展属性
);
GetCommState 检测串口设置
BOOL
WINAPI
GetCommState(
__in HANDLE hFile, // 句柄
__out LPDCB lpDCB // 结构体,存储获得的信息
);
SetCommState 设置串口
BOOL
WINAPI
SetCommState(
__in HANDLE hFile, // 文件句柄
__in LPDCB lpDCB // DCP结构体指针
);
GetCommTimeouts 检测通信超时设置
BOOL
WINAPI
GetCommTimeouts(
__in HANDLE hFile, // 文件句柄
__out LPCOMMTIMEOUTS lpCommTimeouts // 时间信息结构体指针
);
SetCommTimeouts 设置通信超时参数
BOOL
WINAPI
SetCommTimeouts(
__in HANDLE hFile, // 文件句柄
__in LPCOMMTIMEOUTS lpCommTimeouts // 时间信息结构体指针
);
BOOL
WINAPI
SetCommMask(
__in HANDLE hFile, // 文件句柄
__in DWORD dwEvtMask // 监听事件类型 EV_BREAK 输入中断 EV_CTS CTS清除发送信号改变 EV_DSR 数据准备就绪信号改变 EV_ERR 总线信号发生错误 EV_RING 检测到令牌 EV_RLSD 探测到接收信号 EV_RXCHAR 有字符到达缓冲 EV_RXFLAG 有字符到达接收区,且该字符为DCB结构,用于指定串口特性 EV_TXEMPTY 最后一个字符从发送去发送完完毕
);
SetupComm 初始化通信设备参数
BOOL
WINAPI
SetupComm(
__in HANDLE hFile, // 文件句柄
__in DWORD dwInQueue, // 输入缓冲区大小
__in DWORD dwOutQueue // 输出缓冲区大小
);
WaitCommEvent 等待被监控事件发生 用来判断用SetCommMask设定的事件是否发生
BOOL
WINAPI
WaitCommEvent(
__in HANDLE hFile, // 文件句柄
__inout LPDWORD lpEvtMask, // 监听的事件
__inout_opt LPOVERLAPPED lpOverlapped // 重叠结构体指针
);
编码:
一、头文件
二、 定义变量:
2 bool m_bRecvPending; // 读取阻塞标志位
3 OVERLAPPED SendOverLapped; // 发送OverLapped结构体
4 OVERLAPPED RecvOverLapped; // 接收overLapped结构体
5 HANDLE m_hPort; // 串口句柄
三、方法体
1. 打开串口
2 // 参数:
3 // __in BSTR comName 通道名称
4 // 返回:
5 // BOOL TRUE 函数执行成功 FALSE 函数执行失败
6 BOOL CreatePort(BSTR comName)
7 {
8 m_hPort = CreateFile(comName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
9 if (m_hPort == INVALID_HANDLE_VALUE)
10 {
11 CloseHandle(m_hPort);
12 return FALSE;
13 }
14 else
15 return true;
16 }
2.设置串口参数
2 void SetParam()
3 {
4 DCB dcb;
5 GetCommState(m_hPort, &dcb); // 读取当前端口的DCB设置
6 dcb.BaudRate = CBR_9600;
7 dcb.ByteSize = 8; // 库中的选择结果是5-8,表示5-8位长
8 dcb.Parity = NOPARITY;
9 dcb.StopBits = ONESTOPBIT; // 该参数如果开放,则设置不成功
10 SetCommState(m_hPort, &dcb); // 设置当前端口的DCB设置
11
12 COMMTIMEOUTS comTimeouts;
13 GetCommTimeouts(m_hPort, &comTimeouts);
14 comTimeouts.ReadIntervalTimeout = MAXDWORD;
15 comTimeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
16 comTimeouts.ReadTotalTimeoutConstant = 0x0014;
17 comTimeouts.WriteTotalTimeoutMultiplier = MAXDWORD;
18 comTimeouts.WriteTotalTimeoutConstant = 0x0014;
19
20 // WaitCommEvent
21 SetCommMask(m_hPort, EV_RXCHAR | EV_TXEMPTY);
22 SetupComm(m_hPort, 0x0400, 0x0400);
23 PurgeComm(m_hPort, PURGE_TXABORT | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_RXCLEAR);
24 }
3. 发送数据
2 // 参数:
3 // __BYTE* szBuffer 发送字节流
4 // __DWORD iExpectCount 期望发送字节数
5 // 返回:
6 // int 上次操作实际发送字节数
7 int WriteChar(BYTE* szBuffer,DWORD iExpectCount)
8 {
9 COMSTAT ComStat;
10 DWORD dwErrorFlag;
11 DWORD dwWrittenCnt = 0;
12 BOOL bRet;
13
14 if (m_bSendPending)
15 {
16 bRet = GetOverlappedResult(m_hPort, &SendOverLapped, &dwWrittenCnt, FALSE);
17 if(bRet)
18 {
19 m_bSendPending = FALSE;
20 }
21 else
22 {
23 if (GetLastError() == ERROR_IO_INCOMPLETE)
24 return 0;
25 else
26 {
27 bRet = ClearCommError(m_hPort, &dwErrorFlag, &ComStat);
28 m_bSendPending = FALSE;
29 }
30 }
31 }
32
33 if (m_bSendPending == FALSE)
34 {
35 bRet = WriteFile(m_hPort, szBuffer, iExpectCount, &dwWrittenCnt, &SendOverLapped); // 读字节写入缓存
36 if (bRet == FALSE)
37 {
38 if (GetLastError() == ERROR_IO_PENDING)
39 m_bSendPending = TRUE;
40 else
41 bRet = ClearCommError(m_hPort, &dwErrorFlag, &ComStat);
42
43 return 0;
44 }
45 }
46
47 return (INT32)dwWrittenCnt;
48 }
4.接收数据
2 // 参数:
3 // __BYTE* szBuffer 接收数据寄存器
4 // __DWORD& iSize 接收寄存器的容量
5 // 返回:
6 // int 返回实际接收字节数
7 int ReceiveChar(BYTE* szBuffer,DWORD& iSize )
8 {
9 COMSTAT ComStat;
10 DWORD dwReadCnt = 0;
11 DWORD dwErrorFlag;
12 BOOL bRet;
13
14 if (m_bRecvPending)
15 {
16 bRet = GetOverlappedResult(m_hPort, &RecvOverLapped, &dwReadCnt, FALSE);
17 if (bRet)
18 {
19 m_bRecvPending = FALSE;
20 }
21 else
22 {
23 if (GetLastError() == ERROR_IO_INCOMPLETE)
24 return 0;
25 else
26 {
27 bRet = ClearCommError(m_hPort, &dwErrorFlag, &ComStat);
28 m_bRecvPending = FALSE;
29 }
30 }
31 }
32 if (m_bRecvPending == FALSE)
33 {
34 bRet = ReadFile(m_hPort, szBuffer, iSize, &dwReadCnt, &RecvOverLapped); // 读字节写入缓存
35 if (bRet == FALSE)
36 {
37 if (GetLastError() == ERROR_IO_PENDING)
38 m_bRecvPending = TRUE;
39 else
40 bRet = ClearCommError(m_hPort, &dwErrorFlag, &ComStat);
41
42 return 0;
43 }
44 }
45
46 return (INT32)dwReadCnt;
47 }
5. 关闭串口,释放资源
2 {
3
4 if (m_hPort != INVALID_HANDLE_VALUE)
5 {
6 CloseHandle(hPort);
7 m_hPort = INVALID_HANDLE_VALUE;
8 }
9
10 if (SendOverLapped.hEvent != INVALID_HANDLE_VALUE)
11 {
12 CloseHandle(SendOverLapped.hEvent);
13 SendOverLapped.hEvent = INVALID_HANDLE_VALUE;
14 }
15
16 if (RecvOverLapped.hEvent != INVALID_HANDLE_VALUE)
17 {
18 CloseHandle(RecvOverLapped.hEvent);
19 RecvOverLapped.hEvent = INVALID_HANDLE_VALUE;
20 }
21 }
备注:
本文中相对改革点为发送与接收数据,均采用了辅助标志位isPending,从而每次首先检查是否已经投递异步重叠操作请求,如果已经请求则直接使用
该种思路直接实现了单独两个线程实现首发的性能效果,而且避免了线程的空间资源消耗以及线程之间切换的时间性能消耗。
欢迎大家评价,对文章不足之处,希望大家能给以补充