串口通信助手

编译环境

VS2017+MFC

CThread类

CThread.h

#pragma once
#include "CThread.h"

class CThread
{
public:
	CThread(void);
	~CThread(void);
public:
	HANDLE m_hThread;
	bool m_bExit;
	DWORD m_dwParam;		//将外面的一些东西传递到参数里面去
	
public:
	void Start();	//启动线程
	void Stop();	//结束线程
	
public:
	virtual void SetThreadData(DWORD dwParam);
	virtual DWORD GetThreadData();

public:
	virtual void run();

public:
	static DWORD ThreadProc(LPVOID pParam);
};

CThread.cpp

#include "pch.h"
#include "CThread.h"

CThread::CThread() {
	m_hThread = NULL;
	m_bExit = FALSE;
	m_dwParam = 0;
}

CThread::~CThread() {
	if (!m_bExit) {
		Stop();
	}
}

void CThread::Start() {
	DWORD dwThreadID=0;
	HANDLE hThread = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc,this,0,&dwThreadID);
	ASSERT(hThread);
	m_hThread = hThread;
}

void CThread::Stop() {
	if (m_hThread) {
		m_bExit = true;
		::WaitForSingleObject(m_hThread, INFINITE);	//必须等待线程全部退出后,再进行线程关闭,资源释放
		::CloseHandle(m_hThread);
		m_hThread = NULL;
	}

}

DWORD CThread::ThreadProc(LPVOID pParam) {
	CThread* pThis = (CThread*)pParam;
	ASSERT(pThis);

	while (!pThis->m_bExit) {
		pThis->run();
	}
	return true;
}

void CThread::run() {
	Sleep(100);
}


void CThread::SetThreadData(DWORD dwParam) {
	if (m_dwParam != dwParam) {
		m_dwParam = dwParam;
	}
}

DWORD CThread::GetThreadData() {
	return m_dwParam;
}

CThreadComm类

CThreadComm.h

#pragma once
#include "CThread.h"

class CThreadComm:public CThread
{
public:
	CThreadComm();
	~CThreadComm();

public:
	virtual void run();
};

CThreadComm.cpp

#include "pch.h"
#include "CThreadComm.h"
#include "resource.h"
#include "SerialPortDlg.h"
#include "CSerialPort.h"

CThreadComm::CThreadComm() {

}
CThreadComm::~CThreadComm() {

}

void CThreadComm::run() {
	//获取串口对象指针

	Sleep(100);

	//获取全局对话框的对象
	CSerialPortDlg* pWinDemoDlg = (CSerialPortDlg*)AfxGetApp()->m_pMainWnd;
	if (NULL == pWinDemoDlg) {
		return;
	}

	CSerialPort* pSerialPort = (CSerialPort*)GetThreadData();
	if (NULL == pSerialPort) return;

	DWORD dwError = 0;
	COMSTAT comStat;
	BOOL bRet = TRUE;
	TCHAR recvTemp[512];
	ZeroMemory(recvTemp, sizeof(recvTemp));		//临时缓冲区

	TCHAR recvBuf[4096];
	ZeroMemory(recvBuf, sizeof(recvBuf));		//真正缓冲区

	DWORD dwRead = 0;
	int nLength = 0;

	pSerialPort->ClearCommError(&dwError, &comStat);
	if (comStat.cbInQue>0) {
		OVERLAPPED overlappedRead;
		ZeroMemory(&overlappedRead, sizeof(OVERLAPPED));	//清除
		overlappedRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

		if (comStat.cbInQue < 512) {
			bRet = pSerialPort->ReadFile(recvTemp, comStat.cbInQue, &dwRead, &overlappedRead);
		}
		else {
			bRet= pSerialPort->ReadFile(recvTemp, 500, &dwRead, &overlappedRead);
		}

		//判断是否大于等于自己读的字节,若是的话
		if (comStat.cbInQue >= dwRead) {
			
			memcpy(recvBuf + nLength, recvTemp, dwRead);
			//将临时数据放到缓冲区最后面,一旦设置完,数据长度要加dwread
			nLength += dwRead;
		}

		CloseHandle(overlappedRead.hEvent);		//要将句柄关闭,否则会产生内存泄露

		if (comStat.cbInQue == dwRead) {
			nLength = 0;
			CEdit* pEditRecv = (CEdit*)pWinDemoDlg->GetDlgItem(IDC_EDIT_RECV);
			if (pEditRecv) {
				CString strRecv;
				pEditRecv->GetWindowText(strRecv);
				strRecv += recvBuf;
				pEditRecv->SetWindowTextW(strRecv);
			}
			
		}

		if (!bRet) {
			if (ERROR_IO_PENDING == GetLastError()) {		//如果数据被挂起,说明还有数据
				while (!bRet) {
					bRet = pSerialPort->GetOverlappedResult(NULL, &dwRead, TRUE);
					if (GetLastError()!=ERROR_IO_INCOMPLETE) {	//如果数据没有
						pSerialPort->ClearCommError(&dwError, &comStat);
						break;
					}
				}
			}
		}
	}
}

CSerialPort类

CSerialPort.h

#pragma once
#include "CThreadComm.h"

//实现win32串口的面向对象的封装
class CSerialPort
{
public:
	CSerialPort(void);
	~CSerialPort(void);
public:
	HANDLE m_hComm;   //用于保存串口句柄
	CThreadComm m_ThreadComm;
public:
	BOOL OpenComm(CString strComm);	//打开指定串口
	BOOL SetCommState(DWORD dwBaudrate,BYTE byParity,BYTE byByteSize,BYTE byStopBits);
	BOOL SetupComm(DWORD dwInqueue, DWORD dwOutQueue);
	BOOL PurgeComm(DWORD dwFlags);
	BOOL SetCommMask(DWORD dwEvtMask);//设置串口的事件类型
	BOOL WriteFile(IN LPCVOID lpBuffer,IN DWORD nNumberOfBytesToWrite,OUT LPDWORD lpNumberOfBytesWritten,IN LPOVERLAPPED lpOverlapped);
	BOOL ReadFile(OUT LPVOID lpBuffer,IN DWORD nNumberOfBytesToRead,OUT LPDWORD nNumberOfBytesRead,IN LPOVERLAPPED lpOverlapped);
	BOOL ClearCommError(OUT LPDWORD lpErrors, OUT LPCOMSTAT lpStat);
	BOOL GetOverlappedResult(IN LPOVERLAPPED lpOverlapped,OUT LPDWORD lpNumberOfBytesTransferred,IN BOOL bwait);//获取结果
	void CloseComm();//关闭窗口

public:
	void StartComm();
};

CSerialPort.cpp

#include "pch.h"
#include "CSerialPort.h"
#include "CThreadComm.h"

CSerialPort::CSerialPort() {
	m_hComm=NULL;   
}

CSerialPort::~CSerialPort() {
	CloseComm();
}


void CSerialPort::StartComm() {
	m_ThreadComm.SetThreadData((DWORD)this);
	m_ThreadComm.Start();
}

//打开指定串口
BOOL CSerialPort::OpenComm(CString strComm){
	//打开串口(串口在windows是以文件形式存在的)
	if (NULL == m_hComm) {
		m_hComm = CreateFile((TCHAR*)(LPCTSTR)strComm,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,0);		//打开串口就是创建文件,此文件可读可写
		//如果返回无效的句柄值
		if (INVALID_HANDLE_VALUE == m_hComm) {
			int nError = GetLastError();		//获得错误类型
			m_hComm = NULL;
			return FALSE;
		}
		return TRUE;
	}
	return FALSE;
}

//设置
BOOL CSerialPort::SetCommState(DWORD dwBaudrate, BYTE byParity, BYTE byByteSize, BYTE byStopBits) {
	if (NULL == m_hComm) return FALSE;

	//DCB块中返回串口的所有属性
	DCB dcb;
	BOOL bRet = ::GetCommState(m_hComm, &dcb);
	if (!bRet) {
		if (m_hComm) {
			//如果返回为空,且句柄存在,释放句柄
			CloseHandle(m_hComm);
			m_hComm = NULL;
		}
		return FALSE;
	}

	dcb.BaudRate = dwBaudrate;
	dcb.ByteSize = byByteSize;
	dcb.Parity = byParity;
	dcb.StopBits = byStopBits;
	bRet = ::SetCommState(m_hComm,&dcb);		//设置串口是dcb块(使用win32的标准函数)
	if (!bRet) {
		if (m_hComm) {
			//如果返回为空,且句柄存在,释放句柄
			CloseHandle(m_hComm);
			m_hComm = NULL;
		}
		return FALSE;
	}

	return TRUE;
}

//
BOOL CSerialPort::SetupComm(DWORD dwInqueue, DWORD dwOutQueue) {
	if (NULL == m_hComm) return FALSE;

	return ::SetupComm(m_hComm, dwInqueue, dwOutQueue);
}

//
BOOL CSerialPort::PurgeComm(DWORD dwFlags) {
	if (NULL == m_hComm) return FALSE;

	return ::PurgeComm(m_hComm, dwFlags);
}

BOOL CSerialPort::SetCommMask(DWORD dwEvtMask) {
	if (NULL == m_hComm) return FALSE;

	return ::SetCommMask(m_hComm, dwEvtMask);
}

BOOL CSerialPort::WriteFile(IN LPCVOID lpBuffer, IN DWORD nNumberOfBytesToWrite, OUT LPDWORD lpNumberOfBytesWritten, IN LPOVERLAPPED lpOverlapped) {
	if (NULL == m_hComm) return FALSE;

	return ::WriteFile(m_hComm,lpBuffer,nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
}

BOOL CSerialPort::ReadFile(OUT LPVOID lpBuffer, IN DWORD nNumberOfBytesToRead, OUT LPDWORD nNumberOfBytesRead, IN LPOVERLAPPED lpOverlapped) {
	if (NULL == m_hComm) return FALSE;

	return ::ReadFile(m_hComm,lpBuffer, nNumberOfBytesToRead, nNumberOfBytesRead, lpOverlapped);
}

BOOL CSerialPort::ClearCommError(OUT LPDWORD lpErrors, OUT LPCOMSTAT lpStat) {
	if (NULL == m_hComm) return FALSE;

	return ::ClearCommError(m_hComm,lpErrors, lpStat);
}

BOOL CSerialPort::GetOverlappedResult(IN LPOVERLAPPED lpOverlapped, OUT LPDWORD lpNumberOfBytesTransferred, IN BOOL bwait) {
	if (NULL == m_hComm) return FALSE;

	return ::GetOverlappedResult(m_hComm,lpOverlapped,lpNumberOfBytesTransferred,bwait);
}

void CSerialPort::CloseComm() {
	m_ThreadComm.Stop();			//关闭线程
	//串口结束后,要将串口资源释放
	//判断是否存在,存在则释放
	if (m_hComm) {
		CloseHandle(m_hComm);
		m_hComm = NULL;
	}
}

MFC界面

  1. 创建MFC应用程序(基于对话框,此处的名称为SerialPort注意与上方的类CSerialPort进行区分)
  2. 两个编辑框(一个只读,一个可编辑,多行,可下拉)
  3. 加入combo box(用来代表所选择的串口)
  4. 界面如下
    在这里插入图片描述
  5. 串口,所选的串口
  6. 波特率,每秒所发的字节数(若太多误差高,工业一般2400左右的比特位,抗干扰强)
  7. 校验位,奇偶校验,本串发送的字符中,二进制有多少个零,或者多少个1
  8. 数据位,多少位代表是一个数据位
  9. 停止位,看单片机的串口的一些数据的含义
  10. 注意下拉框的属性,sort设为false不进行自动排序,type设为drop list就无法进行编辑了

SerialPortDlg主对话框头文件

SerialPortDlg.h主对话框头文件

新增了如下函数

public:
	void InitComboBox();		//用于初始化窗口
	afx_msg void OnBnClickedBtnCommcontrol();	//打开/关闭串口
	afx_msg void OnBnClickedBtnSend();	//发送数据

SerialPortDlg.cpp主对话框cpp文件

此处主要是出用于初始化下拉框的函数(可自己添加),以及两个按钮的事件响应函数(直接双击按钮即可出现)

//初始化下拉框的
void CSerialPortDlg::InitComboBox() {
	初始化串口的控件
	CComboBox* pComboComm = (CComboBox*)GetDlgItem(IDC_COMBO_COMM);
	ASSERT(pComboComm);
	//接下来for8个窗口的循环
	//将1-8的串口加入到下拉框中
	for (int i = 1; i <= 8;i++) {
		CString strComm;
		strComm.Format(_T("COM%d"),i);
		pComboComm->AddString(strComm);
	}
	pComboComm->SetCurSel(0);				//设置默认显示第一个串口

	初始化波特率(波特率无规律,下方是一个个的添加,所有可能的波特率)
	CComboBox* pComboBaudrate= (CComboBox*)GetDlgItem(IDC_COMBO_BAUDRATE);
	ASSERT(pComboBaudrate);
	pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("300")),300);	//对其进行关联,将下拉框中的300字符关联值300
	pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("600")), 600);
	pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("1200")), 1200);
	pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("2400")), 2400);
	pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("4800")), 4800);
	pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("9600")), 9600);
	pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("19200")), 19200);
	pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("38400")), 38400);
	pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("43000")), 43000);
	pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("56000")), 56000);
	pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("57600")), 57600);
	pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("115200")), 115200);
	pComboBaudrate->SetCurSel(5);		//默认显示9600

	初始化校验位
	CComboBox* pComboCheckbit = (CComboBox*)GetDlgItem(IDC_COMBO_CHECKBIT);
	ASSERT(pComboCheckbit);
	pComboCheckbit->SetItemData(pComboCheckbit->AddString(_T("无None")), NOPARITY);	//对其进行关联,将下拉框中的无None字符关联值0
	pComboCheckbit->SetItemData(pComboCheckbit->AddString(_T("奇ODD")), ODDPARITY);	//1
	pComboCheckbit->SetItemData(pComboCheckbit->AddString(_T("偶EVEN")), EVENPARITY);	//2
	pComboCheckbit->SetCurSel(0);		//默认显示

	初始化数据位
	CComboBox* pComboDataBit = (CComboBox*)GetDlgItem(IDC_COMBO_DATABIT);
	ASSERT(pComboDataBit);
	pComboDataBit->SetItemData(pComboDataBit->AddString(_T("6")), 6);	//对其进行关联,将下拉框中的6字符关联值6
	pComboDataBit->SetItemData(pComboDataBit->AddString(_T("7")), 7);	//7
	pComboDataBit->SetItemData(pComboDataBit->AddString(_T("8")), 8);	//8
	pComboDataBit->SetCurSel(2);		//默认显示8

	初始化停止位
	CComboBox* pComboStopBit = (CComboBox*)GetDlgItem(IDC_COMBO_STOPBIT);
	ASSERT(pComboStopBit);
	pComboStopBit->SetItemData(pComboStopBit->AddString(_T("1")), ONESTOPBIT);	//1个停止位
	pComboStopBit->SetItemData(pComboStopBit->AddString(_T("2")), TWOSTOPBITS);	//2个停止位
	pComboStopBit->SetCurSel(0);		//默认显示1
}

//打开/关闭串口
void CSerialPortDlg::OnBnClickedBtnCommcontrol()
{
	// TODO: 在此添加控件通知处理程序代码
	static BOOL bIsOpen = FALSE;
	CButton* pBtnCommControl = (CButton*)GetDlgItem(IDC_BTN_COMMCONTROL);
	ASSERT(pBtnCommControl);

	CComboBox* pComboBoxComm = (CComboBox*)GetDlgItem(IDC_COMBO_COMM);
	ASSERT(pComboBoxComm);
	int nSel = pComboBoxComm->GetCurSel();
	CString strComm;
	pComboBoxComm->GetLBText(nSel, strComm);

	CComboBox* pComboBoxBaudrate = (CComboBox*)GetDlgItem(IDC_COMBO_BAUDRATE);
	ASSERT(pComboBoxBaudrate);
	nSel = pComboBoxBaudrate->GetCurSel();
	DWORD dwBaudrate = (DWORD)pComboBoxBaudrate->GetItemData(nSel);

	CComboBox* pComboBoxCheckbit = (CComboBox*)GetDlgItem(IDC_COMBO_CHECKBIT);
	ASSERT(pComboBoxCheckbit);
	nSel = pComboBoxCheckbit->GetCurSel();
	BYTE byParity = (BYTE)pComboBoxCheckbit->GetItemData(nSel);

	CComboBox* pComboBoxDatabit = (CComboBox*)GetDlgItem(IDC_COMBO_DATABIT);
	ASSERT(pComboBoxDatabit);
	nSel = pComboBoxDatabit->GetCurSel();
	BYTE byDataSize = (BYTE)pComboBoxDatabit->GetItemData(nSel);

	CComboBox* pComboBoxStopbit = (CComboBox*)GetDlgItem(IDC_COMBO_STOPBIT);
	ASSERT(pComboBoxStopbit);
	nSel = pComboBoxStopbit->GetCurSel();
	BYTE byStopBits = (BYTE)pComboBoxStopbit->GetItemData(nSel);

	//若打开则将按钮文字变为关闭
	if (!bIsOpen) {
		//打开串口时,需产生全局的串口对象,应在全局的app中声明,并在h文件中extern 

		bIsOpen = gSerialPort.OpenComm(strComm);
		if(bIsOpen){
			BOOL bRet = gSerialPort.SetCommState(dwBaudrate, byParity, byDataSize, byStopBits);
			if (!bRet) {
				gSerialPort.CloseComm();
				AfxMessageBox(_T("设置串口属性失败!"));
				return;
			}

			bRet = gSerialPort.SetupComm(1024, 1024);
			if (!bRet) {
				gSerialPort.CloseComm();
				AfxMessageBox(_T("设置串口输入输出缓冲区失败!"));
				return;
			}

			bRet = gSerialPort.PurgeComm(PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_TXABORT);
			if (!bRet) {
				gSerialPort.CloseComm();
				AfxMessageBox(_T("无法清除串口的错误状态!"));
				return;
			}

			//设置串口要接收的事情
			bRet = gSerialPort.SetCommMask(EV_RXCHAR);
			if (!bRet) {
				gSerialPort.CloseComm();
				AfxMessageBox(_T("设置串口事件出错!"));
				return;
			}

			gSerialPort.StartComm();

			bIsOpen = TRUE;
			pBtnCommControl->SetWindowTextW(_T("关闭串口"));
		}
		else {
			//如果打开失败还要继续打开窗口
			pBtnCommControl->SetWindowTextW(_T("S e t W"));
		}
	}
	else {
		//若之前的窗口打开就关闭此窗口
		gSerialPort.CloseComm();
		bIsOpen = FALSE;
		pBtnCommControl->SetWindowTextW(_T("S e t W"));

		//打开窗口后,就收发数据,在run函数中实现
	}
}

//发送数据
void CSerialPortDlg::OnBnClickedBtnSend()
{
	// TODO: 在此添加控件通知处理程序代码
	if (NULL == gSerialPort.m_hComm) {
		AfxMessageBox(_T("请打开串口后发送数据!"));
		return;
	}

	CEdit* pEditSend = (CEdit*)GetDlgItem(IDC_EDIT_SEND);
	ASSERT(pEditSend);;
	CEdit* pEditRecv = (CEdit*)GetDlgItem(IDC_EDIT_RECV);
	ASSERT(pEditRecv);

	//
	CString strSend;
	CString strRecv;
	pEditSend->GetWindowTextW(strSend);
	strSend = strSend.Trim();
	if (strSend.IsEmpty()) {
		return;
	}

	OVERLAPPED overlappedWrite;
	ZeroMemory(&overlappedWrite, sizeof(OVERLAPPED));
	overlappedWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

	int nLen = (strSend.GetLength() + 1) * sizeof(TCHAR);
	DWORD dwWrite = 0;
	gSerialPort.WriteFile((TCHAR*)strSend.GetBuffer(),nLen,&dwWrite,&overlappedWrite);

	pEditSend->SetWindowTextW(_T(""));

	pEditRecv->GetWindowTextW(strRecv);
	strRecv += strSend;
	pEditRecv->SetWindowTextW(strRecv);

	CloseHandle(overlappedWrite.hEvent);		//要将句柄关闭,否则会产生内存泄露
	Sleep(100);
}

SerialPort的主应用程序类

新增的某些类型

SerialPort.h

/*省略*/
#include "resource.h"		// 主符号
#include "CSerialPort.h"		//新增
/*省略*/
extern CSerialPortApp theApp;
extern CSerialPort  gSerialPort;	//新增

SerialPort.cpp

// 唯一的 CSerialPortApp 对象

CSerialPortApp theApp;
CSerialPort  gSerialPort;	//新增

结果演示

  1. 打开虚拟串口
    在这里插入图片描述
    COM1、2就串起来了

  2. 先将生成的exe串口助手复制一个出来(可以不用复制,运行两次也可以),运行一个,将串口设置成COM1,再打开串口(打开后,文字自动变为关闭串口)
    如下
    现在再运行一下exe,打开第二个程序,将串口设置为COM2,打开串口,在编辑框发送文字信息
    在这里插入图片描述
    两个接口COM1和COM2可以相互发送数据,在虚拟串口被连接起来了
    此时串口通信演示完毕

  3. 最后生成的源代码文件资源链接如下
    https://download.csdn.net/download/qq_44870829/13098778.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吾名招财

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值