You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux下SMBus操作AT24C32(16位寄存器)的故障咨询

Linux下通过SMBus操作AT24C32及16位寄存器地址处理方案

问题概述

  • 如何在Linux环境下通过SMBus操作AT24C32 EEPROM?
  • 针对AT24C32这类内存地址为16位(实际为12位,但需按16位格式传输)的大容量EEPROM,SMBus接口看似仅支持8位寄存器地址,该如何适配?
  • 当前读取操作返回0xff,写入操作返回错误码-5(对应EIO输入输出错误),疑问内核是否仅支持8位寄存器地址,不兼容16位地址的设备?

核心原因分析

  1. 设备地址错误:AT24C32的I2C设备地址为0x50加上硬件引脚A2/A1/A0的电平值(每引脚对应一位)。若硬件接线并非A2=1、A1=0、A0=1,代码中AT24C32_ADDR 0x53的地址设置错误会直接导致读写失败。
  2. 地址传输方式错误:AT24C32的内存地址需以两个连续字节(高字节+低字节)的形式发送给设备,而当前使用的i2c_smbus_write_byte_data/read_byte_data仅能传递8位地址,无法覆盖完整内存地址范围,导致设备无法定位到正确存储单元。
  3. 内核支持情况: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 08:40:25