ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

LabVIEW调用周立功DLL实现USBCAN-2E/U的CAN总线通信与报文收发

LabVIEW调用周立功DLL实现USBCAN-2E/U的CAN总线通信与报文收发 1. 项目缘起与整体方案设计1.1 为什么偏偏是这套组合在汽车电子、工业控制和测试测量这几个圈子里LabVIEW 配周立功 CAN 盒几乎是一套“老搭档”方案。原因不复杂LabVIEW 擅长做上位机界面、数据采集和实时曲线显示而周立功的 USBCAN-2E/U 这类设备在底层报文收发上足够稳定价格也相对亲民两者结合能快速搭出一套可用的 CAN 总线监控与分析系统。我最早接触这个组合是在一个整车厂零部件测试项目里当时需要同时监控三路 CAN 网络采样周期要求 10ms 以内还要把报文实时落盘并做在线解析。试过几种方案之后最终还是回到 LabVIEW USBCAN-2E/U 这条路上。不是说它没有坑而是坑踩完之后整套流程足够透明出了问题能自己定位不用等原厂支持。这篇文章面向的读者大致分三类一是刚入行做汽车电子测试的工程师手里有 CAN 盒但不知道怎么和 LabVIEW 对接二是做工业设备开发的同行需要把 CAN 数据接进现有 LabVIEW 平台三是学生或爱好者想用低成本方案学习 CAN 总线通信。不管你是哪一类只要跟着把驱动、DLL 调用、报文收发这几步走通后面做二次开发就是水到渠成的事。1.2 整体架构怎么搭整套方案的核心思路其实就一句话LabVIEW 通过调用周立功提供的动态链接库DLL间接操作 USBCAN-2E/U 设备完成 CAN 报文的收发。LabVIEW 本身不直接跟硬件打交道它扮演的是“指挥官”角色真正干活的是 DLL 里的那些函数。具体分层是这样的硬件层USBCAN-2E/U 设备负责物理层 CAN 差分信号收发支持 CAN2.0A/B部分型号还支持 CAN FD。驱动层周立功提供的设备驱动安装后在系统里注册设备让操作系统能识别到 CAN 盒。接口层ControlCAN.dll或对应 64 位版本这是周立功官方提供的二次开发库封装了设备打开、初始化、发送、接收等函数。应用层LabVIEW 程序通过“调用库函数节点”调用 DLL实现业务逻辑。这个架构的好处是职责清晰。驱动出问题就查设备管理器DLL 调用出问题就查函数参数业务逻辑出问题就查 LabVIEW 代码排查路径不会混在一起。注意周立功不同型号的 CAN 盒对应的 DLL 可能不同USBCAN-2E/U 一般用ControlCAN.dll但如果你手里是 CANFD 系列可能是ControlCANFD.dll这个一定要在动手前确认清楚拿错库文件后面所有调用都会失败。1.3 方案选型背后的取舍有人会问为什么不直接用 LabVIEW 的 CAN 接口或者买 NI 的 CAN 卡这里说说我的实际考量。NI 的 CAN 卡确实和 LabVIEW 集成度更高驱动装完就能用但价格摆在那里一个两通道的接口卡够买好几台周立功设备。对于预算有限或者需要多节点部署的场景周立功的性价比优势非常明显。另一个选择是直接用串口转 CAN 的模块通过 LabVIEW 的 VISA 串口函数通信。这种方式门槛低但串口带宽有限高负载下容易丢帧而且协议解析要自己做工作量不小。USBCAN-2E/U 走的是 USB 接口带宽和实时性都好得多官方还提供了现成的 DLL省去大量底层开发。所以这套方案的本质是用官方 DLL 换取开发效率用国产硬件换取成本优势用 LabVIEW 换取界面和数据处理能力。三者各取所长适合中小规模、快速迭代的项目。2. 驱动配置与开发环境准备2.1 驱动安装的正确姿势驱动这一步看似简单实际上是最容易卡住新手的地方。我见过太多人设备插上去设备管理器里一个黄色感叹号然后就不知道怎么办了。正确的流程是这样的先装驱动再插设备。周立功的驱动安装包一般叫USBCAN_Driver之类的名字去官网下载对应型号的最新版本。安装时建议右键“以管理员身份运行”避免权限不足导致注册表写入失败。安装过程中如果弹出 Windows 安全提示选择“始终安装此驱动程序软件”。这不是病毒是驱动没有微软签名或者签名较老导致的正常提示。装完之后再插入 USBCAN-2E/U系统会自动识别并加载驱动。打开设备管理器在“通用串行总线控制器”或者“周立功设备”分类下应该能看到设备名称没有黄色感叹号就说明驱动正常。确认设备编号。周立功的设备在系统里会有个索引号一般是 0、1、2 这样排下去。如果你只插了一台通常就是 0。这个编号后面调用VCI_OpenDevice时要用到。实操心得如果设备管理器里显示“未知设备”先换一个 USB 口试试尤其是台式机前面的 USB 口供电可能不足。还不行就卸载驱动重启再装一遍十有八九能解决。2.2 LabVIEW 环境与位数匹配LabVIEW 这边有个非常关键的坑位数必须匹配。周立功提供的ControlCAN.dll分 32 位和 64 位两个版本你的 LabVIEW 是 32 位就得用 32 位的 DLL是 64 位就得用 64 位的 DLL混用一定报错。怎么确认 LabVIEW 位数打开 LabVIEW点“帮助”-“关于 LabVIEW”里面会写明是 32-bit 还是 64-bit。目前很多老项目还在用 32 位 LabVIEW因为一些老驱动只支持 32 位这个要提前确认。DLL 文件一般放在周立功 SDK 安装目录下路径类似C:\Program Files\ZLG\USBCAN\ControlCAN.dll。建议把 DLL 复制一份到你的 LabVIEW 项目文件夹里用相对路径调用这样项目拷贝到别的电脑上也能跑。另外LabVIEW 调用 DLL 需要用到“调用库函数节点”Call Library Function Node这个节点在“互联接口”函数选板里。第一次用可能觉得参数配置很繁琐但配好一次之后直接复制就行。2.3 关键函数清单与参数速查在动手写代码之前先把要用的核心函数理清楚。周立功的ControlCAN.dll里函数不少但常用的就下面这几个函数名作用关键参数VCI_OpenDevice打开设备设备类型、设备索引、保留参数VCI_InitCAN初始化 CAN 通道设备类型、索引、通道号、初始化结构体VCI_StartCAN启动 CAN 通道设备类型、索引、通道号VCI_Transmit发送报文设备类型、索引、通道号、报文结构体、数量VCI_Receive接收报文设备类型、索引、通道号、缓冲区、数量、等待时间VCI_CloseDevice关闭设备设备类型、索引设备类型这个参数USBCAN-2E/U 对应的值一般是4VCI_USBCAN2但不同固件版本可能有差异最好查一下随设备附带的头文件或者手册确认。初始化结构体VCI_INIT_CONFIG里有几个关键字段AccCode和AccMask是验收码和屏蔽码用来做硬件滤波Filter是滤波方式Timing0和Timing1是波特率定时参数Mode是工作模式正常模式还是只听模式。波特率这块要重点说一下。周立功的定时参数不是直接填波特率数值而是填两个寄存器值。比如 500kbps 在 16MHz 时钟下常见的配置是Timing0 0x00Timing1 0x1C。这个值算错了通信就不通后面我会专门讲怎么算。3. 核心细节解析与实操要点3.1 波特率定时参数怎么算这是整个项目里最容易被忽略、又最容易出错的地方。很多人初始化失败折腾半天发现是波特率参数填错了。CAN 总线的波特率由位时间决定一个位时间分成四段同步段、传播段、相位缓冲段1、相位缓冲段2。周立功的Timing0和Timing1两个字节就是用来配置这些段的。以常见的 16MHz 时钟、500kbps 为例计算过程是这样的位时间 1 / 500kbps 2 微秒 32 个时钟周期16MHz 下每个周期 62.5ns预分频器 BRP 设为 4则 TQ时间份额 4 个时钟周期总 TQ 数 32 / 4 8分配同步段 1 TQ传播段 1 TQ相位缓冲段1 3 TQ相位缓冲段2 3 TQTiming0的低 6 位是 BRP-1 3高 4 位是同步段传播段相位缓冲段1 的 TQ 数减 1Timing1的低 4 位是相位缓冲段2 的 TQ 数减 1高 4 位是 SJW算下来Timing0 0x00Timing1 0x1C。这个组合在 500kbps 下非常常用可以直接抄。如果你用的是其他波特率比如 250kbps 或者 125kbps建议直接用周立功提供的波特率计算工具或者查手册里的推荐值表不要自己硬算容易出错。注意总线上所有节点的波特率必须完全一致哪怕差一点都通信不了。如果你不确定总线波特率可以用示波器测一下位时间或者用 CAN 分析仪自动侦测。3.2 报文结构体的字段含义发送和接收报文都要用到VCI_CAN_OBJ这个结构体字段不少但常用的就几个ID报文标识符标准帧 11 位扩展帧 29 位。TimeStamp时间戳接收时由硬件填充发送时一般填 0。TimeFlag是否使用时间戳标志。SendType发送类型0 是正常发送1 是单次发送2 是自发自收3 是单次自发自收。RemoteFlag远程帧标志0 是数据帧1 是远程帧。ExternFlag扩展帧标志0 是标准帧1 是扩展帧。DataLen数据长度0 到 8。Data数据数组8 个字节。Reserved保留字段。在 LabVIEW 里这个结构体要用“簇”来对应字段顺序和类型必须和 DLL 定义完全一致否则数据会错位。我一般会先建一个簇把字段按顺序排好然后在调用库函数节点里把这个簇作为参数传进去。有个细节要注意Data是 8 字节数组LabVIEW 里用“无符号 8 位整型数组”表示。如果数据长度小于 8后面的字节填 0 就行接收方会根据DataLen判断有效数据长度。3.3 硬件滤波与软件滤波的取舍周立功的 CAN 盒支持硬件滤波通过AccCode和AccMask两个参数配置。硬件滤波的好处是不相关的报文根本不会进入接收缓冲区减轻 CPU 负担适合总线负载高的场景。但硬件滤波有个限制它只能做基于 ID 的过滤不能根据数据内容过滤。而且配置起来相对麻烦尤其是需要同时接收多个不同 ID 的时候屏蔽码的设置要仔细算。我的建议是如果总线负载低于 50%直接用软件滤波就行把所有报文都收上来在 LabVIEW 里用条件结构筛选。这样灵活改起来也方便。如果负载很高比如超过 70%那就必须上硬件滤波否则接收缓冲区容易溢出丢帧。软件滤波的典型做法是接收函数一次读一批报文比如 100 条然后用 For 循环遍历对每条报文的 ID 做判断符合条件就处理不符合就丢弃。这个逻辑在 LabVIEW 里用数组操作和条件结构很容易实现。4. 实操过程与核心环节实现4.1 设备打开与初始化流程整个流程按顺序走下来是这样的第一步打开设备。调用VCI_OpenDevice传入设备类型USBCAN-2E/U 一般是 4、设备索引0、保留参数0。返回值是 1 表示成功0 表示失败。如果失败先检查设备是不是被其他程序占用了比如周立功自带的 CANTest 软件没关。第二步初始化 CAN 通道。调用VCI_InitCAN传入设备类型、索引、通道号0 或 1对应两路 CAN、初始化结构体。初始化结构体里填好波特率参数、滤波参数、工作模式。返回值同样是 1 成功、0 失败。第三步启动 CAN 通道。调用VCI_StartCAN参数和初始化类似。这一步成功后设备就开始正常收发报文了。第四步收发报文。发送用VCI_Transmit接收用VCI_Receive。接收函数有个等待时间参数单位是毫秒填 0 表示不等待立即返回填 -1 表示一直等待直到有数据。实际用的时候一般填 10 到 100 之间平衡实时性和 CPU 占用。第五步关闭设备。程序退出前调用VCI_CloseDevice释放资源。这一步不能省否则下次打开可能失败。在 LabVIEW 里这五步对应一个顺序结构或者状态机。我习惯用状态机因为可以灵活处理错误和重连。每个状态里放对应的 DLL 调用出错就跳到错误处理状态。4.2 发送报文的完整实现发送报文相对简单核心就是填好VCI_CAN_OBJ结构体然后调用VCI_Transmit。假设我要发送一条标准帧ID 是 0x123数据是 8 个字节01 02 03 04 05 06 07 08周期 100ms。实现步骤建一个VCI_CAN_OBJ簇ID填 0x123SendType填 0正常发送RemoteFlag填 0ExternFlag填 0DataLen填 8Data数组填对应值。把这个簇放进一个数组数组长度 1。调用VCI_Transmit传入设备类型、索引、通道号、数组、数量 1。返回值是实际发送的帧数正常应该是 1。如果是 0说明发送失败可能是总线没有其他节点应答或者总线关闭了。用 LabVIEW 的定时函数做 100ms 循环重复发送。这里有个细节VCI_Transmit是阻塞调用如果总线负载很高可能会等一段时间才返回。如果对实时性要求高可以考虑用多线程把发送和接收分开。实操心得发送失败最常见的原因是总线上没有其他节点。CAN 总线要求至少有两个节点才能正常通信一个发一个收。如果你只插了一台设备想测试发送可以把工作模式设成“自发自收”这样自己发的报文自己能收到方便调试。4.3 接收报文与数据解析接收比发送复杂一些因为要处理缓冲区、超时和数据解析。基本流程是建一个足够大的接收缓冲区比如 100 个VCI_CAN_OBJ的数组。调用VCI_Receive传入设备类型、索引、通道号、缓冲区、缓冲区大小、等待时间。返回值是实际接收到的帧数。如果大于 0说明有数据遍历这些帧进行处理。对每一帧根据ID判断是什么报文然后解析Data里的数据。数据解析这块要根据具体的 CAN 协议来做。比如车速信号可能占用 2 个字节起始位是第 3 位分辨率是 0.01偏移是 0。解析的时候就要从Data数组里取出对应字节做位运算和数值换算。LabVIEW 里处理字节数组很方便用“数组索引”和“联合体”就能把字节转成数值。注意字节序问题CAN 报文里大端小端都有可能要看协议定义。LabVIEW 默认是小端如果协议是大端需要手动做字节交换。接收循环一般放在一个 While 循环里配合定时器控制循环周期。如果VCI_Receive返回 0说明这段时间没有新报文可以稍微延时再读避免空转占满 CPU。4.4 数据落盘与实时显示接收到的数据除了实时显示通常还要落盘保存方便后续分析。落盘格式我一般用 TDMS 或者 CSV。TDMS 是 NI 自家的格式读写快适合大数据量CSV 通用性好用 Excel 就能打开适合小数据量或者需要给别人看的场景。实时显示用 LabVIEW 的波形图表或者 XY 图。波形图表适合显示随时间变化的信号XY 图适合显示两个变量之间的关系。如果数据量大要注意刷新率不要每个点都刷新可以攒一批再刷否则界面会卡。我一般会做一个环形缓冲区把最近 N 条报文存起来显示的时候只显示这个缓冲区里的数据。这样内存占用可控界面也不会因为数据太多而卡顿。5. 常见问题与排查技巧实录5.1 设备打开失败怎么办这是最常见的问题表现是VCI_OpenDevice返回 0。排查顺序如下现象可能原因解决方法设备管理器有黄色感叹号驱动没装好重装驱动注意先卸旧驱动设备管理器正常但打开失败设备被占用关闭 CANTest 等官方软件换电脑后打开失败DLL 位数不匹配确认 LabVIEW 和 DLL 位数一致偶尔成功偶尔失败USB 供电不稳换 USB 口用带供电的 HUB我遇到过最坑的一次是设备管理器显示正常但就是打不开折腾半天发现是之前跑的一个测试程序没退干净后台还占着设备。用任务管理器把残留进程杀掉就好了。5.2 能打开但收不到数据设备打开了初始化也成功了但VCI_Receive一直返回 0。这种情况一般是以下几个原因波特率不对。总线上其他节点的波特率和你的设置不一致报文根本解不出来。用示波器或者 CAN 分析仪确认总线波特率。接线问题。CAN_H 和 CAN_L 接反了或者终端电阻没接。CAN 总线两端各需要一个 120 欧姆终端电阻少了通信不稳定多了负载太重。滤波设置太严。AccCode和AccMask把需要的报文过滤掉了。先把屏蔽码设成全 0接收所有报文试试。工作模式不对。如果设成了只听模式是发不出去报文的但收应该正常。如果设成了其他特殊模式可能影响接收。排查的时候建议用二分法先用官方 CANTest 软件测试如果能收到数据说明硬件和驱动没问题问题在 LabVIEW 代码如果 CANTest 也收不到那就是硬件或接线问题。5.3 高负载下丢帧怎么优化总线负载高的时候丢帧是常见问题优化方向有几个加大接收缓冲区。VCI_Receive的缓冲区数组开大一点比如 1000 条一次多读一些。缩短接收周期。While 循环里少延时尽快把数据读走。启用硬件滤波。把不需要的报文在硬件层面过滤掉减轻软件负担。优化 LabVIEW 代码。避免在接收循环里做耗时操作比如文件写入、界面刷新这些可以放到单独的线程或者用队列传递出去处理。降低波特率。如果总线负载实在太高考虑降低波特率或者拆分网络。实测下来500kbps 波特率下LabVIEW 单线程处理负载 60% 左右可以稳定不丢帧。超过 70% 就要做优化了。5.4 时间戳不准怎么处理VCI_CAN_OBJ里的TimeStamp是硬件时间戳单位是 0.1ms。但有些情况下这个时间戳不准比如设备刚启动的时候或者总线负载突变的时候。如果对时间精度要求高建议用 LabVIEW 自己的时间函数打时间戳在接收到报文的那一刻记录系统时间。虽然精度不如硬件时间戳但一致性更好。另外注意TimeFlag字段要设成 1硬件才会填充时间戳。如果设成 0TimeStamp就是 0。5.5 程序退出后设备没释放这个问题很隐蔽表现是程序关了但下次打开设备失败。原因是VCI_CloseDevice没调用或者调用失败。解决办法是在 LabVIEW 程序里用“事件结构”捕获程序退出事件在退出前确保调用VCI_CloseDevice。如果程序异常崩溃可能来不及调用这时候只能重启电脑或者手动杀进程。更稳妥的做法是用“通知器”或者“队列”做优雅退出确保所有资源都释放了再结束程序。6. 进阶技巧与扩展思路6.1 多通道同步采集USBCAN-2E/U 有两个 CAN 通道可以同时采集两路总线。实现上就是开两个接收循环分别读通道 0 和通道 1然后用队列把数据汇总到一个处理循环里。同步的关键是时间戳。两个通道的硬件时间戳是同一个时钟源所以可以直接比较。如果发现两路数据的时间戳有偏差检查一下是不是两个通道的初始化时间不同步。6.2 CAN FD 报文的处理如果用的是支持 CAN FD 的型号报文结构体和经典 CAN 略有不同数据长度可以到 64 字节。DLL 函数也不一样要用ControlCANFD.dll里的函数。CAN FD 的波特率分仲裁段和数据段仲裁段通常还是 500kbps数据段可以到 2Mbps 甚至更高。初始化的时候要分别设置这两个波特率。6.3 与数据库对接做长期监控如果要做长期监控可以把解析后的数据存到数据库里比如 MySQL 或者 SQLite。LabVIEW 有数据库连接工具包或者用 Python 脚本做中转也行。我一般用 LabVIEW 把数据写成 CSV然后用 Python 脚本定时导入数据库。这样分工明确LabVIEW 专注采集Python 专注数据处理两边都不容易出问题。6.4 做成可复用的子 VI如果多个项目都要用 CAN 通信建议把核心功能封装成子 VI比如“打开设备”、“初始化通道”、“发送报文”、“接收报文”、“关闭设备”。每个子 VI 做好错误处理上层直接调用就行。封装的时候注意把设备类型、索引、通道号这些参数做成输入不要写死。这样换设备或者换通道的时候不用改代码。7. 个人实操体会这套方案我从最早接触到现在前后用在四五个项目里踩过的坑基本都在这篇文章里了。最大的体会是驱动和 DLL 调用这一步一定要按官方文档来不要自己发挥。周立功的文档虽然写得不算特别友好但关键参数都是对的自己乱改反而容易出问题。另一个体会是LabVIEW 调用 DLL 的性能瓶颈往往不在 DLL 本身而在 LabVIEW 的数据处理和界面刷新。如果发现丢帧先查代码里有没有在接收循环里做耗时操作十有八九是这个问题。最后分享一个小技巧调试阶段可以在 LabVIEW 里加一个“原始报文显示”窗口把所有收到的报文原样显示出来不做任何解析。这样能快速判断是通信问题还是解析问题。通信问题就是收不到或者收到乱码解析问题就是报文正常但数值不对。这个窗口在正式发布的时候可以隐藏掉但调试阶段非常有用。
返回列表