Linux下SMBus操作AT24C32(16位寄存器)的故障咨询
Linux下通过SMBus操作AT24C32及16位寄存器地址处理方案
问题概述
- 如何在Linux环境下通过SMBus操作AT24C32 EEPROM?
- 针对AT24C32这类内存地址为16位(实际为12位,但需按16位格式传输)的大容量EEPROM,SMBus接口看似仅支持8位寄存器地址,该如何适配?
- 当前读取操作返回
0xff,写入操作返回错误码-5(对应EIO输入输出错误),疑问内核是否仅支持8位寄存器地址,不兼容16位地址的设备?
核心原因分析
- 设备地址错误:AT24C32的I2C设备地址为
0x50加上硬件引脚A2/A1/A0的电平值(每引脚对应一位)。若硬件接线并非A2=1、A1=0、A0=1,代码中AT24C32_ADDR 0x53的地址设置错误会直接导致读写失败。 - 地址传输方式错误:AT24C32的内存地址需以两个连续字节(高字节+低字节)的形式发送给设备,而当前使用的
i2c_smbus_write_byte_data/read_byte_data仅能传递8位地址,无法覆盖完整内存地址范围,导致设备无法定位到正确存储单元。 - 内核支持情况:Linux内核的SMBus接口完全支持16位地址的设备,只是需要使用适配的操作方式(而非单字节/单字的SMBus命令)。
解决方案与代码修正
1. 基础操作流程(适配16位地址)
对于AT24C32这类EEPROM,正确的SMBus/I2C操作流程为:
- 写入:发送设备写地址 → 发送内存地址高字节 → 发送内存地址低字节 → 发送待写入数据
- 读取:发送设备写地址 → 发送内存地址高字节 → 发送内存地址低字节 → 发送设备读地址 → 读取数据
2. 修正后的代码示例
#include <fcntl.h> #include <stdio.h> #include <linux/i2c-dev.h> #include <errno.h> #include <sys/ioctl.h> #include <unistd.h> #include <string.h> #include <stdint.h> #define AT24C32_ADDR 0x50 // 根据硬件接线修改A2/A1/A0对应的位 // 设置I2C从设备地址 int set_slave_addr(int file, int address, int force) { if (ioctl(file, force ? I2C_SLAVE_FORCE : I2C_SLAVE, address) < 0) { fprintf(stderr, "Error: Could not set address to 0x%02x: %s\n", address, strerror(errno)); return -errno; } return 0; } // 向指定16位地址写入一个字节 int at24c32_write_byte(int fd, uint16_t mem_addr, uint8_t data) { uint8_t buf[3]; buf[0] = (mem_addr >> 8) & 0xFF; // 内存地址高字节 buf[1] = mem_addr & 0xFF; // 内存地址低字节 buf[2] = data; if (write(fd, buf, 3) != 3) { fprintf(stderr, "Write failed: %s\n", strerror(errno)); return -errno; } usleep(10000); // EEPROM写入需要等待时间,根据 datasheet 调整 return 0; } // 从指定16位地址读取一个字节 int at24c32_read_byte(int fd, uint16_t mem_addr, uint8_t *data) { uint8_t buf[2]; buf[0] = (mem_addr >> 8) & 0xFF; buf[1] = mem_addr & 0xFF; // 先写入内存地址 if (write(fd, buf, 2) != 2) { fprintf(stderr, "Write address failed: %s\n", strerror(errno)); return -errno; } // 读取数据 if (read(fd, data, 1) != 1) { fprintf(stderr, "Read failed: %s\n", strerror(errno)); return -errno; } return 0; } // 测试示例 int main() { int fd = open("/dev/i2c-1", O_RDWR); // 根据你的I2C总线编号修改 if (fd < 0) { fprintf(stderr, "Open i2c dev failed: %s\n", strerror(errno)); return -1; } if (set_slave_addr(fd, AT24C32_ADDR, 1) < 0) { close(fd); return -1; } uint8_t write_data = 0xAB; uint16_t test_addr = 0x100; if (at24c32_write_byte(fd, test_addr, write_data) < 0) { close(fd); return -1; } uint8_t read_data; if (at24c32_read_byte(fd, test_addr, &read_data) < 0) { close(fd); return -1; } printf("Wrote 0x%02x to address 0x%04x, read back 0x%02x\n", write_data, test_addr, read_data); close(fd); return 0; }
3. 关键说明
- 总线编号:
/dev/i2c-1对应多数ARM板的I2C1总线,若设备使用其他总线(如I2C0),需修改为对应路径。 - 写入延迟:EEPROM写入完成需要一定时间(AT24C32典型为5ms),代码中
usleep(10000)是保守值,可根据 datasheet 调整。 - 设备地址:务必根据硬件上A2/A1/A0引脚的实际接线修改
AT24C32_ADDR,比如A2/A1/A0均接地时地址为0x50。
常见问题排查
- 若仍返回
EIO错误:检查硬件接线(SDA/SCL是否连接正确,上拉电阻是否存在),用i2cdetect -y 1命令扫描总线,确认AT24C32的地址是否能被检测到。 - 读取返回
0xff:通常是设备未响应或地址错误,先通过i2cdetect确认设备存在,再检查内存地址传输逻辑。
内容的提问来源于stack exchange,提问作者lugc1
相关产品推荐
相关产品推荐

