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 = 
198

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.063186 sec     1     false      {0×0 char}    {[ 208 231 32 233 161 24 71 140]}      8       {0×0 struct}    false    false 
    0.075559 sec     2     false      {0×0 char}    {[245 247 40 248 245 124 204 36]}      8       {0×0 struct}    false    false 
    0.080036 sec     3     false      {0×0 char}    {[107 234 202 245 167 9 217 239]}      8       {0×0 struct}    false    false 
    0.088748 sec     4     false      {0×0 char}    {[ 173 193 190 100 167 43 180 8]}      8       {0×0 struct}    false    false 
    0.10311 sec      5     false      {0×0 char}    {[    70 11 24 210 177 81 243 8]}      8       {0×0 struct}    false    false 
    0.10717 sec      6     false      {0×0 char}    {[112 97 195 203 47 125 114 165]}      8       {0×0 struct}    false    false 
    0.11715 sec      7     false      {0×0 char}    {[ 181 193 70 174 167 41 30 127]}      8       {0×0 struct}    false    false 
    0.12717 sec      8     false      {0×0 char}    {[ 245 87 149 57 192 65 129 178]}      8       {0×0 struct}    false    false 
    0.13717 sec      9     false      {0×0 char}    {[  228 245 140 35 38 65 215 65]}      8       {0×0 struct}    false    false 
    0.14732 sec     10     false      {0×0 char}    {[  208 62 237 89 50 64 157 121]}      8       {0×0 struct}    false    false 
    0.15841 sec     11     false      {0×0 char}    {[90 212 149 140 234 73 193 192]}      8       {0×0 struct}    false    false 
    0.16823 sec     12     false      {0×0 char}    {[  97 145 19 13 135 199 239 33]}      8       {0×0 struct}    false    false 
    0.17871 sec     13     false      {0×0 char}    {[   145 120 3 86 41 203 79 135]}      8       {0×0 struct}    false    false 
    0.1886 sec      14     false      {0×0 char}    {[ 42 154 67 167 176 191 115 21]}      8       {0×0 struct}    false    false 
    0.19816 sec     15     false      {0×0 char}    {[ 58 233 39 211 137 255 20 113]}      8       {0×0 struct}    false    false 
    0.20991 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=198×8 timetable
       Time       ID     Extended       Name                      Data                   Length      Signals       Error    Remote
    __________    ___    ________    __________    __________________________________    ______    ____________    _____    ______

    8.0995 sec    803     false      {0×0 char}    {[  4 41 113 196 174 182 117 235]}      8       {0×0 struct}    false    false 
    8.1096 sec    804     false      {0×0 char}    {[253 238 118 231 98 154 143 216]}      8       {0×0 struct}    false    false 
    8.1195 sec    805     false      {0×0 char}    {[    72 169 154 168 79 84 48 25]}      8       {0×0 struct}    false    false 
    8.1292 sec    806     false      {0×0 char}    {[   73 90 137 253 7 181 231 221]}      8       {0×0 struct}    false    false 
    8.1392 sec    807     false      {0×0 char}    {[ 30 244 112 224 221 90 161 221]}      8       {0×0 struct}    false    false 
    8.1494 sec    808     false      {0×0 char}    {[       5 19 96 38 8 200 83 209]}      8       {0×0 struct}    false    false 
    8.1592 sec    809     false      {0×0 char}    {[    44 173 224 193 58 91 92 68]}      8       {0×0 struct}    false    false 
    8.1692 sec    810     false      {0×0 char}    {[     86 22 115 116 7 163 15 43]}      8       {0×0 struct}    false    false 
    8.1793 sec    811     false      {0×0 char}    {[     175 141 1 73 96 37 19 117]}      8       {0×0 struct}    false    false 
    8.1893 sec    812     false      {0×0 char}    {[     94 211 137 209 117 3 1 42]}      8       {0×0 struct}    false    false 
    8.1992 sec    813     false      {0×0 char}    {[     93 183 40 71 165 73 82 39]}      8       {0×0 struct}    false    false 
    8.2095 sec    814     false      {0×0 char}    {[ 99 229 227 100 172 64 243 159]}      8       {0×0 struct}    false    false 
    8.2192 sec    815     false      {0×0 char}    {[     52 28 144 70 18 40 12 174]}      8       {0×0 struct}    false    false 
    8.2292 sec    816     false      {0×0 char}    {[   200 206 67 229 155 3 88 136]}      8       {0×0 struct}    false    false 
    8.2401 sec    817     false      {0×0 char}    {[ 160 114 207 37 249 213 87 157]}      8       {0×0 struct}    false    false 
    8.2497 sec    818     false      {0×0 char}    {[   77 22 133 211 195 242 85 99]}      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.