如何与CAN总线设备通信
本部分解释在Squish测试中发送和接收CAN总线消息所需的步骤。
控制器局域网(CAN)是一种消息总线标准,允许微控制器和其他设备(统称为ECU)在不依赖中央主机或总线管理器的情况下进行通信。它起源于汽车行业,但此后已被许多其他应用采用。
Squish CAN API的详细描述可以在CAN总线支持 API文档中找到。如果测试需要复杂的交互或需要对ECU进行详细的建模,使用专注于CAN总线仿真的第三方软件可能更有利,这些软件可以通过FMI接口支持与Squish结合使用。
在一个典型的测试设置中,主机AUT的嵌入式设备将连接到CAN总线上。为了允许在测试脚本中与总线交互,测试驱动程序必须有一个兼容的CAN控制器连接到相同的总线。
Squish CAN总线测试设置图
CAN总线设备
所有对CAN接口的引用都应在它自己的应用程序上下文中进行,该上下文标识了连接到总线的特定系统。可以使用ApplicationContext startCAN(options)函数创建支持CAN总线API的应用程序上下文。然后您可以建立与CAN控制器的连接并开始发送和接收消息。
var canContext = startCAN(); var device = new CanBusDevice("socketcan", "can0"); var frame = new CanBusFrame(0x100, "4121999a"); device.writeFrame(frame);
canContext = startCAN() device = CanBusDevice("socketcan", "can0") frame = CanBusFrame( 0x100, "4121999a") device.writeFrame(frame)
my $canContext = startCAN(); my $device = CanBusDevice->new("socketcan", "can0"); my $frame = CanBusFrame->new(0x100, "4121999a"); $device->writeFrame(frame);
canContext = startCAN(); device = CanBusDevice.new("socketcan", "can0"); frame = CanBusFrame.new(0x100, "4121999a"); device.writeFrame(frame);
set canContext [startCAN] set device [CanBusDevice new socketcan can0] set frame [CanBusFrame new 0x100 00deadbeef00] CanBusDevice invoke $device writeFrame $frame
支持的CAN驱动程序及其使用的设备可以使用List CanBusDevice.pluginNames()和List CanBusDevice.availableDevices(driver)静态方法列出。
var canContext = startCAN(); var plugins = CanBusDevice.pluginNames(); for ( var i in plugins ) { var plugin = plugins[i]; test.startSection(plugin); try { var devices = CanBusDevice.availableDevices(plugin); for ( var j in devices ) { test.log("Device: " + devices[j].deviceName); } } catch ( e ) { test.log("Failed: " + e.message ); } test.endSection(); }
canContext = startCAN() plugins = CanBusDevice.pluginNames() for plugin in plugins: test.startSection(plugin) try: devices = CanBusDevice.availableDevices(plugin) for device in devices: test.log("Device: %s" % device.deviceName) except Exception as e: test.log("Failed: %s" % str(e)) test.endSection()
my $canContext = startCAN(); my @plugins = CanBusDevice->pluginNames(); foreach ( @plugins ) { my $plugin = $_; test::startSection($plugin); eval { @devices = CanBusDevice->availableDevices($plugin); foreach ( @devices ) { test::log("Device: $_->deviceName" ); } } or { test::log("Failed: $@" ); } test::endSection(); }
canContext = Squish.startCAN() plugins = CanBusDevice.pluginNames() plugins.each { |plugin| Test.startSection(plugin) begin devices = CanBusDevice.availableDevices(plugin) devices.each { |device| Test.log("Device: " + device.deviceName) } rescue Exception => e Test.log("Failed: " + e.message ) end Test.endSection() }
startCAN set plugins [invoke CanBusDevice pluginNames] foreach plugin $plugins { test startSection $plugin if { [catch { set devices [invoke CanBusDevice availableDevices $plugin] foreach device $devices { test log [concat "Device: " [property get $device deviceName]] } } err] } { test log [concat "Error: " $err ] } test endSection }
帧内容
CAN标准没有定义帧有效负载的内容——这留给CAN网络和ECU的设计师。因此,在没有任何额外信息的情况下,Squish只能将帧有效负载解释为十六进制字符串。由于使用此类帧表示法非常繁琐,Squish提供了描述所选帧类型内容的方法。为了使用它,您可以为ApplicationContext startCAN(options)函数创建一个描述符文件。
<canschema version="1"> <frames> <frame id="0x100" name="Thermometer"> <fields> <field name="temperature" type="floating" size="32"/> </fields> </frame> <frame id="0x200" name="AirConditioning"> <fields> <field name="targetTemp" type="integral" size="32"/> <field name="cooler" type="integral" size="1"/> <field name="heater" type="integral" size="1"/> </fields> </frame> </frames> </canschema>
使用上述描述符文件,在测试脚本中可以访问帧成员。
var canContext = startCAN({schema: File.open(fileName,"r").read()}); var device = new CanBusDevice("socketcan", "can0"); var frame = new ThermometerFrame(); frame.temperature = 10.1; test.log(frame.hexPayload); // Logs "4121999a" device.writeFrame(frame);
canContext = startCAN(open(fileName, "r").read()) device = CanBusDevice("socketcan", "can0") frame = ThermometerFrame() frame.temperature = 10.1 test.log(frame.hexPayload) # Logs "4121999a" device.writeFrame(frame)
my $canContext = startCAN(); my $device = CanBusDevice->new("socketcan", "can0"); my $frame = ThermometerFrame->new(); $frame->temperature = 10.1; test::log($frame->hexPayload); # Logs "4121999a" $device->writeFrame(frame);
canContext = startCAN(); device = CanBusDevice.new("socketcan", "can0"); frame = ThermometerFrame.new(); frame.temperature = 10.1; Test.log(frame.hexPayload); # Logs "4121999a" device.writeFrame(frame);
set canContext [startCAN] set device [CanBusDevice new socketcan can0] set frame [ThermometerFrame new] ThermometerFrame set $frame temperature 10.1 test log [ThermometerFrame get hexPayload $th] # Logs "4121999a" invoke [$device writeFrame $frame]
允许字段类型的详细信息可以在CAN帧模式文档中找到。
发送CAN帧
可以使用CanBusDevice.writeFrame(frame)函数将帧发送到CAN设备。然而,将重要的CAN帧以较短的时间间隔重复发送是一种常见的做法。为了模拟特定ECU的行为,您可以创建一个CanBusFrameRepeater类对象。这样的对象将重复发送指定的帧,直到启用为止。
var canContext = startCAN({schema: File.open(fileName,"r").read()}); var device = new CanBusDevice("socketcan", "can0"); var frame = new ThermometerFrame(); frame.temperature = 10.1; var repeater = new CanBusFrameRepeater(device, frame); repeater.interval = 200; // 200ms interval
canContext = startCAN(open(fileName, "r").read()) device = CanBusDevice("socketcan", "can0") frame = ThermometerFrame() frame.temperature = 10.1 repeater = CanBusFrameRepeater(device, frame) repeater.interval = 200 # 200ms interval
my $canContext = startCAN(); my $device = CanBusDevice->new("socketcan", "can0"); my $frame = ThermometerFrame->new(); $frame->temperature = 10.1; repeater = CanBusFrameRepeater->new(device, frame) repeater.interval = 200 # 200ms interval
canContext = startCAN(); device = CanBusDevice.new("socketcan", "can0"); frame = ThermometerFrame.new(); frame.temperature = 10.1; repeater = CanBusFrameRepeater.new(device, frame); repeater.interval = 200; # 200ms interval
set canContext [startCAN] set device [CanBusDevice new socketcan can0] set frame [ThermometerFrame new] ThermometerFrame set $frame temperature 10.1 set repeater [CanBusFrameRepeater new $device $frame] CanBusFrameRepeater set $repeater interval 200 # 200ms interval
您可以在测试过程中随时修改原始帧对象。更改将立即反映在重复器输出中。
[...] // The measured temperature changes frame.temperature = 12.1; // or repeater.frame.temperature = 12.1;
[...] # The measured temperature changes frame.temperature = 12.1 # or repeater.frame.temperature = 12.1
[...] # The measured temperature changes $frame->temperature = 12.1; # or $repeater->frame->temperature = 12.1;
[...] # The measured temperature changes frame.temperature = 12.1; # or repeater.frame.temperature = 12.1;
[...] # The measured temperature changes ThermometerFrame set frame temperature 12.1 // or ThermometerFrame set [CanBusFrameRepeater get $repeater frame] temperature 12.1
接收帧
可以使用CanBusFrame CanBusDevice.readFrame(timeout)函数接收帧。但是,使用它要求测试编写者及时检查所有传入的帧。虽然它提供了最大的多功能性,但在典型的测试中并不是最方便的工具。相反,您可以使用一个CanBusFrameReceiver类对象。该对象从CAN设备中移除传入的帧,并保留指定ID的接收帧历史记录。
var canContext = startCAN({schema: File.open(fileName,"r").read()}); var device = new CanBusDevice("socketcan", "can0"); var receiver = new CanBusFrameReceiver(device); receiver.setHistorySize(AirConditioningFrame.frameId, 1); snooze(5); // Logs the last-set temperature test.log(receiver.lastFrame(AirConditioningFrame.frameId).targetTemp);
canContext = startCAN() device = CanBusDevice("socketcan", "can0") receiver = CanBusFrameReceiver(device) receiver.setHistorySize(AirConditioningFrame.frameId, 1) snooze(5) # Logs the last-set temperature test.log(receiver.lastFrame(AirConditioningFrame.frameId).targetTemp)
my $canContext = startCAN(); my $device = CanBusDevice->new("socketcan", "can0"); my $receiver = CanBusFrameReceiver->new($device); $receiver->setHistorySize(Squish::AirConditioningFrame->frameId, 1); snooze(5); // Logs the last-set temperature; test::log($receiver->lastFrame(Squish::AirConditioningFrame->frameId)->targetTemp);
canContext = startCAN(); device = CanBusDevice.new("socketcan", "can0"); receiver = CanBusFrameReceiver.new(device); receiver.setHistorySize(AirConditioningFrame.frameId, 1); snooze(5); # Logs the last-set temperature Test.log(receiver.lastFrame(AirConditioningFrame.frameId).targetTemp)
set canContext [startCAN] set device [CanBusDevice new socketcan can0] set receiver [CanBusFrameReceiver new $device] CanBusFrameReceiver invoke $receiver setHistorySize [AirConditioningFrame get frameId] 1 snooze 5 # Logs the last-set temperature set lastFrame [CanBusFrameReceiver invoke $receiver lastFrame [AirConditioningFrame get frameId]] test log [AirConditioningFrame get $lastFrame targetTemp]
还可以等待具有特定ID和特定字段值的帧。
[...] receiver.setHistorySize(AirConditioningFrame.frameId, 1); var frame = receiver.waitForFrame({frameId: AirConditioningFrame.frameId, targetTemp: 18}); test.log("Expected frame received");
[...] receiver.setHistorySize(AirConditioningFrame.frameId, 1) var frame = receiver.waitForFrame({"frameId": AirConditioningFrame.frameId, "targetTemp": 18}) test.log("Expected frame received")
[...] $receiver->setHistorySize(Squish::AirConditioningFrame->frameId, 1); my %query = (frameId => Squish::AirConditioningFrame->frameId, targetTemp => 18); var frame = $receiver->waitForFrame(%query); test::log("Expected frame received");
[...] receiver.setHistorySize(AirConditioningFrame.frameId, 1); frame = receiver.waitForFrame(("frameId"=>Squish::AirConditioningFrame->frameId, "targetTemp"=>18)); Test.log("Expected frame received");
[...] set frameId [AirConditioningFrame get frameId] CanBusFrameReceiver invoke $receiver setHistorySize $frameId 1 set frame [CanBusFrameReceiver invoke waitForFrame (frameId $frameId targetTemp 18)] test log "Expected frame received"
CanBusFrameReceiver.waitForFrame(filter, timeout) 函数在当前历史记录中搜索匹配的帧,如果没有找到,则等待直到接收到匹配的帧。这避免了等待API调用过晚错过刚接收到的帧的情况。
©2024 The Qt Company Ltd. 本文档中的贡献内容均为各自拥有者的版权所有。
本提供的文档根据Free Software Foundation publish的GNU Free Documentation License版本1.3的条款进行许可。
Qt及其相关标志是The Qt Company Ltd在芬兰及/或其他国家和地区的商标。所有其他商标均为其各自所有者的财产。