RK平台I2C开发:从硬件原理到实战排查 电子说
在嵌入式开发中,I2C 总线是连接外设的 “桥梁”—— 小到传感器、EEPROM,大到 LCD 驱动器、音频芯片,都离不开它的控制。而瑞芯微(Rockchip)系列芯片作为主流嵌入式方案,其 I2C 控制器的开发是很多工程师的必备技能。
今天这篇文章,我们将从I2C 硬件原理和数据帧 讲起,再结合官方《ROCKCHIP I2C 开发指南》,梳理 RK 平台 I2C 开发的全流程、关键配置和常见问题,帮你快速上手!
在动手开发前,必须先掌握 I2C 的 “底层逻辑”—— 硬件结构和数据传输规则,否则后续排查问题会寸步难行。
I2C 总线最核心的特点是 “简单”:仅需 SDA(串行数据线) 和SCL(串行时钟线) 两条线,就能实现多主多从的通信(RK 平台仅支持主模式)。
•总线拓扑:所有设备的 SDA 连在一起,SCL 连在一起;每个设备有唯一地址,主设备(如 RK 芯片)通过地址识别从设备(如传感器)。
•上拉电阻:SDA 和 SCL 必须外接上拉电阻(通常 4.7kΩ~10kΩ),因为 I2C 设备的引脚是 开漏输出—— 只能拉低电平,无法主动输出高电平,需通过上拉电阻将总线拉到高电平(Vcc 通常为 3.3V)。
�� RK 文档中提到:“改变上拉电阻大小可调节 I2C 总线的上拉强度”,本质是通过电阻值影响总线的上升沿时间(后面会讲上升沿的重要性)。
•主从关系:主设备负责生成 SCL 时钟、发起通信(发送起始 / 停止信号);从设备被动响应,根据主设备发送的地址匹配自身。
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 平台的具体开发 —— 先了解控制器功能,再区分驱动差异,最后梳理开发流程。
RK 系列芯片的 I2C 控制器兼容性强、配置灵活,核心功能如下(文档 V2.2.0 版本):
•兼容I2C 协议 和SMBus 协议(常见外设均支持);
•仅支持主模式(RK 芯片作为主设备,控制外部从设备);
•时钟频率:软件可编程,最高支持1000kbps(Fast-mode Plus),部分芯片默认支持 400kbps(Fast-mode);
•寻址模式:支持7 位地址 和10 位地址(覆盖绝大多数外设);
•数据传输:一次中断 / 轮询最多传输 32 字节,效率较高。
RK 平台的 I2C 驱动因内核版本不同分为两类,配置方式差异较大,必须区分清楚:
|
驱动文件
|
适用内核版本
|
配置方式
|
最高频率
|
|
i2c-rk3x.c
|
Linux 4.19+(如 RK356X、RV1126)
|
设备树(DTS)配置
|
1000kbps
|
|
i2c-rockchip.c
|
Linux 3.10 内核
|
代码中配置i2c_msg 结构体
|
1000kbps
|
已支持“所有 RK 芯片 + 所有内核版本”,实际开发时需先确认所用内核版本,再选择对应驱动。
RK I2C 控制器的核心是 “传输模式”,不同场景对应不同模式,本质是通过配置寄存器(如 I2C_CON、I2C_CLKDIV)实现。
适用于“主设备向从设备写数据”(如配置传感器参数),步骤如下:
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),结束传输。
适用于“先写后读”(如先发送寄存器地址,再读取该寄存器的值),步骤如下:
1.配置I2C_CLKDIV:设置 SCL 频率;
2.配置I2C_CON:选择“混合模式”,发送起始信号;
3.配置I2C_MRXADDR 和I2C_MRXRADDR:设置从设备地址和读取地址;
4.配置I2C_MRXCNT:设置要读取的数据字节数;
5.等待“接收完成中断”(I2C_IPD[3]);
6.若有更多数据,重复步骤 3~5;若无,发送停止信号,结束传输。
适用于“主设备从从设备读数据”(如读取传感器采集的数值),步骤如下:
1.配置I2C_CLKDIV:设置 SCL 频率;
2.配置I2C_CON:选择“只接收模式”,发送起始信号;
3.配置I2C_MRXCNT:设置接收数据的字节数;
4.等待“接收完成中断”(I2C_IPD[3]);
5.若有更多数据,重复步骤 3~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):
struct i2c_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 时钟频率
RK I2C 内核态开发遵循 Linux 标准 I2C 接口,参考内核文档 Documentation/i2c/writing-clients,核心是:
•注册 I2C 客户端驱动(i2c_driver);
•使用i2c_master_send()(写数据)和i2c_master_recv()(读数据)接口。
通过/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。
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 寄存器)。
RK 内核支持用 GPIO 模拟 I2C,但效率低,仅适合临时调试。配置示例(DTS):
i2c@4 {compatible = "i2c-gpio";gpios = <&gpio5 9 GPIO_ACTIVE_HIGH>, // SDA 引脚<&gpio5 8 GPIO_ACTIVE_HIGH>; // SCL 引脚i2c-gpio,delay-us = <2>; // 约 100kbps 频率status = "okay";// 挂载从设备(如 GT9xx 触摸屏)gt9xx: gt9xx@14 {compatible = "goodix,gt9xx";reg = <0x14>;// 其他外设参数...};};
开发中难免遇到问题,文档中整理了两类驱动的常见错误,我们按“错误类型” 分类梳理,方便排查。
•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,需排查外部干扰(如布线是否靠近强电)。
根据日志中ipd 的值,对应不同问题:
•问题:I2C 控制器异常,无法发送起始信号;
•原因:
a.SCL/SDA 引脚复用错误(IOMUX 配置错);
b.上拉电压不对(如 3.3V 上拉变成 1.8V);
c.引脚被外设拉低(电压异常);
d.I2C 时钟未开启或时钟源过小;
e.同时配置了CON_START 和CON_STOP 位(寄存器配置冲突)。
•问题:控制器正常,但 CPU 无法响应 I2C 中断;
•原因:
a.CPU0 被阻塞(RK I2C 中断默认在 CPU0,用 cat /proc/interrupts 查看);
b.I2C 中断位被关闭(检查中断使能寄存器)。
•问题:SCL 被从设备拉低(总线卡死);
•排查方法:
a.排除法:若外设少,逐个断开外设,复现问题定位“元凶”;
b.硬件检测:在 SCL 总线串入电阻(如 220Ω,约上拉电阻的 1/20),测量电阻两端压差 —— 电压更低的一端对应拉低 SDA/SCL 的设备;
c.波形验证:用示波器抓取波形,对比不同从设备的低电平,与故障时的低电平匹配的即为问题设备。
若以上方法无法解决,最有效的方式是抓取故障时的 I2C 波形:
1.在代码中“卡住 CPU”:在出错位置加 while(1),避免发起新的 I2C 任务;
2.用示波器测量 SDA 和 SCL 引脚:观察是否有起始信号、地址字节、ACK 信号;
3.对比协议要求:若波形缺失(如无起始信号),检查控制器配置;若有 NACK,检查从设备地址或状态。
最后,用一张脑图总结全文核心,方便大家收藏回顾:

RK 平台的 I2C 开发,核心是 “理解协议 + 区分驱动 + 重视时序”—— 只要掌握了硬件原理和数据帧规则,再结合官方文档配置驱动、排查问题,就能快速搞定绝大多数场景。
如果大家在开发中遇到具体问题,欢迎在评论区交流;也可以收藏本文,遇到问题时对照脑图和排查步骤,效率会更高!
全部0条评论
快来发表一下你的评论吧 !