基于MSComm控件的PC串口通信程序(中断方式接收数据)

一、系统概述

使用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 安装步骤

  1. 注册MSComm控件:

    复制代码
    regsvr32 mscomm32.ocx
  2. 在VC++项目中:

    • 添加mscomm.h和mscomm.cpp到项目

    • 在stdafx.h中添加:

      cpp 复制代码
      #include <mscomm.h>
      #pragma comment(lib, "mscomm.lib")

4.3 操作流程

  1. 选择串口号和波特率
  2. 点击"打开串口"按钮
  3. 在发送区输入数据(文本或HEX格式)
  4. 点击"发送"按钮
  5. 接收区显示返回数据
  6. 使用"清空"、"保存"按钮管理数据

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串口通信的中断方式数据接收,具有以下特点:

  1. 高效中断机制:通过OnComm事件实时处理数据
  2. 灵活的数据显示:支持文本和十六进制双模式
  3. 完善的数据管理:提供发送、接收、保存功能
  4. 友好的用户界面:状态栏实时反馈系统状态
  5. 良好的扩展性:可方便添加新功能
相关推荐
十五年专注C++开发2 小时前
达梦数据库在Linux备份报错 -8003: 缺少本地或者远程归档 解决方案
数据库·c++·dm·备份复原
Leo.yuan2 小时前
经营分析如何联动业务与财务?4步打通业财经营分析指标
数据库·数据分析·经营分析
Yana.nice2 小时前
MySQL 事务的四大特性(ACID)
数据库·mysql·oracle
coder阿龙3 小时前
基于SpringAI+Qdrant+Ollama本地模型和向量数据库开发问答和RAG检索
java·数据库·spring boot·ai·数据库开发
小小程序员.¥3 小时前
oracle--视图、序列、索引
服务器·数据库·oracle
fire-flyer3 小时前
ClickHouse系列(二):MergeTree 家族详解
大数据·数据库·clickhouse
Yana.nice3 小时前
MySQL 三大日志(redo log、undo log、binlog)的区别和作用
数据库·mysql
XDHCOM3 小时前
MySQL CASE WHEN语句应用实例:如何实现条件查询与数据转换?
数据库·mysql
Jul1en_3 小时前
【Redis】常用命令及定时器实现思想
数据库·redis·缓存