SANPO USB to FDCAN
The SANPO USB to FDCAN protocol controls FDCAN devices from Windows or Linux and forwards FDCAN data without requiring an additional driver. The raw FDCAN frame is wrapped with a SANPO header and trailer for transmission over the USB management serial port. These extra bytes are used only by the board; the FDCAN device receives the raw FDCAN frame without a SANPO header or trailer.
For SANPO USB to classic CAN, see SANPO USB to CAN.
Connection
After connecting the board over USB, six USB serial ports appear. Two management
ports carry this protocol; Windows normally displays them as
SANPO Studio Management Port. One management port accesses CAN-1/2 and the
other accesses CAN-3/4. The other four ports are independent RS485 serial
ports. COM and /dev/ttyACM* numbering is not guaranteed to follow the board
silkscreen; initially connect only one CAN device to confirm the mapping.
On Linux, you can run the Python serial-port identification example to identify the two management ports.
Automatically Configure All CAN FD Bitrates (Recommended)
Install the dependency:
python -m pip install pyserial
The complete code below automatically finds both management ports of one connected SPINE V8 on Windows or Linux. It sends the same setting to both MCUs, thereby configuring CAN-1 through CAN-4 without requiring any management-port or board-interface mapping. This example configures a 1 Mbps arbitration phase and a 5 Mbps data phase.
import re
import time
import serial
from serial.tools import list_ports
COMMANDS = ["AT+SETFDCAN=1000000,5000000"]
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 send_at(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 "OK" not in text:
raise RuntimeError(f"{stream.port}: {command}: {text or 'no reply'}")
print(f"{stream.port}: {command} -> {text}")
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)
for command in COMMANDS:
send_at(stream, command)
print("CAN/FDCAN-1..CAN/FDCAN-4 configured successfully.")
To use other bitrates, change the arbitration and data-phase numbers in
COMMANDS. The 1_000_000 used to open the management port is only a CDC API
parameter; it does not set the physical CAN or RS485 bitrate. Only the explicit
AT+SETFDCAN command changes the physical bus settings.
Parameter |
Supported values |
|---|---|
Arbitration phase |
|
Data phase |
|
Data Length Code
The CAN FD data-frame length field uses this code and supports up to 64 data bytes:
Code |
Data bytes |
|---|---|
|
0 |
|
1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
12 |
|
16 |
|
20 |
|
24 |
|
32 |
|
48 |
|
64 |
CAN FD Extended Frame
Send and receive use the same format:
Field |
Length |
Description |
|---|---|---|
Header |
2 bytes |
Fixed |
Channel |
1 byte |
CAN FD interface number |
CAN ID |
4 bytes |
29-bit extended ID, big-endian |
Length code |
1 byte |
|
Data |
0-64 bytes |
Byte count is determined by the length code |
Trailer |
2 bytes |
Fixed |
Send 16 data bytes to Channel 1 with extended ID 0x1F100101:
45 46 01 1F 10 01 01 0A
01 01 01 01 42 00 00 00 01 01 64 00 78 56 34 12
0D 0A
Here 0A indicates a 16-byte data area.
CAN FD Standard Frame
Send and receive use the same format:
Field |
Length |
Description |
|---|---|---|
Header |
2 bytes |
Fixed |
Channel |
1 byte |
CAN FD interface number |
Reserved |
2 bytes |
Fixed |
CAN ID |
2 bytes |
11-bit standard ID, big-endian |
Length code |
1 byte |
|
Data |
0-64 bytes |
Byte count is determined by the length code |
Trailer |
2 bytes |
Fixed |
Send 12 data bytes to Channel 2 with standard ID 0x142:
53 46 02 00 00 01 42 09
01 02 03 04 05 06 07 08 09 0A 0B 0C
0D 0A
Parse Replies
Motor feedback uses the same SF or EF format. Use the header to distinguish
standard and extended frames, the length code to determine the data length, and
Channel to identify the first or second interface on the current MCU.
USB is a byte stream. One serial read may contain a partial frame, a complete
frame, or multiple frames. Parse continuously by header, length code, and
trailer; do not assume that one read() returns exactly one frame.
SANPO J8108-R8 FDCAN Example
Example program: read-only management-protocol J8108-R8 FDCAN probe
After installing pyserial, run the program. It automatically finds both
SANPO management ports and probes local Channels 1 and 2 on each MCU, covering
all four CAN/FDCAN paths:
python3 usb2fdcan_j8108_demo_v8.py
The program sends AT+SETFDCAN=1000000,5000000 to both management ports before
probing all four local Channel paths. It sends only SANPO FDCAN v1 HELLO and
STATE_REQUEST messages; it
never enables the motor or sends a motion target. With V82, applying unchanged
FDCAN parameters restores the live controllers without writing Flash again.
Use --list-only to enumerate management ports without configuring bitrates or
sending a test frame.
The J8108-R8 must already be configured and rebooted into the SANPO FDCAN Profile; the example does not switch or write the motor Profile.
If USB metadata is incomplete, or to narrow troubleshooting to one path, specify a Linux management port and local Channel explicitly:
python3 usb2fdcan_j8108_demo_v8.py \
--ports /dev/ttyACM0 \
--channels 1 \
--node-id 1
For an existing J8108-R8 configured for 1M/2M, add
--data-bitrate 2000000. MISS is expected on port/channel paths without a
motor. See the J8108-R8 FDCAN demo guide
for complete instructions.
Troubleshooting
Symptom |
Action |
|---|---|
CAN FD does not reply |
Check arbitration/data rates, BRS, termination resistors, and whether the other device supports CAN FD |
Reply Channel is only 1/2 |
Replies use local numbering on the current MCU; this is normal |
Multiple frames are concatenated |
Parse continuously by header, length code, and trailer instead of serial read boundaries |