串口通信助手
编译环境
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界面
- 创建MFC应用程序(基于对话框,此处的名称为SerialPort注意与上方的类CSerialPort进行区分)
- 两个编辑框(一个只读,一个可编辑,多行,可下拉)
- 加入combo box(用来代表所选择的串口)
- 界面如下
- 串口,所选的串口
- 波特率,每秒所发的字节数(若太多误差高,工业一般2400左右的比特位,抗干扰强)
- 校验位,奇偶校验,本串发送的字符中,二进制有多少个零,或者多少个1
- 数据位,多少位代表是一个数据位
- 停止位,看单片机的串口的一些数据的含义
- 注意下拉框的属性,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; //新增
结果演示
-
打开虚拟串口
COM1、2就串起来了 -
先将生成的exe串口助手复制一个出来(可以不用复制,运行两次也可以),运行一个,将串口设置成COM1,再打开串口(打开后,文字自动变为关闭串口)
如下
现在再运行一下exe,打开第二个程序,将串口设置为COM2,打开串口,在编辑框发送文字信息
两个接口COM1和COM2可以相互发送数据,在虚拟串口被连接起来了
此时串口通信演示完毕 -
最后生成的源代码文件资源链接如下
https://download.csdn.net/download/qq_44870829/13098778.