DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
canopen.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file canopen.cpp
6 * @brief CANopen (CiA 301) message codec (pure, host-tested).
7 */
8
10
11#if DETWS_ENABLE_CANOPEN
12
13#include <string.h>
14
15// All CANopen default-profile identifiers are 11-bit standard frames.
16static void std_frame(CanFrame *f, uint32_t id, uint8_t dlc)
17{
18 f->id = id & DET_CAN_STD_ID_MASK;
19 f->extended = false;
20 f->rtr = false;
21 f->dlc = dlc;
22 memset(f->data, 0, sizeof(f->data));
23}
24
25static bool valid_node(uint8_t node_id)
26{
27 return node_id >= 1 && node_id <= 127;
28}
29
30bool canopen_build_nmt(CanFrame *out, uint8_t command, uint8_t node_id)
31{
32 if (!out || node_id > 127) // 0 = all nodes
33 return false;
34 std_frame(out, CANOPEN_COB_NMT, 2);
35 out->data[0] = command;
36 out->data[1] = node_id;
37 return true;
38}
39
40bool canopen_build_sync(CanFrame *out)
41{
42 if (!out)
43 return false;
44 std_frame(out, CANOPEN_COB_SYNC, 0);
45 return true;
46}
47
48bool canopen_build_heartbeat(CanFrame *out, uint8_t node_id, uint8_t state)
49{
50 if (!out || !valid_node(node_id))
51 return false;
52 std_frame(out, CANOPEN_COB_HEARTBEAT + node_id, 1);
53 out->data[0] = state;
54 return true;
55}
56
57bool canopen_build_emcy(CanFrame *out, uint8_t node_id, uint16_t error_code, uint8_t error_reg, const uint8_t msef[5])
58{
59 if (!out || !valid_node(node_id))
60 return false;
61 std_frame(out, CANOPEN_COB_EMCY + node_id, 8);
62 out->data[0] = (uint8_t)error_code; // error code, little-endian
63 out->data[1] = (uint8_t)(error_code >> 8);
64 out->data[2] = error_reg; // object 0x1001 error register
65 if (msef)
66 memcpy(out->data + 3, msef, 5); // 5 manufacturer-specific error octets
67 return true;
68}
69
70// Map a PDO number (1..4) to its TPDO / RPDO COB-ID base.
71static bool pdo_base(uint8_t pdo_num, bool transmit, uint32_t *base)
72{
73 if (pdo_num < 1 || pdo_num > 4)
74 return false;
75 static const uint32_t tx[4] = {CANOPEN_COB_TPDO1, CANOPEN_COB_TPDO2, CANOPEN_COB_TPDO3, CANOPEN_COB_TPDO4};
76 static const uint32_t rx[4] = {CANOPEN_COB_RPDO1, CANOPEN_COB_RPDO2, CANOPEN_COB_RPDO3, CANOPEN_COB_RPDO4};
77 *base = (transmit ? tx : rx)[pdo_num - 1];
78 return true;
79}
80
81static bool build_pdo(CanFrame *out, uint8_t pdo_num, bool transmit, uint8_t node_id, const uint8_t *data, uint8_t len)
82{
83 uint32_t base;
84 if (!out || !valid_node(node_id) || len > DET_CAN_MAX_DLC || (len && !data) || !pdo_base(pdo_num, transmit, &base))
85 return false;
86 std_frame(out, base + node_id, len);
87 if (len)
88 memcpy(out->data, data, len);
89 return true;
90}
91
92bool canopen_build_tpdo(CanFrame *out, uint8_t pdo_num, uint8_t node_id, const uint8_t *data, uint8_t len)
93{
94 return build_pdo(out, pdo_num, true, node_id, data, len);
95}
96
97bool canopen_build_rpdo(CanFrame *out, uint8_t pdo_num, uint8_t node_id, const uint8_t *data, uint8_t len)
98{
99 return build_pdo(out, pdo_num, false, node_id, data, len);
100}
101
102// Fill data[1..3] with the object index (LE) + sub-index common to every SDO frame.
103static void sdo_set_object(CanFrame *f, uint16_t index, uint8_t sub)
104{
105 f->data[1] = (uint8_t)index;
106 f->data[2] = (uint8_t)(index >> 8);
107 f->data[3] = sub;
108}
109
110bool canopen_build_sdo_read(CanFrame *out, uint8_t node_id, uint16_t index, uint8_t sub)
111{
112 if (!out || !valid_node(node_id))
113 return false;
114 std_frame(out, CANOPEN_COB_SDO_RX + node_id, 8);
115 out->data[0] = (uint8_t)(CANOPEN_SDO_CCS_UPLOAD << 5); // upload initiate request (0x40)
116 sdo_set_object(out, index, sub);
117 return true;
118}
119
120bool canopen_build_sdo_write(CanFrame *out, uint8_t node_id, uint16_t index, uint8_t sub, const uint8_t *data,
121 uint8_t len)
122{
123 if (!out || !valid_node(node_id) || len < 1 || len > 4 || !data)
124 return false;
125 std_frame(out, CANOPEN_COB_SDO_RX + node_id, 8);
126 // download initiate, expedited (e=1), size indicated (s=1); n = unused octets in data[4..7].
127 out->data[0] = (uint8_t)((CANOPEN_SDO_CCS_DOWNLOAD << 5) | (((4u - len) & 3u) << 2) | 0x03u);
128 sdo_set_object(out, index, sub);
129 memcpy(out->data + 4, data, len);
130 return true;
131}
132
133bool canopen_build_sdo_abort(CanFrame *out, uint8_t node_id, uint16_t index, uint8_t sub, uint32_t abort_code,
134 bool to_server)
135{
136 if (!out || !valid_node(node_id))
137 return false;
138 std_frame(out, (to_server ? CANOPEN_COB_SDO_RX : CANOPEN_COB_SDO_TX) + node_id, 8);
139 out->data[0] = (uint8_t)(CANOPEN_SDO_ABORT << 5); // 0x80
140 sdo_set_object(out, index, sub);
141 out->data[4] = (uint8_t)abort_code; // abort code, little-endian
142 out->data[5] = (uint8_t)(abort_code >> 8);
143 out->data[6] = (uint8_t)(abort_code >> 16);
144 out->data[7] = (uint8_t)(abort_code >> 24);
145 return true;
146}
147
148bool canopen_parse(const CanFrame *f, CanopenMsg *out)
149{
150 if (!f || !out || f->extended)
151 return false; // CANopen default profile is 11-bit standard frames
152 uint32_t id = f->id & DET_CAN_STD_ID_MASK;
153 uint32_t func = id & CANOPEN_FUNC_MASK;
154 uint8_t node = (uint8_t)(id & CANOPEN_NODE_MASK);
155 out->type = CanopenType::CANOPEN_T_UNKNOWN;
156 out->node_id = node;
157 out->pdo_num = 0;
158
159 if (id == CANOPEN_COB_NMT)
160 {
161 out->type = CanopenType::CANOPEN_T_NMT;
162 out->node_id = 0;
163 return true;
164 }
165 if (id == CANOPEN_COB_SYNC) // function 0x080 with node 0
166 {
167 out->type = CanopenType::CANOPEN_T_SYNC;
168 out->node_id = 0;
169 return true;
170 }
171 if (id == CANOPEN_COB_TIME)
172 {
173 out->type = CanopenType::CANOPEN_T_TIME;
174 out->node_id = 0;
175 return true;
176 }
177 if (node == 0)
178 return true; // a function base with node 0 we don't classify further
179
180 switch (func)
181 {
182 case CANOPEN_COB_EMCY:
183 out->type = CanopenType::CANOPEN_T_EMCY;
184 return true;
185 case CANOPEN_COB_TPDO1:
186 out->type = CanopenType::CANOPEN_T_TPDO;
187 out->pdo_num = 1;
188 return true;
189 case CANOPEN_COB_RPDO1:
190 out->type = CanopenType::CANOPEN_T_RPDO;
191 out->pdo_num = 1;
192 return true;
193 case CANOPEN_COB_TPDO2:
194 out->type = CanopenType::CANOPEN_T_TPDO;
195 out->pdo_num = 2;
196 return true;
197 case CANOPEN_COB_RPDO2:
198 out->type = CanopenType::CANOPEN_T_RPDO;
199 out->pdo_num = 2;
200 return true;
201 case CANOPEN_COB_TPDO3:
202 out->type = CanopenType::CANOPEN_T_TPDO;
203 out->pdo_num = 3;
204 return true;
205 case CANOPEN_COB_RPDO3:
206 out->type = CanopenType::CANOPEN_T_RPDO;
207 out->pdo_num = 3;
208 return true;
209 case CANOPEN_COB_TPDO4:
210 out->type = CanopenType::CANOPEN_T_TPDO;
211 out->pdo_num = 4;
212 return true;
213 case CANOPEN_COB_RPDO4:
214 out->type = CanopenType::CANOPEN_T_RPDO;
215 out->pdo_num = 4;
216 return true;
217 case CANOPEN_COB_SDO_TX:
218 out->type = CanopenType::CANOPEN_T_SDO_TX;
219 return true;
220 case CANOPEN_COB_SDO_RX:
221 out->type = CanopenType::CANOPEN_T_SDO_RX;
222 return true;
223 case CANOPEN_COB_HEARTBEAT:
224 out->type = CanopenType::CANOPEN_T_HEARTBEAT;
225 return true;
226 default:
227 return true; // unknown function code: type stays CanopenType::CANOPEN_T_UNKNOWN
228 }
229}
230
231bool canopen_parse_emcy(const CanFrame *f, uint8_t *node_id, uint16_t *error_code, uint8_t *error_reg, uint8_t msef[5])
232{
233 if (!f || f->extended || f->dlc < 8)
234 return false;
235 uint32_t id = f->id & DET_CAN_STD_ID_MASK;
236 uint8_t node = (uint8_t)(id & CANOPEN_NODE_MASK);
237 if ((id & CANOPEN_FUNC_MASK) != CANOPEN_COB_EMCY || node == 0)
238 return false; // 0x080 with node 0 is SYNC, not EMCY
239 if (node_id)
240 *node_id = node;
241 if (error_code)
242 *error_code = (uint16_t)(f->data[0] | (f->data[1] << 8));
243 if (error_reg)
244 *error_reg = f->data[2];
245 if (msef)
246 memcpy(msef, f->data + 3, 5);
247 return true;
248}
249
250bool canopen_parse_heartbeat(const CanFrame *f, uint8_t *node_id, uint8_t *state)
251{
252 if (!f || f->extended || f->dlc < 1)
253 return false;
254 uint32_t id = f->id & DET_CAN_STD_ID_MASK;
255 uint8_t node = (uint8_t)(id & CANOPEN_NODE_MASK);
256 if ((id & CANOPEN_FUNC_MASK) != CANOPEN_COB_HEARTBEAT || node == 0)
257 return false;
258 if (node_id)
259 *node_id = node;
260 if (state)
261 *state = (uint8_t)(f->data[0] & 0x7Fu); // bit 7 is the boot toggle in some stacks
262 return true;
263}
264
265bool canopen_parse_sdo_response(const CanFrame *f, CanopenSdoResponse *out)
266{
267 if (!f || !out || f->extended || f->dlc < 8)
268 return false;
269 uint32_t id = f->id & DET_CAN_STD_ID_MASK;
270 if ((id & CANOPEN_FUNC_MASK) != CANOPEN_COB_SDO_TX || (id & CANOPEN_NODE_MASK) == 0)
271 return false;
272
273 uint8_t cmd = f->data[0];
274 uint8_t scs = (uint8_t)(cmd >> 5);
275 out->index = (uint16_t)(f->data[1] | (f->data[2] << 8));
276 out->sub = f->data[3];
277 out->is_abort = false;
278 out->abort_code = 0;
279 out->is_upload = false;
280 out->expedited = false;
281 out->len = 0;
282 memset(out->data, 0, sizeof(out->data));
283
284 if (scs == CANOPEN_SDO_ABORT)
285 {
286 out->is_abort = true;
287 out->abort_code = (uint32_t)f->data[4] | ((uint32_t)f->data[5] << 8) | ((uint32_t)f->data[6] << 16) |
288 ((uint32_t)f->data[7] << 24);
289 return true;
290 }
291 if (scs == CANOPEN_SDO_SCS_UPLOAD) // upload initiate response
292 {
293 out->is_upload = true;
294 bool e = (cmd & 0x02u) != 0; // expedited
295 bool s = (cmd & 0x01u) != 0; // size indicated
296 if (e)
297 {
298 out->expedited = true;
299 out->len = s ? (uint8_t)(4u - ((cmd >> 2) & 0x03u)) : 4u;
300 memcpy(out->data, f->data + 4, out->len);
301 }
302 return true; // a non-expedited (segmented) response is reported with len 0
303 }
304 if (scs == CANOPEN_SDO_SCS_DOWNLOAD) // download initiate response (write acknowledged)
305 return true;
306 return false; // not a recognised server command specifier
307}
308
309#endif // DETWS_ENABLE_CANOPEN
#define DET_CAN_STD_ID_MASK
11-bit standard identifier.
Definition can.h:31
#define DET_CAN_MAX_DLC
classic CAN carries at most 8 data octets.
Definition can.h:30
CANopen (CiA 301) application-layer message codec (DETWS_ENABLE_CANOPEN).
Definition can.h:44
uint8_t data[DET_CAN_MAX_DLC]
Definition can.h:49
bool rtr
Definition can.h:47
uint32_t id
Definition can.h:45
uint8_t dlc
Definition can.h:48
bool extended
Definition can.h:46