在LabVIEW上开发一个支持PCAN-UDS上位机,实际并不是很困难的工作。它看起来困难的原因是需要的知识点比较复杂:不仅仅需要LabVIEW的相关经验,也要对UDS的协议和CAN总线有一定了解。同时最好要用其它语言开发过PCAN-UDS的API程序。
上面的要求看起来要求过于严格,因此有了这篇文章。我们接下来的讲解偏向于LabVIEW封装dll函数,即使不是想要使用PCAN-UDS也可以作为参考。另外讲解时不可避免会涉及到一些UDS协议的内容,届时会同时做出简单的解释。
0 准备工作
按照顺序首先还是要说明准备工作:
PEAK的硬件和驱动,可在peak-system官网进行下载;
下载好PCAN UDS的API的压缩包,以及PCAN ISO-TP和PCAN Basic的压缩包,后两者是UDS的底层API,使用的时候需要调用其中的dll文件。请将其中的dll文件拷贝到可以被调用的位置。
我使用的是LabVIEW 2015 32位的版本。
其实LabVIEW本身也有提供导入dll的功能,只是效果没有那么完美。这里我们可以先尝试一下。
导入dll需要dll文件和头文件,这两个在PCAN UDS的包中都有提供。dll文件分为32位的和64位的,需要和LabVIEW的保持一致。举例:我是64位电脑,32位LabVIEW,需要用32位dll文件。
可以看到导入并不是完全成功的。实际即使这里显示成功的函数,很多也需要进一步更改,因为LabVIEW是按照纯C调用的。这样做的唯一一个好处是,后面不需要一个一个建立子VI了,直接更改LabVIEW做好的就可以。
1 封装自定义类型
下面是由虹科汽车电子团队提供的一个简单例程,您可以直接发邮件到[email protected]获取。请注明虹科汽车电子和您需要的LabVIEW版本。
按照上面实际的工程来看,对于PEAK提供的dll文件,我们除了包装函数以外,一些内容也需要在LabVIEW里面做成自定义类型。我这里总共做了两种:
1.结构体->簇
重复使用的结构体做成簇的自定义类型会方便很多。
以上图为例,头文件的这部分就是需要被我们做成簇的部分,如果我们使用的dll文件同时有说明文档的话,也可以寻找下图这部分(Structure)。
那么实际讲解以TPUDSNetAddrInfo为例,建立的时候和新建簇一样,其中还包含了一些枚举常量,如果枚举常量已经定义好了的话,可以直接拖放进来。
需要特别注意的是,像是SA,TA这样的簇中的内容,它们的顺序和长度定义绝对不能错,否则API读取参数的时候可能就读到了旁边的参数。
这里说明下我们使用的数据类型对应在LabVIEW中的长度:SA,TA,RA都应该定义成U8类型的变量。
BYTE: 无符号8位
WORD: 无符号16位
DWORD: 无符号32位
2.常量->枚举常量
枚举常量的定义就比较简单了,但是需要注意的还是它们的长度。同样,按照h文件中对应的部分进行定义即可。下图是上面簇中展示的枚举常量的定义。
至于上述的常量代表什么含义,请参考ISO-15765标准。
2 封装函数
这里大部分的难点都在于寻找到对应的描述文件对应的位置,如果偶然出现错误,排错的时候可以试着更改附近的参数。
在讲解之前,我们需要先说明C相关的一个知识:函数传递数组的时候,传递的是数组首个元素的指针,而不会是全部元素的值。数组所有元素的地址一定是相邻的。
下面是封装函数的步骤:
1.新建VI,程序框图选择互联接口->库与可执行程序->调用库函数节点
2.找到我们需要封装的函数的相关信息,下面我已经找到并截图下来。包括函数的定义,一些变量的长度,同样,我们需要知道函数的使用方法。
3.库函数的路径和函数名很容易理解,但请注意不要出错(这里犯马虎出错的话很难迅速查找出来原因,有时候LabVIEW会因此直接崩溃)。调用规范需要特别注意,上图中有TPUDSStatus __stdcall UDS_WaitForService,代表了这是一个按照WINAPI调用规范使用的dll文件。
4.参数的定义,首先返回值类型是TPUDSStatus,第一个参数的类型是TPUDSCANHandle,看上图就知道它们分别是WORD和DWORD型的。这里没有特殊说明,它们都是传递了数值。
5.其余的三个参数,全部都是TPUDSMsg类型的,同时看到“*”确认这个参数是传递指针的参数。设置时如下图,三个参数完全一致:
对于这样输入类型是结构体的,要在类型选择匹配至类型,数据格式按照需求选择即可。
实际上由于TPUDSMsg结构里面主要是一个4095(0xFFF,在协议中规定)长度的BYTE数组,用于存放UDS的数据,其余的都是TP层地址的参数。为了方便使用,这里我选择了把它定义成数组,可以方便进行一些操作。这两种方法可以自行选择。
6.然后就是如何给出函数的输入和输出。首先我们需要知道的是哪些参数是输入,哪些是输出,这个并没有一个通用的办法看出来,需要封装的工程师了解使用的dll函数。这里面CanChannel和MessageRequest是输入,其余是输出。
我尝试解释下其它部分像这样做的原因,因为我并不是只连接了输入和输出的部分。
i.CanChannel和MessageRequest是输入,其实只连接左侧,可以不需要把右侧接出来,因为它的输出和输入是完全一样的。我把它们的输出封装出来的原因是:这可以用来控制程序运行的顺序,和错误处理的部分一样。MessageRequest接出来单纯是因为接线方便。
ii.刚刚说了错误处理的部分,一般错误处理按照默认留出来就好,这是LabVIEW的错误处理机制。
iii.尽管MessageBuffer是输出变量,但是由于它传递的是指针,所以我们必须先分配足够的空间并传入这个函数,所以我按照TPUDSMsg的长度传入了一个空数组的指针给函数。如果没做初始化或者分配的空间不够都会导致LabVIEW直接崩溃。
iv. MessageReqBuffer的参数在函数定义时默认就传递了一个NULL值,实际上这个参数对我们没有用途,封装的时候按照要求给出输入就可以了,不需要接出来。
其余的函数按照同样的模式封装就行,上面我选择了一个相对比较困难的函数进行解释。大部分都是重复工作,不过还是推荐每封装一个函数,就对它进行一下测试,确保封装得是正确的。最后我们就可以在LabVIEW中调用封装好的函数了。后面的使用方式和其它语言就类似了。
获取例程,或是对UDS协议有问题的话,也可以联系邮箱[email protected],或者添加我的QQ:3248764982。
虹科也提供UDS相关的培训课程,近期会在上海或武汉有相关课程。有兴趣的朋友可以联系:
电话:136 4075 3740(许小姐,微信同)