news 2026/2/6 11:35:53

韦东山嵌入式Linux I2C驱动开发实战(含代码解析与实验指导)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
韦东山嵌入式Linux I2C驱动开发实战(含代码解析与实验指导)

1. I2C协议基础与硬件框架

I2C(Inter-Integrated Circuit)是一种简单却强大的串行通信协议,它只需要两根信号线就能实现多设备通信。在实际项目中,我经常用它连接各种传感器和存储芯片。先来看看它的硬件连接方式:

  • SCL:时钟线,负责同步数据传输
  • SDA:数据线,承载实际传输的数据
  • 上拉电阻:必须接在两条线上(通常4.7KΩ)

举个实际例子,当我们要用AT24C02 EEPROM存储数据时,硬件连接就像搭积木一样简单:主控芯片的I2C控制器通过这两根线,就能和多个从设备"对话"。这里有个关键点要注意:所有设备都是"开漏输出",这意味着:

  1. 不驱动三极管时,SDA/SCL通过上拉电阻保持高电平
  2. 需要输出低电平时,才驱动三极管拉低线路
  3. 这种设计避免了总线冲突,实现了"线与"逻辑

数据传输时有个有趣的细节:每个字节传输需要9个时钟周期。前8个时钟传数据,第9个时钟用来等待从设备的应答(ACK)。这个ACK信号其实就是从设备在第9个时钟周期把SDA拉低,相当于说"我收到了"。

2. SMBus协议的特殊之处

SMBus(系统管理总线)是基于I2C的"严格版",在电源管理等场景很常见。我在调试笔记本电池管理芯片时深有体会,它有几点特殊要求:

  1. 超时限制:必须在35ms内完成传输,否则认为失败
  2. 特殊命令:比如块读写、过程调用等
  3. 地址保留:0x00-0x07和0x78-0x7F地址有特殊用途

最实用的功能是重复起始条件(Repeated Start)。比如我们要先写寄存器地址再读数据时,可以这样操作:

// 传统方式 i2c_start(); i2c_write(addr|0); // 写模式 i2c_write(reg); i2c_stop(); i2c_start(); i2c_write(addr|1); // 读模式 data = i2c_read(); i2c_stop(); // SMBus优化方式 i2c_start(); i2c_write(addr|0); i2c_write(reg); i2c_start(); // 重复起始,不释放总线 i2c_write(addr|1); data = i2c_read(); i2c_stop();

这种方式避免了总线释放后被其他设备抢占的风险,在多任务系统中特别有用。

3. Linux I2C驱动核心结构体

Linux内核用三个关键结构体来抽象I2C系统,刚开始看源码时容易混淆,我来拆解下:

3.1 i2c_adapter:控制器管家

相当于I2C总线控制器的"身份证",包含:

struct i2c_adapter { struct device dev; const struct i2c_algorithm *algo; // 关键操作函数 int nr; // 总线编号 ... };

其中algo->master_xfer是核心,实现了实际的传输函数。我在调试时发现,不同芯片的这个函数差异很大,比如NXP的驱动使用DMA,而全志的可能是GPIO模拟。

3.2 i2c_client:设备名片

描述挂在I2C总线上的设备:

struct i2c_client { unsigned short addr; // 7位设备地址 char name[I2C_NAME_SIZE]; struct i2c_adapter *adapter; // 所属总线 ... };

这个结构体通常在设备树中定义,比如:

eeprom@50 { compatible = "atmel,24c02"; reg = <0x50>; };

3.3 i2c_msg:传输单元

描述一次数据传输的具体参数:

struct i2c_msg { __u16 addr; // 设备地址 __u16 flags; // 读/写标志 __u16 len; // 数据长度 __u8 *buf; // 数据缓冲区 };

实际使用时要组合多个msg,比如读取EEPROM的0x10地址数据:

u8 addr = 0x10; u8 data; struct i2c_msg msgs[2] = { {0x50, 0, 1, &addr}, // 写地址 {0x50, I2C_M_RD, 1, &data} // 读数据 };

4. I2C-Tools实战技巧

I2C-Tools是调试神器和学习助手,这几个命令我每天都要用:

4.1 设备探测

# 列出所有I2C总线 i2cdetect -l # 扫描总线0上的设备 i2cdetect -y 0

输出示例:

0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 1e -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --

这里能看到地址0x1e的光感芯片和0x50的EEPROM。

4.2 SMBus操作示例

操作AP3216C光感芯片:

# 复位 i2cset -f -y 0 0x1e 0 0x4 # 使能 i2cset -f -y 0 0x1e 0 0x3 # 读取光强(2字节) i2cget -f -y 0 0x1e 0xc w

4.3 原始I2C操作

同样的操作改用I2C协议:

i2ctransfer -f -y 0 w2@0x1e 0 0x4 # 复位 i2ctransfer -f -y 0 w2@0x1e 0 0x3 # 使能 i2ctransfer -f -y 0 w1@0x1e 0xc r2 # 读光强

5. 手把手编写EEPROM驱动

下面这个完整示例演示如何通过/dev/i2c接口操作AT24C02:

#include <linux/i2c-dev.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char **argv) { int file; char filename[20]; unsigned char addr = 0x50; // EEPROM地址 unsigned char buf[32]; // 打开I2C控制器 snprintf(filename, sizeof(filename), "/dev/i2c-%d", atoi(argv[1])); file = open(filename, O_RDWR); // 设置从设备地址 ioctl(file, I2C_SLAVE_FORCE, addr); if(argv[2][0] == 'w') { // 写入字符串 char *str = argv[3]; int i = 0; while(*str) { i2c_smbus_write_byte_data(file, i, *str); usleep(20000); // 等待EEPROM写入完成 i++; str++; } i2c_smbus_write_byte_data(file, i, 0); // 结束符 } else { // 读取数据 int len = i2c_smbus_read_i2c_block_data(file, 0, sizeof(buf), buf); buf[len] = '\0'; printf("Read: %s\n", buf); } close(file); return 0; }

使用时:

# 写入数据 ./eeprom_app 0 w "Hello,100ask" # 读取数据 ./eeprom_app 0 r

6. 常见问题排查指南

在调试I2C时我踩过不少坑,总结几个典型问题:

  1. 设备无响应

    • 检查上拉电阻(通常4.7KΩ)
    • 确认设备地址是否正确(7位地址要左移1位)
    • 用示波器看波形是否正常
  2. 数据错乱

    • 检查时钟频率是否过高(新手建议先用100KHz)
    • 确认设备供电稳定
    • 注意信号线长度(长距离要降低速率)
  3. NACK错误

    • 检查设备是否初始化完成
    • 确认从设备地址正确
    • 查看设备是否处于睡眠模式
  4. 时序问题

    • GPIO模拟I2C时注意延时
    • 某些设备需要stop信号后才能响应
    • EEPROM写入需要等待5-10ms

记得有次调试一个I2C温度传感器,死活不响应,最后发现是PCB设计问题——SCL和SDA走线太长且平行,导致串扰。后来缩短走线并拉开间距就正常了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/6 3:23:07

中文NLP神器:SiameseUniNLU关系抽取效果实测

中文NLP神器&#xff1a;SiameseUniNLU关系抽取效果实测 1. 开箱即用&#xff1a;三分钟跑通关系抽取服务 你是否经历过这样的场景&#xff1a;手头有一批中文新闻、医疗报告或电商评论&#xff0c;想快速抽取出“人物-事件”“公司-产品”“药物-副作用”这类结构化关系&…

作者头像 李华
网站建设 2026/2/4 16:11:14

5分钟搞定图片识别!万物识别-中文-通用领域镜像实测

5分钟搞定图片识别&#xff01;万物识别-中文-通用领域镜像实测 你有没有过这样的经历&#xff1a;拍了一张杂乱的桌面照片&#xff0c;想快速知道里面有哪些东西&#xff1f;或者在开发一个智能相册App时&#xff0c;卡在“怎么让程序看懂这张图”的环节上&#xff1f;又或者…

作者头像 李华
网站建设 2026/2/5 18:09:53

SenseVoice Small效果展示:车载录音(引擎噪音+回声)鲁棒性识别效果

SenseVoice Small效果展示&#xff1a;车载录音&#xff08;引擎噪音回声&#xff09;鲁棒性识别效果 1. 什么是SenseVoice Small SenseVoice Small是阿里通义实验室推出的轻量级语音识别模型&#xff0c;专为边缘设备和实时场景设计。它不像传统大模型那样动辄需要多张显卡、…

作者头像 李华
网站建设 2026/2/5 0:19:25

translategemma-4b-it保姆级教程:Ollama中自定义prompt实现专业领域翻译

translategemma-4b-it保姆级教程&#xff1a;Ollama中自定义prompt实现专业领域翻译 1. 为什么你需要这个模型——轻量又专业的翻译新选择 你有没有遇到过这样的情况&#xff1a;手头有一份技术文档要翻译成中文&#xff0c;但通用翻译工具总把“latency”翻成“延迟时间”&a…

作者头像 李华
网站建设 2026/2/6 0:31:59

微信消息防护与聊天记录安全:小白也能懂的实用指南

微信消息防护与聊天记录安全&#xff1a;小白也能懂的实用指南 【免费下载链接】wechat_no_revoke 项目地址: https://gitcode.com/gh_mirrors/we/wechat_no_revoke 在日常微信沟通中&#xff0c;你是否遇到过重要消息被对方撤回的情况&#xff1f;无论是工作中的关键信…

作者头像 李华