【IMX6ULL驱动开发学习】09.Linux之I2C驱动框架简介和驱动程序模板

news2025/1/23 6:03:01

 参考:Linux之I2C驱动_linux i2c驱动_风间琉璃•的博客-CSDN博客​​​​​​

目录

一、I2C驱动框架简介

1.1 I2C总线驱动

1.2 I2C设备驱动

二、I2C总线-设备-驱动模型

2.1 i2c_driver

2.2 i2c_client

2.3 I2C 设备数据收发和处理

三、Linux I2C驱动程序模板


一、I2C驱动框架简介

在 Linux 内核中 I2C 的体系结构分为 3 个部分:

  • I2C 核心: I2C 核心提供了 I2C 总线驱动和设备驱动的注册、 注销方法
  • I2C 总线驱动: I2C 总线驱动是对 I2C 硬件体系结构中适配器端的实现, 适配器可由CPU 控制, 甚至可以直接集成在 CPU 内部。I2C 总线驱动就是 SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动
  • I2C 设备驱动: I2C 设备驱动是对 I2C 硬件体系结构中设备端的实现 设备一般挂接在受 CPU 控制的 I2C 适配器上, 通过 I2C 适配器与 CPU 交换数据

1.1 I2C总线驱动

I2C 总线和 platform 总线类似, 区别在于platform 总线是虚拟的一条总线, 而 I2C 总线是实际
存在的
。 对于使用 I2C 通信的设备, 在驱动中直接使用 I2C 总结即可。 I2C 总线驱动的重点是 I2C 适配器驱动, 主要涉及到两个结构体: i2c_adapter 和 i2c_algorithm。 在 Linux 内核中用 i2c_adapter 结构体来表示 I2C 适配器。 i2c_adapter 结构体定义在 include/linux/i2c.h 文件中

struct i2c_adapter {
    struct module *owner;
    unsigned int class; /* classes to allow probing for */
    const struct i2c_algorithm *algo; /* 总线访问算法 */
    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;
};

i2c_algorithm 类型的指针变量algo, 对于一个 I2C 适配器, 要对外提供读写 API 函数, 设备驱动程序可以使用这些 API 函数来完成读写操作。 i2c_algorithm 是 I2C 适配器与 IIC 设备进行通信的方法i2c_algorithm 结构体定义在 include/linux/i2c.h 文件中

 
struct i2c_algorithm 
{
    ......
    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 *);
    ......
};

一般 SOC 的 I2C 总线驱动都是由半导体厂商编写的,所以大多数只要专注于 I2C 设备驱动即可。

1.2 I2C设备驱动

在 I2C 设备驱动中主要有两个重要的结构体: i2c_client 和 i2c_driver。 i2c_client 是描述设备信息的,i2c_driver 描述驱动内容

当驱动和设备匹配成功后,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个i2c_client,这个ic_client存储着这个设备的所有信息,如芯片地址。i2c_client 结构体定义在include/linux/i2c.h 文件中

struct i2c_client {
    unsigned short flags; /* 标志 */
    unsigned short addr; /* 芯片地址, 7 位,存在低 7 位*/
    ......
    char name[I2C_NAME_SIZE]; /* 名字 */
    struct i2c_adapter *adapter; /* 对应的 I2C 适配器 */
    struct device dev; /* 设备结构体 */
    int irq; /* 中断 */
    struct list_head detected;
    ......
};

i2c_driver 类似 platform_driver,是编写 I2C 设备驱动重点要处理的内容, i2c_driver 结构体定义在 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 ("eventflag"). */
    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;
};

当 I2C 设备和驱动匹配成功以后 probe 函数就会执行。device_driver 驱动结构体,如果使用设备树,需要设置 device_driver 的of_match_table 成员变量,即驱动的兼容(compatible)性。 未使用设备树的设备需要设置id_table 设备匹配 ID 表

构造i2c_driver结构体,里面会表明支持那些设备,入口函数里注册i2c_driver结构体,如果i2c_driver发现能够支持的i2c_client的话,probe函数就被调用,在probe函数里记录client信息、注册字符设备、注册file_operations、自动创建设备节点

I2C驱动程序与普通的字符设备驱动程序没有本质差别,唯一差别:发起数据传输时用到i2c_transfer,该函数需要用到适配器(i2c控制器),probe函数被调用时内核会传入i2c_client,i2c_client里含有i2c控制器。

二、I2C总线-设备-驱动模型

2.1 i2c_driver

2c_driver表明能支持哪些设备:

  • 使用of_match_table来判断

    • 设备树中,某个I2C控制器节点下可以创建I2C设备的节点

      • 如果I2C设备节点的compatible属性跟of_match_table的某项兼容,则匹配成功

    • i2c_client.name跟某个of_match_table[i].compatible值相同,则匹配成功

  • 使用id_table来判断

    • i2c_client.name跟某个id_table[i].name值相同,则匹配成功

i2c_driver跟i2c_client匹配成功后,就调用i2c_driver.probe函数。

/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id xxx_id[] = {
    {"xxx", 0},
    {}
};
 
 /* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
    { .compatible = "xxx" },
    { /* Sentinel */ }
};

/* i2c 驱动结构体 */
static struct i2c_driver xxx_driver = {
    .probe = xxx_probe,
    .remove = xxx_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "xxx",
        .of_match_table = xxx_of_match,  //使用设备树
    },
    .id_table = xxx_id,   //未使用设备树
};

2.2 i2c_client

i2c_client表示一个I2C设备,创建i2c_client的方法有4种:

(1)方法1:通过设备树来创建(常用)

i2c1: i2c@400a0000 {
		/* ... master properties skipped ... */
		clock-frequency = <100000>;

		flash@50 {
			compatible = "atmel,24c256";
			reg = <0x50>;
		};

		pca9532: gpio@60 {
			compatible = "nxp,pca9532";
			gpio-controller;
			#gpio-cells = <2>;
			reg = <0x60>;
		};
	};

向 i2c1 添加 flash 子节点,flash@50是子节点名字, @后面的50是I2C 器件地址。compatible 属性值为atmel,24c256。reg属性也是设置I2C的器件地址的。I2C 设备节点的创建主要是 compatible 属性和 reg属性的设置, 一个用于匹配驱动, 一个用于设置器件地址

(2)方法2:

有时候无法知道该设备挂载哪个I2C bus下,无法知道它对应的I2C bus number。 但是可以通过其他方法知道对应的i2c_adapter结构体。 可以使用下面两个函数来创建i2c_client:

i2c_new_device

 static struct i2c_board_info sfe4001_hwmon_info = {
	I2C_BOARD_INFO("max6647", 0x4e),
  };

  int sfe4001_init(struct efx_nic *efx)
  {
	(...)
	efx->board_info.hwmon_client =
		i2c_new_device(&efx->i2c_adap, &sfe4001_hwmon_info);

	(...)
  }

i2c_new_probed_device

static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END };

  static int usb_hcd_nxp_probe(struct platform_device *pdev)
  {
	(...)
	struct i2c_adapter *i2c_adap;
	struct i2c_board_info i2c_info;

	(...)
	i2c_adap = i2c_get_adapter(2);
	memset(&i2c_info, 0, sizeof(struct i2c_board_info));
	strscpy(i2c_info.type, "isp1301_nxp", sizeof(i2c_info.type));
	isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info,
						   normal_i2c, NULL);
	i2c_put_adapter(i2c_adap);
	(...)
  }

差别:

  • i2c_new_device:会创建i2c_client,即使该设备并不存在

  • i2c_new_probed_device:

    • 它成功的话,会创建i2c_client,并且表示这个设备肯定存在

    • I2C设备的地址可能发生变化,比如AT24C02的引脚A2A1A0电平不一样时,设备地址就不一样

    • 可以罗列出可能的地址

    • i2c_new_probed_device使用这些地址判断设备是否存在

(3)方法3(不推荐):由i2c_driver.detect函数来判断是否有对应的I2C设备并生成i2c_client  

(4)方法4:通过用户空间(user-space)生成 调试时、或者不方便通过代码明确地生成i2c_client时,可以通过用户空间来生成。

// 创建一个i2c_client, .name = "eeprom", .addr=0x50, .adapter是i2c-3
  # echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device
  
// 删除一个i2c_client
  # echo 0x50 > /sys/bus/i2c/devices/i2c-3/delete_device

2.3 I2C 设备数据收发和处理

在 I2C 设备驱动中首先要完成 i2c_driver 结构体的创建、 初始化和注册, 当设备和驱动匹配成功后,就会执行 probe 函数, probe 函数中就是执行字符设备驱动的一套流程。

一般需要在probe函数里面初始化 I2C 设备,要初始化 I2C 设备需要使用 i2c_transfer 函数对 I2C 设备寄存器进行读写操作。 i2c_transfer 函数会调用 I2C 适配器中 i2c_algorithm里面的 master_xfer 函数, 对于 I.MX6U 而言是 i2c_imx_xfer 这个函数。
 

int i2c_transfer(struct i2c_adapter *adap,struct i2c_msg *msgs,int num)

adap: 所使用的 I2C 适配器, i2c_client 会保存其对应的 i2c_adapter
msgs: I2C 要发送的一个或多个消息
num: 消息数量,msgs 的数量
返回值: 负值,失败,其他非负值,发送的 msgs 数量

msgs参数是一个 i2c_msg 类型的指针参数,Linux内核使用 i2c_msg 结构体 描述一个消息。

struct i2c_msg {
    __u16 addr; /* 从机地址 */
    __u16 flags; /* 标志 */
    #define I2C_M_TEN 0x0010
    #define I2C_M_RD 0x0001
    #define I2C_M_STOP 0x8000
    #define I2C_M_NOSTART 0x4000
    #define I2C_M_REV_DIR_ADDR 0x2000
    #define I2C_M_IGNORE_NAK 0x1000
    #define I2C_M_NO_RD_ACK 0x0800
    #define I2C_M_RECV_LEN 0x0400
    __u16 len; /* 消息(本 msg)长度 */
    __u8 *buf; /* 消息数据 */
};

使用i2c_transfer函数发送数据前,要构建好 i2c_msg,使用 i2c_transfer 进行 I2C 数据收发的模板:

/* 设备结构体 */
struct xxx_dev {
    ......
    void *private_data; /* 私有数据,一般会设置为 i2c_client */
};
 
/*
* @description : 读取 I2C 设备多个寄存器数据
* @param – dev : I2C 设备
* @param – reg : 要读取的寄存器首地址
* @param – val : 读取到的数据
* @param – len : 要读取的数据长度
* @return : 操作结果
*/
static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val,
int len)
{
    int ret;
    struct i2c_msg msg[2];
    struct i2c_client *client = (struct i2c_client *)
    dev->private_data;
 
    /* msg[0],第一条写消息,发送要读取的寄存器首地址 */
    msg[0].addr = client->addr; /* I2C 器件地址 */
    msg[0].flags = 0; /* 标记为发送数据 */
    msg[0].buf = &reg; /* 读取的首地址 */
    msg[0].len = 1; /* reg 长度 */
 
    /* msg[1],第二条读消息,读取寄存器数据 */
    msg[1].addr = client->addr; /* I2C 器件地址 */
    msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
    msg[1].buf = val; /* 读取数据缓冲区 */
    msg[1].len = len; /* 要读取的数据长度 */
    ret = i2c_transfer(client->adapter, msg, 2);
    if(ret == 2) 
    {
        ret = 0;
    } 
    else 
    {
        ret = -EREMOTEIO;
    }
    return ret;
}
 
/*
* @description : 向 I2C 设备多个寄存器写入数据
* @param – dev : 要写入的设备结构体
* @param – reg : 要写入的寄存器首地址
* @param – buf : 要写入的数据缓冲区
* @param – len : 要写入的数据长度
* @return : 操作结果
*/
static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf,u8 len)
{
    u8 b[256];
    struct i2c_msg msg;
    struct i2c_client *client = (struct i2c_client *)
    dev->private_data;
 
    b[0] = reg; /* 寄存器首地址 */
    memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组 b 里面 */
 
    msg.addr = client->addr; /* I2C 器件地址 */
    msg.flags = 0; /* 标记为写数据 */
 
    msg.buf = b; /* 要发送的数据缓冲区 */
    msg.len = len + 1; /* 要发送的数据长度 */
 
    return i2c_transfer(client->adapter, &msg, 1);
}

在设备结构体里面添加一个执行void的指针成员变量private_data,此成员变量用于保存设备的私有数据,在 I2C 设备驱动中一般将其指向 I2C 设备对应的i2c_client

xxx_read_regs 函数用于读取 I2C 设备多个寄存器数据,然后定义了一个i2c_msg 数组, 2 个数组元素。如图,I2C写时序图,因为 I2C 读取数据的时候要先发送要读取的寄存器地址,然后再读取数据,所以需要准备两个 i2c_msg。一个用于发送寄存器地址,一个用于读取寄存器值。

对于 msg[0], 将 flags设置为 0, 表示写数据。 msg[0]的 addr 是 I2C 设备的器件地址, msg[0]的 buf 成员变量就是要读取的寄存器地址。 对于 msg[1], 将 flags 设置为 I2C_M_RD, 表示读取数据。 msg[1]的 buf 成员变量用于保存读取到的数据, len 成员变量就是要读取的数据长度。 调用 i2c_transfer 函数完成 I2C 数据读操作。


 

xxx_write_regs 函数用于向 I2C 设备多个寄存器写数据。 数组 b 用于存放寄存器首地址和要发送的数据,msg 的 addr设置为 I2C 器件地址。然后设置 msg 的 flags 为 0, 也就是写数据。设置要发送的数据, 也就是数组 b。设置 msg 的 len 为 len+1, 因为要加上一个字节的寄存器地址。 最后通过 i2c_transfer 函数完成向 I2C 设备的写操作。

另外还有两个API函数分别用于I2C数据的收发操作,这两个函数都会调用i2c_transfer。

I2C 数据发送函数 i2c_master_send:

int i2c_master_send(const struct i2c_client *client,const char *buf,int count)

client: I2C 设备对应的 i2c_client
buf:要发送的数据
count: 要发送的数据字节数,要小于 64KB,以为 i2c_msg 的 len 成员变量是一个 u16(无
符号 16 位)类型的数据。
返回值: 负值,失败,其他非负值,发送的字节数

I2C 数据接收函数为 i2c_master_recv:

int i2c_master_recv(const struct i2c_client *client,char *buf,int count)

client: I2C 设备对应的 i2c_client
buf:要接收的数据

count: 要接收的数据字节数,要小于 64KB,以为 i2c_msg 的 len 成员变量是一个 u16(无
符号 16 位)类型的数据
返回值: 负值,失败,其他非负值,发送的字节数

三、Linux I2C驱动程序模板

i2c_drv.c

#include "linux/i2c.h"
#include <linux/module.h>
#include <linux/poll.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>

/* 主设备号                                                                 */
static int major = 0;
static struct class *my_i2c_class;

struct i2c_client *g_client;

static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);
struct fasync_struct *i2c_fasync;


/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t i2c_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;

	struct i2c_msg msgs[2];

	/* 初始化i2c_msg */

	err = i2c_transfer(g_client->adapter, msgs, 2);

	/* copy_to_user  */
	
	return 0;
}

static ssize_t i2c_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;

	/* copy_from_user  */


	struct i2c_msg msgs[2];

	/* 初始化i2c_msg */

	err = i2c_transfer(g_client->adapter, msgs, 2);

	
	return 0;    
}


static unsigned int i2c_drv_poll(struct file *fp, poll_table * wait)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	poll_wait(fp, &gpio_wait, wait);
	//return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
	return 0;
}

static int i2c_drv_fasync(int fd, struct file *file, int on)
{
	if (fasync_helper(fd, file, on, &i2c_fasync) >= 0)
		return 0;
	else
		return -EIO;
}


/* 定义自己的file_operations结构体                                              */
static struct file_operations i2c_drv_fops = {
	.owner	 = THIS_MODULE,
	.read    = i2c_drv_read,
	.write   = i2c_drv_write,
	.poll    = i2c_drv_poll,
	.fasync  = i2c_drv_fasync,
};


static int i2c_drv_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{
	// struct device_node *np = client->dev.of_node;   从client获取设备节点
	// struct i2c_adapter *adapter = client->adapter;  从client获取控制器

	/* 记录client */
	g_client = client;

	/* 注册字符设备 */
	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_i2c", &i2c_drv_fops);  /* /dev/gpio_desc */

	my_i2c_class = class_create(THIS_MODULE, "100ask_i2c_class");
	if (IS_ERR(my_i2c_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_i2c");
		return PTR_ERR(my_i2c_class);
	}

	device_create(my_i2c_class, NULL, MKDEV(major, 0), NULL, "myi2c"); /* /dev/myi2c */
	
	return 0;
}

static int i2c_drv_remove(struct i2c_client *client)
{
	/* 反注册字符设备 */
	device_destroy(my_i2c_class, MKDEV(major, 0));
	class_destroy(my_i2c_class);
	unregister_chrdev(major, "100ask_i2c");

	return 0;
}

static const struct of_device_id myi2c_dt_match[] = {
	{ .compatible = "100ask,i2cdev" },
	{},
};
static struct i2c_driver my_i2c_driver = {
	.driver = {
		   .name = "100ask_i2c_drv",
		   .owner = THIS_MODULE,
		   .of_match_table = myi2c_dt_match,
	},
	.probe = i2c_drv_probe,
	.remove = i2c_drv_remove,
};


static int __init i2c_drv_init(void)
{
	/* 注册i2c_driver */
	return i2c_add_driver(&my_i2c_driver);
}

static void __exit i2c_drv_exit(void)
{
	/* 反注册i2c_driver */
	i2c_del_driver(&my_i2c_driver);
}

/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(i2c_drv_init);
module_exit(i2c_drv_exit);

MODULE_LICENSE("GPL");


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

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

相关文章

自然语言处理: 第十章GPT的API使用

理论基础 现在的以GPT为首的生成类模型&#xff0c;它拥有对话的能力&#xff0c;它会根据你输入的暗示(prompt)或者指令(instruct)生成对应的回答。所以&#xff0c;不同的输入会导致不同的输出(其实由于chatgpt最终生成的答案是beam_search 以及随机采样的机制&#xff0c;所…

自定义Chronometer实现定时器

概述 自定义Chronometer实现定时器,引用方便&#xff0c;操作简单。 详细 前言 在Android开发过程中&#xff0c;计时控件是经常回使用到的&#xff0c;在Android控件库中有一个能快捷实现计时功能的控件&#xff0c;它就是Chronometer&#xff0c;今天我们基于它自定义实现…

LLM - Model Load_in_8bit For LLaMA

一.引言 LLM 量化是将大语言模型进行压缩和优化以减少其计算和存储需求的过程。 博主在使用 LLaMA-33B 时尝试使用量化加载模型&#xff0c;用传统 API 参数控制量化失败&#xff0c;改用其他依赖尝试成功。这里先铺下结论: ◆ Load_in_8bit ✔️ ◆ Load_in_4bit ❌ 二.LL…

基于SpringBoot+MybatisPlus+Shiro+mysql+redis智慧云智能教育平台

基于SpringBootMybatisPlusShiromysqlredis智慧云智能教育平台 一、系统介绍二、功能展示三.其他系统实现五.获取源码 一、系统介绍 声明&#xff1a;Java智慧云智能教育平台源码 前后端分离、 开发语言&#xff1a;JAVA 数据库&#xff1a;MySQL5.7以上 开发工具&#xff…

08 通过从 库1 复制 *.ibd 到 库2 导致 mysql 启动报错

前言 呵呵 最近同事有这样的一个需求 需要将 库1 的一张表 复制到 库2 然后 我想到了 之前一直使用的通过复制这个库的 data 文件来进行数据迁移的思路, 是需要复制这个 库对应的 data 目录下的数据文件, 以及 ibdata1 文件 然后 我又在想 这里的场景能否也使用这里的额方式…

基于树莓派设计的酒店房间号智能识别系统(图像识别)

基于树莓派4B设计的酒店房间号智能识别系统(图像识别) 一、设计需求 酒店房间识别系统的主要目的是:通过图像处理技术,以机器视觉的方式识别光学传感器传回的酒店房间图像中的数字,该系统可以以非接触的方式,以图像的方式获得需要的酒店房间读数,这能极大提高老式的酒店房…

ssp offer技巧 | 面试经验专栏介绍 | 金九银十

前言&#xff1a;欢迎来到我的面试经验专栏&#xff01;在这个专栏中&#xff0c;我将与大家分享我在多个领域的面试经验&#xff0c;涵盖Java、GO、操作系统、Mysql、计算机网络、Redis等领域。作为一名经验丰富的技术人员&#xff0c;我将通过文章的形式&#xff0c;为大家提…

基于SpringBoot的在线聊天系统

基于SpringBoot的在线聊天系统 一、系统介绍二、功能展示三.其他系统实现五.获取源码 一、系统介绍 源码编号&#xff1a;F-S03点击查看 项目类型&#xff1a;Java EE项目 项目名称&#xff1a;基于SpringBoot的在线聊天系统 项目架构&#xff1a;B/S架构 开发语言&#x…

【【萌新的STM32学习-16中断的基本介绍1】】

萌新的STM32学习-16中断的基本介绍1 中断 什么是中断 中断是打断CPU执行正常的程序&#xff0c;转而处理紧急程序&#xff0c;然后返回原暂停的程序继续执行&#xff0c;就叫中断 中断的作用 实时控制 &#xff1a; 就像对温度进行控制 故障控制 &#xff1a; 第一时间对突发情…

谷歌浏览器的受欢迎之谜:探析其引人入胜的特点

文章目录 &#x1f340;引言&#x1f340;1. 极速的浏览体验&#x1f340;2. 简洁直观的界面&#x1f340;3. 强大的同步功能&#x1f340;4. 丰富的扩展生态系统&#x1f340;5. 安全与隐私的关注&#x1f340;6. 持续的技术创新&#x1f340;7. 跨平台支持和云整合&#x1f3…

《机器学习核心技术》分类算法 - 决策树

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;小白零基础《Python入门到精通》 决策树 1、决策树API2、决策时实际应用2.1、获取数据集2.2、划分数据集2.3、决策…

网络摄像头:SparkoCam Crack

SparkoCam 网络摄像头软件 SparkoCam 是一款网络摄像头和视频效果软件&#xff0c;用于广播实时网络摄像头效果并将其应用到视频聊天和录音中。 使用佳能/尼康数码单反相机作为常规网络摄像头通过向实时视频聊天和视频录制添加酷炫的网络摄像头效果和图形来增强 USB 网络摄像…

前端(十五)——GitHub开源一个react封装的图片预览组件

&#x1f475;博主&#xff1a;小猫娃来啦 &#x1f475;文章核心&#xff1a;GitHub开源一个react封装的图片预览组件 文章目录 组件开源代码下载地址运行效果展示实现思路使用思路和api实现的功能数据和入口部分代码展示 组件开源代码下载地址 Gitee&#xff1a;点此跳转下载…

CSS中如何实现多行文本溢出省略号效果?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用text-overflow 和 overflow 属性⭐ 使用clamp() 函数⭐ 使用 JavaScript 或 CSS 框架⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到…

juc基础(四)

目录 一、ThreadPool 线程池 1、参数说明 2、拒绝策略 3、线程池种类 &#xff08;1&#xff09;newCachedThreadPool(常用) &#xff08;2&#xff09;newFixedThreadPool(常用) &#xff08;3&#xff09;newSingleThreadExecutor(常用) &#xff08;4&#xff09;ne…

Cocos独立游戏开发框架中的音频管理器

引言 本系列是《8年主程手把手打造Cocos独立游戏开发框架》&#xff0c;欢迎大家关注分享收藏订阅。在独立游戏开发中&#xff0c;音频不仅仅是视听体验的一部分&#xff0c;更是情感、氛围和互动的关键元素。然而&#xff0c;随着项目的复杂性增加&#xff0c;有效地管理和控…

用centos7镜像做yum仓库

用centos7镜像做yum仓库&#xff0c;公司全部服务器使用。 小白教程&#xff0c;一看就会&#xff0c;一做就成。 1.先下载对应版本的centos7的DVD版或Everything版 我用的是DVD的&#xff0c;比Everything版小&#xff0c;功能也挺全&#xff0c;这里里centos7.5的镜像做实验…

Linux驱动之设备树下的platform驱动

目录 一、设备树下的 platform 驱动简介 二、修改设备树文件 2.1 添加 LED 设备节点 2.2 添加 pinctrl 节点 2.3 检查 PIN 是否被其他外设使用 三、platform 驱动程序编写 四、测试 APP 编写 五、运行测试 5.1 编译 5.2 运行测试 前面一篇我们讲解了传统的、未采用设备…

Spring MVC:@RequestMapping

Spring MVC RequestMapping属性 RequestMapping RequestMapping&#xff0c; 是 Spring Web 应用程序中最常用的注解之一&#xff0c;主要用于映射 HTTP 请求 URL 与处理请求的处理器 Controller 方法上。使用 RequestMapping 注解可以方便地定义处理器 Controller 的方法来处…

【Mybatis】关联关系映射表对象之间的关系

目录 ​编辑 1.概述 ( 1 ) 介绍 2.一对一关联映射 2.1数据库表连接&#xff1a; 2.2配置文件&#xff1a; 2.3生成自动代码 2.4 编写测试 3. 一对多关联映射 4.多对多关联映射 1.概述 ( 1 ) 介绍 关联关系映射是指在数据库中&#xff0c;通过定义表之间的关联关系…