📖 推荐阅读:《Yocto项目实战教程:高效定制嵌入式Linux系统》
🎥 更多学习视频请关注 B 站:嵌入式Jerry
EEPROM I2C 驱动实战教程:完整驱动代码实现与详解
一、项目背景
本篇博文详细展示如何使用 Linux 驱动模型实现一个 I2C 接口的 EEPROM 驱动,以广泛应用的 AT24C256 芯片为例。EEPROM 常用于嵌入式设备中保存关键配置信息,例如设备序列号、校准数据、运行参数等。
本文将完整地给出 EEPROM 驱动的实现过程,包括设备树配置、驱动模块代码实现、sysfs 接口创建及用户空间访问测试,并对关键代码进行详细注释和说明。
二、硬件及设备树配置说明
AT24C256 芯片为 I2C 接口的 EEPROM,容量为 256Kbit (32KB),具有以下特性:
- I2C 地址:0x50
- 单页大小:64 字节
- 总容量:32KB
设备树配置如下:
&i2c1 {
status = "okay";
eeprom@50 {
compatible = "atmel,24c256";
reg = <0x50>; // I2C 地址
pagesize = <64>; // EEPROM 单页写入大小
};
};
三、EEPROM 驱动代码及详细注释
完整的驱动代码如下,其中包含 sysfs 接口实现以及关键函数的详细注释说明。
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
static struct class *eeprom_class;
static struct device *eeprom_dev;
static u16 eeprom_addr = 0; // 当前操作的EEPROM地址
static struct i2c_client *eeprom_client;
// 显示当前EEPROM地址
static ssize_t read_addr_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "0x%04x\n", eeprom_addr);
}
// 设置EEPROM地址
static ssize_t read_addr_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
sscanf(buf, "%hx", &eeprom_addr);
return count;
}
// 读取EEPROM当前地址的数据
static ssize_t data_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
u8 addr_buf[2];
u8 data;
struct i2c_msg msgs[2];
// 将16位地址拆分成高低字节
addr_buf[0] = eeprom_addr >> 8;
addr_buf[1] = eeprom_addr & 0xff;
// I2C消息:发送EEPROM读取地址
msgs[0].addr = eeprom_client->addr;
msgs[0].flags = 0;
msgs[0].len = 2;
msgs[0].buf = addr_buf;
// I2C消息:从EEPROM读取1字节数据
msgs[1].addr = eeprom_client->addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 1;
msgs[1].buf = &data;
// 执行I2C传输
if (i2c_transfer(eeprom_client->adapter, msgs, 2) != 2)
return -EIO;
return sprintf(buf, "0x%02x\n", data);
}
// 创建读写地址属性节点
static DEVICE_ATTR_RW(read_addr);
// 创建数据只读属性节点
static DEVICE_ATTR_RO(data);
// EEPROM设备probe函数,设备树匹配成功后调用
static int eeprom_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
eeprom_client = client;
// 创建sysfs设备类
eeprom_class = class_create(THIS_MODULE, "eeprom_demo");
// 创建设备节点
eeprom_dev = device_create(eeprom_class, NULL, 0, NULL, "eeprom0");
// 创建sysfs接口文件
device_create_file(eeprom_dev, &dev_attr_read_addr);
device_create_file(eeprom_dev, &dev_attr_data);
dev_info(&client->dev, "EEPROM driver successfully probed\n");
return 0;
}
// EEPROM设备remove函数,驱动移除时调用
static int eeprom_remove(struct i2c_client *client)
{
// 移除sysfs接口文件
device_remove_file(eeprom_dev, &dev_attr_read_addr);
device_remove_file(eeprom_dev, &dev_attr_data);
// 销毁设备节点及类
device_destroy(eeprom_class, 0);
class_destroy(eeprom_class);
return 0;
}
static const struct i2c_device_id eeprom_ids[] = {
{ "24c256", 0 },
{}
};
static const struct of_device_id eeprom_dt_ids[] = {
{ .compatible = "atmel,24c256" },
{}
};
MODULE_DEVICE_TABLE(i2c, eeprom_ids);
// 定义并注册I2C驱动结构体
static struct i2c_driver eeprom_driver = {
.driver = {
.name = "eeprom_demo",
.of_match_table = of_match_ptr(eeprom_dt_ids),
},
.probe = eeprom_probe,
.remove = eeprom_remove,
.id_table = eeprom_ids,
};
module_i2c_driver(eeprom_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedded Engineer");
MODULE_DESCRIPTION("Detailed EEPROM I2C driver example for AT24C256");
四、驱动加载与用户空间测试
编译并加载驱动到 Linux 内核后,可进行如下测试:
# 设置EEPROM读取地址为0x0010
echo 0x0010 > /sys/class/eeprom_demo/eeprom0/read_addr
# 读取EEPROM对应地址的数据
cat /sys/class/eeprom_demo/eeprom0/data
上述命令执行成功后,终端将显示 EEPROM 中 0x0010 地址的数据。
📖 推荐阅读:《Yocto项目实战教程:高效定制嵌入式Linux系统》
🎥 更多学习视频请关注 B 站:嵌入式Jerry