Linux驱动开发初识
Linux驱动开发初识
文章目录
- Linux驱动开发初识
- 一、驱动的概念
- 1.1 什么是驱动:
- 1.2 驱动的分类:
- 二、设备的概念
- 2.1 主设备号&次设备号:
- 2.2 设备号的作用:
- 三、设备驱动整体调用过程
- 3.1 上层用户操控设备的流程:
- 3.2 Linux驱动的运行方式:
- 四、基于框架编写驱动代码
- 4.1 基本字符设备驱动框架:
- 4.2 驱动代码的编译:
- 4.3 驱动的加载&卸载:
- 4.4 驱动的测试:
- 五、树莓派IO口驱动的编写
- 5.1 BCM2835芯片手册导读:
- 5.2 Pin4引脚定位:
- 5.3 根据驱动框架编写树莓派Pin4引脚驱动:
- 5.4 编译测试Pin4引脚驱动:
一、驱动的概念
1.1 什么是驱动:
Linux内核驱动:是指一段代码,这段代码可以驱动底层硬件,即驱动就是对底层硬件设备的操作进行封装,并向上层提供函数接口。
1.2 驱动的分类:
Linux驱动分为三个基础大类:字符设备驱动,块设备驱动,网络设备驱动。
- 字符设备(Char Device):
- 字符设备是个能够像字节流(类似文件)一样被访问的设备
- 对字符设备发出读/写请求时,实际的硬件I/O操作一般紧接着发生
- 字符设备驱动程序通常至少要实现
open、close、read和write系统调用 - 比如我们常见的lcd、触摸屏、键盘、led、串口等等,他们一般对应具体的硬件都是进行出具的采集、处理、传输
- 块设备(Block Device):
- 一个块设备驱动程序主要通过传输固定大小的数据(一般为512或1k)来访问设备
- 块设备通过buffer cache(内存缓冲区)访问,可以随机存取,即:任何块都可以读写,不必考虑它在设备的什么地方
- 块设备可以通过它们的设备特殊文件访问,但是更常见的是通过文件系统进行访问
- 只有一个块设备可以支持一个安装的文件系统
- 比如我们常见的电脑硬盘、SD卡、U盘、光盘等
- 网络设备(Net Device):
- 任何网络事务都经过一个网络接口形成,即一个能够和其他主机交换数据的设备
- 访问网络接口的方法仍然是给它们分配一个唯一的名字(比如eth0),但这个名字在文件系统中不存在对应的节点
- 内核和网络设备驱动程序间的通信,完全不同于内核和字符以及块驱动程序之间的通信,内核调用一套和数据包传输相关的函(
socket函数)而不是read、write等 - 比如我们常见的网卡设备、蓝牙设备

二、设备的概念
- 在学习驱动和其开发之前,首先要知道所谓驱动,其对象就是设备。
2.1 主设备号&次设备号:
在Linux中,各种设备都以文件的形式存在**/dev目录下**,称为设备文件。最上层的应用程序可以打开,关闭,读写这些设备文件,从而完成对设备的操作。
为了管理这些设备,系统为设备编了号,每个设备都拥有主设备号和次设备号。主设备号用于区分不同种类的设备,而次设备号用于区分同一类型的多个设备。(对于常用的设备如硬盘,Linux赋予的主设备号一般是3)
- 在**
/dev目录下输入ls -l**,就可以看到设备文件对应的主次设备号:

2.2 设备号的作用:
在了解了什么是主次设备号之后,就要了解设备号的用处:
- 在用户态中:当用户调用了如
open,read,write等函数想要操作设备文件时,需要两个参数,第一个是文件名,第二个就是设备号 - 在内核态中:存在着一个驱动链表,用于管理所有设备的驱动,而驱动在链表中的位置就由设备号来检索
三、设备驱动整体调用过程
3.1 上层用户操控设备的流程:
-
C语言上层调用
open函数。open(“/dev/pin4”,O_RDWR);调用/dev下的pin4以可读可写的方式打开。对于上层open调用到内核时会发生一次软中断中断号是0X80,从用户空间进入到内核空间。 -
open会调用到system_call(内核函数),system_call会根据/dev/pin4设备名,去找出需要的设备号。 -
再调到虚拟文件VFS ,调用VFS里的
sys_open,sys_open会找到在驱动链表里面,根据主设备号和次设备号找到引脚4里的open函数,引脚4里的open是对寄存器操作及对硬件的操作。

3.2 Linux驱动的运行方式:
- 将驱动编译进 Linux 内核中,当 Linux 内核启动的时就会自动运行驱动程序
- 将驱动编译成模块(Linux 下模块扩展名为.ko),并在Linux 内核启动以后使用相应命令加载驱动模块
四、基于框架编写驱动代码
4.1 基本字符设备驱动框架:
#include <linux/fs.h> //file_operations声明
#include <linux/module.h> //module_init module_exit声明
#include <linux/init.h> //__init __exit 宏定义声明
#include <linux/device.h> //class devise声明
#include <linux/uaccess.h> //copy_from_user 的头文件
#include <linux/types.h> //设备号 dev_t 类型声明
#include <asm/io.h> //ioremap iounmap的头文件static struct class *pin4_class; //类对象
static struct device *pin4_class_dev; //设备对象static dev_t devno; //设备号
static int major =231; //主设备号
static int minor =0; //次设备号
static char *module_name="pin4"; //模块名//_open函数
static int pin4_open(struct inode *inode,struct file *file)
{printk("pin4_open\n"); //内核的打印函数和printf类似return 0;
}//_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{printk("pin4_write\n"); //内核的打印函数和printf类似return 0;
}static struct file_operations pin4_fops = { //结构体的类型是“file_operations”,名字可以自定义
//该结构体的成员就包含实现open和write的驱动函数
//当上层用户想要open或者write这个设备时,就会最终跳转到这个驱动代码中实现的open和write操作函数
//此处只赋值了该结构体中的三个成员变量(在keil中是不能这样写的,linux中可以),这个结构体其实有很多成员,如果想要实现更多的驱动函数,可以把更多的该结构体成员赋值并在这段代码中重写.owner = THIS_MODULE,.open = pin4_open,.write = pin4_write,
};int __init pin4_drv_init(void) //真实驱动入口
{int ret;devno = MKDEV(major,minor); //创建设备号ret = register_chrdev(major, module_name, &pin4_fops); //注册驱动,告诉内核:把这个驱动加入到内核驱动的链表中//以下两句代码目的是“生成设备文件”,也可以通过“mknod”命令手动生成,但是一般不会这样做pin4_class=class_create(THIS_MODULE,"myfirstdemo"); //先创建‘类’pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //再创建‘设备’return 0;
}void __exit pin4_drv_exit(void)
{device_destroy(pin4_class,devno); //先销毁‘设备’class_destroy(pin4_class); //在销毁‘类’unregister_chrdev(major, module_name); //卸载驱动
}module_init(pin4_drv_init); //入口,内核加载驱动的时候,这个宏会被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2"); //linux内核遵循GPL协议
4.2 驱动代码的编译:
- 进入Linux源码树目录下的驱动目录,因为驱动的是字符设备,所以进入的是驱动目录下的char目录。
/home/shiyahao/SYSTEM/linux-rpi-4.19.y/drivers/char

- 在这个路径下创建一个新的C文件:pin4driver.c,内容为我们刚刚的字符设备驱动:


- 修改当前路径(字符设备驱动)下的Makefile,确保这个新的驱动会被编译到:

- 回到linux内核源码的路径,运行以下指令尝试编译:
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
- 将编译好的驱动模块传到树莓派中:
scp ./drivers/char/pin4driver.ko pi@192.168.31.123:/home/pi

4.3 驱动的加载&卸载:
由于现在刚刚把驱动编译成了.ko的模块,所以需要运行以下指令来加载驱动模块:
sudo insmod pin4driver.ko //加载驱动模块
sudo rmmod pin4driver.ko //卸载驱动模块,此时驱动名字后不用加".ko"
运行成功后,就可以在**/dev**下看到生成的设备文件“pin4”了:

使用ls -l指令查看这个设备的主设备号&次设备号,和框架代码中的设置一样:

给pin驱动加权限:
sudo chmod 666 /dev/pin4
4.4 驱动的测试:
在树莓派下写一个测试驱动的C代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd;fd = open("/dev/pin4", O_RDWR); //打开GPIO4口设备文件if(fd < 0){printf("open pin4 failed\n");}else{printf("open pin4 success\n");}write(fd, "1", 1); //输出高电平return 0;
}
执行测试程序后用dmesg 查看内核打印信息发现打印了驱动函数的信息:

可见内核也按照框架代码中的printk成功打印了信息!驱动测试成功!
同时,结果也再次印证了:当用户在最上层对 驱动文件 调用C库的open函数后,最后的结果还是调用最底层 驱动文件里实现的open驱动函数
五、树莓派IO口驱动的编写
前面我们通过一个基本的字符设备驱动框架来测试了驱动的运行,但是在“pin4_open”和“pin4_write”这两个驱动函数的函数体里只写了一句内核打印的代码,作为一个真正的驱动文件这显然是不够的。
同时,在之前就提到过,驱动位于内核态的最底层,其下方就直接是硬件,所以驱动函数的目标就是直接操控硬件,也就是直接操控寄存器。在我的pin4驱动函数中应该添加的也就是根据函数功能,操作寄存器从而实现I/O口操控的代码。
5.1 BCM2835芯片手册导读:
明确了目标后,就产生了这个问题:我怎么知道应该使用哪些寄存器,又应该怎么使用呢?
答案是:根据开发平台的芯片手册/电路图来找到具体的描述,由于我是在树莓派3B上玩驱动的开发,所以我应该查阅这款树莓派的芯片,也就是BCM2835的芯片手册。
此处我只使用了芯片手册就定位了寄存器,而没有用电路图,原因是树莓派的这个芯片手册已经把用什么寄存器写的很清楚了
在BCM2835芯片手册的第六章描述了General Purpose I/O (GPIO)外设相关寄存器。这里驱动pin4引脚需要用到的寄存器有:
-
GPIO Function Select Registers (GPFSELn) 功能选择寄存器:

该寄存器共有五组,每个寄存器都有32位,以GPIO Alternate function select register 0为例,其中:
29-0位 :每三位对于一个引脚,比如29-27对应的是GPIO Pin 9,26-24对应的是GPIO Pin 8,且这三位取不同的值代表该三位对应的引脚选择不同的功能。比如,当29-27位为000时表示GPIO Pin 9是输入功能,29-27位为001时表示GPIO Pin 9是输出的功能。 -
GPIO Pin Output Set Registers (GPSETn) 置位寄存器:

该寄存器共两组,每个寄存器都有32位,将寄存器某一位置1即将对应的引脚置1。
-
GPIO Pin Output Clear Registers (GPCLRn) 清0寄存器:
与置位寄存器用法一至,将对应位数引脚置0。
-
所需寄存器的地址说明:
在编写驱动程序时,IO空间的起始地址位0X3F000000,加上GPIO的偏移量0X200000,因此GPIO的物理地址是从0X3F200000开始的,而编程所需的地址是虚拟地址,需要通过MMU内存虚拟化管理将地址映射到虚拟地址上。
5.2 Pin4引脚定位:
Pin4引脚指的是BCM4号,对应WiringPi库第7号,物理引脚的7脚:


5.3 根据驱动框架编写树莓派Pin4引脚驱动:
#include <linux/fs.h> //file_operations声明
#include <linux/module.h> //module_init module_exit声明
#include <linux/init.h> //__init __exit 宏定义声明
#include <linux/device.h> //class devise声明
#include <linux/uaccess.h> //copy_from_user 的头文件
#include <linux/types.h> //设备号 dev_t 类型声明
#include <asm/io.h> //ioremap iounmap的头文件static struct class *pin4_class;
static struct device *pin4_class_dev;static dev_t devno; //设备号
static int major =231; //主设备号
static int minor =0; //次设备号
static char *module_name="pin4"; //模块名//首先定义所要用的寄存器,为了防止地址被编译器优化需要用到volatile关键字
volatile unsigned int *GPFSEL0 = NULL;
volatile unsigned int *GPSET0 = NULL;
volatile unsigned int *GPCLR0 = NULL;static int pin4_open(struct inode *inode,struct file *file)
{printk("pin4_open\n"); //内核的打印函数和printf类似//配置引脚4的寄存器,将其配置为输出模式,即将GPFSEL0寄存器的第14-12位配置成001*GPFSEL0 &= 0XFFFF9FFF; //将第14,13位置0*GPFSEL0 |= 0X00001000; //将第12位置1return 0;
}static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{int usercmd;printk("pin4_write\n");copy_from_user(&usercmd,buf,count);//获取应用层write函数写入的内容if(usercmd == 1){printk("set 1\n");*GPSET0 |=(0x1 << 4); //将Pin4引脚置1}else if (usercmd == 0){printk("set 0\n");*GPCLR0 |=(0X1 << 4);//将Pin4引脚置0}else{printk("undo\n");}return 0;
}static struct file_operations pin4_fops = {.owner = THIS_MODULE,.open = pin4_open,//当应用层调用open函数时,内核会调用pin4_open..write = pin4_write,//当应用层调用write函数时,内核会调用pin4_write.
};int __init pin4_drv_init(void) //真实的驱动入口
{int ret;devno = MKDEV(major,minor); //创建设备号ret = register_chrdev(major, module_name,&pin4_fops); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中pin4_class=class_create(THIS_MODULE,"myfirstdemo");//由代码在dev下自动生成设备pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //创建设备文件GPFSEL0 = (volatile unsigned int *)ioremap(0X3f200000,4);//需要将物理地址映射位虚拟地址 ipremap第一个参数需要被映射的物理地址。第二个参数位映射的字节数GPSET0 = (volatile unsigned int *)ioremap(0X3f20001C,4);//通过芯片手册可以看到该寄存器在基础地址上偏移了1CGPCLR0 = (volatile unsigned int *)ioremap(0X3f200028,4);//通过芯片手册可以看到该寄存器在基础地址上偏移了28return 0;
}void __exit pin4_drv_exit(void)
{iounmap(GPFSEL0);iounmap(GPSET0);iounmap(GPCLR0);device_destroy(pin4_class,devno);class_destroy(pin4_class);unregister_chrdev(major, module_name); //卸载驱动
}module_init(pin4_drv_init); //入口
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
然后在树莓派上编写测试代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{int fd,cmd;fd = open("/dev/pin4",O_RDWR);printf("input 0 ro 1 , 0 :Pin4 Set 0,1:Pin4 Set 1\n");scanf("%d",&cmd);printf("cmd = %d \n",cmd);write(fd,&cmd,1);return 0;
}
5.4 编译测试Pin4引脚驱动:
- 将驱动代码编译后生成驱动模块放置在树莓派上进行测试:
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules

- 将生成的驱动模块拷贝至树莓派:
scp ./drivers/char/pin4driver.ko pi@192.168.31.123:/home/pi
- 在树莓派上安装驱动并给驱动权限:
sudo insmod pin4driver.ko
sudo chmod 666 /dev/pin4
运行测试程序:


相关文章:
Linux驱动开发初识
Linux驱动开发初识 文章目录 Linux驱动开发初识一、驱动的概念1.1 什么是驱动:1.2 驱动的分类: 二、设备的概念2.1 主设备号&次设备号:2.2 设备号的作用: 三、设备驱动整体调用过程3.1 上层用户操控设备的流程:3.2…...
前端面试题(三)
11. Web API 面试题 如何使用 fetch 发起网络请求? fetch 是现代浏览器中用于发起网络请求的原生 API。它返回一个 Promise,默认情况下使用 GET 请求:fetch(https://api.example.com/data).then(response > response.json()).then(data &g…...
骨传导耳机哪个牌子最好用?实测五大实用型骨传导耳机分析!
在快节奏的现代生活中,耳机已成为我们不可或缺的伴侣。无论是在通勤路上、运动时,还是在安静的图书馆,耳机都能为我们提供一片属于自己的音乐天地。然而,长时间使用传统耳机可能会对听力造成损害,尤其是在高音量下。因…...
18.1 k8s服务组件之4大黄金指标讲解
本节重点介绍 : 监控4大黄金指标 Latency:延时Utilization:使用率Saturation:饱和度Errors:错误数或错误率 apiserver指标 400、500错误qps访问延迟队列深度 etcd指标kube-scheduler和kube-controller-manager 监控4大黄金指标 …...
MacOS Catalina 从源码构建Qt6.2开发库之02: 配置QtCreator
安装Qt-creator-5.0.2 在option命令中配置Qt Versions指向 /usr/local/bin/qmake6 Kits选入CLang...
某建筑市场爬虫数据采集逆向分析
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 目标网站 aHR0cHM6Ly9qenNjLm1vaHVyZC5nb3YuY24vZGF0YS9jb21wYW55P2NvbXBsZXhuYW1lPSVFNiVCMCVCNA 提示:以下是本篇文章正文内容,下面…...
降低存量房贷利率的主要原因和影响
降低存量房贷利率的主要原因和影响可以从以下几个方面来分析: 原因 刺激消费与内需:降低房贷利率可以减少贷款人的月供负担,增加家庭的可支配收入,理论上能刺激消费,促进经济的内循环。在经济面临压力时,这…...
远程桌面连接工具Microsoft Remote Desktop Beta for Mac
Microsoft Remote Desktop Beta for Mac 是一款功能强大的远程桌面连接工具,具有以下功能特点: 软件下载地址 跨平台连接: 允许 Mac 用户轻松连接到运行 Windows 操作系统的计算机,打破了操作系统的界限,无论这些 Wi…...
Linux 之 logrotate 【日志分割】
简介 logrotate 是一个用于管理日志文件的工具。它可以自动对日志文件进行轮转、压缩、删除等操作,以防止日志文件无限增长占用过多磁盘空间。logrotate 通常作为一个守护进程定期运行,也可以通过 cron 任务来调度执行 工作原理 按照配置文件中的规则…...
Canvas简历编辑器-Monorepo+Rspack工程实践
Canvas简历编辑器-MonorepoRspack工程实践 在之前我们围绕Canvas聊了很多代码设计层面的东西,在这里我们聊一下工程实践。在之前的文中我也提到过,因为是本着学习的态度以及对技术的好奇心来做的,所以除了一些工具类的库例如 ArcoDesign、Re…...
uni-app - - - - -vue3使用i18n配置国际化语言
uni-app - - - - -使用i18n配置国际化语言 1. 安装vue-i18n2. 配置文件2.1 创建如下文件2.2 文件配置2.3 main文件导入i18n 3. 页面内使用3.1 template内直接使用3.2 变量接收使用 1. 安装vue-i18n npm install vue-i18n --save2. 配置文件 2.1 创建如下文件 locales文件夹里…...
VSCode好用的插件推荐
1. Chinese 将vscode翻译成简体中文 如果安装了依然是英文,请参考如下方法: ctrlshfitp 2. ESLint 自动检查规范 3. Prettier - Code formatter 可以自动调整代码的缩进、换行和空格,确保代码风格统一。通过配置,Prettier可…...
Linux:八种重定向详解(万字长文警告)
相关阅读Linuxhttps://blog.csdn.net/weixin_45791458/category_12234591.html?spm1001.2014.3001.5482 本文将讨论Linux中的重定向相关问题,在阅读本文前,强烈建议先学习文件描述符的相关内容Linux:文件描述符详解。 重定向分为两类&#x…...
set和map系列容器
前言 学习完二叉搜索树本来是应该直接深化,讲平衡二叉搜索树的。但是在学习它的底层逻辑之前呢,我们先来学学它的应用场面。 set和map的底层不是平衡二叉搜索树而是红黑树,实际上的难度比平衡搜索二叉树大。所以它的底层逻辑会比平衡二叉树更…...
企业告警智策助手 | OPENAIGC开发者大赛企业组AI创作力奖
在第二届拯救者杯OPENAIGC开发者大赛中,涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到,我们特意开设了优秀作品报道专栏,旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者,希望能带给…...
函数组件、Hooks和类组件区别
1. 函数组件(Function Components) 函数组件是接收props并返回React元素的纯JavaScript函数。它们不能拥有自己的状态(state)或生命周期方法,但在React 16.8中引入Hooks之后,这种情况发生了变化。 特点&a…...
在线点餐新体验:Spring Boot 点餐系统
摘 要 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势;对于网上点餐系统当然也不能排除在外,随着网络技术的不断成熟,带动了网上点餐系统,它彻底改变了过去传统的…...
WPF中Viewbox的介绍和用法
在 WPF(Windows Presentation Foundation) 中,Viewbox 是一个非常有用的容器控件,主要用于根据其自身大小自动调整子元素的缩放比例,以保持其内容的显示效果。无论窗口如何调整大小,Viewbox 内的内容都会按…...
QMT如何获取股票基本信息?如上市时间、退市时间、代码、名称、是否是ST等。QMT量化软件支持!
获取股票概况 包含股票的上市时间、退市时间、代码、名称、是否是ST等。 #获取合约基础信息数据 该信息每交易日9点更新 #内置Python 提示 旧版本客户端中,函数名为ContextInfo.get_instrumentdetail 调用方法 内置python ContextInfo.get_instrument_detai…...
2024年中国科技核心期刊目录(科普卷)
2024年中国科技核心期刊目录 (科普卷) 序号 期刊名称 1 爱上机器人 2 百科知识 3 保健医…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
