基于VC++对话框的串口通信程序(与单片机通讯)
一、系统概述
本程序使用VC++ MFC对话框实现PC与单片机的串口通信,支持数据发送、接收、显示和存储功能,适用于工业控制、数据采集等场景。
二、实现原理
2.1 通信架构
graph LR
A[PC端VC++程序] --串口--> B[51单片机]
B --串口--> A
A --> C[用户界面]
C --> A
2.2 数据流
- PC发送控制命令到单片机
- 单片机执行命令并返回数据
- PC接收数据并显示在界面
- 用户可保存数据或发送新命令
三、完整实现代码
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 安装步骤
-
注册MSComm控件:
regsvr32 mscomm32.ocx -
在VC++项目中:
-
添加mscomm.h和mscomm.cpp到项目
-
在stdafx.h中添加:
#include <mscomm.h> #pragma comment(lib, "mscomm.lib")
-
5.3 操作流程
- 选择串口号和通信参数(波特率、数据位、校验位、停止位)
- 点击"打开串口"按钮
- 在发送区输入数据(文本或HEX格式)
- 点击"发送"按钮
- 接收区显示返回数据
- 使用"清空"、"保存"按钮管理数据
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与单片机的串口通信,具有以下特点:
-
完整的通信功能:
- 支持多种串口参数配置
- 支持文本和十六进制双模式
- 支持数据发送、接收、显示和存储
-
用户友好界面:
- 直观的参数配置
- 清晰的接收显示区
- 实时的状态反馈
-
稳定的通信机制:
- 基于MSComm控件的中断接收
- 完善的错误处理
- 数据校验机制
-
良好的扩展性:
- 可添加自定义通信协议
- 可集成数据分析和可视化
- 可扩展为多设备通信平台
浙公网安备 33010602011771号