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 *)®_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安装包,可以直接安装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 总线(Inter-Integrated Circuit)中,SCL 和 SDA 是两条核心信号线&am…...

VUE:基于MVVN的前端js框架
文章目录 vue框架v-show vue框架 注意是 先写函数名,再写function。 handle:function (){}下面是错误的 function:handle(){}3 v-show 本质上等于号后面还是判断条件,所以不能写赋值语句,下面是正确的 下面是错误的 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…...

法语旅游常用口语-柯桥学外语到蓝天广场泓畅学校
以下是一些实用的法语旅游常用口语,帮助你在法国旅行时能够进行基本的交流: 问候与道别 Bonjour: 用于日常问候。Au revoir: 用于告别。 请求帮助 S’il vous plat: 用于请求帮助,例如在需要寻找某个地点或服务时。 询问信息 Excusez-moi: 用…...

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

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

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

Infineon(英飞凌) TLE985xQX 芯片电机工作电流、电压AD采样
其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 单片机芯片合集 文章目录 其他系列文章导航 文章目录 前言 一、选取合适的端口 1.通过 OP1、OP2 电流采集运放输入端口进行H桥驱动的电流采集。 2.通过 O_D_VBAT_AD_EN、I_A_VBAT_A…...
Sparrow系列拓展篇:对信号量应用问题的深入讨论
前言 笔者之前已经介绍过了Sparrow信号量的源码,但是对于信号量的使用,并没有讲得非常详细,仅仅讲了同步与互斥的概念。 本章让笔者介绍如何使用Sparrow的信号量,深入探讨一下信号量在同步、计数与互斥中的应用。 使用信号量解…...

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

Python编程艺术:优雅与实用的完美平衡(推导式)
在Python这门优雅的编程语言中,处处体现着"简洁即是美"的设计哲学。今天我们深入探讨Python中那些让代码更优雅、更高效的编程技巧,这些技巧不仅能提升代码的可读性,还能让编程过程充满乐趣。 列表推导式的魔力 Python的列表推导…...
Spring Boot框架Starter组件整理
在Spring Boot框架中,starter是一种预定义的依赖集合,旨在简化Maven或Gradle等构建工具中的依赖管理。每个starter都包含了实现特定功能所需的库和组件,以及相应的配置文件。开发者只需在项目中引入相应的starter依赖,即可快速搭建…...
C/C++基础知识复习(27)
1) 移动语义和拷贝语义的区别 拷贝语义和移动语义是C中对象所有权管理的两种机制,主要在对象初始化、赋值或传参时体现。 拷贝语义 (Copy Semantics) 行为:通过深拷贝或浅拷贝,创建一个新对象,并将原对象的值或资源复制到新对象…...

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

flume-将日志采集到hdfs
看到hdfs大家应该做什么? 是的你应该去把集群打开, cd /export/servers/hadoop/sbin 启动集群 ./start-all.sh 在虚拟机hadoop02和hadoop03上的conf目录下配置相同的日志采集方案,‘ cd /export/servers/flume/conf 切换完成之后&#…...
一文学习开源框架LeakCanary
LeakCanary 简介 LeakCanary 是一个由 Square 开发的开源工具,主要用于检测和诊断 Android 应用中的内存泄漏问题。它通过自动化的方式帮助开发者捕捉和分析可能导致内存泄漏的对象,简化了内存问题的排查过程。 LeakCanary 的功能 自动检测内存泄漏&a…...

jetson orin系列开发版安装cuda的gpu版本的opencv
opencv安装包下载地址: https://github.com/opencv/opencv/扩展库下载地址: https://github.com/opencv/opencv_contrib1. 删除jetpack包中的opencv版本 原先的opencv库安装在目录/usr/lib/aarch64-linux-gnu/下(一般其他的第三方库也都安…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...

微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...

计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
如何通过git命令查看项目连接的仓库地址?
要通过 Git 命令查看项目连接的仓库地址,您可以使用以下几种方法: 1. 查看所有远程仓库地址 使用 git remote -v 命令,它会显示项目中配置的所有远程仓库及其对应的 URL: git remote -v输出示例: origin https://…...

java 局域网 rtsp 取流 WebSocket 推送到前端显示 低延迟
众所周知 摄像头取流推流显示前端延迟大 传统方法是服务器取摄像头的rtsp流 然后客户端连服务器 中转多了,延迟一定不小。 假设相机没有专网 公网 1相机自带推流 直接推送到云服务器 然后客户端拉去 2相机只有rtsp ,边缘服务器拉流推送到云服务器 …...

SQLSERVER-DB操作记录
在SQL Server中,将查询结果放入一张新表可以通过几种方法实现。 方法1:使用SELECT INTO语句 SELECT INTO 语句可以直接将查询结果作为一个新表创建出来。这个新表的结构(包括列名和数据类型)将与查询结果匹配。 SELECT * INTO 新…...

浏览器工作原理01 [#]Chrome架构:仅仅打开了1个页面,为什么有4个进程
引用 浏览器工作原理与实践 Chrome打开一个页面需要启动多少进程?你可以点击Chrome浏览器右上角的“选项”菜单,选择“更多工具”子菜单,点击“任务管理器”,这将打开Chrome的任务管理器的窗口,如下图 和Windows任务管…...