USB 转 CAN

  • 如下协议定义只适用于固件 V4.1 版本及以后

  • 发送 AT+VER 串口指令,可以查询固件版本,如未返回版本号或者返回 V33,请下载最新固件

  • 支持 SANPO 电机调试工具 快速生成调试代码,支持全系列 CAN/RS485 协议电机

协议说明

适用场景

USB 发送协议(USB->CAN)

USB 接收协议(CAN->USB)

CAN 扩展帧电机

固定帧头 2 个字节(0x45 0x54)+ Channel 1 个字节 + 扩展帧 CANID 4 个字节 + 数据长度(1 字节)+ 数据(最大 8 字节)+ 固定帧尾 2 个字节(0x0D 0x0A)

固定帧头 2 个字节(0x45 0x54)+ Channel 1 个字节 + 扩展帧 CANID 4 个字节 + 数据长度(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))

电机调试工具

注意:保存路径中不能有中文,否则软件无法启动
注意:小米官方调试软件协议特殊,连接小米官方调试软件以后,开发板会切换为小米模式,使用完成后,请发送 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