从设备树到驱动:在全志D1/F133平台上为Linux添加一个I2C EEPROM设备驱动
2026/6/13 9:51:51 网站建设 项目流程

全志D1/F133平台I2C EEPROM驱动开发实战指南

嵌入式Linux驱动开发者的I2C实战手册

在全志D1/F133这类RISC-V架构的开发板上开发I2C设备驱动,是嵌入式Linux工程师的必备技能。不同于x86平台的"开箱即用"体验,嵌入式系统需要开发者深入理解从硬件连接到内核驱动的完整技术栈。本文将以AT24C16 EEPROM芯片为例,带你完成从设备树配置、内核驱动编写到用户空间测试的全流程实战。

1. 硬件准备与电路设计

1.1 硬件选型与连接

在全志D1开发板上连接I2C EEPROM芯片前,需要确认以下几个硬件细节:

  • 开发板I2C控制器选择:全志D1通常提供多组TWI(全志对I2C的命名)接口,查看原理图确认可用接口
  • EEPROM型号:AT24C16是常见的16Kbit(2KB)EEPROM,支持标准I2C协议
  • 引脚连接
    • SDA:串行数据线
    • SCL:串行时钟线
    • VCC:3.3V电源
    • GND:地线
    • A0-A2:地址引脚(AT24C16可悬空)

提示:I2C总线需要上拉电阻,典型值为4.7kΩ。部分开发板已内置上拉,需确认原理图。

1.2 电气特性验证

使用万用表测量以下关键点:

  1. 电源电压:3.3V±10%
  2. SDA/SCL线空闲电压:接近VCC(3.3V)
  3. 短路检查:确认无引脚间短路
# 在开发板上快速检查I2C总线是否被识别 ls /dev/i2c-*

如果看不到任何I2C设备节点,可能需要先在内核中启用I2C控制器驱动。

2. 设备树配置与内核编译

2.1 设备树节点添加

全志平台使用设备树描述硬件资源。为添加EEPROM设备,需要修改板级设备树文件(通常位于arch/riscv/boot/dts/allwinner/目录下):

&twi0 { clock-frequency = <400000>; status = "okay"; eeprom: eeprom@50 { compatible = "atmel,24c16"; reg = <0x50>; pagesize = <16>; }; };

关键参数说明:

参数说明
clock-frequency400000I2C总线速度400kHz
compatible"atmel,24c16"驱动匹配字符串
reg0x50设备I2C地址
pagesize16EEPROM页写入大小

2.2 内核配置

确保内核配置包含以下选项:

make ARCH=riscv menuconfig

需要启用的关键选项:

  • Device Drivers → I2C support → I2C device interface
  • Device Drivers → I2C support → I2C Hardware Bus support → SUNXI I2C controller
  • Device Drivers → Misc devices → EEPROM support → I2C EEPROMs from most vendors

编译并更新内核:

make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- -j$(nproc)

3. 驱动开发实战

3.1 最简单的EEPROM驱动

虽然内核已有at24驱动,但为了学习目的,我们可以实现一个简化版驱动:

#include <linux/module.h> #include <linux/i2c.h> #include <linux/fs.h> #define DEVICE_NAME "eeprom_demo" #define EEPROM_SIZE 2048 // AT24C16容量 static struct i2c_client *client; static int eeprom_read(struct i2c_client *client, char *buf, unsigned offset, int count) { struct i2c_msg msg[2]; u8 addr_buf[2]; int ret; // 设置读取地址 addr_buf[0] = (offset >> 8) & 0xFF; addr_buf[1] = offset & 0xFF; msg[0].addr = client->addr; msg[0].flags = 0; msg[0].len = 2; msg[0].buf = addr_buf; // 读取数据 msg[1].addr = client->addr; msg[1].flags = I2C_M_RD; msg[1].len = count; msg[1].buf = buf; ret = i2c_transfer(client->adapter, msg, 2); return (ret == 2) ? count : ret; } static int eeprom_probe(struct i2c_client *client, const struct i2c_device_id *id) { printk(KERN_INFO "EEPROM probe at address: 0x%02x\n", client->addr); return 0; } static const struct of_device_id eeprom_dt_ids[] = { { .compatible = "atmel,24c16" }, { } }; MODULE_DEVICE_TABLE(of, eeprom_dt_ids); static struct i2c_driver eeprom_driver = { .driver = { .name = DEVICE_NAME, .of_match_table = eeprom_dt_ids, }, .probe = eeprom_probe, }; module_i2c_driver(eeprom_driver);

3.2 添加sysfs接口

为了方便用户空间访问,可以添加sysfs支持:

static ssize_t eeprom_read(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t off, size_t count) { if (off >= EEPROM_SIZE) return 0; if (off + count > EEPROM_SIZE) count = EEPROM_SIZE - off; return eeprom_read(client, buf, off, count); } static struct bin_attribute eeprom_attr = { .attr = { .name = "data", .mode = 0444, }, .read = eeprom_read, .size = EEPROM_SIZE, }; static int eeprom_probe(struct i2c_client *client, const struct i2c_device_id *id) { int err; err = sysfs_create_bin_file(&client->dev.kobj, &eeprom_attr); if (err) return err; return 0; }

4. 用户空间测试与验证

4.1 使用i2c-tools测试

i2c-tools是调试I2C设备的利器:

# 安装工具 sudo apt install i2c-tools # 扫描I2C总线上的设备 i2cdetect -y 0 # 读取EEPROM前16字节 i2cdump -y 0 0x50 # 写入单个字节 i2cset -y 0 0x50 0x00 0xAA # 读取单个字节 i2cget -y 0 0x50 0x00

4.2 编写自定义测试程序

对于更复杂的测试,可以编写C程序:

#include <stdio.h> #include <linux/i2c-dev.h> #include <fcntl.h> #include <unistd.h> #define I2C_DEV "/dev/i2c-0" #define EEPROM_ADDR 0x50 int main() { int fd; char buf[32]; // 打开I2C设备 if ((fd = open(I2C_DEV, O_RDWR)) < 0) { perror("Failed to open I2C device"); return 1; } // 设置从设备地址 if (ioctl(fd, I2C_SLAVE, EEPROM_ADDR) < 0) { perror("Failed to set I2C slave address"); close(fd); return 1; } // 写入地址指针 buf[0] = 0x00; // 高地址位 buf[1] = 0x00; // 低地址位 if (write(fd, buf, 2) != 2) { perror("Failed to set address pointer"); close(fd); return 1; } // 读取数据 if (read(fd, buf, 16) != 16) { perror("Failed to read from EEPROM"); close(fd); return 1; } // 打印读取的数据 for (int i = 0; i < 16; i++) { printf("%02x ", buf[i] & 0xFF); } printf("\n"); close(fd); return 0; }

5. 高级主题与性能优化

5.1 DMA传输优化

全志D1的TWI控制器支持DMA传输,可以显著提高大块数据传输效率。在设备树中启用DMA:

&twi0 { dmas = <&dma 43>, <&dma 43>; dma-names = "tx", "rx"; };

5.2 电源管理

为降低功耗,可以添加电源管理支持:

static int eeprom_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); // 保存状态或降低功耗 return 0; } static int eeprom_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); // 恢复状态 return 0; } static const struct dev_pm_ops eeprom_pm_ops = { .suspend = eeprom_suspend, .resume = eeprom_resume, }; static struct i2c_driver eeprom_driver = { .driver = { .pm = &eeprom_pm_ops, }, };

5.3 调试技巧

当驱动不工作时,可以尝试以下调试方法:

  1. 检查设备树:确认节点状态为"okay",地址正确
  2. 内核日志dmesg | grep twi查看TWI控制器初始化情况
  3. 逻辑分析仪:抓取I2C总线波形,验证信号质量
  4. sysfs调试节点:全志平台通常提供/sys/module/i2c_sunxi/parameters/transfer_debug
# 启用调试日志 echo 1 > /sys/module/i2c_sunxi/parameters/transfer_debug

6. 常见问题与解决方案

6.1 I2C设备无响应

现象i2cdetect看不到设备或读取返回错误排查步骤

  1. 确认电源连接正常
  2. 检查I2C地址是否正确(AT24C16默认0x50)
  3. 测量SDA/SCL电压(应有上拉)
  4. 尝试降低时钟频率(如100kHz)

6.2 数据写入失败

现象:写入后读取的值不一致解决方案

  1. 确保遵循页写入规则(AT24C16页大小为16字节)
  2. 写入后添加延迟(EEPROM需要时间完成内部写入)
  3. 实现写验证机制
// 示例:带延迟的页写入 static int eeprom_write_page(struct i2c_client *client, unsigned offset, const char *buf, int count) { u8 *tmp = kmalloc(count + 2, GFP_KERNEL); int ret; tmp[0] = (offset >> 8) & 0xFF; tmp[1] = offset & 0xFF; memcpy(&tmp[2], buf, count); ret = i2c_master_send(client, tmp, count + 2); kfree(tmp); // EEPROM编程时间 msleep(10); return ret; }

6.3 性能优化技巧

  1. 批量读取:一次性读取多个字节减少协议开销
  2. 缓存热点数据:对频繁访问的数据在内存中缓存
  3. 异步操作:对非关键写入使用延迟写入机制
  4. 合理设置I2C频率:平衡速度与稳定性

在全志D1平台上开发I2C设备驱动,最关键的环节是正确配置设备树和深入理解硬件特性。通过本文的实战流程,开发者可以建立起从硬件到软件的完整知识体系,为更复杂的嵌入式Linux驱动开发打下坚实基础。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询