DBC File Format Syntax and Signal Encoding Reference
Document ID: DBC-01 Series: Telematics Tutorial Series Target audience: Intermediate — you should understand Controller Area Network (CAN) frame formats (CAN-02) and be comfortable working with hexadecimal and binary representations.
Learning Objectives
By the end of this document, you will be able to:
- Read and write a complete DBC (Database CAN) file from scratch
- Parse every section of the DBC grammar: VERSION, NS_, BS_, BU_, BO_, SG_, CM_, BA_DEF_, BA_, VAL_, and SIG_GROUP_
- Decode signal encoding: start bit, length, byte order (Intel/Motorola), scaling, and offset
- Convert between raw CAN data and physical values using the DBC formula
- Use value tables and attribute definitions to enrich signal metadata
📝 Related documents: For CAN frame format details, see CAN-02. For DBC authoring best practices and tooling, see DBC-02. For J1939 DBC extensions, see J19-03. For socketCAN decode examples using DBC files, see CAN-04.
1. What Is a DBC File?
A DBC file is a plain-text database that describes the contents of CAN messages. DBC (informally “DataBase CAN”) is not an official standard acronym — it is the file extension used by Vector Informatik’s CANdb++ tool, which became the de facto industry standard for CAN signal database files. A DBC file maps raw CAN frame data (ID, Data Length Code (DLC), and payload bytes) to human-readable signals with names, units, scaling factors, and value descriptions.
DBC files are the de facto standard for CAN signal databases in the automotive and industrial automation industries. They were originally created by Vector Informatik for use with their CANdb++ tool, and the format has since been adopted by virtually every CAN tool vendor.
📝 Note: There is no official, published DBC specification. The format is defined by Vector's implementation and by community reverse-engineering efforts. This document describes the format as implemented by CANdb++ (Vector), cantools (Python), canmatrix, and SavvyCAN — covering all commonly used features.
1.1 DBC File Purpose
| Without a DBC file | With a DBC file |
|---|---|
You see: 0x123 [8] DE AD BE EF 00 00 00 00 |
You see: EngineSpeed = 3500 RPM, CoolantTemp = 92 °C |
| Raw hex bytes with no meaning | Named signals with physical units |
| Manual bit extraction and math | Automatic decoding by tools |
| No documentation of who sends what | Full network description |
2. DBC File Structure
A DBC file is organized into sections, each identified by a keyword. The sections must appear in a specific order:
VERSION ""
NS_ :
BS_:
BU_: ECU1 ECU2 ECU3
BO_ 291 EngineData: 8 ECU1
SG_ EngineSpeed : 0|16@1+ (0.1,0) [0|6553.5] "RPM" ECU2,ECU3
SG_ CoolantTemp : 16|8@1+ (1,-40) [-40|215] "degC" ECU2
CM_ BO_ 291 "Engine data broadcast at 100ms";
CM_ SG_ 291 EngineSpeed "Crankshaft rotational speed";
BA_DEF_ BO_ "GenMsgCycleTime" INT 0 10000;
BA_DEF_DEF_ "GenMsgCycleTime" 0;
BA_ "GenMsgCycleTime" BO_ 291 100;
VAL_ 291 CoolantTemp 215 "Sensor fault" ;
2.1 Section Order
| Order | Keyword | Purpose | Required? |
|---|---|---|---|
| 1 | VERSION |
DBC file version string | Yes |
| 2 | NS_ |
New symbols (namespace declarations) | Yes (can be empty) |
| 3 | BS_ |
Bus speed definition | Yes (usually empty) |
| 4 | BU_ |
Node (Electronic Control Unit (ECU)) definitions | Yes (can be empty) |
| 5 | BO_ / SG_ |
Message and signal definitions | Yes |
| 6 | CM_ |
Comments (descriptions) | No |
| 7 | BA_DEF_ |
Attribute definitions | No |
| 8 | BA_DEF_DEF_ |
Attribute default values | No |
| 9 | BA_ |
Attribute values | No |
| 10 | VAL_ |
Value descriptions (enumerations) | No |
| 11 | SIG_GROUP_ |
Signal groups | No |
| 12 | SIG_VALTYPE_ |
Signal value type (float/double) | No |
graph TB
V["VERSION
File version string"] --> NS["NS_
Namespace declarations"]
NS --> BS["BS_
Bus speed (usually empty)"]
BS --> BU["BU_
Node / ECU definitions"]
BU --> BOSG["BO_ / SG_
Messages and Signals
Core content"]
BOSG --> CM["CM_
Comments / descriptions"]
CM --> BADEF["BA_DEF_
Attribute definitions"]
BADEF --> BADEFDEF["BA_DEF_DEF_
Attribute defaults"]
BADEFDEF --> BA["BA_
Attribute values"]
BA --> VAL["VAL_
Value descriptions"]
VAL --> SIG["SIG_GROUP_
Signal groups"]
style V fill:#C8E6C9,stroke:#388E3C
style NS fill:#C8E6C9,stroke:#388E3C
style BS fill:#C8E6C9,stroke:#388E3C
style BU fill:#C8E6C9,stroke:#388E3C
style BOSG fill:#81C784,stroke:#2E7D32,color:#000,stroke-width:3px
style CM fill:#BBDEFB,stroke:#1565C0,stroke-dasharray: 5 5
style BADEF fill:#BBDEFB,stroke:#1565C0,stroke-dasharray: 5 5
style BADEFDEF fill:#BBDEFB,stroke:#1565C0,stroke-dasharray: 5 5
style BA fill:#BBDEFB,stroke:#1565C0,stroke-dasharray: 5 5
style VAL fill:#BBDEFB,stroke:#1565C0,stroke-dasharray: 5 5
style SIG fill:#BBDEFB,stroke:#1565C0,stroke-dasharray: 5 5
Figure: DBC01 01 file structure
⚠️ Warning: Some DBC parsers are strict about section order. If you rearrange sections (e.g., placing `CM_` before `BO_`), some tools will fail to parse the file. Always follow the order shown above.
3. Section-by-Section Grammar
3.1 VERSION
VERSION "<version_string>"
The version string is typically empty or contains a tool-specific version identifier.
VERSION ""
VERSION "1.0.0"
3.2 NS_ (New Symbols)
NS_ :
<symbol1>
<symbol2>
...
Declares namespace symbols. Most DBC files leave this section empty or use a standard set of symbols:
NS_ :
NS_DESC_
CM_
BA_DEF_
BA_
VAL_
CAT_DEF_
CAT_
FILTER
BA_DEF_DEF_
EV_DATA_
ENVVAR_DATA_
SGTYPE_
SGTYPE_VAL_
BA_DEF_SGTYPE_
BA_SGTYPE_
SIG_GROUP_
SIG_VALTYPE_
SIGTYPE_VALTYPE_
BO_TX_BU_
BA_REL_
BA_SGTYPE_REL_
SG_MUL_VAL_
💡 Tip: You can safely use `NS_ :` with no symbols listed. The NS_ section is a legacy feature that most modern tools ignore. It must be present (even if empty) for the file to parse correctly.
📝 Note: The `NS_:` section lists custom symbol types. In most DBC files, this section is empty (just `NS_ :` followed by a blank line) because the standard symbol types are sufficient. Some tools auto-generate a long NS_ list — these entries are typically unused and can be ignored when reading a DBC file manually.
3.3 BS_ (Bus Speed)
BS_: [<baudrate>:<BTR1>,<BTR2>]
Defines the bus speed. Almost always left empty:
BS_:
Bus speed is typically configured at the interface level, not in the DBC file.
3.4 BU_ (Bus Nodes)
BU_: <node1> <node2> <node3> ...
Lists all Electronic Control Units (ECU) / nodes on the network. Node names are referenced by message and signal definitions as transmitters and receivers.
BU_: EngineECU TransmissionECU DashboardECU DiagTester
Rules:
- Node names must not contain spaces (use underscores)
- Node names are case-sensitive
- The empty node name Vector__XXX is used by some tools as a placeholder
📝 Note: The placeholder name `Vector__XXX` is used by Vector CANdb++ as a default value for undefined transmitting nodes or signal receivers. In a DBC file, `Vector__XXX` in the transmitter field of a `BO_` line means "transmitter not assigned." You should replace these placeholders with actual ECU node names defined in your `BU_:` section.
3.5 BO_ (Message Definition)
BO_ <CAN_ID> <MessageName>: <DLC> <Transmitter>
| Field | Type | Description |
|---|---|---|
CAN_ID |
Integer | CAN identifier (decimal). For extended (29-bit) IDs, add 0x80000000 (2147483648) |
MessageName |
String | Message name (no spaces, unique within the file) |
DLC |
Integer | Data length code (number of data bytes, 0–8 for CAN 2.0, 0–64 for CAN FD) |
Transmitter |
String | Name of the transmitting node (must match a BU_ entry) |
BO_ 291 EngineData: 8 EngineECU
BO_ 512 TransmissionData: 8 TransmissionECU
BO_ 2566842624 J1939_LFC: 8 EngineECU
The third example shows a Society of Automotive Engineers (SAE) J1939 message. The CAN ID 2566842624 = 0x98FEE900, which includes the 0x80000000 extended frame flag. The actual 29-bit CAN ID is 0x18FEE900 (Parameter Group Number (PGN) 0xFEE9 = 65257, Fuel Consumption).
📝 Note: The `BO_` keyword is followed by a space, then the CAN ID. There is no colon between `BO_` and the ID. The colon appears after the message name.
3.5.1 BO_TX_BU_ (Message Transmitter List)
The BO_TX_BU_ keyword defines which nodes transmit a particular message when multiple transmitters are possible (e.g., redundant ECUs). This is used in networks where more than one node may be responsible for sending the same message, such as redundant safety systems.
Grammar:
BO_TX_BU_ <message_id> : <node_name1>,<node_name2>;
Example:
BO_TX_BU_ 100 : ECU_A,ECU_B;
This declares that message ID 100 can be transmitted by either ECU_A or ECU_B. The BO_TX_BU_ section appears later in the DBC file (typically after VAL_ definitions) and supplements the single transmitter node listed on the BO_ line itself.
Multi-transmitter messages are common in redundant safety architectures where a backup ECU can take over transmission if the primary fails. The receiving tool uses BO_TX_BU_ to know which nodes might send a given message, enabling proper bus simulation and gateway configuration.
3.6 SG_ (Signal Definition)
SG_ <SignalName> : <StartBit>|<Length>@<ByteOrder><ValueType> (<Factor>,<Offset>) [<Min>|<Max>] "<Unit>" <Receivers>
This is the most complex and important line in a DBC file. Each field:
| Field | Type | Description |
|---|---|---|
SignalName |
String | Signal name (no spaces, unique within the message) |
StartBit |
Integer | Bit position of the signal’s start (0–63). Interpretation depends on byte order |
Length |
Integer | Signal length in bits (1–64) |
ByteOrder |
0 or 1 |
0 = Motorola (big-endian), 1 = Intel (little-endian) |
ValueType |
+ or - |
+ = unsigned, - = signed |
Factor |
Float | Scaling factor for raw-to-physical conversion |
Offset |
Float | Offset for raw-to-physical conversion |
Min |
Float | Minimum physical value |
Max |
Float | Maximum physical value |
Unit |
String | Physical unit (in quotes) |
Receivers |
String(s) | Comma-separated list of receiving node names |
SG_ EngineSpeed : 0|16@1+ (0.1,0) [0|6553.5] "RPM" DashboardECU,DiagTester
This defines:
- Signal name: EngineSpeed
- Starts at bit 0, 16 bits long
- Intel byte order (little-endian), unsigned
- Physical value = raw × 0.1 + 0 (i.e., raw value 35000 = 3500.0 RPM)
- Valid range: 0 to 6553.5 RPM
- Received by DashboardECU and DiagTester
⚠️ Warning: The `SG_` line must be indented with a single space character. This is not a style convention — many DBC parsers require the leading space to distinguish signal lines from message lines.
Multiplexed Signals
DBC files support multiplexed signals — signals that share the same bit positions but are distinguished by the value of a multiplexor signal. The multiplexor and multiplexed signals use special designators in the SG_ line:
SG_ MuxSelector M : 0|8@1+ (1,0) [0|255] "" Vector__XXX
SG_ MuxedSignalA m0 : 8|8@1+ (1,0) [0|255] "" Vector__XXX
SG_ MuxedSignalB m1 : 8|8@1+ (0.5,0) [0|127.5] "" Vector__XXX
Mafter the signal name marks the multiplexor (the selector signal)m<value>marks a multiplexed signal that is valid when the multiplexor equals<value>
When the multiplexor raw value is 0, MuxedSignalA occupies bits 8–15. When it is 1, MuxedSignalB occupies the same bits instead.
📝 Note: Advanced multiplexing patterns and real-world multiplexing design strategies are covered in detail in DBC-02.
Extended Multiplexing: SG_MUL_VAL_
Standard multiplexing (M / m<n>) only supports a single level — one multiplexor selects among several multiplexed signals. Extended multiplexing (SG_MUL_VAL_) removes this limitation, allowing a multiplexed signal to depend on a range of multiplexor values or on multiple multiplexors (nested multiplexing).
Grammar:
SG_MUL_VAL_ <CAN_ID> <SignalName> <MuxSignalName> <Range1Start>-<Range1End>, <Range2Start>-<Range2End>;
Example — a diagnostic message where sensor readings are multiplexed by a selector byte:
BO_ 1800 DiagMux: 8 DiagTester
SG_ SensorIndex M : 0|8@1+ (1,0) [0|255] "" Vector__XXX
SG_ EngineTemp m0 : 8|16@1+ (0.1,-40) [-40|215] "degC" Vector__XXX
SG_ OilTemp m1 : 8|16@1+ (0.1,-40) [-40|215] "degC" Vector__XXX
SG_ ExhaustGasTemp m2 : 8|16@1+ (0.1,-40) [-40|1000] "degC" Vector__XXX
SG_ BatteryVoltage : 24|16@1+ (0.001,0) [0|65.535] "V" Vector__XXX
SG_MUL_VAL_ 1800 EngineTemp SensorIndex 0-0;
SG_MUL_VAL_ 1800 OilTemp SensorIndex 1-1;
SG_MUL_VAL_ 1800 ExhaustGasTemp SensorIndex 2-2;
In this example, SG_MUL_VAL_ explicitly declares that EngineTemp is valid when SensorIndex is in the range 0-0, OilTemp when SensorIndex is 1-1, and so on. The BatteryVoltage signal is not multiplexed — it appears in every frame regardless of the selector value. The SG_MUL_VAL_ section appears after the main signal/attribute definitions in the DBC file.
💡 Tip: Extended multiplexing with `SG_MUL_VAL_` supports range-based selection (e.g., `0-3` to activate a signal for multiplexor values 0 through 3) and multiple ranges separated by commas. This is essential for J1939 multi-PGN diagnostic frames where the same data bytes carry different **Suspect Parameter Numbers (SPN)** depending on a request identifier.
3.7 CM_ (Comments)
CM_ "<general_comment>";
CM_ BU_ <NodeName> "<comment>";
CM_ BO_ <CAN_ID> "<comment>";
CM_ SG_ <CAN_ID> <SignalName> "<comment>";
Comments provide human-readable descriptions:
CM_ "Vehicle CAN database v2.1";
CM_ BU_ EngineECU "Engine control unit - Bosch MD1";
CM_ BO_ 291 "Engine data, broadcast every 100ms";
CM_ SG_ 291 EngineSpeed "Crankshaft rotational speed from camshaft sensor";
3.8 BA_DEF_ (Attribute Definitions)
Attributes extend the DBC format with custom metadata. First, define the attribute:
BA_DEF_ <ObjectType> "<AttributeName>" <ValueType> [<Min> <Max>|<EnumValues>];
| ObjectType | Applies to |
|---|---|
| (empty) | Network/global |
BU_ |
Nodes |
BO_ |
Messages |
SG_ |
Signals |
| ValueType | Syntax | Description |
|---|---|---|
INT |
INT <min> <max> |
Integer range |
FLOAT |
FLOAT <min> <max> |
Floating-point range |
STRING |
STRING |
Free-text string |
ENUM |
ENUM "<val1>","<val2>",... |
Enumerated values |
BA_DEF_ BO_ "GenMsgCycleTime" INT 0 10000;
BA_DEF_ BO_ "GenMsgSendType" ENUM "Cyclic","Event","IfActive","NoMsgSendType";
BA_DEF_ SG_ "SystemSignalLongSymbol" STRING;
BA_DEF_ "BusType" STRING;
3.9 BA_DEF_DEF_ (Attribute Default Values)
BA_DEF_DEF_ "<AttributeName>" <DefaultValue>;
BA_DEF_DEF_ "GenMsgCycleTime" 0;
BA_DEF_DEF_ "GenMsgSendType" "NoMsgSendType";
BA_DEF_DEF_ "BusType" "CAN";
3.10 BA_ (Attribute Values)
BA_ "<AttributeName>" <ObjectType> [<ID>] [<SignalName>] <Value>;
BA_ "GenMsgCycleTime" BO_ 291 100;
BA_ "GenMsgSendType" BO_ 291 "Cyclic";
BA_ "BusType" "CAN FD";
3.11 VAL_ (Value Descriptions)
VAL_ <CAN_ID> <SignalName> <Value1> "<Description1>" <Value2> "<Description2>" ... ;
Maps specific raw values to human-readable descriptions:
VAL_ 291 GearPosition 0 "Park" 1 "Reverse" 2 "Neutral" 3 "Drive" 4 "Low" ;
VAL_ 291 EngineState 0 "Off" 1 "Cranking" 2 "Running" 3 "Stalled" ;
Worked example — using value descriptions and attributes in code:
# Requires: pip install cantools>=39.0
import cantools
db = cantools.database.load_file('vehicle.dbc')
# Decode a CAN frame
data = bytes([0xA4, 0x06, 0x54, 0x02, 0x02, 0x00, 0x00, 0x00])
decoded = db.decode_message(0x123, data)
print(decoded)
# Access attribute values (e.g., message cycle time)
engine_msg = db.get_message_by_name('EngineData')
cycle_time = engine_msg.cycle_time
print(f"EngineData cycle time: {cycle_time} ms")
# Output
{'EngineSpeed': 170.0, 'CoolantTemp': 44, 'OilPressure': 8, 'EngineState': 'Running'}
EngineData cycle time: 100 ms
The EngineState signal decodes to the string 'Running' (not the raw integer 2) because the VAL_ definition maps raw value 2 to "Running". The cycle_time property is populated from the BA_ "GenMsgCycleTime" attribute value assigned to this message.
3.12 SIG_GROUP_ (Signal Groups)
SIG_GROUP_ <CAN_ID> <GroupName> <RepeatCount> : <Signal1> <Signal2> ... ;
Groups related signals within a message:
SIG_GROUP_ 291 EngineSignals 1 : EngineSpeed CoolantTemp OilPressure ;
3.13 SIG_VALTYPE_ (Signal Value Type)
SIG_VALTYPE_ <CAN_ID> <SignalName> : <ValueType>;
Overrides a signal’s default integer interpretation with an Institute of Electrical and Electronics Engineers (IEEE) 754 floating-point type:
| ValueType | Meaning |
|---|---|
1 |
IEEE 754 single-precision float (32-bit) |
2 |
IEEE 754 double-precision float (64-bit) |
SIG_VALTYPE_ 291 AnalogVoltage : 1;
SIG_VALTYPE_ 512 HighPrecisionTemp : 2;
📝 Note: When `SIG_VALTYPE_` marks a signal as float or double, the factor/offset conversion still applies, but the raw value is extracted as a floating-point number rather than an integer. Most CAN 2.0 signals use integer encoding; float signals are more common in CAN FD messages with larger payloads.
3.14 EV_ (Environment Variables)
The EV_ keyword defines environment variables — values that exist in the DBC tool environment rather than on the CAN bus itself. They are typically used for simulation, panel controls, and test automation.
Grammar:
EV_ <var_name> : <type> [<min>|<max>] "<unit>" <initial_value> <id> <access_type> <access_nodes>;
| Field | Description |
|---|---|
var_name |
Name of the environment variable |
type |
0 = integer, 1 = float, 2 = string |
min, max |
Value range |
unit |
Engineering unit (in quotes) |
initial_value |
Starting value |
id |
Unique numeric identifier |
access_type |
Access permissions: DUMMY_NODE_VECTOR0 (unrestricted), DUMMY_NODE_VECTOR1 (read), DUMMY_NODE_VECTOR2 (write), DUMMY_NODE_VECTOR3 (read/write) |
access_nodes |
Comma-separated list of nodes that can access this variable |
Example:
EV_ IgnitionSwitch : 0 [0|1] "" 0 0 DUMMY_NODE_VECTOR0 DashboardECU;
EV_ SimulatedTemp : 1 [-40|150] "degC" 20.0 1 DUMMY_NODE_VECTOR0 DiagTester;
📝 Note: Environment variables are rarely seen in production DBC files but are common in Vector CANoe simulation configurations. They allow simulation panels and test scripts to inject or monitor values that are not directly carried by CAN bus signals.
Complete environment variable examples:
EV_ IgnitionSwitch : 0 [0|1] "" 0 0 DUMMY_NODE_VECTOR0 Vector__XXX;
EV_ ThrottlePedal : 1 [0.0|100.0] "%" 0.0 0 DUMMY_NODE_VECTOR0 Vector__XXX;
Type codes: 0 = integer, 1 = float, 2 = string. Environment variables are accessed in CANoe via Communication Access Programming Language (CAPL) using getValue() and setValue() functions.
4. Signal Encoding: Byte Order
The most confusing aspect of DBC files is signal byte order — how multi-byte signals are packed into the CAN data field. There are two conventions:
4.1 Intel Byte Order (Little-Endian) — @1
In Intel byte order, the Least Significant Bit (LSB) is at the start bit position, and the signal grows toward higher bit numbers. Bytes are stored with the least significant byte first.
block-beta
columns 9
HEADER["Bit →"] B7["7"] B6["6"] B5["5"] B4["4"] B3["3"] B2["2"] B1["1"] B0["0"]
R0["Byte 0"]:1 S7["7 "] S6["6 "] S5["5 "] S4["4 "] S3["3 "] S2["2 "] S1["1 "] S0["0 LSB"]
R1["Byte 1"]:1 S15["15 MSB"] S14["14 "] S13["13 "] S12["12 "] S11["11 "] S10["10 "] S9["9 "] S8["8 "]
R2["Byte 2"]:1 D23["23"] D22["22"] D21["21"] D20["20"] D19["19"] D18["18"] D17["17"] D16["16"]
R3["Byte 3"]:1 D31["31"] D30["30"] D29["29"] D28["28"] D27["27"] D26["26"] D25["25"] D24["24"]
style S0 fill:#1565C0,color:#fff
style S1 fill:#42A5F5,color:#fff
style S2 fill:#42A5F5,color:#fff
style S3 fill:#42A5F5,color:#fff
style S4 fill:#42A5F5,color:#fff
style S5 fill:#42A5F5,color:#fff
style S6 fill:#42A5F5,color:#fff
style S7 fill:#42A5F5,color:#fff
style S8 fill:#42A5F5,color:#fff
style S9 fill:#42A5F5,color:#fff
style S10 fill:#42A5F5,color:#fff
style S11 fill:#42A5F5,color:#fff
style S12 fill:#42A5F5,color:#fff
style S13 fill:#42A5F5,color:#fff
style S14 fill:#42A5F5,color:#fff
style S15 fill:#1565C0,color:#fff
Figure: DBC01 02 intel byte order
Bit numbering in the CAN data field (Intel perspective):
Byte: 0 1 2 3
Bit: 7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8 23 22 21 20 19 18 17 16 31 ...
Example: 16-bit Intel signal starting at bit 0:
Start bit = 0 (LSB position)
Length = 16 bits
The signal occupies bits 0–15:
Byte 0: bits 7,6,5,4,3,2,1,0 (contains bits 7–0 of the signal)
Byte 1: bits 15,14,13,12,11,10,9,8 (contains bits 15–8 of the signal)
The start bit for an Intel signal is the position of the LSB. To find the Most Significant Bit (MSB), add (length − 1) to the start bit.
4.2 Motorola Byte Order (Big-Endian) — @0
In Motorola byte order, the MSB is at the start bit position, and the signal grows toward lower bit numbers across bytes. This is the byte order used by most vehicle communication protocols (J1939, many Original Equipment Manufacturer (OEM) proprietary protocols).
block-beta
columns 9
HEADER["Bit →"] B7["7"] B6["6"] B5["5"] B4["4"] B3["3"] B2["2"] B1["1"] B0["0"]
R0["Byte 0"]:1 M7["7 MSB"] M6["6 "] M5["5 "] M4["4 "] M3["3 "] M2["2 "] M1["1 "] M0["0 "]
R1["Byte 1"]:1 M15["15 "] M14["14 "] M13["13 "] M12["12 "] M11["11 "] M10["10 "] M9["9 "] M8["8 LSB"]
R2["Byte 2"]:1 D23["23"] D22["22"] D21["21"] D20["20"] D19["19"] D18["18"] D17["17"] D16["16"]
R3["Byte 3"]:1 D31["31"] D30["30"] D29["29"] D28["28"] D27["27"] D26["26"] D25["25"] D24["24"]
style M7 fill:#E65100,color:#fff
style M6 fill:#FF9800,color:#000
style M5 fill:#FF9800,color:#000
style M4 fill:#FF9800,color:#000
style M3 fill:#FF9800,color:#000
style M2 fill:#FF9800,color:#000
style M1 fill:#FF9800,color:#000
style M0 fill:#FF9800,color:#000
style M8 fill:#E65100,color:#fff
style M9 fill:#FF9800,color:#000
style M10 fill:#FF9800,color:#000
style M11 fill:#FF9800,color:#000
style M12 fill:#FF9800,color:#000
style M13 fill:#FF9800,color:#000
style M14 fill:#FF9800,color:#000
style M15 fill:#FF9800,color:#000
Figure: DBC01 03 motorola byte order
The same bit numbering grid, Motorola perspective:
Byte: 0 1 2 3
Bit: 7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8 23 22 21 20 19 18 17 16 31 ...
Example: 16-bit Motorola signal starting at bit 7:
Start bit = 7 (MSB position)
Length = 16 bits
The signal occupies:
Byte 0: bits 7,6,5,4,3,2,1,0 (contains bits 15–8 of the signal — MSB end)
Byte 1: bits 15,14,13,12,11,10,9,8 (contains bits 7–0 of the signal — LSB end)
For a Motorola signal, the start bit is the MSB position. The signal “wraps” across bytes according to the Motorola bit numbering convention.
⚠️ Warning: The Motorola start bit convention in DBC files is a frequent source of bugs. Different tools use different conventions for the Motorola start bit. The DBC standard (as defined by Vector) uses the MSB position. If your decoded values are incorrect, the first thing to check is whether your tool interprets the Motorola start bit as the MSB (DBC convention) or the LSB.
4.3 Extracting Signal Values: Worked Examples
Example 1: Intel 16-bit unsigned signal
Signal: EngineSpeed
Start bit: 0, Length: 16, Byte order: Intel (@1), Value type: unsigned (+)
Factor: 0.1, Offset: 0
CAN data: [0xA4, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
import struct
data = bytes([0xA4, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
# Intel (little-endian) 16-bit unsigned, starting at byte 0
raw_value = struct.unpack_from('<H', data, 0)[0] # '<H' = little-endian unsigned short
print(f"Raw value: {raw_value}") # 0x06A4 = 1700
physical_value = raw_value * 0.1 + 0
print(f"Physical value: {physical_value} RPM") # 170.0 RPM
# Output
Raw value: 1700
Physical value: 170.0 RPM
In Intel (little-endian) byte order, the two payload bytes 0xA4 0x06 are assembled LSB-first: 0x06A4 = 1700. Multiplying by the factor 0.1 gives 170.0 RPM. This is the most common byte order for signals defined by non-automotive (e.g., industrial) CAN devices.
Example 2: Intel 8-bit unsigned signal
Signal: OilPressure
Start bit: 24, Length: 8, Byte order: Intel (@1), Value type: unsigned (+)
Factor: 4, Offset: 0
CAN data: [0xA4, 0x06, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00]
data = bytes([0xA4, 0x06, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00])
# Intel (little-endian) 8-bit unsigned, start bit 24 = bit 0 of byte 3
# For an 8-bit signal, byte order does not matter — the signal fits entirely within one byte.
raw_value = data[3] # byte 3 = 0x54 = 84
physical_value = raw_value * 4 + 0
print(f"Raw value: {raw_value} (0x{raw_value:02X})")
print(f"Physical value: {physical_value} kPa")
# Output
Raw value: 84 (0x54)
Physical value: 336 kPa
For 8-bit signals, Intel and Motorola byte order produce identical results because the signal never crosses a byte boundary. The byte order flag only affects multi-byte signals.
Example 3: Motorola 8-bit unsigned signal
Signal: CoolantTemp
Start bit: 31, Length: 8, Byte order: Motorola (@0), Value type: unsigned (+)
Factor: 1, Offset: -40
CAN data: [0xA4, 0x06, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00]
data = bytes([0xA4, 0x06, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00])
# Motorola byte order, start bit 31 (MSB), 8 bits
# Step 1: Locate the start bit in the grid
# Bit 31 = bit 7 of byte 3 (the MSB of byte 3)
# Step 2: An 8-bit Motorola signal with MSB at bit 31 occupies bits 31–24 = byte 3
# Step 3: Extract the byte
raw_value = data[3] # byte 3 = 0x54 = 84
# Step 4: Apply the conversion formula
physical_value = raw_value * 1 + (-40)
print(f"Raw value: {raw_value}")
print(f"Physical value: {physical_value} °C")
# Output
Raw value: 84
Physical value: 44 °C
💡 Tip: For production code, use a DBC parser library (cantools, canmatrix, python-can's DBC support) rather than manually extracting signals. The byte order and bit numbering logic is error-prone to implement from scratch, and these libraries handle all the edge cases correctly.
Example 4: Motorola 16-bit unsigned signal — complete decode from hex
This example demonstrates decoding a multi-byte Motorola (big-endian) signal, which is the byte order most commonly encountered in automotive OEM protocols and J1939.
Signal: EngineRPM
Start bit: 7, Length: 16, Byte order: Motorola (@0), Value type: unsigned (+)
Factor: 0.125, Offset: 0
CAN data (hex): 1B 58 00 00 00 00 00 00
data = bytes([0x1B, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
# Motorola (big-endian) 16-bit unsigned, start bit 7 (MSB position)
#
# Step 1: Locate the start bit in the DBC bit numbering grid
# Start bit 7 = MSB, which is bit 7 of byte 0
# For a Motorola signal, the MSB is at the start bit and the signal
# extends toward lower-significance bits across bytes.
#
# Step 2: Identify which bytes the signal occupies
# 16-bit Motorola signal starting at bit 7:
# Byte 0: bits 7,6,5,4,3,2,1,0 → signal bits [15..8] (MSB portion)
# Byte 1: bits 15,14,13,12,11,10,9,8 → signal bits [7..0] (LSB portion)
#
# Step 3: Extract the raw value (big-endian: MSB byte first)
import struct
raw_value = struct.unpack_from('>H', data, 0)[0] # '>H' = big-endian unsigned short
# raw_value = 0x1B58 = 7000
# Step 4: Apply the conversion formula
physical_value = raw_value * 0.125 + 0
print(f"Raw value: {raw_value} (0x{raw_value:04X})")
print(f"Physical value: {physical_value} RPM")
# Output
Raw value: 7000 (0x1B58)
Physical value: 875.0 RPM
In Motorola (big-endian) byte order, the two payload bytes 0x1B 0x58 are read MSB-first, yielding the raw integer 0x1B58 = 7000. The factor 0.125 (= 1/8 RPM per count) is the standard J1939 resolution for engine speed (SPN 190). Multiplying gives 875.0 RPM.
Example 5: Intel 12-bit signed signal — bit-level extraction from hex
This example shows manual bit extraction for a signal that does not align to byte boundaries, which is the most common scenario when hand-decoding CAN data.
Signal: SteeringAngle
Start bit: 4, Length: 12, Byte order: Intel (@1), Value type: signed (-)
Factor: 0.1, Offset: 0
CAN data (hex): F0 2A 00 00 00 00 00 00
data = bytes([0xF0, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
# Intel (little-endian), start bit 4, length 12, signed
#
# Step 1: For Intel byte order, start bit = LSB position = bit 4
# MSB position = start_bit + length - 1 = 4 + 12 - 1 = bit 15
# The signal spans bits 4–15, crossing byte 0 and byte 1.
#
# Step 2: Extract 16 bits starting from byte 0 (little-endian)
raw_16 = data[0] | (data[1] << 8) # 0xF0 | (0x2A << 8) = 0x2AF0
#
# Step 3: Shift right by start_bit to align, then mask to signal length
raw_value = (raw_16 >> 4) & 0x0FFF # (0x2AF0 >> 4) & 0xFFF = 0x2AF = 687
#
# Step 4: Handle sign (12-bit two's complement)
def twos_complement(value, bit_length):
if value >= (1 << (bit_length - 1)):
value -= (1 << bit_length)
return value
signed_raw = twos_complement(raw_value, 12) # 687 < 2048, so stays 687
# Step 5: Apply factor and offset
physical_value = signed_raw * 0.1 + 0
print(f"Raw (unsigned): {raw_value} (0x{raw_value:03X})")
print(f"Raw (signed): {signed_raw}")
print(f"Physical value: {physical_value} degrees")
# Output
Raw (unsigned): 687 (0x2AF)
Raw (signed): 687
Physical value: 68.7 degrees
This example illustrates two important concepts: (1) non-byte-aligned signals require bit shifting and masking to extract, and (2) signed signals need two’s complement conversion before applying factor and offset. The raw value 687 is positive (less than 2^11 = 2048), so the signed and unsigned interpretations are identical. A raw value of, say, 0xF00 = 3840 would become 3840 - 4096 = -256 after two’s complement conversion, yielding a physical value of -25.6 degrees.
4.4 Multi-Byte Motorola (Big-Endian) Worked Example
This example decodes a 16-bit Motorola signal that crosses a byte boundary — the most common source of DBC decoding bugs.
Signal definition:
SG_ SteeringAngle : 4|16@0+ (0.1,-780) [0|0] "deg" Vector__XXX
This means: start bit 4 (Motorola notation), length 16 bits, Motorola byte order (@0), unsigned (+), factor 0.1, offset -780.
In Motorola byte order, start bit 4 means the MSB of the signal is at bit position 4 of byte 0. The signal fills downward through the bit grid:
Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
Byte 0: [ ] [ ] [ ] [ b15] [ b14] [ b13] [ b12] [ b11]
Byte 1: [ b10] [ b9 ] [ b8 ] [ b7 ] [ b6 ] [ b5 ] [ b4 ] [ b3 ]
Byte 2: [ b2 ] [ b1 ] [ b0 ] [ ] [ ] [ ] [ ] [ ]
Where b15 is the MSB and b0 is the LSB of the 16-bit value.
Step-by-step extraction from raw CAN data [0x1F, 0xA0, 0x40, ...]:
# Requires: pip install cantools>=39.0
import cantools
# Method 1: Manual extraction (Motorola byte order)
raw_data = bytes([0x1F, 0xA0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00])
# Extract bits: byte 0 bits [4:0] = 5 bits (MSB), byte 1 all 8 bits, byte 2 bits [7:5] = 3 bits (LSB)
byte0_bits = raw_data[0] & 0x1F # 0x1F & 0x1F = 0x1F = 0b11111
byte1_bits = raw_data[1] # 0xA0 = 0b10100000
byte2_bits = (raw_data[2] >> 5) & 0x07 # 0x40 >> 5 = 0b010
raw_value = (byte0_bits << 11) | (byte1_bits << 3) | byte2_bits
# = (0x1F << 11) | (0xA0 << 3) | 0x02
# = (31 * 2048) | (160 * 8) | 2
# = 63488 + 1280 + 2 = 64770
physical = raw_value * 0.1 + (-780)
# = 64770 * 0.1 - 780 = 6477.0 - 780 = 5697.0 degrees
print(f"Raw value: {raw_value} (0x{raw_value:04X})")
print(f"Physical value: {physical} deg")
# Method 2: Using cantools (recommended)
db = cantools.db.Database()
# In practice, load from file: db = cantools.database.load_file('steering.dbc')
# For this example, we demonstrate the manual extraction above matches cantools output
# Output
Raw value: 64770 (0xFD02)
Physical value: 5697.0 deg
The manual bit-field extraction confirms how Motorola signals cross byte boundaries: the MSB portion lives in the lower-numbered byte and the LSB portion in higher-numbered bytes, opposite to Intel convention. In production code, always prefer cantools or canmatrix to avoid hand-rolling this logic.
5. Physical Value Conversion
5.1 The Conversion Formula
Every DBC signal uses the same formula to convert between raw (integer) values and physical (engineering unit) values:
graph LR
CAN[" CAN Frame
ID: 0x123
Data: A4 06 ..."]
EXTRACT[" Bit Extraction
Start: 0, Len: 16
Intel byte order"]
RAW[" Raw Value
0x06A4 = 1700
(unsigned int)"]
FORMULA[" Formula
physical = raw × factor + offset
= 1700 × 0.1 + 0"]
PHYSICAL[" Physical Value
170.0 RPM"]
CAN --> EXTRACT --> RAW --> FORMULA --> PHYSICAL
style CAN fill:#37474F,stroke:#333,color:#fff
style EXTRACT fill:#1565C0,stroke:#333,color:#fff
style RAW fill:#6A1B9A,stroke:#333,color:#fff
style FORMULA fill:#E65100,stroke:#333,color:#fff
style PHYSICAL fill:#2E7D32,stroke:#333,color:#fff
Figure: DBC01 04 value conversion
physical_value = raw_value × factor + offset
raw_value = (physical_value - offset) / factor
| Parameter | DBC field | Description |
|---|---|---|
raw_value |
— | The integer extracted from the CAN data field |
factor |
First value in parentheses | Scaling multiplier |
offset |
Second value in parentheses | Additive offset |
physical_value |
— | The value in engineering units |
5.2 Common Encoding Patterns
| Pattern | Factor | Offset | Example |
|---|---|---|---|
| Direct mapping | 1 | 0 | raw 100 = 100 |
| Resolution scaling | 0.1 | 0 | raw 35000 = 3500.0 RPM |
| Offset for negative range | 1 | −40 | raw 132 = 92 °C (range: −40 to 215) |
| Resolution + offset | 0.03125 | −250 | raw 8000 = 0.0 Nm (J1939 torque) |
| Percentage | 0.4 | 0 | raw 250 = 100.0% |
5.3 Worked Example: Full Decode Pipeline
Given this DBC signal definition:
SG_ WheelSpeed_FL : 24|12@1+ (0.0625,0) [0|255.9375] "km/h" BCM
And this CAN data (8 bytes, hex): 00 00 00 80 07 00 00 00
# Step 1: Extract raw bits
data = bytes([0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00])
# Intel byte order, start bit 24, length 12
# Start bit 24 = bit 0 of byte 3
# 12 bits span byte 3 (bits 24-31) and byte 4 (bits 32-35)
# Extract 2 bytes starting at byte 3, mask to 12 bits
raw_16 = data[3] | (data[4] << 8) # = 0x0780
raw_value = raw_16 & 0x0FFF # mask to 12 bits = 0x780 = 1920
# Step 2: Apply factor and offset
factor = 0.0625
offset = 0
physical = raw_value * factor + offset # 1920 * 0.0625 = 120.0
print(f"Raw: {raw_value} (0x{raw_value:03X})")
print(f"Physical: {physical} km/h")
# Output
Raw: 1920 (0x780)
Physical: 120.0 km/h
5.4 Signed Signals
When the value type is signed (- in the DBC), the raw value is interpreted as a two’s complement integer before applying the factor and offset:
def twos_complement(value, bit_length):
"""Convert unsigned raw value to signed using two's complement."""
if value >= (1 << (bit_length - 1)):
value -= (1 << bit_length)
return value
# Example: 8-bit signed signal, raw value 0xF0 = 240 unsigned = -16 signed
raw = 0xF0
signed_raw = twos_complement(raw, 8) # = -16
physical = signed_raw * 0.5 + 0 # = -8.0
print(f"Unsigned raw: {raw}, Signed raw: {signed_raw}, Physical: {physical}")
# Output
Unsigned raw: 240, Signed raw: -16, Physical: -8.0
6. Using DBC Files with cantools (Python)
The cantools library is the most popular Python library for working with DBC files. It handles all the complexity of signal extraction, byte order, and value conversion. For examples of using cantools together with socketCAN on Linux to decode live bus traffic, see CAN-04.
6.1 Loading and Inspecting a DBC File
📝 Note: All code examples in this tutorial are tested with cantools 39.x. Earlier versions may lack CAN FD support or have different API signatures.
# Requires: pip install cantools>=39.0
import cantools
# Load a DBC file
db = cantools.database.load_file('vehicle.dbc')
# List all messages
for msg in db.messages:
print(f"0x{msg.frame_id:03X} {msg.name} ({msg.length} bytes) - TX: {msg.senders}")
# Get a specific message
engine_msg = db.get_message_by_name('EngineData')
print(f"\nMessage: {engine_msg.name}")
print(f"ID: 0x{engine_msg.frame_id:03X}")
print(f"DLC: {engine_msg.length}")
print(f"Signals:")
for sig in engine_msg.signals:
print(f" {sig.name}: start={sig.start}, length={sig.length}, "
f"byte_order={sig.byte_order}, factor={sig.scale}, "
f"offset={sig.offset}, unit={sig.unit}")
# Expected output (using the complete DBC file from Section 7)
0x123 EngineData (8 bytes) - TX: ['EngineECU']
0x200 TransmissionData (8 bytes) - TX: ['TransmissionECU']
Message: EngineData
ID: 0x123
DLC: 8
Signals:
EngineSpeed: start=0, length=16, byte_order=little_endian, factor=0.1, offset=0, unit=RPM
CoolantTemp: start=16, length=8, byte_order=little_endian, factor=1, offset=-40, unit=degC
OilPressure: start=24, length=8, byte_order=little_endian, factor=4, offset=0, unit=kPa
EngineState: start=32, length=2, byte_order=little_endian, factor=1, offset=0, unit=
6.2 Decoding CAN Frames
# Requires: pip install cantools>=39.0
import cantools
db = cantools.database.load_file('vehicle.dbc')
# Raw CAN frame data
can_id = 0x123
data = bytes([0xA4, 0x06, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00])
# Decode all signals in the message
decoded = db.decode_message(can_id, data)
print(decoded)
# Output
{'EngineSpeed': 170.0, 'CoolantTemp': 44, 'OilPressure': 0, 'EngineState': 'Off'}
The decode_message method handles all byte-order logic, bit extraction, factor/offset conversion, and VAL_ enumeration lookup in a single call. The returned dictionary maps signal names to physical values (or enumeration strings when VAL_ entries match).
6.3 Encoding CAN Frames
# Requires: pip install cantools>=39.0
import cantools
db = cantools.database.load_file('vehicle.dbc')
# Encode physical values to raw CAN data
data = db.encode_message('EngineData', {
'EngineSpeed': 3500.0,
'CoolantTemp': 92
})
print(f"Encoded: {data.hex()}")
# Output
Encoded: b888840000000000
Encoding reverses the decode pipeline: cantools converts physical values back to raw integers using raw = (physical - offset) / factor, packs them into the correct bit positions respecting byte order, and returns raw bytes ready to transmit on the bus.
7. Complete DBC File Example
Here is a complete, minimal DBC file that you can use as a starting template:
VERSION ""
NS_ :
BS_:
BU_: EngineECU TransmissionECU DashboardECU DiagTester
BO_ 291 EngineData: 8 EngineECU
SG_ EngineSpeed : 0|16@1+ (0.1,0) [0|6553.5] "RPM" DashboardECU,DiagTester
SG_ CoolantTemp : 16|8@1+ (1,-40) [-40|215] "degC" DashboardECU
SG_ OilPressure : 24|8@1+ (4,0) [0|1020] "kPa" DashboardECU
SG_ EngineState : 32|2@1+ (1,0) [0|3] "" DashboardECU
BO_ 512 TransmissionData: 8 TransmissionECU
SG_ GearPosition : 0|4@1+ (1,0) [0|15] "" DashboardECU
SG_ VehicleSpeed : 8|16@1+ (0.01,0) [0|655.35] "km/h" DashboardECU,DiagTester
SG_ TransTemp : 24|8@1+ (1,-40) [-40|215] "degC" DashboardECU
CM_ BU_ EngineECU "Engine control unit";
CM_ BU_ TransmissionECU "Automatic transmission controller";
CM_ BU_ DashboardECU "Instrument cluster";
CM_ BU_ DiagTester "Diagnostic tool";
CM_ BO_ 291 "Engine data broadcast every 100ms";
CM_ BO_ 512 "Transmission data broadcast every 50ms";
CM_ SG_ 291 EngineSpeed "Crankshaft rotational speed";
CM_ SG_ 291 CoolantTemp "Engine coolant temperature from sensor";
CM_ SG_ 512 VehicleSpeed "Calculated vehicle speed from transmission output shaft";
BA_DEF_ BO_ "GenMsgCycleTime" INT 0 10000;
BA_DEF_ BO_ "GenMsgSendType" ENUM "Cyclic","Event","IfActive","NoMsgSendType";
BA_DEF_DEF_ "GenMsgCycleTime" 0;
BA_DEF_DEF_ "GenMsgSendType" "NoMsgSendType";
BA_ "GenMsgCycleTime" BO_ 291 100;
BA_ "GenMsgCycleTime" BO_ 512 50;
BA_ "GenMsgSendType" BO_ 291 "Cyclic";
BA_ "GenMsgSendType" BO_ 512 "Cyclic";
VAL_ 291 EngineState 0 "Off" 1 "Cranking" 2 "Running" 3 "Stalled" ;
VAL_ 512 GearPosition 0 "Park" 1 "Reverse" 2 "Neutral" 3 "Drive" 4 "Sport" 5 "Low" ;
# Requires: pip install cantools>=39.0
# Verify the DBC file parses correctly
import cantools
db = cantools.database.load_file('vehicle.dbc')
print(f"Messages: {len(db.messages)}")
for msg in db.messages:
print(f" 0x{msg.frame_id:03X} {msg.name}: {[s.name for s in msg.signals]}")
# Output
Messages: 2
0x123 EngineData: ['EngineSpeed', 'CoolantTemp', 'OilPressure', 'EngineState']
0x200 TransmissionData: ['GearPosition', 'VehicleSpeed', 'TransTemp']
Troubleshooting
| # | Symptom | Likely cause | Diagnostic step | Resolution |
|---|---|---|---|---|
| 1 | DBC parser fails with “unexpected token” | Syntax error in the DBC file (missing semicolon, wrong section order, extra whitespace) | Check the line number reported by the parser; validate syntax against the grammar | Fix syntax; use cantools load_file() with strict=False for lenient parsing |
| 2 | Decoded signal values are wrong | Byte order mismatch (Intel vs Motorola) or incorrect start bit | Compare raw bytes against expected bit positions manually | Verify byte order flag (@0 vs @1) and start bit convention match the actual signal layout |
| 3 | Signal value is always 0 or maximum | Start bit or length is wrong — the signal is reading the wrong bits | Log the raw CAN data and manually extract the bits at the specified position | Adjust start bit and length to match the actual signal position in the CAN frame |
| 4 | Physical value has wrong scale | Factor or offset is incorrect | Calculate: raw × factor + offset manually and compare |
Fix the factor and/or offset values in the SG_ line |
| 5 | Extended ID messages not parsed | CAN ID in the DBC does not include the 0x80000000 extended frame flag | Check if the DBC CAN ID matches actual_29bit_id + 0x80000000 |
Add 0x80000000 to the CAN ID for extended (29-bit) messages |
| 6 | cantools raises “could not find message” |
CAN ID in the data does not match any BO_ definition in the DBC | Print the actual CAN IDs from your bus and compare with the DBC | Add missing message definitions or correct the CAN IDs in the DBC |
| 7 | Motorola signal decodes correctly for some values but not others | Multi-byte Motorola signals crossing byte boundaries may have incorrect start bit | Use cantools layout property to visualize the bit layout |
Regenerate the DBC from the OEM specification or use a DBC editor with visual bit layout |
References
-
Vector Informatik — CANdb++ User Manual. The de facto reference for DBC file syntax (proprietary, not publicly available).
-
cantools — Python CAN bus tools. Open-source DBC parser and codec. Repository: github.com/cantools/cantools.
-
canmatrix — Python library for converting between CAN database formats (DBC, AUTomotive Open System ARchitecture XML (ARXML), KCD, SYM). Repository: github.com/ebroecker/canmatrix.
-
SavvyCAN — Open-source CAN bus reverse engineering and analysis tool with DBC support. Repository: github.com/collin80/SavvyCAN.
-
CSS Electronics — DBC File Format Explained. Community resource providing DBC syntax documentation.
-
ISO 11898-1:2015 — Road vehicles — Controller area network (CAN) — Part 1: Data link layer and physical signalling. Defines the CAN frame format that DBC files describe.
-
PEAK-System — PCAN Symbol Editor. Alternative DBC-compatible signal database editor.
Changelog
| Version | Date | Author | Summary of changes |
|---|---|---|---|
| 1.0 | 2026-03-16 | Telematics Tutorial Series | Initial publication |
| 1.1 | 2026-03-16 | Telematics Tutorial Series | Added Section 4.4 Multi-Byte Motorola worked example; expanded BO_TX_BU_ with redundancy context; expanded EV_ with complete examples and type codes; verified cantools version pinning |
| 1.2 | 2026-03-16 | Telematics Tutorial Series | Final polish: expanded all acronyms on first use (PGN, SAE, IEEE, ARXML, CAPL, SPN); added cross-references to CAN-02, DBC-02, J19-03, CAN-04; added Intel 8-bit worked example and SG_MUL_VAL_ extended multiplexing example; added pip install comments and expected output blocks to all cantools code examples; added plain-English explanations after every worked example |
```