嵌入式驱动学习第七周——I2C子系统

news2025/6/8 18:48:00

前言

   I2C子系统详解,本篇博客从内核源码的角度来看I2C子系统。

   嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!

目录

  • 前言
  • I2C介绍
  • I2C通信协议
    • 起始信号和停止信号
    • 应答信号
    • 数据传输
    • 主机与从机通信
  • I2C驱动框架
    • 关键数据结构
    • I2C总线驱动
    • i2c设备驱动核心函数
    • 总结
  • 参考资料

I2C介绍

   I2C支持一主多从,各设备地址独立,标准模式传输速率为100kbit/s,快速模式为400kbit/s。总线通过上拉电阻连接到电源,当I2C设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把电线拉成高电平。

在这里插入图片描述

   I2C物理总线使用两条总线线路,SCL(时钟线,数据收发同步)和SDA(数据线,传输具体数据)。

I2C通信协议

起始信号和停止信号

   在 SCL 为高电平的时候,SDA 出现下降沿就表示为起始位。

在这里插入图片描述

   和起始位的功能相反。在 SCL 位高电平的时候,SDA出现上升沿就表示为停止位。

在这里插入图片描述

应答信号

   I2C主机发送完8位数据后会将SDA设置为输入状态,等待I2C从机应答,即等到I2C从机告诉主机它接收到数据了。应答信号是由从机发出的,主机需要提供应答信号所需的时钟,主机发送完 8 位数据以后紧跟着的一个时钟信号就是给应答信号使用的。从机通过将 SDA 拉低来表示发出应答信号,表示通信成功,否则表示通信失败。

数据传输

   I2C的数据字节定义为8-bits长度,对每次传送的总字节数量没有限制,但对每一次传输必须伴有应答信号。I2C 总线在数据传输的时候要保证在 SCL 高电平期间,SDA 上的数据稳定,因此 SDA 上的数据变化只能在 SCL 低电平期间发生。

在这里插入图片描述

主机与从机通信

  • 开始标志(S)发出后,主设备会传送一个7位的Slave地址,并且后面跟着一个第8位,称为Read/Write位。R/W位表示主设备是在接受从设备的数据还是在向其写数据。
  • 然后,主设备释放SDA线,等待从设备的应答信号(ACK)每个字节的传输都要跟随有一个应答位。应答产生时,从设备将SDA线拉低并且在SCL为高电平时保持低。
  • 数据传输总是以停止标志(P)结束,然后释放通信线路。 然而,主设备也可以产生重复的开始信号去操作另一台从设备,而不发出结束标志。综上可知,所有的SDA信号变化都要在SCL时钟为低电平时进行,除了开始和结束标志

在这里插入图片描述

   先介绍几个缩写:

简写含义
S起始信号
P结束信号
AD地址数据
W写标志
R读标志
ACK应答信号
NACK拒绝应答信号
RA要写入或读取的寄存器地址
DATA传输的数据

   单字节写数据:

在这里插入图片描述

   多字节写数据:
在这里插入图片描述

   举个通俗的例子来理解这个过程,现在老师和学生要打电话取得沟通

老师开始通信——主机发送S,表示开始
老师找到学生的微信,并点击音频通话按钮——主机发送AD+W
学生接通电话——从机发送ACK
老师问:是xxx嘛——主机发送RA
学生说:是我——从机发送ACK
然后老师开始说:两件事情,第一件事xxxx——主机发送DATA
学生说:好的老师——从机发送ACK
然后老师说:第二件事xxx——主机发送DATA
学生说:好的老师——从机发送ACK
老师挂断电话——主机发送停止信号P

   单字节读数据(第一个是W是因为后面需要向设备写入我要读取哪个寄存器):

在这里插入图片描述

   多字节读数据:

在这里插入图片描述

   这个再举老师的例子不合适了,因为老师作为主机举出来的例子很奇怪,但是也不能把学生当做主机,因为在I2C中,只有主机能发S信号,如果把学生作为主机,就会产生一种错觉,就是设备与主机之间能相互做主机,显然是很不对的。因此换一个里子,现在王先生去参加一个学术会议,会议的会务要找他获取一些报名信息

会务想要找王先生沟通——主机发送S信号
会务找到王先生的电话并拨打过去——主机发送AD+W信号
王先生接到电话——从机发送ACK
会务说:您好,我们这边需要向您获取一些报名信息——主机发送RA,表示要读取信号的位置
王先生说:好的——从机发送ACK
会务说:那我们开始了哈——主机发送开始信号
会务说:再次确认,您是王xx先生吗,是的话请您告诉我们所在单位,发票抬头——主机发送地址、读取标志
王先生:好的——从机发送ACK
王先生:我的单位是xxx——从机发送data
会务说:好的——主机发送ACK
王先生:发票抬头是xxx——从机发送data
会务说:好的,我们这边登记完成——主机发送NACK,表示数据读取完成
会务挂断电话——主机发送P

I2C驱动框架

   在裸机开发I2C驱动时,需要根据I2C协议手动配置I2C控制寄存器能够输出起始信号、停止信号、数据信息等。但是Linux中采用的是总线——设备——驱动模型。基于此想法,我们需要两个驱动,一个是I2C驱动,一个是设备驱动,比如用I2C读取mpu6050数据的时候,就需要一个mpu6050驱动。

在这里插入图片描述

   i2c驱动框架包括i2c总线驱动、具体某个设备的驱动。

   I2C总线又包括I2C设备和I2C驱动,当向Linux中注册设备或驱动的时候,按照I2C总线匹配规则进行匹配,配对成功的话,就可以通过I2C_driver中的probe函数创建具体的设备驱动。I2C设备不需要手动创建,而是利用设备树,设备树节点与platform总线相配合使用,所以需要先对I2C总线包装一层platform总线,当设备树节点转换为平台总线设备时,再进一步将其转换为I2C设备,注册到I2C总线中。

关键数据结构

i2c_adapter

   这是一个I2C控制器,用于标识物理I2C总线以及访问它所需的访问算法的结构,其定义在 include/linux/i2c.h文件内

struct i2c_adapter {
	struct module *owner;
	unsigned int class;		  /* classes to allow probing for */
	const struct i2c_algorithm *algo; /* the algorithm to access the bus */
	void *algo_data;

	/* data fields that are valid for all devices	*/
	struct rt_mutex bus_lock;

	int timeout;			/* in jiffies */
	int retries;
	struct device dev;		/* the adapter device */

	int nr;
	char name[48];
	struct completion dev_released;

	struct mutex userspace_clients_lock;
	struct list_head userspace_clients;

	struct i2c_bus_recovery_info *bus_recovery_info;
	const struct i2c_adapter_quirks *quirks;
};

   这里面有两个很重要:

  • const struct i2c_algorithm *algo:这是一个i2c_algorithm结构体,访问总线的算法;
  • struct device dev:表明这是一个设备

struct i2c_algorithm

   这是一个硬件解决方案的接口,这些解决方案可以使用相同的总线算法(碰撞检测等)来解决。下面来看看这个结构体的定义,路径在:include/linux/i2c.h

struct i2c_algorithm {
	/* If an adapter algorithm can't do I2C-level access, set master_xfer
	   to NULL. If an adapter algorithm can do SMBus access, set
	   smbus_xfer. If set to NULL, the SMBus protocol is simulated
	   using common I2C messages */
	/* master_xfer should return the number of messages successfully
	   processed, or a negative value on error */
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
			   unsigned short flags, char read_write,
			   u8 command, int size, union i2c_smbus_data *data);

	/* To determine what the adapter supports */
	u32 (*functionality) (struct i2c_adapter *);

#if IS_ENABLED(CONFIG_I2C_SLAVE)
	int (*reg_slave)(struct i2c_client *client);
	int (*unreg_slave)(struct i2c_client *client);
#endif
};
  • master_xfer:该函数表示作为主设备时,应该返回成功处理的消息数,或者在出错时,返回负数值。
  • smbus_xfer:该函数表示作为从设备时的发送函数

struct i2c_client

   该结构体表示I2C从设备,即MPU6050等,其同样定义在include/linux/i2c.h 中:

struct i2c_client {
	unsigned short flags;		/* div., see below		*/
	unsigned short addr;		/* chip address - NOTE: 7bit	*/
					/* addresses are stored in the	*/
					/* _LOWER_ 7 bits		*/
	char name[I2C_NAME_SIZE];
	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
	struct device dev;		/* the device structure		*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	i2c_slave_cb_t slave_cb;	/* callback for slave mode	*/
#endif
};
  • flags:I2C_CLIENT_TEN表示设备使用10位芯片地址,I2C_CLIENT_PEC表示它使用SMBus数据包错误检查;
  • addr:连接到I2C总线上的地址
  • name:表示设备的类型,通常是芯片名
  • adapter:struct i2c_adapter 结构体,管理托管这个I2C设备的总线段
  • dev:Driver model设备节点
  • init_irq:作为从设备时的发送函数
  • irq:表示该设备生成的中断号
  • detected:一个列表,因为I2C是多从机,这些从机的管理可以用一个链表来管理
  • slave_cb:使用适配器的I2C从模式时回调。适配器调用它来将从属事件传递给从属驱动程序。i2c_client识别连接到i2c总线的单个设备(即芯片)。

struct i2c_driver

   I2C设备驱动,其在内核中的定义同样在include/linux/i2c.h 中:

struct i2c_driver {
	unsigned int class;

	/* Notifies the driver that a new bus has appeared. You should avoid
	 * using this, it will be removed in a near future.
	 */
	int (*attach_adapter)(struct i2c_adapter *) __deprecated;

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);

	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *);

	/* Alert callback, for example for the SMBus alert protocol.
	 * The format and meaning of the data value depends on the protocol.
	 * For the SMBus alert protocol, there is a single bit of data passed
	 * as the alert response's low bit ("event flag").
	 */
	void (*alert)(struct i2c_client *, unsigned int data);

	/* a ioctl like command that can be used to perform specific functions
	 * with the device.
	 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;
	const struct i2c_device_id *id_table;

	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;
};

   来看以下几个成员变量:

  • probe:i2c设备和i2c驱动匹配后,回调该函数指针
  • id_table:struct i2c_device_id *类型,是要匹配的从设备信息
  • address_list:设备地址
  • clients:设备链表
  • detect:设备探测函数

I2C总线驱动

   现在来看看I2C总线的运行机制:

  1. 注册I2C总线
  2. 将I2C驱动添加到I2C总线的驱动链表中
  3. 遍历I2C总线上的链表,根据i2c_device_match函数进行匹配,如果匹配调用i2c_device_probe函数
  4. i2c_device_probe函数会调用I2C驱动的probe函数

   I2C总线定义在drivers/i2c/i2c-core.c中:

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
};

   i2c总线维护着两个链表(I2C驱动、I2C设备),管理I2C设备和I2C驱动的匹配和删除等

i2c总线注册

   linux启动之后,默认执行i2c_init,其函数定义在drivers/i2c/i2c-core.c中:

static int __init i2c_init(void)
{
	int retval;
	...
	retval = bus_register(&i2c_bus_type);				// 注册总线I2C_bus_type
	if (retval)
		return retval;
#ifdef CONFIG_I2C_COMPAT
	i2c_adapter_compat_class = class_compat_register("i2c-adapter");
	if (!i2c_adapter_compat_class) {
		retval = -ENOMEM;
		goto bus_err;
	}
#endif
	retval = i2c_add_driver(&dummy_driver);				// 注册I2C驱动dummy_driver
	if (retval)
		goto class_err;

	if (IS_ENABLED(CONFIG_OF_DYNAMIC))
		WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));

	return 0;

class_err:
#ifdef CONFIG_I2C_COMPAT
	class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
	bus_unregister(&i2c_bus_type);
	return retval;
}

i2c设备和i2c驱动匹配规则

   其定义在drivers/i2c/i2c-core.c

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;

	if (!client)
		return 0;

	/* Attempt an OF style match */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	driver = to_i2c_driver(drv);
	/* match on an id table if there is one */
	if (driver->id_table)
		return i2c_match_id(driver->id_table, client) != NULL;

	return 0;
}
  • of_driver_match_device: 设备树匹配方式,比较 I2C 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性
  • acpi_driver_match_device: ACPI 匹配方式
  • i2c_match_id: i2c总线传统匹配方式,比较 I2C设备名字和 i2c驱动的id_table->name 字段是否相等

   I2C驱动入口函数定义在drivers/i2c/busses/i2c-imx.c中:

static int __init i2c_adap_imx_init(void)
{
	return platform_driver_register(&i2c_imx_driver);
}
subsys_initcall(i2c_adap_imx_init);

static void __exit i2c_adap_imx_exit(void)
{
	platform_driver_unregister(&i2c_imx_driver);
}
module_exit(i2c_adap_imx_exit);

   入口函数和出口函数都在外面封了一层platform驱动,接下来来看这个platform驱动结构体i2c_imx_driver

static struct platform_driver i2c_imx_driver = {
	.probe = i2c_imx_probe,
	.remove = i2c_imx_remove,
	.driver	= {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
		.of_match_table = i2c_imx_dt_ids,
		.pm = IMX_I2C_PM,
	},
	.id_table	= imx_i2c_devtype,
};

   匹配列表为i2c_imx_dt_ids,用于和设备树节点匹配:

static const struct of_device_id i2c_imx_dt_ids[] = {
	{ .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, },
	{ .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },
	{ .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, },
	{ /* sentinel */ }
};

   接下来看probe函数:

static int i2c_imx_probe(struct platform_device *pdev)
{
	const struct of_device_id *of_id = of_match_device(i2c_imx_dt_ids,
							   &pdev->dev);
	struct imx_i2c_struct *i2c_imx;
	struct resource *res;
	struct imxi2c_platform_data *pdata = dev_get_platdata(&pdev->dev);
	void __iomem *base;
	int irq, ret;
	dma_addr_t phy_addr;

	dev_dbg(&pdev->dev, "<%s>\n", __func__);

	irq = platform_get_irq(pdev, 0);							// 获取中断号
	if (irq < 0) {
		dev_err(&pdev->dev, "can't get irq number\n");
		return irq;
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);		// 获取reg属性,该函数实现的功能与of函数获取reg属性相同
	base = devm_ioremap_resource(&pdev->dev, res);				// 获取I2C的基地址后,用该函数转为虚拟地址
	if (IS_ERR(base))
		return PTR_ERR(base);

	phy_addr = (dma_addr_t)res->start;
	i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx), GFP_KERNEL);	// 为i2c_imx申请内存空间
	if (!i2c_imx)
		return -ENOMEM;

	if (of_id)
		i2c_imx->hwdata = of_id->data;
	else
		i2c_imx->hwdata = (struct imx_i2c_hwdata *)
				platform_get_device_id(pdev)->driver_data;

	/* Setup i2c_imx driver structure */
	strlcpy(i2c_imx->adapter.name, pdev->name, sizeof(i2c_imx->adapter.name));			// 这部分是初始化i2c_imx结构体,在程序中代表一个实际的i2c总线。
	i2c_imx->adapter.owner		= THIS_MODULE;
	i2c_imx->adapter.algo		= &i2c_imx_algo;			// 特别需要关心的是该成员,其代表了访问总线的算法
	i2c_imx->adapter.dev.parent	= &pdev->dev;
	i2c_imx->adapter.nr		= pdev->id;
	i2c_imx->adapter.dev.of_node	= pdev->dev.of_node;
	i2c_imx->base			= base;

	/* Get I2C clock */
	i2c_imx->clk = devm_clk_get(&pdev->dev, NULL);
	if (IS_ERR(i2c_imx->clk)) {
		dev_err(&pdev->dev, "can't get I2C clock\n");
		return PTR_ERR(i2c_imx->clk);
	}

	ret = clk_prepare_enable(i2c_imx->clk);
	if (ret) {
		dev_err(&pdev->dev, "can't enable I2C clock\n");
		return ret;
	}
	/* Request IRQ */
	ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr,
			       IRQF_NO_SUSPEND, pdev->name, i2c_imx);
	if (ret) {
		dev_err(&pdev->dev, "can't claim irq %d\n", irq);
		goto clk_disable;
	}

	/* Init queue */
	init_waitqueue_head(&i2c_imx->queue);

	/* Set up adapter data */
	i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);

	/* Set up clock divider */
	i2c_imx->bitrate = IMX_I2C_BIT_RATE;
	ret = of_property_read_u32(pdev->dev.of_node,
				   "clock-frequency", &i2c_imx->bitrate);
	if (ret < 0 && pdata && pdata->bitrate)
		i2c_imx->bitrate = pdata->bitrate;

	/* Set up chip registers to defaults */
	imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
			i2c_imx, IMX_I2C_I2CR);
	imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR);

	/* Add I2C adapter */
	ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
	if (ret < 0) {
		dev_err(&pdev->dev, "registration failed\n");
		goto clk_disable;
	}

	/* Set up platform driver data */
	platform_set_drvdata(pdev, i2c_imx);
	clk_disable_unprepare(i2c_imx->clk);

	dev_dbg(&i2c_imx->adapter.dev, "claimed irq %d\n", irq);
	dev_dbg(&i2c_imx->adapter.dev, "device resources: %pR\n", res);
	dev_dbg(&i2c_imx->adapter.dev, "adapter name: \"%s\"\n",
		i2c_imx->adapter.name);
	dev_info(&i2c_imx->adapter.dev, "IMX I2C adapter registered\n");

	/* Init DMA config if supported */
	i2c_imx_dma_request(i2c_imx, phy_addr);

	return 0;   /* Return OK */

clk_disable:
	clk_disable_unprepare(i2c_imx->clk);
	return ret;
}

   接下来看probe函数,整个.prob函数完成了两个主要工作:

  • 第一,初始化i2c1硬件
  • 第二,初始化一个“代表i2c1”的i2c_adapter结构体,并将其添加到系统中。

   .probe函数完成了i2c的基本初始化并将其添加到了系统中。i2c总线驱动的另外一个重要工作就是实现i2c对外接口函数。 我们在初始化i2c_adapter结构体时已经初始化了访问总线算法结构体i2c_adapter->i2c_algorithm。 具体代码为 i2c_imx->adapter.algo = &i2c_imx_algo

   i2c_imx是一个imx_i2c_struct类型的结构体,该结构体大多数是用于保存i2c硬件信息,其定义如下:

struct imx_i2c_struct {
	struct i2c_adapter	adapter;
	struct clk		*clk;
	void __iomem		*base;
	wait_queue_head_t	queue;
	unsigned long		i2csr;
	unsigned int		disable_delay;
	int			stopped;
	unsigned int		ifdr; /* IMX_I2C_IFDR */
	unsigned int		cur_clk;
	unsigned int		bitrate;
	const struct imx_i2c_hwdata	*hwdata;

	struct imx_i2c_dma	*dma;
};
  • clk: clk结构体保存时钟相关信息
  • bitrate: 保存i2c的波特率
  • dma: struct imx_i2c_dma 结构体 dam相关信息等等

   上面probe函数中,定义i2c_imx结构体的时候,也定义了访问i2c的算法,其定义如下:

static struct i2c_algorithm i2c_imx_algo = {
	.master_xfer	= i2c_imx_xfer,
	.functionality	= i2c_imx_func,
};

   i2c_imx_algo结构体内指定了两个函数,它们就是外部访问i2c总线的接口:

  • 函数i2c_imx_func只是用于返回当前所处状态。
  • 函数i2c_imx_xfer真正实现外部访问i2c总线。
/*
 * @description  : 实现具体的收发工作
 */
static int i2c_imx_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num)

   至此,我们知道i2c总线驱动完成了i2c的硬件初始化、将i2c总线添加到系统、并提供外界访问i2c总线的接口函数。 我们的i2c设备驱动只需要根据特定设备使用这些接口函数即可。

i2c设备驱动核心函数

#define i2c_add_driver(driver)   // 这个宏调用的下面这个函数
/*
 * @description : 注册一个i2c驱动
 * @param-owner : 一般是THIS_MODULE
 * @param-driver: 要注册的i2c_driver
 * @return      : 0表示成功,负数表示失败
 */
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
/*
 * @description : 注册一个i2c驱动
 * @param-adap  : 收发消息所使用的i2c适配器
 * @param-msgs  : struct i2c_msg 结构体,i2c要发送的一个或多个消息
 * @param-num   : 消息数量,也就是msgs的数量
 * @return      : 成功的话返回发送msgs的数量,失败的话返回负数
 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

   i2c_msg 定义在include/uapi/linux/i2c.h中:

struct i2c_msg {
	__u16 addr;	/* slave address			*/
	__u16 flags;
#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
#define I2C_M_RD		0x0001	/* read data, from slave to master */
#define I2C_M_STOP		0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
	__u16 len;		/* msg length				*/
	__u8 *buf;		/* pointer to msg data			*/
};
  • addr: iic设备地址
  • flags: 消息传输方向和特性。I2C_M_RD:表示读取消息;0:表示发送消息。
  • len: 消息数据的长度
  • buf: 字符数组存放消息,作为消息的缓冲区
// 发送一个i2c消息
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
// 接收一个i2c消息
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)

总结

在这里插入图片描述

   简单来说,i2c_client设备有一个属性是name,i2c_driver中有个id_table,然后将client中的name与table中的值进行对比匹配,匹配成功则进入probe函数。而I2C_adapter在Linux系统中起着桥梁作用,连接主机系统与I2C总线。

参考资料

[1] 【正点原子】I.MX6U嵌入式Linux驱区动开发指南 第二十六章、第六十一章
[2] I2C子系统–mpu6050驱动实验

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1592582.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

爱奇艺APP Android低端机性能优化

01 背景介绍 在智能手机市场上&#xff0c;高端机型经常备受瞩目&#xff0c;但低端机型亦占据了不可忽视的份额。众多厂商为满足低端市场的需求&#xff0c;不断推出低配系列手机。另外过去几年的中高端机型&#xff0c;随着系统硬件的快速迭代&#xff0c;现已经被归类为低端…

学习Rust的第4天:常见编程概念

欢迎来到学习Rust的第四天&#xff0c;基于Steve Klabnik的《The Rust Programming Language》一书。昨天我们做了一个 猜谜游戏 &#xff0c;今天我们将探讨常见的编程概念&#xff0c;例如&#xff1a; Variables 变量Constants 常数Shadowing 阴影Data Types 数据类型Functi…

错题记录(2)

来源&#xff1a; FPGA开发/数字IC笔试系列(10) 笔试刷题 笔试 | 海思2022数字IC模拟卷&#xff08;真题模拟&#xff0c;带解析&#xff09; 运算符优先级

【C++算法竞赛 · 图论】图的存储

前言 图的存储 邻接矩阵 方法 复杂度 应用 例题 题解 邻接表 方法 复杂度 应用 前言 上一篇文章中&#xff08;【C算法竞赛 图论】图论基础&#xff09;&#xff0c;介绍了图论相关的概念和一种图的存储的方法&#xff0c;这篇文章将会介绍剩下的两种方法&#xff…

【黑马头条】-day09用户行为-精度丢失-点赞收藏关注

文章目录 1 long类型精度丢失问题1.1 解决1.2 导入jackson序列化工具1.3 自定义注解1.4 原理1.5 测试 2 用户行为要求3 创建微服务behavior3.1 微服务创建3.2 添加启动类3.3 创建bootstrap.yml3.4 在nacos中配置redis3.5 引入redis依赖3.6 更新minio 4 跳过 1 long类型精度丢失…

视频批量高效剪辑,支持将视频文件转换为音频文件,轻松掌握视频格式

在数字化时代&#xff0c;视频内容日益丰富&#xff0c;管理和编辑这些视频变得愈发重要。然而&#xff0c;传统的视频剪辑软件往往操作复杂&#xff0c;难以满足高效批量处理的需求。现在&#xff0c;一款全新的视频批量剪辑神器应运而生&#xff0c;它支持将视频文件一键转换…

【vue】slot 匿名插槽 / 具名插槽

slot父组件向子组件传递数据 匿名插槽–直接写 具名插槽–指定名称 父组件中 子组件中&#xff1a; 代码 App.vue <template><h2>App.vue</h2><!-- 匿名插槽 --><Header><a href"1234567890.com">1234567890</a>&…

Bug的定义生命周期

1、bug的定义 你们觉得bug是什么? 软件的Bug狭义概含是指软件程序的漏洞或缺陷&#xff0c; 广义概念除此之外还包括测试工程师或用户所发现和提出的软件可改进的细节(增强性&#xff0c;建议性)、或 与需求文档存在差异的功能实现等。 我们的职责就是&#xff0c;发现这些B…

002nodejs详细安装步骤和npm配置

1、Node.js简介 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时。Node.js 使用高效、轻量级的事件驱动、非阻塞 I/O 模型。它的包生态系统&#xff0c;npm&#xff0c;是目前世界上最大的开源库生态系统。 2、下载Node.js 官方地址&#xff1a;https://nodejs.org/…

vue3 知识点的补充 之 第一节

01 vue2与vue3的区别 vue2 采用object.defuneProperty()实现 对数组不友好 重写了数组的方法&#xff0c;同时无法监听数组length长度的改变。对于对象只能劫持设置好的数据 新增需要使用vue.set vue3 采用proxy进行代理&#xff0c;不需要重写数组的方法 同时可以监听数组长度…

plsql developer 一键格式化sql/美化sql

PL/SQL 格式化工具 以 Oracle SQL Developer 为例&#xff0c;使用一键格式化的步骤如下&#xff1a; 打开 Oracle SQL Developer。在“文件”菜单中&#xff0c;选择“打开文件”&#xff0c;然后选择你的 PL/SQL 文件。打开文件后&#xff0c;你可以通过右键菜单选择“格式…

机器学习——自动驾驶

本章我们主要学习以下内容: 阅读自动驾驶论文采集数据根据论文搭建自动驾驶神经网络训练模型在仿真环境中进行自动驾驶 论文介绍 本文参考自2016年英伟达发表的论文《End to End Learning for Self-Driving Cars》 📎end2end.pdf

全栈的自我修养 ———— 如何发布一个npm包?

创建本地仓库 npm init在此期间会让你添加一些版本信息和名称 登陆npm npm login ——> yinhaodada arx.040208发布 npm publish查询

微服务(基础篇-008-Elasticsearch分布式搜索【上】)

目录 初识elasticsearch&#xff08;1&#xff09; 了解ES&#xff08;1.1&#xff09; 倒排索引&#xff08;1.2&#xff09; es的一些概念&#xff08;1.3&#xff09; 安装es、kibana&#xff08;1.4&#xff09; ik分词器&#xff08;1.5&#xff09; ik分词器的拓展…

RT-Thread内核简介

1、RT-Thread 内核介绍 RT-Thread 内核架构图,内核处于硬件层之上,内 核部分包括内核库、实时内核实现 内核库是为了保证内核能够独立运行的一套小型的类似 C 库的函数实现子集。这部分根据编译器的不 同自带 C 库的情况也会有些不同,当使用 GNU GCC 编译器时,会携带…

在 Elasticsearch 中扩展 ML 推理管道:如何避免问题并解决瓶颈

作者&#xff1a;来自 Elastic Iulia Feroli 是时候考虑语义搜索运营了吗&#xff1f; 无论你是一位经验丰富的搜索工程师&#xff0c;希望探索新的人工智能功能&#xff0c;还是一位机器学习专家&#xff0c;希望更多地利用搜索基础设施来增强语义相似性模型 —— 充分利用这…

jenkins下载安装(mac)

下载官网 具体 直接命令安装 Sample commands: Install the latest LTS version: brew install jenkins-ltsStart the Jenkins service: brew services start jenkins-ltsRestart the Jenkins service: brew services restart jenkins-ltsUpdate the Jenkins version: brew u…

Linux02(项目部署,手动和自动部署,JDK版本问题,安装软件,安装软件,安装JDK,Tomcat,MySQL,Irzsz)

目录 一、安装软件 1. 安装准备工作 1 Linux里的软件安装方式 2 上传软件到Linux 3 拍照虚拟机快照 2. 安装JDK 1 卸载自带jdk 2 解压JDK 3 配置环境变量 4 测试JDK 3. 安装Tomcat 1 解压Tomcat 2 修改防火墙设置 3 测试Tomcat 启动Tomcat 访问Tomcat 查看Tom…

【Jenkins PipeLine】Jenkins PipeLine 联动参数示例

目录 1. Pipeline script&#xff1a; 1.1.代码说明&#xff1a; 2. 实现效果&#xff1a; 3.联动说明&#xff1a; 4.Jenkins安装插件 1. Pipeline script&#xff1a; properties([parameters([[$class: "ChoiceParameter", choiceType: "PT_SINGLE_SELE…

redis的主从复制(docker方式快速入门和实战)

目录 一、主从复制简介 二、配置主从服务器 2.1使用配置文件的形式来主从复制 2.2使用纯代码的方式来进行主从复制&#xff1b; 2.3脱离主服务器 三、一些注意事项 一、主从复制简介 主从复制&#xff0c;是指将一台Redis服务器的数据&#xff0c;复制到其他的Redis服务器…