SANPO SPI 转 RS485

SANPO SPI 转 RS485 适合 Jetson、树莓派或其他主控板周期控制 RS485 电机和设备。主控板把设备原始指令放入固定长度的 RT 报文,并在当前或后续 SPI 传输中读取反馈。

出厂默认 RS485 参数为 4 Mbps、1 个停止位、无校验、8 个数据位。但是某些 Linux 串口枚举会改变当前运行中的参数,因此,建议每次启动 SPI 控制程序、发送业务数据前,通过管理串口 SANPO Studio Management Port 显式重设 RS485 参数;SPI 数据通道本身不接收文本 AT+ 命令。

发送 AT+ 命令时使用文本模式,并把发送结束符设置为 CRLF。不要把字符 \r\n 当作普通文本发送。

AT+SETRS485=<波特率>,<停止位>,<校验>,<数据位>

例如,设置为宇树电机常用的 4 Mbps、8-N-1:

AT+SETRS485=4000000,1,0,8

示例程序:发送数据前设置 RS485 波特率 和 SPI 模式

安装依赖:

python3 -m pip install pyserial

下面的示例代码会自动找到并通过两个管理口设置 RS485-1~RS485-4 的波特率。示例程序设置为宇树电机常用的 4 Mbps、8-N-1,并把两颗 STM32 MCU 都切换为适合 17 字节指令的 SHORT 模式。

import re
import time
import serial
from serial.tools import list_ports

RS485_COMMAND = "AT+SETRS485=4000000,1,0,8"
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, RS485_COMMAND), "OK"):
            raise RuntimeError(f"{device}: failed to configure RS485 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("RS485-1..RS485-4 and SPI SHORT configured successfully.")

需要其他串口参数时,修改 RS485_COMMAND 中的四个数字;RS485 指令超过 18 字节时,把 SPI_MODE 改为 LONG,并让 SPI 程序改用 73 字节传输。代码中打开管理口使用的 1_000_000 只是 STM32 CDC 串口参数,不会设置 CAN 或 RS485 的物理波特率;物理总线参数只由明确的 AT+SETRS485 命令修改。

参数

支持值

波特率

24004000000, 5000000,6000000,7500000

停止位

12

校验

0 无校验;1 奇校验;2 偶校验

数据位

89

SPI 参数和长度

项目

SHORT

LONG

总传输长度

23 字节

73 字节

报文区

22 字节

72 字节

RS485 数据区

18 字节

68 字节

最后一个字节

CRC-8

CRC-8

两种模式都使用 SPI Mode 0、MSB First。CRC-8 多项式为 0x07,初始值 0x00,无结果异或。

选择 RS485 接口

SPI 片选

Channel

发送目标

CS0(第一颗 STM32)

0x01

RS485-1

CS0(第一颗 STM32)

0x02

RS485-2

CS1(第二颗 STM32)

0x03

RS485-3

CS1(第二颗 STM32)

0x04

RS485-4

CS0或者CS1(任意片选)

0x00

当前 MCU 的两路 RS485

发送时 1/3 都选择当前 STM32 的第一路,2/4 都选择第二路。回包只使用当前 STM32 Channel 1/2。主控中的 CS0、CS1 通常分别连接开发板的两个外部片选。注意:主控和开发板需要共地,接入两个GND可保证数据传输稳定性。

SHORT 报文

发送和接收使用相同布局:

字节位置

内容

0~1

固定 52 54,即 RT

2

Channel

3

RS485 数据长度 018

4~21

18 字节 RS485 数据区,未使用部分填 00

22

前 22 字节的 CRC-8

将 17 字节宇树控制帧发送到 Channel 2 时,CRC 前的报文区为:

52 54 02 11 FE EE 11 00 00 00 00 00 00 00 00 00 00 00 00 18 7C 00

最后增加对这 22 字节计算得到的 CRC-8。

LONG 报文

字节位置

内容

0~1

固定 52 54,即 RT

2

Channel

3

RS485 数据长度 068

4~71

68 字节 RS485 数据区,未使用部分填 00

72

前 72 字节的 CRC-8

CRC 算法与 SANPO SPI 转 CAN相同。

读取 RS485 反馈

开发板收到 RS485 数据后,会使用同样的 RT 布局放入 SPI 发送队列:

  • 字节 2 表示反馈来自当前 MCU 的第一路或第二路 RS485。

  • 字节 3 表示本次反馈的数据长度。

  • 数据从字节 4 开始。

  • 未使用的数据区填 00

SPI 是全双工接口。发送控制帧时读到的可能是旧数据或全 00;如果还没有目标反馈,应继续发送同长度的全零空帧读取。

一次 RS485 接收事件不保证正好对应一条完整的电机协议帧。主控应使用 Channel 区分总线,再按照电机协议的帧头、长度和校验重组数据。

宇树 GO-M8010 示例

完整示例:SPI 转 RS485 宇树 GM8010 使用说明

sudo python3 spi2rs485_unitree_GM8010_demo_v8.py \
  --spibus 0 \
  --cs 0 \
  --motors 1

参数名称以示例程序当前帮助信息为准。首次运行时应降低运动幅度并预留立即断电的安全措施。

常见问题

现象

处理方法

收到全 00

当前没有排队反馈;稍后继续发送同长度空帧

回包 Channel 为 1/2

回包使用当前 MCU 的本地编号,属于正常行为

17 字节指令在 SHORT 可用,较长指令失败

SHORT 最多 18 字节;切换 LONG

CRC 错误

检查 Mode 0、MSB First、固定长度、SPI 时钟和片选时序

设备没有回复

检查片选、Channel、RS485 参数、设备 ID、指令校验、A/B 接线和供电

反馈被拆成多段

按设备协议在主控端进行重组,不要把一次 SPI 读取视为固定的一条电机回包