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

0x01

CAN-1

CS1

0x02

CAN-2

CS2

0x03

CAN-3

CS2

0x04

CAN-4

CS1 or CS2

0x00

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 00

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 45 54

2

Channel

3 to 6

29-bit CAN ID, most significant byte first

7

Data length 0 to 8

8 to 15

CAN data; pad to 8 bytes with 00

16 to 17

Fixed tail 0D 0A

18 to 20

00

21

Fixed 12, meaning the valid message is 18 bytes

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 53 54

2

Channel

3 to 4

Fixed 00 00

5 to 6

11-bit CAN ID, most significant byte first

7

Data length 0 to 8

8 to 15

CAN data; pad to 8 bytes with 00

16 to 17

Fixed tail 0D 0A

18 to 20

00

21

Fixed 12

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 54 or 53 54.

Examples

Troubleshooting

Symptom

Action

Received data is all 00

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 0x00; select a specific channel

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