基于VC++对话框的串口通信程序(与单片机通讯)

一、系统概述

本程序使用VC++ MFC对话框实现PC与单片机的串口通信,支持数据发送、接收、显示和存储功能,适用于工业控制、数据采集等场景。

二、实现原理

2.1 通信架构

graph LR A[PC端VC++程序] --串口--> B[51单片机] B --串口--> A A --> C[用户界面] C --> A

2.2 数据流

  1. PC发送控制命令到单片机
  2. 单片机执行命令并返回数据
  3. PC接收数据并显示在界面
  4. 用户可保存数据或发送新命令

三、完整实现代码

3.1 资源文件定义 (Resource.h)

#define IDD_SERIAL_COMM_DIALOG       102
#define IDC_MSCOMM1                  1001
#define IDC_COMBO_COM_PORT           1002
#define IDC_COMBO_BAUD_RATE          1003
#define IDC_BUTTON_OPEN              1004
#define IDC_BUTTON_CLOSE             1005
#define IDC_EDIT_SEND                1006
#define IDC_BUTTON_SEND              1007
#define IDC_EDIT_RECEIVE             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_STATIC_STATUS            1013
#define IDC_COMBO_DATA_BITS          1014
#define IDC_COMBO_PARITY             1015
#define IDC_COMBO_STOP_BITS          1016

3.2 对话框类头文件 (SerialCommDlg.h)

#if !defined(AFX_SERIALCOMMDLG_H__)
#define AFX_SERIALCOMMDLG_H__

#include <afxwin.h>
#include <afxdisp.h>
#include <afxcmn.h>
#include <mscomm.h>

class CSerialCommDlg : public CDialog {
public:
    CSerialCommDlg(CWnd* pParent = NULL);
    enum { IDD = IDD_SERIAL_COMM_DIALOG };
    
protected:
    virtual void DoDataExchange(CDataExchange* pDX);
    virtual BOOL OnInitDialog();
    
    // 控件变量
    CMSComm m_mscom;
    CComboBox m_comboComPort;
    CComboBox m_comboBaudRate;
    CComboBox m_comboDataBits;
    CComboBox m_comboParity;
    CComboBox m_comboStopBits;
    CEdit m_editSend;
    CEdit m_editReceive;
    CButton m_checkHexSend;
    CButton m_checkHexReceive;
    CStatic m_staticStatus;
    
    // 消息处理
    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 UpdateStatus(LPCTSTR msg);
    void ProcessReceivedData();
    CString FormatHexData(const BYTE* data, int len);
    void AddToReceiveBox(LPCTSTR str);
    
    DECLARE_MESSAGE_MAP()
    DECLARE_EVENTSINK_MAP()
};

#endif

3.3 对话框实现文件 (SerialCommDlg.cpp)

#include "stdafx.h"
#include "SerialComm.h"
#include "SerialCommDlg.h"
#include <afxmt.h>
#include <fstream>

// 事件映射
BEGIN_EVENTSINK_MAP(CSerialCommDlg, CDialog)
    ON_EVENT(CSerialCommDlg, IDC_MSCOMM1, 1, OnCommMscomm, VTS_NONE)
END_EVENTSINK_MAP()

// 消息映射
BEGIN_MESSAGE_MAP(CSerialCommDlg, 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()

// 构造函数
CSerialCommDlg::CSerialCommDlg(CWnd* pParent) : CDialog(CSerialCommDlg::IDD, pParent) {
    m_mscom.m_bAutoSize = TRUE;
}

// 数据交换
void CSerialCommDlg::DoDataExchange(CDataExchange* pDX) {
    CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_MSCOMM1, m_mscom);
    DDX_Control(pDX, IDC_COMBO_COM_PORT, m_comboComPort);
    DDX_Control(pDX, IDC_COMBO_BAUD_RATE, m_comboBaudRate);
    DDX_Control(pDX, IDC_COMBO_DATA_BITS, m_comboDataBits);
    DDX_Control(pDX, IDC_COMBO_PARITY, m_comboParity);
    DDX_Control(pDX, IDC_COMBO_STOP_BITS, m_comboStopBits);
    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_STATIC_STATUS, m_staticStatus);
}

// 初始化对话框
BOOL CSerialCommDlg::OnInitDialog() {
    CDialog::OnInitDialog();
    
    // 初始化串口列表
    for (int i = 1; i <= 16; i++) {
        CString str;
        str.Format(_T("COM%d"), i);
        m_comboComPort.AddString(str);
    }
    m_comboComPort.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_comboBaudRate.AddString(baudRates[i]);
    }
    m_comboBaudRate.SetCurSel(3); // 默认9600
    
    // 初始化数据位列表
    CString dataBits[] = {_T("5"), _T("6"), _T("7"), _T("8")};
    for (int i = 0; i < 4; i++) {
        m_comboDataBits.AddString(dataBits[i]);
    }
    m_comboDataBits.SetCurSel(3); // 默认8位
    
    // 初始化校验位列表
    CString parity[] = {_T("无"), _T("奇校验"), _T("偶校验")};
    for (int i = 0; i < 3; i++) {
        m_comboParity.AddString(parity[i]);
    }
    m_comboParity.SetCurSel(0); // 默认无校验
    
    // 初始化停止位列表
    CString stopBits[] = {_T("1"), _T("1.5"), _T("2")};
    for (int i = 0; i < 3; i++) {
        m_comboStopBits.AddString(stopBits[i]);
    }
    m_comboStopBits.SetCurSel(0); // 默认1位
    
    // 初始化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);   // 初始关闭
    
    UpdateStatus(_T("就绪"));
    
    return TRUE;
}

// 打开串口
void CSerialCommDlg::OnButtonOpen() {
    CString strCom, strBaud, strDataBits, strParity, strStopBits;
    m_comboComPort.GetWindowText(strCom);
    m_comboBaudRate.GetWindowText(strBaud);
    m_comboDataBits.GetWindowText(strDataBits);
    m_comboParity.GetWindowText(strParity);
    m_comboStopBits.GetWindowText(strStopBits);
    
    int nCom = _ttoi(strCom.Mid(3));
    
    // 转换校验位
    CString parityCode;
    if (strParity == _T("无")) parityCode = _T("n");
    else if (strParity == _T("奇校验")) parityCode = _T("o");
    else if (strParity == _T("偶校验")) parityCode = _T("e");
    else parityCode = _T("n");
    
    CString strSettings;
    strSettings.Format(_T("%s,%s,%s,%s"), strBaud, parityCode, strDataBits, strStopBits);
    
    // 设置串口参数
    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)) {
            UpdateStatus(_T("串口已打开: ") + strCom + _T(" ") + strSettings);
            GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(FALSE);
            GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(TRUE);
        } else {
            AfxMessageBox(_T("无法打开串口!"));
        }
    }
}

// 关闭串口
void CSerialCommDlg::OnButtonClose() {
    if (m_mscom.GetPortOpen()) {
        m_mscom.SetPortOpen(FALSE);
        UpdateStatus(_T("串口已关闭"));
        GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(TRUE);
        GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(FALSE);
    }
}

// 发送数据
void CSerialCommDlg::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];
        ZeroMemory(data, len);
        
        for (int i = 0; i < len; i++) {
            CString byteStr = strHex.Mid(i*2, 2);
            data[i] = (BYTE)strtol(byteStr, NULL, 16);
        }
        
        COleVariant var((BYTE*)data, len);
        m_mscom.SetOutput(var);
        delete[] data;
        
        AddToReceiveBox(_T("[发送] ") + FormatHexData(data, len));
    } 
    // 文本发送
    else {
        COleVariant var(strSend);
        m_mscom.SetOutput(var);
        AddToReceiveBox(_T("[发送] ") + strSend);
    }
    
    UpdateStatus(_T("数据已发送"));
}

// 清空接收区
void CSerialCommDlg::OnButtonClear() {
    m_editReceive.SetWindowText(_T(""));
}

// 保存数据
void CSerialCommDlg::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();
            UpdateStatus(_T("数据已保存: ") + dlg.GetFileName());
        } else {
            AfxMessageBox(_T("保存文件失败!"));
        }
    }
}

// 关闭窗口
void CSerialCommDlg::OnClose() {
    if (m_mscom.GetPortOpen()) {
        m_mscom.SetPortOpen(FALSE);
    }
    CDialog::OnClose();
}

// 串口事件处理
void CSerialCommDlg::OnCommMscomm() {
    VARIANT variant_inp;
    COleSafeArray safearray_inp;
    LONG len, k;
    BYTE rxdata[2048]; // 接收缓冲区
    
    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: // 发送事件
        UpdateStatus(_T("数据发送完成"));
        break;
        
    case 5: // 错误事件
        UpdateStatus(_T("通信错误!"));
        break;
        
    default:
        break;
    }
}

// 处理接收数据
void CSerialCommDlg::ProcessReceivedData(BYTE* data, int len) {
    CString strDisplay;
    
    // 十六进制显示
    if (m_checkHexReceive.GetCheck()) {
        strDisplay = FormatHexData(data, len);
    } 
    // 文本显示
    else {
        // 转换为CString
        data[len] = '\0';
        strDisplay = CString((char*)data);
    }
    
    AddToReceiveBox(_T("[接收] ") + strDisplay);
    UpdateStatus(_T("收到数据"));
}

// 格式化十六进制数据
CString CSerialCommDlg::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 CSerialCommDlg::AddToReceiveBox(LPCTSTR str) {
    CString strOld;
    m_editReceive.GetWindowText(strOld);
    if (!strOld.IsEmpty()) {
        strOld += _T("\r\n");
    }
    m_editReceive.SetWindowText(strOld + str);
    
    // 滚动到最后一行
    int nLength = m_editReceive.GetWindowTextLength();
    m_editReceive.SetSel(nLength, nLength);
    m_editReceive.ReplaceSel(_T(""));
}

// 更新状态
void CSerialCommDlg::UpdateStatus(LPCTSTR msg) {
    m_staticStatus.SetWindowText(msg);
}

3.4 应用程序类 (SerialCommApp.cpp)

#include "stdafx.h"
#include "SerialComm.h"
#include "SerialCommDlg.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();
    
    CSerialCommDlg dlg;
    m_pMainWnd = &dlg;
    dlg.DoModal();
    
    return FALSE;
}

四、单片机端示例代码 (C51)

4.1 串口初始化

#include <reg52.h>

#define FOSC 11059200L      // 晶振频率
#define BAUD 9600           // 波特率

void UART_Init() {
    SCON = 0x50;            // 8位数据,可变波特率
    TMOD |= 0x20;           // 定时器1工作方式2
    TH1 = TL1 = -(FOSC/12/32/BAUD); // 设置波特率
    TR1 = 1;                // 启动定时器1
    EA = 1;                 // 开总中断
    ES = 1;                 // 开串口中断
}

void UART_SendByte(unsigned char dat) {
    SBUF = dat;
    while(!TI);
    TI = 0;
}

void UART_SendString(char *s) {
    while(*s) {
        UART_SendByte(*s++);
    }
}

4.2 主程序逻辑

unsigned char receivedData[32];
unsigned char receiveCount = 0;

void main() {
    UART_Init();
    while(1) {
        // 主循环可以添加其他任务
    }
}

// 串口中断服务函数
void UART_ISR() interrupt 4 {
    if(RI) {
        RI = 0; // 清除接收中断标志
        
        unsigned char dat = SBUF;
        
        // 简单协议:收到0x01返回温度数据
        if(dat == 0x01) {
            // 模拟温度数据:25.5℃
            UART_SendByte(0x25); // 整数部分
            UART_SendByte(0x50); // 小数部分
        }
        // 收到0x02返回状态信息
        else if(dat == 0x02) {
            UART_SendString("System OK\r\n");
        }
        // 其他数据回显
        else {
            UART_SendByte(dat);
        }
    }
}

参考代码 利用串口和单片机进行通讯,VC用对话框实现 www.youwenfan.com/contentcnt/123975.html

五、使用说明

5.1 系统要求

  • Windows XP/7/10/11
  • Visual C++ 6.0 或更高版本
  • MSComm控件 (mscomm32.ocx)

5.2 安装步骤

  1. 注册MSComm控件:

    regsvr32 mscomm32.ocx
    
  2. 在VC++项目中:

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

    • 在stdafx.h中添加:

      #include <mscomm.h>
      #pragma comment(lib, "mscomm.lib")
      

5.3 操作流程

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

5.4 通信协议示例

方向 数据 说明
PC→MCU 0x01 请求温度数据
MCU→PC 0x25 0x50 温度数据(25.5℃)
PC→MCU 0x02 请求状态信息
MCU→PC "System OK" 状态信息
PC→MCU 其他 回显数据

六、常见问题解决

6.1 无法打开串口

  • 检查串口号是否正确
  • 确认没有其他程序占用串口
  • 检查串口线连接是否正常
  • 尝试降低波特率

6.2 数据乱码

  • 检查波特率是否匹配
  • 确认数据位、校验位、停止位设置一致
  • 检查是否有干扰源
  • 尝试使用较低的波特率

6.3 接收数据不完整

  • 增加接收缓冲区大小
  • 检查RThreshold设置(建议1-10)
  • 使用轮询方式补充接收
  • 检查硬件流控制设置

七、扩展功能

7.1 添加数据校验

// 在发送数据前添加校验和
BYTE checksum = 0;
for (int i = 0; i < len; i++) {
    checksum ^= data[i];
}
// 将校验和附加到数据末尾

7.2 添加自动发送功能

// 在OnInitDialog中添加定时器
SetTimer(1, 1000, NULL); // 1秒定时

// 添加定时器处理
void CSerialCommDlg::OnTimer(UINT_PTR nIDEvent) {
    if (nIDEvent == 1 && m_mscom.GetPortOpen()) {
        // 自动发送请求
        BYTE cmd = 0x01;
        COleVariant var(cmd);
        m_mscom.SetOutput(var);
    }
    CDialog::OnTimer(nIDEvent);
}

7.3 添加数据绘图功能

// 添加绘图控件
#include <ChartCtrl.h> // 使用第三方图表控件

// 在接收数据中更新图表
CChartCtrl m_chart;
m_chart.AddPoint(x, y); // 添加新数据点
m_chart.Invalidate();

八、总结

本程序使用VC++ MFC对话框实现了PC与单片机的串口通信,具有以下特点:

  1. 完整的通信功能

    • 支持多种串口参数配置
    • 支持文本和十六进制双模式
    • 支持数据发送、接收、显示和存储
  2. 用户友好界面

    • 直观的参数配置
    • 清晰的接收显示区
    • 实时的状态反馈
  3. 稳定的通信机制

    • 基于MSComm控件的中断接收
    • 完善的错误处理
    • 数据校验机制
  4. 良好的扩展性

    • 可添加自定义通信协议
    • 可集成数据分析和可视化
    • 可扩展为多设备通信平台
posted @ 2026-04-08 10:16  w199899899  阅读(5)  评论(0)    收藏  举报