Main Content

使用 CAN 为 Simulink 模型开发 App

此示例说明如何使用 App 设计工具构造一个测试应用程序用户界面 (UI),并使用虚拟 CAN 通道将其连接到 Simulink® 模型。

该测试应用程序 UI 是使用 MATLAB® 的 App 设计工具和几个 Vehicle Network Toolbox™ 函数构造的,用于为汽车巡航控制应用程序的 Simulink 模型提供虚拟 CAN 总线接口。该测试应用程序 UI 允许用户向巡航控制算法模型提供输入激励,观察从模型反馈的结果,记录 CAN 报文以捕获测试激励,并回放记录的 CAN 报文以调试和更正算法模型的问题。该示例说明了在下面各部分中用于实现 CAN 通信的关键 Vehicle Network Toolbox 函数和模块:

  • 通过 CAN 与 Simulink 算法模型通信以进行测试的测试应用程序 UI

  • 记录和回放 CAN 数据的测试应用程序 UI

  • Simulink 算法模型

向 UI 添加虚拟 CAN 通道通信

本节说明用于向 Simulink 巡航控制算法测试应用程序模型添加 CAN 通道接口的关键 Vehicle Network Toolbox 函数。其中涵盖以下主题:

  • 获取可用 CAN 通道的列表

  • 格式化通道信息以用于创建通道

  • 创建 UI 中的通道

  • 配置 UI 以传输和接收 CAN 报文

  • 启动和停止通道

  • 提取选定的报文

打开 App 设计工具

在 App 设计工具中打开测试应用程序 UI。在 App 设计工具中打开测试应用程序 UI 后,您可以在“设计”和“代码”视图之间切换,以查看控件和对应的 MATLAB 代码,了解如何通过虚拟 CAN 通道与 Simulink 巡航控制算法模型进行通信。

运行准备脚本以预加载必要的数据,然后打开 App 设计工具以开发测试 App:

helperPrepareTestBenchParameterData;
appdesigner('CruiseControlTestUI.mlapp')

列出可用的 CAN 通道

首先,实现一种机制来查找和呈现可供用户选择的可用 CAN 通道的列表。为此,我们在测试应用程序 UI 的左上角添加了“通道配置”菜单项。它有“选择 CAN 通道”子菜单。

当用户点击选择 CAN 通道子菜单时,将通过子菜单回调调用辅助函数 getAvailableCANChannelInfo(app)getAvailableCANChannelInfo() 使用 Vehicle Network Toolbox 函数 canChannelList 检测可用的 CAN 通道,如以下代码片段所示:

function getAvailableCANChannelInfo(app)
    % Get a table containing all available CAN channels and devices.
    app.canChannelInfo = canChannelList;
    
    % Format CAN channel information for display on the UI.
    app.availableCANChannelsForDisplay = formatCANChannelEntryForDisplay(app);
    
    % Save the number of available constructors.
    app.numConstructors = numel(app.canChannelInfo.Vendor);
end

运行 canChannelList 以查看可用的 CAN 通道信息是如何存储的。

canChannels = canChannelList
canChannels=4×6 table
      Vendor         Device       Channel    DeviceModel    ProtocolMode     SerialNumber
    ___________    ___________    _______    ___________    _____________    ____________

    "MathWorks"    "Virtual 1"       1        "Virtual"     "CAN, CAN FD"       "0"      
    "MathWorks"    "Virtual 1"       2        "Virtual"     "CAN, CAN FD"       "0"      
    "Vector"       "Virtual 1"       1        "Virtual"     "CAN, CAN FD"       "100"    
    "Vector"       "Virtual 1"       2        "Virtual"     "CAN, CAN FD"       "100"    

canChannelList 返回的通道列表存储在 UI 属性 app.canChannelInfo 中,然后如上所示显示。

格式化通道列表以用于通道配置

用户从“CAN Channel Selection”listdlg 中选择一个 CAN 通道。listdlg 返回与用户的选择对应的索引。该索引传递给辅助函数 formatCANChannelConstructor

function canChannelConstructor = formatCANChannelConstructor(app, index)
    canChannelConstructor = "canChannel(" + "'" + app.canChannelInfo.Vendor(index) + "'" + ", " + "'" + app.canChannelInfo.Device(index) + "'" + ", " + app.canChannelInfo.Channel(index) + ")";
end

如上面的代码片段所示,formatCANChannelConstructor 使用存储在 CAN 通道表 app.canChannelInfo 中的字符串,来构建与用户从通道选择器列表对话框中所选通道相对应的通道对象构造函数字符串。要查看 CAN 通道构造函数字符串的示例,请执行下面所示的代码。

index = 1;
canChannelConstructor = "canChannel(" + "'" + canChannels.Vendor(index) + "'" + ", " + "'" + canChannels.Device(index) + "'" + ", " + canChannels.Channel(index) + ")"
canChannelConstructor = 
"canChannel('MathWorks', 'Virtual 1', 1)"

CAN 通道构造函数字符串存储在 App 的 UI 属性 app.canChannelConstructorSelected 中,稍后我们将用它来创建应用程序 UI 中选定的 CAN 通道对象,以及更新 Vehicle Network Toolbox 中用于在 Simulink 巡航控制算法模型中实现 CAN 通道接口的 Simulink 模块。

在 UI 中创建 CAN 通道

首次打开和使用 UI 时,辅助函数 setupCANChannel 使用存储在 app.canChannelConstructorSelected 中的格式化 CAN 通道构造函数字符串来创建 CAN 通道对象的实例,连接网络配置数据库 (.dbc) 文件,并设置总线速度,如以下代码片段所示。生成的通道对象存储在 UI 属性 app.canChannelObj 中。

function setupCANChannel(app)
    % Open CAN database file.
    db = canDatabase('CruiseControl.dbc');
    
    % Create a CAN channel for sending and receiving messages.
    app.canChannelObj = eval(app.canChannelConstructorSelected);
    
    % Attach CAN database to channel for received message decoding.
    app.canChannelObj.Database = db;
    
    % Set the baud rate (can only do this if the UI has channel initialization access).
    if app.canChannelObj.InitializationAccess
        configBusSpeed(app.canChannelObj, 500000);
    end
end 

要查看示例 CAN 数据库对象,请执行以下操作:

db = canDatabase('CruiseControl.dbc')
db = 
  Database with properties:

             Name: 'CruiseControl'
             Path: 'C:\Users\jvijayak\OneDrive - MathWorks\Documents\MATLAB\ExampleManager\jvijayak.Bdoc23b.j2277816\vnt-ex00964061\CruiseControl.dbc'
        UTF8_File: 'C:\Users\jvijayak\OneDrive - MathWorks\Documents\MATLAB\ExampleManager\jvijayak.Bdoc23b.j2277816\vnt-ex00964061\CruiseControl.dbc'
            Nodes: {2×1 cell}
         NodeInfo: [2×1 struct]
         Messages: {2×1 cell}
      MessageInfo: [2×1 struct]
       Attributes: {'BusType'}
    AttributeInfo: [1×1 struct]
         UserData: []

要查看示例 CAN 通道对象,请执行以下操作:

% Instantiate the CAN channel object using the channel constructor string.
canChannelObj = eval(canChannelConstructor);

% Attach the CAN database to the channel object.
canChannelObj.Database = db
canChannelObj = 
  Channel with properties:

   Device Information
            DeviceVendor: 'MathWorks'
                  Device: 'Virtual 1'
      DeviceChannelIndex: 1
      DeviceSerialNumber: 0
            ProtocolMode: 'CAN'

   Status Information
                 Running: 0
       MessagesAvailable: 0
        MessagesReceived: 0
     MessagesTransmitted: 0
    InitializationAccess: 1
        InitialTimestamp: [0×0 datetime]
           FilterHistory: 'Standard ID Filter: Allow All | Extended ID Filter: Allow All'

   Channel Information
               BusStatus: 'N/A'
              SilentMode: 0
         TransceiverName: 'N/A'
        TransceiverState: 'N/A'
       ReceiveErrorCount: 0
      TransmitErrorCount: 0
                BusSpeed: 500000
                     SJW: []
                   TSEG1: []
                   TSEG2: []
            NumOfSamples: []

   Other Information
                Database: [1×1 can.Database]
                UserData: []

setupCANChannel 使用以下 Vehicle Network Toolbox 函数来执行相关任务:

  • canChannel,它使用 eval 命令和存储在 App 的 UI 属性 app.canChannelConstructorSelected 中的 CAN 通道构造函数字符串实例化通道对象。生成的通道对象存储在 App 的 UI 属性 app.canChannelObj 中。

  • canDatabase,它创建一个表示 DBC 文件的 CAN 数据库 (.dbc) 对象。此对象存储在通道对象的 Database 属性中。

设置为传输 CAN 报文

在建立选定的 CAN 通道对象并将其存储在 UI 属性 app.canChannelObj 中之后,下一步是调用如下代码片段中所示的辅助函数 setupCANTransmitMessagessetupCANTransmitMessages 定义要从 UI 传输的 CAN 报文,用信号填充报文有效负载,为每个信号赋值,对报文排队,以在 CAN 通道启动后周期性地传输报文。

function setupCANTransmitMessages(app)
    % Create a CAN message container.
    app.cruiseControlCmdMessage = canMessage(app.canChannelObj.Database, 'CruiseCtrlCmd');
    
    % Fill the message container with signals and assign values to each signal.
    app.cruiseControlCmdMessage.Signals.S01_CruiseOnOff = logical2Numeric(app, app.cruisePowerCheckBox.Value);
    app.cruiseControlCmdMessage.Signals.S02_Brake =  logical2Numeric(app, app.brakeOnOffCheckBox.Value);
    app.cruiseControlCmdMessage.Signals.S03_VehicleSpeed =  app.vehicleSpeedSlider.Value;
    app.cruiseControlCmdMessage.Signals.S04_CoastSetSw =  logical2Numeric(app, app.cruiseCoastSetCheckBox.Value);
    app.cruiseControlCmdMessage.Signals.S05_AccelResSw =  logical2Numeric(app, app.cruiseAccelResumeCheckBox.Value);
    
    % Set up periodic transmission of this CAN message.  Actual transmission starts/stops with CAN channel start/stop.
    transmitPeriodic(app.canChannelObj, app.cruiseControlCmdMessage, 'On', 0.1);
end

要查看 CAN 报文对象的外观,请执行以下操作:

cruiseControlCmdMessage = canMessage(canChannelObj.Database, 'CruiseCtrlCmd')
cruiseControlCmdMessage = 
  Message with properties:

   Message Identification
    ProtocolMode: 'CAN'
              ID: 256
        Extended: 0
            Name: 'CruiseCtrlCmd'

   Data Details
       Timestamp: 0
            Data: [0 0]
         Signals: [1×1 struct]
          Length: 2

   Protocol Flags
           Error: 0
          Remote: 0

   Other Information
        Database: [1×1 can.Database]
        UserData: []

cruiseControlCmdMessage.Signals
ans = struct with fields:
    S03_VehicleSpeed: 0
      S05_AccelResSw: 0
      S04_CoastSetSw: 0
           S02_Brake: 0
     S01_CruiseOnOff: 0

setupCANTransmitMessages 使用以下 Vehicle Network Toolbox 函数来执行相关任务:

  • canMessage,用于构建在 CAN 数据库对象中定义的 CAN 报文。

  • transmitPeriodic,用于对 UI 属性 app.cruiseControlCmdMessage 中存储的报文排队,以便在 UI 属性 app.canChannelObj 中存储的通道对象定义的通道上周期性传输,传输速率由最后一个参量指定,在本例中为每 0.1 秒一次。

设置以接收 CAN 报文

该 UI 需要周期性接收 CAN 报文,以利用 Simulink 模型中巡航控制算法的反馈更新绘图。为此,首先创建一个 MATLAB 计时器对象,如以下代码片段所示。

% create a timer to receive CAN msgs
app.receiveCANmsgsTimer = timer('Period', 0.5,...
    'ExecutionMode', 'fixedSpacing', ...
    'TimerFcn', @(~,~)receiveCANmsgsTimerCallback(app));

该计时器对象将每 0.5 秒调用一次计时器回调函数 receiveCANmsgsTimerCallback。下面的代码片段中所示的 receiveCANmsgsTimerCallback 从总线检索所有 CAN 报文,使用辅助函数 getCruiseCtrlFBCANmessage 提取从巡航控制算法模型反馈的 CAN 报文,并用提取的 CAN 报文数据更新 UI 绘图。

% receiveCANmsgsTimerCallback Timer callback function for GUI updating
function receiveCANmsgsTimerCallback(app)
    try
        % Receive available CAN messages.
        msg = receive(app.canChannelObj, Inf, 'OutputFormat', 'timetable');
        
        % Update Cruise Control Feedback CAN message data.
        newFbData = getCruiseCtrlFBCANmessage(app, msg);
        
        if ~newFbData
            return;
        end
        
        % Update target speed and engaged plots with latest data from CAN bus.
        updatePlots(app);
    catch err
        disp(err.message)
    end
end

要查看从 receive 命令返回的报文,请运行以下代码:

% Queue periodic transmission of a CAN message to generate some message data once the channel
% starts.
transmitPeriodic(canChannelObj, cruiseControlCmdMessage, 'On', 0.1);

% Start the channel.
start(canChannelObj);

% Wait 1 second to allow time for some messages to be generated on the bus.
pause(1);

% Retrieve all messages from the bus and output the results as a timetable.
msg = receive(canChannelObj, Inf, 'OutputFormat','timetable')
msg=10×8 timetable
        Time        ID     Extended          Name            Data      Length      Signals       Error    Remote
    ____________    ___    ________    _________________    _______    ______    ____________    _____    ______

    0.016169 sec    256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.12518 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.23315 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.34315 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.45114 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.56016 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.6692 sec      256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.77918 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.88211 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.98119 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 

% Stop the channel.
stop(canChannelObj)

receiveCANmsgsTimerCallback 使用以下 Vehicle Network Toolbox 函数来执行相关任务:

  • receive,用于从 CAN 总线检索 CAN 报文。在本例中,该函数配置为检索自上次调用以来的所有报文,并将结果作为 MATLAB 时间表输出。

提取所选 CAN 报文

辅助函数 getCruiseCtrlFBCANmessage 从所有检索的 CAN 报文中过滤出“CruiseCtrlFB”报文,从这些报文中提取 tspeedFbengagedFb 信号,并将这些信号串联到 tspeedFbengagedFb 信号的 MATLAB 时间序列对象。这些时间序列对象分别存储在 UI 属性 app.tspeedFbapp.engagedFb 中。存储的时间序列信号用于更新 UI 上每个信号的绘图。请注意,这里我们使用了 seconds 方法对时间表中存储的时间数据进行转换,从持续时间数组转换为每个信号的时间序列对象中以秒为单位的等效数值数组。

function newFbData = getCruiseCtrlFBCANmessage(app, msg)
    % Exit if no messages were received as there is nothing to update.
    if isempty(msg)
        newFbData = false;
        return;
    end
    
    % Extract signals from all CruiseCtrlFB messages.
    cruiseCtrlFBSignals = canSignalTimetable(msg, "CruiseCtrlFB");
    
    % if no messages then just return as there is nothing to do
    if isempty(cruiseCtrlFBSignals)
        newFbData = false;
        return;
    end
    
    if ~isempty(cruiseCtrlFBSignals)
        % Received new Cruise Control Feedback messages, so create time series from CAN signal data
        % save the Target Speed feedback signal.
        if isempty(app.tspeedFb) % cCeck if target speed feedback property has been initialized.
            app.tspeedFb = cell(2,1);
            
            % It appears Simulink.SimulationData.Dataset class is not
            % compatible with MATLAB Compiler, so change the way we store data
            % from a Dataset format to cell array.
            
            % Save target speed actual data.
            app.tspeedFb = timeseries(cruiseCtrlFBSignals.F02_TargetSpeed, seconds(cruiseCtrlFBSignals.Time),...
                'Name','CruiseControlTargetSpeed');
        else  % Add to existing data.
            % Save target speed actual data.
            app.tspeedFb = timeseries([app.tspeedFb.Data; cruiseCtrlFBSignals.F02_TargetSpeed], ...
                [app.tspeedFb.Time; seconds(cruiseCtrlFBSignals.Time)],'Name','CruiseControlTargetSpeed');
        end
        
        % Save the Cruise Control Engaged actual signal.
        % Check if Cruise engaged property has been initialized.
        if isempty(app.engagedFb)    
            app.engagedFb = cell(2,1);

            % It appears Simulink.SimulationData.Dataset class is not
            % compatible with MATLAB Compiler, so change the way we store data
            % from a Dataset format to cell array.
            
            % Save cruise engaged command data.
            app.engagedFb = timeseries(cruiseCtrlFBSignals.F01_Engaged,seconds(cruiseCtrlFBSignals.Time),...
                'Name','CruiseControlEngaged');
        else  % Add to existing logsout.
            % Save cruise engaged command data.
            app.engagedFb = timeseries([app.engagedFb.Data; cruiseCtrlFBSignals.F01_Engaged], ...
                [app.engagedFb.Time; seconds(cruiseCtrlFBSignals.Time)],'Name','CruiseControlEngaged');
        end
        
        newFbData = true;
    end
end

辅助函数 getCruiseCtrlFBCANmessage 使用以下 Vehicle Network Toolbox 函数来执行相关任务:

  • canSignalTimetable,用于返回包含来自 CAN 报文 CruiseCtrlFB 的信号的 MATLAB 时间表。

启动 CAN 通道

当实例化了 CAN 通道对象 app.canChannelObj 并且设置好了报文的传输和接收后,我们现在就可以启动通道。当用户点击 UI 上的开始仿真时,我们希望通道在我们即将开始运行 Simulink 模型之前启动。为此,需要调用辅助函数 startSimApplication。以下代码片段所示的 startSimApplication 会进行检查以确保我们使用的是虚拟 CAN 通道,因为在您只使用桌面仿真时这是唯一有意义的类型。接下来,它使用 bdIsLoaded 命令进行检查以确保我们要连接的 Simulink 模型已加载到内存中。如果模型已加载且尚未运行,系统会清除 UI 绘图以接受新信号数据,并使用辅助函数 startCANChannel 启动 CAN 通道,然后启动模型。

function startSimApplication(app, index)
    % Start the model running on the desktop.
    
    % Check to see if hardware or virtual CAN channel is selected, otherwise do nothing.
    if app.canChannelInfo.DeviceModel(index) == "Virtual"
        % Check to see if the model is loaded before trying to run.
        if bdIsLoaded(app.mdl)
            % Model is loaded, now check to see if it is already running.
            if ~strcmp('running',get_param(app.mdl,'SimulationStatus'))
                % Model is not already running, so start it
                % flush the CAN Receive message buffers.
                app.tspeedFb = [];
                app.engagedFb = [];
                
                % Clear figure window.
                cla(app.tspeedPlot)
                cla(app.engagedPlot)
                
                % Start the CAN channels and update timer if it isn't already running.
                startCANChannel(app);
                
                % Start the model.
                set_param(app.mdl, 'SimulationCommand', 'start');
                
                % Set the sim start/stop button icon to the stop icon indicating the model has
                % been successfully started and is ready to be stopped at the next button press.
                app.SimStartStopButton.Icon = "IconEnd.png";
                app.StartSimLabel.Text = "Stop Sim";
            else
                % Model is already running, inform the user.
                warnStr = sprintf('Warning: Model %s is already running', app.mdl);
                warndlg(warnStr, 'Warning');
            end
        else
            % Model is not yet loaded, so warn the user.
            warnStr = sprintf('Warning: Model %s is not loaded\nPlease load the model and try again', app.mdl);
            warndlg(warnStr, 'Warning');
        end
    end
end

辅助函数 startCANChannel 如以下代码片段所示。此函数会进行检查以确保通道在启动前尚未运行。接下来,它启动 MATLAB 计时器对象,使上一节中所述的计时器回调函数 receiveCANmsgsTimerCallback 每 0.5 秒调用一次,以从总线检索 CAN 报文数据。

function startCANChannel(app)
    % Start the CAN channel if it isn't already running.
    try
        if ~app.canChannelObj.Running
            start(app.canChannelObj);
        end
    catch
        % do nothing.
    end
    
    % Start the CAN receive processing timer - check to see if it is already running. This allows us to change CAN channels
    % with or without starting and stopping the model running on the real time target.
    if strcmpi(app.receiveCANmsgsTimer.Running, 'off')
        start(app.receiveCANmsgsTimer);
    end
end

startCANchannel 使用以下 Vehicle Network Toolbox 函数来执行相关任务:

  • start,用于启动 CAN 通道运行。通道将保持在线状态,直到发出 stop 命令。

停止 CAN 通道

当用户点击 UI 上的停止仿真时,我们希望在即将停止 CAN 通道之前停止 Simulink 模型。为此,需要调用辅助函数 stopSimApplication。以下代码片段所示的 stopSimApplication 会进行检查以确保我们使用的是虚拟 CAN 通道,因为在您只使用桌面仿真时这是唯一有意义的类型。接下来,它会停止 Simulink 模型,并调用辅助函数 stopCANChannel 来停止 CAN 通道。

function stopSimApplication(app, index)
    % Stop the model running on the desktop.
    try
        % Check to see if hardware or virtual CAN channel is selected.
        if app.canChannelInfo.DeviceModel(index) == "Virtual"
            % Virtual channel selected, so issue a stop command to the
            % the simulation, even if it is already stopped.
            set_param(app.mdl, 'SimulationCommand', 'stop')
            
            % Stop the CAN channels and update timer.
            stopCANChannel(app);
        end
        
        % Set the sim start/stop button text to Start indicating the model has
        % been successfully stopped and is ready to start again at the next
        % button press.
        app.SimStartStopButton.Icon = "IconPlay.png";
        app.StartSimLabel.Text = "Start Sim";
    catch
        % Do nothing at the moment.
    end
end

辅助函数 stopCANChannel 如以下代码片段所示。该函数会停止 CAN 通道,然后停止 MATLAB 计时器对象,以便不再调用上述的计时器回调函数 receiveCANmsgsTimerCallback 来从总线检索报文。

function stopCANChannel(app)
    % Stop the CAN channel.
    stop(app.canChannelObj);
    
    % Stop the CAN message processing timer.
    stop(app.receiveCANmsgsTimer);
end

stopCANchannel 使用以下 Vehicle Network Toolbox 函数来执行相关任务:

  • stop,用于停止 CAN 通道。通道将保持离线状态,直到发出另一个 start 命令。

添加 CAN 日志和回放功能

在此步骤中,我们将说明用于记录、保存和回放 CAN 报文的关键 Vehicle Network Toolbox 函数。为了实现此功能,我们会实例化第二个 CAN 通道,它与创建的第一个通道相同。由于第二个 CAN 通道对象与第一个相同,因此它将看到和收集的报文与第一个 CAN 通道对象相同。当用户点击 UI 上的开始记录时,第二个 CAN 通道将会启动。该通道继续运行并收集报文,直到用户点击停止记录。一旦用户停止记录 CAN 报文,我们将检索第二个 CAN 通道对象的报文缓冲区中累积的所有报文,提取我们需要回放的报文,并将它们保存到 MAT 文件中。一旦保存了报文,我们就可以使用第一个 CAN 通道回放它们,以便为 Simulink 巡航控制算法模型提供输入激励,用于调试和算法验证目的。

此描述涵盖以下主题:

  • 设置记录 CAN 报文的通道

  • 启动和停止通道

  • 检索和提取记录的 CAN 报文

  • 将提取的报文保存到文件

  • 从文件加载保存的报文

  • 开始播放记录的 CAN 报文

  • 停止播放记录的 CAN 报文

在 UI 中设置 CAN 通道对象以记录 CAN 报文

新辅助函数 setupCANLogChannel 使用 app.canChannelConstructorSelected 中存储的格式化 CAN 通道构造函数字符串来创建 CAN 通道对象的第二个实例(其方式与实例化第一个 CAN 通道几乎完全一样),连接用于第一个通道的网络配置数据库 (.dbc) 文件,并设置总线速度,如以下代码片段所示。生成的通道对象存储在 UI 属性 app.canLogChannelObj 中。

function setupCANLogChannel(app)
    % Open CAN database file.
    db = canDatabase('CruiseControl.dbc');
    
    % Create a CAN channel for sending and receiving messages.
    app.canLogChannelObj = eval(app.canChannelConstructorSelected);
    
    % Attach CAN database to channel for received message decoding.
    app.canLogChannelObj.Database = db;
    
    % Set the baud rate (can only do this if the UI has channel initialization access).
    if app.canLogChannelObj.InitializationAccess
        configBusSpeed(app.canLogChannelObj, 500000);
    end
end

要查看示例 CAN 数据库对象,请执行以下代码:

db = canDatabase('CruiseControl.dbc')
db = 
  Database with properties:

             Name: 'CruiseControl'
             Path: 'C:\Users\jvijayak\OneDrive - MathWorks\Documents\MATLAB\ExampleManager\jvijayak.Bdoc23b.j2277816\vnt-ex00964061\CruiseControl.dbc'
        UTF8_File: 'C:\Users\jvijayak\OneDrive - MathWorks\Documents\MATLAB\ExampleManager\jvijayak.Bdoc23b.j2277816\vnt-ex00964061\CruiseControl.dbc'
            Nodes: {2×1 cell}
         NodeInfo: [2×1 struct]
         Messages: {2×1 cell}
      MessageInfo: [2×1 struct]
       Attributes: {'BusType'}
    AttributeInfo: [1×1 struct]
         UserData: []

要查看示例 CAN 通道对象,请执行以下代码:

% Instantiate the CAN channel object using the channel constructor string.
canLogChannelObj = eval(canChannelConstructor);

% Attach the CAN database to the channel object.
canLogChannelObj.Database = db
canLogChannelObj = 
  Channel with properties:

   Device Information
            DeviceVendor: 'MathWorks'
                  Device: 'Virtual 1'
      DeviceChannelIndex: 1
      DeviceSerialNumber: 0
            ProtocolMode: 'CAN'

   Status Information
                 Running: 0
       MessagesAvailable: 0
        MessagesReceived: 0
     MessagesTransmitted: 0
    InitializationAccess: 0
        InitialTimestamp: [0×0 datetime]
           FilterHistory: 'Standard ID Filter: Allow All | Extended ID Filter: Allow All'

   Channel Information
               BusStatus: 'N/A'
              SilentMode: 0
         TransceiverName: 'N/A'
        TransceiverState: 'N/A'
       ReceiveErrorCount: 0
      TransmitErrorCount: 0
                BusSpeed: 500000
                     SJW: []
                   TSEG1: []
                   TSEG2: []
            NumOfSamples: []

   Other Information
                Database: [1×1 can.Database]
                UserData: []

setupCANLogChannel 使用以下 Vehicle Network Toolbox 函数来执行相关任务:

  • canChannel,它使用 eval 命令和存储在 App 的 UI 属性 app.canChannelConstructorSelected 中的 CAN 通道构造函数字符串实例化通道对象。生成的通道对象存储在 App 的 UI 属性 app.canLogChannelObj 中。

  • canDatabase,它创建一个表示 DBC 文件的 CAN 数据库 (.dbc) 对象。此对象存储在通道对象的 Database 属性中。

启动 CAN Log 通道

与第一个 CAN 通道一样,当打开测试应用程序 UI 时,CAN 通道对象 app.canLogChannelObj 已实例化。当用户点击开始记录时,我们调用辅助函数 startCANLogChannel,如以下代码片段所示。此函数检查第二个 CAN 通道是否已在运行,如果没有运行,则启动它。

function startCANLogChannel(app)
    % Start the CAN Log channel if it isn't already running.
    try
        if ~app.canLogChannelObj.Running
            start(app.canLogChannelObj);
        end
    catch
        % Do nothing.
    end
end

startCANLogChannel 使用以下 Vehicle Network Toolbox 函数来执行相关任务:

  • start,用于启动 CAN 通道运行。通道将保持在线状态,直到发出 stop 命令。

停止 CAN Log 通道

当用户点击 UI 上的停止记录时,按钮回调会调用辅助函数 stopCANLogging,如以下代码片段所示。stopCANLogging 停止 CAN 通道,并检索自用户点击开始记录启动第二个 CAN 通道以来第二个通道缓冲区中累积的所有报文。

function stopCANLogging(app)
    % Stop the CAN Log channel.
    stop(app.canLogChannelObj);
    
    % Get the messages from the CAN log message queue.
    retrieveLoggedCANMessages(app);
    
    % Update the button icon and label.
    app.canLoggingStartStopButton.Icon = 'IconPlay.png';
    app.StartLoggingLabel.Text = "Start Logging";
end

stopCANLogging 使用以下 Vehicle Network Toolbox 函数来执行相关任务:

  • stop,用于停止 CAN 通道。通道将保持离线状态,直到发出另一个 start 命令。

检索和提取记录的报文

一旦停止记录 CAN 通道,将调用下面代码片段中所示的辅助函数 retrieveLoggedCANMessages 从第二个通道总线检索所有 CAN 报文。它使用 receive 命令从第二个通道总线获取 CAN 报文,并使用逻辑索引从 receive 命令返回的所有报文时间表中提取“CruiseCtrlCmd”报文。

function retrieveLoggedCANMessages(app)
    try
        % Receive available CAN message
        % initialize buffer to make sure it is empty.
        app.canLogMsgBuffer = [];
        
        % Receive available CAN messages.
        msg = receive(app.canLogChannelObj, Inf, 'OutputFormat', 'timetable');
        
        % Fill the buffer with the logged Cruise Control Command CAN message data.
        app.canLogMsgBuffer = msg(msg.Name == "CruiseCtrlCmd", :);
    catch err
        disp(err.message)
    end
end

要以时间表格式查看从 receive 命令返回的报文,请运行以下代码:

% Queue periodic transmission of CAN messages to generate sample message data once the channel starts.
cruiseControlFbMessage = canMessage(db, 'CruiseCtrlFB');
transmitPeriodic(canChannelObj, cruiseControlFbMessage, 'On', 0.1);
transmitPeriodic(canChannelObj, cruiseControlCmdMessage, 'On', 0.1);

% Start the first channel.
start(canChannelObj);

% Start the second (logging) channel.
start(canLogChannelObj);

% Wait 1 second to allow time for some messages to be generated on the bus.
pause(1);

% Stop the channels.
stop(canChannelObj)
stop(canLogChannelObj)

% Retrieve all messages from the logged message bus and output the results as a timetable.
msg = receive(canLogChannelObj, Inf, 'OutputFormat','timetable')
msg=52×8 timetable
        Time        ID     Extended          Name            Data      Length      Signals       Error    Remote
    ____________    ___    ________    _________________    _______    ______    ____________    _____    ______

    0.073091 sec    256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.073094 sec    512     false      {'CruiseCtrlFB' }    {[0 0]}      2       {1×1 struct}    false    false 
    0.18515 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.18515 sec     512     false      {'CruiseCtrlFB' }    {[0 0]}      2       {1×1 struct}    false    false 
    0.29512 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.29513 sec     512     false      {'CruiseCtrlFB' }    {[0 0]}      2       {1×1 struct}    false    false 
    0.40412 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.40413 sec     512     false      {'CruiseCtrlFB' }    {[0 0]}      2       {1×1 struct}    false    false 
    0.51208 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.51208 sec     512     false      {'CruiseCtrlFB' }    {[0 0]}      2       {1×1 struct}    false    false 
    0.62109 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.62109 sec     512     false      {'CruiseCtrlFB' }    {[0 0]}      2       {1×1 struct}    false    false 
    0.73113 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.73113 sec     512     false      {'CruiseCtrlFB' }    {[0 0]}      2       {1×1 struct}    false    false 
    0.83307 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.83307 sec     512     false      {'CruiseCtrlFB' }    {[0 0]}      2       {1×1 struct}    false    false 
      ⋮

% Extract only the Cruise Control Command CAN messages.
msgCmd = msg(msg.Name == "CruiseCtrlCmd", :)
msgCmd=26×8 timetable
        Time        ID     Extended          Name            Data      Length      Signals       Error    Remote
    ____________    ___    ________    _________________    _______    ______    ____________    _____    ______

    0.073091 sec    256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.18515 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.29512 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.40412 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.51208 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.62109 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.73113 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.83307 sec     256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    0.9331 sec      256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    1.0431 sec      256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    1.1531 sec      256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    1.2631 sec      256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    1.3703 sec      256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    1.4776 sec      256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    1.5872 sec      256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
    1.6972 sec      256     false      {'CruiseCtrlCmd'}    {[0 0]}      2       {1×1 struct}    false    false 
      ⋮

retrieveLoggedCANMessages 使用以下 Vehicle Network Toolbox 函数来执行相关任务:

  • receive,用于从 CAN 总线检索 CAN 报文。在本例中,该函数配置为检索自上次调用以来的所有报文,并将结果作为 MATLAB 时间表输出。

将报文保存到文件

当用户点击 UI 上的保存记录的数据时,将调用辅助函数 saveLoggedCANDataToFile。此函数使用 uinputfile 函数打开文件浏览器窗口。uinputfile 返回用户选择的用来存储记录的 CAN 报文数据的文件名和路径。Vehicle Network Toolbox 函数 canMessageReplayBlockStruct 用于将来自 MATLAB 时间表的 CAN 报文转换为 CAN Replay 模块可以使用的格式。一旦记录的 CAN 报文数据被转换并保存到文件中,即可在以后使用 Vehicle Network Toolbox 的“Replay”Simulink 模块来重新调用和回放这些报文数据。要使用 replay 命令通过 Vehicle Network Toolbox 在 MATLAB 中回放报文,时间表本身需要作为函数的输入来传递。

function savedLoggedCANDataToFile(app)
    % Raise dialog box to prompt user for a CAN log file to store the logged data.
    [FileName,PathName] = uiputfile('*.mat','Select a .MAT file to store logged CAN data');

    if FileName ~= 0
        % User did not cancel the file selection operation, so OK to save
        % convert the CAN log data from Timetable to struct of arrays so the data is compatible
        % with the VNT Simulink Replay block.
        canLogStructOfArrays = canMessageReplayBlockStruct(app.canLogMsgBuffer);
        save(fullfile(PathName, FileName), 'canLogStructOfArrays');

        % Clear the buffer after saving it.
        app.canLogMsgBuffer = [];
    end
end

saveLoggedCANDataToFile 使用以下 Vehicle Network Toolbox 函数来执行相关任务:

  • canMessageReplayBlockStruct,用于将存储在 MATLAB 时间表中的 CAN 报文转换为可由 CAN Message Replay 模块使用的格式。

从文件加载报文

当用户点击 UI 上的保存记录的数据时,将调用辅助函数 loadLoggedCANDataFromFile。此函数使用 uigetfile 函数打开文件浏览器窗口,后一个函数返回用户选择的记录报文的文件名。记录的 CAN 报文数据从文件中加载,并转换回时间表表示以用于 Vehicle Network Toolbox 的 replay 命令。请注意,如果用户希望回放来自 Simulink 模型的数据,这一数据文件可以直接用于 Vehicle Network Toolbox 的“Replay”Simulink 模块。您可以选择使用 Replay 模块回放数据,而不是从 UI 使用 replay 命令来回放,因为 Replay 模块支持 Simulink 调试器,当仿真在断点处停止时会暂停回放。相比之下,replay 命令不能识别 Simulink 模型何时在断点处停止,只会继续播放文件中存储的报文数据。为了完成本示例的目的,我们将使用 replay 命令回放数据。

function loadLoggedCANDataFromFile(app)
    % Raise dialog box to prompt user for a CAN log file to load.
    [FileName,PathName] = uigetfile('*.mat','Select a CAN log file to load');
    
    % Return focus to main UI after dlg closes.
    figure(app.UIFigure)
    
    if FileName ~= 0
        % User did not cancel the file selection operation, so OK to load
        % make sure the message buffer is empty before loading in the logged CAN data.
        app.canLogMsgBuffer = [];
        
        % Upload the saved message data from the selected file.
        canLogMsgStructOfArrays = load(fullfile(PathName, FileName), 'canLogStructOfArrays');
        
        % Convert the saved message data into timetables for the replay command.
        app.canLogMsgBuffer = canMessageTimetable(canLogMsgStructOfArrays.canLogStructOfArrays);
    end
end

loadLoggedCANDataFromFile 使用以下 Vehicle Network Toolbox 函数来执行相关任务:

  • canMessageTimetable,它将以与 CAN 报文 Replay Simulink 模块兼容的格式存储的 CAN 报文转换回 MATLAB 时间表以用于 replay 函数。

开始播放记录的报文

将记录的 CAN 报文数据加载到 UI 并重新格式化后,即可使用 Vehicle Network Toolbox 的 replay 命令进行播放。当用户点击 UI 上的开始回放时,将调用辅助函数 startPlaybackOfLoggedCANData。为了在第一个 CAN 通道上回放记录的 CAN 报文数据,必须停止与该通道相关联的所有活动,并清除缓冲的所有报文数据。如下文的代码片段所示,startPlaybackOfLoggedCANData 关闭周期性传输 CAN 报文,停止 CAN 通道,清除 UI 中缓冲的任何 CAN 报文数据,并清除绘图中从巡航控制算法模型反馈的信号数据显示。然后重新启动 CAN 通道,并回放记录的 CAN 报文数据。

function startPlaybackOfLoggedCANData(app)
    % Turn off periodic transmission of CruiseCtrlCmd CAN message from UI controls.
    transmitPeriodic(app.canChannelObj, app.cruiseControlCmdMessage, 'Off');
    
    % Stop the UI CAN channel so we can instead use if for playback.
    stopCANChannel(app)
    
    % Flush the existing CAN messages stored for plotting.
    flushCANFbMsgQueue(app)
    
    % Clear the existing plots.
    cla(app.tspeedPlot)
    cla(app.engagedPlot)
    
    % Start the CAN Channel and replay the logged CAN message data.
    startCANChannel(app)
    
    % Replay the logged CAN data on the UI CAN Channel.
    replay(app.canChannelObj, app.canLogMsgBuffer);
end

startPlaybackOfLoggedCANData 使用以下 Vehicle Network Toolbox 函数来执行相关任务:

  • transmitPeriodic,用于禁用在“CruiseCtrlCmd”报文中发送到巡航控制算法模型的命令信号的周期性传输。

  • replay,用于在第一个 CAN 通道上回放记录的 CAN 报文数据。

停止播放记录的报文

当用户点击 UI 上的停止回放时,将调用辅助函数 stopPlaybackOfLoggedCANData。为了停止回放记录的 CAN 报文数据,必须停止正用于回放数据的 CAN 通道。一旦完成此操作,“CruiseCtrlCmd”报文的周期性传输即重新启用且通道重新启动,使得用户能够再次从 UI 以交互方式将测试激励信号注入巡航控制算法模型。如以下代码片段所示,stopPlaybackOfLoggedCANData 首先停止通道,这将停止回放记录的报文数据。记录的报文数据将从 UI 上的本地缓冲区中清除,同时清除显示巡航控制算法模型反馈的信号数据的绘图。“CruiseCtrlCmd”报文的周期性传输将重新启用,并且 CAN 通道重新启动。

function stopPlaybackOfLoggedCANData(app)
    % Stop the playback CAN channel.
    stopCANChannel(app)
    
    % Flush the existing CAN messages stored for plotting.
    flushCANFbMsgQueue(app)
    
    % Clear the existing plots.
    cla(app.tspeedPlot)
    cla(app.engagedPlot)
    
    % Re-enable periodic transmission of CruiseCtrlCmd CAN message from UI controls.
    transmitPeriodic(app.canChannelObj, app.cruiseControlCmdMessage, 'On', 0.1);
    
    % Restart the CAN Channel from/To UI.
    startCANChannel(app)
end

stopPlaybackOfLoggedCANData 使用以下 Vehicle Network Toolbox 函数来执行相关任务:

  • stop,用于停止 CAN 通道以停止回放记录的 CAN 报文数据。

  • transmitPeriodic,用于重新启用在“CruiseCtrlCmd”报文中发送到巡航控制算法模型的命令信号的周期性传输。

  • start,用于重新启动 CAN 通道,以便用户可以再次从 UI 以交互方式向巡航控制算法模型注入测试激励信号。

向 Simulink 巡航控制算法模型中添加虚拟 CAN 通道通信

在此步骤中,我们将说明如何使用 Vehicle Network Toolbox Simulink 模块向 Simulink 巡航控制算法模型添加虚拟 CAN 通信功能。

此描述将涵盖以下主题:

  • 添加 CAN 报文接收功能

  • 添加 CAN 报文传输功能

  • 将 CAN 通道配置信息从 UI 推送到 Simulink 模型

打开巡航控制算法 Simulink 模型

运行辅助函数以使用所需的数据参数配置工作区,然后打开巡航控制算法测试框架模型。在 Simulink 模型处于打开状态时,您可以浏览下列各节中说明的模型部分。执行 helperPrepareTestBenchParameterData,然后执行 helperConfigureAndOpenTestBench

添加 CAN 报文接收功能

要使巡航控制算法 Simulink 模型从测试 UI 接收 CAN 数据,需要一个模块将 Simulink 模型连接到特定 CAN 设备,一个模块从所选设备接收 CAN 报文,以及一个模块将接收到的报文数据有效负载解包成单个信号。为完成此目的,一个“Inputs”子系统被添加到了巡航控制算法 Simulink 模型。该“Inputs”子系统使用 Vehicle Network Toolbox 的 CAN Configuration、CAN Receive 和 CAN Unpack 模块并将这些模块互连,如以下屏幕截图所示。

CAN Configuration 模块允许用户确定哪些可用的 CAN 设备和通道与 Simulink 模型连接。CAN Receive 模块从在 CAN Configuration 模块中选择的 CAN 设备和通道接收 CAN 报文。它还允许用户接收总线上的所有报文,或者应用过滤器以只接收选定的报文。CAN Unpack 模块已配置为读取用户定义的网络数据库 (.dbc) 文件。这允许用户确定报文名称、报文 ID 和数据有效负载,以便使用此模块解包信号。对于在网络数据库文件中的报文中定义的每个信号,系统会自动将相应的 Simulink 输入端口添加到模块中。

巡航控制算法 Simulink 模型的“Inputs”子系统使用以下 Vehicle Network Toolbox Simulink 模块来接收 CAN 报文:

  • CAN Configuration 模块,用于选择要连接到 Simulink 模型的 CAN 通道设备。

  • CAN Receive 模块,用于从在 CAN Configuration 模块中选择的 CAN 设备接收 CAN 报文。

  • CAN Unpack 模块,用于将接收到的 CAN 报文的有效负载解包为单个信号,报文中定义的每个数据项对应一个信号。

添加 CAN 报文传输功能

要使巡航控制算法 Simulink 模型向测试 UI 传输 CAN 数据,需要一个模块将 Simulink 模型连接到特定 CAN 设备,一个模块将 Simulink 信号打包到一个或多个 CAN 报文的数据有效负载,以及一个模块传输来自所选设备的 CAN 报文。为此,向巡航控制算法 Simulink 模型中添加了“Outputs”子系统。该“Outputs”子系统使用 Vehicle Network Toolbox 的 CAN Pack 和 CAN Transmit 模块并将这些模块互连,如下图所示。

CAN Pack 模块已配置为读取用户定义的网络数据库 (.dbc) 文件。这允许用户确定报文名称、报文 ID 和数据有效负载,以便使用此模块打包信号。对于在网络数据库文件中的报文中定义的每个信号,Simulink 输出端口会自动添加到模块中。CAN Transmit 模块将在用户使用 Configuration 模块选择的 CAN 通道和 CAN 设备上传输由 Pack 模块封装的报文。请注意,不需要第二个 CAN Configuration 模块,因为“Outputs”子系统使用相同的 CAN 通道和设备来传输和接收 CAN 报文。

巡航控制算法 Simulink 模型的“Outputs”子系统使用以下 Vehicle Network Toolbox Simulink 模块来传输 CAN 报文:

  • CAN Pack 模块,用于将接收到的 CAN 报文的有效负载解包为单个信号,报文中定义的每个数据项对应一个信号。

  • CAN Transmit 模块,用于从在 CAN Configuration 模块中选择的 CAN 设备接收 CAN 报文。

将 CAN 通道配置从 UI 推送到 Simulink 模型

因为用户从 UI 选择使用哪些可用的 CAN 设备和通道,此信息需要发送到巡航控制算法 Simulink 模型,以使 UI 和 Simulink 模型之间的 CAN 设备和通道配置保持同步。为此,CAN Configuration、CAN Transmit 和 CAN Receive 模块所使用的 CAN 设备和通道信息必须通过 UI 以编程方式进行配置。每次用户从 UI 的“通道配置/选择 CAN 通道”菜单中选择 CAN 设备和 CAN 通道时,都会调用辅助函数 updateModelWithSelectedCANChannel。如下文代码片段所示,updateModelWithSelectedCANChannel 在巡航控制算法 Simulink 模型中查找 CAN Configuration、CAN Transmit 和 CAN Receive 模块的模块路径。使用 set_param 命令,将这三个模块的 DeviceDeviceMenuObjConstructor 模块属性设置为用户选择的 CAN 设备和 CAN 通道的对应属性。

function updateModelWithSelectedCANChannel(app, index)
    % Check to see if we are using a virtual CAN channel and whether the model is loaded.
    if app.canChannelInfo.DeviceModel(index) == "Virtual" && bdIsLoaded(app.mdl)
        % Using a virtual channel.
        
        % Find path to CAN configuration block.
        canConfigPath = find_system(app.mdl,'Variants', 'AllVariants', 'LookUnderMasks', 'all',...
            'FollowLInks', 'on', 'Name', 'CAN Configuration');
        
        % Find path to CAN transmit block.
        canTransmitPath = find_system(app.mdl,'Variants', 'AllVariants', 'LookUnderMasks', 'all',...
            'FollowLInks', 'on', 'Name', 'CAN Transmit');
        
        % Find path to CAN receive block.
        canReceivePath = find_system(app.mdl,'Variants', 'AllVariants', 'LookUnderMasks', 'all',...
            'FollowLInks', 'on', 'Name', 'CAN Receive');
        
        % Push the selected CAN channel into the simulation model CAN Configuration block.
        set_param(canConfigPath{1}, 'Device', app.canChannelDeviceSelected);
        set_param(canConfigPath{1}, 'DeviceMenu', app.canChannelDeviceSelected);
        set_param(canConfigPath{1}, 'ObjConstructor', app.canChannelConstructorSelected);
        
        % Push the selected CAN channel into the simulation model CAN Receive block.
        set_param(canReceivePath{1}, 'Device', app.canChannelDeviceSelected);
        set_param(canReceivePath{1}, 'DeviceMenu', app.canChannelDeviceSelected);
        set_param(canReceivePath{1}, 'ObjConstructor', app.canChannelConstructorSelected);
        
        % Push the selected CAN channel into the simulation model CAN Transmit block.
        set_param(canTransmitPath{1}, 'Device', app.canChannelDeviceSelected);
        set_param(canTransmitPath{1}, 'DeviceMenu', app.canChannelDeviceSelected);
        set_param(canTransmitPath{1}, 'ObjConstructor', app.canChannelConstructorSelected);
    end
end

结合使用 UI 和模型

在模型和 UI 都处于打开状态时,您可以在 UI 中探查与模型的交互。点击开始仿真以将模型和 UI 置于在线状态。使用 UI 的“Driver Inputs”和“Calibrations”部分进行试验,以控制模型并执行巡航控制算法。您将在 UI 中看到巡航控制情况和绘制的速度值。您还可以通过 户点击 UI 上件使用上述的记录和回放功能。

以这种方式创建 UI,可以为您的应用程序提供强大而灵活的可自定义测试界面。在仿真中调试和优化算法是很有价值的。通过将选定的 CAN 设备从虚拟通道更改为物理通道,您可以继续使用 UI 与在快速原型平台或目标控制器中运行的算法进行交互。