3、Python I2C库入门:smbus2库安装、SMBus对象创建、读写单字节、读写多字节

好,咱们进入实战环节了。前面讲了那么多EEPROM的协议和地址,总得有个工具去操作它吧?在Python世界里,操作I2C最趁手的库就是smbus2。我个人习惯用它,因为它轻量、稳定,而且API设计得很直观。

你可能会问:为什么不用smbus?嗯,smbus是老库了,有些坑。比如它不支持i2c_rdwr操作,这在某些场景下会卡住。而smbus2是它的升级版,完全兼容,还多了些实用功能。我在项目中遇到过好几次,用smbus读多字节时莫名其妙丢数据,换成smbus2就稳了。

3.1 安装smbus2库

安装很简单,一行命令搞定。不过要注意,你的系统得先装好I2C驱动。树莓派的话,记得在raspi-config里打开I2C接口。

pip install smbus2

如果你在虚拟环境里操作,记得先激活环境。我曾经帮一个学员排查问题,搞了半天发现他装到了系统Python里,但项目用的是虚拟环境……这种低级错误,咱们尽量避免。

小提示: 如果你用的是树莓派,建议先跑一下 sudo i2cdetect -y 1 看看I2C总线上有哪些设备。这样能确认硬件连接没问题。

3.2 创建SMBus对象

安装好库之后,第一步就是创建一个SMBus对象。说白了,就是打开一个I2C总线的通道。

from smbus2 import SMBus

# 打开I2C总线1(树莓派上通常是/dev/i2c-1)
bus = SMBus(1)

# 用完记得关闭
bus.close()

这里有个细节:SMBus(1)里的数字1代表总线编号。树莓派3B+、4B上,GPIO 2和3对应的就是总线1。老一点的树莓派可能是总线0。怎么确认?跑一下ls /dev/i2c*看看。

我建议你用with语句来管理上下文,这样不用手动关总线,代码也更干净:

with SMBus(1) as bus:
    # 在这里做读写操作
    pass

为什么会推荐这种方式?因为如果你在程序中间异常退出了,总线没关,下次再打开可能会报错。用with语句,Python会自动帮你清理。

3.3 读写单字节

单字节读写是最基础的操作。EEPROM的很多控制命令,比如写使能、状态寄存器读取,都是单字节完成的。

写单字节

# 向设备地址0x50,寄存器0x00写入0xAA
bus.write_byte_data(0x50, 0x00, 0xAA)

这里0x50是EEPROM的设备地址,0x00是你要写入的寄存器地址(或者叫内存地址),0xAA是要写入的数据。

嗯,要注意一点:write_byte_data这个函数,它会先发设备地址,再发寄存器地址,最后发数据。这是标准的I2C写流程。我在项目中遇到过有人用write_byte,那个函数只发数据不发地址,结果数据全写到同一个位置去了……

读单字节

# 从设备地址0x50,寄存器0x00读取一个字节
value = bus.read_byte_data(0x50, 0x00)
print(f"读取到的数据: 0x{value:02X}")

读操作其实分两步:先发一个写命令告诉设备我要读哪个地址,然后再发读命令取数据。read_byte_data帮我们封装好了这两步。你想想看,如果自己手动实现,还得处理重复起始条件,挺麻烦的。

重要: 读操作之前,一定要确保EEPROM不在写周期内。EEPROM写完数据后需要5-10ms的写入时间,期间读操作会失败。我一般会在写完后加个 time.sleep(0.01)

3.4 读写多字节

实际项目中,我们很少只读一个字节。比如读EEPROM的页数据,一次就是16字节、32字节甚至更多。这时候就要用多字节读写。

写多字节

# 准备要写入的数据
data = [0x01, 0x02, 0x03, 0x04, 0x05]

# 从设备地址0x50,寄存器0x10开始写入5个字节
bus.write_i2c_block_data(0x50, 0x10, data)

这里write_i2c_block_data的参数分别是:设备地址、起始寄存器地址、数据列表。它会自动连续写入多个字节。

但是!有个大坑要注意:EEPROM的页写入限制。比如AT24C02,一页只有8个字节。如果你一次写入超过8个字节,数据会回绕到当前页的开头,把之前写的数据覆盖掉。我曾经在产线上遇到过一批坏板,就是因为代码里一次写了16个字节,结果前8个字节被后8个字节覆盖了……排查了好久才发现是页边界问题。

警告: 写多字节时,一定要确认EEPROM的页大小。常见的AT24C系列,页大小有8、16、32、64字节不等。跨页写入时,需要手动分页处理。

读多字节

# 从设备地址0x50,寄存器0x10开始读取5个字节
values = bus.read_i2c_block_data(0x50, 0x10, 5)
print(f"读取到的数据: {[hex(v) for v in values]}")

读多字节相对安全,没有页边界限制。你可以一次读任意多个字节,只要不超过EEPROM的总容量就行。不过,读太多字节时,I2C总线上的数据可能会出错。我建议单次读取不要超过128字节,如果数据量大,分多次读。

3.5 知识体系总览

下面这张图,把咱们这一章的核心逻辑串起来了。从安装库到创建对象,再到单字节和多字节的读写,每一步都有对应的函数和注意事项。

Python I2C库(smbus2)知识体系 pip install smbus2 SMBus(1) 或 with SMBus(1) as bus 单字节读写 写: write_byte_data(addr, reg, val) 读: read_byte_data(addr, reg) 多字节读写 写: write_i2c_block_data(addr, reg, list) 读: read_i2c_block_data(addr, reg, len) 注意事项 • 写后需等待5-10ms • 注意页边界限制 • 单次读取不超过128字节 常见坑点 • 别用write_byte代替write_byte_data • 跨页写入需手动分页 • 确认总线编号是否正确

3.6 实战小技巧

最后,分享几个我在项目中积累的小技巧:

  • 先探测再操作: 每次读写前,用i2cdetect确认设备在线。我遇到过因为接线松动导致设备地址漂移的情况,一查才发现是接触不良。
  • 加超时处理: I2C通信有时会卡死,尤其是EEPROM在写周期内。建议给读写操作加上超时,比如用signal模块或者timeout_decorator
  • 日志记录: 调试阶段,把每次读写的地址和数据都打印出来。我曾经靠这个定位了一个很隐蔽的字节序问题。
  • 批量操作用列表推导: 比如要连续读多个寄存器,可以用列表推导式,代码更简洁。
# 连续读取0x00到0x0F共16个寄存器
values = [bus.read_byte_data(0x50, addr) for addr in range(0x10)]
print(values)

好了,这一章的内容就这些。smbus2库虽然简单,但用好它,你就能和EEPROM顺畅沟通了。下一章咱们会深入EEPROM的页写入和时序控制,到时候这些基础操作都会用上。

核心总结:
  • 安装:pip install smbus2
  • 创建对象:SMBus(1)with SMBus(1) as bus
  • 单字节写:write_byte_data(addr, reg, val)
  • 单字节读:read_byte_data(addr, reg)
  • 多字节写:write_i2c_block_data(addr, reg, list)(注意页边界)
  • 多字节读:read_i2c_block_data(addr, reg, len)

专注资料整理