当你的树莓派需要记住一些重要数据——比如传感器校准参数、设备配置信息或者简单的键值对——AT24C系列EEPROM芯片是个可靠的选择。但要让这块小芯片乖乖听话,你得先搞定SMBus协议那些"严苛"的规矩。本文将带你用Python的smbus2库,从硬件连线的物理层一路杀到数据校验的应用层,完整实现EEPROM的读写控制。
AT24C32是常见的I2C接口EEPROM芯片,32Kbit容量(相当于4KB),足够存储大量配置数据。硬件连接简单到令人发指:
注意:树莓派的I2C引脚需要上拉电阻,但大多数EEPROM模块已经内置了4.7kΩ电阻,直接连接即可。
在通电前,务必确认EEPROM的地址引脚(A0-A2)连接方式。以AT24C32为例,其7位I2C地址格式为0b1010(A2)(A1)(A0)。如果所有地址引脚接地,设备地址就是0x50(左移一位后为0xA0)。
python复制# 地址验证代码片段
def check_eeprom_address(bus):
for addr in range(0x50, 0x58): # AT24Cxx可能的地址范围
try:
bus.write_quick(addr)
return addr
except IOError:
continue
raise ValueError("未检测到EEPROM设备")
树莓派默认禁用I2C接口,需要手动开启:
bash复制# 终端操作
sudo raspi-config
# 选择 Interfacing Options → I2C → Enable
接着安装必要的Python库:
bash复制pip install smbus2 RPi.GPIO
为什么选择smbus2而不是smbus? 老旧的smbus库存在诸多限制:
smbus2则完整实现了SMBus 3.0规范,特别适合对可靠性要求高的场景。其核心API分为三类:
虽然SMBus基于I2C,但它制定了更严苛的规范:
| 特性 | I2C | SMBus |
|---|---|---|
| 电压范围 | 无明确限制 | 1.8V-5V |
| 时钟频率 | 无上限 | 10kHz-100kHz |
| 超时机制 | 无 | 35ms总线超时 |
| 数据格式 | 设备自定义 | 严格定义 |
| PEC校验 | 无 | 可选CRC-8校验 |
在EEPROM操作中,这些差异会带来实际影响。例如,当我们需要写入一个16位数据时:
python复制def write_word_safe(bus, addr, reg, value):
"""符合SMBus规范的16位数据写入"""
try:
# 先写高字节,再写低字节(大端序)
bus.write_word_data(addr, reg, ((value & 0xFF) << 8) | (value >> 8))
# SMBus要求10ms内完成写入
time.sleep(0.01)
except IOError as e:
if e.errno == 121: # Remote I/O error
print("写入失败:设备未响应")
elif e.errno == 5: # Input/output error
print("写入失败:总线冲突")
关键提示:AT24C系列有5ms的写周期等待时间,连续写入时必须遵守,否则会导致数据损坏。
最基本的读写单元,适合存储开关状态或模式标志:
python复制def byte_ops_demo(bus, addr):
# 写入魔术字
bus.write_byte_data(addr, 0x00, 0xAA)
# 读取验证
magic = bus.read_byte_data(addr, 0x00)
print(f"读取到的魔术字: {hex(magic)}")
AT24C32的页大小为32字节,高效写入的秘诀在于:
python复制def page_write(bus, addr, start_reg, data):
if len(data) > 32:
raise ValueError("单次写入不能超过32字节")
if (start_reg % 32) + len(data) > 32:
raise ValueError("不能跨页写入")
# SMBus块写入格式:长度字节+数据
bus.write_i2c_block_data(addr, start_reg, data)
time.sleep(0.01) # 等待写入完成
在电磁环境复杂的场景中,启用数据包错误检查:
python复制from smbus2 import SMBus, i2c_msg
def read_with_pec(bus, addr, reg, length):
"""带CRC校验的读取"""
msg = i2c_msg.read(addr, length+1) # 额外1字节用于PEC
bus.i2c_rdwr(msg)
data = list(msg)
pec = data.pop()
# 这里应添加CRC验证逻辑
return bytes(data)
结合Python的魔法方法,我们可以把EEPROM包装成类字典对象:
python复制class EEPROMDict:
def __init__(self, bus, addr=0x50, page_size=32):
self.bus = bus
self.addr = addr
self.page_size = page_size
def __setitem__(self, key, value):
if not isinstance(key, int) or not 0 <= key < 4096:
raise KeyError("地址必须在0-4095范围内")
if isinstance(value, int):
self.bus.write_byte_data(self.addr, key, value)
elif isinstance(value, bytes):
self._write_block(key, value)
def __getitem__(self, key):
if isinstance(key, slice):
return self._read_block(key.start, key.stop-key.start)
return self.bus.read_byte_data(self.addr, key)
def _write_block(self, addr, data):
for i in range(0, len(data), self.page_size):
chunk = data[i:i+self.page_size]
self.bus.write_i2c_block_data(
self.addr,
addr + i,
list(chunk)
)
time.sleep(0.01)
使用示例:
python复制eeprom = EEPROMDict(SMBus(1))
eeprom[0x100] = b"Hello, EEPROM!" # 写入字符串
print(eeprom[0x100:0x10F]) # 读取15字节
时钟拉伸问题:某些EEPROM在写入期间会拉低SCL线(时钟拉伸),而树莓派的硬件I2C对此支持有限。解决方法:
python复制# 设置低速模式
bus = SMBus(1) # I2C1
bus.frequency = 10000 # 10kHz
地址冲突排查:当多个I2C设备共存时,地址冲突会导致诡异问题。用这个命令扫描总线:
bash复制i2cdetect -y 1
电源干扰处理:如果发现数据随机错误,尝试:
在完成所有实验后,建议用这个测试脚本验证EEPROM的完整性:
python复制def stress_test(eeprom, rounds=100):
pattern = os.urandom(32) # 随机测试数据
for i in range(rounds):
addr = random.randint(0, 4000)
eeprom[addr:addr+32] = pattern
read_back = eeprom[addr:addr+32]
assert read_back == pattern, f"验证失败 @ {addr}"
print(f"通过{rounds}轮压力测试")