Linux驱动:I2C驱动看这一篇就够了

news2024/11/26 8:58:14

I2C驱动看这一篇就够了

    • 一、前言
    • 二、Linux 的 I2C 体系结构
      • 2.1 Linux I2C 核心
      • 2.2 Linux I2C 适配器驱动
      • 2.3 Linux I2C 设备驱动
      • 2.4 Linux I2C驱动总结
    • 三、具体设备驱动分析
      • 3.1 Probe函数
      • 3.2 读写函数
    • 四、I2C驱动中几个重要的结构体
      • 4.1 i2c_adapter 结构体
      • 4.2 i2c_client 结构体
      • 4.3 i2c_driver 结构体
    • 五、总结

一、前言

I2C协议是在开发中使用非常频繁的一种协议,相信大家在学习单片机的时候经常会用到支持I2C协议的模块,I2C 总线仅仅使用 SCL、SDA 这两根信号线就实现了设备之间的数据交互,极大地简化了对硬件资源和 PCB 板布线空间的占用。因此,I2C 总线被非常广泛地应用在 EEPROM、实时钟、小型 LCD 等设备与 CPU 的接口中。

但是与裸机开发不同的是在 Linux 系统中,I2C 驱动由 3 部分组成,即 I2C 核心I2C 总线驱动I2C 设备驱动。今天就从这三个部分来给大家讲解一下Linux中的I2C驱动,以及我们应该如何为我们的开发板添加一个I2C设备。

二、Linux 的 I2C 体系结构

由上面分析可知,Linux驱动分为三部分:I2C 核心I2C 总线驱动I2C 设备驱动
在这里插入图片描述

2.1 Linux I2C 核心

I2C 核心提供了 I2C 总线驱动和设备驱动的注册、注销方法,这部分主要是一些与硬件无关的的接口函数,这部分的代码一般不用我们普通开发者进行开发和修改,但是理解这部分的代码逻辑和接口还是非常必要的。

I2C 核心中的主要函数如下:

注册/注销 适配器(adapter)
int i2c_add_adapter(struct i2c_adapter *adap);
int i2c_del_adapter(struct i2c_adapter *adap);

注册/注销 I2C设备驱动程序
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
int i2c_del_driver(struct i2c_driver *driver);
inline int i2c_add_driver(struct i2c_driver *driver);

创建并注册一个新的I2C设备
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);

I2C 传输、发送和接收
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);
int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
int i2c_master_recv(struct i2c_client *client, char *buf ,int count);

上边三个函数用于实现与I2C设备之间的数据交换。i2c_transfer函数可以进行复杂的多消息传输,而i2c_master_sendi2c_master_recv函数用于单个数据消息的发送和接收。

这些函数提供了对于I2C总线读写操作的基本支持,简化了I2C设备驱动的开发,有了这些接口我们就不用关注I2C协议方面的代码了,只需要调用该接口即可完成数据的传输。

注意: i2c_transfer函数本身不具备驱动适配器物理硬件完成消息交互的能力,它只是寻找到 i2c_adapter 对应的 i2c_algorithm,并使用 i2c_algorithmmaster_xfer函数真正驱动硬件流程。

2.2 Linux I2C 适配器驱动

通过上面的介绍我们知道了I2C驱动主要分为三个部分,上面我们已经介绍了I2C核心这一部分,现在我们来介绍一下I2C 适配器驱动,我们知道I2C驱动和其他的那些字符设备驱动有所不同,I2C驱动中维持着一套自己的总线。

I2C 适配器驱动是Linux内核中的一个核心模块,总线层负责管理所有注册到系统的I2C总线适配器和设备,并提供与设备通信的API函数。它提供了一些基本的操作函数,如启动总线、停止总线、发送起始信号、发送停止信号等。但是这部分是由Linux内核完成的,并不需要我们开发者进行修改或添加,所以了解即可。

下面我们用一张图来看一下上面描述的这个过程:
在这里插入图片描述

2.3 Linux I2C 设备驱动

I2C 设备驱动要使用 i2c_driveri2c_client 数据结构并填充其中的成员函数。 i2c_client 一般被包含在设备的私有信息结构体 yyy_data 中,而 i2c_driver 则适合被定义为全局变量并初始化。

看到I2C设备驱动的这两个结构体大家是不是很熟悉了,I2C设备驱动是针对特定类型的I2C设备编写的驱动程序。它包含了对具体设备的操作和控制逻辑,通过调用I2C总线核心驱动提供的API函数与设备进行通信。设备驱动的主要任务包括初始化设备、读写数据、配置设备参数等。

因为这部分是针对特定类型的I2C设备编写的驱动程序,所以这部分才是要我们开发人员来完成编写的,我们如果需要在自己的开发板上添加一个新的I2C模块,我们就要首先编写I2C设备驱动这部分,这部分的编写需要调用上面我们介绍的I2C核心和I2C总线中接口函数来完成模块的初始化。

关于I2C设备驱动我们这里先做一个了解即可,后面会详细介绍这部分的内容,也是我们学习I2C驱动的重点内容。

2.4 Linux I2C驱动总结

I2C总线核心驱动(I2C Core Driver):
【系统厂编写】I2C总线核心驱动是Linux内核中的一个核心模块,负责管理所有注册到系统的I2C总线适配器和设备,并提供与设备通信的API函数。它提供了一些基本的操作函数,如启动总线、停止总线、发送起始信号、发送停止信号等。

I2C适配器驱动(I2C Adapter Driver):
【芯片厂提供】I2C适配器驱动负责与硬件的I2C控制器进行交互,完成硬件层面的初始化、配置和操作。它将底层硬件的特定接口与I2C总线核心驱动进行连接,使得核心驱动能够通过适配器驱动来访问硬件。

I2C设备驱动(I2C Device Driver):
【开发者编写】I2C设备驱动是针对特定类型的I2C设备编写的驱动程序。它包含了对具体设备的操作和控制逻辑,通过调用I2C总线核心驱动提供的API函数与设备进行通信。设备驱动的主要任务包括初始化设备、读写数据、配置设备参数等。

三部分之间的关系如下:

  • I2C核心层驱动作为顶层驱动,管理整个I2C子系统,并提供了基本的I2C操作接口。

  • I2C适配器驱动负责与底层硬件的I2C控制器进行交互,通过适配器驱动,I2C总线核心驱动能够与硬件进行通信。

  • I2C设备驱动则针对具体的I2C设备编写,实现了对设备的初始化、读写数据等操作。

在这里插入图片描述

三、具体设备驱动分析

由于作为开发者我们需要关注并且需要我们亲自编写的部分就只有设备驱动了,所以我们今天就详细介绍一下设备驱动这部分。

当我们需要编写具体的I2C设备驱动程序时,我们需要编写以下内容:probe函数remove函数操作函数以及数据传输与处理,下面将对每部分进行详细介绍。

3.1 Probe函数

具体设备中的probe函数是I2C设备驱动中最重要的函数之一,用于在I2C设备与驱动匹配成功后进行初始化和注册设备。在probe函数中,可以执行以下任务:

  • 进行设备的特定初始化操作,例如配置设备寄存器、申请内存资源等。

  • 注册字符设备、输入设备或其他设备类别,使系统能够识别和使用该设备。

  • 存储设备私有数据,通常使用i2c_set_clientdata函数将私有数据与i2c_client相关联,方便后续的操作函数访问。

我们在学习其他设备驱动的时候就知道了probe函数是设备驱动匹配成功后被调用执行的。它的原型通常如下所示:

static int i2c_device_probe(struct i2c_client *client, const struct i2c_device_id *id);

下面我们就找一个设备驱动来分析一下我们应该如何编写:

这里以rk3x_i2c_probe为例给大家进行分析:
static int rk3x_i2c_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	const struct of_device_id *match;
	struct rk3x_i2c *i2c;
	struct resource *mem;
	int ret = 0;
	int bus_nr;
	u32 value;
	int irq;
	unsigned long clk_rate;
 
	i2c = devm_kzalloc(&pdev->dev, sizeof(struct rk3x_i2c), GFP_KERNEL);
	if (!i2c)
		return -ENOMEM;
 
	match = of_match_node(rk3x_i2c_match, np);
	i2c->soc_data = (struct rk3x_i2c_soc_data *)match->data;
 
	/* use common interface to get I2C timing properties */
	i2c_parse_fw_timings(&pdev->dev, &i2c->t, true);
 
	strlcpy(i2c->adap.name, "rk3x-i2c", sizeof(i2c->adap.name));
	i2c->adap.owner = THIS_MODULE;
	i2c->adap.algo = &rk3x_i2c_algorithm;
	i2c->adap.retries = 3;
	i2c->adap.dev.of_node = np;
	i2c->adap.algo_data = i2c;
	i2c->adap.dev.parent = &pdev->dev;
 
	i2c->dev = &pdev->dev;
 
	spin_lock_init(&i2c->lock);
	init_waitqueue_head(&i2c->wait);
 
	i2c->i2c_restart_nb.notifier_call = rk3x_i2c_restart_notify;
	i2c->i2c_restart_nb.priority = 128;
	ret = register_i2c_restart_handler(&i2c->i2c_restart_nb);
	if (ret) {
		dev_err(&pdev->dev, "failed to setup i2c restart handler.\n");
		return ret;
	}
 
	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
	if (IS_ERR(i2c->regs))
		return PTR_ERR(i2c->regs);
 
	/* Try to set the I2C adapter number from dt */
	bus_nr = of_alias_get_id(np, "i2c");
 
	/*
	 * Switch to new interface if the SoC also offers the old one.
	 * The control bit is located in the GRF register space.
	 */
	if (i2c->soc_data->grf_offset >= 0) {
		struct regmap *grf;
 
		grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
		if (IS_ERR(grf)) {
			dev_err(&pdev->dev,
				"rk3x-i2c needs 'rockchip,grf' property\n");
			return PTR_ERR(grf);
		}
 
		if (bus_nr < 0) {
			dev_err(&pdev->dev, "rk3x-i2c needs i2cX alias");
			return -EINVAL;
		}
 
		/* 27+i: write mask, 11+i: value */
		value = BIT(27 + bus_nr) | BIT(11 + bus_nr);
 
		ret = regmap_write(grf, i2c->soc_data->grf_offset, value);
		if (ret != 0) {
			dev_err(i2c->dev, "Could not write to GRF: %d\n", ret);
			return ret;
		}
	}
 
	/* IRQ setup */
	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "cannot find rk3x IRQ\n");
		return irq;
	}
 
	ret = devm_request_irq(&pdev->dev, irq, rk3x_i2c_irq,
			       0, dev_name(&pdev->dev), i2c);
	if (ret < 0) {
		dev_err(&pdev->dev, "cannot request IRQ\n");
		return ret;
	}
 
	platform_set_drvdata(pdev, i2c);
 
	if (i2c->soc_data->calc_timings == rk3x_i2c_v0_calc_timings) {
		/* Only one clock to use for bus clock and peripheral clock */
		i2c->clk = devm_clk_get(&pdev->dev, NULL);
		i2c->pclk = i2c->clk;
	} else {
		i2c->clk = devm_clk_get(&pdev->dev, "i2c");
		i2c->pclk = devm_clk_get(&pdev->dev, "pclk");
	}
 
	if (IS_ERR(i2c->clk)) {
		ret = PTR_ERR(i2c->clk);
		if (ret != -EPROBE_DEFER)
			dev_err(&pdev->dev, "Can't get bus clk: %d\n", ret);
		return ret;
	}
	if (IS_ERR(i2c->pclk)) {
		ret = PTR_ERR(i2c->pclk);
		if (ret != -EPROBE_DEFER)
			dev_err(&pdev->dev, "Can't get periph clk: %d\n", ret);
		return ret;
	}
 
	ret = clk_prepare(i2c->clk);
	if (ret < 0) {
		dev_err(&pdev->dev, "Can't prepare bus clk: %d\n", ret);
		return ret;
	}
	ret = clk_prepare(i2c->pclk);
	if (ret < 0) {
		dev_err(&pdev->dev, "Can't prepare periph clock: %d\n", ret);
		goto err_clk;
	}
 
	i2c->clk_rate_nb.notifier_call = rk3x_i2c_clk_notifier_cb;
	ret = clk_notifier_register(i2c->clk, &i2c->clk_rate_nb);
	if (ret != 0) {
		dev_err(&pdev->dev, "Unable to register clock notifier\n");
		goto err_pclk;
	}
 
	clk_rate = clk_get_rate(i2c->clk);
	rk3x_i2c_adapt_div(i2c, clk_rate);
 
	ret = i2c_add_adapter(&i2c->adap);
	if (ret < 0) {
		dev_err(&pdev->dev, "Could not register adapter\n");
		goto err_clk_notifier;
	}
 
	dev_info(&pdev->dev, "Initialized RK3xxx I2C bus at %p\n", i2c->regs);
 
	return 0;
 
err_clk_notifier:
	clk_notifier_unregister(i2c->clk, &i2c->clk_rate_nb);
err_pclk:
	clk_unprepare(i2c->pclk);
err_clk:
	clk_unprepare(i2c->clk);
	return ret;
}

从上面的代码我们可以发现rk3x_i2c_probe主要做了以下几件事情:

1、通过devm_kzalloc函数为rk3x_i2c结构体分配内存空间;
2、从设备树中获取I2C设备信息并填充rk3x_i2c结构体;
3、使用devm_platform_ioremap_resource函数来映射设备的寄存器资源到内存中;
4、获取并配置中断;
5、使用i2c_add_adapter注册设备

基本上这个驱动就是一个比较完整的I2C设备初始化流程了,我们如果想要编写其他设备的驱动可以参考该驱动初始化来进行编写。

3.2 读写函数

由于rk3x_i2c中的读写函数和该设备关联性较大,不具备通用性,这里以sx1_i2c_write_bytesx1_i2c_read_byte来给大家进行分析,该函数更具有通用性。

/* Write to I2C device */
int sx1_i2c_write_byte(u8 devaddr, u8 regoffset, u8 value)
{
	struct i2c_adapter *adap;
	int err;
	struct i2c_msg msg[1];
	unsigned char data[2];

	adap = i2c_get_adapter(0);
	if (!adap)
		return -ENODEV;
	msg->addr = devaddr;	/* I2C address of chip */
	msg->flags = 0;
	msg->len = 2;
	msg->buf = data;
	data[0] = regoffset;	/* register num */
	data[1] = value;		/* register data */
	err = i2c_transfer(adap, msg, 1);
	i2c_put_adapter(adap);
	if (err >= 0)
		return 0;
	return err;
}

/* Read from I2C device */
int sx1_i2c_read_byte(u8 devaddr, u8 regoffset, u8 *value)
{
	struct i2c_adapter *adap;
	int err;
	struct i2c_msg msg[1];
	unsigned char data[2];

	adap = i2c_get_adapter(0);
	if (!adap)
		return -ENODEV;

	msg->addr = devaddr;	/* I2C address of chip */
	msg->flags = 0;
	msg->len = 1;
	msg->buf = data;
	data[0] = regoffset;	/* register num */
	err = i2c_transfer(adap, msg, 1);

	msg->addr = devaddr;	/* I2C address */
	msg->flags = I2C_M_RD;
	msg->len = 1;
	msg->buf = data;
	err = i2c_transfer(adap, msg, 1);
	*value = data[0];
	i2c_put_adapter(adap);

	if (err >= 0)
		return 0;
	return err;
}

从上面的代码可以看出,sx1_i2c_write_byte主要完成了以下功能:

1、通过调用i2c_get_adapter(0)函数获取指定索引的I2C适配器对象并赋值给adap变量。
2、初始化一个struct i2c_msg类型的数组msg,该数组包含一个元素用于I2C消息的传输。
3、设置msg结构体中的字段:
	addr:设备的I2C地址。
	flags:传输标志位,此处为0表示写操作。
	len:要传输的字节数,此处设置为2,即寄存器地址和寄存器数据两个字节。
	buf:数据缓冲区的指针,用于存储要发送的数据。
4、将要写入的设备寄存器地址和数据分别存储在data数组的第一个和第二个元素中,即data[0] = regoffset;和data[1] = value;5、调用i2c_transfer()函数进行I2C消息传输,将数据写入设备寄存器。
6、使用i2c_put_adapter()函数释放先前获取的I2C适配器对象。

sx1_i2c_read_byte主要完成了以下功能:

1、通过调用i2c_get_adapter(0)函数获取指定索引的I2C适配器对象并赋值给adap变量。
2、初始化一个struct i2c_msg类型的数组msg,该数组包含一个元素用于I2C消息的传输。
3、设置msg结构体中的字段:
	addr:设备的I2C地址。
	flags:传输标志位,此处为0表示写操作。
	len:要传输或接收的字节数。
	buf:数据缓冲区的指针,用于存储要发送或接收的数据。
4、将要读取的设备寄存器地址存储在data数组的第一个元素中,即data[0] = regoffset;5、调用i2c_transfer()函数进行I2C消息传输,将数据写入设备寄存器。
6、更改flags字段为I2C_M_RD,表示接收模式(读操作)。
7、再次调用i2c_transfer()函数进行I2C消息传输,从设备中读取数据。
8、将读取到的数据存储在data数组的第一个元素中,即*value = data[0];9、使用i2c_put_adapter()函数释放先前获取的I2C适配器对象。

对比I2C读和写的过程大家可能会发现I2C读的过程为什么调用了两次i2c_transfer函数呢?多调用了一次i2c_transfer函数是因为我们在调用i2c_transfer读取数据时,需要先发送要读取的寄存器地址给设备,然后再从设备读取实际的数据。所以第一次使用i2c_transfer发送的信息为需要读取的地址信息,第二次将标志位改为读,然后使用i2c_transfer将从设备返回的信息存储到i2c_adapter中。

四、I2C驱动中几个重要的结构体

在I2C驱动中,有三个比较重要的结构体用于描述和管理I2C设备和传输操作。下面就这三个结构体的成员以及作用来给大家讲解一下:

4.1 i2c_adapter 结构体

定义位置: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	*/
	const struct i2c_lock_operations *lock_ops;
	struct rt_mutex bus_lock;
	struct rt_mutex mux_lock;

	int timeout;			/* in jiffies */
	int retries;
	struct device dev;		/* the adapter device */
	unsigned long locked_flags;	/* owned by the I2C core */
#define I2C_ALF_IS_SUSPENDED		0
#define I2C_ALF_SUSPEND_REPORTED	1

	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;

	struct irq_domain *host_notify_domain;
	struct regulator *bus_regulator;
};

几个重要的成员:

name:适配器的名称。
nr:适配器的编号。
bus_lock 和 bus_unlock:用于保护对适配器的并发访问的锁机制。
algo:指向 I2C 算法结构体的指针,包含了适配器的通信算法,如标准模式、快速模式、高速模式等。

4.2 i2c_client 结构体

定义位置:i2c.h
结构体原型:

struct i2c_client {
	unsigned short flags;		/* div., see below		*/
#define I2C_CLIENT_PEC		0x04	/* Use Packet Error Checking */
#define I2C_CLIENT_TEN		0x10	/* we have a ten bit chip address */
					/* Must equal I2C_M_TEN below */
#define I2C_CLIENT_SLAVE	0x20	/* we are the slave */
#define I2C_CLIENT_HOST_NOTIFY	0x40	/* We want to use I2C host notify */
#define I2C_CLIENT_WAKE		0x80	/* for board_info; true iff can wake */
#define I2C_CLIENT_SCCB		0x9000	/* Use Omnivision SCCB protocol */
					/* Must match I2C_M_STOP|IGNORE_NAK */

	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 init_irq;			/* irq set at initialization	*/
	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
	void *devres_group_id;		/* ID of probe devres group	*/
};

几个重要的成员:

flags:标志位,用于指定设备的特性和行为。
addr:设备的I2C地址。
adapter:指向 i2c_adapter 的指针,表示所属的I2C适配器。
driver:指向设备驱动程序的指针,表示设备所使用的驱动。

4.3 i2c_driver 结构体

定义位置:i2c.h
结构体原型:

struct i2c_driver {
	unsigned int class;

	union {
	/* Standard driver model interfaces */
		int (*probe)(struct i2c_client *client);
		/*
		 * Legacy callback that was part of a conversion of .probe().
		 * Today it has the same semantic as .probe(). Don't use for new
		 * code.
		 */
		int (*probe_new)(struct i2c_client *client);
	};
	void (*remove)(struct i2c_client *client);


	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *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").
	 * For the SMBus Host Notify protocol, the data corresponds to the
	 * 16-bit payload data reported by the slave device acting as master.
	 */
	void (*alert)(struct i2c_client *client, enum i2c_alert_protocol protocol,
		      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 *client, struct i2c_board_info *info);
	const unsigned short *address_list;
	struct list_head clients;

	u32 flags;
};

几个重要的成员:

driver:是一个 struct device_driver 结构体,用于向Linux设备模型注册驱动程序。
probe 和 remove:指向探测和移除设备的函数指针,通过这两个函数,驱动程序可以在发现匹配的设备时执行初始化操作,并在设备被移除时执行清理操作。
id_table:用于指定驱动程序支持的I2C设备ID列表,以便匹配对应的设备。

这些结构体共同构成了Linux内核中的I2C驱动框架,提供了对I2C总线、适配器和设备的抽象和管理功能。开发者可以基于这些结构体来编写自己的I2C驱动程序,并实现与I2C设备的通信和控制。所以我们的工作就是填充这些结构体然后调用对应的接口把我们填充好的结构体传递给I2C设备器驱动和核心驱动从而完成设备的初始化和读写操作。

五、总结

I2C驱动的学习有一个特点:弄懂比较难,会用比较简单,这是因为有很多的有难度的内容以及和协议相关的内容都已经被Linux或者芯片厂封装好了,我们需要做的就是使用他们提供的这些接口完成指定设备的读写操作,但是我们的学习不能止步于此,所以我们不但要会用,还要知其然知其所以然。

关于I2C驱动的知识我也是才疏学浅,也有很多地方不是很了解,关于上面的知识点也只是我的一些理解,如果有不对的地方欢迎大家指出,我们一起交流学习。

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

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

相关文章

管理类联考——英语——技巧篇——必考高频词组

考研英语必考高频词组 【介词名词形式】 第一组 by accident 偶然 on account of 因为&#xff0c;由于 in addition 另外 in addition to 除……之外 in the air 在流行中&#xff0c;在传播中 on (the/an) average 平均&#xff0c;一般来说 on the basis o…

华为的数通认证考试难不难?考试费用是多少?

自从网络出现在我们的世界后&#xff0c;人类社会发生了巨大的变化&#xff0c;我们每个人的生活和网络息息相关&#xff0c;传统的购物、出行、社交方式发生了巨大的变化&#xff0c;这一切都离不开数通技术的支持&#xff0c;数通一般是指计算机通信网络中数据信号的基带传输…

春招面了个字节拿 36K 出来的,让我见识到了基础的天花板

今年的春招基本已经进入大规模的开奖季了&#xff0c;很多小伙伴收获不错&#xff0c;拿到了心仪的 offer。 各大论坛和社区里也看见不少小伙伴慷慨地分享了常见的面试题和八股文&#xff0c;为此咱这里也统一做一次大整理和大归类&#xff0c;这也算是划重点了。 俗话说得好…

Unity中Camera参数—Culling Mask详解

Culling Mask 如下图所示&#xff1a; 显示层级如下&#xff1a; 应用&#xff1a; Culling Mask &#xff1a;主要是相机针对不同层级的物体进行渲染的操作&#xff08;想让相机渲染哪个层就勾选哪个层&#xff09; 层级介绍&#xff1a; unity中的层前7个被unity锁定&#…

第三篇:分治算法

第三篇&#xff1a;分治算法 1. 分治算法简介2. 递归算法框架模板3. 分治演示代码4. 递归算法经典案例 分治算法的思想是将大问题分解成小问题&#xff0c;解决完一个一个小问题便解决了大问题。比如&#xff0c;我们想从杭州出发到徐州&#xff0c;可以分解成杭州到南京&#…

科技云报道:济南公交热线96190背后的“数字力量”

科技云报道原创。 “喂&#xff0c;公交公司吗&#xff1f;我的手提包落在63路车上了&#xff0c;能帮我找一下吗&#xff1f;” “我们小区距离公交站比较远&#xff0c;能增加个公交线路吗&#xff1f;” “等了半天车都不来&#xff0c;公交车为啥这么难等&#xff1f;”…

计算机网络实验---思科模拟器

文章目录 1. 组建小型局域网2. 交换机的配置与管理实验3&#xff1a;交换机划分 Vlan实验4&#xff1a;路由器的基本配置实验5&#xff1a;静态路由实验7&#xff1a;动态路由 1. 组建小型局域网 需要一台交换机&#xff0c;两台PC&#xff0c;连线连起来 配置 PC0 和 PC1 配置…

【大数据之Hive】十二、Hive-HQL查询之分组、join、排序

一、分组 1 group by 语句 group by 通常和聚合函数一起使用&#xff0c;按照一个或多个列的结果进行分组&#xff0c;任何对每个租执行聚合操作。   用group by时&#xff0c;select中只能用在group by中的字段和聚合函数。 --计算emp每个部门中每个岗位的最高薪水&#x…

C++ 设计模式----“单一职责“模式

二、“单一职责”模式 在软件组件的设计中&#xff0c;如果责任划分的不清晰&#xff0c;使用继承得到的结果往往是随着需求的变化&#xff0c;子类急剧膨胀&#xff0c;同时充斥着重复代码&#xff0c;这时候的关键是划清责任。  典型模式 • Decorator • Bridge 【1】D…

RabbitMQ高阶使用延时任务

目录 1 从打车开始说起1.1 需要解决的问题1.1.1 打车超时 2 延时任务2.1 什么是延时任务2.1.1 和定时任务区别 2.2 延时队列使用场景2.3 常见方案2.3.1 数据库轮询2.3.1 JDK的延迟队列2.3.3 netty时间轮算法2.3.4 使用消息队列 2.4 延时队列2.4.1 TTL(消息过期时间) 2.4.1.1 配…

第七十五天学习记录:高等数学:定积分(宋浩板书)

定积分是微积分中的一个重要概念&#xff0c;表示在给定区间上函数曲线下的面积或有向曲线与坐标轴围成的面积。定积分通常用符号 ∫ 来表示&#xff0c;具体形式为 ∫f(x) dx。 对于给定的函数 f(x) 和区间 [a, b]&#xff0c;定积分的计算可以通过求函数 f(x) 在该区间上的原…

【C++】STL的vector容器

目录 2、vector容器 1.1模板实例化 1.2定义与初始化vector对象 2.1vector构造函数 2.2vector赋值操作 2.2vector的容量和大小 2.4vector的插入 2.5vector的删除 2.6vector数据存取 2.7vector互换容器 2.8vector预留空间 2、vector容器 vector是C最常用的容器之一&a…

深度学习(神经网络)

文章目录 神经网络历史形式神经元模型&#xff08;M-P模型&#xff09;感知器多层感知器 误差反向传播算法误差函数和激活函数误差函数二次代价函数交叉熵代价函数 激活函数sigmoid函数RELU函数 似然函数softmax函数 随机梯度下降法批量学习方法在线学习小批量梯度下降法 学习率…

<Linux开发>驱动开发 -之-阻塞、非阻塞IO和异步通知

&#xff1c;Linux开发&#xff1e;驱动开发 -之-阻塞、非阻塞IO和异步通知 交叉编译环境搭建&#xff1a; &#xff1c;Linux开发&#xff1e; linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下&#xff1a; &#xff1c;Linux开发&#xff1e; -之-系统移植 uboot移…

easyui01(基本布局)

一.概述 1.What&#xff1f; jQuery EasyUI是一组基于jQuery的UI插件集合体&#xff0c;能帮助web开发者更轻松的打造出功能丰富并且美观的UI界面 2.Why&#xff1f; ①.使用easyui 不需要写很多代码&#xff0c;只需要编写一些简单 HTML 标记&#xff0c;就可以定义用户界…

Java优先级队列源码分析

先导课程&#xff1a;二叉堆学习 优先级队列 1. Priority Queue 优先级队列&#xff08;Priority Queue&#xff09;也是队列 普通队列按照FIFO原则&#xff0c;也就是先进先出优先级队列按照优先级高低进行出队&#xff0c;比如将优先级最高的元素作为队头优先出队 基本接口和…

Vue3中Composition 其他一些API

一、 Reactive判断的API 1. isProxy 检查对象是否由reactive或者readonly创建的proxy &#xff0c;返回一个布尔值 <script setup> import { reactive, readonly, isProxy } from vuelet foo readonly({ name: WFT1 }) // 其中的属性不可修改let bar reactive({ n…

DeepSpeed结合Megatron-LM训练GPT2模型笔记(上)

文章目录 0x0. 前言0x1. Megatron使用单卡训练GPT2依赖安装准备训练数据训练详细流程和踩坑 0x2. Megatron使用单卡预测训练好的GPT2模型0x3. 参数量和显存估计参数量估计训练显存占用估计 0x4. Megatron使用多卡训练GPT2模型2卡数据并行2卡模型并行 0x5. 总结 0x0. 前言 本文…

【V4L2】 v4l2框架分析之v4l2_subdev

文章目录 一、v4l2_subdev简介二、初始化v4l2_subdev三、注册/注销subdev四、异步注册子设备 一、v4l2_subdev简介 相关源码文件&#xff1a; /include/media/v4l2-subdev.h/drivers/media/v4l2-core/v4l2-subdev.c 在linux内核中&#xff0c;许多驱动程序需要与子设备通信&…

【嵌入式linux】spi驱动加载后probe函数未执行的问题

【嵌入式linux】spi驱动加载后probe函数未执行的问题 问题描述解决办法 问题描述 嵌入式linux平台下的spi分为设备、总线和驱动&#xff0c;一般半导体原厂已经实现好了spi设备和总线的相关代码&#xff0c;开发者只需根据实际使用情况修改设备树以及编写驱动部分的代码即可。…