USB to CAN
V4 is now released. Please visit the latest version, which includes matching motor tuning tools, more sample projects, and a more complete protocol framework.
Protocol Overview
Mode |
Switch |
Use Case |
USB TX (USB->CAN) |
USB RX (CAN->USB) |
|---|---|---|---|---|
Transparent (CAN extended frame) |
AT+ET |
CAN extended frames |
Header 2 bytes (0x45 0x54) + CANID ext 4 bytes + data 8 bytes + tail 2 bytes (0x0D 0x0A) |
Header 2 bytes (0x45 0x54) + CANID ext 4 bytes + data 8 bytes + tail 2 bytes (0x0D 0x0A) |
Transparent (CAN standard frame, firmware V4.1+) |
AT+ET |
CAN standard frames |
Header 2 bytes (0x53 0x54) + fixed 2 bytes (0x00 0x00) + CANID std 2 bytes + data 8 bytes + tail 2 bytes (0x0D 0x0A) |
Header 2 bytes (0x53 0x54) + fixed 2 bytes (0x00 0x00) + CANID std 2 bytes + data 8 bytes + tail 2 bytes (0x0D 0x0A) |
Advanced mode |
AT+AT |
Standard + extended, variable payload length |
Header 2 bytes (0x41 0x54) + frame ID 4 bytes + DLC 1 byte + data up to 8 bytes + tail 2 bytes (0x0D 0x0A) |
Header 2 bytes (0x41 0x54) + frame ID 4 bytes + DLC 1 byte + data up to 8 bytes + tail 2 bytes (0x0D 0x0A) |
Query firmware version:
AT+VER.Default mode after power-on is transparent mode.
Switch to transparent mode:
AT+ET.Switch to advanced mode:
AT+AT.
Example Code
Python example: test_usb2can.py
Advanced Mode Example
Send AT+AT before transmitting data.
Header (2 bytes) |
Frame ID (4 bytes) |
DLC (1 byte) |
Data (max 8 bytes) |
Tail (2 bytes) |
|---|---|---|---|---|
0x41 0x54 |
0x00 0x00 0x00 0x00 |
0x08 |
0x48 0x45 0x4C 0x4F 0x01 0x02 0x03 0x04 |
0x0D 0x0A |
Frame ID is 4 bytes (32-bit):
Bit31-Bit21 |
Bit20-Bit3 |
Bit2 |
Bit1 |
Bit0 |
|---|---|---|---|---|
Standard CAN ID or first 11 bits of extended ID |
Last 18 bits of extended ID |
1=extended, 0=standard |
1=remote, 0=data |
Fixed 0 |
# USB to CAN test tool
pip3 install pyserial
python3 test_usb2can.py
# CAN standard frame example
# Send AT+AT first
std_can_id = 0x142
rtr_bit = 0
frame_id_val = (std_can_id << 21) | (rtr_bit << 1) | (0 << 2)
frame_id_bytes = frame_id_val.to_bytes(4, "big")
dlc = 8
data_bytes = bytes([0x9C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
payload = b"\x41\x54" + frame_id_bytes + bytes([dlc]) + data_bytes[:dlc] + b"\x0D\x0A"
# Parse return (standard frame)
frame_id_val = int.from_bytes(frame_id_bytes, "big")
ide_bit = (frame_id_val >> 2) & 0x01
rtr_bit = (frame_id_val >> 1) & 0x01
std_can_id = (frame_id_val >> 21) & 0x7FF
# CAN extended frame example
ext_can_id = 0x0000FD01
rtr_bit = 0
frame_id_val = (ext_can_id << 3) | (rtr_bit << 1) | (1 << 2)
frame_id_bytes = frame_id_val.to_bytes(4, "big")
dlc = 8
data_bytes = bytes([0x00] * 8)
payload = b"\x41\x54" + frame_id_bytes + bytes([dlc]) + data_bytes[:dlc] + b"\x0D\x0A"
# Parse return (extended frame)
frame_id_val = int.from_bytes(frame_id_bytes, "big")
ide_bit = (frame_id_val >> 2) & 0x01
rtr_bit = (frame_id_val >> 1) & 0x01
ext_can_id = (frame_id_val >> 3) & 0x1FFFFFFF