Linux驱动学习—设备树及设备树下的platform总线

news2024/11/28 19:46:34

1、什么是设备树?

设备树是一种描述硬件资源的数据结构。他通过bootloader将硬件资源传给内核,使得内核和硬件资源 描述相对独立。

2、设备树的由来

2.1 平台总线的由来

要想了解为什么会有设备树,设备树是怎么来的,我们就要先来回顾以下在没有设备树之前我们是怎么来写一个驱动程序的。以字符设备驱动代码框架为例,我们一起一起来回顾下。任何的设备驱动的编写,Linux已经为我们打好了框架,我们只需要做完形填空一样填写就可以了。

下面是注册字符设备驱动框架图:

具体过程可以参考下面这篇文章:

Linux驱动学习—字符设备驱动注册详解-CSDN博客

下面是注册杂项设备驱动框架图:

有关杂项设备驱动注册流程可以参考下面这篇:

Linux驱动学习—杂项设备驱动注册-CSDN博客

通过这些框架,我们可以很容易编写我们 的驱动代码,但是,当我们用这个框架非常熟练的时候,我们就会发现虽然这个方法很简单,但是非常不容易扩展,当有很多很多类似的设备的时候,如果我们都是按照这个框架来完成,那就要写很多遍这个流程,但是多个相似设备之间真正差异的地方只有初始化硬件部分,其他步骤的代码基本都是一样的。这样就会造成大量的重复代码。但是,我们在编写驱动代码的时候,要尽量做到代码的复用,也就是一套驱动尽量可以兼容很多设备,如果我们还按照这个来编写就不太符合我们的规则了。

为了实现这个目标,我们就要吧通用的代码和有差异的代码分离出来,来增强我们驱动代码的可移植性。所以,设备驱动分离的思想就应运而生了,在Linux中,我们是在写代码的时候进行分离。分离是把一些不相似的东西放到device.c,把相似的东西放到driver.c,如果有很多相似的设备或者平台,我们只要修改device.c就可以了,这样我们重复性的工作就大大的减少了。这就是平台总线的由来。

2.2 平台总线这个方法有什么弊端呢?(设备树由来)

当我们用这个方法用习惯以后就会发现,假如soc不变,我们每换一个平台,都要修改C文件,并且还要重新编译。而且会在arch/arm/plat-xxx和arch/arm/mach-xxx下面留下大量关于板级细节的代码。并不是说这个方法不好,只是从Linux的发展来看,这些代码相对于Linux内核来说就是“垃圾代码”,而且这些“垃圾代码”非常多。

为了改变这个现状,设备树也就被引进到Linux上了,用来剔除相对内核来说的“垃圾代码”,即用设备树文件来描述这些设备信息,也就是代替device.c文件,虽然拿到了内核外面,但是platform匹配基本不变,并且相比于之前的方法,使用设备树不仅可以去掉大量的“垃圾代码”,并且采用文本格式,方便阅读和修改,如果需要修改部分资源,我们也不用在重新编译内核了,只需要把设备树源文件编译成二进制文件,在通过bootloader传递给内核就可以了。内核对其进行解析和展开得到关于硬件的拓扑图。我们通过内核提供的接口剖获取设备树的节点和属性就可以了。即内核对于同一soc的不同主板,只需要换设备树文件dtb即可实现不同主板的无差异支持,而无需更换内核文件。

3、设备树的基本概念

3.1 为啥叫设备树呢?

因为他的语法结构像树一样,所以管它叫设备树

3.2常用名词解释

<1>DT:Device Tree           //设备树
<2>FDT:Flattened Device Tree//展开设备树//开放固件,设备树起源于OF,所以我们在设备树中可以看到很多of字母的函数
<3>device tree source(dts)  //设备树代码
<4>device tree source includeDTB(dtsi) //更通用的设备树代码,也就是相同芯片但不同平台都可以使用的代码
<5>device tree blob(dtb)    //DTS编译后得到的DTB文件
<6>device tree complier(dtc) //设备树编译器

dtsi:一个 SOC 可以作出很多不同的板子,这些不同的板子肯定是有共同的信息, 将这些共同的信息提取出来作为一个通用的文件,其他的.dts 文件直接引用这个通用文件即可,这个通用文件就是.dtsi 文件,类似于 C 语言中的头文件。

DTS,DTSI,DTB,DTC他们之间的关系如下:

4、设备树基本语法

4.1 设备树基本框架

<1>设备从根节点开始,每个设备都是节点。
<2>节点和节点之间可以相互嵌套,形成父子关系。
<3>设备的属性用key-value对(键值对)来描述,每个属性用分号结束

4.2 设备树语法

4.2.1节点

什么是节点呢?节点就好比一颗大树,从树的主干开始,然后有一节一节的树枝,这个就叫节点。在代码中的节点是什么样子的呢。我们把上面模板的根节点摘出来,如下所示,这个就是根节点。相当于大树的树干。

/{
};//分号

而树枝就相当于设备树的子节点,同样我们把子节点摘出来就是根节点里面的node1和node2,如下所示:

/{  //根节点
    node1//子节点node1
    {
    };
    node2//子节点node2
    {
    };
};//分号

一个树枝是不是也可以继续分成好几个树枝呢,也就是说子节点里面可以包含子子节点。所以child-node1和child-node2是node1和node2的子节点,如下所示:

/{  //根节点
    node1//子节点node1
    {
        child-node1//子子节点
        {
        };
    };
    node2//子节点node2
    {
        child-node2//子子节点
        {
        };
    };
};//分号
4.2.2 节点名称

节点的命名有一个固定的格式。

格式:<名称>[@<设备地址>]

(1)<名称>节点的名称也不是任意起的,一般要体现设备的类型而不是特点的型号,比如网口,应该命名为ethernet,而不是随意起一个,比如111。

(2)<设备地址>就是用来访问该设备的基地址。但并不是说在操作过程中来描述一个地址,其主要用来区分用。

(3)注意事项:A、同一级的节点只要地址不一样,名称是可以不唯一的。

B、设备地址是一个可选选项,可以不写。但为了任意区分和理解,一般是都写的。

4.2.3 节点别名

当我们找一个节点的时候,必须书写完整的节点路径,如果节点名很长,那么我们在引用的时候就十分不方便,所以,设备树允许我们用下面的形式为节点标注引用(起别名)。举例:

uart8:serial@02288000

其中uart8就是这个节点名称的别名,serial@02288000就是节点名称。

4.2.4 节点引用

一般往节点里面添加内容的时候,不会直接把直接添加的内容写到节点里面,而是通过节点的引用来添加。

举例:

&uart8{
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_uart8>;
    status = "okay";
};

&uart8表示引用节点别名为uart8的节点。并往节点添加以下内容:

    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_uart8>;
    status = "okay";

注意事项:编译设备树的时候,相同的节点的不同属性信息都会被合并,相同节点的先相同的属性会被重写,使用引用可以避免移植者四处找节点。如dts和dtsi里面都有根节点,但最终会合并成一个根节点。

4.2.5属性
(1)reg属性

reg属性用来描述一个设备的地址范围。格式:

reg=<add1 lenth1 [add2 length2]...>

举例:

serial@02288000{
    reg=<101F2000 0x1000>;//101F2000是起始地址,0x1000是长度
};
(2)#address-cells和#size-cells属性

#address-cells用来设置子节点中reg地址的数量

#size-cells用来设置子节点中reg地址长度的数量

举例:

cpu{
    #address-cells = <1>;//用来设置子节点中reg地址的数量
    #size-cells = <1>;//用来设置子节点中reg地址长度的数量
    serial@02288000{
        reg=<101F2000 0x1000>;//101F2000是起始地址,0x1000是长度
    };
};

其中#address-cells和#size-cells均为1,也就是说我们子节点里面的reg属性里这个寄存器组的起始地址只有一个,长度也只有一个。所以101F2000是起始地址,0x1000是长度。

(3)compatible属性

compatible是一个自负床列表,可以在代码中进行匹配。

举例:

compatible = “led";
(4)status属性

status属性的值类型是字符串,这里我们只要记住两个常用的即可,一个是okay,表示涉笔可以正常使用,一个disable,表示设备不能正常使用。

5、在设备树中添加自定义节点

5.1 命令查看设备树节点

<1> cd /proc/device-tree/下就可看到
<2> cd /sys/firmware/devicetree/base/下就可看到

这是设置uboot环境变量的:

5.2 安装dtc工具

(1)直接make dtbs出现这种情况,说明环境没有配置对,则需要安装dtc工具

(2)安装dtc工具

apt-get install device-tre-compiler

5.3 实验:添加一个节点

如下图,添加一个节点test,对这个节点取别名为test1,然后节点引用&test1,一般往节点里面添加内容的时候,不会直接把直接添加的内容写到节点里面,而是通过节点的引用来添加。所以最终的compatible和status属性是节点引用里面的内容。

添加完之后编译dts,在内核源码路径下输入以下命令即可编译:

make ARCH=arm CROSS_COMPILE=arm-linux-guneabihf-  dtbs

把编译的dtb烧录到开发板,cd /proc/device-tree/目录下可以看到test节点已经生成。cat /proc/devicetree/test/compatible发现是test1234,cat /proc/devicetree/test/status发现是okay。

6、设备树中常见的of操作函数

设备都是以节点的形式“挂”到设备树上的,因此姚秀昂获取这个设备的其他属性信息,必须先获取到这个设备的节点。linux内核实验device_node结构体来描述一个节点,此结构体的第一在文件include/linux/of.h中,如下:

struct device_node {
    const char *name;//节点名字
    const char *type;//设备类型
    phandle phandle;
    const char *full_name;//节点全名
    struct fwnode_handle fwnode;
​
    struct  property *properties;//属性
    struct  property *deadprops;    /* removed properties */
    struct  device_node *parent;//父节点
    struct  device_node *child;//子节点
    struct  device_node *sibling;
    struct  kobject kobj;
    unsigned long _flags;
    void    *data;
#if defined(CONFIG_SPARC)
    const char *path_component_name;
    unsigned int unique_id;
    struct of_irq_controller *irq_trans;
#endif
};

节点的属性信息里面保存了驱动所需要的内容,因此对于属性值的提取非常重要,Linux内核中使用结构体property表示属性,此结构体同样定义在include/linux/of.h中,如下:

struct property {
    char    *name;//属性名字
    int length;//属性长度
    void    *value;//属性值
    struct property *next;//下一个属性
    unsigned long _flags;
    unsigned int unique_id;
    struct bin_attribute attr;
};

6.1获取设备树文件节点里面资源的步骤

<1>步骤一:查找我们要找的节点。

<2>步骤一:查找我们要找的属性值。

6.2 查找节点常用的of函数

<1>of_find_node_by_path函数

作用:函数通过路径来查找指定的节点。

函数原型:

static inline struct device_node *of_find_node_by_path(const char *path)
参数:
path:带有全路径的节点名,可以使用节点的别名,比如"/test"就是test这个节点的全路径。使用节点别名的路径是"/test1".
返回值:成功就返回找到的节点。失败则返回NULL。
<2>of_get_parent函数

作用:用于获取节点的父节点(如果有父节点的话)。

struct device_node *of_get_parent(const struct device_node *node);
node:要查找的父节点的节点
返回值:找到的父节点。
<3>of_get_next_child函数

作用:用于迭代的查找子节点。

static inline struct device_node *of_get_next_child(const struct device_node *node, 
struct device_node *prev)
参数如下:
node:父节点。
prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL表示从第一个子节点开始。
返回值:找到的下一个子节点。

6.3 查找节点属性常用的of函数

<1>of_get_property函数

作用:用于查找指定的属性。

static inline const void *of_get_property(const struct device_node *node,
                const char *name,
                int *lenp)
参数如下:   
np:设备节点。
name:属性名字。
lenp:属性值的字节数。
返回值:找到的属性。
<2> of_property_read_u8、of_property_read_u16、of_property_read_u32、of_property_read_u64

有些属性只有一个整型值,者四个函数就是用于读取这种只有一个整型值的属性,分别用于读取u8、u16、u32、u64类型属性值,函数原型如下:

static inline int of_property_read_u8(const struct device_node *np,
                       const char *propname,
                       u8 *out_value);
static inline int of_property_read_u16(const struct device_node *np,
                       const char *propname,
                       u16 *out_value);
static inline int of_property_read_u32(const struct device_node *np,
                       const char *propname,
                       u32 *out_value);
static inline int of_property_read_u64(const struct device_node *np,
                       const char *propname, u64 *out_value);
参数如下:
np:设备节点。
proname:要读取的属性名字。
out_value:读取的值。
返回值:0,读取成功,负值,读取失败。
<3>of_property_read_u8_array、of_property_read_u16_array、of_property_read_u32_array、of_property_read_u64_array

这四个函数分别是读取属性中u8、u16、u32、u64类型的数组数据,比如大多数的reg属性都是数组数据,可以使用这4个函数一次读取reg属性中的所有数据。这四个函数的原型如下:

static inline int of_property_read_u8_array(const struct device_node *np,
            const char *propname,u8 *out_values, size_t sz);
static inline int of_property_read_u16_array(const struct device_node *np,
            const char *propname, u16 *out_values, size_t sz);
static inline int of_property_read_u32_array(const struct device_node *np,
            const char *propname, u32 *out_values, size_t sz);
static inline int of_property_read_u64_array(const struct device_node *np,
            const char *propname, u64 *out_values, size_t sz);
参数:
np:设备节点。
proname:要读取的属性名字。
out_values:读取的数组值。
返回值:0,读取成功,负值,读取失败。
<4>of_property_read_string

作用:用于读取属性只呢个字符串值

int of_property_read_string(const struct device_node *np,
                   const char *propname,
                   const char **out_string);
参数:
np:设备节点。
proname:要读取的属性名字。
out_values:读取的字符串值。
返回值:0,读取成功,负值,读取失败。     
 

6.4 实验:把5.3添加的一个节点的值和属性读取出来

 
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
​
struct device_node *test_device_node;
struct property *test_node_property;
int size;
u32 out_values[2]={0};
const char *str=NULL;
​
static int hello_init(void)
{
    int ret = 0;
    printk("hello_init\n");
    
    //查找要查找的节点
    test_device_node = of_find_node_by_path("/test");
    if(test_device_node == NULL) {
        printk("test_device_node find error\n");
        return -1;
    }
    printk("test_device_node name is %s\n",test_device_node->name);//test
    
    //获取compatible属性内容
    test_node_property = of_find_property(test_device_node, "compatible", &size);
    if(test_node_property == NULL) {
        printk("test_node_property find error\n");
        return -1;
    }
    printk("test_node_property name is %s\n",test_node_property->name);//compatible
    printk("test_node_property->value is %s\n",test_node_property->value);//test1234
    
    //获取reg属性内容
    ret = of_property_read_u32_array(test_device_node, "reg", out_values, 2);
    if(ret < 0) {
        printk("of_property_read_u32_array is error\n");
        return -1;
    }
    printk("out_values[0]  is 0x%08x\n",out_values[0]);//0x020ac000
    printk("out_values[1]  is 0x%08x\n",out_values[1]);//0x00000004
    
    //获取status属性内容
    ret = of_property_read_string(test_device_node, "status", &str);
    if(ret < 0) {
        printk("of_property_read_string is error\n");
        return -1;
    }
    printk("status is %s\n",str);//okay
    
    return 0;
}
​
static void hello_exit(void) 
{
    printk("hello_exit\n");
}
​
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

加载驱动,打印如下:

7、设备树下的platform总线

7.1 传统方法下的platform总线

Linux驱动学习—平台总线模型-CSDN博客

之前这篇文章中是使用传统的方法对平台总线进行学习,什么是传统方法呢,就是硬件设备信息部分写在device.c,驱动部分写在driver.c中。而设备树下的platform总线 则是用设备树文件代替device.c。所以使用设备树的方法在配置好设备树文件后,只需编写driver.c。

7.2 of_iomap函数

作用:of_iomap函数用于直接内存映射,以前我们会通过ioremap函数来完成物理地址到虚拟地址的映射。

函数原型:

void __iomem *of_iomap(struct device_node *node, int index);
参数:
np:设备节点
index:reg属性中要完成内存映射的段,如果reg属性只有一段的话inbdex就设置0。
返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话就表示内存映射失败。

7.3 实验代码

Linux驱动学习—平台总线模型-CSDN博客

直接在上面文章的4.3小节platform driver.c上修改,主要实现的功能就是映射GPIO5的数据寄存器的内存地址,实现对数据寄存器的操作,从而实现对蜂鸣器引脚控制。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h> 
#include <linux/of.h>
#include <linux/of_address.h>
​
struct device_node *test_device_node;
struct property *test_node_property;
int size;
u32 out_values[2]={0};
const char *str=NULL;
unsigned int *vir_gpio_dr;
​
static const of_device_id of_match_table_test[] = {//匹配表
    {.compatible = "test1234"},
};
​
static const platform_device_id beep_id_table ={
    .name = "beep_test",
};
​
/*设备树节点compatible属性与of_match_table_test的compatible相匹配就会进入该函数,pdev是匹配成功后传入的设备树节点*/
int beep_probe(struct platform_device *pdev)
{
    int ret = 0;
    printk("beep_probe\n");
    
    /*
    //查找要查找的节点 pdev是匹配成功后传入的设备树节点,所以不需要用之前的方法进行查找了
    test_device_node = of_find_node_by_path("/test");
    if(test_device_node == NULL) {
        printk("test_device_node find error\n");
        return -1;
    }
    printk("test_device_node name is %s\n",test_device_node->name);//test
    
    //获取compatible属性内容
    test_node_property = of_find_property(test_device_node, "compatible", &size);
    if(test_node_property == NULL) {
        printk("test_node_property find error\n");
        return -1;
    }
    printk("test_node_property name is %s\n",test_node_property->name);//compatible
    printk("test_node_property->value is %s\n",test_node_property->value);//test1234
    */
    
    //获取reg属性内容
    ret = of_property_read_u32_array(pdev->dev.of_node, "reg", out_values, 2);
    if(ret < 0) {
        printk("of_property_read_u32_array is error\n");
        return -1;
    }
    printk("out_values[0]  is 0x%08x\n",out_values[0]);//0x020ac000
    printk("out_values[1]  is 0x%08x\n",out_values[1]);//0x00000004
    
    vir_gpio_dr = of_iomap(pdev->dev.of_node, 0);
    if(vir_gpio_dr == NULL) {
        printk("of_iomap  error\n");
        return -1;
    }
    return 0;
}
​
int beep_remove(struct platform_device *pdev)
{
    pritnk("beep_remove \n");
    return 0;
}
​
strcut platform_driver beep_device = {
    .probe = beep_probe,
    .remove = beep_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name  = "123",
        .of_match_table = of_match_table_test,//匹配表 
    },
    .id_table = &beep_id_table,
};
​
static int beep_driver_init(void)
{
    int ret = -1;
    ret = platform_driver_register(&beep_device);
    if(ret < 0) {
        printk("platform_driver_register error \n");
    }
    printk("platform_driver_register ok\n");
    return 0;
}
​
static void  beep_driver_exit(void)
{
    platform_driver_unregister(&beep_device);
    printk("beep_driver_exit \n");
}
​
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");

编译加载驱动,这样寄存器地址就获取成功了,我们可以注册一个杂项设备对数据引脚进行操作高低电平,从而实现对蜂鸣器的操作。

*vir_gpio_dr |= (1<<1);
*vir_gpio_dr &= ~(1<<1);

7.3.1 匹配优先级

先是platform_driver.driver.of_match_table.compatible,然后是platform_driver.driver.id_table,最后是platform_driver.driver.name

注意:设备树节点compatible属性与of_match_table_test的compatible相匹配就会进入probe函数,其函数参数pdev是匹配成功后传入的设备树节点

7.3.2 reg有多组参数时,of_iomap如何传参

当然,如果reg有多组参数的话,这一个是不一样的,举个例子:

 

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

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

相关文章

71内网安全-域横向网络传输应用层隧道技术

必备知识点&#xff1b; 代理和隧道技术的区别&#xff1f; 代理主要解决的是网络访问问题&#xff0c;隧道是对过滤的绕过&#xff0c; 隧道技术是为了解决什么 解决被防火墙一些设备&#xff0c;ids&#xff08;入侵检测系统&#xff09;进行拦截的东西进行突破&#xff0…

了解.NET 通用主机

写在前面 .NET 通用主机负责应用启动和生存期管理&#xff0c;主机是封装应用资源和生存期功能的对象&#xff0c;通用主机可用于其他类型的 .NET 应用程序&#xff0c;如控制台应用&#xff1b;.NET 通用主机基于类库Microsoft.Extensions.Hosting 来实现&#xff0c;本文记录…

保护Word或Excel的几种方法,总有一种满足你的需求

你已经在Microsoft Word或Excel中创建了一个重要或机密文件,你希望将其保密或至少保持安全。也许你想确保只有你和某些人可以阅读或编辑它。也许你想限制某人可以对文件进行的修改类型。你甚至可以向读者保证这是最终版本。如果你知道在Word和Excel中使用哪些工具以及它们是如…

安装Node修改Node镜像地址搭建Vue脚手架创建Vue项目

1、安装VSCode和Node 下载VSCode Visual Studio Code - Code Editing. Redefined 下载Node Node.js (nodejs.org) 检验是否安装成功&#xff0c;WinR,输入cmd命令&#xff0c;使用node -v可以查看到其版本号 2、修改镜像地址 安装好node之后&#xff0c;开始修改镜像地址 …

UntiyShader(五)属性、内置文件和变量

目录 一、如何使用属性 例子 ShaderLab中的属性的类型和Cg中的变量的类型之间的匹配关系 二、Unity提供的内置文件和变量 内置的包含文件 内置的变量 一、如何使用属性 在一开始我们提到过&#xff0c;材质和UnityShader之间有着密切的练习&#xff0c;我们可以通过材质面…

前后台分离开发

前后台分离开发 简介 前后台分离开发&#xff0c;就是在项目开发过程中&#xff0c;对于前端代码的开发由专门的前端开发人员负责&#xff0c;后端代码则由后端开发人员负责&#xff0c;这样可以做到分工明确、各司其职&#xff0c;提高开发效率&#xff0c;前后端代码并行开…

OpenOCD简介和下载安装(Ubuntu)

文章目录 OpenOCD简介OpenOCD软件模块OpenOCD源码下载OpenOCD安装 OpenOCD简介 OpenOCD&#xff08;Open On-Chip Debugger&#xff09;开放式片上调试器 OpenOCD官网 https://openocd.org/&#xff0c;进入官网点击 About 可以看到OpenOCD最初的设计是由国外一个叫Dominic Ra…

Qt高质量的开源项目合集

文章目录 1.Qt官网下载/文档2.第三方开源 1.Qt官网下载/文档 Qt Downloads Qt 清华大学开源软件镜像站 Qt 官方博客 2.第三方开源 记录了平常项目开发中用到的第三方库&#xff0c;以及一些值得参考的项目&#xff01; Qt AV 基于Qt和FFmpeg的跨平台高性能音视频播放框…

这本书没有一个公式,却讲透了数学的本质

这本书没有一个公式&#xff0c;却讲透了数学的本质&#xff01; 《数学的雨伞下&#xff1a;理解世界的乐趣》。一本足以刷新观念的好书&#xff0c;从超市到对数再到相对论&#xff0c;娓娓道来。对于思维空间也给出一个更容易理解的角度。 作者&#xff1a;米卡埃尔•洛奈 …

汇川PLC(H5U):伺服电机点动控制

一、基本概念理解 伺服电机旋转一圈的脉冲数 伺服电机上都装有一个编码器&#xff0c;这个编码器用于产生脉冲&#xff0c;常见伺服电机转一圈一般是产生10000个脉冲。相当于电机转360&#xff0c;产生了10000个脉冲&#xff0c;那旋转1就产生10000/360 27.7个脉冲。同理&am…

SDG大数据平台简介

联合国可持续发展目标&#xff08;Sustainable Development Goals&#xff09;缩写SDGs&#xff0c;是联合国制定的17个全球发展目标&#xff0c;在2000-2015年千年发展目标&#xff08;MDGs&#xff09;到期之后继续指导2015-2030年的全球发展工作。&#xff08;摘自百度&…

ROS安装PR2

一、PR2介绍 PR2是Willow Garage公司设计的机器人平台&#xff0c;也是目前科研领域经常用到的机器人之一。PR2有两条手臂&#xff0c;每条手臂七个关节&#xff0c;手臂末端是一个可以张合的夹爪&#xff1b;PR2依靠底部的四个轮子移动&#xff0c;在头部、胸部、肘部、夹爪上…

第 378 场周赛 解题报告 | 珂学家 | 分类讨论场

前言 整体评价 感觉是分类讨论场&#xff0c;t3用二分&#xff0c;是因为二分不会错&#xff0c;直接分类讨论容易WA. t4一开始看错题了&#xff0c;T_T, 看成翻转&#xff0c;写了半天StringHash, 还用上双hash&#xff0c;共8个StringHash。 重排的话&#xff0c;其实统计…

搜索引擎推广的实践技巧提升你的品牌影响力-华媒舍

搜索引擎推广是一种有效提升品牌影响力的推广策略。通过关键词优化、广告创意设计、定向投放和数据分析与优化等实践技巧&#xff0c;可以提高品牌的知名度、点击率和转化率。在实施引擎霸屏推广之前&#xff0c;还需对实践效果进行评估&#xff0c;以确保推广策略的有效性和适…

CMake入门教程【基础篇】在Windows、Linux上安装CMake

文章目录 windows平台第1步&#xff1a;下载CMake第2步&#xff1a;安装CMake第3步&#xff1a;验证安装第4步&#xff1a;初次使用第5步&#xff1a;构建和运行 Linux平台 windows平台 第1步&#xff1a;下载CMake https://cmake.org/ 首先&#xff0c;访问CMake官方网站。…

Python数据科学应用从入门到精通--Python读取、合并SPSS数据文件

在很多情况下&#xff0c;我们需要调用SPSS软件产生的数据&#xff0c;下面通过示例来进行讲解。首先需要将本书提供的数据文件存储在安装spyder-py3的默认路径位置&#xff08;C:/Users/Administrator/.spyder-py3/&#xff0c;注意具体的安装路径可能与此不同&#xff09;&am…

rsync的介绍与使用

rsync的介绍与使用 一、简介 rsync&#xff08;remote synchronize&#xff09;是Liunx/Unix下的一个远程数据同步工具。它能够以非常高效的方式传输和同步文件&#xff0c;它可以将一个目录的文件快速地同步到另一个目录&#xff0c;还可以通过网络快速同步多台主机间的文件…

2024年总结的前端学习路线分享(学习导读)

勤学如春起之苗&#xff0c;不见其增&#xff0c;日有所长 。辍学如磨刀之石&#xff0c;不见其损&#xff0c;日有所亏。 在写上一篇 2023年前端学习路线 的时候&#xff0c;时间还在2023年初停留&#xff0c;而如今不知不觉时间已经悄然来到了2024年&#xff0c;回顾往昔岁月…

基于Window下的Node.js安装教程

基于Window下的Node.js安装教程 1.安装包下载安装2.安装字蚁2.1压缩字体 写这篇文章&#xff0c;主要是方便自己以后再安装&#xff0c;容易查看&#xff0c;相关内容有参考网上内容和自己想法。 1.安装包下载安装 Node官网 进入终端查看&#xff1a; echo %PATH%ec…

【信息安全原理】——期末复习(冲刺篇)

&#x1f4d6; 前言&#xff1a;快考试了&#xff0c;做篇期末总结&#xff0c;都是重点与必考点。 题型&#xff1a;简答题&#xff08;45分&#xff09;、协议分析题&#xff08;210分&#xff09;&#xff08;给一个报文或工作流程&#xff0c;分析存在的问题&#xff09;、…