# SPI 转 CAN - 如下协议定义只适用于固件 V4.1 版本及以后 - 发送 AT+VER 串口指令,可以查询固件版本,如未返回版本号或者返回 V33,请下载最新固件 - 支持 SANPO 电机调试工具 快速生成调试代码,支持全系列 CAN/RS485 协议电机 ## 协议说明 | 适用场景 | 接线规则 | 发送协议(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)+ CRC 校验(1 字节) | 固定帧头 2 个字节(0x45 0x54)+ Channel 1 个字节 + CANID 扩展帧 4 个字节 + CAN 数据长度(1 字节)+ CAN 数据(固定 8 字节,不够 8 字节使用 0x00 补齐)+ 固定帧尾 2 个字节(0x0D 0x0A)+ 预留 4 个字节(0x00 0x00 0x00 0x12)+ CRC 校验(1 字节) | | 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)+ CRC 校验(1 字节) | 固定帧头 2 个字节(0x53 0x54)+ Channel 1 个字节 + 固定 2 个字节(0x00 0x00)+ CANID 标准帧 2 个字节 + CAN 数据长度(1 字节)+ CAN 数据(固定 8 字节,不够 8 字节使用 0x00 补齐)+ 固定帧尾 2 个字节(0x0D 0x0A)+ 预留 4 个字节(0x00 0x00 0x00 0x12)+ CRC 校验(1 字节) | ### 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 ```python 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 | - SPI(CS1)控制 CAN1 和 CAN2 接口,SPI(CS2)控制 CAN3 和 CAN4 接口 - Channel 为 0x01 或者 0x02 或者 0x03 或者 0x04,指定向哪个 CAN 接口发送报文,如果为 0x00,将向当前 SPI(CS)通道上的所有 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 | ## 开发样例