如何与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在芬兰及/或其他国家和地区的商标。所有其他商标均为其各自所有者的财产。