Xyntos | Logo

Overview  |  Quick Start  |  Samples  |  Architecture  |  CoSlave  |  CoMaster  |  Listeners  |  Object Dictionary  |  Configuration  |  Eds2Od


CANoopEn Architecture

CANoopEn separates the CANopen protocol logic from everything that is specific to your device: the hardware, the operating system, and the application data. The stack owns the protocols; your application owns the data (object dictionary), the CAN driver, and the reaction to events.

┌───────────────────────────────────────────────────────────────┐
│ Your application                                              │
│                                                               │
│  MyDevice ─ derives CoSlave or CoMaster                       │
│           ─ implements ICoListener            (events)        │
│           ─ implements ICoObjectDictionaryListener (OD writes)│
│           ─ implements ICoSendHandlerListener (CAN transmit)  │
│  MyDeviceOd ─ derives CoObjectDictionary      (your data)     │
└───────────────┬───────────────────────────────▲───────────────┘
   Proceed(msg) │ Proceed()          callbacks  │ SendCanMessage
┌───────────────▼───────────────────────────────┴───────────────┐
│ CANoopEn stack                                                │
│  CoSlave / CoMaster ── protocol layer (NMT, heartbeat, PDO,   │
│  MPDO, SDO server/client, SYNC, TIME, EMCY, LSS, indicators)  │
│  Object dictionary access · COB-ID profile (CiA 301 / 417)    │
│  System layer: CoEvent, CoSemaphore (ported per platform)     │
└───────────────────────────────────────────────────────────────┘

What the application provides

Building a device with CANoopEn means providing five things. The samples in canoopendemo show all of them for bare-metal and FreeRTOS.

  1. A device class deriving from CoSlave or CoMaster and implementing the three listener interfaces. One class covers everything; the constructor wires the parts together:

    MyDevice::MyDevice() :
        CoSlave(*this, _objectDictionary, *this, NodeId, _cobIdCia301),
        _objectDictionary(*this)
    { }
  2. An object dictionary class deriving from CoObjectDictionary. Its members are typed entry objects (CoOdEntryUnsigned8, CoOdEntryUnsigned32, …), one per index/sub-index, registered with AddEntry. The class follows a regular pattern and can be written by hand or generated from the device’s EDS file.

  3. The CAN transmit hookSendCanMessage(const CoCanMessage&), the single point where the stack hands a frame to your CAN driver.

  4. The two Proceed calls — see the execution model below.

  5. The system layer portCoEvent and CoSemaphore are declared by the stack but implemented per platform (a handful of methods each). Ready-made implementations for bare-metal and FreeRTOS ship with the samples; copy the one that matches your platform. Details in Configuration & porting.

Execution model

The stack has no thread of its own and never blocks the caller (with one exception, below). It is driven by exactly two calls:

  • Proceed(const CoCanMessage&) — call for every received CAN frame. The stack dispatches the frame to the protocol that owns its COB-ID.
  • Proceed() — call periodically, every CoSettings::CoCycleTime milliseconds (default 10 ms). This runs all timed functionality: heartbeat production and timeout monitoring, SDO timeouts, indicator flash patterns, event-timer PDOs.

On bare-metal, both calls live in the main loop (poll the CAN driver, check a cycle timer). Under an OS such as FreeRTOS, a receive task feeds Proceed(msg) while a scheduler task calls Proceed() cyclically. Internal state is guarded with CoSemaphore, so the two calls may come from different tasks.

The exception: CoMaster::ReadSdo/WriteSdo are synchronous — they suspend the calling task (via CoEvent) until the SDO response arrives or CoSettings::SdoTimeoutTime elapses. This gives master application code a natural, sequential style, and is the reason the master samples run under FreeRTOS rather than bare-metal. Call them from an application task, never from the task that runs Proceed.

Memory model

CANoopEn performs no dynamic memory allocation. All tables and buffers are statically sized through CoSettings (number of OD entries, remote nodes, SDO channels, PDO mappings, EMCY errors, string lengths). Object dictionary entries and mapping parameters are member objects of your OD class — the stack stores references and never copies or owns them. What you see in the map file is what you get at runtime.

Scaling the stack

Every service can be compiled out with a CoConfiguration feature switch (e.g. -DCO_ENABLE_LSS=0). Disabling a service removes its code from the link and removes its callbacks from ICoListener, so a minimal device implements only the callbacks that remain. Dependencies are resolved automatically (disabling PDO disables MPDO, and so on). See Configuration & porting.

COB-ID profiles

The mapping from protocol to CAN identifier is behind the small ICoCobId interface. CoCobIdCia301 implements the CiA 301 pre-defined connection set (the standard mapping virtually all CANopen devices use). CoCobIdCia417 implements the CiA 417 lift application profile mapping instead — the profile becomes a constructor argument, not a fork of the stack. See Configuration & porting.

Roles: slave and master

  • CoSlave is a CANopen device node: it answers SDO requests, produces/consumes PDOs, heartbeats, EMCYs — and reacts to NMT commands from a master.
  • CoMaster additionally manages remote nodes: it sends NMT commands, monitors heartbeats per node, reads/writes remote object dictionaries as SDO client, and can commission nodes via LSS. Each remote node is described by a CoNode with its own object dictionary image.

Both share the same constructor signature, listener interfaces and execution model, so a device can be developed as a slave and grown into a master without restructuring.

XYNTOS_Logo_mit_claim_weiss