尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

VC++6.0开发RFID上位机:串口通信与MFC工业级实践

VC++6.0开发RFID上位机:串口通信与MFC工业级实践 简介本资源是一套基于Visual C6.0开发的RFID信息管理系统上位机软件实战项目面向C初学者及物联网应用开发学习者聚焦射频识别技术在后台管理场景中的工程落地。项目完整实现卡片读写、电子钱包初始化、充值消费、实名信息绑定与挂失补卡等核心功能依托MFC构建Windows GUI界面并集成串口通信与数据库交互逻辑。压缩包含128个文件主体为19个头文件h与14个源码文件cpp辅以DLL动态库、资源文件rc/ico、可执行程序exe及辅助文档pdf/doc总大小13.47MB结构清晰便于模块化学习与调试。已有95人下载学习提供可直接编译运行的完整工程涵盖从驱动调用、协议解析到业务逻辑封装的全链路代码特别适合理解传统工业级RFID系统与VC6.0/MFC开发范式的结合实践。1. 用 Visual C 6.0 搭建 RFID 上位机信息管理系统不是怀旧是工业现场的硬需求在高校课程设计、老旧产线改造和嵌入式教学实验中你常会遇到一个看似“过时”却无法绕开的组合RFID 读写器通过 RS232/RS485 接口输出原始数据后端数据库用 Access 或 SQL Server 2000而开发环境被明确限定为 Visual C 6.0。这不是技术倒退而是真实场景——大量存量 RFID 设备如 Motorola FX7400、Zebra FX9500 的早期固件版本仅提供串口协议文档工厂 IT 部门禁止安装新版 IDE学生大作业需在 Windows XP SP3 虚拟机中演示。本项目核心不是“用 AI 做识别”而是构建一套稳定、可调试、能对接硬件层的上位机软件它接收 RFID 标签 IDEPC 或 TID、时间戳、读取天线编号完成校验、去重、缓存、写入本地数据库并支持手动触发查询与导出。适合嵌入式初学者、工业自动化维护人员及课程设计指导教师——你需要的不是抽象框架而是能双击运行、串口线一插就能看到“Tag ID: 300800000000000000000001”实时刷屏的可执行程序。2. 为什么必须用 VC6.0串口通信与 MFC 框架的底层绑定逻辑2.1 工业级串口通信不能靠“高级封装”VC6.0 的 CSerialPort 类为何不可替代现代 C 库如 Boost.Asio 或 Qt SerialPort依赖动态链接库和运行时环境在无管理员权限的工控机上常因 DLL 缺失失败。VC6.0 自带的CSerialPort类非微软官方但被广泛集成于SerialPort.h/cpp第三方实现直接调用 Win32 APICreateFile,SetCommState,ReadFile编译后生成纯静态链接的 EXE体积小500KB、无依赖、启动快。其关键优势在于对“非标准波特率”的硬编码支持——某些 RFID 读写器如 Impinj Speedway R220 的 legacy mode要求 125000 波特率而 Windows 2000/XP 的GetCommState默认只识别标准值9600/19200/115200VC6.0 允许通过DCB.BaudRate 125000强制写入绕过系统校验。提示VC6.0 的CSerialPort并非 MFC 内置类需手动添加SerialPort.h/.cpp到工程。常见错误是未定义#define _WIN32_WINNT 0x0400导致COMMTIMEOUTS结构体缺失编译报错error C2065: COMMTIMEOUTS : undeclared identifier。2.2 MFC 对话框架构天然适配 RFID 操作流从“连接-读取-显示-存盘”四步闭环RFID 上位机操作具有强状态性用户点击“连接串口”后界面需禁用其他按钮读取到标签时列表控件CListCtrl要实时追加行导出数据时需弹出文件保存对话框CFileDialog。MFC 的CDialog框架通过DoModal()启动模态对话框天然隔离 UI 线程与串口读取线程避免多线程资源竞争。对比 Qt 的信号槽机制或 .NET 的BackgroundWorkerMFC 的OnTimer()定时轮询串口缓冲区而非依赖事件回调更易调试——你可以在OnTimer()中插入OutputDebugString打印dwBytesRead确认是否真有数据到达而非被驱动层丢弃。2.2.1 关键控件映射表RFID 功能与 MFC 控件的物理对应关系RFID 操作动作MFC 控件类型控件 ID核心事件处理函数数据绑定方式选择 COM 端口号CComboBoxIDC_COM_PORTOnSelchangeComPort()GetLBText(nIndex, strPort)设置波特率CComboBoxIDC_BAUD_RATEOnSelchangeBaudRate()m_comboBaud.GetCurSel()→ 查表得整数值连接/断开设备CButtonIDC_BTN_CONNECTOnBnClickedBtnConnect()调用m_serial.Open()/Close()实时标签列表CListCtrlIDC_LIST_TAGSInsertItem()SetItemText()每次ReadFile()成功后追加一行导出为 CSVCButtonIDC_BTN_EXPORTOnBnClickedBtnExport()CStdioFile::WriteString()2.3 数据协议解析从原始字节流到结构化 Tag ID 的三步拆解RFID 读写器返回的不是 JSON而是十六进制 ASCII 字符流。以常见的 ISO18000-6B 协议为例典型响应为$0100000000000000000000000000000000000000000000000000000000000000*其中$为起始符*为结束符中间 64 位十六进制字符代表 EPC 编码。VC6.0 必须用CString::Mid()截取子串再用sscanf或strtol转换// 假设 m_strRawData $0100000000000000000000000000000000000000000000000000000000000000* CString strEPC m_strRawData.Mid(1, 64); // 取第2到65位索引从0开始 unsigned char tagID[8]; for (int i 0; i 8; i) { sscanf(strEPC.Mid(i*2, 2), %02X, tagID[i]); // 每2字符转1字节 } // 此时 tagID[0]~tagID[7] 即为 64-bit EPC 值可直接用于数据库查询注意sscanf在 VC6.0 中对%02X的支持稳定但若使用std::stoiC11将导致编译失败。务必用 C 风格字符串函数。3. 从零搭建 RFID 上位机VC6.0 工程创建与串口初始化实操3.1 新建 MFC AppWizard 工程选择“基于对话框”并禁用所有高级特性启动 VC6.0 →File → New → Projects → MFC AppWizard (.exe)→ 输入工程名RFID_Manager→ 在 Step 1 中选择Dialog based→ Step 2 全部取消勾选不使用 ActiveX、数据库、OLE 等→ Step 3 选择None不生成任何视图类→ Finish。此配置生成最小 MFC 依赖避免CDocument/CView带来的复杂生命周期管理专注串口与 UI 交互。3.1.1 添加串口类文件手动集成 CSerialPort 到工程下载SerialPort.h和SerialPort.cpp经典版本大小约 8KB右键工程 →Add To Project → Files...→ 选择两个文件。在主对话框头文件RFID_ManagerDlg.h中添加#include SerialPort.h // 必须在 #include afxwin.h 之后 class CRFID_ManagerDlg : public CDialog { // ... 其他声明 private: CSerialPort m_serial; // 实例化串口对象 CString m_strRawData; // 存储原始响应 };在RFID_ManagerDlg.cpp的构造函数中初始化CRFID_ManagerDlg::CRFID_ManagerDlg(CWnd* pParent /*NULL*/) : CDialog(CRFID_ManagerDlg::IDD, pParent) { //{{AFX_DATA_INIT(CRFID_ManagerDlg) //}}AFX_DATA_INIT m_serial.SetOwner(this); // 绑定窗口句柄用于 PostMessage }3.2 串口参数配置波特率、校验位与超时的工业级设置RFID 读写器对串口参数极其敏感。常见错误是使用默认NOPARITY无校验而实际设备要求EVENPARITY偶校验。在OnBnClickedBtnConnect()中必须显式设置BOOL CRFID_ManagerDlg::OnBnClickedBtnConnect() { CString strPort, strBaud; m_comboPort.GetWindowText(strPort); // 获取 COM1/COM2 m_comboBaud.GetWindowText(strBaud); DWORD dwBaud _ttoi(strBaud); // 将字符串转整数如 115200 if (!m_serial.Open(strPort, dwBaud, 8, E, 1)) { // 8数据位偶校验1停止位 AfxMessageBox(_T(串口打开失败请检查端口号和设备是否占用)); return FALSE; } // 设置超时读取等待 500ms写入等待 100ms COMMTIMEOUTS timeouts {0}; timeouts.ReadIntervalTimeout 0; timeouts.ReadTotalTimeoutConstant 500; // 关键防止 ReadFile 长时间阻塞 timeouts.ReadTotalTimeoutMultiplier 0; timeouts.WriteTotalTimeoutConstant 100; timeouts.WriteTotalTimeoutMultiplier 0; m_serial.SetTimeouts(timeouts); SetTimer(1, 100, NULL); // 启动 100ms 定时器轮询 return TRUE; }提示ReadTotalTimeoutConstant 500是 RFID 场景的关键参数。若设为 0ReadFile会立即返回 0 字节若设为过大值如 5000UI 将卡顿。100ms 定时器配合 500ms 超时确保每秒最多读取 10 次平衡实时性与 CPU 占用。3.3 标签数据解析与 UI 更新避免闪烁的 CListCtrl 刷新技巧直接在OnTimer()中调用InsertItem()会导致列表频繁重绘闪烁。正确做法是先收集数据再批量更新void CRFID_ManagerDlg::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent 1 m_serial.IsOpen()) { char buffer[256]; DWORD dwBytesRead; if (m_serial.Read(buffer, sizeof(buffer)-1, dwBytesRead)) { buffer[dwBytesRead] \0; m_strRawData buffer; // 累加原始数据 // 检测完整帧以 $ 开头* 结尾 int nStart m_strRawData.Find($); int nEnd m_strRawData.Find(*); if (nStart ! -1 nEnd nStart) { CString strFrame m_strRawData.Mid(nStart, nEnd - nStart 1); ParseRFIDFrame(strFrame); // 解析单帧 m_strRawData m_strRawData.Mid(nEnd 1); // 截掉已处理部分 } } } CDialog::OnTimer(nIDEvent); } void CRFID_ManagerDlg::ParseRFIDFrame(const CString strFrame) { // 步骤1提取64位EPC示例 CString strEPC strFrame.Mid(1, 64); // 步骤2转换为可读格式如 01000000000000000000000000000000 → 01-00-00-00-00-00-00-00 CString strFormatted; for (int i 0; i 64; i 2) { strFormatted strEPC.Mid(i, 2) (i 62 ? - : ); } // 步骤3插入列表禁用重绘提升性能 m_listTags.SetRedraw(FALSE); int nItem m_listTags.InsertItem(m_listTags.GetItemCount(), strFormatted); m_listTags.SetItemText(nItem, 1, COleDateTime::GetCurrentTime().Format(_T(%H:%M:%S))); m_listTags.SetRedraw(TRUE); m_listTags.Invalidate(); // 强制刷新 }4. 数据持久化与查询用 DAO 访问 Access 数据库实现轻量级信息管理4.1 配置 DAO 支持VC6.0 中启用数据库功能的三处修改VC6.0 默认不启用 DAO需手动开启Project → Settings → General → Use of MFC→ 选择In a shared DLL非静态链接Project → Settings → C/C → Preprocessor → Additional include directories→ 添加$(MSDevDir)\..\..\VC98\IncludeProject → Settings → Link → Object/library modules→ 添加dao360.libWindows 2000 SDK 提供。注意dao360.lib对应 DAO 3.6兼容 Access 2000/2003。若使用 Access 2007ACCDB 格式必须升级到 DAO 3.6 以上但 VC6.0 不支持故本项目强制使用.mdb文件。4.2 创建 RFID 数据表字段设计兼顾扩展性与查询效率在 Access 中新建数据库RFID_Data.mdb创建表tblTags字段名数据类型说明ID自动编号主键无需手动赋值TagID文本 (50)存储格式化后的 EPC 字符串ReadTime日期/时间COleDateTime::GetCurrentTime()AntennaNo数字 (字节)天线编号1-4Status是/否是否已人工确认用于盘点4.3 插入与查询代码DAO Recordset 的安全写入模式void CRFID_ManagerDlg::SaveTagToDB(const CString strTagID) { try { CDaoDatabase db; db.Open(_T(RFID_Data.mdb)); // 相对路径确保 EXE 同目录 CDaoRecordset rs(db); rs.Open(_T(tblTags), dbOpenDynaset); rs.AddNew(); rs.SetFieldValue(_T(TagID), _variant_t(strTagID)); rs.SetFieldValue(_T(ReadTime), _variant_t(COleDateTime::GetCurrentTime())); rs.SetFieldValue(_T(AntennaNo), _variant_t((short)1)); // 默认天线1 rs.SetFieldValue(_T(Status), _variant_t((bool)FALSE)); rs.Update(); rs.Close(); db.Close(); } catch (CDaoException* e) { TCHAR szError[256]; e-GetErrorMessage(szError, 255); AfxMessageBox(szError); e-Delete(); } } void CRFID_ManagerDlg::QueryTodayTags() { COleDateTime today COleDateTime::GetCurrentTime(); CString strWhere _T(DateValue([ReadTime]) DateValue() today.Format(_T(%Y-%m-%d)) _T()); CDaoDatabase db; db.Open(_T(RFID_Data.mdb)); CDaoRecordset rs(db); rs.Open(_T(SELECT * FROM tblTags WHERE ) strWhere, dbOpenSnapshot); m_listTags.DeleteAllItems(); int nRow 0; while (!rs.IsEOF()) { CString strTag rs.GetFieldValue(_T(TagID)).bstrVal; CString strTime rs.GetFieldValue(_T(ReadTime)).bstrVal; m_listTags.InsertItem(nRow, strTag); m_listTags.SetItemText(nRow-1, 1, strTime); rs.MoveNext(); } rs.Close(); db.Close(); }提示DateValue([ReadTime]) DateValue(2024-05-20)是 Access SQL 的日期截断写法避免WHERE ReadTime #2024-05-20# AND ReadTime #2024-05-21#的时区歧义。DAO 中bstrVal是_variant_t的字符串访问方式VC6.0 不支持std::string隐式转换。5. 故障诊断与性能优化解决 RFID 上位机最常遇到的 5 类问题5.1 串口“收不到数据”排查清单从物理层到应用层逐级验证层级检查项验证方法典型现象物理层RS232 线缆 TX/RX 是否反接用万用表测 COM 口 2、3 脚电压空闲时应为 -3V~-15VReadFile始终返回 0 字节驱动层设备管理器中 COM 端口是否黄色感叹号卸载驱动后重新扫描硬件选择“从列表中选择” → “Standard Serial over Bluetooth link”系统提示“设备无法启动”协议层读写器是否处于“透明传输模式”发送指令ATMODE0具体指令查设备手册用串口助手发送测试收到乱码或无响应应用层SetTimeouts是否设为 0在OnTimer()中添加OutputDebugString(_T(Bytes read: ) itoa(dwBytesRead, buf, 10));Debug 输出始终为 “Bytes read: 0”权限层是否以管理员身份运行 VC6.0右键快捷方式 →Properties → Compatibility → Run this program as an administrator打开串口返回ERROR_ACCESS_DENIED5.2 标签重复录入的软件级去重基于内存哈希表的 O(1) 判重RFID 多次读取同一标签是常态需在写入数据库前过滤。VC6.0 不支持std::unordered_set改用CMapStringToPtr// 在头文件中声明 CMapStringToPtr m_mapTagCache; // 在 OnInitDialog() 中初始化 m_mapTagCache.InitHashTable(1024); // 预分配 1024 桶 // 在 ParseRFIDFrame() 后调用 void CRFID_ManagerDlg::DeduplicateAndSave(const CString strTagID) { if (m_mapTagCache.Lookup(strTagID, (void*)NULL)) { // 已存在跳过 return; } // 插入缓存值为当前时间便于后续清理 COleDateTime now COleDateTime::GetCurrentTime(); m_mapTagCache.SetAt(strTagID, (void*)now.GetTime()); // 写入数据库 SaveTagToDB(strTagID); // 清理 5 分钟前的缓存防止内存泄漏 POSITION pos m_mapTagCache.GetStartPosition(); while (pos ! NULL) { CString key; void* value; m_mapTagCache.GetNextAssoc(pos, key, value); COleDateTime cachedTime((time_t)value); if (now - cachedTime COleDateTimeSpan(0, 5, 0, 0)) { m_mapTagCache.RemoveKey(key); } } }5.3 界面卡顿终极方案将耗时操作移出 UI 线程OnTimer()中执行SaveTagToDB()会导致 UI 响应延迟。解决方案是使用PostMessage发送自定义消息// 定义消息 #define WM_SAVE_TAG (WM_USER 100) // 在 ParseRFIDFrame() 中改为 ::PostMessage(m_hWnd, WM_SAVE_TAG, (WPARAM)strTagID.GetBuffer(0), 0); // 在消息映射中添加 ON_MESSAGE(WM_SAVE_TAG, OnSaveTagMessage) // 消息处理函数 LRESULT CRFID_ManagerDlg::OnSaveTagMessage(WPARAM wParam, LPARAM lParam) { CString* pStr (CString*)wParam; DeduplicateAndSave(*pStr); pStr-ReleaseBuffer(); return 0; }此方案确保数据库操作在 UI 线程中异步执行用户点击按钮、拖动窗口完全不受影响。本文还有配套的精品资源点击获取
返回列表