第4章:python-can库入门
好,咱们今天聊聊 python-can 这个库。说实话,这是我做车载网络调试时用得最多的工具之一。你想想看,没有它,我们怎么用 Python 去操控 CAN 总线?
python-can 说白了就是一个 Python 的 CAN 接口库。它帮我们屏蔽了底层硬件的差异,让我们能统一地操作各种 CAN 设备——不管是 Kvaser、PCAN、SocketCAN 还是 Vector 的硬件。我个人习惯把它叫做「CAN 总线的万能遥控器」。
4.1 安装 python-can
安装其实很简单。我建议你直接用 pip:
pip install python-can
嗯,这里要注意。如果你用的是 Linux 系统,特别是 Ubuntu,可能需要先装个 python3-pip。我在项目中遇到过有人装了半天装不上,最后发现是 pip 版本太旧了。
装完之后,验证一下:
python -c "import can; print(can.__version__)"
如果能看到版本号,恭喜你,环境搭好了。
4.2 Bus 类核心 API
Bus 类是 python-can 的核心。它负责跟硬件打交道。说白了,你发报文、收报文,都得通过它。
先看怎么创建一个 Bus 对象:
import can
# 创建一个 CAN 总线对象
bus = can.interface.Bus(
bustype='socketcan', # 接口类型
channel='can0', # 通道名
bitrate=500000 # 波特率
)
这里有几个参数我得跟你讲讲:
- bustype:指定底层驱动。常见的有 'socketcan'(Linux)、'pcan'(PCAN)、'kvaser'、'vector' 等。
- channel:物理通道。Linux 下一般是 can0、can1;Windows 下可能是 PCAN_USBBUS1 这种。
- bitrate:波特率。车载网络常用 500K、250K、125K。别搞错了,不然收不到数据。
Bus 类最常用的方法就这几个:
| 方法 | 说明 | 我用过的场景 |
|---|---|---|
| send(msg) | 发送一条报文 | 模拟 ECU 发送心跳帧 |
| recv(timeout) | 接收一条报文 | 监听总线上的数据 |
| set_filters(filters) | 设置接收过滤器 | 只抓特定 ID 的报文 |
| shutdown() | 关闭总线连接 | 程序退出前释放资源 |
我个人习惯在程序结束时一定要调用 shutdown()。不然下次启动时可能会报「设备忙」的错误。
4.3 Message 数据结构解析
Message 是 python-can 里最基础的数据结构。你收发的每一条 CAN 报文,本质上就是一个 Message 对象。
看看它的结构:
msg = can.Message(
arbitration_id=0x123, # CAN ID
data=[0x11, 0x22, 0x33], # 数据
is_extended_id=False, # 是否扩展帧
is_remote_frame=False, # 是否远程帧
is_error_frame=False, # 是否错误帧
dlc=3 # 数据长度
)
这里我重点说几个字段:
- arbitration_id:CAN ID。标准帧是 11 位(0x000~0x7FF),扩展帧是 29 位。别搞混了。
- data:数据字节。最多 8 个字节。车载网络里经常用 0x00 填充空位。
- dlc:数据长度。注意,dlc 不一定等于 len(data)。有些协议里 dlc 是固定的。
- is_extended_id:扩展帧标志。现在很多新车型都用扩展帧了。
4.4 收发基础报文
好,理论讲完了,咱们直接上手。先看怎么发一条报文:
import can
bus = can.interface.Bus(bustype='socketcan', channel='can0', bitrate=500000)
# 创建一条报文
msg = can.Message(
arbitration_id=0x100,
data=[0x01, 0x02, 0x03, 0x04],
is_extended_id=False
)
# 发送
try:
bus.send(msg)
print("报文发送成功")
except can.CanError:
print("发送失败")
再看怎么收报文:
# 接收一条报文,超时 1 秒
msg = bus.recv(timeout=1.0)
if msg is not None:
print(f"收到报文 ID: 0x{msg.arbitration_id:x}")
print(f"数据: {[hex(b) for b in msg.data]}")
else:
print("超时,没收到数据")
嗯,这里有个坑。recv 的超时时间单位是秒。如果你设成 0,它会立即返回,不管有没有数据。我建议至少设 0.1 秒,不然 CPU 会空转。
实际项目中,我们经常需要持续监听总线。这时候可以用循环:
while True:
msg = bus.recv(timeout=0.1)
if msg:
# 处理报文
print(f"0x{msg.arbitration_id:x}: {msg.data.hex()}")
else:
# 没收到数据,可以做点别的
pass
最后,别忘了关闭总线:
bus.shutdown()
好了,这就是 python-can 最基础的内容。你想想看,是不是比直接用 C 语言操作 CAN 驱动简单多了?下一章我会讲怎么用 python-can 做更高级的调试,比如过滤、多通道、同步收发这些。
记住,动手试一下。光看不练,等于白学。