4. Python的smbus2库入门:安装、扫描与单字节通信

好,咱们正式开始动手了。前面几章讲了I2C协议的理论,说实话,光看时序图确实有点枯燥。这一章我们直接上Python,用smbus2这个库来操作I2C设备。你想想看,能通过几行代码就点亮LCD背光、控制字符显示,是不是比干看波形图有意思多了?

4.1 为什么选smbus2?

Python里操作I2C的库有好几个,比如smbus、smbus2、pyftdi等等。我个人习惯用smbus2,原因很简单:

  • 兼容性好:它完全兼容smbus的API,老代码基本不用改。
  • 性能更优:底层用C实现,读写速度比纯Python实现快不少。
  • 功能完整:支持SMBus协议的所有操作,包括块读写、I2C读写等。
  • 文档清晰:GitHub上的README写得明明白白,遇到问题好查。

我在一个工业项目里用过smbus,当时遇到一个奇怪的问题——连续读写时偶尔会丢数据。换成smbus2之后,问题就再没出现过。后来看了源码才发现,smbus2在锁机制上做了优化。嗯,细节决定成败。

4.2 安装smbus2库

安装过程非常简单,一行命令搞定。前提是你的树莓派(或其它Linux开发板)已经连上了网络。

pip install smbus2

如果你用的是Python 3(现在基本都是了),可能需要用pip3:

pip3 install smbus2
小提示: 如果你在树莓派上操作,记得先开启I2C接口。运行 sudo raspi-config,在"Interface Options"里启用I2C。不然后面扫描设备会什么都扫不到,别问我怎么知道的……我曾经在这上面浪费了半小时。

安装完成后,验证一下:

python3 -c "import smbus2; print(smbus2.__version__)"

如果能正常输出版本号,比如0.4.2,那就说明安装成功了。

4.3 扫描I2C设备地址

拿到一个新的I2C模块,第一件事是什么?我个人习惯先扫一下它的地址。你想想看,如果地址都搞错了,后面所有的读写操作都是白费功夫。

下面这个脚本会扫描0x03到0x77之间的所有地址,看看哪些设备在线:

import smbus2

# 创建SMBus实例,参数1表示I2C总线编号(树莓派通常是1)
bus = smbus2.SMBus(1)

print("正在扫描I2C设备...")
print("     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f")

for address in range(0x03, 0x78):
    try:
        # 尝试读取一个字节,看设备是否有响应
        bus.read_byte(address)
        # 格式化输出地址
        row = address // 16 * 16
        col = address % 16
        if col == 0:
            print(f"{row:02x}: ", end="")
        print(f"{address:02x} ", end="")
    except OSError:
        # 设备无响应,打印"--"
        row = address // 16 * 16
        col = address % 16
        if col == 0:
            print(f"{row:02x}: ", end="")
        print("-- ", end="")
    if (address + 1) % 16 == 0:
        print()

bus.close()
print("\n扫描完成。")

运行结果大概长这样:

正在扫描I2C设备...
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

如果某个地址有设备响应,就会显示对应的十六进制地址。比如常见的LCD1602 I2C转接板,地址通常是0x27或0x3F。

注意: 扫描时如果某个地址的设备正在忙,可能会返回错误。这时候可以加个重试机制,或者等设备空闲了再扫。我曾经遇到过传感器在采集数据时拒绝响应,扫了三次才扫到它。

4.4 发送单字节数据

找到设备地址之后,下一步就是跟它通信了。最简单的操作就是发送一个字节。比如你想控制LCD的背光,可能只需要往某个寄存器写一个字节。

发送单字节数据有两种常用方式:

方法 说明 适用场景
write_byte(addr, val) 向设备地址直接发送一个字节 设备只有一个寄存器,或者不需要指定寄存器地址
write_byte_data(addr, cmd, val) 先发送命令字节(寄存器地址),再发送数据字节 设备有多个寄存器,需要指定操作哪个寄存器

来看个实际例子。假设你的LCD地址是0x27,你想打开背光(背光控制位通常是第3位,即0x08):

import smbus2

# 设备地址
LCD_ADDR = 0x27
# 背光控制位
LCD_BACKLIGHT = 0x08

# 创建总线实例
bus = smbus2.SMBus(1)

try:
    # 发送背光控制字节
    bus.write_byte(LCD_ADDR, LCD_BACKLIGHT)
    print(f"已向地址 0x{LCD_ADDR:02X} 发送背光控制字节 0x{LCD_BACKLIGHT:02X}")
except OSError as e:
    print(f"通信失败:{e}")
finally:
    bus.close()

如果LCD支持寄存器操作,你可能需要这样写:

# 假设寄存器地址是0x00,数据是0x08
bus.write_byte_data(LCD_ADDR, 0x00, LCD_BACKLIGHT)
核心要点: 发送单字节数据时,一定要确认设备的数据手册。有些设备要求先发命令字节再发数据,有些则直接收数据。搞反了,设备不会理你。

4.5 本章知识结构

下面这张图把本章的核心逻辑串起来了,从安装库到实际通信,每一步都环环相扣:

smbus2库入门:安装 → 扫描 → 通信 ① 安装smbus2 pip install smbus2 ② 创建SMBus实例 bus = SMBus(1) ③ 扫描设备地址 read_byte() 探测 ④ 发送单字节数据 write_byte / write_byte_data write_byte(addr, val) 直接发送,不指定寄存器 适用于单寄存器设备 write_byte_data(addr, cmd, val) 先发命令,再发数据 适用于多寄存器设备 关键提醒 发送数据前务必查阅设备数据手册,确认通信协议 地址错误或协议不匹配,设备不会响应

4.6 常见问题与避坑指南

做I2C通信,有些坑是绕不开的。我把自己踩过的坑整理了一下,你遇到了可以直接对照着查:

  • 地址搞错:有些模块的地址是7位,但smbus2要求传入8位地址(左移一位)。不过smbus2内部会自动处理,你直接填数据手册上的地址就行。但要注意,有些库不帮你做这个转换。
  • 总线编号不对:树莓派一般是/dev/i2c-1,对应SMBus(1)。老版本树莓派可能是/dev/i2c-0。用ls /dev/i2c-*确认一下。
  • 权限问题:运行脚本时如果报Permission denied,把用户加到i2c组:sudo usermod -a -G i2c pi,然后重新登录。
  • 设备没上电:这个听起来很蠢,但我真犯过。LCD模块需要5V供电,如果只接了I2C线没接电源,扫描结果就是空的。
曾经踩过的坑: 有一次我扫描I2C总线,死活找不到一个温度传感器。折腾了半小时,最后发现是传感器地址被其他设备占用了,产生了地址冲突。从那以后,我每次接新设备都会先扫一遍总线,看看有没有地址冲突。

4.7 小结

这一章我们走通了smbus2库的完整流程:安装、扫描地址、发送单字节数据。说白了,就是让Python和I2C设备能对上话。下一章我们会用这些基础操作,去控制LCD1602的背光和显示。到时候你会发现,今天学的这些就是所有高级功能的地基。

嗯,动手试试吧。把代码敲一遍,看看你的LCD模块能不能被扫到。如果扫到了,恭喜你,硬件连接没问题,可以继续往下走了。