第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__)"

如果能看到版本号,恭喜你,环境搭好了。

小提示: 如果你用的是 Windows,建议装个 VC++ 运行库。不然有些底层驱动可能加载失败。我曾经因为这个折腾了一下午。

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。别搞错了,不然收不到数据。
注意: 波特率必须和总线上的其他节点一致。我曾经在调试时忘了改波特率,结果抓了半天包全是乱码。后来才发现是 500K 和 250K 没对上。

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:扩展帧标志。现在很多新车型都用扩展帧了。
重点: 我建议你每次创建 Message 时都显式指定 is_extended_id。因为默认是 False,如果你忘了设,发出去的报文 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
经验之谈: 这种循环监听的方式,我建议加个 Ctrl+C 退出机制。不然你只能 kill 进程了。用 try/except KeyboardInterrupt 包一下就好。

最后,别忘了关闭总线:

bus.shutdown()

好了,这就是 python-can 最基础的内容。你想想看,是不是比直接用 C 语言操作 CAN 驱动简单多了?下一章我会讲怎么用 python-can 做更高级的调试,比如过滤、多通道、同步收发这些。

记住,动手试一下。光看不练,等于白学。