SANPO USB to CAN
The SANPO USB to CAN protocol controls CAN devices from Windows or Linux and forwards CAN data without requiring an additional driver. The raw CAN 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 CAN device receives the raw CAN frame without a SANPO header or trailer.
For SANPO USB to CAN FD, see SANPO USB to FDCAN.
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 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 classic CAN to 1 Mbps. The 5 Mbps CAN FD data-phase value is the required second command argument and does not affect classic CAN communication.
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-1..CAN-4 configured successfully.")
To use another classic CAN bitrate, change only the first number 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 |
|---|---|
CAN bitrate |
|
CAN Extended Frame
Send and receive use the same format:
Field |
Length |
Description |
|---|---|---|
Header |
2 bytes |
Fixed |
Channel |
1 byte |
CAN interface number |
CAN ID |
4 bytes |
29-bit extended ID, big-endian |
Data length |
1 byte |
|
CAN data |
0-8 bytes |
Must match the preceding length |
Trailer |
2 bytes |
Fixed |
Send 8 data bytes to Channel 1 with extended ID 0x0000FD01:
45 54 01 00 00 FD 01 08 01 02 03 04 05 06 07 08 0D 0A
CAN Standard Frame
Send and receive use the same format:
Field |
Length |
Description |
|---|---|---|
Header |
2 bytes |
Fixed |
Channel |
1 byte |
CAN interface number |
Reserved |
2 bytes |
Fixed |
CAN ID |
2 bytes |
11-bit standard ID, big-endian |
Data length |
1 byte |
|
CAN data |
0-8 bytes |
Must match the preceding length |
Trailer |
2 bytes |
Fixed |
Send 4 data bytes to Channel 2 with standard ID 0x142:
53 54 02 00 00 01 42 04 11 22 33 44 0D 0A
Xiaomi CyberGear Example
Example program: USB to CAN CyberGear example
Windows example:
python usb2can_cybergear_demo_v8.py --port COM8 --motors 1 --channel 1
Linux example:
sudo python3 usb2can_cybergear_demo_v8.py --port /dev/ttyACM0 --motors 1 --channel 1
Adjust the serial port, interface, and motor ID for the actual installation.
Troubleshooting
Symptom |
Action |
|---|---|
USB serial port cannot be found |
Check the USB cable, power for the control section, and reconnect the board |
No reply after sending |
Check the USB port mapping, Channel, CAN ID, bit rate, termination resistors, wiring, and device power |
The frame is sent to both CAN interfaces |
Channel is |
Reply Channel differs from board CAN-3/4 |
Replies use local |
Reply format differs from this page |
Send |