SPI to CAN
The following protocol applies to firmware V4.1 or later
Send the AT+VER serial command to check the firmware version; if no version is returned or it shows V33, please update to the latest firmware
The SANPO Motor Debug Tool can generate test code quickly and supports CAN/RS485 motors
Protocol Overview
Use Case |
Wiring |
TX Protocol (SPI->CAN) |
RX Protocol (CAN->SPI) |
|---|---|---|---|
CAN extended frame motors (fixed 23-byte frames) |
CS1 controls CAN1/CAN2; CS2 controls CAN3/CAN4 |
Header 2 bytes (0x45 0x54) + Channel 1 byte + CANID ext 4 bytes + CAN data length (1 byte) + CAN data (fixed 8 bytes, pad with 0x00) + tail 2 bytes (0x0D 0x0A) + reserved 4 bytes (0x00 0x00 0x00 0x12) + CRC (1 byte) |
Header 2 bytes (0x45 0x54) + Channel 1 byte + CANID ext 4 bytes + CAN data length (1 byte) + CAN data (fixed 8 bytes, pad with 0x00) + tail 2 bytes (0x0D 0x0A) + reserved 4 bytes (0x00 0x00 0x00 0x12) + CRC (1 byte) |
CAN standard frame motors (fixed 23-byte frames) |
CS1 controls CAN1/CAN2; CS2 controls CAN3/CAN4 |
Header 2 bytes (0x53 0x54) + Channel 1 byte + fixed 2 bytes (0x00 0x00) + CANID std 2 bytes + CAN data length (1 byte) + CAN data (fixed 8 bytes, pad with 0x00) + tail 2 bytes (0x0D 0x0A) + reserved 4 bytes (0x00 0x00 0x00 0x12) + CRC (1 byte) |
Header 2 bytes (0x53 0x54) + Channel 1 byte + fixed 2 bytes (0x00 0x00) + CANID std 2 bytes + CAN data length (1 byte) + CAN data (fixed 8 bytes, pad with 0x00) + tail 2 bytes (0x0D 0x0A) + reserved 4 bytes (0x00 0x00 0x00 0x12) + CRC (1 byte) |
CAN Extended Frame Example
TX frame
Header (2 bytes) |
Channel (1 byte) |
CANID (4 bytes) |
Data length (1 byte) |
Data (fixed 8 bytes) |
Tail (2 bytes) |
Reserved 4 bytes |
CRC (1 byte) |
|---|---|---|---|---|---|---|---|
0x45 0x54 |
0x00 |
0x00 0x00 0xFD 0x01 |
0x08 |
0x48 0x45 0x4C 0x4F 0x00 0x00 0x00 0x00 |
0x0D 0x0A |
0x00 0x00 0x00 0x12 |
0x0F |
SPI (CS1) controls CAN1/CAN2; SPI (CS2) controls CAN3/CAN4 (see the label on the back of the board)
Channel can be 0x01/0x02/0x03/0x04; 0x00 broadcasts to all CAN channels on the current SPI CS
Extended CANID is 29-bit, stored in the low 29 bits of the 4-byte CANID field
RX frame
Header (2 bytes) |
Channel (1 byte) |
CANID (4 bytes) |
Data length (1 byte) |
Data (fixed 8 bytes) |
Tail (2 bytes) |
Reserved 4 bytes |
CRC (1 byte) |
|---|---|---|---|---|---|---|---|
0x45 0x54 |
0x00 |
0x00 0x00 0x01 0xFE |
0x08 |
0x48 0x45 0x4C 0x4F 0x01 0x02 0x03 0x04 |
0x0D 0x0A |
0x00 0x00 0x00 0x12 |
0x0F |
Code Snippet (SPI to CAN Extended Frame, Python)
Full example: spi_cybergear_demo_v4.py
FRAME_HEADER = [0x45, 0x54]
FRAME_TAIL = [0x0D, 0x0A]
RESERVED = [0x00, 0x00, 0x00, 0x12]
# TX (header 2 bytes + channel 1 byte + CANID 4 bytes + DLC 1 byte + data + tail 2 bytes + reserved 4 bytes + CRC)
def build_spi_ext_frame(channel: int, arbitration_id: int, data: list[int]) -> list[int]:
ext_id = [
(arbitration_id >> 24) & 0xFF,
(arbitration_id >> 16) & 0xFF,
(arbitration_id >> 8) & 0xFF,
arbitration_id & 0xFF,
]
data_bytes = (data + [0x00] * 8)[:8]
payload = (
FRAME_HEADER
+ [channel & 0xFF]
+ ext_id
+ [len(data[:8])]
+ data_bytes
+ FRAME_TAIL
+ RESERVED
)
crc = crc8_calculator.calculate(payload)
return payload + [crc]
tx = build_spi_ext_frame(channel=0x00, arbitration_id=0x0000FD01, data=[0x01] + [0x00] * 7)
rx = spi.xfer2(tx) # fixed length 23 bytes
# RX parse (header 2 bytes + channel 1 byte + CANID 4 bytes + DLC 1 byte + data + tail 2 bytes + reserved 4 bytes + CRC)
data_part = rx[:22]
crc_ok = crc8_calculator.calculate(data_part) == rx[22]
CAN Standard Frame Example
TX frame
Header (2 bytes) |
Channel (1 byte) |
Reserved 2 bytes |
CANID (2 bytes) |
Data length (1 byte) |
Data (fixed 8 bytes) |
Tail (2 bytes) |
Reserved 4 bytes |
CRC (1 byte) |
|---|---|---|---|---|---|---|---|---|
0x53 0x54 |
0x00 |
0x00 0x00 |
0x01 0x42 |
0x08 |
0x48 0x45 0x4C 0x4F 0x01 0x02 0x03 0x04 |
0x0D 0x0A |
0x00 0x00 0x00 0x12 |
0x0F |
SPI (CS1) controls CAN1/CAN2; SPI (CS2) controls CAN3/CAN4
Channel can be 0x01/0x02/0x03/0x04; 0x00 broadcasts to all CAN channels on the current SPI CS
Standard CANID is 11-bit, stored in the low 11 bits of the 2-byte CANID field
RX frame
Header (2 bytes) |
Channel (1 byte) |
Reserved 2 bytes |
CANID (2 bytes) |
Data length (1 byte) |
Data (fixed 8 bytes) |
Tail (2 bytes) |
Reserved 4 bytes |
CRC (1 byte) |
|---|---|---|---|---|---|---|---|---|
0x53 0x54 |
0x00 |
0x00 0x00 |
0x01 0x42 |
0x08 |
0x48 0x45 0x4C 0x4F 0x01 0x02 0x03 0x04 |
0x0D 0x0A |
0x00 0x00 0x00 0x12 |
0x0F |