ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
cia402.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file cia402.h
6 * @brief CiA 402 / IEC 61800-7-201 drive + motion profile (PROTOCORE_ENABLE_CIA402) over CANopen.
7 *
8 * The standardized servo / stepper drive profile: the power state machine (Controlword 0x6040 /
9 * Statusword 0x6041), the Modes of Operation, and the target/actual position-velocity-torque
10 * objects. This is the pure profile layer - the state decode + controlword commands are just
11 * value logic, and the setters/getters wrap the shipped `services/canopen` SDO / PDO codec, so
12 * the CAN stack (ESP32 TWAI or an MCP2515) becomes a motion master. Close the loop with a
13 * `services/control` PID.
14 *
15 * Statusword state masks, Controlword command values, and the object indices are verified against
16 * IEC 61800-7-201 (CiA 402) and multiple drive vendors' state-machine tables. Pure, host-tested.
17 *
18 * @author Douglas Quigg (dstroy0)
19 * @date 2026
20 */
21
22#ifndef PROTOCORE_CIA402_H
23#define PROTOCORE_CIA402_H
24
25#include "protocore_config.h" // the entry point: protocore_types.h for the widths
26#include "shared/can/can.h" // CanFrame: the type a parameter points at
27
28#if PROTOCORE_ENABLE_CIA402
29
31
32// This module holds nothing between calls, so it carves no borrow and states none. An entry
33// takes one all the same, and never reads it, so every namespace in the tree is invoked the
34// same way.
35
36// --- object dictionary indices (sub-index 0 unless noted); the comment gives the CANopen type ---
37#define CIA402_OD_ERROR_CODE 0x603Fu ///< u16 last error code
38#define CIA402_OD_CONTROLWORD 0x6040u ///< u16 command word (drives the state machine)
39#define CIA402_OD_STATUSWORD 0x6041u ///< u16 status word (reports the state)
40#define CIA402_OD_QUICK_STOP_OPTION 0x605Au ///< i16 quick-stop option code
41#define CIA402_OD_MODES_OF_OPERATION 0x6060u ///< i8 requested mode
42#define CIA402_OD_MODES_DISPLAY 0x6061u ///< i8 active mode (read-back)
43#define CIA402_OD_POSITION_ACTUAL 0x6064u ///< i32 position actual value
44#define CIA402_OD_VELOCITY_ACTUAL 0x606Cu ///< i32 velocity actual value
45#define CIA402_OD_TARGET_TORQUE 0x6071u ///< i16 target torque (per-mille of rated)
46#define CIA402_OD_TORQUE_ACTUAL 0x6077u ///< i16 torque actual value
47#define CIA402_OD_TARGET_POSITION 0x607Au ///< i32 target position (PP / CSP)
48#define CIA402_OD_PROFILE_VELOCITY 0x6081u ///< u32 profile velocity (PP)
49#define CIA402_OD_PROFILE_ACCEL 0x6083u ///< u32 profile acceleration
50#define CIA402_OD_PROFILE_DECEL 0x6084u ///< u32 profile deceleration
51#define CIA402_OD_TARGET_VELOCITY 0x60FFu ///< i32 target velocity (PV / CSV)
52#define CIA402_OD_SUPPORTED_MODES 0x6502u ///< u32 supported drive modes bitfield
53
54/// Controlword bit masks (object 0x6040).
55#define CIA402_CW_SWITCH_ON 0x0001
56#define CIA402_CW_ENABLE_VOLTAGE 0x0002
57#define CIA402_CW_QUICK_STOP 0x0004 ///< active-low: 0 requests quick stop
58#define CIA402_CW_ENABLE_OPERATION 0x0008
59#define CIA402_CW_FAULT_RESET 0x0080 ///< acts on the rising edge
60#define CIA402_CW_HALT 0x0100
61
62/// Statusword bit masks (object 0x6041).
63#define CIA402_SW_READY_TO_SWITCH_ON 0x0001
64#define CIA402_SW_SWITCHED_ON 0x0002
65#define CIA402_SW_OPERATION_ENABLED 0x0004
66#define CIA402_SW_FAULT 0x0008
67#define CIA402_SW_VOLTAGE_ENABLED 0x0010
68#define CIA402_SW_QUICK_STOP 0x0020 ///< 0 = quick stop active
69#define CIA402_SW_SWITCH_ON_DISABLED 0x0040
70#define CIA402_SW_WARNING 0x0080
71#define CIA402_SW_REMOTE 0x0200
72#define CIA402_SW_TARGET_REACHED 0x0400
73#define CIA402_SW_INTERNAL_LIMIT 0x0800
74
75/// @return true if the Statusword's Target Reached flag (bit 10) is set.
76static inline proto_bool protocore_cia402_target_reached(uint16_t sw)
77{
78 return (sw & CIA402_SW_TARGET_REACHED) != 0;
79}
80/// @return true if the drive reports a fault (bit 3).
81static inline proto_bool protocore_cia402_has_fault(uint16_t sw)
82{
83 return (sw & CIA402_SW_FAULT) != 0;
84}
85/// @return true if a warning is present (bit 7).
86static inline proto_bool protocore_cia402_warning(uint16_t sw)
87{
88 return (sw & CIA402_SW_WARNING) != 0;
89}
90/// @return true if the drive's power stage voltage is applied (bit 4).
91static inline proto_bool protocore_cia402_voltage_enabled(uint16_t sw)
92{
93 return (sw & CIA402_SW_VOLTAGE_ENABLED) != 0;
94}
95/// @return true if the drive follows the Controlword (bit 9 remote).
96static inline proto_bool protocore_cia402_remote(uint16_t sw)
97{
98 return (sw & CIA402_SW_REMOTE) != 0;
99}
100/// @return true if a set-point was internally limited (bit 11).
101static inline proto_bool protocore_cia402_internal_limit(uint16_t sw)
102{
103 return (sw & CIA402_SW_INTERNAL_LIMIT) != 0;
104}
105
106typedef enum PROTO_ENUM_PACKED
107{
108 CIA402_MODE_NO_MODE = 0,
109 CIA402_MODE_PROFILE_POSITION = 1, ///< PP
110 CIA402_MODE_VELOCITY = 2, ///< VL (frequency-converter CIA402_MODE_VELOCITY)
111 CIA402_MODE_PROFILE_VELOCITY = 3, ///< PV
112 CIA402_MODE_PROFILE_TORQUE = 4, ///< TQ
113 CIA402_MODE_HOMING = 6, ///< HM
114 CIA402_MODE_INTERPOLATED_POSITION = 7, ///< IP
115 CIA402_MODE_CYCLIC_SYNC_POSITION = 8, ///< CSP
116 CIA402_MODE_CYCLIC_SYNC_VELOCITY = 9, ///< CSV
117 CIA402_MODE_CYCLIC_SYNC_TORQUE = 10, ///< CST
118} Cia402Mode;
119
120typedef enum PROTO_ENUM_PACKED
121{
122 CIA402_STATE_NOT_READY_TO_SWITCH_ON,
123 CIA402_STATE_SWITCH_ON_DISABLED,
124 CIA402_STATE_READY_TO_SWITCH_ON,
125 CIA402_STATE_SWITCHED_ON,
126 CIA402_STATE_OPERATION_ENABLED,
127 CIA402_STATE_QUICK_STOP_ACTIVE,
128 CIA402_STATE_FAULT_REACTION_ACTIVE,
129 CIA402_STATE_FAULT,
130 CIA402_STATE_UNKNOWN, ///< Statusword matched no defined state
131} Cia402State;
132
133typedef enum PROTO_ENUM_PACKED
134{
135 CIA402_COMMAND_SHUTDOWN, ///< -> Ready to switch on
136 CIA402_COMMAND_SWITCH_ON, ///< -> Switched on
137 CIA402_COMMAND_ENABLE_OPERATION, ///< -> Operation enabled
138 CIA402_COMMAND_DISABLE_VOLTAGE, ///< -> Switch on disabled
139 CIA402_COMMAND_QUICK_STOP, ///< -> Quick stop active
140 CIA402_COMMAND_DISABLE_OPERATION, ///< -> Switched on
141 CIA402_COMMAND_FAULT_RESET, ///< clear a fault (rising edge of bit 7)
142} Cia402Command;
143
144/** @brief What state takes: statusword. */
145typedef struct
146{
147 uint16_t statusword;
148} Cia402StateArgs;
149
150/** @brief What controlword takes: cmd. */
151typedef struct
152{
153 Cia402Command cmd;
154} Cia402ControlwordArgs;
155
156/** @brief What enable_sequence takes: state. */
157typedef struct
158{
159 Cia402State state;
160} Cia402EnableSequenceArgs;
161
162/** @brief What sdo_set_controlword takes: out, node, controlword. */
163typedef struct
164{
165 CanFrame *out;
166 uint8_t node;
167 uint16_t controlword;
168} Cia402SdoSetControlwordArgs;
169
170/** @brief What sdo_set_mode takes: out, node, mode. */
171typedef struct
172{
173 CanFrame *out;
174 uint8_t node;
175 Cia402Mode mode;
176} Cia402SdoSetModeArgs;
177
178/** @brief What sdo_set_target_position takes: out, node, position. */
179typedef struct
180{
181 CanFrame *out;
182 uint8_t node;
183 int32_t position;
184} Cia402SdoSetTargetPositionArgs;
185
186/** @brief What sdo_set_target_velocity takes: out, node, velocity. */
187typedef struct
188{
189 CanFrame *out;
190 uint8_t node;
191 int32_t velocity;
192} Cia402SdoSetTargetVelocityArgs;
193
194/** @brief What sdo_set_target_torque takes: out, node, torque. */
195typedef struct
196{
197 CanFrame *out;
198 uint8_t node;
199 int16_t torque;
200} Cia402SdoSetTargetTorqueArgs;
201
202/** @brief What sdo_read takes: out, node, index, sub. */
203typedef struct
204{
205 CanFrame *out;
206 uint8_t node;
207 uint16_t index;
208 uint8_t sub;
209} Cia402SdoReadArgs;
210
211/** @brief What sdo_get_u16 takes: f, want_index, value. */
212typedef struct
213{
214 const CanFrame *f;
215 uint16_t want_index;
216 uint16_t *value;
217} Cia402SdoGetU16Args;
218
219/** @brief What sdo_get_i32 takes: f, want_index, value. */
220typedef struct
221{
222 const CanFrame *f;
223 uint16_t want_index;
224 int32_t *value;
225} Cia402SdoGetI32Args;
226
227/** @brief What pack_command takes: buf, cap, controlword, target. */
228typedef struct
229{
230 uint8_t *buf;
231 size_t cap;
232 uint16_t controlword;
233 int32_t target;
234} Cia402PackCommandArgs;
235
236/** @brief What unpack_status takes: buf, len, statusword, actual. */
237typedef struct
238{
239 const uint8_t *buf;
240 size_t len;
241 uint16_t *statusword;
242 int32_t *actual;
243} Cia402UnpackStatusArgs;
244
245/**
246 * @brief CiA 402 / IEC 61800-7-201 drive + motion profile (PROTOCORE_ENABLE_CIA402) over CANopen.
247 *
248 * A caller sets the members a call takes, invokes it through ::Cia402 with the bytes it runs
249 * out of, and reads the outcome off the same handle.
250 *
251 * Cia402.state_args.statusword = ...;
252 * Cia402.state(work);
253 * // Cia402.value is what the call reports
254 *
255 * @var Cia402Ns::state_args what state takes: statusword
256 * @var Cia402Ns::controlword_args what controlword takes: cmd
257 * @var Cia402Ns::enable_sequence_args what enable_sequence takes: state
258 * @var Cia402Ns::sdo_set_controlword_args what sdo_set_controlword takes: out, node, controlword
259 * @var Cia402Ns::sdo_set_mode_args what sdo_set_mode takes: out, node, mode
260 * @var Cia402Ns::sdo_set_target_position_args what sdo_set_target_position takes: out, node, position
261 * @var Cia402Ns::sdo_set_target_velocity_args what sdo_set_target_velocity takes: out, node, velocity
262 * @var Cia402Ns::sdo_set_target_torque_args what sdo_set_target_torque takes: out, node, torque
263 * @var Cia402Ns::sdo_read_args what sdo_read takes: out, node, index, sub
264 * @var Cia402Ns::sdo_get_u16_args what sdo_get_u16 takes: f, want_index, value
265 * @var Cia402Ns::sdo_get_i32_args what sdo_get_i32 takes: f, want_index, value
266 * @var Cia402Ns::pack_command_args what pack_command takes: buf, cap, controlword, target
267 * @var Cia402Ns::unpack_status_args what unpack_status takes: buf, len, statusword, actual
268 * @var Cia402Ns::ok a call's true/false outcome
269 * @var Cia402Ns::value the value a call reports
270 * @var Cia402Ns::u16 what a call reports
271 * @var Cia402Ns::n the count a call reports
272 * @var Cia402Ns::state state
273 * @var Cia402Ns::controlword controlword
274 * @var Cia402Ns::enable_sequence enable_sequence
275 * @var Cia402Ns::sdo_set_controlword sdo_set_controlword
276 * @var Cia402Ns::sdo_set_mode sdo_set_mode
277 * @var Cia402Ns::sdo_set_target_position sdo_set_target_position
278 * @var Cia402Ns::sdo_set_target_velocity sdo_set_target_velocity
279 * @var Cia402Ns::sdo_set_target_torque sdo_set_target_torque
280 * @var Cia402Ns::sdo_read sdo_read
281 * @var Cia402Ns::sdo_get_u16 sdo_get_u16
282 * @var Cia402Ns::sdo_get_i32 sdo_get_i32
283 * @var Cia402Ns::pack_command pack_command
284 * @var Cia402Ns::unpack_status unpack_status
285 *
286 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
287 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
288 * a caller drives every namespace the same way.
289 */
290typedef struct
291{
292 Cia402StateArgs state_args;
293 Cia402ControlwordArgs controlword_args;
294 Cia402EnableSequenceArgs enable_sequence_args;
295 Cia402SdoSetControlwordArgs sdo_set_controlword_args;
296 Cia402SdoSetModeArgs sdo_set_mode_args;
297 Cia402SdoSetTargetPositionArgs sdo_set_target_position_args;
298 Cia402SdoSetTargetVelocityArgs sdo_set_target_velocity_args;
299 Cia402SdoSetTargetTorqueArgs sdo_set_target_torque_args;
300 Cia402SdoReadArgs sdo_read_args;
301 Cia402SdoGetU16Args sdo_get_u16_args;
302 Cia402SdoGetI32Args sdo_get_i32_args;
303 Cia402PackCommandArgs pack_command_args;
304 Cia402UnpackStatusArgs unpack_status_args;
305 proto_bool ok;
306 Cia402State value;
307 uint16_t u16;
308 size_t n;
309} Cia402Vars;
310
311/** @brief The operands and the outcome. */
312extern Cia402Vars Cia402V;
313
314/** @brief The entries. */
315typedef struct
316{
317 void (*const state)(uint8_t *work);
318 void (*const controlword)(uint8_t *work);
319 void (*const enable_sequence)(uint8_t *work);
320 void (*const sdo_set_controlword)(uint8_t *work);
321 void (*const sdo_set_mode)(uint8_t *work);
322 void (*const sdo_set_target_position)(uint8_t *work);
323 void (*const sdo_set_target_velocity)(uint8_t *work);
324 void (*const sdo_set_target_torque)(uint8_t *work);
325 void (*const sdo_read)(uint8_t *work);
326 void (*const sdo_get_u16)(uint8_t *work);
327 void (*const sdo_get_i32)(uint8_t *work);
328 void (*const pack_command)(uint8_t *work);
329 void (*const unpack_status)(uint8_t *work);
330} Cia402Ns;
331
332// What the table binds, defined once in the .c and taking one parameter each: everything
333// else an entry needs is an operand in Cia402V or a region of the borrow at a fixed offset.
334void protocore_cia402_state(uint8_t *work);
335void protocore_cia402_controlword(uint8_t *work);
336void protocore_cia402_enable_sequence(uint8_t *work);
337void protocore_cia402_sdo_set_controlword(uint8_t *work);
338void protocore_cia402_sdo_set_mode(uint8_t *work);
339void protocore_cia402_sdo_set_target_position(uint8_t *work);
340void protocore_cia402_sdo_set_target_velocity(uint8_t *work);
341void protocore_cia402_sdo_set_target_torque(uint8_t *work);
342void protocore_cia402_sdo_read(uint8_t *work);
343void protocore_cia402_sdo_get_u16(uint8_t *work);
344void protocore_cia402_sdo_get_i32(uint8_t *work);
345void protocore_cia402_pack_command(uint8_t *work);
346void protocore_cia402_unpack_status(uint8_t *work);
347
348// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
349// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
350// `Cia402.state(work)` resolves to a named function and becomes a DIRECT call. An extern table
351// leaves the call indirect and the symbol live at every level, -O2 -flto included.
352static const Cia402Ns Cia402 __attribute__((unused)) = {
353 .state = protocore_cia402_state,
354 .controlword = protocore_cia402_controlword,
355 .enable_sequence = protocore_cia402_enable_sequence,
356 .sdo_set_controlword = protocore_cia402_sdo_set_controlword,
357 .sdo_set_mode = protocore_cia402_sdo_set_mode,
358 .sdo_set_target_position = protocore_cia402_sdo_set_target_position,
359 .sdo_set_target_velocity = protocore_cia402_sdo_set_target_velocity,
360 .sdo_set_target_torque = protocore_cia402_sdo_set_target_torque,
361 .sdo_read = protocore_cia402_sdo_read,
362 .sdo_get_u16 = protocore_cia402_sdo_get_u16,
363 .sdo_get_i32 = protocore_cia402_sdo_get_i32,
364 .pack_command = protocore_cia402_pack_command,
365 .unpack_status = protocore_cia402_unpack_status,
366};
367
369
370#endif // PROTOCORE_ENABLE_CIA402
371
372#endif // PROTOCORE_CIA402_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
Shared CAN 2.0 frame type for the CAN-based industrial codecs (one source of truth).
Definition can.h:44
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97