[Windows] Windows API 串口通信

Windows 中通过Windows API 进行串口通信主要有以下步骤:

  1. 打开串口
  2. 配置串口
  3. 读写串口
  4. 关闭串口

打开串口

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

posted on 2016-09-23 14:41  chen-jiao  阅读(5525)  评论(0编辑  收藏  举报