VSCode搭建STM32开发环境(极简自我搭建&懒人直接使用插件)

目录

 VSCode搭建STM32开发环境(极简自我搭建&懒人直接使用插件)

写在前面:

方法一:arm工具链+OpenOCD烧录

1.大致思路

2.准备工具

2.1安装工具

2.2补充说明

3.使用STM32CubeMX创建工程

4.VSCode内部配置

4.1添加并配置c_cpp_properties.json

4.2添加settings.json

4.3配置tasks.json

4.4配置launch.json

4.5重定向printf

4.6引入其他库

5.总结

1.准备工具

2.创建工程

3.其他事项(一些坑)


 VSCode搭建STM32开发环境(极简自我搭建&懒人直接使用插件)

一开始是用STM32CubeMX+Keil进行STM32的学习和开发,但是keil的界面属实有点丑。。最开始甚至有点把我劝退。抛开颜值不说,这货的代码补全功能有点菜,很多时候都不显示keyword的提示,至少在我的电脑上是这样,极个别时候倒是还反应挺快。

于是便想要用颜值高而且代码补全功能比较强大的vscode来进行STM32的开发。而且无论何种开发都用一个工具也比较方便。看了一些别人的博客,在这里汇总一下,也算做个笔记。

本教程思路为:使用STM32CubeMX生成Makefile工程(STM32CubeMX自动进行初始化配置实在太香了,应该也是目前stm32开发的主流),然后使用vscode对工程进行编写,编译和烧录。

总算抽出时间写完了,累死了...

写在前面:

方法二所使用的使用ioT Link插件的方法已经不推荐使用,因为方法二是我很久以前写的,已经太久没有维护,不知道现在是个什么状况,事实上在我写完这篇文章的方法二部分之后我就再也没用过这个方法了。。

其次我个人也更倾向于使用方法一,因为方法一尽可能少的使用VSCode插件,仅仅利用VSCode的强大编辑功能,再通过VSCode调用简单的命令行命令去完成后续的编译烧录等,事实上和方法二的内在原理是一样的,只不过方法二会自动帮你下载需要的工具,也会帮助你调用它们。我觉得方法一比方法二要优雅的多,但不可否认的是方法二确实对新手更友好一些,不过方法一每一步的配置过程和内在的原理我已经写的很清楚了,还是希望大家即便是新手也尽可能使用方法一吧,方法二的内容我就先不删除了,虽然很可能已经过时,但或许也能给各位一些帮助。

方法一:arm工具链+OpenOCD烧录

1.大致思路

使用STM32CubeMX生成工程,生成时选项选择生成Makefile,通过make调用arm工具链编译,然后通过OpenOCD烧录。

通过写c_cpp_properties.json可以利用VSCode牛逼的IntelliSense,可以很爽的写代码。通过task.json方便的完成编译和烧录功能,通过写launch.json以正确进入调试。

此外,也可以正常生成MDK或其他工程,然后vscode只写c_cpp_properties.json,这样的目的是用vscode写代码,用MDK编译和烧录,防止玄学问题发生

比如我目前没有解决的一个问题是H7的dma问题。看了其他人的博客,是因为dma使用了不该它使用的内存地址,但是用MDK的话官方生成的工程已经帮你把地址规定好了,是可以正常使用的,用arm工具链编译的话我还不清楚在哪里修改这个地址,仅在代码里定向这个地址的话又不好使....有小伙伴解决了的话欢迎评论区或私信告诉我一下;)

2.准备工具

(1)VSCode

(2)STM32CubeMX

(3)MinGW-w64

(4)arm-none-eabi-gcc

(5)OpenOCD

(6)Git(可选)

2.1安装工具

安装不细说了,记得把MinGW-w64,OpenOCD,arm-none-eabi-gcc,Git添加到环境变量。

桌面右键点击此电脑->属性->高级系统设置->环境变量->Path->编辑->新建->浏览,选择对应的路径(到bin文件夹),最后应用->确定。

参考图:

2.2补充说明

(3)MinGW-w64:Version版本号,选最新即可;Architecture架构跟操作系统有关,64位系统选择x86_64,32位系统选择i686;Threads线程标准windows开发选win32,其他选posix;Exception异常处理模型,x86_64可选为seh和sjlj,i686为dwarf和sjlj,一般选seh和dwarf就可;Build revision构建版本号,选择最大就可。详细可看这篇帖子

(6)Git:原本以为不能用powershell。。。虽然自己也觉得这不可能,但是一开始确实遇到了问题,不过现在姑且已经解决了。用git-bash的话方便一些,powershell的话有些地方要改一下,而且不如用gitbash快,主要体现在执行task时会卡一卡。关于这个地方其实我也不是很懂,不知道有没有其他方法解决,有的话欢迎评论区或私信提醒。:)

(6)Git:后面文章已经彻底改成使用windows的powershell了,本文的宗旨是尽可能减少额外的操作,能用原生的就不下载新的。

3.使用STM32CubeMX创建工程

前面选芯片,配置外设时钟等等正常配就好,要生成工程前点击Project Manager,在Toolchain/IDE处选择Makefile。

然后直接GENERATE CODE。

4.VSCode内部配置

首先在VSCode中打开刚才的工程文件夹。

直接按F5,选择C++(GDB/LLDB)->gcc.exe生成活动调试文件,然后会自动生成.vscode文件夹,当然啦,会报错,点中止就可以了。

4.1添加并配置c_cpp_properties.json

(1)在.vscode文件夹中添加c_cpp_properties.json文件,配置了这个,VSCode才能找到includePath,写代码的时候才不会一堆红波浪线。它的基本格式是这样的:

{
    "configurations": [
        {
            "name": "Win32",
            "browse": {
                "path": [

                ],
                "limitSymbolsToIncludedHeaders": true
            },
            "includePath": [

            ],
            "defines": [
 
            ],
            "compilerPath": " ",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "gcc-x64"
        }
    ],
    "version": 4
}

把鼠标放在对应的参数上可以看到vscode对其的解释,比如把鼠标放在path上:

首先我们看"defines",这个是告诉VSCode你预先定义了哪些宏,怎么知道这里应该填什么呢?打开目录下的Makefile文件便可以看到:

忽略前面的-D,我们便可以知道预先定义的宏是"USE_HAL_DRIVER"和"STM32F407XX"。(一般来说一定是这两个,其中第二个就是你的芯片)

再看"includePath",这个是告诉VSCode去哪些目录里找头文件,怎么知道这里应该填什么呢?同样,打开目录下的Makefile寻找:

忽略前面的-I,将后面的路径填入即可。(或者可以直接偷懒填"${workspaceFolder}/**",其中workspaceFolder表示当前目录,**表示该目录下递归遍历所有文件)

至于"path",似乎不是很重要,和上面的"includePath"填一样的或者填"${workspaceFolder}/"即可。

最后是"compilerPath",显然是填编译器路径,把MinGW-w64下的gcc的路径填进去就可以了,例如我的是这样:"D:\\Programming\\mingw64\\bin\\gcc.exe"。

至此,至少VSCode智能提示和代码补全功能就工作正常了,可以正常的写代码。虽然还不能编译烧录和调试,不过VSCode编辑+Keil编译烧录调试也是不错的选择(毕竟Keil的编辑功能实在是不好用,界面也丑)。

最后大致是这个样子:

{
    "configurations": [
        {
            "name": "Win32",
            "browse": {
                "path": [
                    "${workspaceFolder}/"
                ],
                "limitSymbolsToIncludedHeaders": true
            },
            "includePath": [
                "${workspaceFolder}/**"
            ],
            "defines": [
                "USE_HAL_DRIVER",
                "STM32F407xx"
            ],
            "compilerPath": "D:\\Programming\\mingw64\\bin\\gcc.exe",//编译器路径
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "gcc-x64"
        }
    ],
    "version": 4
}

4.2添加settings.json

在.vscode文件夹中添加settings.json文件,添加:

{
    "terminal.integrated.shell.windows": "D:\\Programming\\Git\\bin\\bash.exe"
}

这样便指定了该文件夹下默认shell是bash,出现提示的时候选择允许改变即可。

vscode更新后已经不推荐使用这种方法

 目前虽然会警告,但仍然可以使用,而且比新方法优先级更高,也就是说目前继续使用此方法不会有任何问题,暂时

新方法如下:

点击左下角小齿轮进入设置,在上方搜索栏搜索terminal default,然后点在settings.json中编辑

 总之只要能把全局的settings.json打开就行。

到这个位置:

 注意"terminal.integrated.defaultProfile.windows""terminal.integrated.profiles.windows"可能不在同一行。

后者是终端配置,包含名称,路径(path),参数(args),图标(icon)。路径也可以不用path用source,这样它会自己寻找路径,比如我图里的PowerShell。

此外,名称里不能有空格,别被第二个Command Prompt骗了。如果有空格的话,这个配置不会被识别,在terminal.integrated.defaultProfile.windows填上也会报警告。

如果打算用Bash作为默认终端的话就像我图里一样把对应的路径写上就行了。

前者是默认的终端是哪个配置,值就是终端配置的名称,比如我这里是PowerShell。

然后,只要ctrl+shift+P然后输入terminal,选择默认配置文件,然后选择对应的就可以了。

就算配置文件里没有写任何配置,Command Prompt和PowerShell也是会存在的,可选的,但如果配置里名称有空格,是不可选的。至于我图里那个带空格的Command Prompt为啥会在我也不是很懂。= =

这样选择终端配置文件,是全局的默认终端。仅在对应文件夹下的默认终端,可以通过文件夹下添加settings.json来实现,只需添加"terminal.integrated.profiles.windows"项即可,如:

    "terminal.integrated.defaultProfile.windows": "Git-Bash"

4.3配置tasks.json

tasks.json配置的是任务。对于使用PowerShell和Git-Bash,从这个地方就有些不同了。

自动生成的大致是这样:

{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: gcc.exe 生成活动文件",
            "command": "D:\\Programming\\mingw64\\bin\\gcc.exe",
            "args": [
                "-g",
                "${file}",
                "-o",
                "${fileDirname}\\${fileBasenameNoExtension}.exe"
            ],
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "调试器生成的任务。"
        }
    ],
    "version": "2.0.0"
}

"tasks"里是包含的任务。

"label"顾名思义标签,可以理解为任务的名称。

"type"是表明任务是作为一个进程运行还是在shell中作为命令运行。

"command"即命令。

"args"是命令的参数,即arguments。

"options"是其他命令选项,可以删掉不管。

"problemMatcher"和"group"是问题匹配程序和分组用的,也不用管,删掉就行。

"detail"顾名思义是细节描述。

对于编译工作,我们使用的是make工具,MinGW-w64自带的名叫mingw32-make.exe,ctrl+`呼出终端输入"mingw32-make"回车执行,便可以编译。

但是

使用gitbash是可以的,而使用PowerShell直接调用会报错:

使用PowerShell需要我们自己先在目录中建一个build文件夹才行。但是用bash的话,它会自动建一个,这和shell自己本身有关吧。

当然每次都输指令还是很麻烦,因此将这个操作编入tasks即可。

那么"type"填"shell","command"填"mingw32-make","args"可以填"-j"提高编译速度,至于"label"和"detail"随意填就好。

如果使用PowerShell的话,就像我刚才说的,需要预先新建build文件夹,所以要加上一个"dependsOn"指定其依赖的其他任务。不过要用powershell的条件判断语句判断是否已经存在build文件夹,否则已经存在build文件夹的情况下mkdir会返回错误,而若每次都在新建build前先将build删除,那么每次都需要重新编译所有文件,效率是很低的。

此外,还可以添加一个Clean任务,用来清除编译文件。

Makefile里的clean操作是这样定义的,其实就是删除build文件夹及其子文件:

 对于gitbash可以直接使用"mingw32-make clean",但是对于PowerShell是没有-fR这个参数的,总之用不了,会报和前面类似的错误。所以不用这个clean,在"commands"直接填"rm -r build"即可。

那么对于烧录工作,我们使用的是OpenOCD工具。ctrl+`呼出终端输入"openocd -h"查看帮助可以看到,用选项-f来指定配置文件,用选项-c来运行指令。

那么思路就是:-f来指定调试器类型和芯片型号等,-c进行烧录。

在目录"...\OpenOCD\share\openocd\scripts"下可以看到有很多写好的配置文件:

可以在interface中指定调试器,target中选择芯片型号。比如我的是stm32f407discovery的板子且板载stlink-v2,那么就可以写"-f interface/stlink-v2.cfg -f target/stm32f4x.cfg"。如果是官方板也可以直接在board里找。比如"-f board/stm32f4discovery.cfg".

而-c的参数既然是烧录操作的指令那么就是:"program build/_NAME_.bin verify reset exit 0x08000000"。其中_NAME_是你的工程的名字。

那么总结一下tasks.json可以这样写:

{
	"version": "2.0.0",
	"tasks": [
		{
			"type": "shell",
			"label": "Create BUILD-DIR",
			"command": "if (!(Test-Path build)) {mkdir build}",
			"detail": "Create new folder \"build\""
		},
		{
			"type":"shell",
			"label": "Build",
			"command": "mingw32-make",
			"dependsOn": "Create BUILD-DIR",
			"args": [
				"-j"
			],
			"detail": "Build with mingw32-make.exe"
		},
		{
			"type":"shell",
			"label": "Clean",
			"command": "rm",
			"args": [
				"-r",
				"build"
			],
			"detail": "Delete folder \"build\" and its content"
		},

		{
			"type": "shell",
			"label": "Burn",
			"command": "openocd",
			"args": [
				"-f",
				"interface/stlink-v2.cfg",
				"-f",
				"target/stm32f4x.cfg",//target
				"-c",
				"program build/_NAME_.bin verify reset exit 0x08000000"
			],
			"detail": "Burn with OpenOCD"
		},
		{
			"type": "shell",
			"label": "OpenOCD",
			"command": "openocd",
			"args": [
				"-f",
				"interface/stlink-v2.cfg",
				"-f",
				"target/stm32f4x.cfg"//target
			],
			"detail": "start OpenOCD and wait for gdb"
		}
	]
}

每个工程的工程名肯定不同,所以_NAME_肯定每次都需要修改。可以复制好工程名之后ctrl+f搜索_NAME_进行全部替换,后文的launch.json同理。工程名可以到Makefile的这个地方直接复制:

由于调试的时候需要用OpenOCD连接gdb,因此此处多加一个仅用OpenOCD连接设备,不烧录的task。

按下F1选择运行任务,就可以了,label和detail的具体效果如下:

可以看到label的内容可以算是标题或者名称,而detail显示为label下的灰字描述。

4.4配置launch.json

默认的launch.json大概是这样子:

 我们要修改的是"program"项,指定可执行文件的路径,如图所示,改到build下,对应的elf文件,"miDebuggerPath"项,指定调试程序的路径,如图所示,改到arm-none-eabi-gdb.exe,以及"setupCommands",指定执行的命令。里面的"description"是描述信息,填不填都行,"text"是执行的命令,"ignoreFailures"是否忽略错误。

调试的整个过程是,因为是调试stm32,要通过调试器,因此首先启动OpenOCD任务,OpenOCD会开放3333端口供gdb连接,然后F5启动调试,为gdb选择文件,然后连接到3333端口,reset和halt之后,烧录文件,就可以开始调试了,最后launch.json大概长这样:

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "调试",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}\\build\\_NAME_.elf",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "D:\\Programming\\arm-none-eabi\\bin\\arm-none-eabi-gdb.exe",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": false,
                },
                {
                    "description": "select .elf to gdb",
                    "text": "file D:/MyDocuments/Programming/STM32/Projects/F407DISC/_NAME_/build/_NAME_.elf",
                    "ignoreFailures": false,
                },
                {
                    "description": "connect gdb server",
                    "text": "target remote localhost:3333",
                    "ignoreFailures": false,
                },
                {
                    "description": "Reset MCU",
                    "text": "monitor reset",
                    "ignoreFailures": false,
                },
                {
                    "description": "Halt",
                    "text": "monitor Halt",
                    "ignoreFailures": false,
                },
                {
                    "description": "Burn",
                    "text": "load",
                    "ignoreFailures": false,
                },
            ],
            "preLaunchTask": "Build"
        }
    ]
}

"preLaunchTask"是调试前执行的任务,因此不用预先调用Build任务。

评论区有朋友指正"monitor Halt"应为"monitor halt",本人目前还没确认过,时间久了也不记得当时情况,如果有朋友这步有问题可以改成小写试一下。

4.5重定向printf

在HAL库的Projects下有一些板子例程,有的板子的Examples里的UART里会有UART_Printf例程,这个就是官方的重定向printf的示例。keil的例程如下:

可以看到,官方定义了PUTCHAR_PROTOTYPE为输出一个字符的原型,后面再对输出一个字符的原型进行改动,改成对应的串口输出。由于keil的AC5编译器对输出一个字符原型的名字不一样,所以使用了条件编译#ifdef。

但是依葫芦画瓢这样改是不行的。为什么呢?参考了这篇文章后,知道了原来是因为底层实现也不一样,gcc还需要重写_write函数。

初步总结:

#ifdef __GNUC__
  /* With GCC, small printf (option LD Linker->Libraries->Small printf
     set to 'Yes') calls __io_putchar() */
  #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
  #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */

PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
  HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF); 

  return ch;
}

__attribute__((weak)) int _write(int file, char *ptr, int len)
{
   int DataIdx;
   for (DataIdx = 0; DataIdx < len; DataIdx++)
   {
      __io_putchar(*ptr++);
   }
   return len;
}

 如果存在中途也要用keil编译的可能,就把重写_write的部分也加上条件编译。

此外,由于keil新版编译器AC6基于clang,因此,宏定义里定义了__GNU__,所以如果使用AC6的话,仅靠__GNU__就无法区分了。需要同时判断是否定义了__GNU__和__clang__。

再加上对scanf的重定向,最后是:

#if defined (__GNUC__) && !defined (__clang__)
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#define GETCHAR_PROTOTYPE int __io_getchar(void)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#define GETCHAR_PROTOTYPE int fgetc(FILE *f)
#endif

PUTCHAR_PROTOTYPE
{
  HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF); 
  return ch;
}

GETCHAR_PROTOTYPE
{
  uint8_t ch;
  HAL_UART_Receive(&huart2, (uint8_t *)&ch, 1, 0xFFFF); 
  return ch;
}

#if defined (__GNUC__) && !defined (__clang__)
__attribute__((weak)) int _write(int file, char *ptr, int len)
{
  int DataIdx;
  for (DataIdx = 0; DataIdx < len; DataIdx++)
    __io_putchar(*ptr++);
  return len;
}

__attribute__((weak)) int _read(int file, char *ptr, int len)
{
  int DataIdx;
  for (DataIdx = 0; DataIdx < len; DataIdx++)
    *ptr++ = __io_getchar();
  return len;
}
#endif

此外,需要在Makefile里加上-u _printf_float-u _scanf_float否则无法输入输出浮点数:

LDFLAGS = $(MCU) -u _printf_float -u _scanf_float -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections

找到这行,加上这俩就行了。

4.6引入其他库

用make进行编译时,make是根据Makefile里的内容寻找宏,头文件和哪些文件需要编译。而这个Makefile是STM32CubeMX根据选择的外设等自动生成的,因此,Makefile里显然只会包含HAL库或HAL的部分(取决于用户自己在生成工程时的选择),如果要引入自己写的驱动,就需要对Makefile进行改动。

添加.c文件,在这个地方:

直接在后面按照格式添加.c文件在工程下的相对路径即可。

添加头文件,在这个地方:

同理,写上头文件在工程下的相对路径就行,别忘了前面的-I。

5.总结

做完上述准备工作后,一次开发的流程大致是这样:

STM32CubeMX创建工程,generate code时选Makefile->

把写好的.vscode文件夹复制到工程目录下->

vscode打开文件夹后,修改c_cpp_properties.json里芯片型号的宏定义以及task.json和launch.json里的文件名->

如需使用第三方库,修改Makefile->

如需使用串口重定向,复制相关代码,如需打印浮点数,修改Makefile对应位置->

愉快coding!

1.准备工具

(1)VSCode

(2)STM32CubeMX

(3)vscode内下载插件ioT Link,如图:

安装好之后应该可以看到左下角有这样一栏:

2.创建工程

(1)首先用STM32CubeMX创建工程,还是一样的,具体配置不叙述,注意生成时选择Makefile。

(2)打开vscode,点击左下角ioT LINK插件的home后,来到如图所示的界面:

(3)点击“导入GCC工程”并选择刚才创建的工程的路径:

工程目录选择刚才生成的工程目录,Makefile会自动选择不用管,硬件平台选择自己的板子就好(没有的话就随便选一个,问题不大)。

(4)再次点击home,然后点击IoT Link设置,选择“调试器”。

(i)如果选择JLink的话,根据板子选择就好,此处不多赘述

(ii)如果选择OpenOCD的话要注意OpenOCD参数的设置,格式为:-f ./interface/{调试器}.cfg -f ./target/{目标器件}.cfg。例如这里我用的是stm32f103,stlink,参数配置如下:

       这里-f参数是指定配置文件,openocd的目录中有很多写好的配置文件,其中interface文件夹对应调试器,target文件夹对应目标器件。

(注意这里OpenOCD路径自动填上了${system_default},因为IoT Link这个插件自带gcc,make,openocd这些工具,可以看到其他工具的路径也被自动添上                     了${system_default},当然这里也可以选择方法一中自己下载的工具的路径。比如我就因为强迫症把插件自带的这些工具都删了,转而用自己下载的,这样也方便日后管理和更新)

(如果要和我一样使用自己下载的工具路径的话,可以移步最后的“其他事项”部分,这里还是有一些坑的)

(5)点击左下角的按钮进行编译和烧录:

Build即编译,Rebuild即重新编译,Download即烧录,Serial是内置的串口助手,Home就是更改IoT Link的设置。

按F5可以进行调试:

3.其他事项(一些坑)

(1)方法二中使用自己下载的工具路径时,注意编译器路径要选择arm-none-eabi的路径且只选择到bin文件夹,不要包含arm-none-eabi-gcc.exe;调试器中的OpenOCD路径要选饿到openocd.exe,而且使用插件的浏览功能只能选择到文件夹,不能选择到openocd.exe这个程序,需要自己手打;工具链中的Make工具路径和GCC工具路径和OpenOCD路径同理都要选择到具体程序。

  • 59
    点赞
  • 322
    收藏
    觉得还不错? 一键收藏
  • 29
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值