当前位置: 首页 > news >正文

linux下i2c开发与框架源码分析

目录

1 概述

2 I2c子系统框架

3 I2C的使用流程

3.1 在驱动里使用

3.2 在应用层使用

3.3 I2ctool的使用

4 为硬件i2c注册一个适配器

5 i2c子系统源码流程分析

5.1 i2c device与driver绑定过程

5.1.1 Driver的注册与处理

5.1.2 Client device的生成

5.2 I2c的发送与接收

5.3 i2c总线设备生成过程

6 i2c作为slave的使用


1 概述

        本文主要描述了i2c的使用以及分析了大部分i2c子系统源码和实现原理,本文以读者对i2c硬件原理已掌握为基础来描述,需要读者理解基础的i2c通信过程。

2 I2c子系统框架

        从分层角度上看,i2c子系统大致分为设备驱动层client、i2c核心层和i2c适配器层,如下图大致描述了整个i2c应用和内核框架的关系逻辑,从上到下,用户可通过底层提供的总线设备或者外设设备来访问挂载在总线上的i2c设备。

        i2c子系统向驱动层提供了i2c client,每一个i2c设备将被实现成一个client,设备驱动在拿到i2c_client后,即可通过该对象来读写i2c数据访问i2c设备。I2c核心层向下也提供了i2c适配器层,每一个硬件i2c都被实现成一个i2c adapter,主要负责向i2c核心层提供硬件操作接口

3 I2C的使用流程

        本节讲i2c在驱动层如何被调用使用,没有特殊说明,均认为i2c作为master,后面章节将介绍i2c作为slave的用法。

3.1 在驱动里使用

        在i2c的驱动应用中,比较常见的是先在设备树里的i2c节点挂在硬件上挂在到该i2c总线的i2c设备,例如一个sensor的节点:

&i2c2 {status = "okay";
...ov5695: ov5695@36 {compatible = "ovti,ov5695";reg = <0x36>;avdd-supply = <&vcc2v8_dvp>;clocks = <&cru SCLK_CIF_OUT>;clock-names = "xvclk";dvdd-supply = <&vcc1v5_dvp>;dovdd-supply = <&vcc1v8_dvp>;pinctrl-names = "default";pinctrl-0 = <&cif_clkout_m0 &mipi_pdn>;reset-gpios = <&gpio2 RK_PB6 GPIO_ACTIVE_LOW>;...};
};

        然后在sensor的驱动里,调用i2c_register_driver将自己定义好的struct i2c_driver传入该接口,i2c总线将会调用我们自定义好的probe函数,让设备驱动程序加载起来,有时也会用i2c注册宏module_i2c_driver来做。

static struct i2c_driver ov5695_i2c_driver = {.driver = {.name = "ov5695",.pm = &ov5695_pm_ops,.of_match_table = of_match_ptr(ov5695_of_match),},.probe      = ov5695_probe,.remove     = ov5695_remove,
};
module_i2c_driver(ov5695_i2c_driver);
----------------------或者-----------------------
static int mpu6050_driver_init(void)
{i2c_add_driver(&mpu6050_driver);return 0;
}
static void mpu6050_driver_exit(void)
{i2c_del_driver(&mpu6050_driver);
}
module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit);

        之后将进入probe函数,传入的i2c_client结构体,用于i2c数据收发

static int ov5695_probe(struct i2c_client *client)
{
}
static int ov5695_read_reg(struct i2c_client *client, u16 reg, unsigned int len, u32 *val)
{struct i2c_msg msgs[2];...int ret;if (len > 4)return -EINVAL;data_be_p = (u8 *)&data_be;/* Write register address */msgs[0].addr = client->addr;msgs[0].flags = 0;msgs[0].len = 2;msgs[0].buf = (u8 *)&reg_addr_be;/* Read data from register */msgs[1].addr = client->addr;msgs[1].flags = I2C_M_RD;msgs[1].len = len;msgs[1].buf = &data_be_p[4 - len];ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));...return 0;
}

3.2 在应用层使用

        I2c子系统同时提供了每个i2c总线的设备,应用可以直接打开i2c总线设备,传入从机地址来进行通信

int fd = open("/dev/i2c-0", O_RDWR);
int i2c_write(uint8_t slave, uint8_t reg, uint8_t * data, int len)
{unsigned char buf[1024];struct i2c_rdwr_ioctl_data i2c_data;struct i2c_msg i2c_msg;i2c_data.nmsgs = 1;i2c_data.msgs = &i2c_msg ;ioctl(_fd, I2C_TIMEOUT, 1);ioctl(_fd, I2C_RETRIES, 2);memset(buf, 0, 1024);buf[0] = reg;memcpy(&buf[1], buf, len);i2c_data.msgs[0].addr = slave;i2c_data.msgs[0].flags = 0; i2c_data.msgs[0].buf = &buf[0];i2c_data.msgs[0].len = len+1;int ret = ioctl(fd, I2C_RDWR, (unsigned long)&i2c_data);...return 0;
}
int i2c_read(int8_t slave, int8_t reg, uint8_t * data, int len)unsigned char buf[2];struct i2c_rdwr_ioctl_data i2c_data;struct i2c_msg i2c_msg[2] ;i2c_data.nmsgs = 2;i2c_data.msgs = &i2c_msg[0] ;ioctl(_fd, I2C_TIMEOUT, 1);ioctl(_fd, I2C_RETRIES, 2);buf[0] = reg ;i2c_data.msgs[0].addr = slave;i2c_data.msgs[0].flags = 0;     i2c_data.msgs[0].buf = &buf[0];i2c_data.msgs[0].len = 1;i2c_data.msgs[1].addr = slave;i2c_data.msgs[1].flags = 1;    i2c_data.msgs[1].buf = data;i2c_data.msgs[1].len = len;int ret = ioctl(_fd, I2C_RDWR, (unsigned long)&i2c_data);....
}
close(fd);

3.3 I2ctool的使用

        有时在调试时,会使用i2ctool命令行进行测试,使用这些命令行接口,需要先把该库打包进行文件系统,或者移植。

        i2cdetect:用于扫描 i2c 总线上的设备,并显示地址。
        i2cset:设置i2c设备某个寄存器的值。
        i2cget:读取i2c设备某个寄存器的值。
        i2cdump:读取某个i2c设备所有寄存器的值。
        i2ctransfer:一次性读写多个字节。

注:参考https://blog.csdn.net/yyz_1987/article/details/131953108

        驱动中常用接口:

        //发送接收消息

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
static inline int i2c_master_recv(const struct i2c_client *client,char *buf, int count)
static inline int i2c_master_send(const struct i2c_client *client,const char *buf, int count)

        //注册与注销:

#define i2c_add_driver(driver) \i2c_register_driver(THIS_MODULE, driver)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver) 
void i2c_del_driver(struct i2c_driver *driver)

        另外i2c还提供了SMBus设备相关接口,不在此文讨论范围。

4 为硬件i2c注册一个适配器

        如何注册一个i2c adapter

        以rockchip为例子,分析rockchip注册一个adapter源码,实现在drivers/i2c/busses/i2c-rk3x.c

设备树对i2c2的定义如下:

i2c2: i2c@ff1a0000 {compatible = "rockchip,px30-i2c", "rockchip,rk3399-i2c";reg = <0x0 0xff1a0000 0x0 0x1000>;clocks = <&cru SCLK_I2C2>, <&cru PCLK_I2C2>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>;pinctrl-names = "default";pinctrl-0 = <&i2c2_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};

        将和驱动匹配

static struct platform_driver rk3x_i2c_driver = {.probe   = rk3x_i2c_probe,.remove_new = rk3x_i2c_remove,.driver  = {.name  = "rk3x-i2c",.of_match_table = rk3x_i2c_match,.pm = &rk3x_i2c_pm_ops,},
};
module_platform_driver(rk3x_i2c_driver);

        注册过程:

rk3x_i2c_probe
struct rk3x_i2c *i2c;
//adapter结构体的初始化,其中rk3x_i2c_algorithm是硬件接口,后面分析strscpy(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设备节点的信息,包括基地址、中断申请、时钟获取和配置等
i2c->regs = devm_platform_ioremap_resource(pdev, 0);
ret = devm_request_irq(&pdev->dev, irq, rk3x_i2c_irq,0, dev_name(&pdev->dev), i2c);
ret = clk_prepare(i2c->clk);
//最后调用i2c子系统api,注册一个adapter
ret = i2c_add_adapter(&i2c->adap);
------------------------------------------------------------------------------
注册到adapter的硬件回调如下,这些将提供给i2c子系统操作硬件的接口,主要是发送与接收数据
static const struct i2c_algorithm rk3x_i2c_algorithm = {.master_xfer        = rk3x_i2c_xfer,//发送与接收.master_xfer_atomic = rk3x_i2c_xfer_polling,//原子发送接收.functionality      = rk3x_i2c_func,//当前i2c 适配器支持哪些特性
};

        以上便是一个i2c adapter的注册过程,比较简单,需要注意的是,在设备树定义了多个i2c节点是,这个driver将被调用多次,即将申请多个adapter,这里一个硬件i2c就申请一个adapter。

i2c0: i2c@ff180000 {compatible = "rockchip,px30-i2c", "rockchip,rk3399-i2c";...};i2c1: i2c@ff190000 {compatible = "rockchip,px30-i2c", "rockchip,rk3399-i2c";....};i2c2: i2c@ff1a0000 {compatible = "rockchip,px30-i2c", "rockchip,rk3399-i2c";...};i2c3: i2c@ff1b0000 {compatible = "rockchip,px30-i2c", "rockchip,rk3399-i2c";....};

        其他都是I2C硬件接口配置过程,不做详细分析。

5 i2c子系统源码流程分析

        I2c子系统源码实现在Linux内核的drivers/i2c下,这里有芯片产商文件,也有i2c子系统核心文件:

        drivers/i2c/i2c-boardinfo.c//i2c ip信息整理接口

        drivers/i2c/i2c-core-acpi.c//acpi设备接口

        drivers/i2c/i2c-core-base.c//核心文件

        drivers/i2c/i2c-core-of.c//设备树解析相关文件

        drivers/i2c/i2c-core-slave.c//i2c从机接口

        drivers/i2c/i2c-core-smbus.c/smbus设备相关接口

        drivers/i2c/i2c-dev.c//i2c总线设备相关文件

5.1 i2c device与driver绑定过程

        I2c子系统如何生成client device与client驱动匹配后调用client的probe函数的流程

5.1.1 Driver的注册与处理

        以sensor节点为例子,在设备树中,有如下设备树i2c定义,i2c节点相关信息定义还有定义在i2c2下的ov5695节点。

 i2c2: i2c@ff1a0000 {compatible = "rockchip,px30-i2c", "rockchip,rk3399-i2c";reg = <0x0 0xff1a0000 0x0 0x1000>;clocks = <&cru SCLK_I2C2>, <&cru PCLK_I2C2>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>;pinctrl-names = "default";pinctrl-0 = <&i2c2_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};
&i2c2 {...ov5695: ov5695@36 {compatible = "ovti,ov5695";reg = <0x36>;avdd-supply = <&vcc2v8_dvp>;clocks = <&cru SCLK_CIF_OUT>;clock-names = "xvclk";dvdd-supply = <&vcc1v5_dvp>;dovdd-supply = <&vcc1v8_dvp>;pinctrl-names = "default";pinctrl-0 = <&cif_clkout_m0 &mipi_pdn>;reset-gpios = <&gpio2 RK_PB6 GPIO_ACTIVE_LOW>;...};
};

        在Linux初始化的时候,这里会被Linux解析成两个device,且i2c2这个device下挂着ov5695这个device,ov5695的driver定义在drivers/media/i2c/ov5695.c,看驱动注册部分:

static struct i2c_driver ov5695_i2c_driver = {.driver = {.name = "ov5695",.pm = &ov5695_pm_ops,.of_match_table = of_match_ptr(ov5695_of_match),},.probe      = ov5695_probe,.remove     = ov5695_remove,
};
module_i2c_driver(ov5695_i2c_driver);
MODULE_DESCRIPTION("OmniVision ov5695 sensor driver");
MODULE_LICENSE("GPL v2");

        定义了一个name为ov5695 的sensor driver,通过module_i2c_driver 注册成一个driver,module_i2c_driver 声明

clude/linux/i2c.h
#define module_i2c_driver(__i2c_driver) \module_driver(__i2c_driver, i2c_add_driver, \i2c_del_driver)
#define i2c_add_driver(driver) \i2c_register_driver(THIS_MODULE, driver)include/linux/device/driver.h
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
整理如上宏定义,最后调用方式
static int __init ov9282_driver_init(void) 
{ return i2c_register_driver(THIS_MODULE, &(ov9282_driver)); 
} 
module_init(ov9282_driver_init);
static void __exit ov9282_driver_exit(void) 
{ i2c_del_driver(&(ov9282_driver)); 
}
module_exit(ov9282_driver_exit);

        即最后调用i2c_register_driver 来注册,看这个函数如何注册一个driver的:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)(drivers/i2c/i2c-core-base.c)driver->driver.owner = owner;driver->driver.bus = &i2c_bus_type;//选择总线,该总线在i2c子系统初始化时加载INIT_LIST_HEAD(&driver->clients);
res = driver_register(&driver->driver);//将该driver注册到i2c_bus_type这个总线上。i2c_for_each_dev(driver, __process_new_driver);//检查是否已有对应device,有则扫描该i2c设备是否在线。

        I2c子系统对driver的注册比较简单,整体来说就是将driver注册到名为i2c_bus_type 的总线上,这条总线是在i2c子系统初始化的时候注册的:

struct bus_type i2c_bus_type = {.name = "i2c",.match = i2c_device_match,.probe = i2c_device_probe,.remove = i2c_device_remove,.shutdown = i2c_device_shutdown,
};
static int __init i2c_init(void) {(drivers/i2c/i2c-core-base.c)retval = bus_register(&i2c_bus_type);//注册一条platform总线
...
retval = i2c_add_driver(&dummy_driver);//添加一个dummy driver,没有做任操作,用来匹配adapter注册时生成的device
}
postcore_initcall(i2c_init);
module_exit(i2c_exit);

一旦总线匹配到device和对应driver,probe将被调用:

static int i2c_device_probe(struct device *dev) {(drivers/i2c/i2c-core-base.c)
struct i2c_client *client = i2c_verify_client(dev);//拿到dev绑定的clientif (driver->probe)status = driver->probe(client);//调用driver的probe,并传入client,这里将调用ov5695里probe函数elsestatus = -EINVAL;
}

        上面分析了driver是如何注册以及如何被调用,client如何传进driver的probe函数,接下来分析i2c子系统是如何生成对应的device,以匹配bus上的driver调用driver的probe函数的。

5.1.2 Client device的生成

        来看此时i2c子系统是如何解析这个层级关系,以及client如何生成。I2c子系统在添加一个adapter的时候,就会轮询该i2c节点下所有的节点,逐一生成client和device的。

int i2c_add_adapter(struct i2c_adapter *adapter)(drivers/i2c/i2c-core-base.c)struct device *dev = &adapter->dev;//如下主要寻找i2c adapter记录在i2c_adapter_idr的序号,如果用的是设备节点的方式,则进如下if分支if (dev->of_node) {id = of_alias_get_id(dev->of_node, "i2c");if (id >= 0) {adapter->nr = id;return __i2c_add_numbered_adapter(adapter);}}
//如果用不是设备树方式或者第一次创建,将用随机分配id的方式进行id = idr_alloc(&i2c_adapter_idr, adapter, __i2c_first_dynamic_bus_num, 0,GFP_KERNEL);return i2c_register_adapter(adapter);
=>__i2c_add_numbered_adapter(这里分析的是使用设备树方式)
记录adapter到i2c_adapter_idr后,调用i2c_register_adapter
==>i2c_register_adapterdev_set_name(&adap->dev, "i2c-%d", adap->nr);//生成一个i2c-x的device,匹配bus注册是dummy driveradap->dev.bus = &i2c_bus_type;adap->dev.type = &i2c_adapter_type;res = device_register(&adap->dev);//注册到i2c_bus_type总线。
===>of_i2c_register_devices(drivers/i2c/i2c-core-of.c)
bus = of_node_get(adap->dev.of_node);//找到parent节点,即i2c2这个节点for_each_available_child_of_node(bus, node) {//轮询i2c2节点下的所有子节点if (of_node_test_and_set_flag(node, OF_POPULATED))//该子节点是否被其他驱动使用了continue;client = of_i2c_register_device(adap, node);//注册一个client device}of_node_put(bus);
====>of_i2c_register_device(drivers/i2c/i2c-core-of.c)ret = of_i2c_get_board_info(&adap->dev, node, &info);//获取节点的数据client = i2c_new_client_device(adap, &info);//生成client device
=====>of_i2c_get_board_info
//找出node这个子节点,compatible标签里的值,并去掉该值‘,’前面的值,取后面的值,在这里取到了ovti,ov5695,只取,	//后面的值,最后type的值是ov5695if (of_alias_from_compatible(node, info->type, sizeof(info->type)) < 0) {dev_err(dev, "of_i2c: modalias failure on %pOF\n", node);return -EINVAL;
}
ret = of_property_read_u32(node, "reg", &addr);//读取i2c设备的从机地址=====>i2c_new_client_device(drivers/i2c/i2c-core-base.c)struct i2c_client *client;client = kzalloc(sizeof *client, GFP_KERNEL);//申请一个i2c_client
//后续有很多client数据填充
client->adapter = adap;
...
i2c_check_addr_validity(client->addr, client->flags);//检查地址有效性,必须是7bit地址i2c_check_addr_busy(adap, i2c_encode_flags_to_addr(client));//检查i2c设备是否在线
//如下是重点,配置dev的属性client->dev.parent = &client->adapter->dev;client->dev.bus = &i2c_bus_type;//该dev要挂载的总线,跟之前driver挂在在同一个总线上client->dev.type = &i2c_client_type;//将ov5695节点赋值到dev下,同时和driver的compitable	一致,所以这里将和上面注册driver匹配
client->dev.of_node = of_node_get(info->of_node);client->dev.fwnode = info->fwnode;
i2c_dev_set_name(adap, client, info);//设置device的名称status = device_register(&client->dev);//注册到总线,此时driver的probe函数将被调用。

        如上分析了client device的生成以及如何和driver匹配,使得driver驱动被加载。

5.2 I2c的发送与接收

        I2c的接收发送接口,最终都调用到adapter提供的硬件通信接口

>i2c_master_send(include/linux/i2c.h)
=>i2c_transfer_buffer_flags(drivers/i2c/i2c-core-base.c)
组装msg
struct i2c_msg msg = {.addr = client->addr,.flags = flags | (client->flags & I2C_M_TEN),.len = count,.buf = buf,};
==>i2c_transfer
申请总线锁
===>__i2c_transfer
分原子操作和非原子操作接口,取决于硬件adapter是否实现了原子操作
adap->algo->master_xfer_atomic
adap->algo->master_xfer adapter的硬件接口
--------------------------------------------------------
>i2c_transfer
=>__i2c_transfer
adap->algo->master_xfer adapter的硬件接口

5.3 i2c总线设备生成过程

        总线设备的生成,主要实现在drivers/i2c/i2c-dev.c这个文件里。


static int __init i2c_dev_init(void) {...res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");//申请设备号范围i2c_dev_class = class_create("i2c-dev");//创建class...
//如果有设备注册到总线i2c_bus_type,i2cdev_notifier会立即被回调(回调下面分析)res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);...
//这里实际上跟上一句话目的一样,如果此时已经有设备注册,则轮询所有已注册设备,绑定adapter创建总线设备。i2c_for_each_dev(NULL, i2c_dev_attach_adapter);...
}
module_init(i2c_dev_init);

        回调过程:

i2c_dev_attach_adapter / i2cdev_notifier_call->i2cdev_attach_adapter
=>i2cdev_attach_adapter
i2c_dev = get_free_i2c_dev(adap);//获取未注册过的i2c dev
cdev_init(&i2c_dev->cdev, &i2cdev_fops);//初始化操作集相关
res = dev_set_name(&i2c_dev->dev, "i2c-%d", adap->nr);// /dev下的设备名称,nr是适配器序号,即设备树i2cxres = cdev_device_add(&i2c_dev->cdev, &i2c_dev->dev);//生成该设备
其中操作集的定义如下:
static const struct file_operations i2cdev_fops = {.owner = THIS_MODULE,.llseek = no_llseek,.read = i2cdev_read,.write = i2cdev_write,.unlocked_ioctl = i2cdev_ioctl,.compat_ioctl = compat_i2cdev_ioctl,.open = i2cdev_open,.release = i2cdev_release,
};
操作集都将调用i2c子系统的发送接收接口,最终调用adapter的硬件接口
---------------
i2cdev_read
=>i2c_master_recv
-------------
i2cdev_write
=>i2c_master_send
-------------
i2cdev_ioctl
=>i2cdev_ioctl_rdwr
==>i2c_transfer

6 i2c作为slave的使用

        I2c子系统对salve的实现是在drivers/i2c/i2c-core-slave.c,提供了三个api。

        注册一个slave

int i2c_slave_register(struct i2c_client *client, i2c_slave_cb_t slave_cb)

        释放

int i2c_slave_unregister(struct i2c_client *client)

        有数据请求时,将被调用,由控制器驱动实现,最终调用用户在i2c_slave_register注册的slave_cb回调。

int i2c_slave_event(struct i2c_client *client,enum i2c_slave_event event, u8 *val)

检查当前设备树配置的设备是否是从机设备

bool i2c_detect_slave_mode(struct device *dev)

相关文章:

linux下i2c开发与框架源码分析

目录 1 概述 2 I2c子系统框架 3 I2C的使用流程 3.1 在驱动里使用 3.2 在应用层使用 3.3 I2ctool的使用 4 为硬件i2c注册一个适配器 5 i2c子系统源码流程分析 5.1 i2c device与driver绑定过程 5.1.1 Driver的注册与处理 5.1.2 Client device的生成 5.2 I2c的发送与接…...

[ruby on rails] 安装docker

1. docker安装 ubuntu14.04后自带docker安装包&#xff0c;可以直接安装docker.io sudo apt-get updatesudo apt-get install -y docker.io # 安装后启动sudo service docker start最新版本docker 安装docker-ce # 官方源 curl -fsSL https://download.docker.com/linux/ubun…...

I2C学习

详情学习 12. I2C通讯 — [野火]Linux基础与应用开发实战指南——基于LubanCat-RK系列板卡 文档 (embedfire.com) 问题 i2c总线中scl和sda两条线的作用以及区别 在 I2C 总线&#xff08;Inter-Integrated Circuit&#xff09;中&#xff0c;SCL 和 SDA 是两条核心信号线&am…...

VUE:基于MVVN的前端js框架

文章目录 vue框架v-show vue框架 注意是 先写函数名&#xff0c;再写function。 handle:function (){}下面是错误的 function:handle(){}3 v-show 本质上等于号后面还是判断条件&#xff0c;所以不能写赋值语句&#xff0c;下面是正确的 下面是错误的 v-show " ge…...

06、Spring AOP

在我们接下来聊Spring AOP之前我们先了解一下设计模式中的代理模式。 一、代理模式 代理模式是23种设计模式中的一种,它属于结构型设计模式。 对于代理模式的理解: 程序中对象A与对象B无法直接交互,如:有人要找某个公司的老总得先打前台登记传达程序中某个功能需要在原基…...

c语言学习26字符串的应用

字符串在stm32串口中的应用 串口控制流水灯 pc通过串口发送字符串命令控制流水灯 open 流水灯打开 close 流水灯关闭 speed 1~9速度控制 if(strcmp((char *)usart1_rec_buff,"open")0) { led_flag 1; } else if(strcmp((char *)usart1_rec_buff,"close&qu…...

法语旅游常用口语-柯桥学外语到蓝天广场泓畅学校

以下是一些实用的法语旅游常用口语&#xff0c;帮助你在法国旅行时能够进行基本的交流&#xff1a; 问候与道别 Bonjour: 用于日常问候。Au revoir: 用于告别。 请求帮助 S’il vous plat: 用于请求帮助&#xff0c;例如在需要寻找某个地点或服务时。 询问信息 Excusez-moi: 用…...

Kafka 生产者优化与数据处理经验

Kafka&#xff1a;分布式消息系统的核心原理与安装部署-CSDN博客 自定义 Kafka 脚本 kf-use.sh 的解析与功能与应用示例-CSDN博客 Kafka 生产者全面解析&#xff1a;从基础原理到高级实践-CSDN博客 Kafka 生产者优化与数据处理经验-CSDN博客 Kafka 工作流程解析&#xff1a…...

MySQL 主从复制之多线程复制

一、MySQL 多线程复制的背景 MySQL 的主从复制延迟一直是受开发者最为关注的问题之一&#xff0c;MySQL 从 5.6 版本开始追加了并行复制功能&#xff0c;目的就是为了改善复制延迟问题&#xff0c;并行复制称为enhanced multi-threaded slave&#xff08;简称MTS&#xff09;。…...

Linux2.6内核进程调度队列

文章目录 前言运行队列 runqueue优先级活动队列过期队列活跃队列 VS 过期队列active指针和expired指针O(1)调度算法 前言 在前面学习并认识了进程之后&#xff0c;我们会发出一个疑问&#xff1a;Linux内核是如何调度进程的呢&#xff1f; 接下来我们就以Linux2.6内核为例深入探…...

Infineon(英飞凌) TLE985xQX 芯片电机工作电流、电压AD采样

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 单片机芯片合集 文章目录 其他系列文章导航 文章目录 前言 一、选取合适的端口 1.通过 OP1、OP2 电流采集运放输入端口进行H桥驱动的电流采集。 2.通过 O_D_VBAT_AD_EN、I_A_VBAT_A…...

Sparrow系列拓展篇:对信号量应用问题的深入讨论

前言 笔者之前已经介绍过了Sparrow信号量的源码&#xff0c;但是对于信号量的使用&#xff0c;并没有讲得非常详细&#xff0c;仅仅讲了同步与互斥的概念。 本章让笔者介绍如何使用Sparrow的信号量&#xff0c;深入探讨一下信号量在同步、计数与互斥中的应用。 使用信号量解…...

图文详解Docker下配置、测试Redis

文章目录 前言实测环境&#xff1a;实验思路&#xff1a; 正文1.准备工作2. 配置、运行 Redis 容器3. 配置测试 总结 前言 配置、测试redis数据库服务器&#xff0c;首先确保正确安装docker&#xff0c;并且已启动运行&#xff0c;具体安装docker方法见笔者前面的博文《OpenEu…...

Python编程艺术:优雅与实用的完美平衡(推导式)

在Python这门优雅的编程语言中&#xff0c;处处体现着"简洁即是美"的设计哲学。今天我们深入探讨Python中那些让代码更优雅、更高效的编程技巧&#xff0c;这些技巧不仅能提升代码的可读性&#xff0c;还能让编程过程充满乐趣。 列表推导式的魔力 Python的列表推导…...

Spring Boot框架Starter组件整理

在Spring Boot框架中&#xff0c;starter是一种预定义的依赖集合&#xff0c;旨在简化Maven或Gradle等构建工具中的依赖管理。每个starter都包含了实现特定功能所需的库和组件&#xff0c;以及相应的配置文件。开发者只需在项目中引入相应的starter依赖&#xff0c;即可快速搭建…...

C/C++基础知识复习(27)

1) 移动语义和拷贝语义的区别 拷贝语义和移动语义是C中对象所有权管理的两种机制&#xff0c;主要在对象初始化、赋值或传参时体现。 拷贝语义 (Copy Semantics) 行为&#xff1a;通过深拷贝或浅拷贝&#xff0c;创建一个新对象&#xff0c;并将原对象的值或资源复制到新对象…...

IEC61850实现方案和测试-2

IEC61850实现方案和测试-1作为介绍实现方案和测试的第二篇文章&#xff0c;后续会继续更新&#xff0c;欢迎关注。 第一篇是&#xff1a;IEC61850实现方案和测试-1-CSDN博客 UCA详细测试用例下载&#xff1a; 链接: https://pan.baidu.com/s/1TTMlYRfzKITgrkWwwtcrDg 提取码:…...

flume-将日志采集到hdfs

看到hdfs大家应该做什么&#xff1f; 是的你应该去把集群打开&#xff0c; cd /export/servers/hadoop/sbin 启动集群 ./start-all.sh 在虚拟机hadoop02和hadoop03上的conf目录下配置相同的日志采集方案&#xff0c;‘ cd /export/servers/flume/conf 切换完成之后&#…...

一文学习开源框架LeakCanary

LeakCanary 简介 LeakCanary 是一个由 Square 开发的开源工具&#xff0c;主要用于检测和诊断 Android 应用中的内存泄漏问题。它通过自动化的方式帮助开发者捕捉和分析可能导致内存泄漏的对象&#xff0c;简化了内存问题的排查过程。 LeakCanary 的功能 自动检测内存泄漏&a…...

jetson orin系列开发版安装cuda的gpu版本的opencv

opencv安装包下载地址&#xff1a; https://github.com/opencv/opencv/扩展库下载地址&#xff1a; https://github.com/opencv/opencv_contrib1. 删除jetpack包中的opencv版本 原先的opencv库安装在目录/usr/lib/aarch64-linux-gnu/下&#xff08;一般其他的第三方库也都安…...

数据结构-8.Java. 七大排序算法(中篇)

本篇博客给大家带来的是排序的知识点, 由于时间有限, 分两天来写, 中篇主要实现后三种排序算法: 冒泡排序,快速排序,下一篇讲 归并排序. 文章专栏: Java-数据结构 若有问题 评论区见 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作…...

数据结构C语言描述4(图文结合)--栈的实现,中序转后序表达式的实现

前言 这个专栏将会用纯C实现常用的数据结构和简单的算法&#xff1b;有C基础即可跟着学习&#xff0c;代码均可运行&#xff1b;准备考研的也可跟着写&#xff0c;个人感觉&#xff0c;如果时间充裕&#xff0c;手写一遍比看书、刷题管用很多&#xff0c;这也是本人采用纯C语言…...

python基本数据类型 -- 元组tuple

在 Python 中&#xff0c;元组&#xff08;Tuple&#xff09;是一种轻量级的、不可变的数据结构。与列表类似&#xff0c;元组用于存储有序的数据集合&#xff0c;但它一旦创建&#xff0c;其内容就无法更改。这种特性让元组在某些场景下更加安全和高效。本文将从定义、操作、应…...

tcpdump交叉编译

TCPDUMP在Libpcap上开发。 首先需要编译libcap。 网上那么多教程&#xff0c;下载地址都只给了一个英文的官网首页&#xff0c; 你尽可以试试&#xff0c;从里面找到下载地址都要费半天时间。 \color{red}网上那么多教程&#xff0c;下载地址都只给了一个英文的官网首页&#…...

Spring IOC注入方式、Bean作用域

Spring IOC注入 手动注入 set方法注入 需要提供set方法 public class UserService {private UserDao userDao; ​public void setUserDao(UserDao userDao) {this.userDao userDao;} } 设置属性字段的值 <bean id"userService" class"com.shsxt.servi…...

uniapp微信小程序转发跳转指定页面

onShareAppMessage 是微信小程序中的一个重要函数&#xff0c;用于自定义转发内容。当用户点击右上角的菜单按钮&#xff0c;并选择“转发”时&#xff0c;会触发这个函数。开发者可以在这个函数中返回一个对象&#xff0c;用于定义分享卡片的标题、图片、路径等信息。 使用场…...

利用uniapp开发鸿蒙:运行到鸿蒙模拟器—踩坑合集

从uniapp运行到鸿蒙模拟器上这一步&#xff0c;就有非常多的坑&#xff0c;一些常见的坑&#xff0c;官网都有介绍&#xff0c;就不再拿出来了&#xff0c;这里记录一下官网未记录的大坑 1.运行路径从hbuilderx启动鸿蒙模拟器 解决方法&#xff1a; Windows系统&#xff0c;官…...

【Vue】Vue3.0(二十五)Vue3.0中的具名插槽 的概念和使用场景

上篇文章 【Vue】Vue3.0&#xff08;二十四&#xff09;Vue3.0中 r e f s 、 refs 、 refs、parent 的概念和使用场景 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Vue专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年11月20日16点30分 …...

【pytorch-02】:张量的索引、形状操作和常见运算函数

文章目录 1 张量索引1.1 简单行列索引和列表索引1.2 布尔索引和多维索引 2 张量的形状操作2.1 reshape函数2.2 transpose和permute函数的使用2.3 view和contiguous函数2.4 squeeze和unsqueeze函数用法2.5 张量更改形状小结 3 常见运算函数 1 张量索引 1.1 简单行列索引和列表索…...

C语言-指针作为函数返回值及二级指针

1、指针作为函数返回值 c语言允许函数的返回值是一个指针&#xff08;地址&#xff09;我们将这样的函数称为指针函数&#xff0c;下面的例子定义一了一个函数strlong&#xff08;&#xff09;&#xff0c;用来返回两个字符串中较长的一个&#xff1a; 1. #include <stdio…...