# SPI 转 CAN 本协议用于 SPI 接口收发经典 CAN 报文。SPI 为固定长度全双工协议,最后 1 字节为CRC,前面的字节为报文区。 经典 CAN 支持 `SHORT` 和 `LONG` 两种 SPI 报文模式。 经典 CAN 波特率使用 `AT+SETFDCAN=,` 中的 `arb_baud`。`data_baud` 只用于 CAN FD 数据段,不影响经典 CAN。 | 模式 | 总线长度 | 报文区长度 | 说明 | | --- | ---: | ---: | --- | | `SHORT` | 23 字节 | 22 字节 | 默认模式,低开销,支持经典 CAN 和 RS485。 | | `LONG` | 73 字节 | 72 字节 | 使用 AT+ 指令切换,切换后下次上电保持切换状态。支持经典 CAN、CAN FD 和 RS485 | CAN FD 请使用 [SPI 转 FDCAN](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 ```python 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 ```