Main Content

Log CAN Messages to BLF Files Using Channel Callback Functions

This example shows how to log messages received on a CAN channel into BLF files using a callback function.

The example uses MathWorks® virtual CAN channels in a loopback configuration to simulate message logging without physical hardware. However, the same workflow also extends to BLF logging application from a real CAN or CAN FD network.

In this example, you will learn how to:

  • Configure CAN channels in MATLAB®.

  • Implement a callback function to log messages in batches.

  • Generate synthetic CAN traffic for testing purposes.

  • Save data to timestamped BLF files for offline analysis.

This workflow is useful for building automated logging systems, testing CAN applications, or simulating real-world traffic in a controlled environment.

Create the Transmitting and Receiving Channels

Use the canChannel function to create two MathWorks virtual CAN channels: one for transmitting and one for receiving. These channels are connected internally in a loopback configuration.

txCh = canChannel("MathWorks", "Virtual 1", 1)
txCh = 
  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: []
                UserData: []

rxCh = canChannel("MathWorks", "Virtual 1", 2)
rxCh = 
  Channel with properties:

   Device Information
            DeviceVendor: 'MathWorks'
                  Device: 'Virtual 1'
      DeviceChannelIndex: 2
      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: []
                UserData: []

Configure the Callback Function

To automatically process incoming messages, assign a callback function logToBLF to the receiving channel. This function will be triggered when a specified number of messages are available.

rxCh.MessageReceivedFcn = @logToBLF;

Set the Message Threshold for Callback Execution

Define how many messages should be received before the callback function is executed. In this example, the callback runs every time 200 messages are available. This batching approach helps manage memory and file I/O efficiently, especially in high-throughput scenarios.

rxCh.MessageReceivedFcnCount = 200;

Implement the Callback Function

The callback function logToBLF receives all available messages from the channel and writes them to a new BLF file. Each file is uniquely named using a timestamp to avoid overwriting.

type logToBLF.m
function logToBLF(rxCh)
% Receive available CAN messages on the input CAN channel and log them to 
% a new BLF file. This callback function is triggered whenever the number
% of messages available on the channel reaches the configured threshold.

%   Copyright 2025 The MathWorks, Inc.

    % Receive all available CAN messages in a timetable.
    rxMsg = receive(rxCh, Inf, OutputFormat="timetable");

    % Get the current date and time.
    currentDateTime = datetime("now");
    
    % Format the datetime as a string suitable for a filename.
    formattedDateTime = string(currentDateTime, "yyyyMMdd_HHmmss_SSS");

    % Use the formatted string as part of the BLF file name.
    filename = strcat("can_log_", formattedDateTime, ".blf");

    % Write the CAN messages to a new BLF file on channel 1.
    blfwrite(filename, rxMsg, 1, "CAN");
end

Start the Channels

Use the start command to set both channels online. Start the receiving channel before the transmitting one to avoid dropping messages.

start(rxCh);
start(txCh);

Generate CAN Traffic on Transmitting Channel

The function generateCANTrafficSweepID simulates a sweeping scan of CAN IDs. It creates CAN messages with IDs ranging from 1 to 1000, and transmit them at a fixed interval of 0.01 seconds using a timer object.

type generateCANTrafficSweepID.m
function generateCANTrafficSweepID(txCh)
% Generate and transmit CAN messages with IDs from 1 to 1000 using a timer 
% executed at fixed interval of 0.01 seconds. This function assumes the 
% transmitting CAN channel is already created and started.

%   Copyright 2025 The MathWorks, Inc.

    % Create a structure to hold the user data for timer, including the
    % transmitting channel and the message ID.
    userData.TxCh = txCh;
    userData.MsgID = 1;

    % Create the timer object.
    txTimer = timer(ExecutionMode="fixedRate", Period=0.01, TasksToExecute=1000, UserData=userData, TimerFcn=@transmitMsg);

    % Start and wait for the timer to finish.
    start(txTimer);
    wait(txTimer);

    % Clean up the timer after use.
    delete(txTimer);
end

function transmitMsg(txTimer, ~)
% Local function used as the timer callback to transmit the next message.

    % Get user data.
    userData = txTimer.UserData;

    % Create a CAN message with the current ID and 8 bytes of random data.
    msg = canMessage(userData.MsgID, false, 8);
    msg.Data = uint8(randi([0 255], 1, 8));

    % Transmit the CAN message.
    transmit(userData.TxCh, msg);

    % Increment the message ID and update the timer's user data.
    userData.MsgID = userData.MsgID + 1;
    txTimer.UserData = userData;
end

As the messages are transmitted, the callback function on the receiving channel executes each time the threshold specified by property MessageReceivedFcnCount is met.

generateCANTrafficSweepID(txCh);

Manually Log Remaining Messages

After transmission completes, there may still be messages in the receive buffer that did not meet the callback threshold. You can manually invoke the callback once to ensure any remaining messages are logged.

if rxCh.MessagesAvailable ~= 0
    rxCh.MessagesAvailable
    logToBLF(rxCh);
end
ans = 
199

Stop and Clean Up the Channels

Once logging is complete, stop the channels and clear them from the workspace.

stop(txCh);
stop(rxCh);
clear txCh rxCh

Inspect the Logged BLF Files

List the generated BLF files and inspect the messages in the earliest and latest files.

blfFiles = dir("*.blf")
blfFiles=5×1 struct array with fields:
    name
    folder
    date
    bytes
    isdir
    datenum

blfDataFirst = blfread(blfFiles(1).name);
blfDataFirst{1}
ans=201×8 timetable
        Time        ID    Extended       Name                     Data                   Length      Signals       Error    Remote
    ____________    __    ________    __________    _________________________________    ______    ____________    _____    ______

    0.075501 sec     1     false      {0×0 char}    {[ 208 231 32 233 161 24 71 140]}      8       {0×0 struct}    false    false 
    0.087792 sec     2     false      {0×0 char}    {[245 247 40 248 245 124 204 36]}      8       {0×0 struct}    false    false 
    0.091273 sec     3     false      {0×0 char}    {[107 234 202 245 167 9 217 239]}      8       {0×0 struct}    false    false 
    0.10135 sec      4     false      {0×0 char}    {[ 173 193 190 100 167 43 180 8]}      8       {0×0 struct}    false    false 
    0.11584 sec      5     false      {0×0 char}    {[    70 11 24 210 177 81 243 8]}      8       {0×0 struct}    false    false 
    0.11985 sec      6     false      {0×0 char}    {[112 97 195 203 47 125 114 165]}      8       {0×0 struct}    false    false 
    0.12936 sec      7     false      {0×0 char}    {[ 181 193 70 174 167 41 30 127]}      8       {0×0 struct}    false    false 
    0.13989 sec      8     false      {0×0 char}    {[ 245 87 149 57 192 65 129 178]}      8       {0×0 struct}    false    false 
    0.14935 sec      9     false      {0×0 char}    {[  228 245 140 35 38 65 215 65]}      8       {0×0 struct}    false    false 
    0.1592 sec      10     false      {0×0 char}    {[  208 62 237 89 50 64 157 121]}      8       {0×0 struct}    false    false 
    0.16973 sec     11     false      {0×0 char}    {[90 212 149 140 234 73 193 192]}      8       {0×0 struct}    false    false 
    0.17917 sec     12     false      {0×0 char}    {[  97 145 19 13 135 199 239 33]}      8       {0×0 struct}    false    false 
    0.18911 sec     13     false      {0×0 char}    {[   145 120 3 86 41 203 79 135]}      8       {0×0 struct}    false    false 
    0.19966 sec     14     false      {0×0 char}    {[ 42 154 67 167 176 191 115 21]}      8       {0×0 struct}    false    false 
    0.20907 sec     15     false      {0×0 char}    {[ 58 233 39 211 137 255 20 113]}      8       {0×0 struct}    false    false 
    0.21936 sec     16     false      {0×0 char}    {[  27 246 1 198 209 222 21 102]}      8       {0×0 struct}    false    false 
      ⋮

blfDataLast = blfread(blfFiles(end).name);
blfDataLast{1}
ans=199×8 timetable
       Time       ID     Extended       Name                      Data                   Length      Signals       Error    Remote
    __________    ___    ________    __________    __________________________________    ______    ____________    _____    ______

    8.0907 sec    802     false      {0×0 char}    {[      246 80 204 73 3 24 84 78]}      8       {0×0 struct}    false    false 
    8.1013 sec    803     false      {0×0 char}    {[  4 41 113 196 174 182 117 235]}      8       {0×0 struct}    false    false 
    8.1119 sec    804     false      {0×0 char}    {[253 238 118 231 98 154 143 216]}      8       {0×0 struct}    false    false 
    8.1203 sec    805     false      {0×0 char}    {[    72 169 154 168 79 84 48 25]}      8       {0×0 struct}    false    false 
    8.1303 sec    806     false      {0×0 char}    {[   73 90 137 253 7 181 231 221]}      8       {0×0 struct}    false    false 
    8.1401 sec    807     false      {0×0 char}    {[ 30 244 112 224 221 90 161 221]}      8       {0×0 struct}    false    false 
    8.1508 sec    808     false      {0×0 char}    {[       5 19 96 38 8 200 83 209]}      8       {0×0 struct}    false    false 
    8.1609 sec    809     false      {0×0 char}    {[    44 173 224 193 58 91 92 68]}      8       {0×0 struct}    false    false 
    8.1703 sec    810     false      {0×0 char}    {[     86 22 115 116 7 163 15 43]}      8       {0×0 struct}    false    false 
    8.1803 sec    811     false      {0×0 char}    {[     175 141 1 73 96 37 19 117]}      8       {0×0 struct}    false    false 
    8.1908 sec    812     false      {0×0 char}    {[     94 211 137 209 117 3 1 42]}      8       {0×0 struct}    false    false 
    8.2002 sec    813     false      {0×0 char}    {[     93 183 40 71 165 73 82 39]}      8       {0×0 struct}    false    false 
    8.2107 sec    814     false      {0×0 char}    {[ 99 229 227 100 172 64 243 159]}      8       {0×0 struct}    false    false 
    8.2202 sec    815     false      {0×0 char}    {[     52 28 144 70 18 40 12 174]}      8       {0×0 struct}    false    false 
    8.2303 sec    816     false      {0×0 char}    {[   200 206 67 229 155 3 88 136]}      8       {0×0 struct}    false    false 
    8.241 sec     817     false      {0×0 char}    {[ 160 114 207 37 249 213 87 157]}      8       {0×0 struct}    false    false 
      ⋮

This allows you to verify that messages were logged correctly and to examine the structure of the recorded data.