SANPO SPI to FDCAN
SANPO SPI to FDCAN is suitable for periodic control of FDCAN devices from a Jetson, Raspberry Pi, or another host controller. CAN FD is available only in SPI LONG mode, with a fixed 73-byte transfer.
For SANPO SPI to classic CAN, see SANPO SPI to CAN.
Switch to LONG Before Use
The factory default SPI mode is SHORT, which cannot transmit or receive CAN FD.
First switch the mode through a management port. After connecting USB, Windows
shows six USB serial ports; two are management ports for the two STM32 MCUs and
are normally displayed as SANPO Studio Management Port. Connect to each
management port and send:
AT+SPIMODE=LONG
Use CRLF as the line ending. After receiving OK, wait at least 10 ms before
sending 73-byte SPI frames. The setting is saved to Flash and remains LONG after
power-up.
Also query the CAN FD bit rates:
AT+SETFDCAN?
The factory defaults are a 1 Mbps arbitration phase and 5 Mbps data phase, with BRS enabled. The data phase can be set to 2, 3, 4, or 5 Mbps; see Communication Settings.
Automatically Configure All CAN FD Bitrates and SPI LONG (Recommended)
Install the dependency:
python3 -m pip install pyserial
The complete code below automatically finds both management ports of one connected SPINE V8 and sends the same settings to both MCUs. It therefore configures CAN-1 through CAN-4 without requiring any management-port, SPI chip select, or board-interface mapping. This example configures a 1 Mbps arbitration phase, a 5 Mbps data phase, and ensures that both MCUs use the LONG mode required by CAN FD.
import re
import time
import serial
from serial.tools import list_ports
FDCAN_VALUE = "FDCAN:1000000,5000000"
FDCAN_COMMAND = "AT+SETFDCAN=1000000,5000000"
SPI_MODE = "LONG"
def is_management_port(port):
if (port.vid, port.pid) != (0x1209, 0x2323):
return False
info = " ".join(str(getattr(port, name, "") or "")
for name in ("hwid", "interface", "location")).upper()
return ("MI_01" in info or "MANAGEMENT PORT" in info or
re.search(r":(?:X|\d+)\.1(?:\D|$)", info) is not None)
def request(stream, command):
stream.reset_input_buffer()
stream.write(command.encode("ascii") + b"\r\n")
stream.flush()
deadline, reply = time.monotonic() + 1.0, bytearray()
while time.monotonic() < deadline:
reply.extend(stream.read(stream.in_waiting or 1))
if b"OK\r\n" in reply or b"ERR," in reply:
break
text = reply.decode("ascii", errors="replace").strip()
if not text:
raise RuntimeError(f"{stream.port}: {command}: no reply")
print(f"{stream.port}: {command} -> {text}")
return text
def has_line(reply, expected):
return expected in {line.strip() for line in reply.splitlines()}
ports = sorted(p.device for p in list_ports.comports()
if is_management_port(p))
if len(ports) != 2:
raise SystemExit(f"Expected 2 SANPO management ports, found {len(ports)}: {ports}")
for device in ports:
with serial.Serial(device, 1_000_000, timeout=0.05, write_timeout=1) as stream:
time.sleep(0.05)
if not has_line(request(stream, "AT+SETFDCAN?"), FDCAN_VALUE):
if not has_line(request(stream, FDCAN_COMMAND), "OK"):
raise RuntimeError(f"{device}: failed to configure CAN FD bitrate")
if not has_line(request(stream, "AT+SPIMODE?"), f"SPIMODE:{SPI_MODE}"):
if not has_line(request(stream, f"AT+SPIMODE={SPI_MODE}"), "OK"):
raise RuntimeError(f"{device}: failed to configure SPI mode")
print("CAN/FDCAN-1..CAN/FDCAN-4 and SPI LONG configured successfully.")
To use other CAN FD bitrates, change the arbitration and data-phase numbers in
both FDCAN_VALUE and FDCAN_COMMAND. The data phase supports 2, 3, 4, and
5 Mbps. The 1_000_000 used to open the management port is only an STM32 CDC
serial-port parameter and does not configure the physical CAN or RS485 bitrate.
Physical bus parameters change only through the explicit AT+SETFDCAN command.
Parameter |
Supported values |
|---|---|
Arbitration phase |
|
Data phase |
|
SPI Settings
Item |
Setting |
|---|---|
SPI mode |
Mode 0 (CPOL=0, CPHA=0) |
Bit order |
MSB First |
Transfer |
72-byte payload + 1-byte CRC, 73 bytes total |
CRC-8 |
Polynomial |
Select a CAN FD Interface
SPI chip select |
Channel |
Target |
|---|---|---|
CS0 (first STM32) |
|
CAN-1 |
CS0 (first STM32) |
|
CAN-2 |
CS1 (second STM32) |
|
CAN-3 |
CS1 (second STM32) |
|
CAN-4 |
CS0 or CS1 (either chip select) |
|
Both CAN FD interfaces on the current STM32 |
When sending, 1/3 select the first interface on the selected STM32 and 2/4
select the second. Replies use local Channel 1/2 on the current STM32.
CS0 and CS1 on the host normally connect to the board’s two external chip
selects. The host and board must share ground; two GND connections are
recommended for stable data transfer.
Data Length Code
Code |
Bytes |
Code |
Bytes |
|---|---|---|---|
|
0 |
|
8 |
|
1 |
|
12 |
|
2 |
|
16 |
|
3 |
|
20 |
|
4 |
|
24 |
|
5 |
|
32 |
|
6 |
|
48 |
|
7 |
|
64 |
73-Byte Message
Extended Frame
Byte positions |
Content |
|---|---|
0-1 |
Fixed |
2 |
Channel |
3-6 |
29-bit CAN ID, big-endian |
7 |
Length code |
8-71 |
Fixed 64-byte data area; unused bytes are |
72 |
CRC-8 of the first 72 bytes |
Standard Frame
Byte positions |
Content |
|---|---|
0-1 |
Fixed |
2 |
Channel |
3-4 |
Fixed |
5-6 |
11-bit CAN ID, big-endian |
7 |
Length code |
8-71 |
Fixed 64-byte data area; unused bytes are |
72 |
CRC-8 of the first 72 bytes |
For example, when sending 16 bytes to Channel 1 with extended ID
0x1F100101, the first 24 bytes are:
45 46 01 1F 10 01 01 0A
01 01 01 01 42 00 00 00 01 01 64 00 78 56 34 12
Append 48 00 bytes and then the CRC-8 calculated over the first 72 bytes.
Calculate CRC
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
Read CAN FD Feedback
SPI is full duplex. Data read while sending a command may be old feedback or all zeros. Continue sending 73-byte empty frames to read later feedback.
For each non-zero frame, check in order:
The byte-73 CRC equals the calculated CRC of bytes 0-71.
The header is
SForEF.Channel is
1/2on the current STM32.The length code is valid.
The CAN ID and data match the target device protocol.
Troubleshooting
Symptom |
Action |
|---|---|
All bytes are |
No feedback is queued; continue polling with a 73-byte empty frame |
No response in 23-byte mode |
CAN FD requires LONG; after switching, send 73 bytes per transfer |
CRC error |
Check Mode 0, MSB First, fixed length, SPI clock, and chip-select timing |
CAN FD device does not reply |
Check arbitration/data rates, BRS, Channel, ID, termination resistors, wiring, and power |
Length code is correct but data is incomplete |
Fill the complete 64-byte data area and pad unused bytes with |