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绑定的client
  if (driver->probe)
    status = driver->probe(client);//调用driver的probe,并传入client,这里将调用ov5695里probe函数
  else
    status = -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_adapter
  	dev_set_name(&adap->dev, "i2c-%d", adap->nr);//生成一个i2c-x的device,匹配bus注册是dummy driver
  	adap->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的值是ov5695
    	if (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是适配器序号,即设备树i2cx
 	res = 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)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

学习路之phpstudy--安装mysql5.7后在my.ini文件中无法修改sql_mode

windows环境下使用phpstudy安装mysql5.7后需要修改mysql中的sql_mode配置&#xff0c;但是在phpstudy中打开mysql配置文件my.ini后&#xff0c; 通过查找找不到sql_mode或sql-mode&#xff0c; 此时无法在my.ini文件中直接进行修改&#xff0c;可以使用mysql命令进行修改&#…

【大数据学习 | Spark-Core】详解分区个数

RDD默认带有分区的&#xff0c;那么创建完毕rdd以后他的分区数量是多少&#xff1f; 从hdfs读取文件的方式是最正规的方式&#xff0c;我们通过计算原理可以推出blk的个数和分区数量是一致的&#xff0c;本地化计算。 我们可以发现数据的读取使用的是textInputFormat&#xff…

前端常用内容

Style 1. 文本左对齐 style"text-align: left;" 2. 文本居中 style"text-align: center;" 3. 文本右对齐 style"text-align: right;"margin 属性可以设置以下四种类型的外边距&#xff1a; 1. 单一值&#xff1a;为所有四个方向&#xff08;上、…

免费微调自己的大模型(llama-factory微调llama3.1-8b)

目录 1. 名词/工具解释2. 微调过程3. 总结 本文主要介绍通过llama-factory框架&#xff0c;使用Lora微调方法&#xff0c;微调meta开源的llama3.1-8b模型&#xff0c;平台使用的是趋动云GPU算力资源。 微调已经经过预训练的大模型目的是&#xff0c;通过调整模型参数和不断优化…

pytest日志总结

pytest日志分为两类&#xff1a; 一、终端&#xff08;控制台&#xff09;打印的日志 1、指定-s&#xff0c;脚本中print打印出的信息会显示在终端&#xff1b; 2、pytest打印的summary信息&#xff0c;这部分是pytest 的默认输出&#xff08;例如测试结果PASSED, FAILED, S…

labview关于文件路径的问题

在调用文件或拆分文件的时候经常会用到拆分路径函数和创建路径函数&#xff0c;最常用的也是当前应用程序目录或者是当前VI目录。 这里我们看到应用程序目录和VI目录在同一项目中&#xff0c;应用程序目录更像是根目录&#xff0c;往下拆分成了各个VI的子目录。 接下来我们来拆…

【MySQL课程学习】:MySQL安装,MySQL如何登录和退出?MySQL的简单配置

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;MySQL课程学习 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 MySQL在Centos 7环境下的安装&#xff1a; 卸载…

第二十一周机器学习笔记:动手深度学习之——数据操作、数据预处理

第二十周周报 摘要Abstract一、动手深度学习1. 数据操作1.1 数据基本操作1.2 数据运算1.2.1 广播机制 1.3 索引和切片 2. 数据预处理 二、复习RNN与LSTM1. Recurrent Neural Network&#xff08;RNN&#xff0c;循环神经网络&#xff09;1.1 词汇vector的编码方式1.2 RNN的变形…

SSM全家桶 1.Maven

或许总要彻彻底底地绝望一次 才能重新再活一次 —— 24.11.20 maven在如今的idea中已经实现自动配置&#xff0c;不需要我们手动下载 一、Maven的简介和快速入门 Maven 是一款为 Java 项目构建管理、依赖管理的工具(软件)&#xff0c;使用 Maven 可以自动化构建测试、打包和发…

《Python 股票交易分析:开启智能投资新时代》(二)

Python 进行股票交易分析的优势 简洁易读&#xff1a;Python 的语法简洁明了&#xff0c;即使是编程新手也能较快上手&#xff0c;降低了股票交易分析的门槛。 Python 的简洁易读是其在股票交易分析中受欢迎的重要原因之一。Python 的语法简洁明了&#xff0c;与其他编程语言相…

cangjie (仓颉) vscode环境搭建

sdk下载 下载中心-仓颉编程语言官网 可选择半年更新版&#xff0c;不用申请。目前版本&#xff1a;0.53.13 &#xff0c;选择不同平台压缩包下载解压到任意位置即可 补充下载&#xff0c;vscode插件解压后&#xff0c;在vscode扩展中选择从vsix安装&#xff0c;安装后新增名为…

HarmonyOS Next原创项目

学友市集 HarmonyOS毕设,项目完整,代码原创,可接毕设 项目展示 项目简介 学友集市是一款基于HarmonyOS Next开发的二手交易平台,适配HarmonyOS5.0&#xff0c;采用前后端分离架构&#xff0c;致力于为用户提供安全、便捷、高品质的二手商品交易服务。平台整合了华为云认证服…

从〇开始深度学习(0)——背景知识与环境配置

从〇开始深度学习(0)——背景知识与环境配置 文章目录 从〇开始深度学习(0)——背景知识与环境配置写在前面1.背景知识1.1.Pytorch1.2.Anaconda1.3.Pycharm1.4.CPU与GPU1.5.整体关系 2.环境配置2.1.准备工作2.1.1.判断有无英伟达显卡2.1.2.清理电脑里的旧环境 2.1.安装Anaconda…

Gate学习(6) 指令学习3

一、/particle/ 目录及其子目录下的命令 在 `/particle/` 命令目录及其子目录下,可以控制和管理粒子相关的属性和过程。以下是每个命令目录和命令的简要解释: ### `/particle/` 这是粒子控制命令的主目录,包括选择粒子、列出粒子名称、查找粒子编码、创建所有离子和同位旋等…

【Git】:Git基本操作

目录 创建、配置本地仓库 创建本地仓库 配置本地仓库 认识工作区、暂存区、版本库 修改文件 版本回退 撤销修改 删除文件 创建、配置本地仓库 创建本地仓库 我们通常可以通过以下两种方式之一获取 Git 存储库&#xff1a; 自己在本地目录创建一个本地仓库 从其它服务…

android 性能分析工具(03)Android Studio Profiler及常见性能图表解读

说明&#xff1a;主要解读Android Studio Profiler 和 常见性能图表。 Android Studio的Profiler工具是一套功能强大的性能分析工具集&#xff0c;它可以帮助开发者实时监控和分析应用的性能&#xff0c;包括CPU使用率、内存使用、网络活动和能耗等多个方面。以下是对Android …

LabVIEW配电网谐波在线监测与分析系统

统利用LabVIEW与NI数据采集卡&#xff0c;结合高精度谐波分析算法&#xff0c;实现了配电网谐波的实时监测与分析。通过虚拟仪器技术的灵活性和扩展性&#xff0c;显著提高电网运行的可靠性与电能质量&#xff0c;提供了一套有效的技术解决方案。 项目背景 随着非线性负载&am…

git使用(二)

git使用&#xff08;二&#xff09; git常用基本操作命令git clonegit loggit remotegit statusgit addgit commitgit pushgit branchgit pull git常用基本操作命令 git clone 项目开发中项目负责人会在github上创建一个远程仓库&#xff0c;我们需要使用git clone将远程仓库…

Excel求和如何过滤错误值

一、问题的提出 平时&#xff0c;我们在使用Excel时&#xff0c;最常用的功能就是求和了&#xff0c;一说到求和你可能想到用sum函数&#xff0c;但是如果sum的求和区域有#value #Div等错误值怎么办&#xff1f;如下图&#xff0c;记算C列中工资的总和。 直接用肯定会报错&…

【数据分享】2024年我国省市县三级的住宿服务设施数量(8类住宿设施/Excel/Shp格式)

宾馆酒店、旅馆招待所等住宿服务设施的配置情况是一个城市公共基础设施完善程度的重要体现&#xff0c;一个城市住宿服务设施种类越丰富&#xff0c;数量越多&#xff0c;通常能表示这个城市的公共服务水平越高&#xff01; 本次我们为大家带来的是我国各省份、各地级市、各区…