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 即可。
