调触摸驱动时,我们经常会看到类似这样的信息:

这里的 i2c2、reg = <0x5d> 到底是什么意思?主控又是怎么通过两根线找到对应外设的?这篇就以 RK3576 为例,简单捋一捋 I2C 的通信机制。
I2C,全称是 Inter-Integrated Circuit,直译过来就是“内部集成电路总线”。
不用纠结这个名字,简单理解就是:
I2C 是一种两线制串行通信协议,只需要两根线,就能实现主控和多个外设之间通信。
这两根线分别是:
SDA:Serial Data,串行数据线,用来传输数据;SCL:Serial Clock,串行时钟线,用来同步通信节奏。
也就是说,I2C 通信至少需要两根线:一根负责“说话”,一根负责“打节拍”。

在 RK3576 这类 SoC 中,I2C 控制器一般由主控内部提供,比如 I2C0、I2C1、I2C2 等。外部设备则挂在对应的 I2C 总线上。
为了好理解,可以把 I2C 通信理解成 RK3576 发起的一次“点名对话”。
假设:
主机:RK3576从设备:GT911 触摸芯片设备地址:0x5d
整个过程大概是:
RK3576:大家注意,我要开始通信了!RK3576:地址是 0x5d 的设备在不在?GT911:在!RK3576:我要给你写数据 / 我要从你这里读数据。GT911:收到。RK3576:通信结束,总线释放。
I2C 协议看起来有点绕,但核心流程就是这么回事。
I2C 通信开始前,主机会先发送一个起始信号,也就是 Start Condition。
条件是:SCL 保持高电平期间,SDA 从高电平跳变为低电平。
这个动作的意思就是:总线上的外设注意,我要开始通信了。
此时挂在这条 I2C 总线上的所有从设备都会“听着”,等待后面的地址匹配。
这里有个关键点:
I2C 总线上,SDA 的变化不是随便什么时候都算有效。一般来说,只有在 SCL 为高电平时,SDA 的跳变才有特殊意义。
比如:
SCL 高电平时,SDA 高 -> 低:起始信号SCL 高电平时,SDA 低 -> 高:停止信号
而在数据传输过程中,SDA 通常会在 SCL 低电平期间准备数据,在 SCL 高电平期间保持稳定,供接收方采样。
这些时序一般由 RK3576 的 I2C 控制器自动处理,我们写驱动时通常不用手动去拉高拉低 SDA/SCL。
起始信号之后,RK3576 会发送从设备地址。
I2C 常见地址是 7bit,再加 1bit 读写控制位,组成 8bit 数据。
格式如下:
前 7 位:从设备地址最后 1 位:读写位0:写1:读
比如 GT911 的设备地址是 0x5d,那么 RK3576 会在总线上发出这个地址。
总线上所有从设备都会收到这个地址,但只有地址匹配的设备才会应答。这个应答信号叫 ACK。
ACK 的表现是:接收方在第 9 个时钟周期,将 SDA 拉低。
也就是说,I2C 发送 1 个字节并不是只需要 8 个时钟,而是需要 9 个时钟:
前 8 个时钟:传输 8bit 数据第 9 个时钟:传输 ACK 应答
如果主机没有检测到 ACK,通常说明:
设备地址不对;设备没有上电;I2C 引脚配置不对;外设没有正常工作;总线硬件异常。
所以调 I2C 设备时,如果设备一直不应答,第一反应就应该去查地址、供电、复位、pinctrl、上拉电阻这些基础条件。
地址匹配成功后,就进入真正的数据传输阶段。
I2C 数据传输的单位是字节,也就是 8bit。
并且传输时遵循:高位在前,MSB first。
比如一个字节 0xA5,会从最高位 bit7 开始传。
根据读写位不同,通信方向也不同。
写操作是:RK3576 -> 从设备。比如主控给 GT911 写配置、写寄存器地址等。
过程大概是:
RK3576 发送 8bit 数据从设备回复 ACKRK3576 再发送下一个 8bit 数据从设备继续 ACK直到数据发送完成
读操作是:从设备 -> RK3576。比如主控读取触摸坐标、读取芯片 ID、读取状态寄存器等。
过程大概是:
从设备发送 8bit 数据RK3576 回复 ACK从设备继续发送下一个 8bit 数据RK3576 继续 ACK直到读取完成
以上这些通信细节,大部分由 I2C 控制器和 Linux I2C 子系统帮我们处理好了。我们写驱动时,更多是调用类似 i2c_transfer()、i2c_smbus_read_byte_data() 这类接口,而不是手动模拟时序。
数据传输完成后,主机会发送停止信号,也就是 Stop Condition。
条件是:SCL 保持高电平期间,SDA 从低电平跳变为高电平。
这个动作表示:本次通信结束,总线释放。
停止信号发出后,从设备回到等待状态,RK3576 的 I2C 控制器也可以继续和其他外设通信。
通过前面的“点名对话”,可以总结出 I2C 通信里几个最关键的信号。

理解这三个信号,基本就能看懂 I2C 通信的大致过程了。
I2C 是一条总线上挂多个设备,因此每个从设备都需要有自己的地址。
但实际项目中,可能会遇到两个外设地址一样的情况。比如两个相同型号的触摸芯片,默认地址都一样,那就会冲突。
因为主机一喊 0x5d,两个设备都应答,总线就乱套了。针对这种情况,一般有两种解决办法。
RK3576 有多个通用 I2C 控制器,比如 I2C0、I2C1、I2C2 等。如果两个设备地址一样,可以把它们分别挂到不同的 I2C 总线上。
比如:
设备 A -> I2C2设备 B -> I2C3
这样虽然地址一样,但它们不在同一条总线上,就不会冲突。
有些外设支持通过硬件引脚配置地址。比如某些芯片可以通过拉高或拉低地址选择引脚,切换不同的 I2C 地址。这种情况下,只要硬件设计时把地址区分开,就能避免冲突。
所以画原理图时,I2C 地址一定要提前确认,不然后期调试会很难受。
实际调 I2C 外设时,不要一上来就怀疑驱动。
可以先按下面几个点查:
[ ] 外设供电是否正常?[ ] reset / enable 引脚是否拉到正确电平?[ ] SDA / SCL 是否配置了正确 pinctrl?[ ] I2C 控制器节点 status 是否为 okay?[ ] 从设备 reg 地址是否正确?[ ] SDA / SCL 是否有上拉电阻?[ ] 是否存在地址冲突?[ ] i2cdetect 能否扫到设备?
很多 I2C 问题,最后都不是协议本身有多复杂,而是供电、复位、地址、pinctrl 这些基础条件没对上。
I2C 通信总结起来其实就是几个关键词:
两根线:SDA + SCL一个主机:RK3576多个从设备:触摸、PMIC、RTC、EEPROM 等三个信号:Start、ACK、Stop一个地址:通过从设备地址找到目标外设
整个过程就像主控在总线上点名:
开始通信 -> 发送地址 -> 等待应答 -> 读写数据 -> 停止通信
对于驱动开发来说,理解到这一层基本就够用了。再往下深挖,就是更底层的时序、电气特性、模拟电路和数字电路内容了。

那些当然也重要,但对大多数基于 SDK 做外设适配的开发者来说,先搞懂通信流程、地址匹配和 ACK 应答,已经足够解决大部分 I2C 调试问题。
(完)
下期继续聊:为什么 I2C需要上拉?
本人专注 Linux 嵌入式全栈开发,可提供从硬件方案评估与设计、Linux/Android BSP 适配、驱动开发、外设调试、系统移植到产品交付的全流程技术支持。
全部0条评论
快来发表一下你的评论吧 !