Linux学习第28天:Platform设备驱动开发(二): 专注与分散

Linux版本号4.1.15   芯片I.MX6ULL                                    大叔学Linux    品人间百味  思文短情长 


三、硬件原理图分析

四、驱动开发

1、platform设备与驱动程序开发

53 /*
54 * 设备资源信息,也就是 LED0 所使用的所有寄存器
55 */
56 static struct resource led_resources[] = {
57 [0] = {
58 .start = CCM_CCGR1_BASE,
59 .end = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),
60 .flags = IORESOURCE_MEM,
61 },
62 [1] = {
63 .start = SW_MUX_GPIO1_IO03_BASE,
64 .end = (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
65 .flags = IORESOURCE_MEM,
66 },
67 [2] = {
68 .start = SW_PAD_GPIO1_IO03_BASE,
69 .end = (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
70 .flags = IORESOURCE_MEM,
71 },
72 [3] = {
73 .start = GPIO1_DR_BASE,
74 .end = (GPIO1_DR_BASE + REGISTER_LENGTH - 1),
75 .flags = IORESOURCE_MEM,
76 },
77 [4] = {
78 .start = GPIO1_GDIR_BASE,
79 .end = (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1),
80 .flags = IORESOURCE_MEM,
81 },
82 };

        led_resources 数组,也就是设备资源,描述了 LED 所要使用到的寄存器信息,也就是 IORESOURCE_MEM 资源。

85 /*
86 * platform 设备结构体
87 */
88 static struct platform_device leddevice = {
89 .name = "imx6ul-led",
90 .id = -1,
91 .dev = {
92 .release = &led_release,
93 },
94 .num_resources = ARRAY_SIZE(led_resources),
95 .resource = led_resources,
96 };

        platform 设备结构体变量 leddevice,这里要注意 name 字段为“imx6ul-led”,所
以稍后编写 platform 驱动中的 name 字段也要为“imx6ul-led”,否则设备和驱动匹配失败。

98 /*
99 * @description : 设备模块加载
100 * @param : 无
101 * @return : 无
102 */
103 static int __init leddevice_init(void)
104 {
105 return platform_device_register(&leddevice);
106 }

        设备模块加载函数,在此函数里面通过 platform_device_register 向 Linux 内核注册 leddevice 这个 platform 设备。

108 /*
109 * @description : 设备模块注销
110 * @param : 无
111 * @return : 无
112 */
113 static void __exit leddevice_exit(void)
114 {
115 platform_device_unregister(&leddevice);
116 }

        设备模块卸载函数,在此函数里面通过 platform_device_unregister 从 Linux内核中删除掉 leddevice 这个 platform 设备。

34 #define LEDDEV_CNT 1 /* 设备号长度 */
35 #define LEDDEV_NAME "platled" /* 设备名字 */
36 #define LEDOFF 0
37 #define LEDON 1
38
39 /* 寄存器名 */
40 static void __iomem *IMX6U_CCM_CCGR1;
41 static void __iomem *SW_MUX_GPIO1_IO03;
42 static void __iomem *SW_PAD_GPIO1_IO03;
43 static void __iomem *GPIO1_DR;
44 static void __iomem *GPIO1_GDIR;
45
46 /* leddev 设备结构体 */
47 struct leddev_dev{
48 dev_t devid; /* 设备号 */
49 struct cdev cdev; /* cdev */
50 struct class *class; /* 类 */
51 struct device *device; /* 设备 */
52 int major; /* 主设备号 */
53 };
54
55 struct leddev_dev leddev; /* led 设备 */
56
57 /*
58 * @description : LED 打开/关闭
59 * @param - sta : LEDON(0) 打开 LED, LEDOFF(1) 关闭 LED
60 * @return : 无
61 */
62 void led0_switch(u8 sta)
63 {
64 u32 val = 0;
65 if(sta == LEDON){
66 val = readl(GPIO1_DR);
67 val &= ~(1 << 3);
68 writel(val, GPIO1_DR);
69 }else if(sta == LEDOFF){
70 val = readl(GPIO1_DR);
71 val|= (1 << 3);
72 writel(val, GPIO1_DR);
73 }
74 }
75
76 /*
77 * @description : 打开设备
78 * @param – inode : 传递给驱动的 inode
79 * @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量
80 * 一般在 open 的时候将 private_data 指向设备结构体。
81 * @return : 0 成功;其他 失败
82 */
83 static int led_open(struct inode *inode, struct file *filp)
84 {
85 filp->private_data = &leddev; /* 设置私有数据 */
86 return 0;
87 }
88
89 /*
90 * @description : 向设备写数据
91 * @param – filp : 设备文件,表示打开的文件描述符
92 * @param - buf : 要写给设备写入的数据
93 * @param - cnt : 要写入的数据长度
94 * @param - offt : 相对于文件首地址的偏移
95 * @return : 写入的字节数,如果为负值,表示写入失败
96 */
97 static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
98 {
99 int retvalue;
100 unsigned char databuf[1];
101 unsigned char ledstat;
102
103 retvalue = copy_from_user(databuf, buf, cnt);
104 if(retvalue < 0) {
105 return -EFAULT;
106 }
107
108 ledstat = databuf[0]; /* 获取状态值 */
109 if(ledstat == LEDON) {
110 led0_switch(LEDON); /* 打开 LED 灯 */
111 }else if(ledstat == LEDOFF) {
112 led0_switch(LEDOFF); /* 关闭 LED 灯 */
113 }
114 return 0;
115 }
116
117 /* 设备操作函数 */
118 static struct file_operations led_fops = {
119 .owner = THIS_MODULE,
120 .open = led_open,
121 .write = led_write,
122 };

        以上是传统的字符设备驱动。

124 /*
125 * @description : flatform 驱动的 probe 函数,当驱动与设备
126 * 匹配以后此函数就会执行
127 * @param - dev : platform 设备
128 * @return : 0,成功;其他负值,失败
129 */
130 static int led_probe(struct platform_device *dev)
131 {
132 int i = 0;
133 int ressize[5];
134 u32 val = 0;
135 struct resource *ledsource[5];
136
137 printk("led driver and device has matched!\r\n");
138 /* 1、获取资源 */
139 for (i = 0; i < 5; i++) {
140 ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);
141 if (!ledsource[i]) {
142 dev_err(&dev->dev, "No MEM resource for always on\n");
143 return -ENXIO;
144 }
145 ressize[i] = resource_size(ledsource[i]);
146 }
147
148 /* 2、初始化 LED */
149 /* 寄存器地址映射 */
150 IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);
151 SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);
152 SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);
153 GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);
154 GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);
155
156 val = readl(IMX6U_CCM_CCGR1);
157 val &= ~(3 << 26); /* 清除以前的设置 */
158 val |= (3 << 26); /* 设置新值 */
159 writel(val, IMX6U_CCM_CCGR1);
160
161 /* 设置 GPIO1_IO03 复用功能,将其复用为 GPIO1_IO03 */
162 writel(5, SW_MUX_GPIO1_IO03);
163 writel(0x10B0, SW_PAD_GPIO1_IO03);
164
165 /* 设置 GPIO1_IO03 为输出功能 */
166 val = readl(GPIO1_GDIR);
167 val &= ~(1 << 3); /* 清除以前的设置 */
168 val |= (1 << 3); /* 设置为输出 */
169 writel(val, GPIO1_GDIR);
170
171 /* 默认关闭 LED1 */
172 val = readl(GPIO1_DR);
173 val |= (1 << 3) ;
174 writel(val, GPIO1_DR);
175
176 /* 注册字符设备驱动 */
177 /*1、创建设备号 */
178 if (leddev.major) { /* 定义了设备号 */
179 leddev.devid = MKDEV(leddev.major, 0);
180 register_chrdev_region(leddev.devid, LEDDEV_CNT,
LEDDEV_NAME);
181 } else { /* 没有定义设备号 */
182 alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT,
LEDDEV_NAME);
183 leddev.major = MAJOR(leddev.devid);
184 }
185
186 /* 2、初始化 cdev */
187 leddev.cdev.owner = THIS_MODULE;
188 cdev_init(&leddev.cdev, &led_fops);
189
190 /* 3、添加一个 cdev */
191 cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
192
193 /* 4、创建类 */
194 leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
195 if (IS_ERR(leddev.class)) {
196 return PTR_ERR(leddev.class);
197 }
198
199 /* 5、创建设备 */
200 leddev.device = device_create(leddev.class, NULL, leddev.devid,
NULL, LEDDEV_NAME);
201 if (IS_ERR(leddev.device)) {
202 return PTR_ERR(leddev.device);
203 }
204
205 return 0;
206 }

        probe 函数,当设备和驱动匹配以后此函数就会执行,当匹配成功以后会在终端上输出“led driver and device has matched!”这样语句。在 probe 函数里面初始化 LED、注册字符设备驱动。也就是将原来在驱动加载函数里面做的工作全部放到 probe 函数里面完成。

208 /*
209 * @description :移除 platform 驱动的时候此函数会执行
210 * @param - dev : platform 设备
211 * @return : 0,成功;其他负值,失败
212 */
213 static int led_remove(struct platform_device *dev)
214 {
215 iounmap(IMX6U_CCM_CCGR1);
216 iounmap(SW_MUX_GPIO1_IO03);
217 iounmap(SW_PAD_GPIO1_IO03);
218 iounmap(GPIO1_DR);
219 iounmap(GPIO1_GDIR);
220
221 cdev_del(&leddev.cdev); /* 删除 cdev */
222 unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
223 device_destroy(leddev.class, leddev.devid);
224 class_destroy(leddev.class);
225 return 0;
226 }

        remove 函数,当卸载 platform 驱动的时候此函数就会执行。在此函数里面释放内存、注销字符设备等。也就是将原来驱动卸载函数里面的工作全部都放到 remove 函数中完成。

228 /* platform 驱动结构体 */
229 static struct platform_driver led_driver = {
230 .driver = {
231 .name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */
232 },
233 .probe = led_probe,
234 .remove = led_remove,
235 };

        platform_driver 驱动结构体,注意 name 字段为"imx6ul-led",和我们在leddevice.c 文件里面设置的设备 name 字段一致。

237 /*
238 * @description : 驱动模块加载函数
239 * @param : 无
240 * @return : 无
241 */
242 static int __init leddriver_init(void)
243 {
244 return platform_driver_register(&led_driver);
245 }

        驱动模块加载函数,在此函数里面通过 platform_driver_register 向 Linux 内核注册 led_driver 驱动。

247 /*
248 * @description : 驱动模块卸载函数
249 * @param : 无
250 * @return : 无
251 */
252 static void __exit leddriver_exit(void)
253 {
254 platform_driver_unregister(&led_driver);
255 }

        驱动模块卸载函数,在此函数里面通过 platform_driver_unregister 从 Linux
内核卸载 led_driver 驱动。

2、测试APP开发

1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "fcntl.h"
6 #include "stdlib.h"
7 #include "string.h"
8 /***************************************************************
9 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
10 文件名 : ledApp.c
11 作者 : 左忠凯
12 版本 : V1.0
13 描述 : platform 驱动驱测试 APP。
14 其他 : 无
15 使用方法 : ./ledApp /dev/platled 0 关闭 LED
16 ./ledApp /dev/platled 1 打开 LED
17 论坛 : www.openedv.com
18 日志 : 初版 V1.0 2019/8/16 左忠凯创建
19 ***************************************************************/
20 #define LEDOFF 0
21 #define LEDON 1
22
23 /*
24 * @description : main 主程序
25 * @param - argc : argv 数组元素个数
26 * @param - argv : 具体参数
27 * @return : 0 成功;其他 失败
28 */
29 int main(int argc, char *argv[])
30 {
31 int fd, retvalue;
32 char *filename;
33 unsigned char databuf[2];
34
35 if(argc != 3){
36 printf("Error Usage!\r\n");
37 return -1;
38 }
39
40 filename = argv[1];
41 /* 打开 led 驱动 */
42 fd = open(filename, O_RDWR);
43 if(fd < 0){
44 printf("file %s open failed!\r\n", argv[1]);
45 return -1;
46 }
47
48 databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
49 retvalue = write(fd, databuf, sizeof(databuf));
50 if(retvalue < 0){
51 printf("LED Control Failed!\r\n");
52 close(fd);
53 return -1;
54 }
55
56 retvalue = close(fd); /* 关闭文件 */
57 if(retvalue < 0){
58 printf("file %s close failed!\r\n", argv[1]);
59 return -1;
60 }
61 return 0;
62 }

五、运行测试

1、编译驱动程序和测试APP

4 obj-m := leddevice.o leddriver.o

        设置 obj-m 变量的值为“leddevice.o leddriver.o”。

        输入如下命令编译出驱动模块文件:
                make -j32
        编译成功以后就会生成一个名为“leddevice.ko leddriver.ko”的驱动模块文件。

        输入如下命令编译测试 ledApp.c 这个测试程序:
                arm-linux-gnueabihf-gcc ledApp.c -o ledApp
        编译成功以后就会生成 ledApp 这个应用程序。

2、运行测试

depmod //第一次加载驱动的时候需要运行此命令
modprobe leddevice.ko //加载设备模块
modprobe leddriver.ko //加载驱动模块

        根文件系统中/sys/bus/platform/目录下保存着当前板子 platform 总线下的设备和驱动,其中
devices 子目录为 platform 设备, drivers 子目录为 plartofm 驱动。查看/sys/bus/platform/devices/
目录,看看我们的设备是否存在,我们在 leddevice.c 中设置 leddevice(platform_device 类型)的
name 字段为“imx6ul-led”,也就是设备名字为 imx6ul-led,因此肯定在/sys/bus/platform/devices/
目录下存在一个名字“imx6ul-led”的文件,否则说明我们的设备模块加载失败,结果如图 所示:

        查看/sys/bus/platform/drivers/目录,看一下驱动是否存在,我们在 leddriver.c 中设置
led_driver (platform_driver 类型)的 name 字段为“imx6ul-led”,因此会在/sys/bus/platform/drivers/
目录下存在名为“imx6ul-led”这个文件,结果如图 所示:

驱动模块和设备模块加载成功以后 platform 总线就会进行匹配,当驱动和设备匹配成功以
后就会输出如图 所示一行语句:

        驱动和设备匹配成功以后就可以测试 LED 灯驱动了,输入如下命令打开 LED 灯:

/ledApp /dev/platled 1 //打开 LED 灯


        在输入如下命令关闭 LED 灯:

./ledApp /dev/platled 0 //关闭 LED 灯


        观察一下 LED 灯能否打开和关闭,如果可以的话就说明驱动工作正常,如果要卸载驱动的
话输入如下命令即可:
rmmod leddevice.ko
rmmod leddriver.ko

六、总结:

        本篇笔记主要学习了platform设备驱动开发的相关概念。将分成两次笔记进行学习。本次笔记主要学习platform设备驱动开发相关的理论知识。主要内容包括:Linux驱动的分离与分层、platform平台驱动模型简介。其中驱动的分离与分层有包括驱动的分离、驱动的分层。platform平台驱动模型简介主要包括platform总线、platform驱动与platform设备。
————————————————
版权声明:本文为CSDN博主「大叔学Linux」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jiage987450/article/details/134125677        


本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。

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

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

相关文章

在ffmpeg中,如何把h264转换为rgb格式

在ffmpeg中&#xff0c;网络视频流h264为什么默认的转为YUV而不是其他格式 文章中介绍了&#xff0c;h264解码的时候是直接解码为yuv的&#xff0c;如果在使用的过程中 需要用到rgb的格式&#xff0c;我们该如何来转换这种格式呢&#xff1f; 在上面的文章中&#xff0c;我们已…

Nacos全面知识 ----微服务 SpringCloud

快速入门 分级存储模型 修改集群配置 Nacos设置负载均衡策略 集群优先 权重优先 Nacos热更新配置 Nacos添加配置信息 微服务配置拉取 热更新:推荐使用第二种方法进行热部署 ConfigurationProperties(prefix "pattern") 是 Spring Boot 中用于自动配置属性的注解。它…

echarts中 对seriesLayoutBy的理解

https://echarts.apache.org/handbook/zh/concepts/dataset/ ‘row’: 系列被安放到 dataset 的行上面。 这里x轴是目录轴&#xff0c;那么一列就是一个系列 ‘column’: 默认值。系列被安放到 dataset 的列上面。 用自己的话总结就是&#xff1a; 当 seriesLayoutBy 为行时&…

CLion 2023.2.2(C ++ IDE智能代码编辑器)

CLion 2023是一款跨平台C/C集成开发环境&#xff08;IDE&#xff09;。它为Mac用户提供了高效的编程体验&#xff0c;帮助程序员们在Mac平台上进行C/C开发。 CLion 2023支持多种编译器和调试器&#xff0c;并具有强大的代码分析和导航功能。它还为用户提供了许多便捷的工具和插…

【SOC基础】单片机学习案例汇总 Part2:蜂鸣器、数码管显示

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

vi vim 末尾编辑按GA 在最后一行下方新增一行编辑按Go

vim 快速跳到文件末尾 在最后一行下方新增一行 移到末尾,并且进入文本录入模式 GA (大写G大写A) 在一般模式(刚进入的模式,esc模式) GA 或 Shift ga 先 G 或 shiftg 到最后一行 然后 A 或 shifta 到本行末尾 并且进入文本录入模式 在最后一行下方新增一行 (光标换行,文字不…

不会搭建企业网,建议你不要点开这篇文章

中午好&#xff0c;我是老杨。 企业网络搭建一直是网工技术里的基础且重要的部分&#xff0c;一些小白粉丝和我反馈&#xff0c;技术是了解一些&#xff0c;但是整体逻辑搭建不起来。 企业网是很多网工都会面临的一个工作日常环境。 但是&#xff0c;尽管很多人每日接触&…

react-router

一、react-router是什么 react-router是一个用于管理React应用程序中路由的库。路由是指确定应用程序如何根据URL路径来渲染不同的组件和页面。使用react-router可以将应用程序的不同页面映射到不同的URL路径&#xff0c;以及在用户导航时动态地加载适当的组件。这样&#xff…

shardingphere Sharding count cannot be null解决方案

shardingphere Sharding count cannot be null解决方案 问题背景解决方案Lyric&#xff1a; 我知道坚持要走 问题背景 使用shardingphere分表分库时&#xff0c;出现分表算法设置错误 Caused by: java.lang.IllegalArgumentException: Sharding count cannot be null.at com.…

mapbox使用marker创建html点位信息

mapbox使用marker创建html点位信息 codePen地址 mapboxgl.accessToken "pk.eyJ1IjoibGl1emhhbzI1ODAiLCJhIjoiY2xmcnV5c2NtMDd4eDNvbmxsbHEwYTMwbCJ9.T0QCxGEJsLWC9ncE1B1rRw"; const center [121.29786, 31.19365]; const map new mapboxgl.Map({container: &quo…

双十一买电视盒子什么牌子好?拆机达人强推目前性能最好的电视盒子

我这几年拆过的电视盒子已经有40多款了&#xff0c;最近看到网友们在讨论双十一电视盒子怎么挑选&#xff0c;就我拆机的经验来说&#xff0c;有些产品在硬件上存在问题的确较多&#xff0c;不知道双十一买电视盒子什么牌子好&#xff0c;可以参考我整理的目前性能最好的电视盒…

windows 运行 Mysql Command Line Client 自动关闭闪退原因分析

目录 原因分析一 原因分析二 原因分析三 第一次使用 MySQL Command Line Client 有可能输入密码后一按下回车键&#xff0c;程序窗口就自动关闭&#xff0c;出现闪退现象。本节主要分析产生闪退现象的原因以及如何处理这种情况。 原因分析一 首先可以查看程序默认执行文件…

电源管理(PMIC)MAX20428ATIA/VY、MAX20428ATIC/VY、MAX20428ATIE/VY适合汽车ADAS应用的开关稳压器

一、概述 MAX20428是一款高效率、八路输出、低压PMIC。OUT1将输入电源升压至5V&#xff0c;电流高达500mA&#xff0c;而三个同步降压转换器的输入电压范围为3.0V至4.2V&#xff0c;输出电压范围为0.8V至3.9875V&#xff0c;峰值电流分别高达1.3A、1.3A和3.5A。三个300mA pMOS…

团队表 -多级团队设计

团队表 -多级团队设计 user_team团队表 &#xff0c;如果存在子团队 1.我们可以通过每一个团队字段加一个parentid &#xff08;相当于一对多的关系&#xff09; 2.还可以设置一个字段CodingNum,比如这样: //系统为了管理查询团队自动生成的有序编号 可以使用3位数代表一个…

Spring cloud教程Gateway服务网关

Spring cloud教程|Gateway服务网关 写在前面的话&#xff1a; 本笔记在参考网上视频以及博客的基础上&#xff0c;只做个人学习笔记&#xff0c;如有侵权&#xff0c;请联系删除&#xff0c;谢谢&#xff01; Spring Cloud Gateway 是 Spring Cloud 的一个全新项目&#xff0c;…

JS中null和undefined的区别

首先 Undefined 和 Null 都是基本数据类型&#xff0c;这两个基本数据类型分别都只有一个值&#xff0c;就是 undefined 和 null。 undefined 代表的含义是未定义&#xff0c;null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined&#xff0c;null主要…

使用HttpClient库的爬虫程序

使用HttpClient库的爬虫程序&#xff0c;该爬虫使用C#来抓取内容。 using System; using System.Net.Http; using System.Threading.Tasks; ​ namespace CrawlerProgram {class Program{static void Main(string[] args){// 创建HttpClient对象using (HttpClient client new…

Ubuntu更新apt-get安装镜像源

前往清华开源镜像站获取镜像链接 修改镜像源&#xff0c;打开配置文件&#xff0c;将镜像源链接粘贴到配置文件 sudo vim /etc/apt/sources.list更新软件列表到本地 sudo apt-get update更新所有软件&#xff08;非必要&#xff09; sudp apt-get upgrade

java入门,程序=数据结构+算法

一、前言 在学习java的时候&#xff0c;我印象最深的一句话是&#xff1a;程序数据结构算法&#xff0c;对于写java程序来说&#xff0c;这就是java的入门。 二、java基本数据结构与算法 1、数据类型 java中的数据类型8种基本数据类型&#xff1a; 整型 byte 、short 、int…

win7中安装node14和vue

下载并安装低版本node 13 到官网去找早期历史版本的 nodejs 13 msi格式即可&#xff0c;并一键安装&#xff0c;我安装在了 D:\Program Files\nodejs 目录下 https://nodejs.org/download/release/v13.14.0/ 下载高版本node 14 下载高版本的node zip包 https://nodejs.org/…