欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[Windows] Windows API 串口通信

程序员文章站 2022-07-05 11:06:45
...

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