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

资讯详情

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

MFC上位机开发USBCAN设备:报文收发、滤波配置与排障实践

MFC上位机开发USBCAN设备:报文收发、滤波配置与排障实践 简介一份基于VC6.0与MFC框架的CAN通信测试程序源码包面向汽车电子、工业自动化及嵌入式开发者尤其适用于需要对接周立功CAN接口卡或USBCAN设备的调试场景。源码内含对话框界面与消息处理逻辑可在VC6.0工程中直接编译运行帮助快速验证CAN帧收发、数据解析与实时显示。资源共47个文件压缩包大小3.62MB主要包含头文件、C源文件、动态链接库及说明文档其中ControlCAN库为周立功官方驱动接口另有设备配置文件与使用说明文档目录结构清晰便于按需检索。目前已有846人学习下载。通过这套代码读者可以理解MFC程序与CAN总线交互的完整流程掌握CAN ID仲裁、数据长度编码、CRC校验与位填充等关键协议概念并据此进行二次开发快速搭建自己的CAN通信测试工具。 作为一个常年跟单片机、嵌入式打交道的开发者我最近做的一个项目正好是把周立功的USBCAN设备用VC/MFC上位机跑起来做报文调试。这个过程中查了不少资料也踩了不少坑分享出来给正在做CAN上位机或者接手老MFC项目的朋友参考。这篇文章不打算讲CAN协议入门重点是把“用MFC写一个稳定收发CAN报文的工具”这件事完整落地驱动模型怎么理解、API调用顺序怎么排、界面和线程怎么设计、滤波怎么配、出问题怎么排查一条线讲清楚。如果你手里有周立功的USBCAN系列设备又要用VC或VS的MFC框架做上位机这篇文章应该能帮你省下至少两三天的摸索时间。1. 周立功SDK与ZCANPro的关系先会调试再写代码1.1 驱动安装和控制面板软件的作用很多人拿到周立功的CAN卡第一反应是去翻SDK里的示例代码。我的建议是反过来先装官方驱动和ZCANPro控制面板软件用ZCANPro把设备跑起来确认硬件、线缆、终端的电平都正常了再动手写代码。ZCANPro在周立功官网上对应你设备型号的下载区就能找到安装驱动后它会自动识别USBCAN设备可以选择CAN通道、设置波特率、打开设备然后像串口助手一样收发CAN报文。这一步不是多余。CAN通信出问题最常见的原因不是代码而是硬件链路。我之前遇到过一次“代码明明是对的但就是收不到报文”的情况最后用ZCANPro一发一收测试发现是CAN_H和CAN_L两根线接反了。如果你跳过工具直接写代码问题定位会很痛苦因为你无法确定是本机程序的问题还是链路问题。先用ZCANPro验证硬件链路能让你在后面的开发中只盯着程序本身。1.2 SDK的核心数据类型周立功的二次开发SDK中最核心的数据结构就两个一个是初始化配置结构体一个是报文对象结构体。所有操作都是围绕它们进行的。初始化配置在旧版本SDK里一般叫VCI_INIT_CONFIG新版本里有些叫ZCAN_INIT_CONFIG但思路一致。核心字段包括验收码、屏蔽码、滤波方式、波特率分频参数、工作模式。报文对象结构体则用于收发里面包含ID、数据长度、数据字节、帧类型、发送类型、时间戳等信息。我的建议是拿到SDK后不要急着看所有接口先找头文件里这两个结构体的定义把字段弄清楚。后面的初始化、收发、滤波全部都是在这两个结构体的基础上展开的。2. 初始化链路打开设备、配置CAN口、启动一步都不能省2.1 函数调用顺序周立功的CAN接口编程虽然不同型号的函数名略有差异老SDK以VCI_开头新版以ZCAN_开头但整个调用链是固定的顺序不能乱打开设备VCI_OpenDevice(设备类型, 设备索引, 保留参数)初始化CAN通道VCI_InitCAN(设备类型, 设备索引, CAN通道号, 初始化配置结构体指针)启动CAN通道VCI_StartCAN(设备类型, 设备索引, CAN通道号)发送/接收报文VCI_Transmit/VCI_Receive关闭设备VCI_CloseDevice(设备类型, 设备索引)这个顺序看起来很简单但有一个细节特别容易忽略有些型号的设备有多个CAN通道InitCAN和StartCAN必须逐个通道调用而不是只初始化你要用的那一路。比如USBCAN-II有双通道你只用通道0但如果不同时对通道1做一次空配置某些固件版本下通道0也可能工作异常。这个属于极端情况但我在实际项目中确实遇到过。稳妥做法是无论用不用第二路都对两个通道做一次InitCAN和StartCAN参数保持一致。2.2 波特率参数表与配置波特率的配置不是直接填一个“500K”或“250K”而是通过两个字节的定时参数来确定。不同SDK版本对应的字段名可能不一样一般是一个叫Timing0、一个叫Timing1。官方文档会给出不同波特率对应的取值表。以我常遇见的几组为例波特率Timing0Timing11000Kbps0x000x14500Kbps0x000x1C250Kbps0x010x1C125Kbps0x030x1C100Kbps0x040x1C这两字节如果不确定该填什么最稳妥的方法是在ZCANPro里选择你需要的波特率然后看它生成的参数值。不同晶振方案的设备同样的波特率对应的参数可能不一样所以我建议不要死记硬背这张表要用的时候以你手上这个型号的设备手册为准。2.3 工作模式的选择初始化配置结构体里还有一个Mode字段通常有正常模式、只听模式等选项。开发调试阶段如果只是观察总线上的数据而不参与通信可以先用只听模式这样程序不会向外发送任何报文也不会影响总线上已有的通信。确认接收数据正确之后再切换回正常模式做发送测试。这一段操作还有一个容易被忽略的点设备打开后如果程序没有对CAN通道做InitCAN和StartCAN就直接调用发送接口通常会返回失败。原因是驱动层认为该通道没有处于启动状态不会把报文投递下去。所以如果你发现程序“打开设备成功但发不出去”先检查一下是不是漏了StartCAN。3. MFC界面解耦控件只管交互逻辑不塞进OnClicked3.1 界面模块划分MFC写界面最大的问题就是容易把所有代码都塞进按钮响应函数里。特别是从控制台Demo改成界面程序时很多人习惯在OnBnClickedButtonSend里直接写发送逻辑看起来能用后面加功能就痛苦了。我给这个CAN调试工具划分的界面模块大致是这样的顶部是设备参数区包括设备类型下拉框、通道选择、波特率选择、打开/关闭设备按钮中间左侧是发送区包括报文ID、帧类型、数据长度、数据字节的输入框以及“单帧发送”和“定时发送”两个按钮中间右侧是接收区用一个列表控件显示收到的报文ID、时间戳、数据类型和数据底部是一个状态栏显示设备状态、发送计数、接收计数。这个布局的核心原则是控件只负责收集用户输入和展示结果业务逻辑放在独立的消息处理函数里。为此需要把当前的设备类型、通道号、CAN配置参数保存为对话框类成员变量界面控件跟这些成员变量之间通过消息和更新函数交互而不是在按钮响应里直接调用VCI_Transmit。3.2 配置参数绑定打开设备按钮的逻辑可以这样组织从组合框读取用户选择的波特率参数填入初始化配置结构体然后依次调用OpenDevice、InitCAN、StartCAN。如果任一步返回失败弹窗提示并停止初始化如果成功更新设备状态栏把打开按钮置灰把关闭按钮启用。这里的改进点是不要把所有步骤堆在同一个函数里至少拆成三个小函数一个用于从控件收集参数、一个用于执行打开流程、一个用于更新界面状态。这样后面如果要支持多个设备类型只需要在参数收集里加个映射其他部分不用动。4. 接收线程与UI刷新让列表控件安全显示报文4.1 为什么必须单独开线程CAN报文的接收是持续发生的如果放在主线程里用VCI_Receive轮询界面会卡死按钮点了没反应。最常用的做法是专门开一个接收线程循环调用接收接口。周立功的接收接口会返回实际接收到的报文数最后一个参数是等待时间毫秒。这个等待时间很关键决定了线程轮询的频率。我设的是50毫秒超时也就是每50毫秒从驱动读取一次数据。如果填写0接收函数会立即返回线程空转CPU占用率会明显升高。如果填写-1或者较大的值在某些驱动版本下线程会一直阻塞关闭设备时可能导致线程无法立即退出。4.2 线程与UI的通信线程里接收到报文后不能直接操作列表控件因为MFC控件必须在创建它的线程主线程里操作。正确做法是在线程里把报文数据拷贝一份通过PostMessage发送自定义消息给主线程在主窗口的消息处理函数里再更新列表。自定义消息可以在对话框头文件里定义一个宏比如WM_UPDATE_CAN_LIST通过ON_MESSAGE映射到处理函数。这样设计的好处是接收线程只负责拿数据、传数据界面怎么显示、要不要保存完全由主线程决定两者职责清晰。接收线程的退出也要小心。关闭设备时先设置一个表示“停止接收”的BOOL标志位同时调用关闭设备接口让正在阻塞的接收调用返回然后等待线程结束。如果顺序反了先等线程结束再关设备可能因为接收函数一直阻塞而永久等待。我的做法是先置退出标志再CloseDevice紧接着WaitForSingleObject等待线程退出等待超时给5秒防止极端情况下卡住对话框销毁流程。5. 验收码与屏蔽码的滤波配置5.1 滤波原理周立功CAN卡在驱动层就支持硬件滤波不需要把所有报文都收上来再在上位机里过滤。这功能在大流量总线上非常实用。滤波的核心是两个字段验收码和屏蔽码。把CAN报文ID跟验收码对比屏蔽码中为0的位要求两者必须一致为1的位表示不关心。这样组合起来可以实现“只接收指定范围ID”的效果。如果不想滤波希望接收总线上所有报文可以把验收码设为0屏蔽码设为全F。因为在屏蔽码全为F的情况下所有位都不关心任何ID都会通过。这也是驱动默认的配置逻辑。5.2 实际配置举例举个例子假设只关心标准帧ID为0x123的设备节点标准帧ID只有11位有效。那么验收码填0x123屏蔽码的低11位填0表示这11位必须匹配高21位置1表示不关心。这样驱动在底层就会把ID不等于0x123的标准帧全部丢弃只把0x123的报文交给应用层可以显著降低主线程和接收线程的负担。代码里的初始化配置大致是这个思路VCI_INIT_CONFIG initConfig {0}; // 只接收标准帧 ID0x123 initConfig.AccCode 0x123; initConfig.AccMask 0xFFFFF800; // Filter的含义参考SDK文档常见为0表示不滤波、1表示仅标准帧、2表示仅扩展帧 initConfig.Filter 0; initConfig.Timing0 0x00; initConfig.Timing1 0x1C; initConfig.Mode 0;需要说明的是具体滤波字段的位含义、Filter取值对应的接收类型不同型号的CAN卡之间可能有差异。最准确的参考是你手上设备型号的用户手册上面通常会有一节专门讲验收码和屏蔽码的计算方法。6. 高频排障设备打不开、收不到报文、中文乱码6.1 “设备打不开”的快速定位VCI_OpenDevice返回失败常见的原因有四类驱动没装好设备类型填错设备被ZCANPro或者其他进程占用了USB线供电不足或接触不良。判断方法很简单打开ZCANPro试试能不能正常打开设备。如果ZCANPro也打不开重点检查驱动和硬件如果ZCANPro能打开但程序打不开那基本就是设备类型常量填错了或者没有先在程序里加载驱动库。老式SDK在打开设备前需要确保依赖的DLL在可执行文件目录下否则函数调用会直接失败。我在排障时习惯在每次调用接口处都检查返回值并且把错误码打印出来周立功的驱动通常会有对应的错误码说明。虽然这样代码看起来啰嗦但在排查问题的时候价值极大。6.2 “能发不能收”的排查链路能发送说明链路、设备、通道基本上没问题收不到大概率是这几个地方的问题接线是否正确终端电阻是否匹配滤波配置把报文过滤掉了接收线程没有启动收发的波特率不一致。我排查的顺序是先在ZCANPro里开一个通道和被测设备对发如果能接收到说明链路正常再用我自己的程序收ZCANPro发出来的报文收不到就分段检查接收线程和滤波配置。注意ZCANPro强占设备时要先关掉它否则程序会一直拿不到设备。6.3 中文乱码Unicode与ANSI的坑老VC工程默认是ANSI编码新一点的VS版本新建MFC工程默认是Unicode。如果是从老工程基础上改的容易出现字符串类型混用的问题。特别是在“根据CAN数据解析出中文字符串显示在编辑框”这种场景下直接用CString和char[]互转很可能出现乱码。我的建议是在新环境里统一使用Unicode字符集CAN报文中收到的原始字节先作为BYTE数组保留转字符串时用带编码参数的转换函数不要依赖默认的隐式转换。如果必须维护一个老的ANSI工程项目属性里确认字符集设置所有接口明确使用CStringA或者CStringW不要混用。6.4 发送不出去的一个隐蔽原因还有一次遇到一个很奇怪的问题发送接口返回成功但总线上就是看不到报文。查到最后发现报文的ID字段填错了。示例代码里ID直接赋值看起来没错但有些SDK对扩展帧和标准帧的ID范围校验不同标准帧如果填了大于0x7FF的ID有的驱动会静默失败。这个问题在传输接口返回成功的情况下特别难发现所以写入ID前要明确这个帧是标准帧还是扩展帧值不要超出有效范围。7. 稳定性补强从“能跑”到“能用”的几个细节7.1 接收报文的保存与回放调试现场离不开报文记录。把接收线程拿到的报文带时间戳写文件这是最基础的功能。MFC里可以用CStdioFile按行写入也可以自己维护一个二进制文件。写文件不要在接收线程里直接做容易阻塞接收建议用一个独立的写盘队列主线程或一个轻量级写盘线程定期从队列取数据落盘。回放数据则更简单读取保存的文件把每条记录按照原始时间间隔或者手动指定间隔重新调用发送接口。这个功能在做回归测试时非常有用重置设备节点后可以重复注入相同的报文序列。7.2 定时发送与连续发送定时间隔发送的标准做法是使用MFC的定时器设置比如50毫秒的定时器事件在定时器处理里发送一帧。这个方案在Windows下足够用了。有个细节是如果定时器间隔太短而发送数据量很大驱动缓冲区和发送接口可能跟不上造成报文实际间隔不稳定。建议实际测量一下发送的实际间隔是否和设定一致。7.3 关键设备状态的用户提示一个实用的进步是把设备状态映射到界面提示上。比如打开设备失败时除了弹窗提示还可以在状态栏里显示错误码还能把“器件已打开但通道未启动”和“通道已启动”区分开。这样拿着软件去现场的人不需要看代码只根据界面状态就能判断出当前处于哪一步。我以前习惯把所有初始化都放在一个按钮里后来拆成“打开设备”和“启动CAN”两个独立按钮排查问题时直观多了。7.4 个人体会这几个项目做下来我最深的体会是CAN上位机开发程序本身不难难点在于链路复杂、现场干扰多、报文语义各不相同。一个真正能稳定使用的MFC CAN调试工具需要把设备管理、线程安全、数据记录、界面反馈这些基础功做扎实而不是简单地把周立功的示例代码搬过来堆一个窗口就完事。调试工具这东西平时没人在意可真到了定位总线故障的时候一个用起来顺手、数据准确、能保存回放的工具带来的效率提升是实实在在的。如果你也在用MFC配合周立功CAN卡做类似的上位机工具可以先把链路打通再把细节打磨到位这套思路在任何CAN调试项目中都适用。本文还有配套的精品资源点击获取
返回列表