# SANPO兴普智能 - 机器人集成开发板
-
如下协议定义只适用于固件V4.1版本及以后
- 发送AT+VER(以\r\n结尾)串口指令,可以查询固件版本,如未返回版本号低于V42,请下载最新固件
- 支持SANPO电机调试工具快速生成调试代码。支持全系列CAN/RS485协议电机,包括小米,宇树,灵足,达秒,脉塔,翎控等
目录
简称定义
-
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) |
- 使用SANPO电机调试工具 快速生成测试代码
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 |
电机调试工具
- SANPO电机调试工具 支持通用CAN/RS485电机,包括小米,宇树,灵足,达秒,脉塔,翎控等
- 宇树电机官方调试软件 下载地址 使用说明
开发样例
- USB转RS485样例程序 下载地址
例如:向宇树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:波特率(示例:CAN1000000/500000/250000/125000,RS4854000000等)stop:停止位,1或2parity:校验位,0=None、1=Odd、2=Evenbits:数据位,8或9
持久化规则:
- 开发板重新上电后将自动加载并应用上次保存的参数。
示例:
#发送指令时,要有\r\n结束符
AT+SETCAN=1000000 #CAN 通常为1M波特率,例如小米CyberGear
AT+SETRS485=4000000,1,0,8 #RS485 通常为4M波特率,例如宇树GM8010
字段定义
id: 批次编号,范围1..99name: 批次名称(英文/数字/下划线/中划线),最大16字符idx: 当前批次中的指令索引,从1开始,必须连续递增proto:1=CAN_STD,2=CAN_EXT,3=RS485ch: 通道1..4(0表示广播)delay_ms: 指令间相对延时,范围0..9999mshex_payload/hex_part: 十六进制文本(2字符=1字节)mode:1=ONCE,2=PERIODICrepeat: 循环次数,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分片上传。
使用样例
- 创建批次
AT+BATBEGIN=1,walk_cycle
- 添加 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)
- 添加 RS485 条目
#发送指令时,要有\r\n结束符
AT+BATADD=1,2,3,2,10,A55A010203040506
- 提交并运行(周期100ms,持续循环)
AT+BATCOMMIT=1 #<id>
AT+BATRUN=1,2,100,0 #<id>,<mode>(1=ONCE 2=PERIODIC),<period_ms>,<repeat>
- 停止运行
AT+BATSTOP
- 分片示例(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
- 查询开发板已经存储的批次信息
AT+BATLIST?
#返回格式
BATLIST:1,walk_cycle;2,test_rs485
#无批次时返回
BATLIST:NONE
- 回读开发板内已存储的指令条目,便于校验和备份
# `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>
- 查询状态信息
AT+BATSTAT?
- 删除批次
AT+BATDEL=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 #返回示例(未配置)
- 清空整个 BATCH 区域,硬擦除清空整个BATCH存储区域(不可频繁使用,易损坏芯片Flash存储)
# 执行后会清空所有批次记录,不可恢复。
AT+BATERASE
错误码
所有 AT+ 命令均需以 \r\n 结尾,若未以 \r\n 结束,命令可能被判定为不完整并超时/返回 ERR
所有 AT+BAT* 指令失败时,返回格式:
ERR,<code>
错误码定义:
0:成功(通常返回OK)1:BAT_ERR_PARAM,参数格式错误/缺参数/非法字符2:BAT_ERR_STATE,状态错误(未BATBEGIN就BATADD、分片顺序错误、idx不连续等)3:BAT_ERR_RANGE,参数超范围(id/idx/seq/delay_ms/长度等)4:BAT_ERR_CRC,BATADDENDCRC 校验失败5:BAT_ERR_NOSPACE,Flash 空间不足6:BAT_ERR_NOTFOUND,批次不存在或被删除7:BAT_ERR_QUEUE,发送队列忙(可重试)8:BAT_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? | 查询通信统计 | 查询当前固件通信统计信息(如各类队列满计数),用于定位队列压力和丢包风险 |