SANPO SPI to CAN
SANPO SPI to CAN lets a Jetson, Raspberry Pi, or another host controller operate classic CAN devices in real time. The host sends a fixed-length SPI frame containing a CAN ID and data, then reads device feedback during later SPI transfers.
Use V47 or later firmware.
SPI Connection and Settings
Item |
Setting |
|---|---|
SPI mode |
Mode 0 |
Bit order |
MSB First |
Transfer length |
Exactly 23 bytes |
Initial test clock |
Start at 2 MHz |
Recommended frame gap |
At least 100 microseconds |
Check |
Byte 23 is CRC-8 over the first 22 bytes |
Board chip select CS1 controls CAN-1 and CAN-2; CS2 controls CAN-3 and CAN-4. On a Linux host, software CS0 and CS1 normally connect to the board connectors marked CS1 and CS2.
The factory CAN bitrate is 1 Mbps. To change and save it, use AT+SETCAN over USB as described in SANPO USB to CAN. For a temporary SPI-side change, see spi_set_baudrate.py. SPI-side temporary settings are lost after a power cycle.
Example: temporarily configure the first CAN channel selected by Linux SPI0 CS0 for 500 kbit/s:
sudo python3 spi_set_baudrate.py --spibus 0 --cs 0 --mode can --cannum 1 --baudrate 500000
The example supports 1000000, 500000, 250000, and 125000. Verify the result with actual CAN traffic. The setting is not written to Flash.
Select a CAN Channel
Board chip select |
Channel |
Target |
|---|---|---|
CS1 |
|
CAN-1 |
CS1 |
|
CAN-2 |
CS2 |
|
CAN-3 |
CS2 |
|
CAN-4 |
CS1 or CS2 |
|
Both CAN channels controlled by the selected MCU |
Received CAN frames use local channel numbering: 0x01 for the first channel and 0x02 for the second. Feedback from CAN-3 and CAN-4 through CS2 therefore also reports 0x01 and 0x02.
23-Byte SPI Frame
Each transfer contains a 22-byte message area and one CRC byte:
Position |
Content |
|---|---|
Bytes 0 to 20 |
CAN message; fill unused bytes with |
Byte 21 |
Valid message length |
Byte 22 |
CRC-8 over bytes 0 to 21 |
CRC-8 uses polynomial 0x07, initial value 0x00, and no final XOR:
def crc8(data: bytes) -> int:
crc = 0
for value in data:
crc ^= value
for _ in range(8):
crc = ((crc << 1) ^ 0x07) & 0xFF if crc & 0x80 else (crc << 1) & 0xFF
return crc
CAN Extended Frame
Byte position |
Content |
|---|---|
0 to 1 |
Fixed header |
2 |
Channel |
3 to 6 |
29-bit CAN ID, most significant byte first |
7 |
Data length |
8 to 15 |
CAN data; pad to 8 bytes with |
16 to 17 |
Fixed tail |
18 to 20 |
|
21 |
Fixed |
22 |
CRC-8 |
Example for Channel 1, extended ID 0x0000FD01, and 8 data bytes:
45 54 01 00 00 FD 01 08 01 00 00 00 00 00 00 00 0D 0A 00 00 00 12 FF
CAN Standard Frame
Byte position |
Content |
|---|---|
0 to 1 |
Fixed header |
2 |
Channel |
3 to 4 |
Fixed |
5 to 6 |
11-bit CAN ID, most significant byte first |
7 |
Data length |
8 to 15 |
CAN data; pad to 8 bytes with |
16 to 17 |
Fixed tail |
18 to 20 |
|
21 |
Fixed |
22 |
CRC-8 |
Example for Channel 1, standard ID 0x142, and 4 data bytes:
53 54 01 00 00 01 42 04 11 22 33 44 00 00 00 00 0D 0A 00 00 00 12 8C
Read CAN Feedback
SPI is full duplex. Data returned while sending a command may be an older cached frame or all 00. To wait for motor feedback, continue sending all-zero 23-byte polling frames after the command.
A valid reply should meet all of the following:
Total length is 23 bytes.
Byte 22 matches CRC-8 over the first 22 bytes.
Byte 21 gives the valid message length.
The valid message begins with
45 54or53 54.
Examples
Troubleshooting
Symptom |
Action |
|---|---|
Received data is all |
No feedback is queued; wait briefly and send another empty frame |
CRC fails |
Check Mode 0, MSB First, 23-byte transfers, and then lower the SPI clock or increase the frame gap |
CAN device does not reply |
Check chip select, Channel, CAN ID, bitrate, termination, wiring, and power |
Frame is sent on both CAN channels |
Channel is |
Occasional loss during continuous high-rate traffic |
Start at 2 MHz with a 100 microsecond frame gap and increase the rate only after stable testing |