Linux 设备驱动 -- I2C 子系统快速入门
前言
- 个人邮箱:zhangyixu02@gmail.com
- I2C 是一个简单的低速通讯协议,其通讯原理以及被网上讲烂了,因此我这里不会进行赘述。
- 我这里主要是介绍 I2C 的 Linux 驱动层架构和 API。说明非芯片原厂的 I2C 驱动设备适配过程中要考虑的软硬件方向。
- 想要全面了解 I2C 协议,建议阅读: I2C协议文档(中文版)- 周立功翻译
Linux I2C 子系统
Linux I2C 子系统架构
- Linux I2C 子系统架构图如下:
- 设备驱动层 : 非芯片原厂工程师编写,该类人群为大多数,主要是负责给公司生产的 Linux 开发板做一些板级支持。
- 核心层 : 这一部分为 Linux 内核开发者来编写。他们起到承上启下的作用,负责定义各类 API 接口。无论是设备驱动层还是适配器驱动层,都是要听核心层的规定来进行填鸭。
- 适配器驱动层 : 芯片原厂工程师编写,负责将公司生产的芯片 I2C 根据核心层定义进行适配。因核心层定义的 API 较多,芯片原厂工程师也是会根据情况适配常用的 API,并不一定会将核心层所有的 API 接口适配完毕。
设备驱动 I2C
设备驱动设备树
- 我这里仅介绍设备驱动中 I2C 的设备树编写方法。
- &i2c1 : 表明当前设备挂载在 i2c1 总线上。
- clock-frequency : 设置 I2C 总线的时钟频率为 100kHz
- pinctrl : pinctrl属性,这个需要根据具体芯片具体分析
- at24c02@50 : 设备节点
- compatible : 与驱动代码中的 i2c_driver.driver.of_match_table.compatible属性匹配
- reg : I2C 从机设备地址。驱动与设备树匹配成功后,该值存储在 i2c_client.addr 中。
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
at24c02@50 {
compatible = "at24c02";
reg = <0x50>;
};
};
注册/注销
- i2c 子系统注册和平台总线模型注册类似,只不过这里是调用的
i2c_add_driver()
函数。然后数据类型有所不同,直接看代码更容易理解。所以我就不再赘述。
static const struct of_device_id dts_table[] =
{
{.compatible = "at24c02"},
{ /* Sentinel (end of array marker) */ }
};
static const struct i2c_device_id at24c02_ids[] = {
{ "xxxxyyy", (kernel_ulong_t)NULL },
{ /* END OF LIST */ }
};
static struct i2c_driver at24c02_i2c_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "at24c02",
.of_match_table = dts_table,
},
.probe = i2c_drver_probe,
.remove = i2c_drver_remove,
.id_table = at24c02_ids,
};
static int __init i2c_drv_init(void)
{
int ret = 0;
printk("i2c_add_driver before \n");
ret = i2c_add_driver(&at24c02_i2c_driver);
if(ret < 0)
{
printk("i2c_add_driver error \n");
return ret;
}
printk("i2c_add_driver ok \n");
return ret;
}
- 这里需要注意的是,对于早于 4.10 的内核版本,即使不需要使用
.id_table
成员变量,仅进行设备树匹配,驱动开发者也需要强制加上该变量。其原因是因为drivers/i2c/i2c-core.c
中i2c_device_probe()
函数有如下判断条件。
if (!driver->probe || !driver->id_table)
return -ENODEV;
数据传输
- 设备驱动层的 I2C 数据传输的 API 有 3 个 :
i2c_master_recv()
、i2c_master_send()
、i2c_transfer()
。 - 但是如果你真的去看其底层实现,会发现这三个 API 本质上就只有一个,即
i2c_transfer()
。
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
{
struct i2c_adapter *adap = client->adapter;
struct i2c_msg msg;
int ret;
msg.addr = client->addr;
msg.flags = client->flags & I2C_M_TEN;
msg.flags |= I2C_M_RD;
msg.len = count;
msg.buf = buf;
ret = i2c_transfer(adap, &msg, 1);
/*
* If everything went ok (i.e. 1 msg received), return #bytes received,
* else error code.
*/
return (ret == 1) ? count : ret;
}
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
{
int ret;
struct i2c_adapter *adap = client->adapter;
struct i2c_msg msg;
msg.addr = client->addr;
msg.flags = client->flags & I2C_M_TEN;
msg.len = count;
msg.buf = (char *)buf;
ret = i2c_transfer(adap, &msg, 1);
/*
* If everything went ok (i.e. 1 msg transmitted), return #bytes
* transmitted, else error code.
*/
return (ret == 1) ? count : ret;
}
- 既然我们知道了数据传输本质上就只是
i2c_transfer()
函数,那么为了保证简约,我就只介绍该 API 了。
struct i2c_msg {
__u16 addr; /* 从机地址 */
__u16 flags; /* 传输方向标志(读/写),其可选宏定义如下 */
#define I2C_M_RD 0x0001 /* 读取操作,即从从设备读取数据到主设备 */
#define I2C_M_TEN 0x0010 /* 使用 10 位从设备地址模式 */
#define I2C_M_RECV_LEN 0x0400 /* 用于 SMBus 协议。当设置此标志时,主设备首先接收一个字节,该字节指示后续数据的长度。 */
#define I2C_M_NO_RD_ACK 0x0800 /* I2C 控制器声明支持 I2C_FUNC_PROTOCOL_MANGLING,则在读取过程中不发送 ACK 信号 */
#define I2C_M_IGNORE_NAK 0x1000 /* I2C 控制器声明支持 I2C_FUNC_PROTOCOL_MANGLING,则在从设备未应答(NAK)时忽略该情况,继续传输后续消息 */
#define I2C_M_REV_DIR_ADDR 0x2000 /* I2C 控制器声明支持 I2C_FUNC_PROTOCOL_MANGLING,则在地址阶段设置读取/写入方向位,与标准 I2C 协议相反 */
#define I2C_M_NOSTART 0x4000 /* I2C 控制器声明支持 I2C_FUNC_NOSTART,则在本次消息传输前不发送起始信号(START),这在某些特殊情况下使用*/
#define I2C_M_STOP 0x8000 /* I2C 控制器声明支持 I2C_FUNC_PROTOCOL_MANGLING,则在本次消息传输结束后发送停止信号(STOP) */
__u16 len; /* 数据缓冲区的长度 */
__u8 *buf; /* 数据缓冲区(指向传输的数据) */
};
/* 作用 : 读取指定 I2C 设备的连续多个寄存器数据
* adap : I2C 控制器
* msgs : 指向 i2c_msg 结构体数组的指针。每个 i2c_msg 结构体表示一次单独的 I2C 读/写操作,他定义了 I2C 数据的传输方向、地址、数据指针和数据长度等信息。
* num : msgs 数组的长度,即表示需要传输的 I2C 消息数量
* 返回值 : 成功时,返回已成功传输的 I2C 消息数量;否则返回负数
*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
I2C 应用设计
仲裁机制
- I2C 支持多主多从,因此 I2C 总线上可能出现两个或多个主控同时向总线发送数据的情况,这种情况专业术语叫做总线竞争。
- 多主机时,总线具有“线与”的逻辑功能,即只要有一个节点发送低电平时,总线上就表现为低电平。
- 总线被启动后,多个主机在每发送一个数据位时都要对自己的输出电平进行检测,只要检测的电平与自己发出的电平一致,就会继续占用总线。
时钟延展
- 我们都知道,I2C 通讯中,时钟信号 SCL 是由主机产生。但当主机和从机的速率不匹配时,从设备就可以强行拉低 SCL 保持低电平来暂缓传输。
- 时钟延展功能应用场景如下:
- 传感器和 ADC/DAC:一些传感器或模拟-数字转换器(ADC)、数字-模拟转换器(DAC)在采样数据时需要额外的时间。在这种情况下,使用时钟延展可以确保主设备不会在从设备未准备好时继续发送数据。
- 外部存储设备:在与 EEPROM 或其他存储设备的通信中,如果存储器内部的写入操作较慢,时钟延展可以确保数据被正确写入或读取。
- 微控制器:如果从设备为处理器,当收到主机的命令处理任务后进行返回结果。但因处理器的处理速度限制,导致速率跟不上,就可以采用时钟延展的功能。
3.注意:- 并不是所有 I2C 主机都支持时钟延展,如果主机不支持时钟延展,可能导致整个通讯失败!
- 如果 GPIO 设置为了推挽输出,可能导致从机无法下拉电平,最终无法实现时钟延展功能!
推挽与开漏
- I2C 要求将 GPIO 配置为推挽模式,其目的是为了实现"线与"功能,但是,并不是必须为推挽输出!
- 一主一从:当I2C总线上仅有一个主设备和一个从设备,且无其他设备接入时,可以采用推挽输出。
- I2C高速模式(HS Mode):推挽输出比开漏输出拥有更快的速率,因此在高速模式下,会切换成推挽输出。
上拉电阻选取
最小值
-
I2C 总线上的上拉电阻最小值计算公式如下 :
-
V C C V_{CC} VCC : I2C 总线供电电压。
-
V O L V_{OL} VOL : 主控芯片 GPIO 低电平时刻的最大输出电压,通常为 0.4V。
-
I O L I_{OL} IOL : 主控芯片 GPIO 最大灌电流能力,通常为 3mA。
-
R m i n = V C C − V O L I O L R_{min} = \frac{V_{CC}-V_{OL}}{I_{OL}} Rmin=IOLVCC−VOL
- 如果 I2C 总线采用 3.3V 系统,那么最终计算可得总线最小上拉电阻为 967欧。考虑到电阻生产过程中存在的阻值偏差问题,所以一般最小采用 1k 阻值电阻。
R m i n = V C C − V O L I O L = 3.3 V − 0.4 V 3 m A ≈ 967 Ω R_{min} = \frac{V_{CC}-V_{OL}}{I_{OL}} = \frac{3.3V-0.4V}{3mA}\approx 967\Omega Rmin=IOLVCC−VOL=3mA3.3V−0.4V≈967Ω
最大值
- I2C 总线上的上拉电阻最大值计算公式如下 :
-
t r t_{r} tr : 信号上升时间。
-
C b C_{b} Cb : I2C 总线电容。
-
0.8473 : 常数,用于近似 RC 电路的时间常数。
R m a x = t r 0.8473 ∗ C b R_{max} = \frac{t_{r}}{0.8473 \ast C_{b}} Rmax=0.8473∗Cbtr
- I2C 的总线电容在标准模式(100Kbit/s)和快速模式(400Kbit/s),负载的最大容量分别是 400pF 和 200pF。实测后发现,一般总线电容超过 200pF 波形就不太漂亮。所以这里总线电容以 200pF 来进行计算。
- 在标准模式、快速模式、高速模式下的 t r t_{r} tr 最大值分别为 1000ns、300ns、120ns。
4.最后可计算得到不同模式下的最大电阻值。
- 标准模式: R m a x = t r 0.8473 ∗ C b = 1000 ∗ 1 0 − 9 0.8473 ∗ 200 ∗ 1 0 − 12 ≈ 5.9 k Ω R_{max} = \frac{t_{r}}{0.8473 \ast C_{b}}=\frac{1000\ast10 ^{-9}}{0.8473 \ast 200\ast10^{-12}}\approx5.9k\Omega Rmax=0.8473∗Cbtr=0.8473∗200∗10−121000∗10−9≈5.9kΩ
- 快速模式: R m a x = t r 0.8473 ∗ C b = 300 ∗ 1 0 − 9 0.8473 ∗ 200 ∗ 1 0 − 12 ≈ 1.77 k Ω R_{max} = \frac{t_{r}}{0.8473 \ast C_{b}}=\frac{300\ast10 ^{-9}}{0.8473 \ast 200\ast10^{-12}}\approx1.77k\Omega Rmax=0.8473∗Cbtr=0.8473∗200∗10−12300∗10−9≈1.77kΩ
- 高速模式: R m a x = t r 0.8473 ∗ C b = 120 ∗ 1 0 − 9 0.8473 ∗ 200 ∗ 1 0 − 12 ≈ 708 Ω R_{max} = \frac{t_{r}}{0.8473 \ast C_{b}}=\frac{120\ast10 ^{-9}}{0.8473 \ast 200\ast10^{-12}}\approx708\Omega Rmax=0.8473∗Cbtr=0.8473∗200∗10−12120∗10−9≈708Ω
- 通过上面计算,我们能够注意到一个问题:“在高速模式中,如果总线电压为3.3V,最大电阻值为 708 Ω 708\Omega 708Ω,而计算得到的最小电阻反而是 967 Ω 967\Omega 967Ω。”因此,高速模式下,I2C 总线电压一般采用 1.8V。
总结
- 上拉电阻的范围一般在1K~10k之间,之所以有这个范围,可以简单的认为:电阻过小,功耗比较大,而且容易烧毁 I2C 接口;而电阻过大,会影响信号的上升沿时间,也就是影响到时钟频率,会出现误码。
总线电容
总线电容带来的影响
- I2C 从设备包含7位和10位地址。
- 7位地址模式:理论容量 128 个设备,去掉保留地址和特殊用途地址还有128-16=112个。
- 10位地址模式:理论容量 1024 个设备,保留地址组:11111XX,通用广播地址:00000000, 其他特殊用途地址(如00000001),因此最终留下 1008 个。
- 这样算下来,I2C 总线上可以挂载上千个从机设备。但是真的是这样的吗?前面上拉电阻的计算中讲到了,一般不建议让总线电容超出 200pF。随着连接设备数量的增加,总线电容增大,信号的上升和下降时间会延长,可能导致通信错误。因此,在设计时需要权衡设备数量和通信速率,以确保信号完整性。
- 在实际应用中,由于总线电容和信号完整性的限制,通常建议连接的从设备数量不超过 8 至 10 个。
I2C 缓冲器/I2C 中继器
- 在消费电子中,I2C 总线挂载的从设备一般不会超过8个。可是在工业自动化领域中是需要进行冗余设计的,核心是为了防止单点故障,能够做到自动切换,降低故障率。
- 工业传感器网络 I2C 从机设备可能高达20个甚至更多的传感器,主要是为了多参数协同监控,融合了工艺优化、质量控制、设备维护、能源管理等多重需求。
- 这个时候肯定有人会问,I2C 通讯距离不是一般不建议超过 50cm吗?即使优化上拉电阻(1-2.2kΩ)和屏蔽线缆 其最大长度也不建议超出 2m。如此高密度的情况下,真的需要这么多的传感器吗?
- 在一些工业机床中其传感器密度可能为 15-20个/m²,这明显是超出的 8至 10 个从设备的限制。为了解决这个问题,我们可以采用 I2C 缓冲器来切割总线电容,从而能够挂载更多的从机设备。
- 在实际应用中,I2C中继器(Repeater) 和 I2C缓冲器(Buffer) 可能存在部分功能重叠。部分厂商可能会将两者概念混合,所以具体情况还是需要看芯片数据手册。
- I2C中继器(Repeater) :核心是是延长总线的传输距离。
- I2C缓冲器(Buffer) : 隔离总线,降低每个部分的电容负载,从而提高信号完整性和通信可靠性。
debug
i2c-tools
- i2c-tools 配置视频教程:RK3568驱动指南|第十五篇 I2C-第180章 I2C Tools工具讲解
- I2C Tools中存在五个工具:
- i2cdetect : 扫描 I2C 总线上的连接设备
- i2cdump : 读取某个 I2C 从设备上所有寄存器的值
- i2cset : 设置某个 I2C 从设备上指定寄存器的值
- i2cget : 读取某个 I2C 从设备上指定寄存器的值
- i2ctransfer : 在单个命令中完成读写多个字节操作
i2cdetect
- 如下为 i2cdetect 工具使用说明。
Usage:i2cdetect[-y][-a][-q|-r] I2CBUS [FIRST LAST]
i2cdetect -F I2CBUS
i2cdetect -l
I2CBUS is an integer or an I2c bus name
If provided, FIRsT and LAsT limit the probing range.
选项 | 解释 |
---|---|
-y | 取消交互模式。默认情况下,i2cdetect 将等待用户的确认,当使用此标志时,它将直接执行操作。 |
-a | 强行扫描非规则地址(一般不推荐)。 |
-q | 使用 SMBus “快速写入” 命令进行探测(一般不推荐)。 |
-r | 使用 SMBus “接收字节” 命令进行探测(一般不推荐)。 |
-F | 显示系统总线支持的功能列表。 |
-V | 显示 I2C 工具版本。 |
-l | 显示已经在系统中使用的 I2C 总线。 |
i2cbus | 表示要扫描的 I2C 总线的编号或名称。 |
fisrt last | 表示要扫描的从设备地址范围。 |
- 显示已经在系统中使用的 I2C 总线。如下表示当前有两条 I2C 总线可供使用。
$ i2cdetect -l
i2c-0 i2c Freescale i2c bus 0 I2C adapter
i2c-1 i2c Freescale i2c bus 1 I2C adapter
- 查询当前芯片平台的指定 I2C-0 总线支持的功能。
- 查看指定 I2C 总线上的从机设备挂载情况。下图表示,当前 I2C-0 总线上一共有四个从机设备,分别是 0x40、0x41、0x44、0x50。其中0x40、0x41这两个设备已经被某一个驱动使用,不可以再对该地址进行操作。0x44、0x50上存在设备,但可以对其进行操作。
- “–” :表示该地址被检测到了,但是没有芯片应答。
- “UU” :表示地址正在被某一个驱动使用。
- 需要注意:不是所有的i2c设备都能探测到,有些16位寄存器i2c设备无法探测到,可以尝试使用i2ctransfer去尝试读写来判断当前地址是否存在设备。
$ i2cdetect -a -y 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: UU UU -- 44 -- -- -- -- -- -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
i2cdump
- 使用说明如下。
i2cdump [-f] [-r first-last] [-y] i2cbus address [mode [bank [bankreg]]]
I2CBUS: 指定 I²C 总线编号,例如 0、1 等
address: 目标 I²C 设备的从地址,以 16 进制表示,例如 0x50(0x03-0x77,如果给出了-a,则为0x00 - 0x7f)
MODE:指定读取模式,常见模式包括:
b: 字节模式(默认),每次读取 1 字节的数据,并显示为 2 个十六进制字符
w: 字模式,每次读取 2 字节数据(即 16 位)并显示为 4 个十六进制字符
W: 双字模式,每次读取 4 字节数据(即 32 位)并显示为 8 个十六进制字符
s: 块模式,读取特定的连续寄存器地址中的数据块,适合于读取存储器或数据缓存等连续区域的内容
i: 单字节输入输出模式,读取设备的单字节数据并尝试对设备进行写操作,该模式可以通过设备的寄存器读取数据并同时对特定寄存器进行修改
c: 字节读取和写入模式,类似于 i 模式,但这种模式主要用于读取和写入字符数据,适合读取字符型寄存器内容
选项 | 解释 |
---|---|
-r | 限制正在访问的寄存器范围。此选项仅在b,w,c,W中可用。对于模式W,first 必须是偶数,last必须是奇数。 |
-f | 强制访问设备,即使它处于 busy 状态。 |
-y | 取消人机交互模式。默认情况下,i2cdetect 将等待用户的确认,当使用此标志时,它将直接执行操作。 |
-V | 显示 I2C 工具版本。 |
i2cbus | 表示要扫描的 I2C 总线的编号或名称。 |
fisrt last | 表示要扫描的从设备地址范围。 |
- 常用命令:查看 i2c-0 上 0x44 外设所有寄存器的值。
i2cdump -f -y 0 0x44
i2cset
- 使用方法如下。
i2cset [-f] [-y] [-m MASK] [-r] [-a] i2cbus chip-address data-address [value] ... [mode]
- 常见用法:向 i2c-0 总线上 0x44 地址从设备 0x40 寄存器中写入 0x55。
i2cset -f -y 0 0x44 0x40 0x55
i2cget
- 使用方法如下。
i2cget [-f] [-y] [-a] i2cbus chip-address [data-address [mode]]
- 常见用法:读取 i2c-0 总线上 0x44 地址从设备 0x40 寄存器中的值。
i2cget -f -y 0 0x44 0x40
i2ctransfer
- 使用方法如下。
i2ctransfer [-f] [-y] [-v] [-V] [-a] I2CBUS DESC [DATA] [DESC [DATA]]...
选项 | 解释 |
---|---|
-f | 强制访问设备,即使它处于 busy 状态。 |
-y | 取消人机交互模式。默认情况下,i2cdetect 将等待用户的确认,当使用此标志时,它将直接执行操作。 |
-v | 启动详细输出,它将打印所有信息发送,不论是读消息还是写消息 |
-V | 显示 I2C 工具版本。 |
-a | 允许在0x00 - 0x02 和 0x78 - 0x7f 之间使用地址。不推荐。 |
i2cbus | 表示要扫描的 I2C 总线的编号或名称。 |
desc | {r|w}length[@address]r :读取操作w :写入操作length :要读取或写入的数据字节数,范围为 0 到 65535@address :I²C 设备地址,如果省略,默认使用最后一个指定的设备地址 |
DATA | 传输的数据,数据长度由 LENGTH 指定,可以使用特定的后缀进行操作 |
- 向 i2c-0 总线上地址为 0x44 的从机写入两个字节,分别为0x40、0x10。
i2ctransfer -f -y 0 w2@0x44 0x40 0x10
- 向 i2c-0 总线上地址为 0x44 的从机先写入 1 字节的 0x10,后读出从机 0x10 地址的 4 个寄存器的值。
i2ctransfer -f -y 0 w1@0x44 0x10 r4
I2C ACK error
- 在I2C协议中,主机向从机写入 1 字节数据后(8个脉冲),第9个时钟脉冲为 ACK/NACK 信号周期,从机通过拉低 SDA 线发送 ACK信号。当主机检测到 SDA 未被拉低时,即触发 ACK Error(错误码ENXIO)。
- 排查思路:
- 使用 i2c-tools 检查总线上设备是否存在。如果不存在,考虑其硬件问题,例如接线是否稳定,从机是否损坏。
# 探测可用i2c总线
i2cdetect -l
# 部分16位寄存器i2c设备无法探测到,需要使用如下命令
i2cdetect -a -y 0
# 探测0x40地址是否存在设备
i2ctransfer -y 0 w2@0x40 0x00 0x00 r1
- 如果设备存在,需要拿示波器抓取波形。 如果发现存在波形,但是从机就是不会拉低 SDA 线,可以适当降低 I2C 通讯速率,来检查主从是否速率不匹配。
- 如果发现波形存在,但是无法将电平完全拉高,或者只有小上升趋势,考虑 GPIO 模式配置是否正确,GPIO 驱动电流是否满足,查看上拉电阻是否正确。
I2C timeout
- I2C 通讯中,并没有严格规定其通讯的规定时间。但是在一些驱动程序中利用定时器设置一个超时时间。当时间到了,定时器中断中会去检测对应的寄存器,检测I2C通讯是否完成。
- 其解决办法如下:
- GPIO 配置:
- GPIO 电流驱动能力
- GPIO 工作模式是否是 I2C 模式
- GPIO 是否有内部上拉电阻
- GPIO 默认电平状态
- 排查 slave 顺序:
- log 中第一个发生 timeout 的 slave
- 有 power 控制和 reset 控制的 slave
- other slave
- 复现问题后,可以手动将相应外设去掉,确认是哪个外设将 i2c bus 拉住,再与供应商沟通,debug 一下该 IC 状态,理清拉住 i2c bus原因。
其他可能问题
芯片复位却发现 SDA 被钳位至低电平
- 常见原因是因为主机和从机在通讯过程中,通讯并没有完成,但是主机此时进行复位,而从机要发送数据 0 或者 ACK,最终导致 SDA 总线被从机钳位至低电平。
- 解决办法:将 SCL 配置为推挽输出,SDA 引脚为浮空输入。SCL 模拟脉冲,每发送一次脉冲,就检测一下 SDA 线是否为高电平。如果不是高电平,SCL 继续发送脉冲。如果为高电平,那么主机此时立马接管总线,发送停止信号。
SCL 钳位至低电平
- SCL被钳位至低电平,一般都是因为从设备也是一个处理器的原因。要从该处理器从机程序下手。
ACK 存在毛刺
- 如下图,从机在第9个CLK产生ACK后出现了一个毛刺。其原因是从机与主机的换手时差所导致,即在这个时差中,无人控制总线所产生了毛刺。
- 该毛刺并不会影响 I2C 的通讯,但如果追求通讯时序漂亮,可以联系原厂调整 master 的 setup time 和 hold time 来减小毛刺幅值。
参考
- RK3568驱动指南|第十五篇 I2C-第169章I2C子系统框架学习;
- Github 设备驱动笔记 : I2C 驱动;
- I2C的总线电容 总线的最大电容 400pF限制;
- I2C协议文档(中文版)- 周立功翻译;
- TI 应用笔记—I2C buffers overview;
- I2C总线:通信线缆长度的影响及改进措施;
- I2C总线:何时使用I2C缓冲器;
- RK3568驱动指南|第十五篇 I2C-第180章 I2C Tools工具讲解;
- i2cdetect 使用总结;
- I2C从机挂死分析和解决方法 ;
- I2C传输发生timeout时;
- 为什么MCU I2C波形中会出现的脉冲毛刺?;