ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

LabVIEW调用周立功USBCAN-2E/U实现CAN通信完整指南

LabVIEW调用周立功USBCAN-2E/U实现CAN通信完整指南 1. 为什么要在LabVIEW里折腾周立功USBCAN-2E/U如果你手头同时有LabVIEW和ZLG周立功的USBCAN-2E/U设备大概率会经历这样一个阶段设备管理器里能看到ZLG自家的CANTest软件跑得飞起但一打开LabVIEW就不知道从哪下手。LabVIEW自带的CAN函数库主要面向NI自家的CAN卡对第三方设备的支持并不直接而周立功提供的LabVIEW驱动包又藏得比较深官方文档写得偏“手册化”新手照着做经常卡在第一步。这篇内容就是把我自己从零跑通这套组合的完整过程拆开来讲。核心目标很明确让LabVIEW通过周立功USBCAN-2E/U设备完成CAN报文的发送和接收并且能稳定跑起来。涉及的关键环节包括驱动安装、DLL调用方式选择、数据结构对齐、波特率配置、收发线程处理以及实际调试中那些文档里不会写的坑。适合的读者是有LabVIEW基础会连线、懂基本数据类型但对CAN通信和第三方设备集成不太熟的人或者已经用过CANTest想把它集成进自己LabVIEW上位机项目里的工程师。USBCAN-2E/U这个设备本身是双通道CAN转USB接口卡支持CAN2.0A/B最高1Mbps工业现场用得很多尤其是汽车电子、工控设备调试场景。把它接进LabVIEW意味着你可以用LabVIEW做自动化测试、数据记录、甚至和别的仪器联动而不是每次手动开CANTest点来点去。我踩过的第一个坑就是以为装了ZLG的驱动就万事大吉结果LabVIEW里根本找不到设备。后来才搞明白ZLG的驱动分两层——底层USB驱动和上层API动态库LabVIEW要调用的是后者而且调用方式有讲究。下面从驱动配置开始一步步说清楚。2. 驱动安装与LabVIEW调用方式的选型逻辑2.1 ZLG驱动包的组成与安装顺序周立功USBCAN-2E/U的驱动包通常包含几个部分USB设备驱动让系统识别硬件、CAN接口动态库ControlCAN.dll或zlgcan.dll、以及示例代码。这里有个关键分水岭老款USBCAN-2E/U一般配套的是ControlCAN.dll这套经典API而较新的设备可能用zlgcan.dllZLG CAN FD那套统一接口。你得先确认自己设备对应的库是哪个因为两者的函数名、数据结构完全不一样。安装顺序上我的建议是先装ZLG官方驱动包通常叫“USBCAN驱动”或“ZLG CAN驱动”装完后插上设备在设备管理器里确认能看到“ZLG USBCAN Device”之类的条目没有黄色感叹号。然后找到安装目录下的DLL文件一般在C:\Program Files (x86)\ZLG\USBCAN\或者类似路径。把这个路径记下来LabVIEW调用DLL时需要指定完整路径或者把DLL复制到系统目录。注意32位LabVIEW只能调用32位DLL64位LabVIEW只能调用64位DLL。ZLG的驱动包通常两种都有但默认安装可能只装了一种。如果你LabVIEW是64位的而DLL是32位的调用时会直接报“找不到库”或“库加载失败”。这个坑我见过太多人中招排查半天以为是路径问题其实是位数不匹配。2.2 LabVIEW调用DLL的三种方式对比LabVIEW调用外部DLL常见有三种路子调用库函数节点Call Library Function Node简称CLFN、导入共享库向导、以及通过.NET或ActiveX封装。针对ControlCAN.dll最直接的是CLFN。调用方式优点缺点适用场景调用库函数节点灵活、可控、无需额外封装需手动配置每个函数参数函数数量少、调用逻辑清晰时导入共享库向导自动生成VI、省事对复杂结构体支持差、易出错快速验证、函数简单时.NET/ActiveX封装面向对象、易维护需要额外写封装层大型项目、多设备管理我最终选的是CLFN原因是ControlCAN.dll的函数就那么几个核心的打开设备、初始化、启动、发送、接收、关闭用CLFN逐个配置反而最清楚出问题也好定位。导入向导生成的VI在处理VCI_CAN_OBJ这种结构体数组时经常对不齐调试起来更痛苦。2.3 核心API函数清单与参数含义ControlCAN.dll里我们主要用这几个函数先把它们的作用和参数搞清楚VCI_OpenDevice(DWORD DevType, DWORD DevIndex, DWORD Reserved)打开设备。DevType对USBCAN-2E/U通常是4具体查手册DevIndex是设备索引0开始Reserved填0。VCI_InitCAN(DWORD DevType, DWORD DevIndex, DWORD CANIndex, PVCI_INIT_CONFIG pInitConfig)初始化某一路CAN。CANIndex是通道号0或1。VCI_StartCAN(DWORD DevType, DWORD DevIndex, DWORD CANIndex)启动CAN通道。VCI_Transmit(DWORD DevType, DWORD DevIndex, DWORD CANIndex, PVCI_CAN_OBJ pSend, ULONG Len)发送报文Len是帧数。VCI_Receive(DWORD DevType, DWORD DevIndex, DWORD CANIndex, PVCI_CAN_OBJ pReceive, ULONG Len, INT WaitTime)接收报文WaitTime是等待超时毫秒。VCI_CloseDevice(DWORD DevType, DWORD DevIndex)关闭设备。其中VCI_INIT_CONFIG和VCI_CAN_OBJ是两个关键结构体LabVIEW里需要用“簇”来对应而且字段顺序、数据类型必须和C语言定义完全一致否则数据全乱。这个后面单独讲。3. 结构体对齐LabVIEW簇与C结构体的映射细节3.1 VCI_INIT_CONFIG的字段拆解C语言里这个结构体大概长这样以经典ControlCAN为例typedef struct { DWORD AccCode; DWORD AccMask; DWORD Reserved; UCHAR Filter; UCHAR Timing0; UCHAR Timing1; UCHAR Mode; } VCI_INIT_CONFIG;对应到LabVIEW要建一个簇字段顺序不能变AccCodeU32、AccMaskU32、ReservedU32、FilterU8、Timing0U8、Timing1U8、ModeU8。这里有个大坑C结构体默认有字节对齐DWORD是4字节对齐后面四个UCHAR连续放总共占16字节。LabVIEW簇默认是紧凑排列还是对齐排列取决于你建簇时的设置。如果不对齐传给DLL的数据就会错位。我的做法是在LabVIEW里建簇时把每个元素按顺序放好然后在CLFN配置界面里对结构体参数选择“按值传递”并且确保LabVIEW的簇布局和C结构体一致。实测下来只要字段顺序和类型对LabVIEW默认的簇排列在大多数情况下能对上但保险起见可以在簇里手动加填充字节或者用“扁平化字符串”再传。3.2 VCI_CAN_OBJ的收发数据结构这个结构体更关键收发都用它typedef struct { UINT ID; UINT TimeStamp; BYTE TimeFlag; BYTE SendType; BYTE RemoteFlag; BYTE ExternFlag; BYTE DataLen; BYTE Data[8]; BYTE Reserved[3]; } VCI_CAN_OBJ;LabVIEW里对应IDU32、TimeStampU32、TimeFlagU8、SendTypeU8、RemoteFlagU8、ExternFlagU8、DataLenU8、DataU8数组长度8、ReservedU8数组长度3。注意Data是固定8字节数组即使DataLen小于8也要占满8字节。Reserved占3字节别漏掉否则整个结构体大小不对DLL读到的就是垃圾数据。提示发送时SendType一般填0正常发送RemoteFlag填0数据帧ExternFlag填0标准帧或1扩展帧。接收时这些标志位由设备填充你直接读就行。3.3 字节序与大小端问题CAN报文里的数据字节序以及结构体里多字节字段如ID、TimeStamp的字节序在x86平台上都是小端。LabVIEW默认也是小端所以一般不用额外转换。但如果你在LabVIEW里用“强制类型转换”或者“扁平化”操作要注意别把字节序搞反。我遇到过有人用“数值转字符串”再拼接结果ID高低字节颠倒发出去的报文ID完全不对。正确做法是直接用簇→扁平化字符串让LabVIEW按内存布局输出。4. 从打开设备到收发跑通的完整操作链路4.1 设备打开与通道初始化的顺序整个流程必须严格按顺序来跳步就会失败调用VCI_OpenDevice检查返回值是否为1成功。调用VCI_InitCAN初始化通道0传入配置簇。调用VCI_StartCAN启动通道0。如果要双通道对通道1重复2、3步。收发操作。结束时VCI_CloseDevice。这里有个细节VCI_InitCAN的配置里Timing0和Timing1决定波特率。ZLG手册里有一张表比如1Mbps对应Timing00x00、Timing10x14500kbps对应0x00、0x1C250kbps对应0x01、0x1C。这个不能随便填填错了通信不上而且不会报错只是收不到数据。我建议先把两个通道都设成一样的波特率用CANTest验证能通再换到LabVIEW。4.2 发送报文的LabVIEW实现发送逻辑相对简单建一个VCI_CAN_OBJ簇填好ID、DataLen、Data然后组成数组长度1传给VCI_Transmit。CLFN配置时pSend参数选“数组”元素类型是簇传递方式选“按引用”还是“按值”这里建议用“按引用”指针因为DLL期望的是指针。LabVIEW CLFN里对数组参数选“数组数据指针”即可。发送成功后返回值是实际发送的帧数正常应该是1。如果返回0说明发送失败可能是通道没启动、波特率不对、或者总线没接好。我习惯在发送后加一个简单的错误判断返回0就亮个指示灯方便调试。4.3 接收报文的超时与缓冲处理接收是难点。VCI_Receive的WaitTime参数很关键填0表示非阻塞立即返回当前缓冲区里的帧填大于0表示阻塞等待直到有数据或超时。在LabVIEW里如果你把它放在一个While循环里轮询WaitTime填0会占满CPU填100毫秒则循环每100ms查一次CPU占用低但实时性稍差。我的做法是单独开一个While循环专门做接收WaitTime设50ms每次调用接收函数传入一个长度比如100的VCI_CAN_OBJ数组返回值是实际收到的帧数。然后把收到的帧解析出来ID、Data、时间戳等存到队列或者直接显示。注意接收数组要预先分配好大小LabVIEW里用“初始化数组”建一个100元素的簇数组传给CLFN。注意如果总线上数据量大接收数组太小会丢帧。100帧的缓冲在500kbps下大概能撑几十毫秒一般够用。如果发现丢帧加大数组长度或者缩短WaitTime提高轮询频率。4.4 关闭设备与资源释放程序退出前一定要调VCI_CloseDevice否则下次打开可能失败或者设备处于异常状态。LabVIEW里可以放在“停止”按钮的事件分支里或者用“程序结束”事件。我一般还会在关闭前先停止CAN通道如果有VCI_StopCAN函数的话不过ControlCAN经典API里没有单独的Stop直接Close就行。5. 调试中那些文档不会告诉你的坑5.1 设备索引与通道号的混淆VCI_OpenDevice的DevIndex和VCI_InitCAN的CANIndex是两个概念。DevIndex是设备序号如果你只插了一个USBCAN-2E/U它就是0。CANIndex是设备上的通道号USBCAN-2E/U有两个通道分别是0和1。我见过有人把CANIndex填成1去初始化第一个通道结果一直失败。记住设备索引从0开始通道索引也从0开始但它们是独立的。5.2 波特率配置与终端电阻波特率不对通信绝对不通而且没有任何错误提示。除了Timing0/Timing1要查表填对还要注意总线两端的120欧姆终端电阻。USBCAN-2E/U本身可能内置了终端电阻看型号如果总线上已经有其他节点带了电阻再并一个可能导致电阻过低通信距离短或者直接不通。我一般先用CANTest确认物理层没问题再切LabVIEW。5.3 LabVIEW版本与DLL位数不匹配前面提过这里再强调一次。如果你用的是LabVIEW 2020 64位而ZLG驱动装的是32位DLLCLFN加载时会报错。解决办法要么换32位LabVIEW要么找64位DLL。ZLG官网一般两种都有但安装包可能默认只装一种。去安装目录下看看有没有ControlCAN.dll和ControlCAN64.dll之类的区分。5.4 接收数据错乱与结构体对齐如果收到的ID、数据全是乱码九成是结构体没对齐。检查LabVIEW簇的字段顺序、类型是否和C定义完全一致特别是Data数组和Reserved数组。另外CLFN里对结构体参数要选“按值传递”还是“按引用”对于VCI_Receive的pReceive它是输出缓冲区应该传数组指针LabVIEW里选“数组数据指针”并且数组元素是簇。如果选错了DLL写回来的数据就错位。5.5 多线程与循环 timingLabVIEW里接收循环如果WaitTime设0CPU会飙到100%。设50ms比较平衡。但如果你同时有发送循环和接收循环注意别让它们互相阻塞。我一般把发送放在事件结构里按钮触发接收放在独立While循环两者通过队列或全局变量交换数据。这样发送不会因为接收循环的等待而延迟。6. 跑通之后还能怎么扩展基础收发跑通后可以往上叠的东西很多。比如加一个CAN报文解析层根据ID把数据映射成物理量温度、转速等用波形图显示。或者加记录功能把收到的帧带时间戳存成TDMS或CSV方便事后分析。再进一步可以做UDS诊断或者CANopen协议栈那就复杂了但底层还是这套收发机制。我自己项目里还加了一个“总线负载率”计算根据单位时间收到的帧数和波特率估算负载超过阈值就报警。这个在调试整车网络时挺有用。另外如果你有多个USBCAN设备DevIndex依次递增可以同时管理多路CANLabVIEW的多线程优势就体现出来了。最后说一个实际体会ZLG的DLL调用本身不复杂难的是细节对齐和调试信息的获取。建议第一次跑的时候每一步的返回值都打印出来用LabVIEW的“单步执行”或者探针看数据流。一旦通了后面就是复制粘贴的事。
返回列表