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

原创 嵌入式悦翔园 2023-06-16 11:40

关注星标公众号,第一时间获取信息

一、前言

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

推荐阅读



01

加入嵌入式交流群


02

嵌入式资源获取


03

STM32中断优先级详解


04

STM32下载程序新思路--使用串口下载程序


嵌入式悦翔园 专注于嵌入式技术,包括但不限于STM32、Arduino、51单片机、物联网、Linux等编程学习笔记,同时包含大量的学习资源。欢迎关注,一同交流学习,共同进步!
评论
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 100浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 106浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 101浏览
  • TOF多区传感器: ND06   ND06是一款微型多区高集成度ToF测距传感器,其支持24个区域(6 x 4)同步测距,测距范围远达5m,具有测距范围广、精度高、测距稳定等特点。适用于投影仪的无感自动对焦和梯形校正、AIoT、手势识别、智能面板和智能灯具等多种场景。                 如果用ND06进行手势识别,只需要经过三个步骤: 第一步&
    esad0 2024-12-04 11:20 50浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 98浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 108浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 120浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 71浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 84浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 122浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 119浏览
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 68浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦