Skip to main content
emobility.academy
M.TechCAN & vehicle networksAdvanced About 10 weeksKit + add-ons

Lightweight CANopen Node (NMT, PDO, SDO) on STM32

Implement a lightweight CANopen slave on the STM32 with network management, heartbeat, an object dictionary, expedited SDO access and mapped PDOs for battery and throttle data, and test it from a Python CANopen master on a USB-CAN adapter. Its footprint and timing are compared with an open-source stack.

Uses: CAN · UART

The problem

CANopen is used in many light EVs, industrial vehicles and motor controllers to configure devices and exchange process data in a standard way. Building a node from the protocol up teaches how object dictionaries, service data and process data fit together and what an implementation costs in memory and timing on a small MCU.

Objectives

  • Implement NMT state handling and boot-up and heartbeat messages.
  • Build an object dictionary with mandatory communication objects and application objects.
  • Implement expedited SDO upload and download with abort codes.
  • Implement TPDO and RPDO with configurable mapping and transmission types.
  • Measure memory use and response times and compare with an open-source stack.

What you need

From the EV Embedded Development Kit

  • STM32F103C8T6 microcontroller unit (Arm Cortex-M3)
  • CAN bus interface with screw terminal
  • 1S battery with connector and T-sense input
  • Analog potentiometer input
  • Serial UART interface
  • 16×2 LCD with contrast control
  • RGB LEDs and a buzzer

Software

  • STM32CubeIDE
  • Embedded C with STM32 HAL
  • Python with python-can and the canopen library
  • An open-source CANopen stack such as CANopenNode for the baseline comparison

Extra parts

  • USB-CAN adapter for the PC — The board has one CAN node. The adapter lets a PC act as the CANopen master with the Python canopen library, sending NMT commands and SDO requests and receiving PDOs.

This project uses the kit's lithium cell. Respect polarity, never short the terminals, and don't leave the cell charging unattended.

Key concepts

Object dictionary
A table of all a device's parameters and data, addressed by 16-bit index and 8-bit sub-index. Communication settings live at 0x1000–0x1FFF and application data usually from 0x2000 upward.
NMT states
Initialisation, pre-operational, operational and stopped. A master moves nodes between them with NMT commands on COB-ID 0x000; PDOs flow only when operational.
SDO
Service data objects read or write one dictionary entry with a confirmed request–response exchange, on COB-IDs 0x600 + node ID (to the node) and 0x580 + node ID (from the node). Expedited SDO carries up to 4 bytes in a single frame.
PDO
Process data objects carry real-time data without protocol overhead; their content is defined by mapping entries that point to dictionary objects. Default TPDO1 uses 0x180 + node ID.

Step-by-step guide

  1. 1

    Scope the implementation

    List the services you will implement and those you will not, such as segmented SDO, SYNC-driven PDOs, emergency messages and LSS. Choose a node ID and bit rate (500 kbit/s). Define application objects: cell voltage, temperature and potentiometer throttle as read-only, and an LED set-point and a heartbeat period as writable. Write the test plan with the Python canopen library as master.

    Checkpoint: A scope document listing services, object dictionary entries and the test plan.

  2. 2

    Set up bus and tools

    Connect the USB-CAN adapter to the kit's screw terminal with twisted pair and check termination. Enable CAN, battery sense, potentiometer and LCD DIP switches. Configure bxCAN at 500 kbit/s with a filter that accepts the NMT, SYNC and SDO-request IDs and RPDO IDs for your node. On the PC, connect python-can and confirm frames flow both ways. Use a node ID between 1 and 127 and note it in every document.

    Checkpoint: Raw frames in both directions are seen in a python-can trace.

  3. 3

    Build the object dictionary

    Represent entries as a constant table: index, sub-index, data type, access (read-only, read-write), pointer to the variable and limits. Include 0x1000 device type, 0x1001 error register, 0x1017 producer heartbeat time, 0x1018 identity, the TPDO communication and mapping objects (0x1800, 0x1A00) and RPDO objects (0x1400, 0x1600). Write an EDS-style text description that the Python library can load. Keep variable data in RAM and the table itself in flash. Check each entry's size against its data type at start-up so a mistake in the table is found immediately, not during an SDO access.

    Checkpoint: A dictionary lookup function returns the correct entry for every defined index and sub-index in PC unit tests.

  4. 4

    Implement NMT and heartbeat

    On start-up send the boot-up message (COB-ID 0x700 + node ID, one data byte 0) and enter pre-operational. Handle NMT commands addressed to your node or to all nodes: start (0x01), stop (0x02), enter pre-operational (0x80), reset node (0x81) and reset communication (0x82). Send heartbeat with the current state at the period in 0x1017 and show the state on the LCD and LEDs.

    Checkpoint: The Python master moves the node through all states and the heartbeat reports each correctly.

  5. 5

    Implement expedited SDO

    Parse the command specifier of each SDO request: initiate upload, and expedited download of 1, 2 or 4 bytes. Look up the entry, check access rights and size, then respond with the correct command byte and data, or an abort with the right abort code for non-existent object, read-only write or value out of range. SDO must work in pre-operational and operational states.

    Checkpoint: The Python master reads and writes every object and receives the correct abort code for each invalid access.

  6. 6

    Implement PDOs and mapping

    Implement TPDO1 carrying cell voltage, temperature and throttle as mapped in 0x1A00, sent on change with an inhibit time or periodically with an event timer. Implement RPDO1 to set the LED value. Allow the mapping to be changed by SDO while pre-operational, and check total mapped length is no more than 8 bytes. Show the active mapping on a debug LCD screen and validate each mapping entry against the dictionary before accepting it, aborting the SDO write if the mapped object does not exist or cannot be mapped.

    Checkpoint: Remapping TPDO1 from the PC by SDO changes what the node sends after it is restarted to operational.

  7. 7

    Test against the master

    Write a Python test script with the canopen library that loads your EDS description, runs NMT transitions, reads and writes every object, remaps PDOs and checks PDO content against values shown on the LCD. Add negative tests for invalid accesses. Run the full suite after every firmware change. Log each test's frames so a failure can be inspected afterwards. Include tests for state rules, for example that PDOs stop in stopped state and that SDO is refused in stopped state, and run the suite at a raised bus load from a second traffic source.

    Checkpoint: The full automated test suite passes with no manual steps.

  8. 8

    Measure footprint and timing

    Record flash and RAM use from the linker map file. Measure SDO response time by toggling a GPIO from request reception to response transmission, or from PC timestamps with the adapter. Measure TPDO timing jitter. Then build a minimal CANopenNode configuration with the same objects on the same board and repeat the measurements as a baseline. Repeat the timing measurements at low and high bus load so the comparison covers realistic conditions.

    Checkpoint: A table comparing your node and the open-source stack for flash, RAM, SDO response time and PDO jitter.

  9. 9

    Analyse trade-offs

    Discuss where the lightweight node saves resources and what it gives up: missing services, less validation, no certification. Identify which missing feature would matter first in a real product, for example emergency messages for faults or SYNC for coordinated motion. Note anything the protocol requires that your scope left out. Estimate how much extra flash and RAM each missing service would add, using the open-source stack's modules as a guide, and say which subset you would ship for a small light-EV controller and why.

    Checkpoint: A written trade-off analysis based on your measurements.

  10. 10

    Document

    Document the architecture, object dictionary, services, test suite, measurements and limitations. Include the EDS-style file, firmware and Python scripts so the tests can be rerun. Describe how to add a new object and map it to a PDO, step by step. Add sequence diagrams for boot-up, an SDO read, an SDO abort and a PDO remap, and a table of every abort code the node can send with its meaning. Record known limitations and deviations from the protocol so a reader knows exactly what the node supports.

    Checkpoint: A repository where a reviewer can run the test suite against the node and reproduce the measurements.

Testing & validation

  • Automated test suite from a Python CANopen master.
  • Correct abort codes for invalid SDO accesses.
  • PDO mapping changes taking effect after state transitions.
  • Flash, RAM, SDO latency and PDO jitter against an open-source stack.

Expected results

A working lightweight CANopen node with NMT, heartbeat, expedited SDO and mapped PDOs, an automated test suite and a measured comparison with an open-source stack.

Research angle for your M.Tech thesis

Research questions: what is the minimum resource cost of a useful CANopen subset on a small Cortex-M3, and how do response times and jitter compare with a general-purpose open-source stack? Metrics: flash and RAM, SDO response time, PDO jitter, CPU load at bus loads from low to high. Baseline: CANopenNode configured with the same object dictionary. A quantified comparison with a clear test method can support an M.Tech thesis chapter; a well-documented benchmark is the most useful contribution.

Take it further

  • Add segmented SDO for longer objects such as a device name.
  • Add emergency messages driven by battery limits.
  • Add SYNC-triggered PDOs and measure synchronisation jitter.
  • Add a second kit as another CANopen node and coordinate both from the master.

Viva and review questions

What is the difference between SDO and PDO?

SDO is a confirmed request–response service for reading or writing individual dictionary entries; PDO carries mapped real-time data without per-message protocol overhead.

Why can PDO mapping only change in pre-operational state?

Changing the mapping while PDOs are flowing could make receivers misinterpret data, so the configuration is changed while process data is not being exchanged.

How does a master know a node has started?

The node sends a boot-up message on 0x700 + node ID with data 0, then heartbeats reporting its state.

Related EV projects

All 100 EV projects