【MFC开发】串口通信示例

最近刚学了一点关于串口方面的知识,具体关于“串口”、“USB”、“HID”等等相关词汇呢,大家可以自己去百度啥的,或者看书也可以。

这里我就分享一个简单的小操作,在MFC中,用程序去实现上位机与下位机的通信。上位机呢,就是这台电脑啦,那么下位机呢,我选了一个某宝就能买到的USB继电器。继电器是啥不知道的话可以百度一下,我也不赘述了。

这个USB继电器呢,就可以用如红圈中的“A0 01 01 A2”来实现通信,这个是16进制的。

如果你有串口调试助手的话,可以打开来,把USB继电器插到电脑上,然后按着下面这个图上的操作来,你就会听到“啪”的一声,继电器吸合了。关闭继电器也同理。也会“啪”的一声。

————————————————————————————————

那么接下来,我们就用MFC来完成这样一个操作。

工作流程:1、找到串口--------->2、打开串口----------->3、发送“开”(此处有“啪”)----------->4、发送“关”(此处也有“啪”)-------->5、关闭串口

首先,我们创建一个MFC,

对话框里放这 5 个按钮。

分别对应5个函数。

头函数加入:

#include "Dbt.h"
#include "setupapi.h"

—————————寻找继电器———————————

那么首先在寻找继电器这一步,一般呢有两种自动的做法,1、遍历USB设备   2、遍历注册表

可以参照一下这里:https://blog.csdn.net/wangningyu/article/details/78696221

遍历注册表呢,也有两种,

一种是在  \HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM  这个路径下找,只能找到有设备接入的COM口,不能看到谁接入着

另一种呢,是在  \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB ,利用对应的 PID VID找(这种方法有个小问题,就是我一个USB设备在电脑上不同口插过以后,都会被注册留下痕迹,而我用程序去找的时候只能找到展开的最上面的那个,也就是第一次插的被注册的那个)

大家想知道自己的USB继电器插在哪里呢,也很简单,人工右键“此电脑”打开“管理”,再点左边“设备管理器”,最后点“端口”

可以看到这个继电器在COM5.

具体你想用什么方式去寻找到你的设备,你可以看情况选择。

我这里用了:(如果你发现有问题,可以具体打断点看一下)

/************************************************************************/
// 根据注册表中的PID、VID信息读取
/************************************************************************/
int COpen_COMDlg::SearchRelayCOM(CString strVidPid)
{
	DWORD	dwIndex = 0;
	HKEY	hKey;
	TCHAR	CommName[_MAX_FNAME] = { 0x00 };
	DWORD	lcbName = _MAX_FNAME;
	DWORD	lValue = MAX_PATH;
	TCHAR	szValue[MAX_PATH];
	LONG	lRtn = 0;
	CString	strTemp, strName, strKey;
	int		nPort = -1;
	int		nStart = -1;
	int		nEnd = -1;

	// 读取PID对应的端口
	strTemp.Format(_T("SYSTEM\\CurrentControlSet\\Enum\\USB\\%s"), strVidPid);
	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, strTemp, NULL, KEY_READ, &hKey) == ERROR_SUCCESS)
	{
		dwIndex = 0;
		while (RegEnumKey(hKey, dwIndex++, CommName, lcbName) == ERROR_SUCCESS)
		{
			// 读取PID对应的端口
			strKey.Format(_T("SYSTEM\\CurrentControlSet\\Enum\\USB\\%s\\%s\\Device Parameters"), strVidPid, CommName);
			RegCloseKey(hKey);
			if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, strKey, NULL, KEY_READ, &hKey) == ERROR_SUCCESS)
			{
				lRtn = RegQueryValueEx(hKey, _T("PortName"), NULL, &lValue, (LPBYTE)szValue, &lValue);
				if (lRtn == ERROR_SUCCESS)
				{
					// 寻找串口信息
					strName.Format(_T("%s"), szValue);
					
					//能否找到COM(理论上是可以的)
					nStart = strName.Find(_T("COM"), 0);

					//nEnd = strName.Find(_T(")"), 0);
					
					//if (nStart == -1 || nEnd == -1)
					if (nStart == -1)
					{
						RegCloseKey(hKey);
						return -1;
					}

					//strTemp = strName.Mid(nStart + 3, nEnd - nStart);
					strTemp = strName.Mid(nStart+3, 1);
					nPort = _tstoi(LPCTSTR(strTemp));

					RegCloseKey(hKey);
					return nPort;
				}
			}

			lValue = MAX_PATH;
			lcbName = MAX_PATH;
		}

		RegCloseKey(hKey);
	}

	return nPort;
}

————————打开继电器————————

上一步,我们假设已经知道了继电器所在的COM口的编号

那么这里,我们就可以用这些代码去打开一个端口,主要用到  CreateFile这个函数。顺便在这个函数里我也分配了缓冲区大小,还设置了超时结构、最后设置了DCB。(大家可以参考《计算机高级接口技术》这本书)

void COpen_COMDlg::OnBnClickedOpen()
{

	DWORD dwError;
	DCB dcb;
	COMMTIMEOUTS Timeouts;

	CString str;
	str.Format(_T("\\\\.\\COM%d"), RelayPort);
	//str.Format(_T("\\\\.\\COM6"));

	hCom = CreateFile(  str,						//str.GetBuffer(50),
									GENERIC_READ | GENERIC_WRITE,
									0,
									NULL,
									OPEN_EXISTING,
									0, // FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
									NULL);

	if(hCom != INVALID_HANDLE_VALUE)
	{
		GetDlgItem(IDC_OPEN)->EnableWindow(FALSE);
		GetDlgItem(IDC_START)->EnableWindow(TRUE);
		GetDlgItem(IDC_CLOSE)->EnableWindow(TRUE);

		AfxMessageBox(_T("串口打开成功!"));
	}
	else
	{
		dwError = GetLastError();
		AfxMessageBox(_T("串口打开失败!"));
		return;
	} 

	SetupComm(hCom, 1024, 1024);        //分配缓冲区


    //设置超时结构
	Timeouts.ReadIntervalTimeout = 1000;
	Timeouts.ReadTotalTimeoutMultiplier = 500;
	Timeouts.ReadTotalTimeoutConstant = 500;
	Timeouts.WriteTotalTimeoutConstant = 500;
	Timeouts.WriteTotalTimeoutMultiplier = 500;
	SetCommTimeouts(hCom, &Timeouts);

    //配置串行接口(也就是改变设备控制块DCB的成员变量值)
	GetCommState(hCom, &dcb);
	dcb.BaudRate = 9600;
	dcb.ByteSize = 8;
	dcb.Parity = NOPARITY;
	dcb.StopBits = ONE5STOPBITS;
	SetCommState(hCom, &dcb);

}

—————————发送“开”——————————

串口已经被我们打开了,那么这里我们就发送一下“开”

用这个函数:(这个函数我是从一位分享了VR设备源码那看的,他的代码挺全的,可惜网页链接找不到了,抱歉)

BOOL COpen_COMDlg::ComSendPacket(unsigned char *sendbuffer, int length)
{
	DWORD dwBytesWrite = length;
	DWORD dwErrorFlags;
	BOOL bWriteStat;

	if (!sendbuffer)
		return FALSE;
	
	ResetEvent(ComWriteOverlapped.hEvent);
	//清空串口的输出缓冲区
	PurgeComm(hCom, PURGE_TXABORT | PURGE_TXCLEAR); //立即中断所有写操作并立即返回,即使写操作还没有完成 | 清空输出缓冲区
	ClearCommError(hCom, &dwErrorFlags, &ComWriteStat);

	bWriteStat = WriteFile(hCom, sendbuffer, dwBytesWrite, &dwBytesWrite, &ComWriteOverlapped);
	if (!bWriteStat)
	{
		if (GetLastError() == ERROR_IO_PENDING)
		{
			WaitForSingleObject(ComWriteOverlapped.hEvent, 2000);
		}
		else
		{
			ClearCommError(hCom, &dwErrorFlags, &ComWriteStat);
			//清空串口的输出缓冲区
			PurgeComm(hCom, PURGE_TXABORT | PURGE_TXCLEAR); //立即中断所有写操作并立即返回,即使写操作还没有完成 | 清空输出缓冲区
			ResetEvent(ComWriteOverlapped.hEvent);
			AfxMessageBox(_T("串口发送数据失败!"));
			return FALSE;
		}
	}

	ClearCommError(hCom, &dwErrorFlags, &ComWriteStat);
	//清空串口的输出缓冲区
	PurgeComm(hCom, PURGE_TXABORT | PURGE_TXCLEAR); //立即中断所有写操作并立即返回,即使写操作还没有完成 | 清空输出缓冲区
	ResetEvent(ComWriteOverlapped.hEvent);

	return TRUE;
}

然后我们给“发送开”按钮添加事件:

void COpen_COMDlg::OnBnClickedStart()
{
	unsigned char OpenRelay[] = { 0xA0, 0x01, 0x01, 0xA2 };
	while (!ComSendPacket(OpenRelay, 4))
	{
		AfxMessageBox(_T("重新发送中!"));
	}
	AfxMessageBox(_T("串口发送数据成功!"));
	
}

其实到这里,你生成一下工程,用3个按钮已经可以听到“啪”的一声了。此时继电器工作指示灯也会亮起。

———————————发送“关”————————

基本雷同上一步,改成:

unsigned char CloseRelay[] = { 0xA0, 0x01, 0x00, 0xA1 };
	while (!ComSendPacket(CloseRelay, 4))

即可。

——————————关闭串口——————————

好了现在假设我们不想玩这个串口了,那我们就要跟他好聚好散。

void COpen_COMDlg::OnBnClickedClose()
{
	//禁止串行端口所有事件 
	SetCommMask(hCom, 0);
	//清除数据终端就绪信号
	EscapeCommFunction(hCom, CLRDTR);
	//丢弃通信资源的输出或输入缓冲区字符并终止在通信资源上挂起的读、写操操作
	PurgeComm(hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
	CloseHandle(hCom);
	hCom = NULL;
	AfxMessageBox(_T("串口关闭成功!"));
	return;
}

就这样。

——————————.h 文件中————————————

我们加入:

private:
	int SearchRelayCOM(CString strVidPid);
	BOOL ComSendPacket(unsigned char *sendbuffer, int length);

	int RelayPort;
	HANDLE hCom;

	OVERLAPPED ComWriteOverlapped;
	COMSTAT ComWriteStat;

public:
	afx_msg void OnBnClickedFind();
	afx_msg void OnBnClickedOpen();
	afx_msg void OnBnClickedClose();
	afx_msg void OnBnClickedStart();
	afx_msg void OnBnClickedOver();

即可。动手试试吧,疯狂听继电器“啪”/“啪”/“啪”………

不说了,刚过完双11,哎…………T T

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值