Linux驱动开发笔记(九)IIC子系统及其驱动

news2024/11/28 3:15:32

文章目录

  • 前言
  • 一、IIC驱动框架
  • 二、总线驱动
    • 2.1 iic总线的运行机制
    • 2.2 重要数据结构
      • 2.2.1 i2c_driver结构体
      • 2.2.2 i2c总线结构体
    • 2.3 匹配规则
  • 三、设备树的修改
  • 四、设备驱动的编写
    • 4.1 相关API函数
      • 4.1.1 i2c_add_adapter( )
      • 4.1.2 i2c_register_driver( )
      • 4.1.3 i2c_transfer( )
      • 4.1.4 i2c_master_send( )
      • 4.1.5 i2c_master_recv( )
      • 4.1.6 i2c_transfer_buffer_flags( )
      • 4.1.7 i2c_del_driver( )
    • 4.2 MPU6050
      • 4.2.1 基本介绍
      • 4.2.2 主要特点
      • 4.2.3 引脚对应表
    • 4.3 驱动编写
      • 4.3.1 IIC驱动的设计框架
      • 4.3.2 .probe函数
      • 4.3.3 .remove函数
      • 4.3.4 mpu6050初始化函数
      • 4.3.5 write/read函数
      • 4.3.6 report函数


前言

  IIC我们已经学习过很多次了,在应用部分我们已经介绍过其应用层的开发,这章我们将继续驱动部分的开发。本次实验采用MPU6050,使用了input子系统及IIC子系统构成。

一、IIC驱动框架

  i2c总线包括i2c设备(i2c_client)和i2c驱动(i2c_driver),当我们向linux中注册设备或驱动的时候,按照i2c总线匹配规则进行配对,这也意味着我们不再需要手动创建,而是使用设备树机制引入,设备树节点是与paltform总线相配合使用,在匹配成功之后自动进入.probe函数。
在这里插入图片描述
I2C core框架
  提供了核心数据结构的定义和相关接口函数,用来实现I2C适配器驱动和设备驱动的注册、注销管理,以及I2C通信方法上层的、与具体适配器无关的代码,为系统中每个I2C总线增加相应的读写方法。
I2C总线驱动
  定义描述具体I2C总线适配器的i2c_adapter数据结构、实现在具体I2C适配器上的I2C总线通信方法,并由i2c_algorithm数据结构进行描述。 经过I2C总线驱动的的代码,可以为我们控制I2C产生开始位、停止位、读写周期以及从设备的读写、产生ACK等。
I2C 设备驱动
  I2C 设备驱动通过I2C适配器与CPU通信,其中主要包含i2c_driver和i2c_client数据结构,i2c_driver结构对应一套具体的驱动方法。i2c_client数据结构由内核根据具体的设备注册信息自动生成,设备驱动根据硬件具体情况填充。

二、总线驱动

  一个完整的iic驱动函数包括两部分,即iic总线驱动和设备驱动,而总线部分的驱动通常情况下在外设出厂时就由厂商提供,这里我们便简单了解即可。

2.1 iic总线的运行机制

  1. 注册i2C总线
  2. 将i2C驱动添加到i2C总线的驱动链表中
  3. 遍历i2C总线上的设备链表,根据i2c_device_match函数进行匹配,如果匹配调用i2c_device_probe函数
  4. i2c_device_probe函数会调用I2C驱动的probe函数

2.2 重要数据结构

  在应用层的学习中我们已经介绍过i2c_algorithm,i2c_client和i2c_adapter结构体,感兴趣可以回顾下。

2.2.1 i2c_driver结构体

struct i2c_driver {
        unsigned int class;
        
        int (*probe)(struct i2c_client *, const struct i2c_device_id *);
        int (*remove)(struct i2c_client *);

        struct device_driver driver;
        const struct i2c_device_id *id_table;

        int (*detect)(struct i2c_client *, struct i2c_board_info *);
        const unsigned short *address_list;
        struct list_head clients;
            ...
    };
  • probe: i2c设备和i2c驱动匹配后,回调该函数指针。
  • id_table: struct i2c_device_id 要匹配的从设备信息。
  • address_list: 设备地址
  • clients: 设备链表
  • detect: 设备探测函数

2.2.2 i2c总线结构体

//定义总线,维护着两个链表(I2C驱动、I2C设备),管理I2C设备和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,
    };

2.3 匹配规则

  一般来说,i2c的匹配方式有三种,包括设备树,ACPI和字符匹配,这部分的对比前章已经介绍过了。现在我们习惯性采用设备树的匹配方式:

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
    struct i2c_client       *client = i2c_verify_client(dev);
    struct i2c_driver       *driver;

    //设备树匹配方式,比较 I2C 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性
    if (i2c_of_match_device(drv->of_match_table, client))
        return 1;

    //ACPI 匹配方式
    if (acpi_driver_match_device(dev, drv))
        return 1;

    driver = to_i2c_driver(drv);
	//i2c总线传统匹配方式,比较 I2C设备名字和 i2c驱动的id_table->name 字段是否相等
    if (i2c_match_id(driver->id_table, client))
        return 1;

    return 0;
}

三、设备树的修改

  下面是瑞芯微官方给出的ic3控制器的设备树代码:

//存放于“rk3568-pinctrl.dtsi”
i2c2: i2c@fe5b0000 {

		//驱动名称
		compatible = "rockchip,rk3399-i2c";
		
		//寄存器
		reg = <0x0 0xfe5b0000 0x0 0x1000>;
		
		//时钟源
		clocks = <&cru CLK_I2C2>, <&cru PCLK_I2C2>;
		clock-names = "i2c", "pclk";
		
		//中断源
		interrupts = <GIC_SPI 48 IRQ_TYPE_LEVEL_HIGH>;
		
		pinctrl-names = "default";
		pinctrl-0 = <&i2c2m0_xfer>;
		#address-cells = <1>;
		#size-cells = <0>;
		status = "disabled";

//存放于“rk3568-pinctrl.dtsi”
i2c2 {
		/omit-if-no-ref/
		i2c2m0_xfer: i2c2m0-xfer {
			rockchip,pins =
				/* i2c2_sclm0 */
				<0 RK_PB5 1 &pcfg_pull_none_smt>,
				/* i2c2_sdam0 */
				<0 RK_PB6 1 &pcfg_pull_none_smt>;
		};

		/omit-if-no-ref/
		i2c2m1_xfer: i2c2m1-xfer {
			rockchip,pins =
				/* i2c2_sclm1 */
				<4 RK_PB5 1 &pcfg_pull_none_smt>,
				/* i2c2_sdam1 */
				<4 RK_PB4 1 &pcfg_pull_none_smt>;
		};
	};

  接下来就是我们要编写的内容,这部分编写方式和以前一样即可。

&i2c2 {
     status = "okay";

    pinctrl-names = "default";
    pinctrl-0 = <&i2c2m0_xfer>;     
    #address-cells = <1>;
    #size-cells = <0>;
	/*添加你的I2C设备参考*/
    myi2c: myi2c@68 {
        compatible = "company,myi2c";
        reg = <0x68>;
        status = "okay";
 };

四、设备驱动的编写

4.1 相关API函数

4.1.1 i2c_add_adapter( )

  使用这个函数时,不需要提前指定适配器编号,内核会负责管理和分配编号,适合于大多数情况下的使用。

//自动分配 I2C 适配器编号
int i2c_add_adapter(struct i2c_adapter *adapter);

  适配器编号(adapter->nr)在注册过程中由系统自动设置,具体的步骤如下:

  • 适配器初始化:调用这个函数之前,需要先初始化 i2c_adapter 结构体,填充相关字段。
  • 自动分配编号:内核会自动选择一个可用的编号,并将其分配给 adapter->nr。
  • 注册适配器:将适配器注册到 I2C子系统中,使其可以被使用。

注:相似地还还存在int i2c_add_numbered_adapter(struct i2c_adapter *adapter)函数,这个函数用于手动设置 I2C 适配器编号。调用这个函数之前,需要先初始化 i2c_adapter 结构体,并填充 adapter->nr 字段和其他相关字段。
i2c_register_driver 函数用于在 Linux 内核中注册一个 I2C 驱动。这个函数是 I2C 子系统的一部分,用于将一个 I2C 驱动程序注册到 I2C 驱动程序模型中,以便内核能够识别并管理该驱动程序。

4.1.2 i2c_register_driver( )

  这个函数是 I2C 子系统的一部分,用于将一个 I2C 驱动程序注册到 I2C 驱动程序模型中,以便内核能够识别并管理该驱动程序。

//在 Linux 内核中注册一个 I2C 驱动
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
  • 参数:
    • owner:通常使用 THIS_MODULE 宏来指定当前模块
    • driver:指向一个 i2c_driver 结构体,该结构体包含了驱动程序的相关信息和操作函数
  • 返回值:
    • 0:成功注册
    • 负数:注册失败

注:#define i2c_add_driver(driver)宏定义是对i2c_register_driver函数的调用,也可以直接使用这个宏定义进行注册

4.1.3 i2c_transfer( )

  i2c_transfer 是一个底层函数,它可以执行多条消息的读写操作。

//在 I2C 总线上进行数据传输
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
  • 参数
    • adap:指向 i2c_adapter 结构体的指针,表示 I2C 适配器。
    • msgs:指向 i2c_msg 结构体数组的指针,每个 i2c_msg 结构体表示一条 I2C 消息。
    • num:消息数量。
  • 返回值
    • 正值:表示成功传输的消息数量。
    • 负值:表示传输失败,返回一个负的错误代码。

注:i2c_msg结构体之前在应用开发实验时已经介绍过了, 这里简单回忆一下:

//描述一个iic消息
struct i2c_msg {
    __u16 addr;     //iic设备地址
    __u16 flags;	//消息传输方向和特性。I2C_M_RD:表示读取消息;0:表示发送消息
    __u16 len;      //消息数据的长度
    __u8 *buf;      //字符数组存放消息,作为消息的缓冲区
	    ... 
};

4.1.4 i2c_master_send( )

  i2c_master_send 是一个便捷函数,用于向 I2C 设备发送数据。

复制代码
int i2c_master_send(const struct i2c_client *client, const char *buf, int count);
  • 参数
    • client:指向 i2c_client 结构体的指针,表示目标 I2C 设备。
    • buf:指向数据缓冲区的指针。
    • count:要发送的数据长度。
  • 返回值
    • 正值:表示成功发送的字节数。
    • 负值:表示发送失败,返回一个负的错误代码。

4.1.5 i2c_master_recv( )

  i2c_master_recv 是一个便捷函数,用于从 I2C 设备接收数据。

int i2c_master_recv(const struct i2c_client *client, char *buf, int count);
  • 参数
    • client:指向 i2c_client 结构体的指针,表示目标 I2C 设备。
    • buf:指向接收缓冲区的指针。
    • count:要接收的数据长度。
  • 返回值
    • 正值:表示成功接收的字节数。
    • 负值:表示接收失败,返回一个负的错误代码。

4.1.6 i2c_transfer_buffer_flags( )

//用于在 I2C 总线上进行带有特定标志的数据传输
int i2c_transfer_buffer_flags(const struct i2c_client *client, char *buf, int count, u16 flags);
  • 参数
    • client:指向 i2c_client 结构体的指针,表示目标 I2C 设备。
    • buf:指向数据缓冲区的指针。
    • count:要传输的数据长度。
    • flags:传输标志。
  • 返回值
    • 正值:表示成功传输的字节数。
    • 负值:表示传输失败,返回一个负的错误代码。

4.1.7 i2c_del_driver( )

  这个函数与 i2c_register_driver 相对应,i2c_register_driver 用于注册一个 I2C 驱动程序,而 i2c_del_driver 用于注销它。

//从I2C 子系统中注销一个 I2C 驱动程序
void i2c_del_driver(struct i2c_driver *driver);
  • 参数
    • driver:指向一个 i2c_driver 结构体,该结构体表示要注销的 I2C 驱动程序

4.2 MPU6050

4.2.1 基本介绍

  MPU6050是全球首例整合性6轴(3轴陀螺仪+3轴加速度计)运动处理组件,也可以通过扩展实现9轴运动处理(在连接三轴磁传感器后)。它集成了三轴MEMS陀螺仪和三轴MEMS加速度计,以及一个可扩展的数字运动处理器DMP(Digital Motion Processor)。MPU6050通过I2C接口与微控制器通信,广泛应用于需要精确姿态测量的场合,如无人机、机器人和智能穿戴设备等。

4.2.2 主要特点

  1. 整合性:MPU6050免除了组合陀螺仪与加速器时间轴之差的问题,减少了大量的封装空间。
  2. 灵活性:MPU6050的角速度全格感测范围为±250、±500、±1000与±2000°/sec(dps),可准确追踪快速与慢速动作;用户可程式控制的加速器全格感测范围为±2g、±4g、±8g与±16g,满足各种应用需求。
  3. 接口:MPU6050支持最高至400kHz的I2C接口,对于需要高速传输的应用,也可使用SPI接口(但请注意,SPI接口仅在MPU-6000上可用)。
  4. 稳定性:MPU6050具有内建的温度感测器和在工作环境下仅有±1%变动的振荡器,保证了测量数据的稳定性。
  5. 尺寸与封装:MPU6050采用QFN封装(无引线方形封装),尺寸为4x4x0.9mm,可承受最大10000g的冲击。
  6. 电源:VDD供电电压为2.5V±5%、3.0V±5%、3.3V±5%;VDDIO为1.8V±5%或VDD。
  7. 功耗:陀螺仪运作电流5mA,待命电流5μA;加速器运作电流350μA,省电模式电流20μA@10Hz。
  8. 性能:陀螺仪敏感度131 LSBs/°/sec,加速度计范围±2g至±16g。

4.2.3 引脚对应表

MPU6050引脚说明泰山派引脚
SCLSCL引脚GPIO0_B5
SDASDA引脚GPIO0_B6
XDA没有使用
XCL没有使用
AD0接地GND
INT(Interrupt)悬空或者接地
GNDGNDGND
VCC电源3.3V

4.3 驱动编写

4.3.1 IIC驱动的设计框架

  本次实验大致采用input子系统和IIC子系统,这里着重讲一下这部分的设计思路:

  1. 本实验采用设备树匹配的方式进行匹配,故而需要设置i2c_driver结构体。
//定义ID匹配表
static const struct i2c_device_id gtp_device_id[] = {
    {"company,myi2c", 0},
    {}
};
//定义设备树匹配表
static const struct of_device_id mpu6050_of_match_table[] = {
    {.compatible = "company,myi2c"},
    {}
};
//定义i2c设备结构体
struct i2c_driver mpu6050_driver = {
    .probe = mpu6050_probe,
    .remove = mpu6050_remove,
    .id_table = gtp_device_id,
    .driver = {
        .owner = THIS_MODULE,
        .name = "company,myi2c",
        .of_match_table = mpu6050_of_match_table,
    },
};
  1. 出入口函数的编写,通常情况下这里需要编写module_init和module_exti,并且在其中分别使用i2c_register_driver( )函数和i2c_del_driver( )函数进行iic总线的注册和注销。但是这里引入一个module_i2c_driver(driver) 宏定义,这个宏定义可以自动进行iic总线的注册和注销,故而不需要前两个函数的编写。
  2. probe函数的编写,这部分内容我们需要的进行内存申请,之后便可以进行选择利用字符设备的方式还是input子系统,笔者这里选择趁热打铁使用input设备,这里流程不太熟悉的可以回顾上章内容。
  3. remove函数的编写,这部分与probe函数对应即可,如果选择字符设备的方式,则依次进行device_destroy(设备删除)、class_destroy(清除类)、cdev_del(清除设备号)、unregister_chrdev_region(注销字符设备);若采用input的子系统,则仅需要使用input_unregister_device(注销input设备即可)。
  4. 具体外设的初始化、读、写函数的编写,这部分内容根据厂商提供的寄存器、时序图操作即可。
  5. 若使用字符设备的话,这里还需要编写operations结构体相关函数,包括open、write、read、release,并利用cdev_init()函数与设备进行绑定。

4.3.2 .probe函数

static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id){
    struct mpu6050_data *data;
    int ret = 0;
    printk(KERN_EMERG "mpu6050_probe enter!\n");

    data = devm_kzalloc(&client->dev, sizeof(struct mpu6050_data), GFP_KERNEL);
    if (!data){
        printk(KERN_EMERG "devm_kzalloc err!\n");
        return -ENOMEM;
    }
    
    data->client = client;

    ret = mpu6050_init_device(client);
    if(ret < 0){
        printk(KERN_EMERG "mpu6050_init_device err!\n");
        return ret;
    }

    data->input_dev = devm_input_allocate_device(&client->dev);
    if (!data->input_dev){
        printk(KERN_EMERG "devm_input_allocate_device err!\n");
        return -ENOMEM;
    }
        
    data->input_dev->name = "mpu6050";
    data->input_dev->id.bustype = BUS_I2C;

    input_set_abs_params(data->input_dev, ABS_X, -32768, 32767, 0, 0);
    input_set_abs_params(data->input_dev, ABS_Y, -32768, 32767, 0, 0);
    input_set_abs_params(data->input_dev, ABS_Z, -32768, 32767, 0, 0);
    input_set_abs_params(data->input_dev, ABS_RX, -32768, 32767, 0, 0);
    input_set_abs_params(data->input_dev, ABS_RY, -32768, 32767, 0, 0);
    input_set_abs_params(data->input_dev, ABS_RZ, -32768, 32767, 0, 0);

    ret = input_register_device(data->input_dev);
    if (ret < 0){
        printk("input_register_device err!\n");
        return ret;
    }

    i2c_set_clientdata(client, data);

    INIT_DELAYED_WORK(&data->work, mpu6050_report_data);
    schedule_delayed_work(&data->work, msecs_to_jiffies(100));
   
    return 0;
}

4.3.3 .remove函数

static int mpu6050_remove(struct i2c_client *client){
    struct mpu6050_data *data = i2c_get_clientdata(client);
    printk(KERN_EMERG "mpu6050_remove enter!\n");

    cancel_delayed_work_sync(&data->work);
    input_unregister_device(data->input_dev);

    return 0;
}

4.3.4 mpu6050初始化函数

static int mpu6050_init_device(struct i2c_client *client)
{
    int error = 0;
    error += i2c_write_mpu6050(client, PWR_MGMT_1, 0x00);
    error += i2c_write_mpu6050(client, SMPLRT_DIV, 0x07);
    error += i2c_write_mpu6050(client, CONFIG, 0x06);
    error += i2c_write_mpu6050(client, ACCEL_CONFIG, 0x01);

    if (error < 0) {
        printk(KERN_DEBUG "mpu6050_init_device error\n");
        return error;
    }
    return 0;
}

4.3.5 write/read函数

static int i2c_write_mpu6050(struct i2c_client *mpu6050_client, u8 address, u8 data){
    int error = 0;
    u8 write_data[2];
    struct i2c_msg send_msg;

    write_data[0] = address;
    write_data[1] = data;

    send_msg.addr = mpu6050_client->addr;
    send_msg.flags = 0;
    send_msg.buf = write_data;
    send_msg.len = 2;

    error = i2c_transfer(mpu6050_client->adapter, &send_msg, 1);
    if (error != 1) {
        printk(KERN_DEBUG "i2c_write_mpu6050 error\n");
        return -EIO;
    }
    return 0;
}

static int i2c_read_mpu6050(struct i2c_client *mpu6050_client, u8 address, void *data, u32 length){
    int error = 0;
    u8 address_data = address;
    struct i2c_msg mpu6050_msg[2];

    mpu6050_msg[0].addr = mpu6050_client->addr;
    mpu6050_msg[0].flags = 0;
    mpu6050_msg[0].buf = &address_data;
    mpu6050_msg[0].len = 1;

    mpu6050_msg[1].addr = mpu6050_client->addr;
    mpu6050_msg[1].flags = I2C_M_RD;
    mpu6050_msg[1].buf = data;
    mpu6050_msg[1].len = length;

    error = i2c_transfer(mpu6050_client->adapter, mpu6050_msg, 2);
    if (error != 2) {
        printk(KERN_DEBUG "i2c_read_mpu6050 error\n");
        return -EIO;
    }
    return 0;
}

4.3.6 report函数

static void mpu6050_report_data(struct work_struct *work)
{
    struct mpu6050_data *data = container_of(work, struct mpu6050_data, work.work);
    struct i2c_client *client = data->client;
    s16 accel_data[3];
    s16 gyro_data[3];
    u8 buffer[14];
    int ret;

    ret = i2c_read_mpu6050(client, ACCEL_XOUT_H, buffer, 14);
    if (ret < 0) {
        dev_err(&client->dev, "Failed to read data: %d\n", ret);
        return;
    }

    accel_data[0] = (buffer[0] << 8) | buffer[1];
    accel_data[1] = (buffer[2] << 8) | buffer[3];
    accel_data[2] = (buffer[4] << 8) | buffer[5];
    gyro_data[0] = (buffer[8] << 8) | buffer[9];
    gyro_data[1] = (buffer[10] << 8) | buffer[11];
    gyro_data[2] = (buffer[12] << 8) | buffer[13];

	//用于报告绝对坐标事件
    input_report_abs(data->input_dev, ABS_X, accel_data[0]);
    input_report_abs(data->input_dev, ABS_Y, accel_data[1]);
    input_report_abs(data->input_dev, ABS_Z, accel_data[2]);
    input_report_abs(data->input_dev, ABS_RX, gyro_data[0]);
    input_report_abs(data->input_dev, ABS_RY, gyro_data[1]);
    input_report_abs(data->input_dev, ABS_RZ, gyro_data[2]);

    input_sync(data->input_dev);
    schedule_delayed_work(&data->work, msecs_to_jiffies(100));
}

免责声明:本内容部分参考野火科技及其他相关公开资料,若有侵权或者勘误请联系作者。

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

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

相关文章

Vue68-路由简介

一、路由的应用&#xff1a;&#xff08;单页面应用&#xff09; 单页面应用&#xff1a;页面不刷新&#xff0c;但是路径会改变。 二、路由的原理&#xff1a; 2-1、多页面应用&#xff1a; 2-2、路由的相关概念 2-3、前端路由、后端路由 前端路由&#xff1a;你是什么路径…

Spring Boot程序打包docker镜像

1.将springboot程序使用maven package打包出jar。 2.创建dockerfile。 FROM openjdk:8 VOLUME /tmp EXPOSE 8601 #ADD 后面的参数是项目名字 / 后面的参数是自定义的别名 ADD webflux-hello-0.0.1-SNAPSHOT.jar /webflux-hello.jar #这里的最后一个变量需要和前面起的别名相同…

Day15—热点搜索词统计

一、要求 根据用户上网的搜索记录对每天的热点搜索词进行统计&#xff0c;以了解用户所关心的热点话题。 要求完成&#xff1a;统计每天搜索数量前3名的搜索词&#xff08;同一天中同一用户多次搜索同一个搜索词视为1次&#xff09;。 二、数据 三、配置scala环境 1.下载sca…

Linux:用户账号和权限管理的命令

目录 一、Linux用户的分类和组的分类 1.1、用户账号和组账号 1.2、用户的分类 1.3、组账号 1.4、用户账号文件/etc/passwd 二、用户管理相关命令 2.1、chage命令&#xff1a;用来修改帐号和密码的有效期限&#xff0c;针对目前系统已经存在的用户 2.2、useradd&#xf…

八大经典排序算法

前言 本片博客主要讲解一下八大排序算法的思想和排序的代码 &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;排序_普通young man的博客-CSDN博客 若有问题 评论区见&#x1f4dd; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 目录 …

Java——web开发

两个月前大概学完了JavaSE&#xff0c;老师推荐说&#xff0c;直接做项目&#xff0c;跟着尚硅谷的视频去做。但对于我来说&#xff0c;难度还是太大了&#xff0c;可能一下午才搞懂几行代码&#xff0c;学习效率不高&#xff0c;&#xff0c;没有成就感和充实感。于是&#xf…

SpringCloud Alibaba Sentinel 流量控制之流控效果实践总结

当 QPS 超过某个阈值的时候&#xff0c;则采取措施进行流量控制。流量控制的效果包括以下几种&#xff1a;直接拒绝、Warm Up、匀速排队/排队等待。对应 FlowRule 中的 controlBehavior 字段。 注意&#xff1a;若使用除了直接拒绝之外的流量控制效果&#xff0c;则调用关系限流…

【Redis】哈希类型的常用命令以及使用场景

Redis 哈希是一种用于存储键值对的数据结构。在 Redis 哈希中&#xff0c;每个键&#xff08;key&#xff09;都关联着一个哈希表&#xff08;hash&#xff09;。这个哈希表包含了多个字段&#xff08;field&#xff09;和值&#xff08;value&#xff09;。哈希非常适合存储对…

Java | Leetcode Java题解之第171题Excel表列序号

题目&#xff1a; 题解&#xff1a; class Solution {public int titleToNumber(String columnTitle) {int number 0;int multiple 1;for (int i columnTitle.length() - 1; i > 0; i--) {int k columnTitle.charAt(i) - A 1;number k * multiple;multiple * 26;}ret…

44、基于深度学习的癌症检测(matlab)

1、基于深度学习的癌症检测原理及流程 基于深度学习的癌症检测是利用深度学习算法对医学影像数据进行分析和诊断&#xff0c;以帮助医生准确地检测癌症病变。其原理和流程主要包括以下几个步骤&#xff1a; 数据采集&#xff1a;首先需要收集包括X光片、CT扫描、MRI等医学影像…

【vite】define 全局常量定义

&#x1f9ed; define 说明 类型&#xff1a; Record<string, any> 定义全局常量替换方式。其中每项在开发环境下会被定义在全局&#xff0c;而在构建时被静态替换。 Vite 使用 esbuild define 来进行替换&#xff0c;因此值的表达式必须是一个包含 JSON 可序列化值&a…

xshell传输文件速率为0

你们好&#xff0c;我是金金金。 场景 此时我通过xshell客户端上传文件&#xff0c;速率一直为0 解决 安装 yum -y install lrzsz 即可 这个工具主要提供 rz 和 sz 命令&#xff0c;用于通过 Zmodem 协议在本地计算机和远程服务器之间传输文件 编写有误还请大佬指正&#xff0…

C++的智能指针 RAII

目录 产生原因 RAII思想 C11的智能指针 智能指针的拷贝与赋值 shared_ptr的拷贝构造 shared_ptr的赋值重置 shared_ptr的其它成员函数 weak_ptr 定制删除器 简单实现 产生原因 产生原因&#xff1a;抛异常等原因导致的内存泄漏 int div() {int a, b;cin >> a…

在Ubuntu系统中部署Java及Spring Boot开发环境

选择Java及Spring Boot构建Web服务具有显著优势&#xff0c;Java的跨平台兼容性保证了服务可在不同操作系统上顺畅运行&#xff0c;而Spring Boot的成熟框架则大大简化了开发流程&#xff0c;减少了繁琐配置。此外&#xff0c;强大的社区支持、易于维护与扩展的特性、优异的性能…

a-table 根据数据自动进行 行合并

<template><div class"chat_query_result"><button click"temp">点击</button><a-table :columns"columns" :data-source"data" bordered></a-table></div> </template><script&g…

计算机毕业设计hadoop+spark+hive游戏推荐系统 游戏数据分析可视化大屏 steam游戏爬虫 游戏大数据 大数据毕业设计 机器学习 知识图谱

游戏推荐系统开题报告 一、引言 随着信息技术和网络技术的飞速发展&#xff0c;电子游戏已成为人们日常生活中不可或缺的一部分。然而&#xff0c;面对海量的游戏资源&#xff0c;用户往往难以找到适合自己的游戏。因此&#xff0c;构建一个高效、准确的游戏推荐系统显得尤为…

C++ | Leetcode C++题解之第171题Excel表列序号

题目&#xff1a; 题解&#xff1a; class Solution { public:int titleToNumber(string columnTitle) {int number 0;long multiple 1;for (int i columnTitle.size() - 1; i > 0; i--) {int k columnTitle[i] - A 1;number k * multiple;multiple * 26;}return num…

万界星空科技MES系统中的仓库管理功能

制造执行系统&#xff08;Manufacturing Execution System&#xff0c;简称MES&#xff09;作为一种面向车间生产调度的管理信息系统&#xff0c;被广泛应用在车间作业调度和控制管理系统中&#xff0c;它以实现车间生产调度最优化为目标。同时&#xff0c;MES作为衔接ERP&…

Scikit-Learn梯度提升决策树(GBDT)

Scikit-Learn梯度提升决策树 1、梯度提升决策树(GBDT)1.1、Boosting方法1.2、GBDT的原理1.3、GBDT回归的损失函数1.4、梯度下降与梯度提升1.5、随机森林与GBDT1.6、GBDT的优缺点2、Scikit-Learn梯度提升决策树(GBDT)2.1、Scikit-Learn GBDT回归2.1.1、Scikit-Learn GBDT回归…

阿里云上构建_VPC专有网络_子网划分_原理说明_创建_释放---分布式云原生部署架构搭建006

可以看到有这种子网掩码计算工具可以使用 可以看到这个是,我们设计的一个子网 192.168.0.0/16 可以看到地址是 从192.168.0.1 到 192.168.255.254 有了子网,我们去看,可以看到在阿里云的管理后台,就有个 创建交换机.可以看到 然后指定这个交换机的网段 这里我们指定了192…