CANopen (CiA 301) Client APIs
The CanopenClient class provides a lightweight, standards-based CANopen
(CiA 301) client / master for talking to a CANopen server node - for example a
Basicmicro MCP236/MCP266 motor controller - over a classic CAN 2.0 bus. It
implements NMT master commands, heartbeat / boot-up consumption, an SDO client
(expedited upload/download of 1/2/4-byte objects with typed accessors, plus
segmented upload for strings such as the manufacturer device name 0x1008),
RPDO transmit / TPDO reception dispatch, and SYNC transmission.
The Ds402Drive class layers the CiA 402 (DS402) drive profile on top of a
CanopenClient: it decodes the statusword into the standard power-drive-system
states, walks the enable sequence (Shutdown -> Switch On -> Enable Operation)
with statusword polling and timeout, supports quick stop and fault reset, mode
selection (profile velocity / profile position / homing) verified via the modes
display object, and provides motion helpers such as set_target_velocity()
and set_target_position() (including the profile-position new-set-point
controlword handshake).
The client is transport-agnostic: it transmits by invoking a user-provided
send function with a plain espp::detail::CanFrame, and the application
feeds received frames to process_frame(). The CanFrame struct mirrors
espp::Twai::Message field-for-field, so wiring it to the Twai component
is a trivial conversion (as the example shows) - but any CAN transport works.
SDO transactions are blocking with a configurable timeout, so
process_frame() must be called from a different task than the one
performing SDO transfers; with Twai this is automatically the case since its
on_receive callback runs in the Twai receive task.
API Reference
Header File
Classes
-
class CanopenClient : public espp::BaseComponent
A lightweight CANopen (CiA 301) client / master for a single server node.
Implements the master-side services needed to drive a typical CANopen device (e.g. a Basicmicro MCP236/MCP266 motor controller):
NMT master commands (start / stop / pre-operational / reset)
Heartbeat & boot-up consumption (cached state + optional callback)
SDO client: expedited upload/download of 1/2/4-byte objects, and segmented upload (for strings such as device name 0x1008)
PDO helpers: RPDO transmit and per-COB-ID TPDO reception dispatch
SYNC transmission
The client is transport-agnostic: it transmits by calling the configured
sendfunction with a espp::detail::CanFrame, and the application feeds every received frame to process_frame() (e.g. from the espp::Twaion_receivecallback — the frame layouts match field-for-field).SDO transactions are blocking: the calling task sends the request and waits on a condition variable until process_frame() delivers the matching response (COB-ID 0x580 + node id) or the configured timeout expires. Note: process_frame() must therefore be called from a different task than the one performing SDO reads/writes; with espp::Twai this is automatically the case since
on_receiveruns in the Twai receive task. One SDO transaction may be in flight per client at a time (serialized internally by a mutex); NMT / SYNC / PDO helpers are non-blocking and unserialized.CANopen Client Example
// The CANopen node id of the device we want to talk to (e.g. a Basicmicro // MCP236/MCP266 motor controller). Change to match your device. static constexpr uint8_t node_id = 1; // Forward-declared handle so the Twai on_receive callback (registered at // Twai construction) can feed frames to the client we construct just below. static espp::CanopenClient *client_ptr = nullptr; // Bring up the TWAI (CAN 2.0) peripheral. NOTE: talking to a real CANopen // device requires Mode::NORMAL with a 3.3V CAN transceiver (e.g. SN65HVD230) // wired to the tx/rx GPIOs, a properly terminated bus, and a matching // baudrate (Basicmicro MCP2xx default is 250 kbit/s). // // The on_receive callback runs in the Twai receive task, i.e. NOT in the // task performing the (blocking) SDO transactions below -- which is exactly // what CanopenClient::process_frame() requires. The CanFrame struct mirrors // espp::Twai::Message field-for-field, so conversion is trivial. // NOTE: twai and client are function-local STATICS: the Twai receive task // and the client's send lambda (which captures &twai) reference them, and // app_main() has early-return error paths -- static storage guarantees they // outlive every callback regardless of how app_main() exits. static espp::Twai twai({ .tx_gpio = 5, // GPIO5 (change to match your board / transceiver) .rx_gpio = 4, // GPIO4 (change to match your board / transceiver) .baudrate = 250000, .mode = espp::Twai::Mode::NORMAL, .tx_queue_depth = 5, .on_receive = [](const espp::Twai::Message &msg) { if (client_ptr) { client_ptr->process_frame(espp::CanopenClient::CanFrame{ .id = msg.id, .extended = msg.extended, .rtr = msg.rtr, .dlc = msg.dlc, .data = msg.data, }); } }, .log_level = espp::Logger::Verbosity::INFO, }); // The CANopen client is transport-agnostic: give it a send function which // transmits an espp::detail::CanFrame (here: over TWAI). The CanFrame struct // mirrors espp::Twai::Message field-for-field, so conversion is trivial. static espp::CanopenClient client({ .node_id = node_id, // captureless: twai has static storage duration and is referenced // directly (capturing a static is ill-formed under -Werror) .send = [](const espp::CanopenClient::CanFrame &frame) { espp::Twai::Message msg{ .id = frame.id, .extended = frame.extended, .rtr = frame.rtr, .dlc = frame.dlc, .data = frame.data, }; std::error_code tx_ec; return twai.transmit(msg, tx_ec); }, .sdo_timeout = 100ms, .on_heartbeat = // captureless: logger has static storage duration (see above) [](uint8_t hb_node, espp::CanopenClient::NmtState state) { logger.info("Heartbeat from node {}: NMT state {}", hb_node, static_cast<int>(state)); }, .log_level = espp::Logger::Verbosity::INFO, }); client_ptr = &client; std::error_code ec; if (!twai.initialize(ec)) { logger.error("Failed to initialize TWAI: {}", ec.message()); return; } // NMT: put the node into Operational so its PDOs (if any) are active. if (!client.nmt_start(ec)) { logger.error("Failed to send NMT start: {}", ec.message()); return; } logger.info("Sent NMT start to node {}", node_id); std::this_thread::sleep_for(100ms); // SDO: read the standard identification objects. espp::Ds402Drive drive( client, {.state_timeout = 1s, .poll_period = 20ms, .log_level = espp::Logger::Verbosity::INFO}); auto device_type = drive.get_device_type(ec); if (ec) { logger.error("Failed to read device type (0x1000): {} -- is the node on the bus?", ec.message()); return; } logger.info("Device type (0x1000): 0x{:08X}", device_type); // device profile number is in the lower 16 bits; 402 => a CiA 402 drive const bool is_ds402 = (device_type & 0xFFFF) == 402; logger.info("Vendor id (0x1018:1): 0x{:08X}", drive.get_vendor_id(ec)); logger.info("Product code (0x1018:2): 0x{:08X}", drive.get_product_code(ec)); logger.info("Revision (0x1018:3): 0x{:08X}", drive.get_revision_number(ec)); logger.info("Serial number (0x1018:4): 0x{:08X}", drive.get_serial_number(ec)); // manufacturer device name (0x1008) is a string -> segmented SDO upload auto name = drive.get_device_name(ec); if (!ec) { logger.info("Device name (0x1008): '{}'", name); } if (!is_ds402) { logger.warn("Device does not report the CiA 402 profile; skipping motion demo"); } else { // DS402: profile velocity mode, enable, gentle ramp, stop, disable. if (auto state = drive.get_state(ec); !ec) { logger.info("Drive state: {}", espp::detail::ds402::state_to_string(state)); if (state == espp::Ds402Drive::State::Fault) { logger.info("Drive is in Fault; attempting fault reset"); if (!drive.fault_reset(ec)) { logger.error("Fault reset failed: {}", ec.message()); return; } } } if (!drive.set_mode(espp::Ds402Drive::OperatingMode::ProfileVelocity, ec)) { logger.error("Failed to set profile velocity mode: {}", ec.message()); return; } // conservative profile accel / decel (device units) drive.set_profile_acceleration(1000, ec); drive.set_profile_deceleration(1000, ec); if (!drive.enable_operation(ec)) { logger.error("Failed to enable operation: {}", ec.message()); return; } // gentle velocity ramp up and back down static constexpr int32_t max_velocity = 500; // device units, keep it gentle static constexpr int32_t step = 100; for (int32_t v = step; v <= max_velocity; v += step) { drive.set_target_velocity(v, ec); std::this_thread::sleep_for(500ms); logger.info("target={:4}, actual={:4}", v, drive.get_velocity_actual(ec)); } for (int32_t v = max_velocity - step; v >= 0; v -= step) { drive.set_target_velocity(v, ec); std::this_thread::sleep_for(500ms); logger.info("target={:4}, actual={:4}", v, drive.get_velocity_actual(ec)); } // stop and disable the power stage drive.set_target_velocity(0, ec); if (!drive.disable(ec)) { logger.error("Failed to disable drive: {}", ec.message()); } logger.info("Motion demo complete"); }
NMT master / SYNC / PDO (non-blocking)
-
inline bool send_nmt(NmtCommand command, uint8_t target_node_id, std::error_code &ec)
Send an NMT master command.
- Parameters:
command – The command specifier.
target_node_id – Target node id, or 0 to address all nodes.
ec – Set on transmit failure.
- Returns:
True on success.
-
inline bool nmt_start(std::error_code &ec)
NMT-start the configured server node.
- Parameters:
ec – Set on failure.
- Returns:
True on success.
-
inline bool nmt_stop(std::error_code &ec)
NMT-stop the configured server node.
- Parameters:
ec – Set on failure.
- Returns:
True on success.
-
inline bool nmt_pre_operational(std::error_code &ec)
Put the configured server node into pre-operational.
- Parameters:
ec – Set on failure.
- Returns:
True on success.
-
inline bool nmt_reset_node(std::error_code &ec)
Reset the configured server node (application reset).
- Parameters:
ec – Set on failure.
- Returns:
True on success.
-
inline bool nmt_reset_communication(std::error_code &ec)
Reset communication of the configured server node.
- Parameters:
ec – Set on failure.
- Returns:
True on success.
-
inline bool send_sync(std::error_code &ec)
Send a SYNC frame (COB-ID 0x080).
- Parameters:
ec – Set on transmit failure.
- Returns:
True on success.
-
inline bool send_rpdo(uint32_t cob_id, std::span<const uint8_t> data, std::error_code &ec)
Transmit an RPDO (build + send a data frame on
cob_id).- Parameters:
cob_id – COB-ID to transmit on (e.g. 0x200 + node id for RPDO1).
data – Packed application data (up to 8 bytes).
ec – Set on transmit failure.
- Returns:
True on success.
-
inline void register_tpdo_callback(uint32_t cob_id, pdo_callback_fn callback)
Register a callback for received frames on a TPDO COB-ID.
- Parameters:
cob_id – COB-ID to match (e.g. 0x180 + node id for TPDO1).
callback – Invoked from the process_frame() context; replaces any previous callback for this COB-ID.
-
inline void unregister_tpdo_callback(uint32_t cob_id)
Remove the callback registered for a TPDO COB-ID.
- Parameters:
cob_id – The COB-ID whose callback should be removed.
Heartbeat state
SDO client (blocking, expedited)
-
inline bool sdo_download(uint16_t index, uint8_t subindex, std::span<const uint8_t> data, std::error_code &ec)
Write (SDO expedited download) raw little-endian object data.
- Parameters:
index – Object dictionary index.
subindex – Object dictionary subindex.
data – Object data, little-endian, 1, 2, or 4 bytes.
ec – Set on transmit failure, timeout, or SDO abort.
- Returns:
True on success.
-
inline size_t sdo_upload(uint16_t index, uint8_t subindex, std::span<uint8_t> out, std::error_code &ec)
Read (SDO expedited upload) raw little-endian object data.
- Parameters:
index – Object dictionary index.
subindex – Object dictionary subindex.
out – Destination for the object data (little-endian).
ec – Set on transmit failure, timeout, SDO abort, or if the object is larger than
out(use read_string() for segmented transfers).
- Returns:
Number of bytes read (> 0), or 0 on error.
-
inline std::string read_string(uint16_t index, uint8_t subindex, std::error_code &ec)
Read a string object via SDO segmented (or expedited) upload.
Handles the toggle-bit protocol for multi-segment transfers; used e.g. for the manufacturer device name (0x1008).
- Parameters:
index – Object dictionary index.
subindex – Object dictionary subindex.
ec – Set on transmit failure, timeout, SDO abort, or toggle error.
- Returns:
The string data, or an empty string on error.
-
inline bool write_u8(uint16_t index, uint8_t subindex, uint8_t value, std::error_code &ec)
Write an unsigned 8-bit object.
- Parameters:
index – Object index.
subindex – Object subindex.
value – Value to write.
ec – Set on failure.
- Returns:
True on success.
-
inline bool write_u16(uint16_t index, uint8_t subindex, uint16_t value, std::error_code &ec)
Write an unsigned 16-bit object.
- Parameters:
index – Object index.
subindex – Object subindex.
value – Value to write.
ec – Set on failure.
- Returns:
True on success.
-
inline bool write_u32(uint16_t index, uint8_t subindex, uint32_t value, std::error_code &ec)
Write an unsigned 32-bit object.
- Parameters:
index – Object index.
subindex – Object subindex.
value – Value to write.
ec – Set on failure.
- Returns:
True on success.
-
inline bool write_i8(uint16_t index, uint8_t subindex, int8_t value, std::error_code &ec)
Write a signed 8-bit object.
- Parameters:
index – Object index.
subindex – Object subindex.
value – Value to write.
ec – Set on failure.
- Returns:
True on success.
-
inline bool write_i16(uint16_t index, uint8_t subindex, int16_t value, std::error_code &ec)
Write a signed 16-bit object.
- Parameters:
index – Object index.
subindex – Object subindex.
value – Value to write.
ec – Set on failure.
- Returns:
True on success.
-
inline bool write_i32(uint16_t index, uint8_t subindex, int32_t value, std::error_code &ec)
Write a signed 32-bit object.
- Parameters:
index – Object index.
subindex – Object subindex.
value – Value to write.
ec – Set on failure.
- Returns:
True on success.
-
inline uint8_t read_u8(uint16_t index, uint8_t subindex, std::error_code &ec)
Read an unsigned 8-bit object.
- Parameters:
index – Object index.
subindex – Object subindex.
ec – Set on failure.
- Returns:
The value (0 on error).
-
inline uint16_t read_u16(uint16_t index, uint8_t subindex, std::error_code &ec)
Read an unsigned 16-bit object.
- Parameters:
index – Object index.
subindex – Object subindex.
ec – Set on failure.
- Returns:
The value (0 on error).
-
inline uint32_t read_u32(uint16_t index, uint8_t subindex, std::error_code &ec)
Read an unsigned 32-bit object.
- Parameters:
index – Object index.
subindex – Object subindex.
ec – Set on failure.
- Returns:
The value (0 on error).
-
inline int8_t read_i8(uint16_t index, uint8_t subindex, std::error_code &ec)
Read a signed 8-bit object.
- Parameters:
index – Object index.
subindex – Object subindex.
ec – Set on failure.
- Returns:
The value (0 on error).
-
inline int16_t read_i16(uint16_t index, uint8_t subindex, std::error_code &ec)
Read a signed 16-bit object.
- Parameters:
index – Object index.
subindex – Object subindex.
ec – Set on failure.
- Returns:
The value (0 on error).
-
inline int32_t read_i32(uint16_t index, uint8_t subindex, std::error_code &ec)
Read a signed 32-bit object.
- Parameters:
index – Object index.
subindex – Object subindex.
ec – Set on failure.
- Returns:
The value (0 on error).
-
inline uint32_t last_abort_code() const
The abort code from the most recent SDO abort response (0 if none).
Public Types
-
using CanFrame = detail::CanFrame
Transport-agnostic CAN frame type.
-
using NmtCommand = detail::canopen::NmtCommand
NMT master command specifier.
-
using NmtState = detail::canopen::NmtState
NMT state (heartbeat / boot-up).
-
typedef std::function<bool(const CanFrame &frame)> send_fn
Function used to transmit a frame on the bus.
Should return true if the frame was (queued to be) sent.
-
typedef std::function<void(uint8_t node_id, NmtState state)> heartbeat_callback_fn
Callback invoked (from the process_frame() context) for every heartbeat / boot-up frame received from any node.
-
typedef std::function<void(const CanFrame &frame)> pdo_callback_fn
Callback invoked (from the process_frame() context) for a received frame on a registered TPDO COB-ID.
Public Functions
-
inline explicit CanopenClient(const Config &config)
Create a CANopen client.
- Parameters:
config – The configuration.
-
inline uint8_t node_id() const
The configured server node id.
-
inline void process_frame(const CanFrame &frame)
Feed a received CAN frame to the client.
Call this for every frame received from the bus (e.g. from the espp::Twai
on_receivecallback). Dispatches SDO responses to the waiting transaction, caches heartbeat states (invoking the optional heartbeat callback), and dispatches registered TPDO callbacks. Must not be called from the task performing SDO transactions (see class description).- Parameters:
frame – The received frame.
-
inline const std::string &get_name() const
Get the name of the component
Note
This is the tag of the logger
- Returns:
A const reference to the name of the component
-
inline void set_log_tag(const std::string_view &tag)
Set the tag for the logger
- Parameters:
tag – The tag to use for the logger
-
inline espp::Logger::Verbosity get_log_level() const
Get the log level for the logger
See also
See also
- Returns:
The verbosity level of the logger
-
inline void set_log_level(espp::Logger::Verbosity level)
Set the log level for the logger
See also
See also
- Parameters:
level – The verbosity level to use for the logger
-
inline void set_log_verbosity(espp::Logger::Verbosity level)
Set the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls set_log_level
- Parameters:
level – The verbosity level to use for the logger
-
inline espp::Logger::Verbosity get_log_verbosity() const
Get the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls get_log_level
- Returns:
The verbosity level of the logger
-
inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
Set the rate limit for the logger
See also
Note
Only calls to the logger that have _rate_limit suffix will be rate limited
- Parameters:
rate_limit – The rate limit to use for the logger
-
struct Config
Configuration for the CanopenClient.
Public Members
-
uint8_t node_id
Server node id (1-127) this client talks to.
-
std::chrono::milliseconds sdo_timeout = {100}
Timeout for one SDO round-trip.
-
heartbeat_callback_fn on_heartbeat = {nullptr}
Optional heartbeat / boot-up callback.
-
uint8_t node_id
Header File
Classes
-
class Ds402Drive : public espp::BaseComponent
CiA 402 (DS402) drive-profile helper, layered on a CanopenClient.
Wraps the standard CiA 402 objects (controlword 0x6040, statusword 0x6041, modes of operation 0x6060/0x6061, target/actual velocity & position, profile velocity/acceleration/deceleration) and the power-drive-system state machine: decode the statusword into a State, walk the enable sequence (Shutdown -> Switch On -> Enable Operation) with statusword polling and timeout, quick-stop, and fault reset (rising edge of controlword bit 7). Supports the profile velocity (pv), profile position (pp), and homing (hm) modes, including the new-set-point handshake (controlword bits 4/5/6) for profile position moves.
All communication goes through the referenced CanopenClient’s SDO channel, so the same threading rules apply (calls block, and frames must be delivered to the client from another task).
DS402 Drive Example
// The CANopen node id of the device we want to talk to (e.g. a Basicmicro // MCP236/MCP266 motor controller). Change to match your device. static constexpr uint8_t node_id = 1; // Forward-declared handle so the Twai on_receive callback (registered at // Twai construction) can feed frames to the client we construct just below. static espp::CanopenClient *client_ptr = nullptr; // Bring up the TWAI (CAN 2.0) peripheral. NOTE: talking to a real CANopen // device requires Mode::NORMAL with a 3.3V CAN transceiver (e.g. SN65HVD230) // wired to the tx/rx GPIOs, a properly terminated bus, and a matching // baudrate (Basicmicro MCP2xx default is 250 kbit/s). // // The on_receive callback runs in the Twai receive task, i.e. NOT in the // task performing the (blocking) SDO transactions below -- which is exactly // what CanopenClient::process_frame() requires. The CanFrame struct mirrors // espp::Twai::Message field-for-field, so conversion is trivial. // NOTE: twai and client are function-local STATICS: the Twai receive task // and the client's send lambda (which captures &twai) reference them, and // app_main() has early-return error paths -- static storage guarantees they // outlive every callback regardless of how app_main() exits. static espp::Twai twai({ .tx_gpio = 5, // GPIO5 (change to match your board / transceiver) .rx_gpio = 4, // GPIO4 (change to match your board / transceiver) .baudrate = 250000, .mode = espp::Twai::Mode::NORMAL, .tx_queue_depth = 5, .on_receive = [](const espp::Twai::Message &msg) { if (client_ptr) { client_ptr->process_frame(espp::CanopenClient::CanFrame{ .id = msg.id, .extended = msg.extended, .rtr = msg.rtr, .dlc = msg.dlc, .data = msg.data, }); } }, .log_level = espp::Logger::Verbosity::INFO, }); // The CANopen client is transport-agnostic: give it a send function which // transmits an espp::detail::CanFrame (here: over TWAI). The CanFrame struct // mirrors espp::Twai::Message field-for-field, so conversion is trivial. static espp::CanopenClient client({ .node_id = node_id, // captureless: twai has static storage duration and is referenced // directly (capturing a static is ill-formed under -Werror) .send = [](const espp::CanopenClient::CanFrame &frame) { espp::Twai::Message msg{ .id = frame.id, .extended = frame.extended, .rtr = frame.rtr, .dlc = frame.dlc, .data = frame.data, }; std::error_code tx_ec; return twai.transmit(msg, tx_ec); }, .sdo_timeout = 100ms, .on_heartbeat = // captureless: logger has static storage duration (see above) [](uint8_t hb_node, espp::CanopenClient::NmtState state) { logger.info("Heartbeat from node {}: NMT state {}", hb_node, static_cast<int>(state)); }, .log_level = espp::Logger::Verbosity::INFO, }); client_ptr = &client; std::error_code ec; if (!twai.initialize(ec)) { logger.error("Failed to initialize TWAI: {}", ec.message()); return; } // NMT: put the node into Operational so its PDOs (if any) are active. if (!client.nmt_start(ec)) { logger.error("Failed to send NMT start: {}", ec.message()); return; } logger.info("Sent NMT start to node {}", node_id); std::this_thread::sleep_for(100ms); // SDO: read the standard identification objects. espp::Ds402Drive drive( client, {.state_timeout = 1s, .poll_period = 20ms, .log_level = espp::Logger::Verbosity::INFO}); auto device_type = drive.get_device_type(ec); if (ec) { logger.error("Failed to read device type (0x1000): {} -- is the node on the bus?", ec.message()); return; } logger.info("Device type (0x1000): 0x{:08X}", device_type); // device profile number is in the lower 16 bits; 402 => a CiA 402 drive const bool is_ds402 = (device_type & 0xFFFF) == 402; logger.info("Vendor id (0x1018:1): 0x{:08X}", drive.get_vendor_id(ec)); logger.info("Product code (0x1018:2): 0x{:08X}", drive.get_product_code(ec)); logger.info("Revision (0x1018:3): 0x{:08X}", drive.get_revision_number(ec)); logger.info("Serial number (0x1018:4): 0x{:08X}", drive.get_serial_number(ec)); // manufacturer device name (0x1008) is a string -> segmented SDO upload auto name = drive.get_device_name(ec); if (!ec) { logger.info("Device name (0x1008): '{}'", name); } if (!is_ds402) { logger.warn("Device does not report the CiA 402 profile; skipping motion demo"); } else { // DS402: profile velocity mode, enable, gentle ramp, stop, disable. if (auto state = drive.get_state(ec); !ec) { logger.info("Drive state: {}", espp::detail::ds402::state_to_string(state)); if (state == espp::Ds402Drive::State::Fault) { logger.info("Drive is in Fault; attempting fault reset"); if (!drive.fault_reset(ec)) { logger.error("Fault reset failed: {}", ec.message()); return; } } } if (!drive.set_mode(espp::Ds402Drive::OperatingMode::ProfileVelocity, ec)) { logger.error("Failed to set profile velocity mode: {}", ec.message()); return; } // conservative profile accel / decel (device units) drive.set_profile_acceleration(1000, ec); drive.set_profile_deceleration(1000, ec); if (!drive.enable_operation(ec)) { logger.error("Failed to enable operation: {}", ec.message()); return; } // gentle velocity ramp up and back down static constexpr int32_t max_velocity = 500; // device units, keep it gentle static constexpr int32_t step = 100; for (int32_t v = step; v <= max_velocity; v += step) { drive.set_target_velocity(v, ec); std::this_thread::sleep_for(500ms); logger.info("target={:4}, actual={:4}", v, drive.get_velocity_actual(ec)); } for (int32_t v = max_velocity - step; v >= 0; v -= step) { drive.set_target_velocity(v, ec); std::this_thread::sleep_for(500ms); logger.info("target={:4}, actual={:4}", v, drive.get_velocity_actual(ec)); } // stop and disable the power stage drive.set_target_velocity(0, ec); if (!drive.disable(ec)) { logger.error("Failed to disable drive: {}", ec.message()); } logger.info("Motion demo complete"); }
Standard object accessors
-
inline uint32_t get_device_type(std::error_code &ec)
Read the device type (object 0x1000).
- Parameters:
ec – Set on failure.
- Returns:
The value.
-
inline uint8_t get_error_register(std::error_code &ec)
Read the error register (object 0x1001).
- Parameters:
ec – Set on failure.
- Returns:
The value.
-
inline std::string get_device_name(std::error_code &ec)
Read the manufacturer device name (object 0x1008, segmented upload).
- Parameters:
ec – Set on failure.
- Returns:
The device name string.
-
inline uint32_t get_vendor_id(std::error_code &ec)
Read the identity vendor id (object 0x1018:1).
- Parameters:
ec – Set on failure.
- Returns:
The value.
-
inline uint32_t get_product_code(std::error_code &ec)
Read the identity product code (object 0x1018:2).
- Parameters:
ec – Set on failure.
- Returns:
The value.
-
inline uint32_t get_revision_number(std::error_code &ec)
Read the identity revision number (object 0x1018:3).
- Parameters:
ec – Set on failure.
- Returns:
The value.
-
inline uint32_t get_serial_number(std::error_code &ec)
Read the identity serial number (object 0x1018:4).
- Parameters:
ec – Set on failure.
- Returns:
The value.
-
inline bool set_controlword(uint16_t controlword, std::error_code &ec)
Write the controlword (object 0x6040).
- Parameters:
controlword – Value to write.
ec – Set on failure.
- Returns:
True on success.
-
inline uint16_t get_statusword(std::error_code &ec)
Read the statusword (object 0x6041).
- Parameters:
ec – Set on failure.
- Returns:
The value.
-
inline int32_t get_velocity_actual(std::error_code &ec)
Read the velocity actual value (object 0x606C).
- Parameters:
ec – Set on failure.
- Returns:
The value.
-
inline int32_t get_position_actual(std::error_code &ec)
Read the position actual value (object 0x6064).
- Parameters:
ec – Set on failure.
- Returns:
The value.
-
inline bool set_profile_velocity(uint32_t velocity, std::error_code &ec)
Write the profile velocity (object 0x6081).
- Parameters:
velocity – Value to write.
ec – Set on failure.
- Returns:
True on success.
-
inline bool set_profile_acceleration(uint32_t acceleration, std::error_code &ec)
Write the profile acceleration (object 0x6083).
- Parameters:
acceleration – Value to write.
ec – Set on failure.
- Returns:
True on success.
-
inline bool set_profile_deceleration(uint32_t deceleration, std::error_code &ec)
Write the profile deceleration (object 0x6084).
- Parameters:
deceleration – Value to write.
ec – Set on failure.
- Returns:
True on success.
State machine
-
inline State get_state(std::error_code &ec)
Read the statusword and decode the CiA 402 drive state.
- Parameters:
ec – Set on failure (returns State::Unknown).
- Returns:
The decoded state.
-
inline bool enable_operation(std::error_code &ec)
Walk the drive to Operation Enabled.
Issues Shutdown (0x0006) -> Switch On (0x0007) -> Enable Operation (0x000F), polling the statusword after each command until the corresponding state is reached or the configured state_timeout expires. If the drive is in Fault, call fault_reset() first.
- Parameters:
ec – Set on communication failure or transition timeout.
- Returns:
True once the drive reports Operation Enabled.
-
inline bool disable(std::error_code &ec)
Disable the drive (Shutdown command -> Ready to switch on, power stage off).
- Parameters:
ec – Set on communication failure or transition timeout.
- Returns:
True once the drive reports Ready to switch on.
-
inline bool quick_stop(std::error_code &ec)
Issue a quick stop (controlword 0x0002).
Depending on the drive’s quick-stop option code it transitions to Quick Stop Active or directly to Switch On Disabled, so this does not poll for a specific target state.
- Parameters:
ec – Set on communication failure.
- Returns:
True on success.
-
inline bool fault_reset(std::error_code &ec)
Reset a drive fault (rising edge on controlword bit 7).
Writes controlword 0x0000 then 0x0080, then polls until the drive leaves the Fault state.
- Parameters:
ec – Set on communication failure or if the fault persists.
- Returns:
True once the drive is no longer in Fault.
-
inline bool set_mode(OperatingMode mode, std::error_code &ec)
Set the mode of operation (object 0x6060) and verify via 0x6061.
- Parameters:
mode – The mode to select (e.g. OperatingMode::ProfileVelocity).
ec – Set on communication failure or if the drive does not report the mode within the state timeout.
- Returns:
True once modes-of-operation-display matches.
-
inline int8_t get_mode_display(std::error_code &ec)
Read the mode of operation display (object 0x6061).
- Parameters:
ec – Set on failure.
- Returns:
The reported mode.
Motion
-
inline bool set_target_velocity(int32_t velocity, std::error_code &ec)
Write the target velocity (object 0x60FF; profile velocity mode).
- Parameters:
velocity – Target velocity in device units.
ec – Set on failure.
- Returns:
True on success.
-
inline bool set_target_position(int32_t position, std::error_code &ec, bool immediate = true, bool relative = false)
Command a profile-position move (object 0x607A + new-set-point handshake).
Writes the target position, then raises controlword bit 4 (new set-point) with bit 5 (change set immediately) and bit 6 (relative) as requested, waits for the drive to acknowledge via statusword bit 12, and clears bit 4 again. The drive must already be in Operation Enabled in profile position mode.
- Parameters:
position – Target position in device units.
ec – Set on communication failure or acknowledge timeout.
immediate – If true, the drive starts the new move immediately (bit 5).
relative – If true, the target is relative to the current position (bit 6).
- Returns:
True once the set-point was acknowledged and bit 4 released.
-
inline bool is_target_reached(std::error_code &ec)
Check whether the drive reports target reached (statusword bit 10).
- Parameters:
ec – Set on failure.
- Returns:
True if the target is reached.
Public Types
-
using State = detail::ds402::State
CiA 402 drive state.
-
using OperatingMode = detail::ds402::OperatingMode
CiA 402 mode of operation.
Public Functions
-
inline explicit Ds402Drive(CanopenClient &client, const Config &config)
Create a DS402 drive helper.
- Parameters:
client – The CANopen client for the drive’s node. Must outlive this object.
config – The configuration.
-
inline explicit Ds402Drive(CanopenClient &client)
Create a DS402 drive helper with the default configuration.
- Parameters:
client – The CANopen client for the drive’s node. Must outlive this object.
-
inline const std::string &get_name() const
Get the name of the component
Note
This is the tag of the logger
- Returns:
A const reference to the name of the component
-
inline void set_log_tag(const std::string_view &tag)
Set the tag for the logger
- Parameters:
tag – The tag to use for the logger
-
inline espp::Logger::Verbosity get_log_level() const
Get the log level for the logger
See also
See also
- Returns:
The verbosity level of the logger
-
inline void set_log_level(espp::Logger::Verbosity level)
Set the log level for the logger
See also
See also
- Parameters:
level – The verbosity level to use for the logger
-
inline void set_log_verbosity(espp::Logger::Verbosity level)
Set the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls set_log_level
- Parameters:
level – The verbosity level to use for the logger
-
inline espp::Logger::Verbosity get_log_verbosity() const
Get the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls get_log_level
- Returns:
The verbosity level of the logger
-
inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
Set the rate limit for the logger
See also
Note
Only calls to the logger that have _rate_limit suffix will be rate limited
- Parameters:
rate_limit – The rate limit to use for the logger
-
struct Config
Configuration for the Ds402Drive.
-
inline uint32_t get_device_type(std::error_code &ec)
Header File
Classes
-
class SdoSegmentedUpload
Small accumulator for a segmented SDO upload.
Feed the parsed SegmentedUploadInit response, then each parsed UploadSegment response; verifies the toggle-bit alternation and collects the payload bytes. The caller drives the request side (make_sdo_upload_segment_request with next_toggle()).
Public Functions
-
inline bool start(const SdoResponse &init, size_t max_size = kDefaultMaxSize)
Start a transfer from a parsed SegmentedUploadInit response.
- Parameters:
init – The parsed initiate response.
max_size – Maximum accepted total size (reservation AND growth cap).
- Returns:
False if the remote-indicated size exceeds
max_size(the transfer must then be aborted by the caller), true otherwise.
-
inline bool next_toggle() const
The toggle bit to use for the next segment request.
-
inline bool consume(const SdoResponse &segment)
Consume a parsed UploadSegment response.
- Parameters:
segment – The parsed segment response.
- Returns:
False on toggle-bit mismatch or if the accumulated data would exceed the start() size cap (protocol error either way), true otherwise.
-
inline bool done() const
True once the final segment (c bit) has been consumed.
-
inline const std::string &data() const
The accumulated payload bytes.
Public Static Attributes
-
static constexpr size_t kDefaultMaxSize = 1024
Default cap on the total transfer size. total_size in the initiate response is remote-supplied (up to 4 GiB), and an un-sized transfer could otherwise grow indefinitely — either would exhaust memory on an embedded target. Segmented uploads here serve small objects (device name / version strings), so the default is deliberately tight; pass a larger cap to start() when a bigger object is genuinely expected.
-
inline bool start(const SdoResponse &init, size_t max_size = kDefaultMaxSize)