# 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 |
## 开发样例