Linux驱动开发(速记版)--平台总线

news2024/10/1 1:38:00

第四十七章 平台总线模型介绍

47.1 什么是平台总线?

        平台总线是Linux内核中的一种虚拟机制,用于连接和匹配平台设备与对应的平台驱动。它简化了设备与驱动之间的绑定过程,提高了系统对硬件的适配性和扩展性。

        当设备或驱动被注册时,平台总线会自动寻找并绑定匹配的组件,让设备能被正确初始化和控制。这种机制增强了嵌入式系统的灵活性和可移植性。

设备、平台总线、驱动的关系

47.2 平台总线的优势

引入平台总线模型后,解决了多设备驱动管理中的重复和冗余问题。其优势包括:

        设备与驱动分离:清晰划分设备和驱动的代码,提高了代码的可读性和维护性。

        增强代码重用性:允许同类型设备共享同一驱动代码,通过配置不同的设备文件来适应不同硬件,减少重复编写。

        减少重复性代码:避免为每个设备编写相似驱动代码,简化了开发流程。

        提高可移植性:使驱动不依赖于特定标准总线,便于跨平台移植和重用。

第四十八章 注册 platform 设备

48.1 注册 platform 设备

48.1.1 platform_device_register() 函数

platform_device_register() 函数是 Linux 内核中用于注册平台设备的一个关键函数。

platform_device结构体包含设备名称设备资源设备 ID 等信息,描述和标识平台设备。

// 平台总线设备注册函数原型  
int platform_device_register(struct platform_device *pdev)  
{  
    // 初始化设备结构体      
    device_initialize(&pdev->dev);  //设置设备的引用计数、类型等基本信息。
  
    // 根据架构设置特定数据(可选)  
    arch_setup_pdev_archdata(pdev);  
  
    // 将平台设备添加到内核中  
    return platform_device_add(pdev); //包括将其添加到设备层级结构和 platform 总线 
}

// 在头文件中的声明  
#include <linux/platform_device.h>  
extern void platform_device_register(struct platform_device *);
/*平台设备结构体*/
struct platform_device {  
    const char *name;     // 设备名称  
    int id;               // 设备ID号  
    struct device dev;    // 内嵌的具体的device结构体,表示设备在设备模型中的抽象  
    u32 num_resources;    // 资源数量  
    struct resource *resource; // 指向设备的资源描述符数组
                               // 资源描述符包含了设备的物理地址、大小、类型等信息。  
    const struct platform_device_id *id_entry; // 用于匹配设备和驱动程序的ID结构体
                                               // 通过该结构体可以实现设备和驱动的自动匹配。  
    // ... 其他成员,如指向设备对应的平台驱动程序的指针等  
};

48.1.2 platform_device_unregister() 函数

        platform_device_unregister() 函数用于从内核中取消注册并移除已经注册的平台设备

        这个函数通过减少设备的引用计数和将其从设备层级结构移除来完成清理工作。

/*从平台总线移除已经注册的设备*/
void platform_device_unregister(struct platform_device *pdev)  
{  
    // 将设备从platform总线的设备列表中移除  
    platform_device_del(pdev);  
  
    // 减少设备的引用计数,必要时释放设备结构体和相关资源  
    platform_device_put(pdev);  
}

// 在头文件中的声明  
#include <linux/platform_device.h>  
extern void platform_device_unregister(struct platform_device *);

48.1.3 platform_device 结构体

        platform_device 结构体是 Linux 内核中用于描述和管理平台设备的数据结构。

        它包含了设备名称设备ID资源信息以及与其他设备模型和架构相关的数据。

/*平台总线设备结构体*/
struct platform_device {  
    const char *name;       // 设备的名称,用于唯一标识  
    int id;                 // 设备的ID,区分同种设备的不同实例,可选,-1 表示不使用  
    bool id_auto;           // ID 是否自动生成,通常不是直接操作的对象  
    struct device dev;      // 基本的设备管理和操作结构体,必须有效且实现 release 方法  
    u32 num_resources;      // 设备资源的数量  
    struct resource *resource; // 指向资源数组的指针  
    const struct platform_device_id *id_entry; // 用于匹配设备和驱动的ID表项  
    char *driver_override;  // 强制指定驱动名称  
    struct mfd_cell *mfd_cell; // 指向多功能设备(MFD)单元的指针  
    struct pdev_archdata archdata; // 存储特定于架构的设备数据  
};
/*资源结构体*/
struct resource {  
    resource_size_t start;  // 资源的起始物理地址  
    resource_size_t end;    // 资源的结束物理地址  
    const char *name;       // 资源的名称  
    unsigned long flags;    // 资源标志,如 IORESOURCE_MEM(内存资源)  
    // ... 其他成员  
};

        在实际使用中,platform_device 的创建和注册通常由内核的初始化代码、设备树(Device Tree)或特定的初始化函数完成,而不是直接在代码中手动构造。

        platform_device_register() 函数用于注册设备

        platform_device_unregister() 用于注销设备

48.1.4 resource 结构体

        resource 结构体用于在 Linux 内核中描述和管理系统资源,如内存区域、I/O 端口、中断等。它定义了资源的起始和结束地址名称标志位,以及与其他资源的父子或兄弟关系

/*资源结构体*/
struct resource {  
    resource_size_t start;    // 资源的起始地址  
    resource_size_t end;      // 资源的结束地址  
    const char *name;         // 资源的名称  
    unsigned long flags;      // 资源的标志位,用于表示属性、特征等  
    // ... 其他成员和宏定义略  
};

/*
示例标志位
#define IORESOURCE_IO       0x00000100  // I/O 端口资源  
#define IORESOURCE_MEM      0x00000200  // 内存资源  
  
// 资源属性和特征相关  
#define IORESOURCE_READONLY 0x00000400  // 资源是只读的  
#define IORESOURCE_CACHEABLE 0x00000800 // 资源支持缓存  
  
// 其他状态和控制  
#define IORESOURCE_DISABLED 0x00001000  // 资源当前被禁用  
#define IORESOURCE_BUSY     0x00002000  // 驱动程序将此资源标记为繁忙
*/

48.2 注册平台总线设备实验

        本实验将注册一个名为 "my_platform_device" 的平台设备。

        当注册平台设备时,该驱动程序提供了两个资源:

        一个内存资源和一个中断资源。这些资源被定义在名为 my_resources 的结构体数组中。

        内存资源:

                起始地址:MEM_START_ADDR(0xFDD60000)

                结束地址:MEM_END_ADDR(0xFDD60004)

                标记:IORESOURCE_MEM

        中断资源:

                中断资源号:IRQ_NUMBER(101)

                标记:IORESOURCE_IRQ

/*注册平台总线设备代码,platform_device.c 代码*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#define MEM_START_ADDR 0xFDD60000  //设备资源起始地址
#define MEM_END_ADDR 0xFDD60004    //设备资源结束地址  总共4字节
#define IRQ_NUMBER 101             //中断资源号

/* resource结构体数组定义,resources[0]放内存资源,resources[1]放中断资源 */
static struct resource my_resources[] = {
    {
        .start = MEM_START_ADDR, // 内存资源起始地址
        .end = MEM_END_ADDR, // 内存资源结束地址
        .flags = IORESOURCE_MEM, // 标记为内存资源
    },
    {
        .start = IRQ_NUMBER, // 中断资源号
        .end = IRQ_NUMBER, // 中断资源号
        .flags = IORESOURCE_IRQ, // 标记为中断资源
    },
};

/*平台设备释放函数*/
static void my_platform_device_release(struct device *dev)
{
    // 释放各种资源,由plat_form结构体的release成员指向该函数,在
    // platform_device_unregister()被调用且设备被成功注销时执行
}

/*定义平台设备结构体*/
static struct platform_device my_platform_device = {
    .name = "my_platform_device", // 设备名称
    .id = -1, // 设备 ID
    .num_resources = ARRAY_SIZE(my_resources), // 资源数量
    .resource = my_resources, // 资源数组
    .dev.release = my_platform_device_release, // 释放资源的回调函数
};
//platform_device结构体中的.dev.release字段被设置为一个函数,
//那么当platform_device_unregister()被调用且设备被成功注销时,这个函数将被执行。

/*驱动入口*/
static int __init my_platform_device_init(void)
{
    int ret;
    ret = platform_device_register(&my_platform_device); // 注册平台设备
    if (ret) {
        printk(KERN_ERR "Failed to register platform device\n");
        return ret;
    }
    printk(KERN_INFO "Platform device registered\n");
    return 0;
}

/*驱动出口*/
static void __exit my_platform_device_exit(void)
{
    platform_device_unregister(&my_platform_device); // 注销平台设备
    printk(KERN_INFO "Platform device unregistered\n");
}
module_init(my_platform_device_init);
module_exit(my_platform_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");

        来到/sys/bus/platform/devices 目录下,可以看到我们创建的 my_platform_device 平台总线设备。

第四十九章 注册 platform 驱动实验

49.1 注册 platform 驱动

49.1.1 platform_driver_register() 函数

        platform_driver_register() 在 Linux 内核中用于注册平台驱动程序到内核。

#include <linux/platform_device.h>  

/*注册平台驱动程序*/
int platform_driver_register(struct platform_driver *driver);
#define platform_driver_register(drv) \  
    __platform_driver_register(drv, THIS_MODULE)

int __platform_driver_register(struct platform_driver *drv,   //平台驱动结构体
                               struct module *owner)          //所属模块
{  
    drv->driver.owner = owner;                // 将当前模块与平台驱动程序关联  
    drv->driver.bus = &platform_bus_type;     // 设置总线类型为平台总线  
    drv->driver.probe = platform_drv_probe;   // 设置探测函数,发现匹配设备时回调  
    drv->driver.remove = platform_drv_remove; // 设置移除函数,移除设备时回调  
    drv->driver.shutdown = platform_drv_shutdown; // 设置关机函数,系统关机时调用  
    return driver_register(&drv->driver);     // 注册驱动程序到内核  
}

49.1.2 platform_driver_unregister() 函数

  platform_device_unregister() 用于从 Linux 内核中注销平台设备,以进行清理和释放操作。

#include <linux/platform_device.h> 

/*从内核注销平台驱动*/ 
void platform_driver_unregister(struct platform_driver *pdrv);
/*从内核注销平台驱动*/
void platform_driver_unregister(struct platform_driver *drv)  
{  
    driver_unregister(&drv->driver);  
}
/*移除平台驱动底层代码*/
void driver_unregister(struct device_driver *drv)  
{  
    /*参数检查:验证传入的设备驱动程序指针是否有效。*/
    if (!drv || !drv->p) {  
        printk(KERN_WARNING "Unexpected driver unregister!\n");  
        return;  
    }  
  
    /*移除属性组:从内核中移除与设备驱动程序关联的属性组*/
    if (drv->groups)  
        sysfs_remove_groups(&drv->p->kobj, drv->groups);  
    
    /*从总线中移除驱动程序,可能调用remove相关回调*/
    bus_remove_driver(drv);  
  
    kfree(drv->p->klist_devices.k_list);  
    kfree(drv->p);  
  
    module_put(drv->owner);  
}  

49.1.3 platform_driver 结构体

        platform_driver 结构体是 Linux 内核中用于编写平台设备驱动程序的关键数据结构,它包含驱动的匹配设备ID表,和一系列驱动操作相关的回调函数

/*平台总线驱动结构体*/
struct platform_driver {  
    int (*probe)(struct platform_device *);  // 探测函数,初始化设备  
    int (*remove)(struct platform_device *); // 移除函数,清理资源  
    void (*shutdown)(struct platform_device *); // 关闭函数,系统关闭时调用  
    int (*suspend)(struct platform_device *, pm_message_t); // 挂起函数  
    int (*resume)(struct platform_device *); // 恢复函数  
  
    struct device_driver driver; // 通用设备驱动程序数据,如驱动程序的名称、总线类型等。
                                 // 其中,name 参数需要与 platform_device 的 .name 参数相同,
                                 // 以便匹配成功并调用 probe 函数。 
    const struct platform_device_id *id_table; // 设备ID表,用于匹配设备和驱动 
                                //确定哪个平台设备与该驱动程序匹配。其优先级高于 driver.name。 
    bool prevent_deferred_probe; // 是否禁用延迟探测  
};

探测函数probe:系统检测到与驱动程序匹配的平台设备时,调用此函数初始化和配置设备。

移除函数remove:当平台设备从系统中移除时,调用此函数来执行清理和释放资源的操作。

关闭函数shutdown:当系统关闭时,调用此函数来执行与平台设备相关的关闭操作。

挂起函数suspend:系统进入挂起状态时,执行与平台设备相关的挂起操作。

恢复函数resume:系统从挂起状态恢复时,执行与平台设备相关的恢复操作。

device_driver结构体

        与设备驱动程序相关的通用数据,如驱动程序的名称、总线类型等。其中,name 参数需要与 platform_device 的 .name 参数相同,以便匹配成功并调用 probe 函数。

设备与驱动关联表id_table

        通过该表,可以确定哪个平台设备与该驱动程序匹配。其优先级高于 driver.name。

探测函数禁用布尔值

        如果设置为 true,则延迟探测将被禁用。

/*平台设备id匹配表*/
static const struct platform_device_id my_id_table[] = {  
    { "my_device", 0 },  
    { },  
};

49.2 注册平台总线驱动实验

// 平台设备的探测函数
static int my_platform_probe(struct platform_device *pdev)
{
    printk(KERN_INFO "my_platform_probe: Probing platform device\n");
    // 添加设备特定的操作
    // ... return 0;
}

// 平台设备的移除函数
static int my_platform_remove(struct platform_device *pdev)
{
    printk(KERN_INFO "my_platform_remove: Removing platform device\n");
    // 清理设备特定的操作
    // ... return 0;
}

// 定义平台驱动结构体
static struct platform_driver my_platform_driver = {
    .probe = my_platform_probe, .remove = my_platform_remove, 
    .driver = {
        .name = "my_platform_device", 
        .owner = THIS_MODULE, 
    }
};

// 模块初始化函数
static int __init my_platform_driver_init(void)
{
    int ret;
    // 注册平台驱动
    ret = platform_driver_register(&my_platform_driver);
    if (ret) {
        printk(KERN_ERR "Failed to register platform driver\n");
        return ret;
    }
    printk(KERN_INFO "my_platform_driver: Platform driver initialized\n");
    return 0;
}

// 模块退出函数
static void __exit my_platform_driver_exit(void)
{
    // 注销平台驱动
    platform_driver_unregister(&my_platform_driver);
    printk(KERN_INFO "my_platform_driver: Platform driver exited\n");
}

        平台总线设备驱动用来处理平台总线设备的生命周期,包括探测、移除、系统关闭、挂起、恢复这几个阶段。

        首先加载平台设备.ko文件,     来到/sys/bus/platform/devices 目录下,可以看到我们创建的 my_platform_device 平台总线设备文件夹

        然后加载平台设备驱动.ko文件,来到/sys/bus/platform/drivers 目录下,可以看到我们创建的 my_platform_driver 平台驱动文件夹就成功生成了。

第五十章 probe 函数编写

        驱动是要控制硬件的,因此正确做法是在 platform_driver的 probe函数中获得设备硬件资源。

50.1 获取 device 资源

方法1:直接访问 platform_device结构体的资源数组

        在Linux内核中,platform_device结构体包含了一个 resource数组,该数组存储了设备的硬件资源信息。驱动程序可以直接访问这个数组来获取所需的资源。

/*直接访问platform_device结构体的resource数组获取资源*/
if (pdev->num_resources >= 2) {  
    struct resource *res_mem = &pdev->resource[0]; // 获取内存资源  
    struct resource *res_irq = &pdev->resource[1]; // 获取中断资源  
    printk("Memory Resource: start = 0x%llx, end = 0x%llx\n", res_mem->start, res_mem->end);  
    printk("IRQ Resource: number = %lld\n", res_irq->start);  
}

方法2:使用 platform_get_resource()获取硬件资源

        platform_get_resource()提供了一种更灵活的方式来获取 platform_device的资源。它允许驱动程序根据资源的类型和索引来获取特定的资源。

/*获取平台设备的资源*/
struct resource *platform_get_resource(struct platform_device *pdev, //平台设备结构体
                                       unsigned int type,            //资源类型宏
                                       unsigned int num);            //资源编号
/*
示例标志位
#define IORESOURCE_IO       0x00000100  // I/O 端口资源  
#define IORESOURCE_MEM      0x00000200  // 内存资源  
  
// 资源属性和特征相关  
#define IORESOURCE_READONLY 0x00000400  // 资源是只读的  
#define IORESOURCE_CACHEABLE 0x00000800 // 资源支持缓存  
  
// 其他状态和控制  
#define IORESOURCE_DISABLED 0x00001000  // 资源当前被禁用  
#define IORESOURCE_BUSY     0x00002000  // 驱动程序将此资源标记为繁忙
*/
struct resource *res_mem;  
res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 获取第一个内存资源  
if (res_mem) {  
    printk("Memory Resource: start = 0x%llx, end = 0x%llx\n", res_mem->start, res_mem->end);  
} else {  
    // 处理获取内存资源失败的情况  
    printk("Failed to get memory resource\n");  
}

第五十三章 点亮 LED 灯(平台总线)

        编写完成的 platform_led.c 代码。注册平台总线设备模块。

        open设备文件以后,向设备文件写入1,通过寄存器点灯,写入0,通过寄存器关灯。        

        在 模块入口函数中,通过

        platform_driver_register(&my_platform_driver); 注册平台驱动

        在 平台驱动的 probe函数中,注册字符设备cdev。完成整套逻辑。

struct device_test{
    dev_t dev_num; //设备号
    int major ; //主设备号
    int minor ; //次设备号
    struct cdev cdev_test; // cdev
    struct class *class; //类
    struct device *device; //设备
    char kbuf[32];
    unsigned int *vir_gpio_dr;
};

struct device_test dev1;

/*file_operations结构体填充部分*/
/*打开设备函数*/
static int cdev_test_open(struct inode *inode, struct file *file)
{
    file->private_data=&dev1;//设置私有数据
    printk("This is cdev_test_open\r\n");
    return 0;
}

/*向设备写入数据函数*/
static ssize_t cdev_test_write(struct file *file,
                               const char __user *buf,
                               size_t size,
                               loff_t *off)
{
    struct device_test *test_dev=(struct device_test *)file->private_data;
    // copy_from_user:用户空间向内核空间传数据
    if (copy_from_user(test_dev->kbuf, buf, size) != 0) 
    {
        printk("copy_from_user error\r\n");
        return -1;
    }

    //如果应用层传入的数据是 1,则打开灯
    if(test_dev->kbuf[0]==1){ 
        *(test_dev->vir_gpio_dr) = 0x8000c040; //设置数据寄存器的地址
        printk("test_dev->kbuf [0] is %d\n",test_dev->kbuf[0]); //打印传入的数据
    }
    //如果应用层传入的数据是 0,则关闭灯
    else if(test_dev->kbuf[0]==0) 
    { *(test_dev->vir_gpio_dr) = 0x80004040; //设置数据寄存器的地址
        printk("test_dev->kbuf [0] is %d\n",test_dev->kbuf[0]); //打印传入的数据
    }
    return 0;
}

/**从设备读取数据*/
static ssize_t cdev_test_read(struct file *file, 
                              char __user *buf, 
                              size_t size, 
                              loff_t *off)
{
    struct device_test *test_dev=(struct device_test *)file->private_data;
    // copy_to_user:内核空间向用户空间传数据
    if (copy_to_user(buf, test_dev->kbuf, strlen( test_dev->kbuf)) != 0) 
    {
        printk("copy_to_user error\r\n");
        return -1;
    }
    printk("This is cdev_test_read\r\n");
    return 0;
}

/*用户调用close关闭设备文件时,release函数被调用*/
static int cdev_test_release(struct inode *inode, struct file *file)
{
    printk("This is cdev_test_release\r\n");
    return 0;
}

/*设备操作函数*/
struct file_operations cdev_test_fops = {
    .owner = THIS_MODULE, //将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
    .open = cdev_test_open, //将 open 字段指向 chrdev_open(...)函数
    .read = cdev_test_read, //将 open 字段指向 chrdev_read(...)函数
    .write = cdev_test_write, //将 open 字段指向 chrdev_write(...)函数
    .release = cdev_test_release, //将 open 字段指向 chrdev_release(...)函数
};

/*平台设备驱动函数编写*/
static int my_platform_driver_probe(struct platform_device *pdev)
{
    struct resource *res_mem;
    int ret;
    res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res_mem) {
        dev_err(&pdev->dev, "Failed to get memory resource\n");
        return -ENODEV;
    }

    /*注册字符设备驱动*/
    /*1 创建设备号*/
    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name"); //动态分配设备号
    if (ret < 0)
    {
            goto err_chrdev;
    }

    printk("alloc_chrdev_region is ok\n");
    dev1.major = MAJOR(dev1.dev_num); //获取主设备号
    dev1.minor = MINOR(dev1.dev_num); //获取次设备号
    printk("major is %d \r\n", dev1.major); //打印主设备号
    printk("minor is %d \r\n", dev1.minor); //打印次设备号

    /*2 初始化 cdev*/
    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_fops);

    /*3 添加一个 cdev,完成字符设备注册到内核*/
    ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if(ret<0)
    {
        goto err_chr_add;
    }

    /*4 创建设备类(设备节点)*/
    dev1. class = class_create(THIS_MODULE, "test");
    if(IS_ERR(dev1.class))
    {
        ret=PTR_ERR(dev1.class);
        goto err_class_create;
    }

    /*5 创建设备*/
    dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if(IS_ERR(dev1.device))
    {
        ret=PTR_ERR(dev1.device);
        goto err_device_create;
    }

    dev1.vir_gpio_dr=ioremap(res_mem->start,4); //将物理地址转化为虚拟地址
    if(IS_ERR(dev1.vir_gpio_dr))
    {
        ret=PTR_ERR(dev1.vir_gpio_dr); //PTR_ERR()来返回错误代码
        goto err_ioremap;
    }
    return 0;

err_ioremap:
    iounmap(dev1.vir_gpio_dr);
err_device_create:
    class_destroy(dev1.class); //删除类
err_class_create:
    cdev_del(&dev1.cdev_test); //删除 cdev
err_chr_add:
    unregister_chrdev_region(dev1.dev_num, 1); //注销设备号
err_chrdev:
    return ret;
}

/*设备驱动remove函数*/
static int my_platform_driver_remove(struct platform_device *pdev)
{
    // 设备移除操作
    return 0;
}

static struct platform_driver my_platform_driver = {
    .driver = {
        .name = "my_platform_device", // 与 platform_device.c 中的设备名称匹配
        .owner = THIS_MODULE, },
        .probe = my_platform_driver_probe,
        .remove = my_platform_driver_remove, };

/*模块入口函数*/
static int __init my_platform_driver_init(void)
{
    int ret;
    ret = platform_driver_register(&my_platform_driver); // 注册平台驱动
    if (ret) {
        printk("Failed to register platform driver\n");
        return ret;
    }
    printk("Platform driver registered\n");
    return 0;
}

/*模块出口函数*/
static void __exit my_platform_driver_exit(void)
{
    /*注销字符设备*/
    unregister_chrdev_region(dev1.dev_num, 1); //注销设备号
    cdev_del(&dev1.cdev_test); //删除 cdev
    device_destroy(dev1.class, dev1.dev_num); //删除设备
    class_destroy(dev1.class); //删除类
    platform_driver_unregister(&my_platform_driver); // 注销平台驱动
    printk("Platform driver unregistered\n");
}

module_init(my_platform_driver_init);
module_exit(my_platform_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");

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

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

相关文章

Java 如何从图片上提取文字

生活中我们可能会遇到想从图片上直接复制上边的文字&#xff0c;该如何获取呢&#xff0c;接下来看看如何使用Java程序实现从图片中读取文字。 实现过程 1、引入Tess4J 依赖 <!--Tess4J 依赖--> <dependency><groupId>net.sourceforge.tess4j</groupId…

AI绘画助手,对比总结出这六款

AI绘画助手&#xff0c;AI绘画助手近年来在艺术创作领域中逐渐崭露头角&#xff0c;成为许多艺术家和设计师的新宠。随着人工智能技术的飞速发展&#xff0c;这些工具不仅提高了创作效率&#xff0c;还为用户带来了前所未有的创意体验。下文将介绍六款简单好用的AI绘画助手&…

主成分分析法

主成分分析法 1. 基础知识1.1 向量1.1.1 样本均值1.1.2 向量投影 1.2 矩阵1.2.1 矩阵微分1.2.2 矩阵特征值和特征向量1.2.2.1 特征值和特征向量的几何意义1.2.2.2 特征向量与矩阵变换的关系1.2.2.3 特征值与矩阵的迹 1.3 Lagrange乘子法1.3.1 等式约束优化问题 2. 主成分分析法…

【JavaScript】LeetCode:56-60

文章目录 56 路径总和Ⅲ57 二叉树的最近公共祖先58 二叉树中的最大路径59 岛屿数量60 腐烂的橘子 56 路径总和Ⅲ 递归遍历每个节点所有可能的路径。pathSum()&#xff1a;返回所有节点满足条件的路径数目&#xff0c;traversal()&#xff1a;返回当前遍历节点满足条件的路径数目…

鸿蒙开发(NEXT/API 12)【状态查询与订阅】手机侧应用开发

注意 该接口的调用需要在开发者联盟申请设备基础信息权限与穿戴用户状态权限&#xff0c;穿戴用户状态权限还需获得用户授权。 实时查询穿戴设备可用空间、电量状态。订阅穿戴设备连接状态、低电量告警、用户心率告警。查询和订阅穿戴设备充电状态、佩戴状态、设备模式。 使…

基于Apache和Tomcat的负载均衡实验报告

说明&#xff1a;本实验为浙江科技大学软件工程本科专业课程《软件体系结构》的实验报告。 五、总结 负载均衡的基础是集群&#xff0c;集群就是一组连在一起的计算机&#xff0c;从外部看它是一个系统&#xff0c;各节点可以是不同的操作系统或不同的硬件构成的计算机。负载…

【d56】【sql】完成sql 7题目

... 有一题感觉没意义&#xff0c;直接不刷

vue + echarts 快速入门

vue echarts 快速入门 本案例即有nodejs和vue的基础&#xff0c;又在vue的基础上整合了echarts Nodejs基础 1、Node简介 1.1、为什么学习Nodejs(了解) 轻量级、高性能、可伸缩web服务器前后端JavaScript同构开发简洁高效的前端工程化 1.2、Nodejs能做什么(了解) Node 打破了…

TCP-2;CSNSDWSSC;肿瘤血管及M1型巨噬细胞靶向肽

【TCP-2 简介】 TCP-2&#xff08;Tumor-conditioned medium-stimulated Clone-2&#xff09;是一种特定的靶向肽&#xff0c;主要针对肿瘤微环境中的肿瘤血管和M1型巨噬细胞。这种肽在肿瘤治疗领域显示出巨大的潜力&#xff0c;尤其是在提高药物递送的精确性、增强免疫介导的抗…

【网络安全】绕过 Etplorer 管理面板实现RCE

未经许可,不得转载。 文章目录 正文使用 ffuf 进行FUZZ查找漏洞漏洞复现目标网站:https://app.redacted.com 正文 使用 ffuf 进行FUZZ ffuf -u https://app.redacted.com/FUZZ -w wordlist.txt -c -r-c:表示彩色输出,方便用户在终端中查看结果。 -r:忽略响应中的重定向…

【重学 MySQL】四十三、多行子查询

【重学 MySQL】四十三、多行子查询 使用 IN 子查询示例&#xff1a;查找属于特定部门的员工 使用 ANY 和 ALL 子查询使用 ANY使用 ALL 注意事项 在 MySQL 中&#xff0c;多行子查询&#xff08;也称为 IN 子查询&#xff09;是指子查询返回多行数据&#xff0c;并且这些数据用于…

新质农业——水资源可持续管理

橙蜂智能公司致力于提供先进的人工智能和物联网解决方案&#xff0c;帮助企业优化运营并实现技术潜能。公司主要服务包括AI数字人、AI翻译、埃域知识库、大模型服务等。其核心价值观为创新、客户至上、质量、合作和可持续发展。 橙蜂智农的智慧农业产品涵盖了多方面的功能&…

基于SpringBoot+Vue的服装销售管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

C++ | Leetcode C++题解之第437题路径总和III

题目&#xff1a; 题解&#xff1a; class Solution { public:unordered_map<long long, int> prefix;int dfs(TreeNode *root, long long curr, int targetSum) {if (!root) {return 0;}int ret 0;curr root->val;if (prefix.count(curr - targetSum)) {ret pref…

CaChe的基本原理

目录 一、Cache的定义与结构 二、Cache的工作原理 三、Cache的映射与替换策略 四、Cache的写操作处理 Cache&#xff0c;即高速缓冲存储器&#xff0c;是计算机系统中位于CPU与主存之间的一种高速存储设备。它的主要作用是提高CPU对存储器的访问速度&#xff0c;从而优化系…

YOLOv8改进 - 注意力篇 - 引入SCAM注意力机制

一、本文介绍 作为入门性篇章&#xff0c;这里介绍了SCAM注意力在YOLOv8中的使用。包含SCAM原理分析&#xff0c;SCAM的代码、SCAM的使用方法、以及添加以后的yaml文件及运行记录。 二、SCAM原理分析 SCAM官方论文地址&#xff1a;SCAM文章 SCAM官方代码地址&#xff1a;SC…

解决 Macos下 Orbstack docker网络问题

两种解决方法&#xff0c;第一种开代理 参考 —— 但是我这一种没成功&#xff0c;第二种方法是换镜像源 { "registry-mirrors": ["http://hub-mirror.c.163.com","https://docker.mirrors.ustc.edu.cn","https://mirrors.tencent.com&q…

安防监控/视频系统EasyCVR视频汇聚平台如何过滤134段的告警通道?

视频汇聚/集中存储EasyCVR安防监控视频系统采用先进的网络传输技术&#xff0c;支持高清视频的接入和传输&#xff0c;能够满足大规模、高并发的远程监控需求。平台支持国标GB/T 28181协议、部标JT808、GA/T 1400协议、RTMP、RTSP/Onvif协议、海康Ehome、海康SDK、大华SDK、华为…

大麦演唱会门票

切勿再令您所爱的人耗费高昂的价格去购置黄牛票 ⚠️核心内容参考: 据悉&#xff0c;于购票环节&#xff0c;大麦凭借恶意流量清洗技术&#xff0c;于网络层实时甄别并阻拦凭借自动化手段发起下单请求的流量&#xff0c;强化对刷票脚本、刷票软件以及虚拟设备的识别能力&#…

开源 AI 智能名片 2+1 链动模式 S2B2C 商城小程序的数据运营策略与价值创造

一、引言 1.1 研究背景 在当今数字化时代&#xff0c;数据运营已成为企业发展的核心驱动力。开源 AI 智能名片 21 链动模式 S2B2C 商城小程序作为一种创新的营销工具&#xff0c;与数据运营紧密相连。该小程序通过集成人工智能、大数据分析等先进技术&#xff0c;能够实时收集…