使用 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
中之后,下一步是调用如下代码片段中所示的辅助函数 setupCANTransmitMessages
。setupCANTransmitMessages
定义要从 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”报文,从这些报文中提取 tspeedFb 和 engagedFb 信号,并将这些信号串联到 tspeedFb 和 engagedFb 信号的 MATLAB 时间序列对象。这些时间序列对象分别存储在 UI 属性 app.tspeedFb 和 app.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 命令,将这三个模块的 Device
、DeviceMenu
和 ObjConstructor
模块属性设置为用户选择的 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 与在快速原型平台或目标控制器中运行的算法进行交互。