SANPO SPI 转 CAN
SANPO SPI 转 CAN 适合 Jetson、树莓派或其他主控板周期控制经典 CAN 设备。主控板通过固定长度 SPI 报文发送 CAN数据,并在当前或后续传输中读取设备反馈。
SANPO SPI 转 CAN FD 请阅读 SANPO SPI 转 FDCAN。
SPI 参数
项目 |
设置 |
|---|---|
SPI 参数 |
Mode 0(CPOL=0、CPHA=0) |
数据顺序 |
MSB First |
SHORT模式 |
22 字节报文区 + 1 字节 CRC,共 23 字节 |
LONG模式 |
72 字节报文区 + 1 字节 CRC,共 73 字节 |
CRC-8 |
多项式 |
出厂默认 SHORT。经典 CAN 可使用 SHORT 或者 LONG,但是 CAN FD 只能在 LONG 下使用。如果不使用 FDCAN,建议使用开销更小的 SHORT。
切换模式:
接入开发板USB,Windows 会出现 6 个 USB 串口,其中2个为管理口,对应两颗STM32 MCU,通常在Windows中显示为 SANPO Studio Management Port。分别连接2个管理口,发送如下串口指令,进行模式设置:
AT+SPIMODE=SHORT
AT+SPIMODE=LONG
命令通过管理口按文本发送并以 CRLF 结尾。设置成功后会保存;主控应等待至少 10 ms,再使用新长度通信。
示例程序:发送数据前设置 CAN 波特率 和 SPI 模式
安装依赖:
python3 -m pip install pyserial
下面的示例代码会自动找到2个管理口,并向两颗 MCU 发送 CAN-1~CAN-4 波特率配置。示例把经典 CAN 设置为 1 Mbps,并把两颗 MCU 都切换为 SHORT。
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 = "SHORT"
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 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-1..CAN-4 and SPI SHORT configured successfully.")
需要其他经典 CAN 波特率时,同时修改 FDCAN_VALUE 和 FDCAN_COMMAND 中
的第一个数字;如果 SPI 程序按 73 字节通信,把 SPI_MODE 改为 LONG。
5 Mbps 数据段参数不影响经典 CAN。代码中打开管理口使用的 1_000_000 只是 STM32 CDC 串口参数,不会设置 CAN 或 RS485 的物理波特率;物理总线参数只由明确的 AT+SETFDCAN 命令修改。
参数 |
支持值 |
|---|---|
CAN波特率 |
|
选择 CAN 接口
SPI 片选 |
Channel |
发送目标 |
|---|---|---|
CS0(第一颗STM32) |
|
CAN-1 |
CS0(第一颗STM32) |
|
CAN-2 |
CS1(第二颗STM32) |
|
CAN-3 |
CS1(第二颗STM32) |
|
CAN-4 |
CS0或者CS1(任意片选) |
|
当前 STM32 的两路 CAN |
发送时 1/3 都选择当前 STM32 的第一路,2/4 都选择第二路。回包只使用本地 Channel 1/2。主控中的 CS0、CS1 通常分别连接开发板的两个外部片选。注意:主控和开发板需要共地,接入两个GND可保证数据传输稳定性。
SHORT 模式
扩展帧
字节位置 |
内容 |
|---|---|
0~1 |
固定 |
2 |
Channel |
3~6 |
29 位 CAN ID,高字节在前 |
7 |
数据长度 |
8~15 |
8 字节数据区,未使用部分填 |
16~21 |
填 |
22 |
前 22 字节的 CRC-8 |
标准帧
字节位置 |
内容 |
|---|---|
0~1 |
固定 |
2 |
Channel |
3~4 |
固定 |
5~6 |
11 位 CAN ID,高字节在前 |
7 |
数据长度 |
8~15 |
8 字节数据区,未使用部分填 |
16~21 |
填 |
22 |
前 22 字节的 CRC-8 |
例如,向 Channel 1、扩展帧 ID 0x0000FD01 发送 8 字节数据时,CRC 前的 22 字节为:
45 54 01 00 00 FD 01 08 01 02 03 04 05 06 07 08 00 00 00 00 00 00
LONG 模式
LONG 的前 8 字节与 SHORT 相同,字节 8~71 为固定 64 字节数据区,最后的字节 72 为 CRC-8。经典 CAN 只使用数据区前 0~8 字节,其余填 00。
类型 |
字节 0~7 |
字节 8~71 |
字节 72 |
|---|---|---|---|
扩展帧 |
|
64 字节数据区 |
CRC-8 |
标准帧 |
|
64 字节数据区 |
CRC-8 |
计算 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
SHORT 对前 22 字节计算,LONG 对前 72 字节计算,然后把结果放在最后一个字节。
读取 CAN 反馈
SPI 是全双工接口。发送控制报文时同时读到的内容可能是之前缓存的反馈,也可能全为 00。发送后如果没有立即读到目标反馈,应继续发送同长度的全 00 空帧进行轮询。
有效反馈满足:
总长度与当前 SHORT/LONG 模式一致。
最后一个字节 CRC 正确。
报文区以
45 54或53 54开头。Channel 为本地
01或02。字节 7 给出有效 CAN 数据长度。
一次读取不是一次请求的固定应答;主控应根据 CAN ID 和电机协议识别反馈。
开发示例
常见问题
现象 |
处理方法 |
|---|---|
收到全 |
当前没有排队的反馈;稍后继续发送同长度空帧读取 |
CRC 错误 |
检查 Mode 0、MSB First、固定传输长度、SPI 时钟和片选时序 |
CAN 设备没有回复 |
检查片选、Channel、CAN ID、波特率、终端电阻、接线和供电 |
报文发到两路 CAN |
Channel 为 |
切换 LONG 后完全无响应 |
主控仍在发送 23 字节;改为每次完整发送 73 字节 |