주요 콘텐츠

이 페이지는 기계 번역을 사용하여 번역되었습니다. 영어 원문을 보려면 여기를 클릭하십시오.

CAN을 사용하여 Simulink 모델을 위한 애플리케이션 개발

이 예제는 앱 디자이너를 사용하여 테스트 애플리케이션 사용자 인터페이스(UI)를 구축하고 가상 CAN 채널을 통해 이를 Simulink® 모델에 연결하는 방법을 보여줍니다.

테스트 애플리케이션 UI는 MATLAB® 앱 디자이너와 여러 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에서 채널 생성하기

  • CAN 메시지를 송신 및 수신하도록 UI 구성하기

  • 채널 시작 및 중지

  • 선택된 메시지 추출하기

앱 디자이너 열기

앱 디자이너에서 테스트 애플리케이션 UI를 엽니다. 앱 디자이너에서 테스트 애플리케이션 UI를 열어둔 상태에서 "디자인"과 "코드" 뷰를 전환하며, 가상 CAN 채널을 통해 Simulink 크루즈 컨트롤 알고리즘 모델과 통신하는 컨트롤 및 해당 MATLAB 코드를 탐색해 볼 수 있습니다.

필요한 데이터를 미리 로드하기 위해 준비 스크립트를 실행한 후, 테스트 앱을 개발하기 위해 앱 디자이너를 엽니다.

helperPrepareTestBenchParameterData;
appdesigner('CruiseControlTestUI.mlapp')

사용 가능한 CAN 채널 목록

먼저, 사용자가 선택할 수 있도록 사용 가능한 CAN 채널 목록을 찾고 표시하는 메커니즘을 구현합니다. 이를 위해 테스트 애플리케이션 UI의 왼쪽 상단 모서리에 "Channel Configuration" 메뉴 항목을 추가했습니다. "Select CAN Channel" 하위 메뉴가 있습니다.

사용자가 Select CAN Channel 하위 메뉴를 클릭하면, 하위 메뉴 콜백을 통해 헬퍼 함수 getAvailableCANChannelInfo(app)가 호출됩니다. getAvailableCANChannelInfo() 는 아래 코드 조각에서 보여지듯, 사용 가능한 CAN 채널을 감지하기 위해 Vehicle Network Toolbox 함수인 canChannelList를 사용합니다.

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 채널 생성자 문자열은 앱 UI 속성 app.canChannelConstructorSelected에 저장되며, 이후 애플리케이션 UI에서 선택된 CAN 채널 객체를 생성하고 Simulink 크루즈 컨트롤 알고리즘 모델에서 CAN 채널 인터페이스를 구현하는 Vehicle Network Toolbox Simulink 블록을 업데이트하는 데 사용됩니다.

UI에서 CAN 채널 생성하기

UI가 처음 열려 사용될 때, app.canChannelConstructorSelected에 저장되어 있는 서식 지정된 CAN 채널 생성자 문자열이 헬퍼 함수 setupCANChannel에 의해 사용되어 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 명령어와 앱 UI 속성 app.canChannelConstructorSelected에 저장된 CAN 채널 생성자 문자열을 사용하여 채널 객체를 인스턴스화합니다. 생성된 채널 객체는 앱 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" 메시지를 필터링하여 추출합니다. 이 메시지들로부터 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에서 Start Sim을 클릭하면, 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에서 Stop Sim을 클릭할 때, CAN 채널을 중지하기 직전에 Simulink 모델을 중지하고자 합니다. 이를 위해 헬퍼 함수 stopSimApplication가 호출됩니다. 아래 코드 조각에 표시된 stopSimApplication는 가상 CAN 채널을 사용하고 있는지 확인합니다. 데스크톱 시뮬레이션만 사용하는 경우 이 유형만이 의미가 있기 때문입니다. 다음으로, Simulink 모델을 중지하고 CAN 채널을 중지하기 위해 헬퍼 함수 stopCANChannel를 호출합니다.

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 채널을 중지한 후, 앞서 설명한 타이머 콜백 함수 receiveCANmsgsTimerCallback가 더 이상 버스에서 메시지를 가져오기 위해 호출되지 않도록 MATLAB 타이머 객체를 중지합니다.

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 파일에 저장합니다. 메시지가 저장되면, 디버깅 및 알고리즘 검증 목적으로 Simulink 크루즈 컨트롤 알고리즘 모델에 입력 자극을 제공하기 위해 첫 번째 CAN 채널을 사용하여 메시지를 재생할 수 있습니다.

이 설명은 다음 주제를 다룹니다.

  • CAN 메시지를 기록하기 위해 채널 설정하기

  • 채널 시작 및 중지

  • 기록된 CAN 메시지 검색 및 추출하기

  • 추출된 메시지를 파일에 저장하기

  • 파일에서 저장된 메시지 불러오기

  • 기록된 CAN 메시지 재생 시작하기

  • 기록된 CAN 메시지 재생 중지하기

UI에서 CAN 채널 객체를 설정하여 CAN 메시지를 기록하기

첫 번째 CAN 채널이 생성된 방식과 정확히 동일한 방식으로, app.canChannelConstructorSelected에 저장되어 있는 서식 지정된 CAN 채널 생성자 문자열이 새로운 헬퍼 함수 setupCANLogChannel에 의해 사용됩니다. 이 함수는 두 번째 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 명령어와 앱 UI 속성 app.canChannelConstructorSelected에 저장된 CAN 채널 생성자 문자열을 사용하여 채널 객체를 인스턴스화합니다. 생성된 채널 객체는 앱 UI 속성 app.canLogChannelObj에 저장됩니다.

  • canDatabase는 DBC 파일을 나타내는 CAN 데이터베이스(.dbc) 객체를 생성합니다. 이 객체는 채널의 Database 속성에 저장됩니다.

CAN 로그 채널 시작하기

첫 번째 CAN 채널과 마찬가지로, CAN 채널 채널 객체 app.canLogChannelObj는 테스트 애플리케이션 UI가 열릴 때 인스턴스화되었습니다. 사용자가 시작 기록을 클릭하면, 아래 코드 조각에 표시된 헬퍼 함수 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 로그 채널 중지하기

사용자가 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 채널을 중지합니다. 채널은 다른 시작 명령이 실행될 때까지 오프라인 상태로 유지됩니다.

기록된 메시지 검색 및 추출하기

로깅 CAN 채널이 중지되면, 아래 코드 조각에 표시된 헬퍼 함수 retrieveLoggedCANMessages가 호출되어 두 번째 채널 버스에서 모든 CAN 메시지를 가져옵니다. CAN 메시지는 receive 명령을 사용하여 두 번째 채널 버스에서 수집되며, 논리적 인덱스를 통해 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 "재생" Simulink 블록을 사용하여 나중에 다시 불러와 재생할 수 있습니다. MATLAB에서 메시지를 재생하기 위해 Vehicle Network Toolbox를 사용하며, replay 명령어를 통해 타임테이블 자체가 함수의 입력으로 전달됩니다.

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 메시지 재생 블록에서 사용할 수 있는 형태로 변환합니다.

파일에서 메시지 불러오기

사용자가 UI에서 로그된 데이터 불러오기를 클릭하면, 헬퍼 함수 loadLoggedCANDataFromFile가 호출됩니다. 이 함수는 uigetfile 함수를 사용하여 파일 탐색기 창을 엽니다. 이 함수는 사용자가 선택한 로그 메시지 파일 이름을 반환합니다. 로그된 CAN 메시지 메시지 데이터는 파일에서 불러와 Vehicle Network Toolbox replay 명령어 사용을 위해 타임테이블 형식으로 다시 변환됩니다. Simulink 모델의 데이터를 재생하고자 하는 경우 동일한 데이터 파일을 Vehicle Network Toolbox Replay Simulink 블록과 직접 함께 사용할 수 있습니다. UI에서 replay 명령어를 사용하는 대신 Replay 블록을 통해 데이터를 재생할 수도 있습니다. Replay 블록은 Simulink 디버거와 연동되어 시뮬레이션이 중단점(breakpoint)에서 멈출 때 재생을 일시 중지하기 때문입니다. 반면 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 메시지 재생 Simulink 블록과 호환되는 형식으로 저장된 CAN 메시지를 replay 함수 사용을 위한 MATLAB 타임테이블로 다시 변환합니다.

기록된 메시지 재생 시작하기

기록된 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 메시지 송신 기능 추가

  • UI에서 Simulink 모델로 CAN 채널 구성 정보 전송

크루즈 컨트롤 알고리즘 Simulink 모델 열기

필요한 데이터 파라미터로 작업 공간을 구성하기 위해 헬퍼 함수를 실행한 후, Cruise Control 알고리즘 테스트 하네스 모델을 엽니다. Simulink 모델을 열어두면 아래 섹션에서 설명하는 모델의 일부를 살펴볼 수 있습니다. helperPrepareTestBenchParameterData를 실행한 후 helperConfigureAndOpenTestBench를 실행하십시오.

CAN 메시지 수신 기능 추가하기

크루즈 컨트롤 알고리즘 Simulink 모델이 테스트 UI로부터 CAN 데이터를 수신하려면, Simulink 모델을 특정 CAN 장치에 연결하는 블록, 선택된 장치로부터 CAN 메시지를 수신하는 블록, 그리고 수신된 메시지의 데이터 페이로드를 개별 신호로 언패킹하는 블록이 필요합니다. 이를 달성하기 위해, 크루즈 컨트롤 알고리즘 Simulink 모델에 "입력" 서브시스템이 추가됩니다. "입력" 서브시스템은 아래 스크린샷에 표시된 대로 상호 연결된 Vehicle Network Toolbox CAN 구성, CAN 수신 및 CAN Unpack 블록을 사용합니다.

CAN Configuration 블록은 사용자가 사용 가능한 CAN 장치 및 채널 중 어느 것을 Simulink 모델과 연결할지 결정할 수 있도록 합니다. CAN Receive 블록은 CAN Configuration 블록에서 선택된 CAN 장치 및 채널로부터 CAN 메시지를 수신합니다. 또한 사용자는 버스에서 모든 메시지를 수신하거나 필터를 적용하여 선택한 메시지만 수신할 수 있습니다. CAN Unpack 블록은 사용자 정의 네트워크 데이터베이스(.dbc) 파일을 읽도록 구성되었습니다. 이를 통해 사용자는 이 블록으로 신호를 언패킹하기 위한 메시지 이름, 메시지 ID 및 데이터 페이로드를 결정할 수 있습니다. 네트워크 데이터베이스 파일의 메시지에 정의된 각 신호에 대해 Simulink 입력 포트가 블록에 자동으로 추가됩니다.

크루즈 컨트롤 알고리즘 Simulink 모델의 "Inputs" 서브시스템은 CAN 메시지를 수신하기 위해 다음과 같은 Vehicle Network Toolbox Simulink 블록을 사용합니다.

  • CAN Configuration 블록을 사용하여 Simulink 모델에 연결할 CAN 채널 장치를 선택합니다.

  • CAN Receive 블록은 CAN Configuration 블록에서 선택된 CAN 장치로부터 CAN 메시지를 수신합니다.

  • CAN Unpack 블록은 수신된 CAN 메시지의 페이로드를 개별 신호로 언패킹하여, 메시지에 정의된 각 데이터 항목마다 하나의 신호를 생성합니다.

CAN 메시지 송신 기능 추가하기

크루즈 컨트롤 알고리즘 Simulink 모델이 CAN 데이터를 테스트 UI로 전송하려면, Simulink 모델을 특정 CAN 장치에 연결하는 블록, 하나 이상의 CAN 메시지 데이터 페이로드에 Simulink 신호를 패킹하는 블록, 그리고 선택된 장치에서 CAN 메시지를 송신하는 블록이 필요합니다. 이를 달성하기 위해, Cruise Control 알고리즘 Simulink 모델에 "출력" 서브시스템이 추가됩니다. "출력" 서브시스템은 아래 스크린샷에 표시된 대로 상호 연결된 Vehicle Network Toolbox CAN 패킹 및 CAN Transmit 블록을 사용합니다.

CAN Pack 블록은 사용자 정의 네트워크 데이터베이스(.dbc) 파일을 읽도록 구성되었습니다. 이를 통해 사용자는 이 블록으로 신호를 패킹할 메시지 이름, 메시지 ID 및 데이터 페이로드를 결정할 수 있습니다. 네트워크 데이터베이스 파일의 메시지에 정의된 각 신호에 대해 Simulink 출력 포트가 블록에 자동으로 추가됩니다. CAN Transmit 블록은 Pack Block에서 조립된 메시지를 사용자가 Configuration 블록으로 선택한 CAN 채널 및 CAN 장치로 송신합니다. "출력" 서브시스템이 CAN 메시지를 수신하는 데 사용되는 동일한 CAN 채널 및 장치에서 CAN 메시지를 송신하므로 두 번째 CAN Configuration 블록은 필요하지 않습니다.

크루즈 컨트롤 알고리즘 Simulink 모델의 "출력" 서브시스템은 CAN 메시지를 송신하기 위해 다음 Vehicle Network Toolbox Simulink 블록을 사용합니다.

  • CAN Pack 블록은 수신된 CAN 메시지의 페이로드를 개별 신호로 언패킹하여, 메시지에 정의된 각 데이터 항목마다 하나의 신호를 생성합니다.

  • CAN Transmit 블록은 CAN Configuration 블록에서 선택된 CAN 장치로부터 CAN 메시지를 수신합니다.

UI에서 Simulink 모델로 CAN 채널 구성을 푸시하기

사용자가 UI에서 사용 가능한 CAN 장치 및 채널 중 사용할 항목을 선택하므로, 이 정보는 크루즈 컨트롤 알고리즘 Simulink 모델로 전송되어 UI와 Simulink 모델 간의 CAN 장치 및 채널 구성을 동기화해야 합니다. 이를 달성하기 위해 CAN 구성, CAN 송신 및 CAN Receive 블록에서 사용하는 CAN 장치 및 채널 정보는 UI에서 프로그래밍 방식으로 구성해야 합니다. 사용자가 UI의 "채널 구성/CAN 채널 선택" 메뉴에서 CAN 장치와 CAN 채널을 선택할 때마다 헬퍼 함수 updateModelWithSelectedCANChannel가 호출됩니다. 아래 코드 조각에서 볼 수 있듯이, updateModelWithSelectedCANChannel는 크루즈 컨트롤 알고리즘 Simulink 모델 내에서 CAN 구성, CAN 송신 및 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 내에서 모델과의 상호작용을 탐색할 수 있습니다. Start Sim을 클릭하여 모델과 UI를 온라인 상태로 전환하십시오. UI의 "드라이버 입력" 및 "캘리브레이션" 섹션을 실험하여 모델을 제어하고 크루즈 컨트롤 알고리즘을 작동시키십시오. 크루즈 컨트롤 작동 상태와 속도 값이 UI에 표시됩니다. 앞서 설명한 로깅 및 재생 기능도 UI 컨트롤을 통해 사용할 수 있습니다.

이러한 방식으로 UI를 생성하면 애플리케이션에 맞게 커스터마이징할 수 있는 강력하고 유연한 테스트 인터페이스를 얻을 수 있습니다. 시뮬레이션에서 알고리즘을 디버깅하고 최적화할 때 유용합니다. 선택된 CAN 장치를 가상 채널에서 물리 채널로 변경함으로써, UI를 계속 사용하여 신속 프로토타이핑 플랫폼 또는 대상 컨트롤러에서 실행 중인 알고리즘과 상호작용할 수 있습니다.