在嵌入式开发中,I2C总线是连接外设的“桥梁”——小到传感器、EEPROM,大到LCD驱动器、音频芯片,都离不开它的控制。而瑞芯微(Rockchip)系列芯片作为主流嵌入式方案,其I2C控制器的开发是很多工程师的必备技能。
今天这篇文章,我们将从I2C硬件原理和数据帧讲起,再结合官方《ROCKCHIP I2C开发指南》,梳理RK平台I2C开发的全流程、关键配置和常见问题,帮你快速上手!
一、先搞懂基础:I2C硬件原理与数据帧
在动手开发前,必须先掌握I2C的“底层逻辑”——硬件结构和数据传输规则,否则后续排查问题会寸步难行。
1. I2C硬件原理:两条线搞定通信
I2C总线最核心的特点是“简单”:仅需SDA(串行数据线)和SCL(串行时钟线)两条线,就能实现多主多从的通信(RK平台仅支持主模式)。
关键硬件细节:
•总线拓扑:所有设备的SDA连在一起,SCL连在一起;每个设备有唯一地址,主设备(如RK芯片)通过地址识别从设备(如传感器)。
•上拉电阻:SDA和SCL必须外接上拉电阻(通常4.7kΩ~10kΩ),因为I2C设备的引脚是开漏输出——只能拉低电平,无法主动输出高电平,需通过上拉电阻将总线拉到高电平(Vcc通常为3.3V)。
��RK文档中提到:“改变上拉电阻大小可调节I2C总线的上拉强度”,本质是通过电阻值影响总线的上升沿时间(后面会讲上升沿的重要性)。
•主从关系:主设备负责生成SCL时钟、发起通信(发送起始/停止信号);从设备被动响应,根据主设备发送的地址匹配自身。
2. I2C数据帧:通信的“语言规则”
I2C数据以“帧”为单位传输,每帧包含固定的结构,主从设备必须遵循这套规则才能正常通信。完整帧结构如下(以常用的7位寻址为例):
[起始信号]→[地址字节]→[ACK/NACK]→[数据字节1]→[ACK/NACK]→ ... →[数据字节n]→[ACK/NACK]→[停止信号]
各部分详解:
•起始信号(S):主设备拉低SDA(此时SCL为高电平),表示通信开始。
•地址字节:8位,前7位是从设备地址,第8位是“读写位”——0表示“主→从写数据”,1表示“主←从读数据”。
若用10位寻址,地址字节分两部分:第一字节是固定前缀11110+ 10位地址的高2位,第二字节是10位地址的低8位。
•ACK/NACK:每传输1字节后,接收方需反馈1位:
◦ACK(应答):接收方拉低SDA(表示已接收);
◦NACK(非应答):接收方不拉低SDA(表示未接收或结束传输)。
•数据字节:8位,可连续传输(RK控制器一次最多传32字节)。
•停止信号(P):主设备拉高SDA(此时SCL为高电平),表示通信结束。
二、RK平台I2C开发核心:控制器、驱动与流程
掌握基础后,我们聚焦RK平台的具体开发——先了解控制器功能,再区分驱动差异,最后梳理开发流程。
1. RK I2C控制器:支持哪些能力?
RK系列芯片的I2C控制器兼容性强、配置灵活,核心功能如下(文档V2.2.0版本):
•兼容I2C协议和SMBus协议(常见外设均支持);
•仅支持主模式(RK芯片作为主设备,控制外部从设备);
•时钟频率:软件可编程,最高支持1000kbps(Fast-mode Plus),部分芯片默认支持400kbps(Fast-mode);
•寻址模式:支持7位地址和10位地址(覆盖绝大多数外设);
•数据传输:一次中断/轮询最多传输32字节,效率较高。
2.关键注意:RK平台的两种I2C驱动
RK平台的I2C驱动因内核版本不同分为两类,配置方式差异较大,必须区分清楚:
| 驱动文件
|
适用内核版本
|
配置方式
|
最高频率
|
| i2c-rk3x.c
|
Linux 4.19+(如RK356X、RV1126)
|
设备树(DTS)配置
|
1000kbps
|
| i2c-rockchip.c
|
Linux 3.10内核
|
代码中配置i2c_msg结构体
|
1000kbps
|
已支持“所有RK芯片+所有内核版本”,实际开发时需先确认所用内核版本,再选择对应驱动。
3. RK I2C开发流程:三种传输模式
RK I2C控制器的核心是“传输模式”,不同场景对应不同模式,本质是通过配置寄存器(如I2C_CON、I2C_CLKDIV)实现。
模式1:只发送模式(Transmit only,I2C_CON[1:0] = 2'b00)
适用于“主设备向从设备写数据”(如配置传感器参数),步骤如下:
1.配置I2C_CLKDIV:设置SCL时钟频率(如400kbps);
2.配置I2C_CON:选择“只发送模式”,并发送起始信号(S);
3.向I2C_TXDATA0~TXDATA7写入要发送的数据;
4.配置I2C_MTXCNT:设置发送数据的字节数;
5.等待“发送完成中断”(I2C_IPD[2]);
6.若有更多数据,重复步骤3~5;若无,配置I2C_CON发送停止信号(P),结束传输。
模式2:混合模式(Mix mode,I2C_CON[1:0] = 2'b01/11)
适用于“先写后读”(如先发送寄存器地址,再读取该寄存器的值),步骤如下:
1.配置I2C_CLKDIV:设置SCL频率;
2.配置I2C_CON:选择“混合模式”,发送起始信号;
3.配置I2C_MRXADDR和I2C_MRXRADDR:设置从设备地址和读取地址;
4.配置I2C_MRXCNT:设置要读取的数据字节数;
5.等待“接收完成中断”(I2C_IPD[3]);
6.若有更多数据,重复步骤3~5;若无,发送停止信号,结束传输。
模式3:只接收模式(Receive only,I2C_CON[1:0] = 2'b10)
适用于“主设备从从设备读数据”(如读取传感器采集的数值),步骤如下:
1.配置I2C_CLKDIV:设置SCL频率;
2.配置I2C_CON:选择“只接收模式”,发送起始信号;
3.配置I2C_MRXCNT:设置接收数据的字节数;
4.等待“接收完成中断”(I2C_IPD[3]);
5.若有更多数据,重复步骤3~4;若无,发送停止信号,结束传输。
4.驱动参数配置:关键是“时钟频率”
I2C通信能否稳定,核心是“时钟频率配置”——需符合I2C协议对“上升沿时间(Tr)”和“下降沿时间(Tf)”的要求,否则会出现通信失败。
第一步:明确协议时序要求
I2C协议对不同模式的时序有严格规定(文档中表格整理):
| 参数
|
标准模式(100kbps)
|
快速模式(400kbps)
|
高速模式(1000kbps)
|
单位
|
| SCL频率
|
≤100
|
≤400
|
≤1000
|
kHz
|
| 上升沿Tr
|
≤1000
|
≤300
|
≤120
|
ns
|
| 下降沿Tf
|
≤300
|
≤300
|
≤300
|
ns
|
注:Tr和Tf需用示波器测量,若超过最大值,需调整上拉电阻(如减小电阻值缩短上升沿)。
第二步:两种驱动的配置示例
•i2c-rk3x.c(DTS配置):
配置在设备树中,关键参数是clock-frequency(时钟频率)、i2c-scl-rising-time-ns(SCL上升沿时间)。
示例(配置I2C1为400kbps):
&i2c1 {status ="okay";i2c-scl-rising-time-ns = <265>; //示波器实测上升沿265nsi2c-scl-falling-time-ns = <11>; //下降沿通常不变,可默认clock-frequency = <400000>; //400kbps(Fast-mode)// 挂载从设备(如 ES8316 音频芯片)es8316: es8316@10 {compatible ="everest,es8316";reg = <0x10>; //从设备地址0x10// 其他外设参数...};};
•i2c-rockchip.c(代码配置):
在代码中配置i2c_msg结构体的scl_rate成员,示例(配置200kbps):
structi2c_msg xfer_msg;xfer_msg[0].addr = client->addr; // 从设备地址xfer_msg[0].len = num; // 数据长度xfer_msg[0].flags = client->flags;// 读写标志(0=写,1=读)xfer_msg[0].buf = buf; // 数据缓存xfer_msg[0].scl_rate =200*1000; //200kbps 时钟频率
5. RK I2C如何使用?内核态/用户态/工具
(1)内核态使用(推荐,适合产品化)
RK I2C内核态开发遵循Linux标准I2C接口,参考内核文档Documentation/i2c/writing-clients,核心是:
•注册I2C客户端驱动(i2c_driver);
•使用i2c_master_send()(写数据)和i2c_master_recv()(读数据)接口。
(2)用户态使用(适合调试)
通过/dev/i2c-%d设备节点直接访问,步骤:
1.打开设备节点:int fd = open("/dev/i2c-1", O_RDWR);;
2.设置从设备地址:ioctl(fd, I2C_SLAVE, 0x10);(0x10为从设备地址);
3.读写数据:用read()/write()函数直接读写。
��参考内核文档Documentation/i2c/dev-interface。
(3)I2C工具(快速调试必备)
I2C-tools是开源工具集,需交叉编译后使用,支持命令行调试:
•下载地址:
https://www.kernel.org/pub/software/utils/i2c-tools/
或git clone git://git.kernel.org/pub/scm/utils/i2c-tools/i2c-tools.git
•核心工具:
◦i2cdetect:扫描I2C总线及挂载的设备(如i2cdetect -y 1扫描总线1);
◦i2cdump:读取从设备所有寄存器的值(如i2cdump -f -y 1 0x10);
◦i2cget:读取单个寄存器(如i2cget -y 1 0x10 0x01读地址0x10的0x01寄存器);
◦i2cset:写入单个寄存器(如i2cset -y 1 0x10 0x01 0x55写0x55到0x01寄存器)。
(4)GPIO模拟I2C(不推荐)
RK内核支持用GPIO模拟I2C,但效率低,仅适合临时调试。配置示例(DTS):
i2c@4{compatible ="i2c-gpio";gpios = <&gpio5 9GPIO_ACTIVE_HIGH>,// SDA 引脚<&gpio5 8GPIO_ACTIVE_HIGH>;// SCL 引脚i2c-gpio,delay-us = <2>;// 约 100kbps 频率status ="okay";// 挂载从设备(如 GT9xx 触摸屏)gt9xx: gt9xx@14{compatible ="goodix,gt9xx";reg = <0x14>;// 其他外设参数...};};
三、RK I2C常见问题:从错误码到波形Debug
开发中难免遇到问题,文档中整理了两类驱动的常见错误,我们按“错误类型”分类梳理,方便排查。
1. NACK错误:从设备无应答
现象:
•i2c-rk3x.c:调用传输接口返回-6(-ENXIO);
•i2c-rockchip.c:调用传输接口返回-11(-EAGAIN)。
原因与解决:
1.I2C地址错误:确认从设备地址(如文档中ES8316是0x10,GT9xx是0x14),注意地址是否需要左移(7位地址通常需左移1位,加读写位);
2.从设备未正常工作:检查从设备供电(如是否上电)、上电时序是否正确;
3.时序不匹配:从设备需要停止信号(P),但主设备发送了重复起始信号(Sr),需修改传输流程;
4.总线干扰:用示波器测量,若实际是ACK波形却报NACK,需排查外部干扰(如布线是否靠近强电)。
2.超时错误:日志提示“timeout”
根据日志中ipd的值,对应不同问题:
(1)日志:timeout, ipd: 0x00, state: 1
•问题:I2C控制器异常,无法发送起始信号;
•原因:
a.SCL/SDA引脚复用错误(IOMUX配置错);
b.上拉电压不对(如3.3V上拉变成1.8V);
c.引脚被外设拉低(电压异常);
d.I2C时钟未开启或时钟源过小;
e.同时配置了CON_START和CON_STOP位(寄存器配置冲突)。
(2)日志:timeout, ipd: 0x10, state: 1
•问题:控制器正常,但CPU无法响应I2C中断;
•原因:
a.CPU0被阻塞(RK I2C中断默认在CPU0,用cat /proc/interrupts查看);
b.I2C中断位被关闭(检查中断使能寄存器)。
(3)日志:timeout, ipd: 0x80, state: 1或scl was hold by slave
•问题:SCL被从设备拉低(总线卡死);
•排查方法:
a.排除法:若外设少,逐个断开外设,复现问题定位“元凶”;
b.硬件检测:在SCL总线串入电阻(如220Ω,约上拉电阻的1/20),测量电阻两端压差——电压更低的一端对应拉低SDA/SCL的设备;
c.波形验证:用示波器抓取波形,对比不同从设备的低电平,与故障时的低电平匹配的即为问题设备。
3.终极Debug:抓取I2C波形
若以上方法无法解决,最有效的方式是抓取故障时的I2C波形:
1.在代码中“卡住CPU”:在出错位置加while(1),避免发起新的I2C任务;
2.用示波器测量SDA和SCL引脚:观察是否有起始信号、地址字节、ACK信号;
3.对比协议要求:若波形缺失(如无起始信号),检查控制器配置;若有NACK,检查从设备地址或状态。
四、RK I2C开发知识脑图
最后,用一张脑图总结全文核心,方便大家收藏回顾:
写在最后
RK平台的I2C开发,核心是“理解协议+区分驱动+重视时序”——只要掌握了硬件原理和数据帧规则,再结合官方文档配置驱动、排查问题,就能快速搞定绝大多数场景。
如果大家在开发中遇到具体问题,欢迎在评论区交流;也可以收藏本文,遇到问题时对照脑图和排查步骤,效率会更高!