一、系统概述
使用MSComm控件实现PC串口通信,采用中断方式接收数据,支持数据发送、接收、显示和存储功能。适用于工业控制、数据采集等场景。
二、实现原理
2.1 MSComm控件特性
- 事件驱动机制:通过OnComm事件处理通信事件
- 支持查询和中断两种数据接收方式
- 提供丰富的串口参数配置选项
- 支持RTS/CTS硬件握手协议
2.2 中断接收机制
单片机 PC端(MSComm) 单片机 PC端(MSComm) 数据到达触发OnComm事件 发送请求命令(0x01) 返回数据(温度值) 执行OnComm事件处理函数 解析数据并更新界面
三、完整VC++实现代码
3.1 资源文件定义 (Resource.h)
cpp
#define IDD_MAIN_DIALOG 102
#define IDC_MSCOMM1 1001
#define IDC_BUTTON_OPEN 1002
#define IDC_BUTTON_CLOSE 1003
#define IDC_BUTTON_SEND 1004
#define IDC_EDIT_SEND 1005
#define IDC_EDIT_RECEIVE 1006
#define IDC_COMBO_COM 1007
#define IDC_COMBO_BAUD 1008
#define IDC_CHECK_HEX_SEND 1009
#define IDC_CHECK_HEX_RECEIVE 1010
#define IDC_BUTTON_CLEAR 1011
#define IDC_BUTTON_SAVE 1012
#define IDC_STATUS_BAR 1013
3.2 主对话框类头文件 (MainDlg.h)
cpp
#if !defined(AFX_MAINDLG_H__)
#define AFX_MAINDLG_H__
#include <afxwin.h>
#include <afxdisp.h>
#include <afxcmn.h>
#include <mscomm.h>
class CMainDlg : public CDialog {
public:
CMainDlg(CWnd* pParent = NULL);
enum { IDD = IDD_MAIN_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
// 控件变量
CMSComm m_mscom;
CComboBox m_comboCom;
CComboBox m_comboBaud;
CEdit m_editSend;
CEdit m_editReceive;
CButton m_checkHexSend;
CButton m_checkHexReceive;
CStatusBar m_statusBar;
// 消息处理
afx_msg void OnButtonOpen();
afx_msg void OnButtonClose();
afx_msg void OnButtonSend();
afx_msg void OnButtonClear();
afx_msg void OnButtonSave();
afx_msg void OnCommMscomm();
afx_msg void OnClose();
// 辅助函数
void UpdateStatusBar(LPCTSTR msg);
void ProcessReceivedData();
CString FormatHexData(const BYTE* data, int len);
DECLARE_MESSAGE_MAP()
DECLARE_EVENTSINK_MAP()
};
#endif
3.3 主对话框实现文件 (MainDlg.cpp)
cpp
#include "stdafx.h"
#include "SerialComm.h"
#include "MainDlg.h"
#include <afxmt.h>
#include <fstream>
// 事件映射
BEGIN_EVENTSINK_MAP(CMainDlg, CDialog)
ON_EVENT(CMainDlg, IDC_MSCOMM1, 1, OnCommMscomm, VTS_NONE)
END_EVENTSINK_MAP()
// 消息映射
BEGIN_MESSAGE_MAP(CMainDlg, CDialog)
ON_BN_CLICKED(IDC_BUTTON_OPEN, OnButtonOpen)
ON_BN_CLICKED(IDC_BUTTON_CLOSE, OnButtonClose)
ON_BN_CLICKED(IDC_BUTTON_SEND, OnButtonSend)
ON_BN_CLICKED(IDC_BUTTON_CLEAR, OnButtonClear)
ON_BN_CLICKED(IDC_BUTTON_SAVE, OnButtonSave)
ON_WM_CLOSE()
END_MESSAGE_MAP()
// 构造函数
CMainDlg::CMainDlg(CWnd* pParent) : CDialog(CMainDlg::IDD, pParent) {
m_mscom.m_bAutoSize = TRUE;
}
// 数据交换
void CMainDlg::DoDataExchange(CDataExchange* pDX) {
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_MSCOMM1, m_mscom);
DDX_Control(pDX, IDC_COMBO_COM, m_comboCom);
DDX_Control(pDX, IDC_COMBO_BAUD, m_comboBaud);
DDX_Control(pDX, IDC_EDIT_SEND, m_editSend);
DDX_Control(pDX, IDC_EDIT_RECEIVE, m_editReceive);
DDX_Control(pDX, IDC_CHECK_HEX_SEND, m_checkHexSend);
DDX_Control(pDX, IDC_CHECK_HEX_RECEIVE, m_checkHexReceive);
DDX_Control(pDX, IDC_STATUS_BAR, m_statusBar);
}
// 初始化对话框
BOOL CMainDlg::OnInitDialog() {
CDialog::OnInitDialog();
// 初始化状态栏
if (!m_statusBar.Create(this) ||
!m_statusBar.SetIndicators(NULL, 1)) {
TRACE0("Failed to create status bar\n");
return FALSE;
}
m_statusBar.SetPaneInfo(0, 0, SBPS_STRETCH, 0);
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0);
UpdateStatusBar(_T("就绪"));
// 初始化串口列表
for (int i = 1; i <= 16; i++) {
CString str;
str.Format(_T("COM%d"), i);
m_comboCom.AddString(str);
}
m_comboCom.SetCurSel(0);
// 初始化波特率列表
CString baudRates[] = {_T("1200"), _T("2400"), _T("4800"),
_T("9600"), _T("19200"), _T("38400"),
_T("57600"), _T("115200")};
for (int i = 0; i < 8; i++) {
m_comboBaud.AddString(baudRates[i]);
}
m_comboBaud.SetCurSel(3); // 默认9600
// 初始化MSComm控件
if (!m_mscom.Create(NULL, WS_VISIBLE | WS_CHILD, CRect(0,0,0,0), this, IDC_MSCOMM1)) {
AfxMessageBox(_T("无法创建MSComm控件!"));
return FALSE;
}
// 设置默认参数
m_mscom.SetCommPort(1); // 默认COM1
m_mscom.SetSettings(_T("9600,n,8,1")); // 默认参数
m_mscom.SetInputMode(1); // 二进制模式
m_mscom.SetRThreshold(1); // 每接收1个字符触发事件
m_mscom.SetSThreshold(0); // 不触发发送事件
m_mscom.SetPortOpen(FALSE); // 初始关闭
return TRUE;
}
// 打开串口
void CMainDlg::OnButtonOpen() {
CString strCom, strBaud;
m_comboCom.GetWindowText(strCom);
m_comboBaud.GetWindowText(strBaud);
int nCom = _ttoi(strCom.Mid(3));
CString strSettings = strBaud + _T(",n,8,1");
// 设置串口参数
if (m_mscom.GetPortOpen()) {
m_mscom.SetPortOpen(FALSE);
}
m_mscom.SetCommPort(nCom);
m_mscom.SetSettings(strSettings);
// 打开串口
if (!m_mscom.GetPortOpen()) {
if (m_mscom.SetPortOpen(TRUE)) {
UpdateStatusBar(_T("串口已打开: ") + strCom + _T(" ") + strBaud);
GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(TRUE);
} else {
AfxMessageBox(_T("无法打开串口!"));
}
}
}
// 关闭串口
void CMainDlg::OnButtonClose() {
if (m_mscom.GetPortOpen()) {
m_mscom.SetPortOpen(FALSE);
UpdateStatusBar(_T("串口已关闭"));
GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(FALSE);
}
}
// 发送数据
void CMainDlg::OnButtonSend() {
if (!m_mscom.GetPortOpen()) {
AfxMessageBox(_T("串口未打开!"));
return;
}
CString strSend;
m_editSend.GetWindowText(strSend);
if (strSend.IsEmpty()) {
return;
}
// 十六进制发送处理
if (m_checkHexSend.GetCheck()) {
CString strHex = strSend;
strHex.Remove(' ');
strHex.Remove('-');
if (strHex.GetLength() % 2 != 0) {
AfxMessageBox(_T("HEX数据长度必须为偶数!"));
return;
}
int len = strHex.GetLength() / 2;
BYTE* data = new BYTE[len];
for (int i = 0; i < len; i++) {
CString byteStr = strHex.Mid(i*2, 2);
data[i] = (BYTE)strtol(byteStr, NULL, 16);
}
m_mscom.SetOutput(COleVariant((BYTE*)data, len));
delete[] data;
}
// 文本发送
else {
m_mscom.SetOutput(COleVariant(strSend));
}
// 更新状态栏
CString statusMsg;
statusMsg.Format(_T("已发送: %s"), strSend);
UpdateStatusBar(statusMsg);
}
// 清空接收区
void CMainDlg::OnButtonClear() {
m_editReceive.SetWindowText(_T(""));
}
// 保存数据
void CMainDlg::OnButtonSave() {
CString strData;
m_editReceive.GetWindowText(strData);
if (strData.IsEmpty()) {
AfxMessageBox(_T("接收区为空!"));
return;
}
CFileDialog dlg(FALSE, _T("txt"), _T("serial_data.txt"),
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
_T("文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*||"));
if (dlg.DoModal() == IDOK) {
CStdioFile file;
if (file.Open(dlg.GetPathName(), CFile::modeCreate | CFile::modeWrite)) {
file.WriteString(strData);
file.Close();
UpdateStatusBar(_T("数据已保存: ") + dlg.GetFileName());
} else {
AfxMessageBox(_T("保存文件失败!"));
}
}
}
// 关闭窗口
void CMainDlg::OnClose() {
if (m_mscom.GetPortOpen()) {
m_mscom.SetPortOpen(FALSE);
}
CDialog::OnClose();
}
// 串口事件处理 (中断方式接收数据)
void CMainDlg::OnCommMscomm() {
VARIANT variant_inp;
COleSafeArray safearray_inp;
LONG len, k;
BYTE rxdata[2048]; // 接收缓冲区
CString strTemp;
switch (m_mscom.GetCommEvent()) {
case 2: // 接收事件
variant_inp = m_mscom.GetInput();
safearray_inp = variant_inp;
len = safearray_inp.GetOneDimSize();
// 数据复制到缓冲区
for (k = 0; k < len; k++) {
safearray_inp.GetElement(&k, rxdata + k);
}
// 处理接收到的数据
ProcessReceivedData(rxdata, len);
break;
case 4: // 发送事件
UpdateStatusBar(_T("数据发送完成"));
break;
case 5: // 错误事件
UpdateStatusBar(_T("通信错误!"));
break;
default:
break;
}
}
// 处理接收数据
void CMainDlg::ProcessReceivedData(BYTE* data, int len) {
CString strDisplay;
// 十六进制显示
if (m_checkHexReceive.GetCheck()) {
strDisplay = FormatHexData(data, len);
}
// 文本显示
else {
// 转换为CString
data[len] = '\0';
strDisplay = CString(data);
}
// 追加到接收区
CString strOld;
m_editReceive.GetWindowText(strOld);
if (!strOld.IsEmpty()) {
strOld += _T("\r\n");
}
m_editReceive.SetWindowText(strOld + strDisplay);
// 滚动到最后一行
int nLength = m_editReceive.GetWindowTextLength();
m_editReceive.SetSel(nLength, nLength);
m_editReceive.ReplaceSel(_T(""));
// 更新状态栏
CString statusMsg;
statusMsg.Format(_T("收到 %d 字节数据"), len);
UpdateStatusBar(statusMsg);
}
// 格式化十六进制数据
CString CMainDlg::FormatHexData(const BYTE* data, int len) {
CString strHex;
for (int i = 0; i < len; i++) {
CString byteStr;
byteStr.Format(_T("%02X "), data[i]);
strHex += byteStr;
if ((i+1) % 16 == 0) {
strHex += _T("\r\n");
}
}
return strHex;
}
// 更新状态栏
void CMainDlg::UpdateStatusBar(LPCTSTR msg) {
m_statusBar.SetPaneText(0, msg);
m_statusBar.RedrawWindow();
}
3.4 应用程序类 (SerialCommApp.cpp)
cpp
#include "stdafx.h"
#include "SerialComm.h"
#include "MainDlg.h"
BEGIN_MESSAGE_MAP(CSerialCommApp, CWinApp)
ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()
CSerialCommApp::CSerialCommApp() {
m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;
}
CSerialCommApp theApp;
BOOL CSerialCommApp::InitInstance() {
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);
CWinApp::InitInstance();
CMainDlg dlg;
m_pMainWnd = &dlg;
dlg.DoModal();
return FALSE;
}
参考代码 基于MSComm控件的PC串口通信原程序,利用中断方式接收数据 www.youwenfan.com/contentcst/124006.html
四、使用说明
4.1 系统要求
- Windows操作系统 (XP/7/10/11)
- Visual C++ 6.0 或更高版本
- MSComm控件 (mscomm32.ocx)
4.2 安装步骤
-
注册MSComm控件:
regsvr32 mscomm32.ocx -
在VC++项目中:
-
添加mscomm.h和mscomm.cpp到项目
-
在stdafx.h中添加:
cpp#include <mscomm.h> #pragma comment(lib, "mscomm.lib")
-
4.3 操作流程
- 选择串口号和波特率
- 点击"打开串口"按钮
- 在发送区输入数据(文本或HEX格式)
- 点击"发送"按钮
- 接收区显示返回数据
- 使用"清空"、"保存"按钮管理数据
4.4 功能特点
- 中断方式接收:通过OnComm事件实时处理数据
- 双模式显示:支持文本和十六进制显示
- 双模式发送:支持文本和十六进制发送
- 数据管理:支持清空和保存接收数据
- 状态显示:实时显示通信状态
五、通信协议示例
5.1 与51单片机通信协议
| 方向 | 数据格式 | 说明 |
|---|---|---|
| PC→MCU | 0x01 | 请求温度数据 |
| MCU→PC | [温度值高8位][低8位] | 16位温度数据(0.1℃精度) |
| PC→MCU | 0x02 | 设置参数 |
| MCU→PC | [状态码] | 操作结果(0=成功,1=失败) |
5.2 数据解析示例
cpp
// 在OnCommMscomm中添加数据处理
case 2: // 接收事件
// ... 接收数据到rxdata
// 示例:解析温度数据
if (len == 2) {
short temp_raw = (rxdata[0] << 8) | rxdata[1];
float temperature = temp_raw / 10.0f;
CString strTemp;
strTemp.Format(_T("温度: %.1f°C"), temperature);
// 显示温度值...
}
break;
六、常见问题解决
6.1 控件注册失败
- 以管理员身份运行命令提示符
- 检查ocx文件路径是否正确
- 使用Dependency Walker检查依赖项
6.2 无法打开串口
- 检查串口号是否正确
- 确认没有其他程序占用串口
- 检查串口线连接是否正常
- 尝试降低波特率
6.3 数据接收不全
- 增加接收缓冲区大小
- 检查RThreshold设置(建议1-10)
- 使用轮询方式补充接收
- 检查硬件流控制设置
七、扩展功能
7.1 添加数据校验
cpp
// 在发送数据前添加校验和
BYTE checksum = 0;
for (int i = 0; i < len; i++) {
checksum ^= data[i];
}
m_mscom.SetOutput(COleVariant((BYTE*)data, len));
m_mscom.SetOutput(COleVariant(checksum));
7.2 添加自动发送功能
cpp
// 在OnInitDialog中添加定时器
SetTimer(1, 1000, NULL); // 1秒定时
// 添加定时器处理
void CMainDlg::OnTimer(UINT_PTR nIDEvent) {
if (nIDEvent == 1 && m_mscom.GetPortOpen()) {
// 自动发送请求
BYTE cmd = 0x01;
m_mscom.SetOutput(COleVariant(cmd));
}
CDialog::OnTimer(nIDEvent);
}
7.3 添加数据绘图功能
cpp
// 添加绘图控件
#include <afxcmn.h>
#include <ChartCtrl.h> // 使用第三方图表控件
// 在接收数据中更新图表
CChartCtrl m_chart;
m_chart.AddPoint(x, y); // 添加新数据点
m_chart.Invalidate();
八、总结
本程序基于MSComm控件实现了PC串口通信的中断方式数据接收,具有以下特点:
- 高效中断机制:通过OnComm事件实时处理数据
- 灵活的数据显示:支持文本和十六进制双模式
- 完善的数据管理:提供发送、接收、保存功能
- 友好的用户界面:状态栏实时反馈系统状态
- 良好的扩展性:可方便添加新功能