# 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 报文模式: ```text AT+SPIMODE? AT+SPIMODE=LONG ``` | 模式 | 总线长度 | 报文区长度 | 说明 | | --- | ---: | ---: | --- | | `LONG` | 73 字节 | 72 字节 | 支持 CAN FD 标准帧、扩展帧 | 最后 1 字节为 SPI CRC;前 72 字节为报文区。CAN FD 支持 BRS,仲裁段波特率由 `AT+SETFDCAN=,` 的 `arb_baud` 配置,数据段波特率由 `data_baud` 配置,当前支持 `2Mbps` 或 `5Mbps`。 ## 协议格式 | 类型 | 帧格式 | | --- | --- | | 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 示例 ```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 ```