SPI 转 FDCAN

本协议用于 SPI 接口收发 CAN FD 报文。CAN FD 只支持 SPI LONG 模式。

出厂固件默认 SPI 报文模式为 SHORT,不能直接收发 SPI CAN FD 报文。使用 SPI CAN FD 前,需要先通过 USB CDC串口工具发送 AT+SPIMODE=LONG,并把发送结束符设置为 CRLF。切换成功后返回 OK,配置会写入固件持久化存储,下次上电继续使用 LONG模式,无需重复配置。如果执行过 AT+CFGERASE,SPI 模式会恢复为 SHORT,需要重新切换。

可通过以下命令查询或切换 SPI 报文模式:

AT+SPIMODE?
AT+SPIMODE=LONG

模式

总线长度

报文区长度

说明

LONG

73 字节

72 字节

支持 CAN FD 标准帧、扩展帧

最后 1 字节为 SPI CRC;前 72 字节为报文区。CAN FD 支持 BRS,仲裁段波特率由 AT+SETFDCAN=<arb_baud>,<data_baud>arb_baud 配置,数据段波特率由 data_baud 配置,当前支持 2Mbps5Mbps

协议格式

类型

帧格式

CAN FD 扩展帧

0x45 0x46 + Channel(1B) + ExtID(4B) + 数据长度编码(1B) + Data(64B) + CRC(1B)

CAN FD 标准帧

0x53 0x46 + Channel(1B) + 0x00 0x00(保留字段) + StdID(2B) + 数据长度编码(1B) + Data(64B) + CRC(1B)

字段说明

字段

说明

0x45 0x46

ASCII EF 的十六进制表示,CAN FD 扩展帧 报文头

0x53 0x46

ASCII SF 的十六进制表示,CAN FD 标准帧 报文头

Channel

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

ExtID

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

StdID

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

保留字段

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

数据长度编码

CAN FD 数据长度编码,取值 0x00~0x0F

Data

固定 64 字节数据区;真实有效长度由“数据长度编码”决定,不足部分填 0x00

CRC

SPI CRC 字节

CAN FD 的“数据长度编码”,字段范围 0x00~0x0F

编码字段值

真实数据长度

0x00

0

0x01

1

0x02

2

0x03

3

0x04

4

0x05

5

0x06

6

0x07

7

0x08

8

0x09

12

0x0A

16

0x0B

20

0x0C

24

0x0D

32

0x0E

48

0x0F

64

CRC 说明

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

Python 示例

LEN_TABLE = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64]

def length_code_to_size(code: int) -> int:
    if not 0 <= code < len(LEN_TABLE):
        raise ValueError("invalid CAN FD length code")
    return LEN_TABLE[code]

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