Problem Definition
You need to push an arbitrary byte stream across a CAN bus that is already running CANopen. CAN itself does not care what the payload means, but the moment the segment carries CANopen nodes, every 11-bit identifier you emit competes with the pre-defined connection set. Two decisions drive everything: which COB-ID is legal to use, and which service (PDO or SDO) can carry the payload length you need.
The short answer: for payloads of 8 bytes or fewer, transmit on a TPDO COB-ID belonging to your node ID. For anything longer, use SDO segmented or block transfer against a manufacturer-specific object of type DOMAIN. Only fall back to a custom, non-CANopen identifier when you are certain it collides with nothing on the bus.
COB-ID Map: What Is Already Taken
An 11-bit CANopen COB-ID splits into a 4-bit function code (bits 10-7) and a 7-bit node ID (bits 6-0, valid range 1-127). The pre-defined connection set in CiA 301 v4.2 allocates the identifiers as follows.
| Object | Function code base | Resulting COB-ID range | Direction |
|---|---|---|---|
| NMT node control | 0x000 | 0x000 (broadcast) | Master to all |
| SYNC | 0x080 | 0x080 (broadcast) | Producer to all |
| EMCY | 0x080 + NodeID | 0x081-0x0FF | Node to bus |
| TPDO1 | 0x180 + NodeID | 0x181-0x1FF | Node transmits |
| RPDO1 | 0x200 + NodeID | 0x201-0x27F | Node receives |
| TPDO2 | 0x280 + NodeID | 0x281-0x2FF | Node transmits |
| RPDO2 | 0x300 + NodeID | 0x301-0x37F | Node receives |
| TPDO3 | 0x380 + NodeID | 0x381-0x3FF | Node transmits |
| RPDO3 | 0x400 + NodeID | 0x401-0x47F | Node receives |
| TPDO4 | 0x480 + NodeID | 0x481-0x4FF | Node transmits |
| RPDO4 | 0x500 + NodeID | 0x501-0x57F | Node receives |
| SDO transmit (server to client) | 0x580 + NodeID | 0x581-0x5FF | Server response |
| SDO receive (client to server) | 0x600 + NodeID | 0x601-0x67F | Client request |
| Heartbeat / NMT error control | 0x700 + NodeID | 0x701-0x77F | Node to bus |
Because node IDs stop at 127 (0x7F) while each function-code block spans 128 identifiers, small gaps remain at the top of each RPDO/SDO-rx block: 0x101-0x180, 0x280, 0x380, 0x480, 0x580, 0x680-0x700, and 0x780-0x7FF. These are the only sizeable ranges that are not claimed by the pre-defined connection set. CiA 301 also permits 29-bit extended identifiers, but does not require devices to support them, so a mixed bus may ignore or mis-handle extended frames.
Option 1: TPDO COB-IDs for Payloads up to 8 Bytes
PDOs are unconfirmed, broadcast-style producer/consumer objects and are the intended vehicle for process data. They accept any DLC from 0 to 8, which lets you size the frame to the payload instead of padding to 8.
- Assign your device a unique node ID in 1-127.
- Emit raw data on
0x180 + NodeID(TPDO1). Use TPDO2-4 at0x280/0x380/0x480 + NodeIDfor additional independent channels; four TPDOs give you 32 bytes per node in the pre-defined set. - Set the DLC to the actual number of valid bytes. Consumers should not assume 8.
- If a real CANopen master is present, describe the mapping in the device EDS so the master's PDO configuration (communication parameter
0x1800+n, mapping parameter0x1A00+n) matches what you transmit.
If you want your ad-hoc device to look like a standard CANopen node to a third-party master, model it on the CiA 401 generic I/O module profile and mimic its PDO layout as closely as your data allows. That gets you interoperability with off-the-shelf configuration tools without writing a custom profile.
Custom (non-profile) identifiers
CANopen does not forbid using identifiers outside the pre-defined set - PDO COB-IDs are themselves reconfigurable through sub-index 1 of 0x1800+n / 0x1400+n. The rules you must respect:
- The identifier must not collide with any COB-ID actually in use on the segment, including EMCY, heartbeat and SDO channels of nodes that are currently offline but will come online later.
- Lower numeric IDs win arbitration. Placing bulk raw traffic on a low ID (for example in the 0x101-0x17F gap) starves higher-ID PDOs and heartbeats under load. Put bulk traffic on a high ID such as the 0x780-0x7FF range if latency of the CANopen traffic matters.
- Document the reservation. An undocumented custom ID is the classic cause of a bus that works on the bench and fails when a fifth node is added.
Option 2: SDO for Payloads Larger Than 8 Bytes
SDO is a confirmed, peer-to-peer client/server service on 0x600 + NodeID (request) and 0x580 + NodeID (response). Its whole purpose is to move objects that do not fit in one frame. Three transfer modes exist, per the CiA SDO protocol description:
| Mode | Payload capacity | Per-frame data | Use when |
|---|---|---|---|
| Expedited | 1-4 bytes | 4 bytes in one request/response pair | Small scalar parameters |
| Segmented | Arbitrary length | 7 bytes per segment, alternating toggle bit | General bulk transfer, simple to implement |
| Block (download/upload) | Arbitrary length | 7 bytes per segment, up to 127 segments per block before acknowledgement | Throughput-critical bulk transfer |
Segmented transfer costs one response frame per data frame, so effective throughput is roughly 7 bytes per two frames. Block transfer amortises the handshake across up to 127 segments (889 bytes) and is the correct choice for firmware images, log dumps, or recorder buffers.
The Object Dictionary you cannot avoid
SDO addresses data by index/sub-index, so the server must implement at least a minimal object dictionary. Follow the layout in the CANopen internal device architecture:
-
0x1000-0x1FFF- communication profile area (device type, error register, SDO server parameters, heartbeat). Implement0x1000,0x1001, and0x1017at minimum. -
0x2000-0x5FFF- manufacturer-specific area. Put your raw data object here. -
0x6000-0x9FFF- standardized device and application profile parameters. Do not use this range for vendor payloads.
Declare the raw buffer with data type DOMAIN (0x000F). A DOMAIN entry carries an unbounded, uninterpreted byte stream and is exactly what SDO segmented and block transfer are designed to move. Example minimal declaration:
Index : 0x2000
Sub-index: 0x00
Name : Raw data buffer
Data type: DOMAIN (0x000F)
Access : rw
PDO map : no ; DOMAIN > 64 bits cannot be PDO-mapped
An object may be PDO-mapped only if it is 64 bits (8 bytes) or smaller. If part of your data must arrive cyclically and the rest in bulk, split it: a small mapped object for the cyclic slice, a DOMAIN object for the block.
Choosing Between PDO and SDO
| Criterion | PDO (TPDO COB-ID) | SDO (0x600/0x580 + NodeID) |
|---|---|---|
| Max payload | 8 bytes per frame, no fragmentation | Arbitrary, protocol-level fragmentation |
| Variable DLC | Yes (0-8) | No, frames are always 8 bytes |
| Confirmation | None (unconfirmed broadcast) | Confirmed, every request acknowledged |
| Addressing model | Producer/consumer | Client/server, point-to-point |
| Object dictionary required | Minimal (mapping parameters) | Yes, plus SDO server state machine |
| Latency | Low, single frame | Higher, handshake per segment or block |
| Implementation effort | Low | Significantly higher |
Rule of thumb: cyclic, latency-sensitive, small - PDO. Acyclic, large, must-not-be-lost - SDO. Fragmenting your own multi-frame protocol on top of PDOs is possible but you then re-implement toggle/sequence handling, timeout and abort semantics that SDO already specifies.
Diagnostics and Verification
- Scan before you allocate. Put a bus analyser on the segment with all nodes powered and running, capture for several minutes including a power-cycle, and list every observed identifier. Verify your chosen ID appears nowhere.
- Check offline nodes too. Compute the reserved COB-IDs for every node ID that could be commissioned later: EMCY 0x080+n, TPDO/RPDO 0x180-0x57F+n, SDO 0x580/0x600+n, heartbeat 0x700+n.
- Confirm DLC handling. Transmit a short PDO (for example DLC 3) and verify the consumer does not read stale bytes 4-8.
-
Test SDO aborts. A failed SDO transfer terminates with an abort code in the four data bytes of the abort frame. Common codes to handle:
0x05040000(SDO protocol timeout),0x06070010(data type does not match, length of service parameter does not match),0x06020000(object does not exist in the object dictionary). Log the raw 32-bit code - it identifies the fault precisely. - Load-test arbitration. Run the bulk transfer at full rate while monitoring heartbeat jitter. If heartbeats start timing out, your raw traffic has too low an identifier or too high a duty cycle; move it to a higher COB-ID or throttle the block size.
- Verify error counters. Rising transmit/receive error counters or bus-off events after adding the raw traffic usually indicate a duplicate identifier being transmitted by two nodes simultaneously, not a wiring fault.
FAQ
Which CANopen COB-ID should I use for custom raw data?
Use a TPDO COB-ID belonging to your node: 0x180 + NodeID for TPDO1, then 0x280/0x380/0x480 + NodeID. If you need an identifier outside the pre-defined connection set, the unclaimed gaps are roughly 0x101-0x180, 0x680-0x700 and 0x780-0x7FF, and you must verify no node on the segment uses them.
Can a CANopen frame carry fewer than 8 data bytes?
Only PDOs. PDOs may use any DLC from 0 to 8, which is why they are the right choice for short raw payloads. SDO, EMCY, NMT error control and SYNC use fixed frame layouts.
How do I send more than 8 bytes over CANopen?
Use SDO on 0x600 + NodeID / 0x580 + NodeID. Segmented transfer moves 7 data bytes per frame with an alternating toggle bit; block transfer streams up to 127 segments per block for higher throughput.
Where in the object dictionary should vendor-specific raw data live?
In the manufacturer-specific range 0x2000-0x5FFF, declared as data type DOMAIN (0x000F). The range 0x6000-0x9FFF is reserved for standardized device and application profile parameters and must not be used for vendor payloads.
Why does my SDO transfer abort with 0x06070010?
That abort code means the data type does not match or the length of the service parameter does not match - typically writing more bytes than the target object accepts, or using expedited transfer for an object larger than 4 bytes. Declare the target as DOMAIN and switch to segmented or block transfer.