往期内容
I2C子系统专栏:
- I2C(IIC)协议讲解-CSDN博客
- SMBus 协议详解-CSDN博客
- I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客
- 内核提供的通用I2C设备驱动I2c-dev.c分析:注册篇
- 内核提供的通用I2C设备驱动I2C-dev.c分析:file_ops篇
- 设备驱动与设备树匹配机制详解
- 编写一个通用的i2c设备驱动框架
- 编写一个通用的i2c控制器驱动框架
- consumer 角度讲一下i2c外设-CSDN博客
总线和设备树专栏:
前言
-
Linux文档
- Linux-4.9.88\Documentation\devicetree\bindings\i2c\i2c-gpio.txt?i2c-gpio.txt
-
Linux驱动源码
- Linux-4.9.88\drivers\i2c\busses\i2c-gpio.c?i2c-gpio.c
- Linux-4.9.88\drivers\i2c\algos\i2c-algo-bit.c?i2c-algo-bit.c
-
扩展板原理图:imx6ull_extend_v10.pdf
?07_i2c_gpio_dts_imx6ull.zip
如果I2C接口、总线不够用,就可以使用GPIO来模拟I2C总线(简易的I2C控制器,没有中断休眠功能,用两个GPIO引脚模拟I2C控制器的驱动),模拟I2C协议进行输出
Bitbanging I2C bus driver using the GPIO API
注:驱动代码分析部分皆摘抄自linux内核提供的用gpio模拟IIC协议的驱动文件:i2c-gpio.c
1. 回顾I2C协议
内容在之前的章节都有讲过,具体内容跳转:
2. 使用GPIO模拟I2C的要点
- 引脚设为GPIO
- GPIO设为输出、开极/开漏(open collector/open drain)
- 要有上拉电阻
3. 驱动程序分析
3.1 平台总线设备驱动模型
3.2 设备树
对于GPIO引脚的定义,有两种方法:
- 老方法:gpios
i2c-gpio{
compatible = "i2c-gpio";
gpios = <&gpio0 3 0
&gpio 2 0>;
i2c-gpio,delay-us = <2>;
#address-cells = <1>;
#size-cells = <0>;
rtc0:rtc@48{
compatible = "st,m41t00";
reg = <0x68>;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 新方法:sda-gpios、scl-gpios
i2cl:i2c{
compatible = "i2c-gpio";
sda-gpios = <&gpioa 5 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
scl-gpios = <&gpioa 4 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
}
- 1
- 2
- 3
- 4
- 5
3.3 驱动程序分析
I2C-GPIO驱动层次
-
分析的文件为内核提供的用gpio模拟i2c驱动源码:
- Linux-4.9.88\drivers\i2c\busses\i2c-gpio.c?i2c-gpio.c
- Linux-4.9.88\drivers\i2c\algos\i2c-algo-bit.c?i2c-algo-bit.c
-
可以简单的看看上图,下面是深入理解,注意注释中标注(1.1)等标志的会抽出来另外讲解
Linux-4.9.88\drivers\i2c\busses\i2c-gpio.c:
static int i2c_gpio_probe(struct platform_device *pdev)
{
struct i2c_gpio_private_data *priv;
struct i2c_gpio_platform_data *pdata;
struct i2c_algo_bit_data *bit_data;
struct i2c_adapter *adap;
unsigned int sda_pin, scl_pin;
int ret;
/* 从设备树中获取 I2C SDA 和 SCL 引脚号,如果失败则延迟探测 */
if (pdev->dev.of_node) {
ret = of_i2c_gpio_get_pins(pdev->dev.of_node, &sda_pin, &scl_pin);
if (ret)
return ret;
} else {
/* 如果没有设备树节点,获取平台设备数据 */
if (!dev_get_platdata(&pdev->dev))
return -ENXIO;
pdata = dev_get_platdata(&pdev->dev);
sda_pin = pdata->sda_pin;
scl_pin = pdata->scl_pin;
}
/* 请求 SDA 引脚 */
ret = devm_gpio_request(&pdev->dev, sda_pin, "sda");
if (ret) {
if (ret == -EINVAL)
ret = -EPROBE_DEFER; /* 如果引脚不可用,延迟探测 */
return ret;
}
/* 请求 SCL 引脚 */
ret = devm_gpio_request(&pdev->dev, scl_pin, "scl");
if (ret) {
if (ret == -EINVAL)
ret = -EPROBE_DEFER; /* 如果引脚不可用,延迟探测 */
return ret;
}
/* 分配并初始化私有数据结构 */
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
adap = &priv->adap;
bit_data = &priv->bit_data;
pdata = &priv->pdata;
/* 设置平台数据或从设备树中获取 */
if (pdev->dev.of_node) {
pdata->sda_pin = sda_pin;
pdata->scl_pin = scl_pin;
of_i2c_gpio_get_props(pdev->dev.of_node, pdata); ----------(1.1)
} else {
memcpy(pdata, dev_get_platdata(&pdev->dev), sizeof(*pdata));
}
/* 配置 SDA 引脚为开漏输出或输入模式 */
if (pdata->sda_is_open_drain) {
gpio_direction_output(pdata->sda_pin, 1); // 设置 SDA 为输出模式
bit_data->setsda = i2c_gpio_setsda_val; // 设置 SDA 引脚值
} else {
gpio_direction_input(pdata->sda_pin); // 设置 SDA 为输入模式
bit_data->setsda = i2c_gpio_setsda_dir; // 切换 SDA 引脚方向
}
/* 配置 SCL 引脚为开漏输出或仅输出模式 */
if (pdata->scl_is_open_drain || pdata->scl_is_output_only) {
gpio_direction_output(pdata->scl_pin, 1); // 设置 SCL 为输出模式
bit_data->setscl = i2c_gpio_setscl_val; // 设置 SCL 引脚值
} else {
gpio_direction_input(pdata->scl_pin); // 设置 SCL 为输入模式
bit_data->setscl = i2c_gpio_setscl_dir; // 切换 SCL 引脚方向
}
/* 如果 SCL 不是仅输出模式,设置读取 SCL 引脚的函数 */
if (!pdata->scl_is_output_only)
bit_data->getscl = i2c_gpio_getscl;
bit_data->getsda = i2c_gpio_getsda; // 设置读取 SDA 引脚的函数
/* 设置时钟延迟 (udelay),根据是否支持时钟伸展功能 */
if (pdata->udelay)
bit_data->udelay = pdata->udelay;
else if (pdata->scl_is_output_only)
bit_data->udelay = 50; /* 10 kHz */
else
bit_data->udelay = 5; /* 100 kHz */
/* 设置超时值,如果未定义则设置为 100 ms */
if (pdata->timeout)
bit_data->timeout = pdata->timeout;
else
bit_data->timeout = HZ / 10; /* 100 ms */
bit_data->data = pdata;
/* 设置 I2C 适配器的一些基本信息 */
adap->owner = THIS_MODULE;
if (pdev->dev.of_node)
strlcpy(adap->name, dev_name(&pdev->dev), sizeof(adap->name));
else
snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id);
/* 将算法数据和适配器绑定 */
adap->algo_data = bit_data;
adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; // 支持的设备类别
adap->dev.parent = &pdev->dev;
adap->dev.of_node = pdev->dev.of_node;
/* 将适配器注册到 I2C 核心 */
adap->nr = pdev->id;
ret = i2c_bit_add_numbered_bus(adap); -------(1.2)
if (ret)
return ret;
platform_set_drvdata(pdev, priv);
/* 打印使用的引脚信息 */
dev_info(&pdev->dev, "using pins %u (SDA) and %u (SCL%s)\n",
pdata->sda_pin, pdata->scl_pin,
pdata->scl_is_output_only ? ", no clock stretching" : "");
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
(1.1)
of_i2c_gpio_get_props(pdev->dev.of_node, pdata);
从设备树中读取 I2C GPIO 相关的属性,并将这些属性填充到 i2c_gpio_platform_data
结构体中:
Linux-4.9.88\drivers\i2c\busses\i2c-gpio.c:
static void of_i2c_gpio_get_props(struct device_node *np,
struct i2c_gpio_platform_data *pdata)
{
u32 reg;
/* 从设备树中读取 "i2c-gpio,delay-us" 属性,设置 I2C 信号切换的延迟(微秒) */
of_property_read_u32(np, "i2c-gpio,delay-us", &pdata->udelay);
/* 从设备树中读取 "i2c-gpio,timeout-ms" 属性,设置超时时间(毫秒),并将其转换为 jiffies */
if (!of_property_read_u32(np, "i2c-gpio,timeout-ms", ®))
pdata->timeout = msecs_to_jiffies(reg);
/* 判断设备树中是否有 "i2c-gpio,sda-open-drain" 属性,设置 SDA 引脚是否为开漏模式 */
pdata->sda_is_open_drain =
of_property_read_bool(np, "i2c-gpio,sda-open-drain");
/* 判断设备树中是否有 "i2c-gpio,scl-open-drain" 属性,设置 SCL 引脚是否为开漏模式 */
pdata->scl_is_open_drain =
of_property_read_bool(np, "i2c-gpio,scl-open-drain");
/* 判断设备树中是否有 "i2c-gpio,scl-output-only" 属性,设置 SCL 引脚是否为仅输出模式 */
pdata->scl_is_output_only =
of_property_read_bool(np, "i2c-gpio,scl-output-only");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
(1.2)
ret = i2c_bit_add_numbered_bus(adap);
用于在运行时注册 I2C bit-banging 算法。该函数主要负责将 I2C 适配器与 bit-banging 算法相关联,并执行必要的检查和初始化。
\Linux-4.9.88\drivers\i2c\algos\i2c-algo-bit.c
int i2c_bit_add_numbered_bus(struct i2c_adapter *adap)
{
return __i2c_bit_add_bus(adap, i2c_add_numbered_adapter); //继续往下看
}
/*
* 函数 __i2c_bit_add_bus
* 该函数的作用是注册 I2C bit-banging 算法到指定的 I2C 适配器,并执行相关的初始化。
*
* 参数:
* - adap: 指向 I2C 适配器的指针。
* - add_adapter: 用于将适配器注册到系统的回调函数。
*/
static int __i2c_bit_add_bus(struct i2c_adapter *adap,
int (*add_adapter)(struct i2c_adapter *))
{
/* 获取与适配器关联的 bit-banging 数据 */
struct i2c_algo_bit_data *bit_adap = adap->algo_data;
int ret;
/* 如果设置了 bit_test 标志,则进行总线测试 */
if (bit_test) {
ret = test_bus(adap); // 测试 I2C 总线
/* 如果 bit_test 标志值大于等于 2 且测试失败,返回 -ENODEV 表示设备不存在 */
if (bit_test >= 2 && ret < 0)
return -ENODEV;
}
/* 注册新的适配器到 I2C 模块 */
adap->algo = &i2c_bit_algo; --------(2.1)
// 设置适配器的算法为 i2c_bit_algo
adap->retries = 3; // 设置重试次数为 3 次
/* 如果没有设置 getscl 函数,表示不支持读取 SCL,引脚可能仅支持输出模式 */
if (bit_adap->getscl == NULL)
adap->quirks = &i2c_bit_quirk_no_clk_stretch; // 记录适配器的 quirks,表示不支持时钟伸展
/* 调用传入的回调函数将适配器注册到系统中 */
ret = add_adapter(adap);
if (ret < 0)
return ret; // 如果注册失败,则返回错误代码
/* 检查是否能够读取 SCL */
if (bit_adap->getscl == NULL) {
/* 如果不能读取 SCL,引发警告,表示该 I2C 总线可能不完全符合 I2C 规范 */
dev_warn(&adap->dev, "Not I2C compliant: can't read SCL\n");
dev_warn(&adap->dev, "Bus may be unreliable\n"); // 总线可能不稳定
}
return 0; // 返回 0 表示成功
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
(2.1)adap->algo = &i2c_bit_algo;
将适配器的 algo
设置为 i2c_bit_algo
,这是 bit-banging I2C 的算法。 来看看i2c_bit_algo定义的内容:
const struct i2c_algorithm i2c_bit_algo = {
.master_xfer = bit_xfer, // 这个是用于执行传输的函数,进入看
.functionality = bit_func,
};
/*
* 函数 bit_xfer
* 该函数通过 I2C bit-banging 机制传输 I2C 消息。
*
* 参数:
* - i2c_adap: 指向 I2C 适配器的指针。
* - msgs[]: 要传输的 I2C 消息数组。
* - num: 消息数组中的消息数量。
*
* 返回值:
* - 成功返回消息数量,失败返回负值表示错误。
*/
static int bit_xfer(struct i2c_adapter *i2c_adap,
struct i2c_msg msgs[], int num)
{
struct i2c_msg *pmsg; // 当前消息的指针
struct i2c_algo_bit_data *adap = i2c_adap->algo_data; // 获取适配器的 bit-banging 算法数据
int i, ret; // 循环计数器和返回值
unsigned short nak_ok; // 是否允许 NAK 信号(非应答信号)
/* 在传输之前,执行可选的 pre_xfer 函数 */
if (adap->pre_xfer) {
ret = adap->pre_xfer(i2c_adap);
if (ret < 0)
return ret; // 如果 pre_xfer 失败,则返回错误
}
/* 发送开始条件 */
bit_dbg(3, &i2c_adap->dev, "emitting start condition\n");
i2c_start(adap); // 发送 I2C 开始信号
for (i = 0; i < num; i++) {
pmsg = &msgs[i]; // 获取当前消息
nak_ok = pmsg->flags & I2C_M_IGNORE_NAK; // 判断是否允许忽略 NAK(非应答)
/* 如果没有设置 I2C_M_NOSTART 标志,则发送(重复)启动条件 */
if (!(pmsg->flags & I2C_M_NOSTART)) {
if (i) {
/* 如果不是第一条消息,发送重复启动信号 */
bit_dbg(3, &i2c_adap->dev, "emitting repeated start condition\n");
i2c_repstart(adap);
}
/* 发送设备地址并进行检查 */
ret = bit_doAddress(i2c_adap, pmsg);
if ((ret != 0) && !nak_ok) {
/* 如果设备地址返回 NAK 且不允许忽略,则退出 */
bit_dbg(1, &i2c_adap->dev, "NAK from device addr 0x%02x msg #%d\n",
msgs[i].addr, i);
goto bailout;
}
}
/* 根据消息类型(读/写)处理数据 */
if (pmsg->flags & I2C_M_RD) {
/* 读取数据到缓冲区 */
ret = readbytes(i2c_adap, pmsg);
if (ret >= 1)
bit_dbg(2, &i2c_adap->dev, "read %d byte%s\n", ret, ret == 1 ? "" : "s");
if (ret < pmsg->len) {
/* 如果读取的字节数小于期望值,返回错误 */
if (ret >= 0)
ret = -EIO;
goto bailout;
}
} else {
/* 将缓冲区的数据写入设备 */
ret = sendbytes(i2c_adap, pmsg); ------(2.2)
if (ret >= 1)
bit_dbg(2, &i2c_adap->dev, "wrote %d byte%s\n", ret, ret == 1 ? "" : "s");
if (ret < pmsg->len) {
/* 如果写入的字节数小于期望值,返回错误 */
if (ret >= 0)
ret = -EIO;
goto bailout;
}
}
}
/* 所有消息成功传输,返回消息数量 */
ret = i;
bailout:
/* 发送停止条件 */
bit_dbg(3, &i2c_adap->dev, "emitting stop condition\n");
i2c_stop(adap);
/* 在传输之后,执行可选的 post_xfer 函数 */
if (adap->post_xfer)
adap->post_xfer(i2c_adap);
return ret; // 返回结果
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
(2.2)上方中就实现了模拟IIC传输,怎么模拟的??以写为例进入看看,函数为ret = sendbytes(i2c_adap, pmsg);
:
/*
* 函数 sendbytes
* 该函数将消息缓冲区中的字节数据发送到 I2C 设备。
*
* 参数:
* - i2c_adap: 指向 I2C 适配器的指针。
* - msg: 指向要发送的 I2C 消息的指针,包含数据缓冲区和相关标志。
*
* 返回值:
* - 成功时返回写入的字节数,失败时返回负值表示错误。
*/
static int sendbytes(struct i2c_adapter *i2c_adap, struct i2c_msg *msg)
{
const unsigned char *temp = msg->buf; // 指向消息缓冲区的指针
int count = msg->len; // 要发送的字节数
unsigned short nak_ok = msg->flags & I2C_M_IGNORE_NAK; // 判断是否可以忽略 NAK
int retval; // 用于存储每次发送的结果
int wrcount = 0; // 成功发送的字节数
/* 循环发送每一个字节 */
while (count > 0) {
/* 通过 I2C 发送当前字节 */
retval = i2c_outb(i2c_adap, *temp); -------下面介绍
/* 如果发送成功(返回 ACK)或允许忽略 NAK(返回 0),继续 */
if ((retval > 0) || (nak_ok && (retval == 0))) {
count--; // 减少剩余要发送的字节数
temp++; // 指向下一个要发送的字节
wrcount++; // 记录成功发送的字节数
/* 如果设备返回 NAK 且不允许忽略 NAK,则退出 */
} else if (retval == 0) {
dev_err(&i2c_adap->dev, "sendbytes: NAK bailout.\n");
return -EIO; // 返回 I/O 错误
/* 超时或其他错误(如总线仲裁丢失) */
} else {
dev_err(&i2c_adap->dev, "sendbytes: error %d\n", retval);
return retval; // 返回错误码
}
}
/* 返回成功写入的字节数 */
return wrcount;
}
/*
* 函数 i2c_outb
* 该函数将一个字节的 I2C 数据发送到总线,并等待从设备的应答。
*
* 参数:
* - i2c_adap: 指向 I2C 适配器的指针。
* - c: 要发送的字节数据。
*
* 返回值:
* - 如果接收到 ACK,返回 1;如果没有接收到 ACK,返回 0;如果超时,返回 -ETIMEDOUT。
*/
static int i2c_outb(struct i2c_adapter *i2c_adap, unsigned char c)
{
int i; // 用于遍历字节中的各个位
int sb; // 当前位
int ack; // 用于存储从设备的应答
struct i2c_algo_bit_data *adap = i2c_adap->algo_data; // 获取 I2C 的算法位操作数据
/* assert: scl is low */
// 逐位发送字节(从高位到低位)
for (i = 7; i >= 0; i--) {
sb = (c >> i) & 1; // 取出当前的位 (从高位开始)
setsda(adap, sb); // 设置 SDA 线的电平 (发送数据位)
udelay((adap->udelay + 1) / 2); // 等待半个延迟周期
/* 提高时钟线,准备传输数据位 */
if (sclhi(adap) < 0) { // 检查 SCL 是否成功拉高 (超时检查)
bit_dbg(1, &i2c_adap->dev, "i2c_outb: 0x%02x, "
"timeout at bit #%d\n", (int)c, i);
return -ETIMEDOUT; // 如果超时则返回错误
}
// FIXME: 在这里进行仲裁检测(如果主设备发送了高电平但 SDA 被拉低,说明有其他设备仲裁总线)
// if (sb && !getsda(adap)) -> 仲裁失败,退出传输
scllo(adap); // 将时钟线拉低,准备发送下一位
}
/* 发送完所有数据位后,将 SDA 拉高,准备接收 ACK */
sdahi(adap);
if (sclhi(adap) < 0) { // 提高时钟线,准备读取 ACK 信号 (超时检查)
bit_dbg(1, &i2c_adap->dev, "i2c_outb: 0x%02x, "
"timeout at ack\n", (int)c);
return -ETIMEDOUT; // 超时则返回错误
}
/* 读取 ACK 信号: 如果从设备拉低 SDA 表示成功 (ACK),否则表示失败 (NAK) */
ack = !getsda(adap); // 如果 SDA 被拉低表示接收到 ACK (成功)
bit_dbg(2, &i2c_adap->dev, "i2c_outb: 0x%02x %s\n", (int)c,
ack ? "A" : "NA"); // 打印调试信息 (ACK 或 NAK)
scllo(adap); // 结束数据传输,将时钟线拉低
return ack; // 返回 ACK 的结果 (1 表示成功, 0 表示失败)
/* assert: scl is low (sda undef) */
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
建议还是看着时序图去进行理解该段代码:
3.4 传输函数分析
drivers\i2c\algos\i2c-algo-bit.c?i2c-algo-bit.c
/*
* 函数 i2c_outb
* 该函数将一个字节的 I2C 数据发送到总线,并等待从设备的应答。
*
* 参数:
* - i2c_adap: 指向 I2C 适配器的指针。
* - c: 要发送的字节数据。
*
* 返回值:
* - 如果接收到 ACK,返回 1;如果没有接收到 ACK,返回 0;如果超时,返回 -ETIMEDOUT。
*/
static int i2c_outb(struct i2c_adapter *i2c_adap, unsigned char c)
{
int i; // 用于遍历字节中的各个位
int sb; // 当前位
int ack; // 用于存储从设备的应答
struct i2c_algo_bit_data *adap = i2c_adap->algo_data; // 获取 I2C 的算法位操作数据
/* assert: scl is low */
// 逐位发送字节(从高位到低位)
for (i = 7; i >= 0; i--) {
sb = (c >> i) & 1; // 取出当前的位 (从高位开始)
setsda(adap, sb); // 设置 SDA 线的电平 (发送数据位)
udelay((adap->udelay + 1) / 2); // 等待半个延迟周期
/* 提高时钟线,准备传输数据位 */
if (sclhi(adap) < 0) { // 检查 SCL 是否成功拉高 (超时检查)
bit_dbg(1, &i2c_adap->dev, "i2c_outb: 0x%02x, "
"timeout at bit #%d\n", (int)c, i);
return -ETIMEDOUT; // 如果超时则返回错误
}
// FIXME: 在这里进行仲裁检测(如果主设备发送了高电平但 SDA 被拉低,说明有其他设备仲裁总线)
// if (sb && !getsda(adap)) -> 仲裁失败,退出传输
scllo(adap); // 将时钟线拉低,准备发送下一位
}
/* 发送完所有数据位后,将 SDA 拉高,准备接收 ACK */
sdahi(adap);
if (sclhi(adap) < 0) { // 提高时钟线,准备读取 ACK 信号 (超时检查)
bit_dbg(1, &i2c_adap->dev, "i2c_outb: 0x%02x, "
"timeout at ack\n", (int)c);
return -ETIMEDOUT; // 超时则返回错误
}
/* 读取 ACK 信号: 如果从设备拉低 SDA 表示成功 (ACK),否则表示失败 (NAK) */
ack = !getsda(adap); // 如果 SDA 被拉低表示接收到 ACK (成功)
bit_dbg(2, &i2c_adap->dev, "i2c_outb: 0x%02x %s\n", (int)c,
ack ? "A" : "NA"); // 打印调试信息 (ACK 或 NAK)
scllo(adap); // 结束数据传输,将时钟线拉低
return ack; // 返回 ACK 的结果 (1 表示成功, 0 表示失败)
/* assert: scl is low (sda undef) */
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
在上面已经介绍过啦,这里就额外提出来让大家看的清楚一点。
4. 怎么使用I2C-GPIO
使用 I2C-GPIO 驱动时,可以通过在设备树中添加相应的节点来配置 I2C 总线。以下是一个示例代码以及如何设置 I2C 总线所需的各个属性:
&i2c_gpio {
compatible = "i2c-gpio";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c_gpio>; /* 配置GPIO引脚为SDA和SCL */
/* GPIO配置 */
gpios = <&gpio0 3 0>, /* SDA 引脚,GPIO 0_3 */
<&gpio0 4 0>; /* SCL 引脚,GPIO 0_4 */
i2c-gpio,sda-open-drain; /* SDA 已经被配置为开漏模式 */
i2c-gpio,scl-open-drain; /* SCL 已经被配置为开漏模式 */
/* 指定时钟频率:两种方式任选其一 */
i2c-gpio,delay-us = <5>; /* 设置延迟,100kHz */
clock-frequency = <400000>; /* 设置 I2C 频率为 400kHz */
#address-cells = <1>; /* 指定地址单元大小 */
#size-cells = <0>; /* 指定大小单元大小 */
/* 在此处可以添加I2C设备子节点 */
eeprom@50 {
compatible = "at24,24c04";
reg = <0x50>; /* I2C 设备地址 */
};
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
-
compatible 属性:
"i2c-gpio"
:定义此节点为 I2C-GPIO 驱动程序实例。
-
引脚控制 (pinctrl):
pinctrl-0
: 配置 I2C 使用的 GPIO 引脚,将其设置为开漏模式,可以通过pinctrl
设置。gpios
: 定义 SDA 和 SCL 的 GPIO 引脚编号。<&gpio0 3 0>
: 指定 SDA 引脚,表示 GPIO 0 的第 3 号引脚。<&gpio0 4 0>
: 指定 SCL 引脚,表示 GPIO 0 的第 4 号引脚。
-
时钟频率:
- 可以通过两种方式指定频率:
i2c-gpio,delay-us = <5>;
:设置延迟时间为 5 微秒,频率大约为 100 kHz。clock-frequency = <400000>;
:直接设置时钟频率为 400 kHz。
- 可以通过两种方式指定频率:
-
Open-drain 配置:
i2c-gpio,sda-open-drain
: 该属性表示 SDA 已被其他系统或驱动配置为开漏模式,驱动程序不需要再进行配置。i2c-gpio,scl-open-drain
: 类似的,表示 SCL 引脚已经被配置为开漏模式。
-
#address-cells 和 #size-cells:
- 这些属性指定了 I2C 设备的地址和大小单元,通常分别设置为
1
和0
。
- 这些属性指定了 I2C 设备的地址和大小单元,通常分别设置为
-
子设备节点:
- 可以在 I2C-GPIO 节点下添加 I2C 设备,如
eeprom@50
,并为其指定compatible
属性和 I2C 地址 (reg
属性)。
- 可以在 I2C-GPIO 节点下添加 I2C 设备,如
关键步骤:
- SDA 和 SCL 使用哪一对 GPIO 引脚。
- 是否已经配置为开漏模式,影响驱动是否需要重新配置。
- 通过
delay-us
或clock-frequency
设置 I2C 时钟频率。 - 在节点中定义所连接的 I2C 从设备。
通过以上设备树配置,系统就能通过 GPIO 引脚来模拟 I2C 总线,从而与 I2C 设备进行通信。
评论记录:
回复评论: