当前位置: 首页 > news >正文

嵌入式Linux学习: 设备树实验

设备树(DeviceTree)是一种硬件描述机制,用于在嵌入式系统和操作系统中描述硬件设备的特性、连接关系和配置信息。它提供了一种与平台无关的方式来描述硬件,使得内核与硬件之间的耦合度降低,提高了系统的可移植性和可维护性。

1. 为什么需要设备树这种机制?

在Linux内核V2.6版本之前,尤其是ARM架构下,用于描述不同硬件信息的文件大多存放在arch/arm/plat-xxxarch/arm/mach-xxx文件夹下。这些文件包含了大量的板级硬件信息,而这些信息对于内核的通用性来说并无实质性帮助,反而导致了内核代码的冗余和复杂化。随着每年新推出的ARM架构芯片和基于这些芯片的板子数量不断增加,板级信息文件也呈指数级增长,这使得内核维护变得异常困难
设备树通过一种结构化的方式来描述硬件平台,包括CPU、内存、外设、总线等资源。这种描述方式使得驱动程序和内核代码能够更容易地适应不同的硬件平台,而无需对内核代码进行大量的修改。通过更换设备树文件(.dtb),即可实现不同主板的无差异支持,从而大大提高了代码的可移植性和可重用性

2. 设备树基础知识

设备树专业术语介绍:

DTS(Device Tree Source) : DTS 是设备树的源文件, 采用一种类似于文本的语法来描述硬件设备的结构、 属性和连接关系。 DTS 文件以.dts 为扩展名, 通常由开发人员编写。 它是人类可读的形式, 用于描述设备树的层次结构和属性信息。

DTSI(Device Tree Source Include) : DTSI 文件是设备树源文件的包含文件。 它扩展了 DTS文件的功能, 用于定义可重用的设备树片段。DTSI 文件以.dtsi 为扩展名, 可以在多个 DTS 文件中包含和共享。 通过使用 DTSI, 可以提高设备树的可重用性和可维护性(和 C 语言中头文件的作用相同) 。

DTB(Device Tree Blob) : DTB 是设备树的二进制表示形式。 DTB 文件是通过将 DTS 或 DTSI文件编译而成的二进制文件, 以.dtb 为扩展名。 DTB 文件包含了设备树的结构、 属性和连接信息, 被操作系统加载和解析。 在运行时, 操作系统使用 DTB 文件来动态识别和管理硬件设备。

DTC(Device Tree Compiler) : DTC 是设备树的编译器。 它是一个命令行工具, 用于将 DTS和 DTSI 文件编译成 DTB 文件。 DTC 将文本格式的设备树源代码转换为二进制的设备树表示形 式, 以便操作系统能够加载和解析。 DTC 是设备树开发中一个重要的工具。

DTS、 DTSI、 DTB 和 DTC 之间的关系:

  • 开发人员使用文本编辑器编写 DTS 和 DTSI 文件, 描述硬件设备的层次结构、 属性和连接关系。
  • DTSI 文件可以在多个 DTS 文件中包含和共享, 以提高设备树的可重用性和可维护性。
  • 使用 DTC 编译器, 开发人员将 DTS 和 DTSI 文件编译成二进制的 DTB 文件
    在这里插入图片描述
  • 操作系统在启动过程中加载和解析 DTB 文件, 以识别和管理硬件设备。

设备树文件路径
ARM体系结构: I.MX6ULL

(内核目录)/arch/arm/boot/dts

瑞芯微的RK3568

(内核目录)/arch/arm64/boot/dts/rockchip
DTC工具的使用

在 Linux 内核源码中, DTC(Device Tree Compiler) 的源代码和相关工具通常存放在(内核目录)/scripts/dtc/目录中在这里插入图片描述
在编译完源码之后 dtc 设备树编译器会默认生成, 如果没有生成相应的 dtc 可执行文件,可以查看在内核默认配置文件中((内核目录)/.config) CONFIG_DTC 是否使能。
在这里插入图片描述

① 设备树的编译

dtc -I dts -O dtb -o output.dtb input.dts

其中, input.dts是输入的设备树源文件, output.dtb是编译后的二进制设备树文件。
编译器会验证设备树源文件的语法和语义, 生成与硬件描述相对应的设备树表示形式。

②设备树的反编译

dtc -I dtb -O dts -o output.dts input.dtb

将二进制设备树文件反编译为设备树源文件
input.dtb 是输入的二进制设备树文件,output.dts是反编译后的设备树源文件

3.设备树基本语法

3.1根节点

设备树使用一种层次结构,其中的根节点(Root Node)是整个设备树的起始点和顶层节点。

/dts-v1/;   // 设备树版本信息
/{ 			// 根节点开始// 可以在里面添加描述根节点的属性和配置
};  

子节点格式如下

[label:] node-name@[unit-address] {[properties definitions][child nodes]
};
  • 节点标签(Label) (可选) : 节点标签是一个可选的标识符, 用于在设备树中引用该节点。 标签允许其他节点直接引用此节点, 以便在设备树中建立引用关系。
  • 节点名称(Node Name) : 节点名称是一个字符串, 用于唯一标识该节点在设备树中的位置。 节点名称通常是硬件设备的名称, 但必须在设备树中是唯一的。
  • 单元地址(Unit Address)(可选):单元地址用于标识设备的实例。 它可以是一个整数、 一个十六进制值或一个字符串, 具体取决于设备的要求。 单元地址的目的是区分相同类型的设备的不同实例, 例如在下图 中名为 cpu 的节点通过它们的单元地址值 0 和 1 来区分, 名称为 ethernet 的节点通过其单元地址值 fe002000 和 fe003000 来区分。
    在这里插入图片描述
  • 属性定义(Properties Definitions) : 属性定义是一组键值对, 用于描述设备的配置和特性。 属性可以根据设备的需求进行定义, 例如寄存器地址、 中断号、 时钟频率等
  • 子节点(Child Nodes) : 子节点是当前节点的子项, 用于进一步描述硬件设备的子组件或配置。 子节点可以包含自己的属性定义和更深层次的子节点, 形成设备树的层次结构。

例子如下:

/dts-v1/;
/{
uart0: uart@fe001000 {compatible="ns16550";reg=<0xfe001000 0x100>;};
};

可以使用以下方法来修改uart@fe001000这个node

// 根节点之外使用label来引用node
&uart0 {status = “disabled”;
};
根节点之外使用全路径:
&{/uart@fe001000} {status = “disabled”;
};

address-cells 和 size-cells 属性

  • #address-cells 属性是一个位于设备树根节点的特殊属性, 它指定了设备树中地址单元的位数。 地址单元是设备树中用于表示设备地址的单个单位。 它通常是一个整数, 可以是十进制或十六进制值
    *#size-cells属性也是一个位于设备树根节点的特殊属性, 它指定了设备树中大小单元的位数。 大小单元是设备树中用于表示设备大小的单个单位。 它通常是一个整数, 可以是十进制或十六进制值。

示例1:

node1 {#address-cells = <1>;#size-cells = <1>;node1-child {reg = <0x02200000 0x4000>;// 其他属性和子节点的定义};
};
/* 解释: node1-child 节点的 reg 属性使用了 <0x02200000 0x4000> 表示地址和大小。
由于 #address-cells 的值为 <1>, 表示使用一个单元来表示地址。 #size-cells 的值也为 <1>, 表示使用一个单元来表示大小。
解释后的地址和大小值如下:
地址部分: 0x02200000 被解释为一个地址单元, 地址为 0x02200000。
大小部分: 0x4000 被解释为一个大小单元, 大小为 0x4000   
*/

示例2:

node1 {#address-cells = <2>;#size-cells = <0>;node1-child {reg = <0x0000 0x0001>;// 其他属性和子节点的定义};
};
/* 解释: node1-child 节点的 reg 属性使用了 <0x0000 0x0001> 表示地址。 由于#address-cells 的值为 <2>, 表示使用两个单元来表示地址。 #size-cells 的值为 <0>, 表示不使用单元来表示大小。
解释后的地址值如下:
地址部分: 0x0000 0x0001 被解释为两个地址单元, 其中第一个地址单元为 0x0000, 第二个地址单元为 0x0001。
*/ 

model属性

model 属性用于描述设备的型号或者名称(可选)

my_device{compatible = "device";model = "My Device";
};

status属性

status 属性用于描述设备或节点的状态

  • “okay”: 表示设备或节点正常工作, 可用。
  • “disabled”: 表示设备或节点被禁用, 不可用。
  • “reserved”: 表示设备或节点已被保留, 暂时不可用。
  • “fail”: 表示设备或节点初始化或操作失败, 不可用
my_device{compatible = "device";model = "My Device";status = "okay";
};

compatible属性

compatible 属性用于描述设备的兼容性信息,用于识别设备节点与驱动程序之间的匹配关系
compatible 属性的值是一个字符串或字符串列表
建议取这样的形式: “manufacturer,model”,即“厂家名,模块名”。

示例:

my_device {compatible = "vendor,device";  // 指定设备节点与特定厂商的特定设备兼容// 其他属性和子节点的定义
};compatible =  ["vendor,device1", "vendor,device2"]; //用于指定设备节点与多个设备兼容, 通常用于设备节点具有多种变体或配置compatible = "vendor,*"// 指定设备节点与特定厂商的所有设备兼容, 不考虑具体的设备标识。

3.2 aliases节点

aliases 节点是一个特殊的节点, 用于定义设备别名,位于设备树的根部

/dts-v1/;
/{aliases{mmc0 = &sdmmc0;serial0 = "/simple@fe000000/seria1@11c500";};
};
/*
1. mmc0 别名与设备树中的 sdmmc0 节点相关联。 通过使用别名 mmc0, 其他设备节点或客户端程序可以更方便地引用 sdmmc0 节点, 而不必直接使用其完整路径。
2. serial0 别名与设备树中的路径 /simple@fe000000/seria1@11c500 相关联。 通过使用别名 serial0, 其他设备节点或客户端程序可以更方便地引用该路径, 而不必记住整个路径字符串。
*/

注: aliases 节点中定义的别名只在设备树内部可见, 不能在设备树之外引用。它们主要用于设备树的内部组织和引用, 以提高可读性和可维护性。

3.3 chosen节点

chosen 节点是设备树中的一个特殊节点, 用于传递和存储系统引导和配置的相关信息。它位于设备树的根部。
chosen节点包含以下子节点和属性:

  • bootargs: 用于存储引导内核时传递的命令行参数。 它可以包含诸如内核参数、 设备树参数等信息。 在引导过程中, 操作系统或引导加载程序可以读取该属性来获取启动参数。
  • stdout-path:用于指定用于标准输出的设备路径。 在引导过程中, 操作系统可以使用该属性来确定将控制台输出发送到哪个设备, 例如串口或显示屏。
  • firmware-name:用于指定系统固件的名称。 它可以用于标识所使用的引导加载程序或固件的类型和版本。
  • linux,initrd-start linux,initrd-end: 这些属性用于指定 Linux 内核初始化 RAM 磁盘(initrd) 的起始地址和结束地址。 这些信息在引导过程中被引导加载程序使用, 以将 initrd 加载到内存中供内核使用。
  • 其他自定义属性: chosen 节点还可以包含其他自定义属性, 用于存储特定于系统引导和配置的信息。
chosen {bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
};
/*
通过这些命令行参数, 操作系统或引导加载程序可以配置内核在引导过程中正确地加载NFS 根文件系统, 并将控制台输出发送到指定的串口设备。
*/

通过使用 chosen 节点, 系统引导过程中的相关信息可以方便地传递给操作系统或引导加载程序。

3.3 device_type节点

device_type 节点是用于描述设备类型的节点

 1. cpu: 	  	表示中央处理器2. memory: 	表示内存设备3. display:   显示设备,液晶显示屏4. serial:    表示串行通信设备,串口5. ethernet:  表示以太网设备6. usb:		表示通用串行总线设备7. i2c:  	    表示使用I2C(Inter-Integrated Circuit)总线通信的设备8. spi:       表示使用SPI(Serial Peripheral Interface)总线通信的设备9. gpio:       表示通用输入/输出设备10.pwm:  		表示脉宽调制设备 

3.4 板子启动后查看设备树

# ls /sys/firmware/
devicetree fdt
  • /sys/firmware/devicetree 目录下是以目录结构程现的 dtb 文件, 根节点对应 base 目录,每一个节点对应一个目录, 每一个属性对应一个文件。
  • 这些属性的值如果是字符串,可以使用 cat 命令把它打印出来;对于数值,可以用hexdump 把它打印出来。

4. 内核对设备树的处理

从源代码文件 dts 文件开始,设备树的处理过程为:
在这里插入图片描述
①dts 在 PC 机上被编译为 dtb 文件;
② u-boot 把 dtb 文件传给内核;
③ 内核解析 dtb 文件,把每一个节点都转换为 device_node 结构体;
④ 对于某些 device_node 结构体,会被转换为 platform_device 结构体。

device_node 结构体定义和property结构体在内核源码的“/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; // 已删除的属性列表struct device_node *parent; // 父设备节点指针struct device_node *child; // 子设备节点指针struct device_node *sibling; // 兄弟设备节点指针struct kobject kobj; // 内核对象(用于 sysfs)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
};
struct property {char	*name; // 属性的名称int	length; // 属性值的长度(字节数)void	*value; // 属性值的指针struct property *next; // 下一个属性节点指针unsigned long _flags; // 属性的标志位unsigned int unique_id; // 属性的唯一标识 struct bin_attribute attr;// 内核对象二进制属性
};

哪些设备树节点会被转换为 platform_device

  • 根据规则 1, 首先遍历根节点下包含 compatible 属性的子节点, 对于每个子节点, 创建一个对应的 platform_device。
  • 根据规则 2, 遍历包含 compatible 属性为 “simple-bus”、 “simple-mfd” 、“arm、amba-bus”、“isa” 的节点以及它们的子节点。 如果子节点包含 compatible 属性值则会创建一个对应的 platform_device。
  • 根据规则 3, 总线 I2C、 SPI 节点下的子节点: 不转换为 platform_device某个总线下到子节点, 应该交给对应的总线驱动程序来处理, 它们不应该被转换为platform_device。

5. of操作函数

5.1获取设备树节点

of_find_node_by_name 函数

函数原型:
struct device_node *of_find_node_by_name(struct device_node *from, const char *nam
e);
函数参数和返回值含义如下:
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
name:要查找的节点名字。
返回值: 找到的节点,如果为 NULL 表示查找失败。

of_find_node_by_path 函数

函数原型:
struct device_node *of_find_node_by_path(const char *path);
函数参数和返回值含义如下:
path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个
节点的全路径。
返回值: 找到的节点,如果为 NULL 表示查找失败

of_get_parent 函数

函数原型:
struct device_node *of_get_parent(const struct device_node *node);
头文件:
#include <linux/of.h>
函数作用:
该函数接收一个指向设备树节点的指针 node, 并返回该节点的父节点的指针。
参数含义:
node: 要获取父节点的设备树节点指针。
返回值:
如果找到匹配的节点, 则返回对应的 struct device_node 指针。
如果未找到匹配的节点, 则返回 NULL

of_get_next_child 函数

函数原型:
struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev);
头文件:
#include <linux/of.h>
函数作用:
该函数接收两个参数: node 是当前节点, prev 是上一个子节点。 它返回下一个子节点的指针。
参数含义:
node: 当前节点, 用于指定要获取子节点的起始节点。
prev: 上一个子节点, 用于指定从哪个子节点开始获取下一个子节点。 如果为 NULL, 则从起始节点的第一个子节点开始。
返回值:
如果找到匹配的节点, 则返回对应的 struct device_node 指针。
如果未找到匹配的节点, 则返回 NULL

5.2提取属性值

of_find_property函数

函数原型:
property *of_find_property(const struct device_node *np,
const char *name,int *lenp)
函数参数和返回值含义如下:
np:设备节点。
name: 属性名字。
lenp:属性值的字节数
返回值: 找到的属性。

of_property_count_elems_of_size 函数

int of_property_count_elems_of_size(const struct device_node *np,
const char *propname, int elem_size)
函数参数和返回值含义如下:
np:设备节点。
proname: 需要统计元素数量的属性名字。
elem_size:元素长度。
返回值: 得到的属性元素数量。

of_property_read_u32_index 函数

int of_property_read_u32_index(const struct device_node *np,
const char *propname,
u32 index,
u32 *out_value)
函数参数和返回值含义如下:
np:设备节点。
proname: 要读取的属性名字。
index:要读取的值标号。
out_value:读取到的值
返回值: 0 读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。

of_property_read_string 函数

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

of_n_addr_cells 函数

int of_n_addr_cells(struct device_node *np)
函数参数和返回值含义如下:
np:设备节点。
返回值: 获取到的#address-cells 属性值。

of_n_size_cells 函数

int of_n_size_cells(struct device_node *np)
函数参数和返回值含义如下:
np:设备节点。
返回值: 获取到的#size-cells 属性值。

6.pinctrl和gpio子系统

在设备树实验开始之前,我们先了解一下pinctrl和gpio子系统

Pinctrl子系统

pinctrl(引脚控制) 用于描述和配置硬件设备上的引脚功能和连接方式。 它是设备树的一部分, 用于在启动过程中传递引脚配置信息给操作系统和设备驱动程序, 以便正确地初始化和控制引脚。
在设备树中, pinctrl(引脚控制) 使用了客户端和服务端的概念来描述引脚控制的关系和配置。

现在的芯片动辄几百个引脚,在使用到 GPIO 功能时,让你一个引脚一个引脚去找对应的寄存器,配置过程烦躁且费劲。
通过把引脚的复用、配置分离出来,做成Pinctrl子系统,给GPIO、I2C等使用。

    my_device_100ask_imx6ull{pinctrl-names = "default";pinctrl-0 = <&myled_for_gpio_subsys>;status = "okay";};

解释如下:
pinctrl-names 属性:

pinctrl-names = "default";
这一行定义了一个或多个引脚配置状态的名称。在这个例子中,只定义了一个名为 "default" 的状态。这意味着当设备启动或需要配置引脚时,会查找名为 "default" 的引脚配置。

pinctrl-0 属性:

pinctrl-0 = <&myled_for_gpio_subsys>;
这一行指定了对应于 "default" 状态(由 pinctrl-names 属性定义)的引脚配置引用。<&myled_for_gpio_subsys> 是一个对设备树中另一个节点的引用,这个节点应该包含了具体的引脚配置信息,比如哪些引脚被选中、它们的电气特性(如驱动能力、上拉/下拉等)以及它们被配置为什么功能(如GPIO、I2C等)。

GPIO子系统
GPIO(General Purpose Input/Output,通用输入输出)子系统是Linux内核中用于管理通用输入输出引脚的一个子系统。GPIO引脚是芯片上的一种常见资源,可以用于实现各种简单的输入输出功能。

以前我们通过寄存器来操作 GPIO 引脚,即使 LED 驱动程序,对于不同的板子它的代码也完全不同。
当 BSP 工程师实现了 GPIO 子系统后,我们就可以:

  • 在设备树里指定 GPIO 引脚
  • 在驱动代码中:使用 GPIO 子系统的标准函数获得 GPIO、设置 GPIO 方向、读取/设置 GPIO 值
/dts-v1/;
/{...
my_device_100ask_imx6ull{compatible = "100ask,leddrv";pinctrl-names = "default";pinctrl-0 = <&myled_for_gpio_subsys>;led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;status = "okay";};
};&iomuxc_snvs {...imx6ul-evk {... /* 省略其他代码 */myled_for_gpio_subsys: myled_for_gpio_subsys {        /*!< Function assigned for the core: Cortex-A7[ca7] */fsl,pins = <MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03        0x000110A0/*  数值定义在IMX6ULLRM.pdf文档中的1529页*/>;};};
};

解释如下:
led-gpios 属性:

&gpio5:这是一个对GPIO控制器的引用,指向设备树中定义的GPIO控制器节点之一。在这个例子中,它指的是编号为5的GPIO控制器。这个引用告诉系统LED灯连接到了哪个GPIO控制器上。3:这个数字指定了GPIO控制器上的具体引脚编号。在这个例子中,LED灯连接到了GPIO控制器5的第3个引脚上。GPIO_ACTIVE_LOW:这是一个标志,用于指定GPIO引脚的活动状态。GPIO_ACTIVE_LOW意味着当引脚处于低电平时(即0V或接近0V),LED灯被认为是激活的(即点亮)。相反,如果引脚处于高电平(通常是3.3V或5V,取决于系统的电源电压),LED灯则被认为是非激活的(即熄灭)。

pinctrl-names 和 pinctrl-0 属性(虽然它们本身不直接属于GPIO子系统,但与GPIO引脚的使用密切相关)

总的来说,这些代码片段共同描述了如何将一个GPIO引脚(GPIO5的引脚3)配置为LED灯的控制引脚,并指定了该引脚在低电平时激活LED灯的行为。同时,它们还引用了一个引脚配置(通过myled_for_gpio_subsys)

这里我们使用"Pins_Tool_for_i.MX_Processors_v6_x64"工具打IMX6ULL 的配置文件“MCIMX6Y2xxx08.mex”,就可以在 GUI 界面中选择引脚,
配置它的功能,这就可以自动生成 Pinctrl 的子节点信息。
在这里插入图片描述

7. 设备树下的 LED 驱动实验

注:本实验使用的是韦东山I.MX6U开发板

100ask_imx6ull-14x14.dts 完成编写后,在(内核目录)编译dts文件

book@100ask:~/100ask_imx6ull-sdk/Linux-4.9.88$ make dtbs

将编译好的dtb文件拷贝到网络文件系统中

cp /home/book/100ask_imx6ull-sdk/Linux-4.9.88/arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/

7.1编写驱动文件

led_driver.c

#include "asm-generic/errno-base.h"
#include "asm-generic/int-ll64.h"
#include "asm/gpio.h"
#include "linux/compiler.h"
#include "linux/err.h"
#include "linux/export.h"
#include "linux/gpio/consumer.h"
#include "linux/gpio/driver.h"
#include "linux/ioport.h"
#include "linux/kdev_t.h"
#include "linux/leds.h"
#include "linux/printk.h"
#include "linux/stddef.h"
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define LED_DEV_COUNT 1
#define LED_DEV_NAME "dts_plat_led"/* led_dev结构体 */
struct led_dev {dev_t dev_id; /* 设备id */struct cdev cdev; /* cdev*/struct class *class; /* class*/struct device *device; /* device*/int major; /* major*/struct device_node *node; /* led_node*/struct gpio_desc    *led_gpio; /* led_gpio*/
};static struct led_dev led_dev;static int led_open (struct inode *node, struct file *filp){printk("%s %s line:%d\n", __FILE__,__FUNCTION__,__LINE__);/* 设置GPIO引脚为输出模式,输出低电平 */gpiod_direction_output(led_dev.led_gpio, 0);return 0;
}static ssize_t led_write (struct file *filp, const char __user *buf, size_t size, loff_t *offset){int status;int err;printk("%s %s line:%d\n", __FILE__,__FUNCTION__,__LINE__);err = copy_from_user(&status, buf, 1);/* 设置GPIO的值 */gpiod_set_value(led_dev.led_gpio,status);return 1;
}static const struct file_operations led_fops = {.owner	= THIS_MODULE,.open   = led_open,.write  = led_write,
};/*  当驱动与设备匹配成功后执行此函数   */
int led_probe(struct platform_device *led_device){printk("%s %s line:%d\n", __FILE__,__FUNCTION__,__LINE__);// 1.gpio/*  led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>; */led_dev.led_gpio = gpiod_get(&led_device->dev, "led", 0);if(IS_ERR(led_dev.led_gpio)){dev_err(&led_device->dev, "Failed to get GPIO for led");return PTR_ERR(led_dev.led_gpio);}// 2.注册file_operations结构体// 2.1注册设备号if(led_dev.major){led_dev.dev_id = MKDEV(led_dev.major, 0);register_chrdev_region(led_dev.dev_id, LED_DEV_COUNT, LED_DEV_NAME);}else{alloc_chrdev_region(&led_dev.dev_id,0, LED_DEV_COUNT, LED_DEV_NAME);led_dev.major = MAJOR(led_dev.dev_id);}// 2.2添加cdev结构体cdev_init(&led_dev.cdev, &led_fops);cdev_add(&led_dev.cdev, led_dev.dev_id, LED_DEV_COUNT);// 2.3创建classled_dev.class = class_create(THIS_MODULE, LED_DEV_NAME);if(IS_ERR(led_dev.class)){return PTR_ERR(led_dev.class);}// 2.4 创建设备 /dev/dts_plat_ledled_dev.device = device_create(led_dev.class, NULL, led_dev.dev_id, NULL, LED_DEV_NAME);if(IS_ERR(led_dev.device)){return PTR_ERR(led_dev.device);}return 0;
}/*  remove 函数,移除 platform 驱动的时候此函数会执行    */
int led_remove(struct platform_device *led_device){gpio_set_value((unsigned int)led_dev.led_gpio,1); // turn off LEDcdev_del(&led_dev.cdev);unregister_chrdev_region(led_dev.dev_id, LED_DEV_COUNT);device_destroy(led_dev.class, led_dev.dev_id);class_destroy(led_dev.class);gpiod_put(led_dev.led_gpio); // release gpioreturn 0;}static const struct of_device_id leds_table[]   = { {.compatible = "100ask,leddrv"}, /* 100ask_imx6ull-14x14.dts中定义,compatible相同时才匹配成功 */{}
};static struct platform_driver led_driver = {.driver = {.name = "imx6ul-led", /* driver name */.owner = THIS_MODULE,.of_match_table = leds_table, /* device tree match table */   },.probe = led_probe, /* device probe function */    .remove = led_remove, /* device remove function */
} ;static int __init led_init(void){printk("%s %s line %d\n", __FILE__,__FUNCTION__,__LINE__);return platform_driver_register(&led_driver);
}
static void __exit led_exit(void){printk("%s %s line %d\n", __FILE__,__FUNCTION__,__LINE__);platform_driver_unregister(&led_driver);
}module_init(led_init); 
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Pumpk1n");

led_test.c

#include "asm-generic/fcntl.h"
#include "linux/string.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>int main(int argc, char **argv) {int fd;int status;if(argc != 3){printf("Usage: %s /dev/dts_plat_led on\n", argv[0]); //  turn on ledprintf("Usage: %s /dev/dts_plat_led off\n", argv[0]);// turn off ledreturn -1;}fd = open(argv[1],O_RDWR);if(fd < 0){printf("Error opening /dev/dts_plat_led\n");return -1;}if(strcmp(argv[2], "on") == 0){// turn on the LEDstatus = 1;write(fd,&status,1);}else if(strcmp(argv[2], "off") == 0){// turn off the LEDstatus = 0;write(fd,&status,1);}return 0;
}

Makefile文件

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88obj-m += led_driver.oall: $(MAKE) -C $(KERN_DIR) M=`pwd` modules$(CROSS_COMPILE)gcc -o led_test led_test.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f led_test

执行命令: make

编译 led_driver.c文件为 led_driver.ko内核模块文件
编译led_test.c文件为led_test可执行文件

拷贝这两个文件到网络文件系统中

cp led_test led_driver.ko ~/nfs_rootfs/

串口连接开发板

挂载到网络文件系统中

[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt

将/mnt/100ask_imx6ull-14x14.dtb文件复制到/boot/下面

[root@100ask:~]# cp /mnt/100ask_imx6ull-14x14.dtb /boot/

重启开发板,等待重启完成后重新挂载网络文件系统中

[root@100ask:~]# reboot
[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt

进入到/mnt/目录中,并向内核加载模块文件led_driver.ko

[root@100ask:~]# cd /mnt
[root@100ask:mnt]# insmod led_driver.ko

运行测试文件测试实验结果

[root@100ask:/mnt]# ./led_test /dev/dts_plat_led on
[root@100ask:/mnt]# ./led_test /dev/dts_plat_led off

相关文章:

嵌入式Linux学习: 设备树实验

设备树&#xff08;DeviceTree&#xff09;是一种硬件描述机制&#xff0c;用于在嵌入式系统和操作系统中描述硬件设备的特性、连接关系和配置信息。它提供了一种与平台无关的方式来描述硬件&#xff0c;使得内核与硬件之间的耦合度降低&#xff0c;提高了系统的可移植性和可维…...

eqmx上读取数据处理以后添加到数据库中

目录 定义一些静态变量 定时器事件的处理器 订阅数据的执行器 处理json格式数据和将处理好的数据添加到数据库中 要求和最终效果 总结一下 定义一些静态变量 // 在这里都定义成全局的 一般都定义成静态的private static MqttClient mqttClient; // mqtt客户端 private s…...

【中项】系统集成项目管理工程师-第5章 软件工程-5.3软件设计

前言&#xff1a;系统集成项目管理工程师专业&#xff0c;现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试&#xff0c;全称为“全国计算机与软件专业技术资格&#xff08;水平&#xff09;考试”&…...

C++学习笔记-内联函数使用和含义

引言 内联函数是C为了优化在函数的调用带来的性能开销而设计的&#xff0c;特别是当函数体很小且频繁调用时&#xff0c;内联函数可以让编译器在调用点直接展开函数体&#xff0c;从而避免了函数调用的开销。 一、内联函数的定义与含义 1.1 定义 内联函数是通过在函数声明或…...

数据库(MySQL)-视图、存储过程、触发器

一、视图 视图的定义、作用 视图是从一个或者几个基本表&#xff08;或视图&#xff09;导出的表。它与基本表不同&#xff0c;是一个虚表。但是视图只能用来查看表&#xff0c;不能做增删改查。 视图的作用&#xff1a;①简化查询 ②重写格式化数据 ③频繁访问数据库 ④过…...

js 优雅的实现模板方法设计模式

在JavaScript中&#xff0c;优雅地实现模板方法设计模式通常意味着我们要遵循一些最佳实践&#xff0c;如清晰地定义算法的骨架&#xff08;模板方法&#xff09;&#xff0c;并确保子类能够灵活地扩展或修改这些算法中的特定步骤。由于JavaScript是一种动态语言&#xff0c;我…...

C语言——输入输出

C语言——输入输出 输入输出函数的类型getcharputcharprintf占位符的分类 scanf 什么是输入输出呢&#xff1f; 所谓输入输出是以计算机为主机而言的&#xff0c;往内存中输入数据为输入&#xff0c;反之从内存中输出数据为输出。 输入输出的功能 C语言本身是不提供输入输出功能…...

【微软蓝屏】微软Windows蓝屏问题汇总与应对解决策略

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…...

OpenCV图像滤波(2)均值平滑处理函数blur()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在OpenCV中&#xff0c;blur()函数用于对图像应用简单的均值模糊&#xff08;mean blur&#xff09;。这种模糊效果可以通过将图像中的每个像素替…...

Android lmkd机制详解

目录 一、lmkd介绍 二、lmkd实现原理 2.1 工作原理图 2.2 初始化 2.3 oom_adj获取 2.4 监听psi事件及处理 2.5 进程选取与查杀 2.5.1 进程选取 2.5.2 进程查杀 三、关键系统属性 四、核心数据结构 五、代码时序 一、lmkd介绍 Android lmkd采用epoll方式监听linux内…...

linux shell(中)

结构化命令 if语句 if-then 最基本的结构化命令是 if-then 语句。if-then 语句的格式如下&#xff1a; if command thencommands ifif command; then # 通过把分号&#xff08;;&#xff09;放在待求值的命令尾部&#xff0c;可以将 then 语句写在同一行commands ifbash sh…...

VMware三种网络模式---巨细

文章目录 目录 ‘一.网络模式概述 二.桥接模式 二.NAT模式 三.仅主机模式 四.案例演示 防火墙配置&#xff1a; 虚拟电脑配置 前言 本文主要介绍VMware的三种网络模式 ‘一.网络模式概述 VMware中分为三种网络模式&#xff1a; 桥接模式&#xff1a;默认与宿主机VMnet0绑…...

力扣高频SQL 50 题(基础版)第一题

文章目录 力扣高频SQL 50 题&#xff08;基础版&#xff09;第一题1757.可回收且低脂的产品题目说明思路分析实现过程准备数据&#xff1a;实现方式&#xff1a;结果截图&#xff1a; 力扣高频SQL 50 题&#xff08;基础版&#xff09;第一题 1757.可回收且低脂的产品 题目说…...

2.1.卷积层

卷积 ​ 用MLP处理图片的问题&#xff1a;假设一张图片有12M像素&#xff0c;那么RGB图片就有36M元素&#xff0c;使用大小为100的单隐藏层&#xff0c;模型有3.6B元素&#xff0c;这个数量非常大。 识别模式的两个原则&#xff1a; 平移不变性&#xff08;translation inva…...

网易《永劫无间》手游上线,掀起游戏界狂潮

原标题&#xff1a;网易《永劫无间》手游上线&#xff0c;网友&#xff1a;发烧严重 易采游戏网7月26日消息&#xff1a;自网易宣布《永劫无间》手游即将上线以来&#xff0c;广大游戏玩家的期待值就不断攀升。作为一款拥有丰富内容和极高自由度的游戏&#xff0c;《永劫无间》…...

RNN(一)——循环神经网络的实现

文章目录 一、循环神经网络RNN1.RNN是什么2.RNN的语言模型3.RNN的结构形式 二、完整代码三、代码解读1.参数return_sequences2.调参过程 一、循环神经网络RNN 1.RNN是什么 循环神经网络RNN主要体现在上下文对理解的重要性&#xff0c;他比传统的神经网络&#xff08;传统的神…...

php 根据位置的经纬度计算距离

在开发中,我们要经常和位置打交道,要计算附近的位置、距离什么的。如下: 一.sql语句 SELECT houseID,title,location,chamber,room,toward,area,rent,is_verify,look_type,look_time, traffic,block_name,images,tag,create_time,update_time, location->&g…...

17 Python常用内置函数——基本输入输出

input() 和 print() 是 Python 的基本输入输出函数&#xff0c;前者用来接收用户的键盘输入&#xff0c;后者用来把数据以指定的格式输出到标准控制台或指定的文件对象。无论用户输入什么内容&#xff0c;input() 一律作为字符串对待&#xff0c;必要时可以使用内置函数 int()、…...

【Web】LitCTF 2024 题解(全)

目录 浏览器也能套娃&#xff1f; 一个....池子&#xff1f; 高亮主题(划掉)背景查看器 百万美元的诱惑 SAS - Serializing Authentication exx 浏览器也能套娃&#xff1f; 随便试一试&#xff0c;一眼ssrf file:///flag直接读本地文件 一个....池子&#xff1f; {…...

家政项目小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;家政人员管理&#xff0c;家政服务管理&#xff0c;咨询信息管理&#xff0c;咨询服务管理&#xff0c;家政预约管理&#xff0c;留言板管理&#xff0c;系统管理 微信端账号功能…...

electron TodoList网页应用打包成linux deb、AppImage应用

这里用的是windows的wsl的ubuntu环境 electron应用打包linux应用需要linux下打包&#xff0c;这里用windows的wsl的ubuntu环境进行操作 1&#xff09;linux ubuntu安装nodejs、electron 安装nodejs&#xff1a; sudo apt update sudo apt upgrade ##快捷安装 curl -fsSL http…...

【C语言】 使用fgets和fputs完成两个文件的拷贝

目录 1、使用fgets和fputs完成两个文件的拷贝 2、使用fgets统计给定文件的行号 fgets和fputs的使用方法函数原型&#xff1a;int fputs&#xff08;const char *s,FILE *stream)&#xff1b; char *fgets(char *s,int size,FILE *stream)&#xff1b;fupts…...

使用PyTorch导出JIT模型:C++ API与libtorch实战

PyTorch导出JIT模型并用C API libtorch调用 本文将介绍如何将一个 PyTorch 模型导出为 JIT 模型并用 PyTorch 的 CAPI libtorch运行这个模型。 Step1&#xff1a;导出模型 首先我们进行第一步&#xff0c;用 Python API 来导出模型&#xff0c;由于本文的重点是在后面的部署…...

Python——异常捕获,传递及其抛出操作

01. 异常的概念 1. 程序在运行时&#xff0c;如果 python解释器遇到一个错误&#xff0c;会停止程序的执行&#xff0c;并且提示一些错误信息&#xff0c;这就是异常。 2. 程序停止执行并且提示错误信息这个动作&#xff0c;我们通常称之为&#xff1a;抛出&#xff08;raise…...

【Maven】 的继承机制

Maven是一个强大的项目管理工具&#xff0c;主要用于Java项目的构建和管理。它以其项目对象模型&#xff08;POM&#xff09;为基础&#xff0c;允许开发者定义项目的依赖、构建过程和插件。Maven的继承机制是其核心特性之一&#xff0c;它允许子项目继承和复用父项目的配置&am…...

微信小程序结合后端php发送模版消息

前端&#xff1a; <view class"container"><button bindtap"requestSubscribeMessage">订阅消息</button> </view> // index.js Page({data: {tmplIds: [UTgCUfsjHVESf5FjOzls0I9i_FVS1N620G2VQCg1LZ0] // 使用你的模板ID},requ…...

sqlalchemy报错sqlalchemy.orm.exc.DetachedInstanceError

解决方案&#xff1a; 在初始化数据库的代码中&#xff0c;将 maker sessionmaker(bindeng)修改为 maker sessionmaker(bindeng, expire_on_commitFalse)为什么要添加 expire_on_commitFalse 参数&#xff1f; expire_on_commit 可以用来更改 SQLAlchemy 的对象刷新机制&…...

华为网络模拟器eNSP安装部署教程

eNSP是图形化网络仿真平台&#xff0c;该平台通过对真实网络设备的仿真模拟&#xff0c;帮助广大ICT从业者和客户快速熟悉华为数通系列产品&#xff0c;了解并掌握相关产品的操作和配置、提升对企业ICT网络的规划、建设、运维能力&#xff0c;从而帮助企业构建更高效&#xff0…...

【React】详解样式控制:从基础到进阶应用的全面指南

文章目录 一、内联样式1. 什么是内联样式&#xff1f;2. 内联样式的定义3. 基本示例4. 动态内联样式 二、CSS模块1. 什么是CSS模块&#xff1f;2. CSS模块的定义3. 基本示例4. 动态应用样式 三、CSS-in-JS1. 什么是CSS-in-JS&#xff1f;2. styled-components的定义3. 基本示例…...

【ROS2】高级:安全-理解安全密钥库

目标&#xff1a;探索位于 ROS 2 安全密钥库中的文件。 教程级别&#xff1a;高级 时间&#xff1a;15 分钟 内容 背景安全工件位置 公钥材料 私钥材料域治理政策 安全飞地 参加测验&#xff01; 背景 在继续之前&#xff0c;请确保您已完成设置安全教程。 sros2 包可以用来创…...

C语言 ——— 数组指针的定义 数组指针的使用

目录 前言 数组指针的定义 数组指针的使用 前言 之前有编写过关于 指针数组 的相关知识 C语言 ——— 指针数组 & 指针数组模拟二维整型数组-CSDN博客 指针数组 顾名思义就是 存放指针的数组 那什么是数组指针呢&#xff1f; 数组指针的定义 何为数组指针&#xf…...

opencascade AIS_ManipulatorOwner AIS_MediaPlayer源码学习

前言 AIS_ManipulatorOwner是OpenCascade中的一个类&#xff0c;主要用于操纵对象的交互控制。AIS_ManipulatorOwner结合AIS_Manipulator类&#xff0c;允许用户通过可视化工具&#xff08;如旋转、平移、缩放等&#xff09;来操纵几何对象。 以下是AIS_ManipulatorOwner的基…...

如何防止用户通过打印功能复制页面文字

简单防白嫖&#xff0c;要让打印出来的页面是空白&#xff0c;通常的做法是在打印时隐藏页面上的所有内容。这可以通过CSS的媒体查询&#xff08;Media Queries&#xff09;来实现&#xff0c;特别是针对media print的查询。 在JavaScript中&#xff0c;你通常不会直接控制打印…...

Python3网络爬虫开发实战(3)网页数据的解析提取

文章目录 一、XPath1. 选取节点2. 查找某个特定的节点或者包含某个指定的值的节点3. XPath 运算符4. 节点轴5. 利用 lxml 使用 XPath 二、CSS三、Beautiful Soup1. 信息提取2. 嵌套选择3. 关联选择4. 方法选择器5. css 选择器 四、PyQuery1. 初始化2. css 选择器3. 信息提取4. …...

基于 HTML+ECharts 实现监控平台数据可视化大屏(含源码)

构建监控平台数据可视化大屏&#xff1a;基于 HTML 和 ECharts 的实现 监控平台的数据可视化对于实时掌握系统状态、快速响应问题至关重要。通过直观的数据展示&#xff0c;运维团队可以迅速发现异常&#xff0c;优化资源配置。本文将详细介绍如何利用 HTML 和 ECharts 实现一个…...

立创梁山派--移植开源的SFUD和FATFS实现SPI-FLASH文件系统

本文主要是在sfud的基础上进行fatfs文件系统的移植&#xff0c;并不对sfud的移植再进行过多的讲解了哦&#xff0c;所以如果想了解sfud的移植过程&#xff0c;请参考我的另外一篇文章&#xff1a;传送门 正文开始咯 首先我们需要先准备资料准备好&#xff0c;这里对于fatfs的…...

MySQL之视图和索引实战

1.新建数据库 mysql> create database myudb5_indexstu; Query OK, 1 row affected (0.01 sec) mysql> use myudb5_indexstu; Database changed 2.新建表 1.学生表student&#xff0c;定义主键&#xff0c;姓名不能重名&#xff0c;性别只能输入男或女&#xff0c;所在…...

快速参考:用C# Selenium实现浏览器窗口缩放的步骤

背景介绍 在现代网络环境中&#xff0c;浏览器自动化已成为数据抓取和测试的重要工具。Selenium作为一个强大的浏览器自动化工具&#xff0c;能够与多种编程语言结合使用&#xff0c;其中C#是非常受欢迎的选择之一。在实际应用中&#xff0c;我们常常需要调整浏览器窗口的缩放…...

MyBatis 插件机制、分页插件如何实现的

MyBatis 插件机制允许开发者在 SQL 执行的各个阶段&#xff08;如预处理、执行、结果处理等&#xff09;中插入自定义逻辑&#xff0c;从而实现对 MyBatis 行为的扩展和增强。以下是 MyBatis 插件运行原理的详细介绍&#xff1a; 插件接口 MyBatis 插件通过实现 org.apache.i…...

CentOS6.0安装telnet-server启用telnet服务

CentOS6.0安装telnet-server启用telnet服务 一步到位 fp"/etc/yum.repos.d" ; cp -a ${fp} ${fp}.$(date %0y%0m%0d%0H%0M%0S).bkup echo [base] nameCentOS-$releasever - Base baseurlhttp://mirrors.163.com/centos-vault/6.0/os/$basearch/http://mirrors.a…...

H5+CSS+JS工作性价比计算器

工作性价比&#xff1d;平均日新x综合环境系数/35 x(工作时长&#xff0b;通勤时长—0.5 x摸鱼时长) x学历系数 如果代码中的公式不对&#xff0c;请指正 效果图 源代码 <!DOCTYPE html> <html> <head> <style> .calculator { width: 300px; padd…...

Linux:基础命令学习

目录 一、ls命令 实例&#xff1a;-l以长格式显示文件和目录信息 实例&#xff1a;-F根据文件类型在列出的文件名称后加一符号 实例&#xff1a; -R 递归显示目录中的所有文件和子目录。 实例&#xff1a; 组合使用 Home目录和工作目录 二、目录修改和查看命令 三、mkd…...

遇到Websocket就不会测了?别慌,学会这个Jmeter插件轻松解决....

websocket 是一种双向通信协议&#xff0c;在建立连接后&#xff0c;websocket服务端和客户端都能主动向对方发送或者接收数据&#xff0c;而在http协议中&#xff0c;一个request只能有一个response&#xff0c;而且这个response也是被动的&#xff0c;不能主动发起。 websoc…...

高性能 Java 本地缓存 Caffeine 框架介绍及在 SpringBoot 中的使用

在现代应用程序中&#xff0c;缓存是一种重要的性能优化技术&#xff0c;它可以显著减少数据访问延迟&#xff0c;降低服务器负载&#xff0c;提高系统的响应速度。特别是在高并发的场景下&#xff0c;合理地使用缓存能够有效提升系统的稳定性和效率。 Caffeine 是一个高性能的…...

Http 和 Https 的区别(图文详解)

在现代网络通信中&#xff0c;保护数据的安全性和用户的隐私是至关重要的。HTTP&#xff08;Hypertext Transfer Protocol&#xff09;和 HTTPS&#xff08;Hypertext Transfer Protocol Secure&#xff09;是两种常见的网络通信协议&#xff0c;但它们在数据保护方面的能力存在…...

DP学习——外观模式

学而时习之&#xff0c;温故而知新。 外观模式 角色 2个角色&#xff0c;外观类&#xff0c;子系统类。 个人理解 感觉就是对外接口封装&#xff0c;这个是封装一个功能的对外接口&#xff0c;越简单越好&#xff0c;提供给第三方用。 应用场景 封装为对外库时&#xff…...

Vue3 + Vite 打包引入图片错误

1. 具体报错 报错信息 报错代码 2. 解决方法 改为import引入&#xff0c;注意src最好引用为符引入&#xff0c;不然docker部署的时候可能也会显示不了 <template><img :src"loginBg" alt""> </template><script langts setup> …...

搭建NFS、web、dns服务器

目录 1、搭建一个nfs服务器&#xff0c;客户端可以从该服务器的/share目录上传并下载文件 服务端配置&#xff1a; 客户端测试&#xff1a; 2、搭建一个Web服务器&#xff0c;客户端通过www.haha.com访问该网站时能够看到内容:this is haha 服务端配置&#xff1a; 客户端…...

C++的UI框架和开源项目介绍

文章目录 1.QT2.wxWidgets3.Dear ImGui 1.QT QT的开源项目&#xff1a;QGIS&#xff08;地理信息系统&#xff09; https://github.com/qgis/QGIS?tabreadme-ov-file 2.wxWidgets wxWidgets的开源项目&#xff1a;filezilla https://svn.filezilla-project.org/svn/ wxWidg…...

SpringBoot连接PostgreSQL+MybatisPlus入门案例

项目结构 一、Java代码 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://mave…...