SPI 转 CAN

本协议用于 SPI 接口收发经典 CAN 报文。SPI 为固定长度全双工协议,最后 1 字节为CRC,前面的字节为报文区。

经典 CAN 支持 SHORTLONG 两种 SPI 报文模式。

经典 CAN 波特率使用 AT+SETFDCAN=<arb_baud>,<data_baud> 中的 arb_bauddata_baud 只用于 CAN FD 数据段,不影响经典 CAN。

模式

总线长度

报文区长度

说明

SHORT

23 字节

22 字节

默认模式,低开销,支持经典 CAN 和 RS485。

LONG

73 字节

72 字节

使用 AT+ 指令切换,切换后下次上电保持切换状态。支持经典 CAN、CAN FD 和 RS485

CAN FD 请使用 SPI 转 FDCAN

SHORT 模式格式

类型

帧格式

经典 CAN 扩展帧

0x45 0x54 + Channel(1B) + ExtID(4B) + Len(1B) + Data(8B) + Padding(6B) + CRC(1B)

经典 CAN 标准帧

0x53 0x54 + Channel(1B) + 0x00 0x00(保留字段) + StdID(2B) + Len(1B) + Data(8B) + Padding(6B) + CRC(1B)

LONG 模式格式

类型

帧格式

经典 CAN 扩展帧

0x45 0x54 + Channel(1B) + ExtID(4B) + Len(1B) + Data(8B) +  Padding(56B) + CRC(1B)

经典 CAN 标准帧

0x53 0x54 + Channel(1B) + 0x00 0x00(保留字段) + StdID(2B) + Len(1B) + Data(8B) + Padding(56B) + CRC(1B)

字段说明

字段

说明

0x45 0x54

ASCII ET 的十六进制表示,经典 CAN 扩展帧 报文头

0x53 0x54

ASCII ST 的十六进制表示,经典 CAN 标准帧 报文头

Channel

0x01~0x04 指定 CAN 接口;0x00 表示发送到当前 SPI 片选对应的所有 CAN 接口

ExtID

29 bit 扩展帧 CAN ID,放在 4 字节字段低 29 bit

StdID

11 bit 标准帧 CAN ID,放在 2 字节字段低 11 bit

保留字段

标准帧 ID 前的保留字段,固定 0x00 0x00

Len

经典 CAN 真实数据长度,范围 0~8

Data

固定长度数据区;只有前 Len 字节有效,其余填 0x00

Padding

填充字段,固定 0x00

CRC

SPI CRC 字节

CRC 说明

总线长度包含最后 1 字节 CRC。固件使用硬件CRC,若主机使用软件模拟 SPI,需要按固件配置生成最后 1 字节 CRC。

Python 示例

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

def build_spi_can_ext_short_payload(channel: int, can_id: int, data: bytes) -> bytes:
    if not 0 <= can_id <= 0x1FFFFFFF:
        raise ValueError("extended CAN ID must be 0..0x1FFFFFFF")
    data = data[:8]
    data8 = data + bytes([0x00] * (8 - len(data)))
    payload = bytes([0x45, 0x54, channel & 0xFF]) + can_id.to_bytes(4, "big") + bytes([len(data)]) + data8 + bytes([0x00] * 6)
    if len(payload) != 22:
        raise ValueError("invalid SHORT payload size")
    return payload

def build_spi_can_ext_long_payload(channel: int, can_id: int, data: bytes) -> bytes:
    if not 0 <= can_id <= 0x1FFFFFFF:
        raise ValueError("extended CAN ID must be 0..0x1FFFFFFF")
    data = data[:8]
    data64 = data + bytes([0x00] * (64 - len(data)))
    payload = bytes([0x45, 0x54, channel & 0xFF]) + can_id.to_bytes(4, "big") + bytes([len(data)]) + data64
    if len(payload) != 72:
        raise ValueError("invalid LONG payload size")
    return payload