Skip to content
0x53APublic

About

Yet Another CAN Open liBrary

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

This is Yet Another CanOpen liBrary for Rust. It was developed with the goal of providing full functionality, including SDO server and client, for both embedded devices (no_std) and desktop platforms.

Implementation was heavily LLM-assisted, but verified on real hardware, using both embassy on STM32 and ESP32, and Linux with a USB-CAN adapter.

The OD (object dictionary) can either be imported from an EDS file, or specified in-code through a dsl:

object_dictionary! {
    pub struct NodeOd {
        [0x1000] device_type: u32 = 0x0000_0191, ro;
        [0x1001] error_register: u8 = 0x00, ro;
        [0x1018] identity: record {
            [1] vendor_id: u32 = 0x0000_CAFE, ro;
            [2] product_code: u32 = 0x0001, ro;
            [3] revision: u32 = 0x0001_0000, ro;
            [4] serial_number: u32 = 0x0000_0001, ro;
        };
        // CiA 401-style process I/O. Perspective is the physical process,
        // not the bus: an "input" is read from the world and published on
        // the bus (TPDO); an "output" is commanded from the bus (RPDO)
        // and driven into the world.
        [0x6000] inputs: record {
            [1] button: u8 = 0, ro, pdo;      // PB7 (0=released, 1=pressed)
        };
        [0x6200] outputs: record {
            [1] led: u8 = 0, rw, pdo;         // PB8 (0=off, 1=on)
        };

        // Bus-loopback test object. It has no physical-world meaning, so it
        // lives in the manufacturer-specific area (0x2000..=0x5FFF) instead
        // of the device-profile area. Names are from the device's view:
        // echo_in arrives from the bus, echo_out is sent back.
        [0x2000] echo: record {
            [1] echo_in: u16 = 0, rw, pdo;    // written by remote
            [2] echo_out: u16 = 0, ro, pdo;   // node mirrors echo_in here
        };

        // TPDO1: data this node sends (0x181 for node 1).
        // - event_driven: send on change, not tied to SYNC. Other options:
        //   sync_acyclic, sync_cyclic(N), or a raw CiA 301 value (e.g. 255).
        // - inhibit_time: minimum spacing between sends. event_timer: periodic
        //   fallback — send even if nothing changed (omit to disable). Both
        //   take unit suffixes (50ms, 0.1s, 500us) or raw CiA 301 values.
        // - Fields are packed into one CAN frame: [button (1 byte) | echo_out (2 bytes)]
        tpdo[1](transmission_type = event_driven, inhibit_time = 50ms, event_timer = 1s) {
            button,
            echo_out,
        };

        // RPDO1: data this node receives (0x201 for node 1).
        // - event_driven: apply values to the OD immediately on arrival. With
        //   sync_acyclic, values would be buffered until the next SYNC pulse
        //   (useful for coordinated updates).
        // - Fields are unpacked from the CAN frame: [led (1 byte) | echo_in (2 bytes)]
        // - Writing to these emits a typed NodeOdChange, which wakes main via
        //   EVENT_SIGNAL.
        rpdo[1](transmission_type = event_driven) {
            led,
            echo_in,
        };
    }
}

PDOs, SDOs, NMT, heartbeat, EMCY, and SYNC are implemented. The inline, reactive, and scan application models are agreed but their public APIs are not implemented yet; the current event-queue APIs are transitional. See _Tasks/application-models.md for the design and implementation status.

Before using the stack for command or safety traffic, read the canonical _Tasks/known-issues.md defect index and the _Tasks/conformance-matrix.md. In particular, RPDO acceptance is not yet transactional for malformed process images.

Nevertheless, I am already using it for a few private projects, both implementating CANopen nodes, or interacting with existing hardware like DS402 motor drivers.

About

Yet Another CAN Open liBrary

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages