首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐
2025年7月3日 星期四 6:05am

使用GPIO模拟I2C的驱动程序分析:i2c-gpio.c

  • 25-03-04 14:02
  • 4086
  • 6321
blog.csdn.net

往期内容

I2C子系统专栏:

  1. I2C(IIC)协议讲解-CSDN博客
  2. SMBus 协议详解-CSDN博客
  3. I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客
  4. 内核提供的通用I2C设备驱动I2c-dev.c分析:注册篇
  5. 内核提供的通用I2C设备驱动I2C-dev.c分析:file_ops篇
  6. 设备驱动与设备树匹配机制详解
  7. 编写一个通用的i2c设备驱动框架
  8. 编写一个通用的i2c控制器驱动框架
  9. consumer 角度讲一下i2c外设-CSDN博客

总线和设备树专栏:

  1. 总线和设备树_憧憬一下的博客-CSDN博客
  2. 设备树与 Linux 内核设备驱动模型的整合-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协议

内容在之前的章节都有讲过,具体内容跳转:

I2C(IIC)协议讲解-CSDN博客

2. 使用GPIO模拟I2C的要点

  • 引脚设为GPIO
  • GPIO设为输出、开极/开漏(open collector/open drain)
  • 要有上拉电阻

3. 驱动程序分析

3.1 平台总线设备驱动模型

img

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驱动层次

img

img

  • 分析的文件为内核提供的用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", &reg))
		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

建议还是看着时序图去进行理解该段代码:
img

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
  1. compatible 属性:

    • "i2c-gpio":定义此节点为 I2C-GPIO 驱动程序实例。
  2. 引脚控制 (pinctrl):

    • pinctrl-0: 配置 I2C 使用的 GPIO 引脚,将其设置为开漏模式,可以通过 pinctrl 设置。
    • gpios: 定义 SDA 和 SCL 的 GPIO 引脚编号。
      • <&gpio0 3 0>: 指定 SDA 引脚,表示 GPIO 0 的第 3 号引脚。
      • <&gpio0 4 0>: 指定 SCL 引脚,表示 GPIO 0 的第 4 号引脚。
  3. 时钟频率:

    • 可以通过两种方式指定频率:
      • i2c-gpio,delay-us = <5>;:设置延迟时间为 5 微秒,频率大约为 100 kHz。
      • clock-frequency = <400000>;:直接设置时钟频率为 400 kHz。
  4. Open-drain 配置:

    • i2c-gpio,sda-open-drain: 该属性表示 SDA 已被其他系统或驱动配置为开漏模式,驱动程序不需要再进行配置。
    • i2c-gpio,scl-open-drain: 类似的,表示 SCL 引脚已经被配置为开漏模式。
  5. #address-cells 和 #size-cells:

    • 这些属性指定了 I2C 设备的地址和大小单元,通常分别设置为 1 和 0。
  6. 子设备节点:

    • 可以在 I2C-GPIO 节点下添加 I2C 设备,如 eeprom@50,并为其指定 compatible 属性和 I2C 地址 (reg 属性)。

关键步骤:

  • SDA 和 SCL 使用哪一对 GPIO 引脚。
  • 是否已经配置为开漏模式,影响驱动是否需要重新配置。
  • 通过 delay-us 或 clock-frequency 设置 I2C 时钟频率。
  • 在节点中定义所连接的 I2C 从设备。

通过以上设备树配置,系统就能通过 GPIO 引脚来模拟 I2C 总线,从而与 I2C 设备进行通信。

文章知识点与官方知识档案匹配,可进一步学习相关知识
C技能树首页概览220410 人正在系统学习中
注:本文转载自blog.csdn.net的憧憬一下的文章"https://blog.csdn.net/caiji0169/article/details/143135962"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top