Skip to main content
emobility.academy
B.Tech / BECAN & vehicle networksIntermediate About 5 weeksKit only

DBC-Style Signal Packing and Decoding Library

Write a reusable C library that packs and unpacks CAN signals defined DBC-style, with start bit, length, Intel or Motorola byte order, sign, factor and offset, and verify it against a PC reference in loopback mode on the kit. Generated tables keep firmware and documentation in step.

Uses: CAN · UART

The problem

Vehicle networks are described in DBC files that define each signal's position and scaling inside a CAN frame. Hand-written shifts and masks are a common source of bugs, especially for Motorola byte order and signed signals that cross byte boundaries. This project builds a tested library so every later CAN project can pack and decode signals correctly.

Objectives

  • Implement generic pack and unpack functions for Intel and Motorola signals of 1 to 64 bits.
  • Support signed values, factor, offset and range limits.
  • Describe messages in a table that could be generated from a DBC file.
  • Verify results against a Python reference using the cantools library.
  • Test on the kit in CAN loopback mode and measure execution time.

What you need

From the EV Embedded Development Kit

  • STM32F103C8T6 microcontroller unit (Arm Cortex-M3)
  • CAN bus interface with screw terminal
  • Serial UART interface
  • Analog potentiometer input
  • 16×2 LCD with contrast control

Software

  • STM32CubeIDE
  • Embedded C with STM32 HAL
  • Python with cantools and pyserial
  • A text editor for writing DBC files

Extra parts

None. Everything this project needs is on the kit.

Key concepts

Intel (little-endian) byte order
The signal's least significant bit is at the start bit and the value continues into higher-numbered bytes.
Motorola (big-endian) byte order
The start bit marks the most significant bit in DBC convention, and the value continues into the next byte down the payload. Its bit numbering across bytes is the classic source of errors.
Factor and offset
Physical value = raw × factor + offset. For example, temperature with factor 0.1 and offset −40 stores −40.0 to 6513.5 °C in 16 bits.
Sign extension
A signed raw value shorter than its container must have its top bit copied into the higher bits after extraction, or negative values decode as large positives.

Step-by-step guide

  1. 1

    Study the DBC conventions

    Write a short DBC file by hand with three messages covering Intel and Motorola signals, signed and unsigned, signals crossing byte boundaries, a 1-bit flag and a 12-bit value. Load it in Python with cantools and print each signal's start bit, length, byte order, factor and offset. Use cantools to encode sample values and print the payload bytes; these become your test vectors.

    Checkpoint: A DBC file and a printed table of test vectors with physical values and expected payloads.

  2. 2

    Design the data structures

    Define a signal descriptor struct: start bit, length, byte order, signed flag, factor and offset (as integer numerator and denominator or fixed-point, because the MCU has no FPU), minimum and maximum. A message descriptor holds ID, DLC, cycle time and an array of signals. Decide that raw values pass as uint64_t internally. Keep the descriptors in flash as const tables so they cost no RAM, and make the pack and unpack functions take a descriptor pointer so one implementation serves every message.

    Checkpoint: A header with descriptor types reviewed against every field your DBC uses.

  3. 3

    Implement Intel pack and unpack

    Write unpack_intel(payload, start, length) that walks from the start bit upward, extracting bits into a 64-bit value, and the matching pack function that clears the bits first, then writes them, leaving other signals untouched. Handle a 64-bit length without undefined shifts. Write unit tests on the PC with gcc before running on the target. Test signals at the very start and end of the payload as well as in the middle.

    Checkpoint: All Intel test vectors pass on the PC build.

  4. 4

    Implement Motorola pack and unpack

    For Motorola, convert the DBC start bit (MSB position) to a sequence of bits: within a byte move from high bit to low, and when you pass bit 0 of a byte move to bit 7 of the next byte. Implement carefully and test crossing two and three bytes. Compare every result with cantools. Draw the bit layout of each test signal on paper first; it makes errors much easier to find.

    Checkpoint: All Motorola test vectors, including multi-byte crossings, pass on the PC build.

  5. 5

    Add sign, scaling and limits

    Sign-extend raw values for signed signals. Convert to physical units with integer arithmetic, for example returning a value scaled by 10 or 100 to avoid floats, and offer a float convenience function for display only. When packing, clamp to minimum and maximum and report saturation, so a firmware bug cannot send an out-of-range raw value that wraps around. Count saturations so tests can check them.

    Checkpoint: Tests with negative values, limit values and out-of-range inputs all behave as specified.

  6. 6

    Generate message tables

    Write a Python script that reads the DBC with cantools and generates a C file of signal and message descriptor tables and a header of named constants. This keeps the documentation and the code consistent. Commit both the DBC file and the generated code. Make the generator fail with a clear message if the DBC contains a feature the library does not support, such as multiplexed signals, rather than generating wrong code silently. Run the generator as part of your build or test script.

    Checkpoint: Regenerating after changing one signal in the DBC changes only the matching descriptor.

  7. 7

    Test on the kit in loopback

    Enable the CAN DIP switch, configure bxCAN in loopback mode, pack a test message using the library with values from the potentiometer and counters, transmit it with HAL_CAN_AddTxMessage, receive it with HAL_CAN_GetRxMessage and unpack it. Display sent and decoded values on the LCD and stream payloads over UART. Use one message with Intel signals and one with Motorola signals so both paths run on the target. Check that bxCAN in loopback mode does not need the bus to be connected, which makes this test independent of wiring.

    Checkpoint: Decoded values on the LCD equal the sent values for every signal while the potentiometer is moved.

  8. 8

    Cross-check with the PC

    Stream each transmitted payload and physical values over UART. A Python script decodes the payload with cantools and compares with the firmware's values, flagging any mismatch. Run for several thousand frames with random values from a firmware pseudo-random generator. Generate values across each signal's full range, including minimum, maximum and zero, and include negative values for signed signals. Log any mismatch with the payload and both decoded values so the failing case can be reproduced in a PC unit test.

    Checkpoint: Zero mismatches over at least 10,000 random frames.

  9. 9

    Measure performance and document

    Measure cycles per pack and unpack of a full message using the DWT cycle counter and compare a generic implementation with a hand-optimised one for a byte-aligned signal. Document the API, conventions, limitations and the test method. Write a short guide showing how a later project adds a message. Note how the cost grows with signal length and with the number of signals in a message, so later projects can budget CPU time.

    Checkpoint: A performance table and an API guide with an example of adding a new message.

Testing & validation

  • Every test vector from cantools matches on PC and target builds.
  • Random-value cross-check over thousands of loopback frames.
  • Correct handling of signed, boundary and out-of-range values.
  • Measured execution time per message.

Expected results

A tested DBC-style signal library with a generator script, verified against an independent reference, ready to use in every later CAN project on the kit.

Take it further

  • Add multiplexed signals.
  • Add CRC and counter signal helpers for end-to-end protection.
  • Test across a two-node bus with a second kit or a USB-CAN adapter.
  • Add support for value tables (enumerations) from the DBC.

Viva and review questions

What is the difference between Intel and Motorola byte order in a DBC?

Intel stores the least significant bits first and continues into higher bytes; Motorola stores the most significant bits first and continues into lower-numbered positions in the next byte. The start bit means LSB for Intel and MSB for Motorola in DBC notation.

Why do you need sign extension?

A 12-bit signed value extracted into a 64-bit variable keeps its sign only if the top bit is copied into the higher bits; otherwise a negative value reads as a large positive one.

Why use loopback mode for testing?

It lets one board transmit and receive its own frames through the CAN peripheral without needing a second node, so the library can be tested on the real hardware.

Related EV projects

All 100 EV projects