这篇文章记录自己在尝试CANoe使用自带的FDX协议与其他语言通讯的过程。
环境设置
.cfg文件设置
使能FDX协议
在CANoe的Options->Extensions->XIL API & FDX Protocol中使能FDX,CANoe作为UDP的Server,根据情况设置一个PortNumber。
创建环境变量
创建如下的环境变量,这里使用4种比较常用的数据类型
添加FDX Description Files
回到一开始的FDX设置页面,添加一个新的FDX描述文件,在cfg的根目录下创建一个xml文件,添加如下文字,这样CANoe就可以解析了
<?xml version="1.0" encoding="ISO-8859-1"?>
<canoefdxdescription version="1.0">
</canoefdxdescription>
创建加载xml文件后,辨析需要接收或发送的变量,为刚刚创建的System Varible创建如下对的字段映射表
这里我给String留了16Byte的空间,一般的字符串也够了
UDP报文格式
这里使用WireShark软件对DataRequest Command类型的报文进行抓包分析,其他类型的请求报文可以参考官方文档进行分析
下面这张图是Python发送的报文格式
下面这张图是CANoe返回的报文格式,其中蓝底的0x03代表着下面对的数据场有三段含义
- 红底的16Byte的标识为0x0004的数据段,代表CANoe的Status
- 黄底的8Byte的标识为0x000b的数据段,代表CANoe的Error
- 绿底的40Byte的标识为0x0005的数据段,代表CANoe对我们上面发送的请求的回复,实际有效的数据是最后的32个Byte,对应着在XML文件中的4个数据字段
- 0x00000001代表INT32类型的isOpenDoor变量,采用补码表示,十进制为1
- 0x4030800000000000代表DOUBLE类型的vehSpd变量,采用IEEE754标准表示,为什么我知道,因为我刚刚手算过,十进制为16.5
- 0xffffffec代表INT32类型的EMTorque变量,采用补码表示,十进制为-20
- 最后面的16Byte是ASCII编码的字符串格式,解码后为String
CANoe当前的变量如下图所示
掌握了这个方法,基本上就可以在任意的编程语言下面打包拆包报文了,其他类型的交互报文类似
交互模式
这部分我们使用Python写一个UDP的Socket和CANoe进行交互
交互模式有两种
- 一种是其他端口发送一个UDP报文,CANoe返回一个UDP报文,必须先给CANoe发送,CANoe才能回复
- 另一种是其他端口给CANoe发送一个自由模式的请求报文,这段报文中指明了比如CANoe可以循环发送某一个数据组,或者通过CAPL来触发。必须也是其他端口先发送一个请求。
CANoe接收
Start Command
按照规定的报文格式,填充下面的数据,可以使能CANoe的Start
import socket
import time
client = socket.socket(socket.AF_INET,
socket.SOCK_DGRAM) #udp协议
client.bind(('127.0.0.1', 2021))
ip_port = ('127.0.0.1', 2020)
startCommond = \
'\x43\x41\x4E\x6F\x65\x46\x44\x58'\
'\x02\x00\x01\x00\x00\x00\x01\x00'\
'\x00\x04\x00\x01'.encode('utf-8')
client.sendto(startCommond, ip_port)
client.close()
python发送完上面这一段后,可以看到CANoe开始测量了
Stop Command
import socket
import time
client = socket.socket(socket.AF_INET,
socket.SOCK_DGRAM) #udp协议
client.bind(('127.0.0.1', 2021))
ip_port = ('127.0.0.1', 2020)
stopCommond = \
'\x43\x41\x4E\x6F\x65\x46\x44\x58'\
'\x02\x00\x01\x00\x00\x00\x01\x00'\
'\x00\x04\x00\x02'.encode('utf-8')
client.sendto(stopCommond , ip_port)
client.close()
python发送完上面这一段后,可以看到CANoe停止测量了
Key Command
这部分没有试过,先贴出来
DataRequest Command
import socket
import time
client = socket.socket(socket.AF_INET,
socket.SOCK_DGRAM) #udp协议
client.bind(('127.0.0.1', 2021))
ip_port = ('127.0.0.1', 2020)
dataRequestCommond = \
'\x43\x41\x4E\x6F\x65\x46\x44\x58'\
'\x02\x00\x01\x00\x00\x00\x01\x00'\
'\x00\x06\x00\x06\x00\x01'.encode('utf-8')
client.sendto(startCommond, ip_port)
client.close()
这部分抓包分析见上面的“UDP报文格式”部分
Status Request Command
这部分没有试,当时从其他请求中CANoe返回的报文可以看出,CANoe会发出来
Function Call Command
这部分没有试
Increment Time Command
这部分没有试
双方交互
DataExchange Command
这段数据场可以XML中指定的CANoe的数据
import socket
import time
client = socket.socket(socket.AF_INET,
socket.SOCK_DGRAM) #udp协议
client.bind(('127.0.0.1', 2021))
ip_port = ('127.0.0.1', 2020)
dataExchangeCommond = \
'\x43\x41\x4E\x6F\x65\x46\x44\x58'\
'\x02\x00\x01\x00\x00\x00\x01\x00'\
'\x00\x28\x00\x05\x00\x01\x00\x20'\
'\x00\x00\x00\x00\x40\xA4\x12\x00'\
'\x00\x00\x00\x00\x00\x00\x66\x34'\
'\x41\x42\x43\x44\x45\x46\x47\x48'\
'\x49\x50\x51\x52\x53\x54\x55'.encode('utf-8')
client.sendto(dataExchangeCommond, ip_port)
client.close()
这段数据发送后,会改变CANoe中预先设定的变量,数据变化后的CANoe数据如下图所示
CANoe发送
DataError Command
Status Command
Sequence Number Error Command
自由模式
FreeRunningRequest Command
FreeRunningCancel Command
总结
- 使用这个功能可以让CANoe和任意编程语言交互数据
- 在这个通讯中CANoe扮演着Slave的角色
- 我在使用Python发送DOUBLE的16进制给CANoe的时候,WireShark总是捕捉到多发一个0x2C,不知道是不是Python写的不对,比如编码格式是不是错了。
- 还没有试在一个局域网中两台主机交互的情况,不过应该也可以
- Sequence number这个字段理论上其他端口在给CANoe发送的时候,应该是循环变化的,如果一直发一个固定值,CANoe会返回一个错误,提示期望的分组和收到的分组不一致,但是不影响通讯
- 我觉得在实际使用中,FDX最好不要直接控制CAN报文,因为这相当于跨了两层协议,最好只和CANoe的环境变量交互,中间只用一个XML文件约定通讯方式,写和CANoe交互的APP的时候可以写一个xml解析的功能,让双方交互更灵活。