文件最后提交记录最后更新时间
add spi comm test 2 个月前
update 3 个月前
update 3 个月前
README.md

# SANPO兴普智能 - 机器人集成开发板

  • 如下协议定义只适用于固件V4.1版本及以后
  • 发送AT+VER(以\r\n结尾)串口指令,可以查询固件版本,如未返回版本号低于V42,请下载最新固件
  • 支持SANPO电机调试工具快速生成调试代码。支持全系列CAN/RS485协议电机,包括小米,宇树,灵足,达秒,脉塔,翎控等

目录

  1. USB转CAN协议说明
  2. USB转RS485协议说明
  3. SPI转CAN协议说明
  4. SPI转RS485协议说明
  5. 开发板可编程功能(批量指令离线自动执行)
  6. SPI配置功能
  7. 其他配置功能

简称定义

  • CAN Channel: 开发板上CAN接口的编号

    CAN1 CAN2 CAN3 CAN4
    0x01 0x02 0x03 0x04
  • RS485 Channel:开发板上RS485接口的编号

    RS485-1 R485-2 RS485-3 RS485-4
    0x01 0x02 0x03 0x04

1. USB转CAN协议说明

适用场景
USB发送协议(USB->CAN) USB接收协议(CAN->USB)
CAN扩展帧电机 固定帧头2个字节(0x45 0x54) + Channel 1个字节 + 扩展帧CANID4个字节 + 数据长度(1字节) + 数据(最大8字节) + 固定帧尾2个字节(0x0D 0x0A) 固定帧头2个字节(0x45 0x54) + Channel 1个字节 + 扩展帧CANID4个字节 + 数据长度(1字节) + 数据(最大8字节) + 固定帧尾2个字节(0x0D 0x0A)
CAN标准帧电机 固定帧头2个字节(0x53 0x54) + Channel 1个字节 + 预留2个字节(0x00 0x00) + CANID标准帧2个字节 + 数据长度(1字节) + 数据(最大8字节) + 固定帧尾2个字节(0x0D 0x0A) 固定帧头2个字节(0x53 0x54) + Channel 1个字节 + 预留2个字节(0x00 0x00) + CANID标准帧2个字节 + 数据长度(1字节) + 数据(最大8字节) + 固定帧尾2个字节(0x0D 0x0A)

CAN扩展帧报文示例

发送报文

固定帧头(2字节) Channel(1个字节) CANID(4字节) 数据长度(1字节) 数据(最大8字节) 固定帧尾(2字节)
0x45 0x54 0x00 0x01 0x7F 0xFF 0x07 0x08 0x7F 0x2A 0x7F 0xFF 0x05 0x1E 0x19 0x99 0x0D 0x0A
  • 接入USB后,会显示2个串口,两个串口分别对应STM32(1)和STM32(2),请查看开发板背面的说明,每个串口控制2路CAN接口
  • Channel为0x01或者0x02或者0x03或者0x04, 指定向哪个CAN接口发送报文,如果为0x00,将向当前串口上的所有CAN接口发送报文
  • 扩展帧CANID为29bit,占据CANID 4个字节的低29bit

接收报文

固定帧头(2字节) Channel(1个字节) CANID(4字节) 数据长度(1字节) 数据(最大8字节) 固定帧尾(2字节)
0x45 0x54 0x00 0x02 0x00 0x09 0xFD 0x08 0x7F 0xF9 0x7F 0xDF 0x7F 0xFF 0x01 0x11 0x0D 0x0A

代码片段(CAN扩展帧,Python)

(完整样例请参考 usb2can_cybergear_sine_demo_v4.py

HEADER_NORMAL_EXT = bytes([0x45, 0x54])
TAIL = bytes([0x0D, 0x0A])

# 发送(帧头2字节 + channel1字节 + CANID4字节 + DLC1字节 + data + 帧尾2字节)
def build_extended_frame(channel: int, arbitration_id: int, data: list[int]) -> bytes:
    can_id_bytes = arbitration_id.to_bytes(4, byteorder="big", signed=False)
    payload = bytearray(HEADER_NORMAL_EXT)
    payload.append(channel & 0xFF)
    payload.extend(can_id_bytes)
    data_bytes = bytearray(data[:8])
    payload.append(len(data_bytes))
    payload.extend(data_bytes)
    payload.extend(TAIL)
    return bytes(payload)


payload = build_extended_frame(channel=0x00, arbitration_id=0x0000FD01, data=[0x01] + [0x00] * 7)
ser.write(payload)
ser.flush()

# 接收(帧头2字节 + channel1字节 + CANID4字节 + DLC1字节 + data + 帧尾2字节)
rx = ser.read(ser.in_waiting or 1)
if rx[:2] == HEADER_NORMAL_EXT and rx[-2:] == TAIL:
    channel = rx[2]
    can_id = int.from_bytes(rx[3:7], byteorder="big", signed=False)
    dlc = rx[7]
    data = rx[8 : 8 + dlc]
    print(channel, f"0x{can_id:08X}", dlc, list(data))

CAN标准帧报文示例

发送报文

固定帧头(2字节) Channel(1个字节) 预留2个字节 CANID(2字节) 数据长度(1字节) 数据(最大8字节) 固定帧尾(2字节)
0x53 0x54 0x00 0x00 0x00 0x01 0x42 0x08 0x48 0x45 0x4C 0x4F 0x01 0x02 0x03 0x04 0x0D 0x0A
  • 接入USB后,会显示2个串口,两个串口分别对应STM32(1)和STM32(2),请查看开发板背面的说明,每个串口控制2路CAN接口
  • Channel为0x01或者0x02或者0x03或者0x04, 指定向哪个CAN接口发送报文,如果为0x00,将向当前串口上的所有CAN接口发送报文
  • 标准帧CANID为11bit,占据CANID 2个字节的低11bit

接收报文

固定帧头(2字节) Channel(1个字节) 预留2个字节 CANID(2字节) 数据长度(1字节) 数据(最大8字节) 固定帧尾(2字节)
0x53 0x54 0x00 0x00 0x00 0x01 0x42 0x08 0x48 0x45 0x4C 0x4F 0x01 0x02 0x03 0x04 0x0D 0x0A

代码片段(CAN标准帧,Python)

(完整代码请参考 usb2can_demo.py

HEADER_NORMAL_STD = bytes([0x53, 0x54])
TAIL = bytes([0x0D, 0x0A])

# 发送(帧头2字节 + channel1字节 + 预留2字节 + CANID2字节 + DLC1字节 + data + 帧尾2字节)
def build_standard_frame(channel: int, can_id_11bit: int, data: bytes) -> bytes:
    if not 0 <= can_id_11bit <= 0x7FF:
        raise ValueError("standard CAN ID must be 0..0x7FF (11 bits)")
    can_id_bytes = can_id_11bit.to_bytes(2, byteorder="big", signed=False)
    dlc = len(data)
    return (
        HEADER_NORMAL_STD
        + bytes([channel & 0xFF])
        + bytes([0x00, 0x00])  # reserved
        + can_id_bytes
        + bytes([dlc])
        + data
        + TAIL
    )

payload = build_standard_frame(channel=0x00, can_id_11bit=0x142, data=bytes([0x00] * 8))
ser.flush()
ser.write(payload)
ser.flush()

# 接收(帧头2字节 + channel1字节 + 预留2字节 + CANID2字节 + DLC1字节 + data + 帧尾2字节)
rx = ser.read(ser.in_waiting or 1)
if rx[:2] == HEADER_NORMAL_STD and rx[-2:] == TAIL:
    channel = rx[2]
    can_id = int.from_bytes(rx[5:7], byteorder="big", signed=False)
    dlc = rx[7]
    data = rx[8 : 8 + dlc]
    print(channel, f"0x{can_id:04X}", dlc, list(data))

电机调试工具

  • SANPO电机调试工具 支持通用CAN/RS485电机,包括小米,宇树,灵足,达秒,脉塔,翎控等
  • 小米CyberGear电机官方调试软件 下载地址
    注意:保存路径中不能有中文,否则软件无法启动
    注意:小米官方调试软件协议特殊,连接小米官方调试软件以后,开发板会切换为小米模式,使用完成后,请发送AT+ET串口指令切换为正常模式

开发样例

  • USB转CAN扩展帧样例程序(Python 版本) 下载地址

例如:向小米CyberGear CAN电机(电机ID为12)下发正弦运控指令,电机接入开发板串口Windows COM8(Linux /dev/ttyACM0),CAN-4接口

Windows:
python3 usb2can_cybergear_sine_demo_v4.py --port COM8  --motors 12 --channel 4

Ubuntu(Jetson):
sudo python3 usb2can_cybergear_sine_demo_v4.py --port /dev/ttyACM0  --motors 12 --channel 4

2. USB串口转RS485协议说明

适用场景
发送协议
接收协议
RS485协议电机,
支持最大64个字节的报文长度
透传电机数据
最后4个字节,Channel 1个字节 + 数据长度(1字节) + 报文尾0x0D 0x0A
透传电机反馈数据
  • RS485协议的前两个字节不可以使用预留帧头0x45 0x54或者0x41 0x54或者0x53 0x54,其他字节没有限制

RS485报文示例

发送报文

RS485电机数据透传(最大60个字节) Channel(1字节) 数据长度(1字节) 报文尾(2字节)
0x22 0x33 0x00 0x53 0x54 0x00 0x53 0x54 0x00 0x53 0x54 0x00 0x53 0x54 0x00 0x53 0x54 0x00 0x11 0x0D 0x0A

接收报文(透传电机反馈数据)

RS485电机反馈数据透传(最大60个字节)
0x22 0x33 0x00 0x53 0x54 0x00 0x53 0x54 0x00 0x53 0x54 0x00 0x53 0x54 0x00 0x53 0x54

电机调试工具

开发样例

例如:向宇树RS485 GO-M8010电机(电机ID为1)下发正弦运控指令,电机接入开发板串口Windows COM9(Linux /dev/ttyACM0),RS485-2接口

Windows:
python3 usb2rs485_unitree_sine_demo_v4.py --port COM9 --motors 1 --channel 2

Ubuntu(Jetson):
sudo python3 usb2rs485_unitree_sine_demo_v4.py --port /dev/ttyACM0 --motors 1 --channel 2

3. SPI转CAN协议说明

适用场景
接线规则
发送协议(SPI->CAN) 接收协议(CAN->SPI)
CAN扩展帧电机
固定报文长度23个字节
CS1片选通道控制CAN1,CAN2
CS2片选通道控制CAN3,CAN4
固定帧头2个字节(0x45 0x54) + Channel 1个字节 + CANID扩展帧 4个字节 + CAN数据长度(1字节) + CAN数据(固定8字节,不够8字节使用0x00补齐) + 固定帧尾2个字节(0x0D 0x0A) + 预留4个字节(0x00 0x00 0x00 0x12) + CRC1个字节 固定帧头2个字节(0x45 0x54) + Channel 1个字节 + CANID扩展帧 4个字节 + CAN数据长度(1字节) + CAN数据(固定8字节,不够8字节使用0x00补齐) + 固定帧尾2个字节(0x0D 0x0A) + 预留4个字节(0x00 0x00 0x00 0x12) + CRC1个字节
CAN标准帧电机
固定报文长度23个字节
CS1片选通道控制CAN1,CAN2
CS2片选通道控制CAN3,CAN4
固定帧头2个字节(0x53 0x54) + Channel 1个字节 + 固定2个字节(0x00 0x00) + CANID标准帧2个字节 + CAN数据长度(1字节) + CAN数据(固定8字节,不够8字节使用0x00补齐) + 固定帧尾2个字节(0x0D 0x0A) + 预留4个字节(0x00 0x00 0x00 0x12) + CRC1个字节 固定帧头2个字节(0x53 0x54) + Channel 1个字节 + 固定2个字节(0x00 0x00) + CANID标准帧2个字节 + CAN数据长度(1字节) + CAN数据(固定8字节,不够8字节使用0x00补齐) + 固定帧尾2个字节(0x0D 0x0A) + 预留4个字节(0x00 0x00 0x00 0x12) + CRC1个字节

CAN扩展帧报文示例

发送报文

固定帧头(2字节) Channel(1个字节) CANID(4字节) 数据长度(1字节) 数据(固定8字节) 固定帧尾(2字节) 预留4个字节 CRC校验(1字节)
0x45 0x54 0x00 0x00 0x00 0xFD 0x01 0x08 0x48 0x45 0x4C 0x4F 0x00 0x00 0x00 0x00 0x0D 0x0A 0x00 0x00 0x00 0x12 0x0F
  • SPI(CS1)控制CAN1和CAN2接口,SPI(CS2)控制CAN3和CAN4接口,请参考开发板背面的说明
  • Channel为0x01或者0x02或者0x03或者0x04, 指定向哪个CAN接口发送报文,如果为0x00,将向当前SPI(CS)通道上的所有CAN接口发送报文
  • 扩展帧CANID为29bit,占据CANID 4个字节的低29bit

接收报文

固定帧头(2字节) Channel(1个字节) CANID(4字节) 数据长度(1字节) 数据(固定8字节) 固定帧尾(2字节) 预留4个字节 CRC校验(1字节)
0x45 0x54 0x00 0x00 0x00 0x01 0xFE 0x08 0x48 0x45 0x4C 0x4F 0x01 0x02 0x03 0x04 0x0D 0x0A 0x00 0x00 0x00 0x12 0x0F

代码片段(SPI转CAN扩展帧,Python)

完整代码样例请参考 spi_cybergear_demo_v4.py

FRAME_HEADER = [0x45, 0x54]
FRAME_TAIL = [0x0D, 0x0A]
RESERVED = [0x00, 0x00, 0x00, 0x12]

# 发送(帧头2字节 + channel1字节 + CANID4字节 + DLC1字节 + data + 帧尾2字节 + 预留4个字节+ CRC校验(1字节))

def build_spi_ext_frame(channel: int, arbitration_id: int, data: list[int]) -> list[int]:
    ext_id = [
        (arbitration_id >> 24) & 0xFF,
        (arbitration_id >> 16) & 0xFF,
        (arbitration_id >> 8) & 0xFF,
        arbitration_id & 0xFF,
    ]
    data_bytes = (data + [0x00] * 8)[:8]
    payload = (
        FRAME_HEADER
        + [channel & 0xFF]
        + ext_id
        + [len(data[:8])]
        + data_bytes
        + FRAME_TAIL
        + RESERVED
    )
    crc = crc8_calculator.calculate(payload)
    return payload + [crc]


tx = build_spi_ext_frame(channel=0x00, arbitration_id=0x0000FD01, data=[0x01] + [0x00] * 7)
rx = spi.xfer2(tx)  # 固定长度23字节

# 接收报文(帧头2字节 + channel1字节 + CANID4字节 + DLC1字节 + data + 帧尾2字节 + 预留4个字节+ CRC校验(1字节))
data_part = rx[:22]
crc_ok = crc8_calculator.calculate(data_part) == rx[22]

CAN标准帧报文示例

发送报文

固定帧头(2字节) Channel(1个字节) 预留2个字节 CANID(2字节) 数据长度(1字节) 数据(固定8字节) 固定帧尾(2字节) 预留4个字节 CRC校验(1字节)
0x53 0x54 0x00 0x00 0x00 0x01 0x42 0x08 0x48 0x45 0x4C 0x4F 0x01 0x02 0x03 0x04 0x0D 0x0A 0x00 0x00 0x00 0x12 0x0F
  • 接入USB后,会显示2个串口,每个串口控制其中2路CAN接口
  • Channel为0x01或者0x02或者0x03或者0x04, 指定向哪个CAN接口发送报文,如果为0x00,将向当前串口上的所有CAN接口发送报文
  • 标准帧CANID为11bit,占据CANID 2个字节的低11bit

接收报文

固定帧头(2字节) Channel(1个字节) 预留2个字节 CANID(2字节) 数据长度(1字节) 数据(固定8字节) 固定帧尾(2字节) 预留4个字节 CRC校验(1字节)
0x53 0x54 0x00 0x00 0x00 0x01 0x42 0x08 0x48 0x45 0x4C 0x4F 0x01 0x02 0x03 0x04 0x0D 0x0A 0x00 0x00 0x00 0x12 0x0F

4. SPI转RS485协议说明

适用场景 接线规则
发送协议(SPI->CAN)
接收协议(CAN->SPI)
RS485协议电机
固定报文长度23个字节
CS1片选通道控制RS485-1,RS485-2
CS2片选通道控制RS485-3,RS485-4
透传
固定长度23个字节,预留最后3个字节,最后一个字节为CRC值,倒数第二个字节为实际有效数据长度,倒数第三个字节为Channel
透传
固定长度23个字节,预留最后3个字节,最后一个字节为CRC值,倒数第二个字节为实际有效数据长度,倒数第三个字节为Channel
  • RS485协议的前两个字节不可以使用预留帧头0x45 0x54或者0x41 0x54或者0x53 0x54,其他字节没有限制帧头

报文示例

发送报文

RS485电机数据透传(不够20个字节时,使用0x00补齐) Channel 数据长度 CRC校验(1字节)
0x22 0x33 0x00 0x53 0x54 0x00 0x53 0x54 0x00 0x53 0x54 0x00 0x53 0x54 0x00 0x53 0x54 0x00 0x00 0x00 0x00 0x17 0x0F

接收报文

RS485电机反馈数据透传 Channel 电机反馈数据长度 CRC校验(1字节)
0x22 0x33 0x00 0x53 0x54 0x00 0x53 0x54 0x00 0x53 0x54 0x00 0x53 0x54 0x00 0x53 0x00 0x00 0x00 0x00 0x00 0x16 0x0F

代码片段(SPI转RS485,Python)

完整代码样例请参考 spi_unitree_GO_M8010_demo_v4.py

# 发送:电机指令数据(<=20字节,不足补0) + Channel + 数据长度 + CRC
def build_spi_rs485_frame(cmd: bytes, channel: int) -> bytes:
    data_bytes = bytearray(cmd[:20])
    data_len = len(data_bytes)
    if data_len < 20:
        data_bytes.extend([0x00] * (20 - data_len))
    payload = bytes(data_bytes) + bytes([channel & 0xFF, data_len])
    crc = crc8_calculator.calculate(payload)
    return payload + bytes([crc])  # 固定长度23字节

tx = build_spi_rs485_frame(cmd, channel=0x00)
rx = spi.xfer2(list(tx))  # 固定长度23字节

# 接收解析:CRC 校验 + 取有效数据长度
data_part = rx[:22]
crc_ok = crc8_calculator.calculate(data_part) == rx[22]
data_len = rx[21]
motor_data = rx[:data_len]

开发样例

  • SPI转RS485样例程序(Python 版本) 下载地址

  • SPI默认使用固定23个字节的收发报文

5. 开发板可编程功能(批量指令离线自动执行)

用于在开发板本地存储一批 CAN/RS485 指令,并按预设周期和次数自动执行。
快速集成可使用 SANPO Studio工具 进行批量CAN/RS485指令调试、存储和自动执行配置。

所有 AT+ 命令均需以 \r\n 结尾

该功能只适用于固件V4.2版本及以后。发送 AT+VER 查询固件版本,如未返回V42版本号,请下载最新固件

命令总览

指令 说明
AT+BATBEGIN=<id>,<name> 创建/覆盖一个批次,开始上传该批次指令
AT+BATADD=<id>,<idx>,<proto>,<ch>,<delay_ms>,<hex_payload> 添加单条指令(短 payload),发送指令时,要有\r\n结束符
AT+BATADDX=<id>,<idx>,<proto>,<ch>,<delay_ms>,<total_len>,<seq>,<off>,<hex_part> 添加分片指令(长 payload),发送指令时,要有\r\n结束符
AT+BATADDEND=<id>,<idx>,<crc16> 结束分片上传并校验
AT+BATCOMMIT=<id> 提交并持久化批次
AT+BATRUN=<id>,<mode>,<period_ms>,<repeat> 运行批次,模式(1=ONCE,2=PERIODIC)
AT+BATSTOP 停止当前运行
AT+BATLIST? 查询批次列表
AT+BATREAD=<id>,<idx> 读取指定批次的单条指令;idx=0 返回批次摘要(名称与条数)
AT+BATDEL=<id> 删除批次
AT+BATSTAT? 查询运行状态
AT+BATAUTOSET=<id>,<mode>,<period_ms>,<repeat> 设置上电自动执行
AT+BATAUTOCLR 取消上电自动执行
AT+BATAUTO? 查询上电自动执行
AT+BATERASE 硬擦除清空整个BATCH存储区域(不可频繁使用,易损坏芯片Flash存储)

全局通信参数(持久化)

为支持开发板离线批量执行,建议先设置全局 CAN/RS485 参数:

  • AT+SETCAN=<baud>:设置全局 CAN 波特率(当前开发板上的 CAN 通道统一生效),发送指令时,要有\r\n结束符
  • AT+SETRS485=<baud>,<stop>,<parity>,<bits>:设置全局 RS485 参数(当前开发板上的 RS485 通道统一生效),发送指令时,要有\r\n结束符

字段说明:

  • baud:波特率(示例:CAN 1000000/500000/250000/125000,RS485 4000000 等)
  • stop:停止位,12
  • parity:校验位,0=None1=Odd2=Even
  • bits:数据位,89

持久化规则:

  • 开发板重新上电后将自动加载并应用上次保存的参数。

示例:

#发送指令时,要有\r\n结束符
AT+SETCAN=1000000 #CAN 通常为1M波特率,例如小米CyberGear
AT+SETRS485=4000000,1,0,8 #RS485 通常为4M波特率,例如宇树GM8010

字段定义

  • id: 批次编号,范围 1..99
  • name: 批次名称(英文/数字/下划线/中划线),最大 16 字符
  • idx: 当前批次中的指令索引,从 1 开始,必须连续递增
  • proto: 1=CAN_STD, 2=CAN_EXT, 3=RS485
  • ch: 通道 1..40 表示广播)
  • delay_ms: 指令间相对延时,范围 0..9999 ms
  • hex_payload/hex_part: 十六进制文本(2字符=1字节)
  • mode: 1=ONCE, 2=PERIODIC
  • repeat: 循环次数,0 表示无限循环
  • seq: 分片序号,从 1 开始递增
  • off: 分片偏移(字节),从 0 开始

Payload 编码定义

  • payload 使用十六进制文本编码(2字符=1字节)。
  • proto=1 (CAN_STD): CANID(2B) + DLC(1B) + DATA(0~8B),总长度 3~11B
  • proto=2 (CAN_EXT): CANID(4B) + DLC(1B) + DATA(0~8B),总长度 5~13B
  • proto=3 (RS485): 单条RS485数据<=17B可使用AT+BATADD,更长请使用分片 AT+BATADDX
  • 示例(CAN_EXT): 00000123081122334455667788 表示 CANID=0x00000123, DLC=0x08, DATA=11 22 33 44 55 66 77 88
  • 示例(RS485): A55A010203040506

长度与分片规则

  • AT+BATADD 适合短 payload(长度<=17字节)。
  • 更长 payload 使用 AT+BATADDX + AT+BATADDEND 分片上传。

使用样例

  1. 创建批次
AT+BATBEGIN=1,walk_cycle
  1. 添加 CAN_EXT 条目(CANID(4B)+DLC(1B)+DATA(<=8B)
#发送指令时,要有\r\n结束符
AT+BATADD=1,1,2,1,20,00000123081122334455667788 #<id>,<idx>,<proto>,<ch>,<delay_ms>,<hex_payload> CANID(4B) + DLC(1B) + DATA(0~8B)
  1. 添加 RS485 条目
#发送指令时,要有\r\n结束符
AT+BATADD=1,2,3,2,10,A55A010203040506
  1. 提交并运行(周期100ms,持续循环)
AT+BATCOMMIT=1 #<id>
AT+BATRUN=1,2,100,0 #<id>,<mode>(1=ONCE 2=PERIODIC),<period_ms>,<repeat>
  1. 停止运行
AT+BATSTOP
  1. 分片示例(40字节 RS485)
#发送指令时,要有\r\n结束符
AT+BATADDX=2,1,3,2,20,40,1,0,11223344556677889900AABBCCDDEEFF
AT+BATADDX=2,1,3,2,20,40,2,16,0102030405060708090A0B0C0D0E0F10
AT+BATADDX=2,1,3,2,20,40,3,32,1112131415161718
AT+BATADDEND=2,1,7A3C
AT+BATCOMMIT=2
  1. 查询开发板已经存储的批次信息
AT+BATLIST?
#返回格式
BATLIST:1,walk_cycle;2,test_rs485
#无批次时返回
BATLIST:NONE
  1. 回读开发板内已存储的指令条目,便于校验和备份
# `id`: 批次编号,`1..16`
# `idx`:
#  `0`: 读取批次摘要(名称、总条数)
#  `1..N`: 读取对应序号的指令
AT+BATREAD=<id>,<idx>

# 返回格式
# `idx=0`(摘要):
# BATREAD:id=<id>,name=<name>,count=<count>
# `idx>=1`(单条指令):
# BATREAD:id=<id>,idx=<idx>,proto=<proto>,ch=<ch>,delay_ms=<delay_ms>,payload=<hex_payload>
# 使用样例(读取整个序列)
# 1. 先读摘要拿到总条数:
AT+BATREAD=1,0
# 2. 再循环读取每条:
AT+BATREAD=1,1
AT+BATREAD=1,2
...
AT+BATREAD=1,<count>
  1. 查询状态信息
AT+BATSTAT?
  1. 删除批次
AT+BATDEL=1
  1. 上电自动执行批次,用于配置开发板每次上电后,自动执行某个已存储的批次指令。
# 1. 设置上电自动执行
# id:批次编号(1~16)
# mode:运行模式(1=单次,2=周期)
# period_ms:周期毫秒数(mode=1 时忽略,固定填 0)
# repeat:重复次数(0=无限;mode=1 时忽略,固定填 0)
# 再次执行 AT+BATAUTOSET 会直接覆盖之前配置
AT+BATAUTOSET=1,2,200,2 # <id>,<mode>,<period_ms>,<repeat> 周期模式执行 
AT+BATAUTOSET=1,1,0,0 # <id>,<mode>,<period_ms>,<repeat> 一次性模式执行

# 某些电机上电后固件初始化的慢,会导致上电后立刻发送指令无效,可以使用如下方式调试:
AT+BATAUTOSET=<id>,2,2000,0 #上电后,无限循环每2000ms执行一次,验证指令有效
AT+BATAUTOSET=<id>,2,<period_ms>,2 #逐渐调小period_ms 找到合适的延迟,repeat次数设置为2,第一次因为电机没有就绪,可能不会执行,延迟period_ms后,会执行第二次

# 2. 取消上电自动执行
# AT+BATSTOP 只停止当前运行,不会清除上电自动执行配置;清除需 AT+BATAUTOCLR
AT+BATAUTOCLR

# 3. 查询上电自动执行
AT+BATAUTO?
BATAUTO:ID=3,MODE=2,PERIOD=100,REPEAT=0 #返回示例(已配置)
BATAUTO:NONE #返回示例(未配置)
  1. 清空整个 BATCH 区域,硬擦除清空整个BATCH存储区域(不可频繁使用,易损坏芯片Flash存储)
# 执行后会清空所有批次记录,不可恢复。
AT+BATERASE

错误码

所有 AT+ 命令均需以 \r\n 结尾,若未以 \r\n 结束,命令可能被判定为不完整并超时/返回 ERR
所有 AT+BAT* 指令失败时,返回格式:

ERR,<code>

错误码定义:

  • 0:成功(通常返回 OK
  • 1BAT_ERR_PARAM,参数格式错误/缺参数/非法字符
  • 2BAT_ERR_STATE,状态错误(未 BATBEGINBATADD、分片顺序错误、idx不连续等)
  • 3BAT_ERR_RANGE,参数超范围(id/idx/seq/delay_ms/长度等)
  • 4BAT_ERR_CRCBATADDEND CRC 校验失败
  • 5BAT_ERR_NOSPACE,Flash 空间不足
  • 6BAT_ERR_NOTFOUND,批次不存在或被删除
  • 7BAT_ERR_QUEUE,发送队列忙(可重试)
  • 8BAT_ERR_FLASH,Flash 读写/擦除失败

6. SPI配置功能

  • 设置CAN协议波特率: 固定帧头6个字节(0x45 0x54 0x45 0x54 0xFF 0x01)+CAN总线的编号1字节+保留1字节0x00+波特率4个字节+固定帧尾4个字节(0x0D 0x0A 0x0D 0x0A)+预留6个字节(0x00 0x00 0x00 0x00 0x00 0x10)+CRC1个字节

发送报文样例

固定帧头(6字节) Channel(1字节) 保留(1字节) 波特率(4字节) 固定帧尾(4字节) 预留(6字节) CRC(1字节)
0x45 0x54 0x45 0x54 0xFF 0x01 0x01 0x00 0x00 0x0F 0x42 0x40 0x0D 0x0A 0x0D 0x0A 0x00 0x00 0x00 0x00 0x00 0x10 0x48

代码片段(Python)
完整代码请参考 spi_set_baudrate.py

# 设置CAN-1通道 波特率为1M
def build_spi_can_baudrate_frame(cannum: int, baudrate: int) -> list[int]:
    if cannum not in (1, 2, 3, 4):
        raise ValueError("cannum must be 1..4")
    if baudrate not in (1000000, 500000, 250000, 125000):
        raise ValueError("invalid baudrate")

    frame_header = [0x45, 0x54, 0x45, 0x54, 0xFF, 0x01]
    canid_bytes = [cannum, 0x00]
    baudrate_bytes = [
        (baudrate >> 24) & 0xFF,
        (baudrate >> 16) & 0xFF,
        (baudrate >> 8) & 0xFF,
        baudrate & 0xFF,
    ]
    frame_tail = [0x0D, 0x0A, 0x0D, 0x0A]
    reserved_bytes = [0x00, 0x00, 0x00, 0x00, 0x00, 0x10]
    payload = frame_header + canid_bytes + baudrate_bytes + frame_tail + reserved_bytes
    crc = crc8_calculator.calculate(payload)
    return payload + [crc]
  • 设置RS485协议波特率:固定帧头6个字节(0x45 0x54 0x45 0x54 0xFF 0x02)+RS485总线的编号1个字节+保留1字节0x00+波特率4个字节+停止位1个字节+奇偶校验位1个字节+数据位1个字节+固定帧尾4个字节(0x0D 0x0A 0x0D 0x0A)+预留3个字节(0x00 0x00 0x13)+CRC1个字节

发送报文样例

固定帧头(6字节) Channel(1字节) 保留(1字节) 波特率(4字节) 停止位(1字节) 奇偶校验位(1字节) 数据位(1字节) 固定帧尾(4字节) 预留(3字节) CRC(1字节)
0x45 0x54 0x45 0x54 0xFF 0x02 0x01 0x00 0x00 0x3D 0x09 0x00 0x01 0x00 0x08 0x0D 0x0A 0x0D 0x0A 0x00 0x00 0x13 0xE8

代码片段(Python)
完整代码请参考 spi_set_baudrate.py

# 设置RS485-1通道 波特率为4M
def build_spi_rs485_baudrate_frame(
    rs485num: int,
    baudrate: int,
    stop_bits: int = 1,
    parity: int = 0,
    data_bits: int = 8,
) -> list[int]:
    if rs485num not in (1, 2, 3, 4):
        raise ValueError("rs485num must be 1..4")

    frame_header = [0x45, 0x54, 0x45, 0x54, 0xFF, 0x02]
    rs485id_bytes = [rs485num, 0x00]
    baudrate_bytes = [
        (baudrate >> 24) & 0xFF,
        (baudrate >> 16) & 0xFF,
        (baudrate >> 8) & 0xFF,
        baudrate & 0xFF,
    ]
    frame_tail = [0x0D, 0x0A, 0x0D, 0x0A]
    reserved_bytes = [0x00, 0x00, 0x13]
    payload = (
        frame_header
        + rs485id_bytes
        + baudrate_bytes
        + [stop_bits, parity, data_bits]
        + frame_tail
        + reserved_bytes
    )
    crc = crc8_calculator.calculate(payload)
    return payload + [crc]

7. 其他配置功能

USB串口指令
功能 说明
AT+CLOSECAN 关闭CAN接口 只使用RS485时,可以关闭CAN接口,获得STM32 MCU更高的性能。开发板重新上电后,所有接口恢复默认可用状态
AT+CLOSERS485 关闭RS485接口 只使用CAN接口时,可以关闭RS485接口,获得STM32 MCU更高的性能。开发板重新上电后,所有接口恢复默认可用状态
AT+STAT? 查询通信统计 查询当前固件通信统计信息(如各类队列满计数),用于定位队列压力和丢包风险