# USB CDC串口转 FDCAN 本协议用于 USB CDC串口协议收发 CAN FD 报文,为变长协议,支持标准帧和扩展帧,单帧数据区最大 64 字节。CAN FD 支持 BRS。仲裁段波特率由 `AT+SETFDCAN=,` 的 `arb_baud` 配置,数据段波特率由 `data_baud` 配置,当前支持 `2Mbps` 或 `5Mbps`。 出厂默认 CAN/FDCAN 参数为仲裁段 `1Mbps`、数据段 `5Mbps`、BRS 开启;如果现场 CAN FD 总线使用该参数,可以直接按本页 `SF / EF` 帧格式发送。需要修改波特率时,再通过 USB CDC串口工具发送 `AT+SETFDCAN=,`,并把发送结束符设置为 `CRLF`。配置会写入固件持久化存储,下次上电继续使用,无需重复配置。 如果在 Linux 上使用 SocketCAN,请参考 [SocketCAN / gs_usb](socketcan)。SocketCAN 不使用本页的 `SF / EF` 帧头。 USB CDC串口转 FDCAN 协议报文以 `0x0D 0x0A` 结尾。 ## 协议格式 | 类型 | 帧格式 | | --- | --- | | CAN FD 扩展帧 | `0x45 0x46 + Channel(1字节) + ExtID(4字节) + 数据长度编码(1字节) + Data(0~64字节) + 0x0D 0x0A` | | CAN FD 标准帧 | `0x53 0x46 + Channel(1字节) + 0x00 0x00(保留字段2字节) + StdID(2字节) + 数据长度编码(1字节) + Data(0~64字节) + 0x0D 0x0A` | ## 字段说明 | 字段 | 说明 | | --- | --- | | `0x45 0x46` | ASCII`EF`的十六进制表示,CAN FD 扩展帧 报文头 | | `0x53 0x46` | ASCII`SF`的十六进制表示,CAN FD 标准帧 报文头 | | `Channel` | `0x01~0x04` 指定 CAN/FDCAN 接口;`0x00` 表示发送到当前设备侧所有 CAN/FDCAN 接口 | | `ExtID` | 29 bit 扩展帧 CAN ID,放在 4 字节字段低 29 bit | | `StdID` | 11 bit 标准帧 CAN ID,放在 2 字节字段低 11 bit | | `保留字段` | 标准帧保留字段,固定 `0x00 0x00` | | `数据长度编码` | CAN FD 数据长度编码,取值 `0x00~0x0F` | | `Data` | CAN FD 数据,真实长度由“数据长度编码”决定 | | `0x0D 0x0A` | 报文尾 | 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 | ## 扩展帧示例 | 帧头 | Channel | CANID | 数据长度编码 | Data | 帧尾 | | --- | --- | --- | --- | --- | --- | | `0x45 0x46` | `0x01` | `0x01 0x7F 0xFF 0x07` | `0x0A` | 16 字节有效数据 | `0x0D 0x0A` | ## 标准帧示例 | 帧头 | Channel | Reserved | CANID | 数据长度编码 | Data | 帧尾 | | --- | --- | --- | --- | --- | --- | --- | | `0x53 0x46` | `0x02` | `0x00 0x00` | `0x01 0x42` | `0x09` | 12 字节有效数据 | `0x0D 0x0A` | ## Python 示例 ```python TAIL = bytes([0x0D, 0x0A]) 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_usb_fdcan_ext(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") return bytes([0x45, 0x46, channel & 0xFF]) + can_id.to_bytes(4, "big") + bytes([length_code]) + data + TAIL def build_usb_fdcan_std(channel: int, can_id: int, length_code: int, data: bytes) -> bytes: if not 0 <= can_id <= 0x7FF: raise ValueError("standard CAN ID must be 0..0x7FF") size = length_code_to_size(length_code) if len(data) != size: raise ValueError("data length does not match CAN FD length code") return bytes([0x53, 0x46, channel & 0xFF, 0x00, 0x00]) + can_id.to_bytes(2, "big") + bytes([length_code]) + data + TAIL ```