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.mfunction 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.mfunction 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.