[Windows] Windows API 串口通信
Windows 中通过Windows API 进行串口通信主要有以下步骤:
- 打开串口
- 配置串口
- 读写串口
- 关闭串口
打开串口
关键API: CreateFile
Windows 中进行设备的操作,第一步都是需要通过CreateFile 函数进行打开设备。
HANDLE WINAPI CreateFile( _In_ LPCTSTR lpFileName, _In_ DWORD dwDesiredAccess, _In_ DWORD dwShareMode, _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, _In_ DWORD dwCreationDisposition, _In_ DWORD dwFlagsAndAttributes, _In_opt_ HANDLE hTemplateFile );
具体函数说明可以参考MSDN。
此处针对串口设备,稍微解释一下各个参数:
lpFileName:串口名,常见szPort.Format(_T("\\\\.\\COM%d"), nPort),nPort 是串口号;
dwDesiredAccess:设置访问权限,一般设置为GENERIC_READ | GENERIC_WRITE,即可读可写;
dwShareMode:串口不可共享,所以这个值必须是0;
lpSecurityAttributes:文件安全模式,必须设置为NULL;
dwCreationDisposition:创建方式,串口必须是OPEN_EXISTING;
dwFlagsAndAttributes:涉及到同步操作和异步操作的概念,具体可参考MSDN。一般如果同步的话就是设置为0;如果异步设置为FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED;
hTemplateFile:文件句柄,对于串口通信必须设置为NULL。
通过判断函数返回值是否是有效的handle,判断是否有成功打开串口设备。
配置串口
关键数据结构:DCB structure; COMMTIMEOUTS structure
关键API:BuildCommDCB; GetCommState; SetCommState; SetupComm; SetCommTimeouts
DCB structure:
DCB结构体中包含了许多信息,对于串口而已主要有波特率、数据位数、奇偶校验和停止位数等信息。在查询或配置串口的属性时,都需要使用到DCB 结构体。
在使用SetCommState对端口进行配置前,需要使用BuildCommDCB 先build 好DCB 结构体;或是使用GetCommState 拿到DCB 结构体,然后再相应修改对应数据。
一般在使用SetCommState 配置串口后,还需要使用SetupComm 设置串口的缓冲区大小。
COMMTIMEOUTS structure:
这个结构体和SetCommTimeouts 函数主要是用来设置读写超时的信息的,可以具体参考MSDN。
其中读写串口的超时有两种:间隔超时和总超时。
- 间隔超时:是指在接收时两个字符之间的最大时延。
- 总超时:是指读写操作总共花费的最大时间。写操作只支持总超时,而读操作两种超时均支持。
参考代码
BOOL OpenComDev(int nPort, LPCTSTR lpDef, int nControl) { CloseComDev(); // DCB dcb; CString szPort; CString szDcb; szPort.Format(_T("\\\\.\\COM%d"), nPort); if (lpDef == NULL) { szDcb.Format(_T("baud=1200 parity=N data=8 stop=1")); } else { szDcb = lpDef; } m_hDev = CreateFile( szPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL ); if (m_hDev == INVALID_HANDLE_VALUE) { DWORD dwError = GetLastError(); return FALSE; } COMMTIMEOUTS CommTimeOuts; CommTimeOuts.ReadIntervalTimeout = MAXDWORD; //0xFFFFFFFF; CommTimeOuts.ReadTotalTimeoutMultiplier = 0; CommTimeOuts.ReadTotalTimeoutConstant = 0; CommTimeOuts.WriteTotalTimeoutMultiplier = 0; CommTimeOuts.WriteTotalTimeoutConstant = 5000; SetCommTimeouts( m_hDev, &CommTimeOuts ); FillMemory(&dcb, sizeof(dcb), 0); dcb.DCBlength = sizeof(dcb); if (!BuildCommDCB(szDcb, &dcb)) { goto _Fail; } // DCB is ready for use. if (!SetCommState(m_hDev, &dcb ) || !SetupComm(m_hDev, 1024*16, 1024*16)) { DWORD dwError = GetLastError(); goto _Fail; } return TRUE; _Fail: CloseComDev(); return FALSE; }
读写串口
关键数据结构:OVERLAPPED structure(当采用异步读写时需要)
关键API:ReadFile, WriteFile
在读写串口时,要注意是同步操作还是异步操作,这个是由上文"打开串口"中的CreateFile 参数决定的。
同步读写操作简单,当调用ReadFile 和 WriteFile 时会阻塞,直到处理结束这两个函数才会完成;
异步操作时,调用ReadFile 和 WriteFile 时会立刻返回,费事的IO操作将在后台执行,此时需要自己去设置Event 等去进行同步等待。
下面主要是异步操作的code,其中异步操作需要使用到OVERLAPPED structure,且event 是定义的全局变量。
BOOL UART_ReadData(HANDLE hIDComDev, LPVOID lpBuffer, int num) { if (hIDComDev == NULL) return FALSE; OVERLAPPED overlapped; memset(&overlapped, 0, sizeof(OVERLAPPED)); overlapped.hEvent = g_hReadEvent; ResetEvent(overlapped.hEvent); BOOL bReadStatus; DWORD dwBytesRead, dwErrorFlags; COMSTAT ComStat; ClearCommError(hIDComDev, &dwErrorFlags, &ComStat); if (!ComStat.cbInQue) return FALSE; dwBytesRead = (DWORD)ComStat.cbInQue; if (num < (int) dwBytesRead) dwBytesRead = (DWORD)num; bReadStatus = ReadFile(hIDComDev, lpBuffer, dwBytesRead, &dwBytesRead, &overlapped); if (!bReadStatus) { if (GetLastError() == ERROR_IO_PENDING) { WaitForSingleObject(overlapped.hEvent, 2000); return (int)dwBytesRead; } return FALSE; } return dwBytesRead; } BOOL UART_WriteData(HANDLE hIDComDev, LPCVOID lpBuffer, int num ) { if (hIDComDev == NULL) return FALSE ; BOOL bWriteStat; DWORD dwBytesWritten; OVERLAPPED overlapped; memset(&overlapped, 0, sizeof(OVERLAPPED)); overlapped.hEvent = g_hWriteEvent; ResetEvent(overlapped.hEvent); bWriteStat = WriteFile(hIDComDev, (LPVOID) lpBuffer, num, &dwBytesWritten, &overlapped); if (!bWriteStat && (GetLastError() == ERROR_IO_PENDING)) { if (WaitForSingleObject(overlapped.hEvent, 2000)) { dwBytesWritten = 0; } else { GetOverlappedResult(hIDComDev, &overlapped, &dwBytesWritten, FALSE); overlapped.Offset += dwBytesWritten; } } return dwBytesWritten; }
关闭串口
关键API:CloseHandle
关闭串口很简单,只是将上文中"打开串口" 中获得的Handle 正确close 即可。