cortex-M4 SWD调试笔记

简介

本文包括以下三个内容:

1、SWD调试的硬件结构以及传输协议

2、如何在keil上搭建SWD debug环境

3、实际调试记录

术语和缩写

SWD: Serial Wire Debug ( 串行线调试 ) 

JTAG:    Joint Test Action Group( 联合测试行小组 )

DP:Debug Port ( 调试端口 )

AHB-AP: AHB - Access Port( 高级高性能总线  - 访问端口 )

cortex-M4 内存结构简介

从这个表可以看到 Cortex -M4 的内核存在SCS里面,关于PC指针和内部通用寄存器应该都在这里面,如下表对于SCB的解释就是处理器的R0 -R14寄存器

一、SWD硬件结构和传输协议简介

1.1 SWD调试的硬件结构分为两个部分:

图1 SWD各个模块之间的关系

第一部分:上位机和调试器( 如ARM V9 仿真器 )

上位机负责主动发起调试,通过USB与调试器交互

调试器负责将调试信息编码输出给芯片,或将读取到的芯片信息上报给上位机

第二部分:由芯片内部有两个物理单元组成,DP和AHB-AP(总称 DAP):

DP负责AHB-AP与上位机连接和通讯解析。

AHB-AP连接在AHB总线上可对单片机内部进行寻址访问(包括ARM内核、中断寄存器、通用寄存器RAM 和 FLASH),访问AP需要通过控制DP访问。

1.1.1 DP的主要模块

寄存器名

作用

IDCODE

识别码寄存器,用于识别SW-DP

ABORT

当交互出现异常的时候该寄存器对应bit会置一

CTRL/STAT

控制状态寄存器

RDBUFF

读取AP模块上报的数据

DP模块中有四个寄存器,其中IDCODE是嵌入式工程师在keil上可见的,识别到SWD的IDCODE就说明芯片已被识别到,如下图

1.1.2 AHB-AP的主要模块

AP 模块是复用的,决定使用哪个Bank由 DP模块的select模块决定,即一个DP可以同时连接多个AP

AP模块有多个共用的寄存器,使用AP模块中寄存器的哪一组由DP选择寄存器控制

AHB-AP模块连接在AHB总线上并拥有总线访问和控制权,所以其内部的寄存器比DP模块多,其中重要的几个寄存器如下:

寄存器

作用

CSW

可通过配置这个寄存器访问连接的内存系统

TAR

传送地址寄存器,保存要访问的内存地址

DRW

数据读/写寄存器,用于写入或读取TAR中地址的数据

IDR

AP模块的标识

DBGDRAR(调试寄存器)

通过这个寄存器里面的地址值,上位机可以找到芯片内部支持的调试功能(默认地址值0xE00FF000),这个地址里面存的一片区域叫做ROM表

A、 ROM表

keil和仿真器会读逐个取ROM表中对应基地址的模块,并检验是否支持该功能,支持就会在debug时打印出来,具体支持功能可参考文章的  

2.6   打开debug模式时左下角的打印如下

B、内核调试表(Debug and system Registers 调试寄存器)

内核调试表属于ROM表中的第一个功能SCS(System Control Space 系统控制空间)上位机keil通过AHB-AP模块控制这些寄存器就能让设备和上位机同步进行Debug(上位机控制内核状态,并通过SWD返回各个模块变量在芯片中的值)

第一个控制寄存器DHCSR: 控制处理器的暂停、单步和重启等动作,具体控制关系如下

第二个寄存器主要用于对内核和内存模块追踪,不用理解

第三个内核调试寄存器选择DCRSR : 通过这个寄存器来选择要读写的寄存器地址

第四个内核寄存器数据DCRDR : 通过这个寄存器读写DCRSR寄存器地址所对应的值

读寄存器内容,步骤如下:

(1)确认处理器已暂停DHCSR[0] =1。

(2)写DCRSR寄存器,第16位为0表示这是一次读操作。

(3)等待 DHCSR(0xE000EDF0)中的S_REGRDY位置1。

(4)读取DCRDR获得寄存器内容。

写寄存器的操作如下:

(1)确认处理器已暂停DHCSR[0] =1。

(2)将数据值写入DCRDR。

(3)写 DCRSR寄存器,第16位为1表示这是一次写操作。

(4)等待DHCSR(OxEO00EDFO)中的S_REGRDY位置1。

DCRSR和 DCRDR寄存器只能在暂停模式调试期间传送寄存器的数值,若利用调试监控处理进行调试,可以从栈空间访问一些寄存器的内容,其他的则可以直接在监控异常处理中访问。

1.1.3 如何从JTAG模式切换到SWD模式

在keil上直接切换debug模式即可,不需要手动发波形

1.2 SWD的传输协议:

1.2.1 SWD的硬件电路

SWD的物理层由两条线组成:

SWCLK: host驱动的时钟线

SWDIO: 双向数据线

1.2.2 SWD的传输协议

主机写数据

主机读数据

SWD传输模式类似于I2C,一个时钟一个数据线

每一次交互都是由主机发起,RnW决定是读或写,3个bit的握手信号、32 bit的数据信息(每一位都有特殊的意义,并非数据)具体细节请参考本文结尾处的参考文章://SWD时序精讲

1.3 SWD的操作逻辑:

第一步:上位机将调试信息发送给调试器,调试器把调试信息编码成一个通用的32位调试总线信号发送给DP设备

第二步:上位机通过调试器识别DP模块中的识别码IDCODE,根据IDCODE了解设备支持的DAP功能

第三步:调试器通过读取AHB-AP的ID寄存器检测AHB-AP 的连接

第四步:通过读取ROM表中的寄存器值逐个遍历调试部件支持的功能

第五步:在知道支持的SWD调试功能后keil端发起的全速到断点、步进、步出功能,并在内核停止时修改内核寄存器、RAM、flash、硬件IO等可访问地址的值

二、SWD debug环境搭建

2.1、准备工具

  • J link 软件驱动

    • ARM V9 仿真器

    • 生成sof文件对应的工程文件

2.2、安装环境(KEIL需要是522版本)

安装这个jlink的驱动文件之后在keil中才能识别到jlink设备

2.3 使用J link (Arm v9 仿真器)的时候物理连接如图1

图1.1

2.4 在keil工具中设置jlink仿真器的模式

图1.2

图1.3

2.5 取消掉下载bin文件到flash中的过程

这里选择不擦除flash

图1.4

2.6设置好后打开debug模式

2.7 打开debug模式时左下角的打印如下

图1.5

1、DP模块的ID:和在图1.3中看到的第二点 IDCODE一致

2、AP模块的ID:读取AP模块的寄存器得出AP模块的类型为AHB-AP

3、识别到Cortex -Mx的内核,小端模式

4、这里有一个报错,显示的是找到的Cortex -M3,实际配置的是Cortex -M4,原因是拿着151的工程文件,实际上接的设备是STM32F103C8T6

5、

6、CoreSight 信息打印(SWD使用的是CoreSight协议,作为嵌入式工程师不需要完全了解原理,只需要知道有哪些有用的模块信息即可)

  • 第一行的E00FF000是ROM表的起始地址(ROM表中会显示该单片机内部支持哪些调试功能并可计算出这些调试功能处于单片机的那个存储位置,调试工具根据)

  • 第二行是ROM表中的第一个地址(0xE00FF000),存的值为FFF0F000,指向的地方为SCS(系统控制空间)

    • 其SCS地址=0xE00FF000 + 0xFFF0F000 = 0xE000E000(截取32位)

  • 第三行是ROM表中的第二个地址(0xE00FF004),存的值为FFF02000,指向的地方为DWT(数据监视和跟踪单元)

    • 其DWT地址=0xE00FF000(不变) + 0xFFF02000 = 0xE0001000(截取32位)

  • 第四行是ROM表中的第三个地址(0xE00FF008),存的值为FFF03000,指向的地方为FPB(断点单元)我们在程序打的断点的值就会写入这个模块,当程序PC指针运行到这个位置的时候FPB模块就让内核停止运转

    • 其DWT地址=0xE00FF000(不变) + 0xFFF03000 = 0xE0002000(截取32位)重定向到指令跟踪宏单元

  • 第五行是ROM表中的第三个地址(0xE00FF00C),存的值为FFF01000,指向的地方为ITM(重定向到指令跟踪宏单元)

    • 其ITM地址=0xE00FF000(不变) + 0xFFF03000 = 0xE0000000(截取32位)

  • 第六行是ROM表中的第三个地址(0xE00FF010),存的值为FFF41000,指向的地方为TPIU(跟踪端口接口单元)

    • 其TPIU地址=0xE00FF000(不变) + 0xFFF41000 = 0xE0040000(截取32位)

三、实际调试记录

问题一:发现无法搜寻到ARM 的JTAG/SWD模块,无法读取到IDCODE。

在RTL给出的可测试sof文件后,根据JTAG/SWD调试手册 JTAG_SWD debug prot 手册 将FPGA中PIN脚的功能设置成为JTAG/SWD模式,然后将仿真器连接到FPGA板上,发现无法搜寻到ARM 的JTAG/SWD模块,无法读取到IDCODE,其中有一个现象是fpga上的PIN脚(GPIO 8 9 19)一上电就会表现为高电平。

现象解释:吴晨说这些GPIO口上电后默认为高阻态,用万用表量到的状态由其上下拉电阻决定

debug思路1:确认FPAG引脚的功能是否正常?

根据FPGA 的PIN脚操作流程,将GPIO 2 3 8 9 18 19 的PIN脚功能选择子的两位:如8050 00f2[1:0]  GPIO8 设成0,然后再把8050 0201 、8050 0205 的[0]设置成 1 、1 ,最后8050 0209的[0]设置成为0和1时GPIO8能输出高低电平,其他的PIN脚类似的设置后也是可以输出高低电平的,这说明给出的sof中的GPIO是可用的。

debug思路2:ARM v9 仿真器与FPGA板子物理连线是否正确

首先根据FPGA_V1板原理图和吴晨给给出的GPIO映射到FPGA板子上的对应关系,重新检测FPGA的PIN脚是否是对应的GPIO口

GPIO2->CAM_T_VD

GPIO3->CAM_T_HD

GPIO8->CAM_T_PCLK

GPIO9->CAM_T2_PO0

GPIO18->CAM_T2_PO1

GPIO19->CAM_T2_PO2

GPIO23->CAM_T2_PO3

检查的方法就是按照debug思路1单个控制GPIO,看对应的引脚的电平是否正常输出高和低,如果正常就说明FPGA上该PIN脚对应的就是对应的GPIO,通过设置寄存器的值后确实能在转接板上输出高低电平。

然后检查FPAG上的GPIO口和仿真器上的JTAG数据口是否一一对应,并且用万用表的通断模式去检测FPAG上的PIN脚和仿真器的PIN连接是否是通的,没有开路。

          

测试完成这个步骤之后发现FPGA上的PIN脚和GPIO口已经正确找到,并且FPGA和仿真器的PIN脚是一一对应且连接稳定的,故物理连接层是正常且正确的。

debug思路3:ARM v9 仿真器是否能正常工作?

为了验证这个问题,就需要抓包看仿真器上到底做了什么操作,时序是什么样的(仿真器没有连接过JTAG设备,只连接过STM32F103的SWD模式)

于是用TraveBus 来抓包验,TraveBus是支持JTAG和SWD协议的抓包的。连接图如下:

将JTAG的五个引脚和地接好后就可以开始抓包,开始抓取后打开KEIL的debug模式,选择JLINK后setting,仿真器就会发出搜寻波形。

FPGA板子没有上电的时候抓到的波形如下:

时钟、TDI、TMS、TREST都有数据,唯独没有TDO,因为没有设备,TDI的数据就没有接收方,无法形成闭环,所以TDO没有是正常的,其中有一个细节时序是在TCK起来的725ns后 TREST被拉高

在当烧录可用的sof文件后,抓到的波形如下:

TCK的电平一直被拉低,从来没有被拉起来过,其他数据线上的电平和之前GPIO上电后测到的时序保持一致,初步得出的结论是TCK在烧录sof文件并将对应的GPIO设置成为JTAG模式的时无法被拉高,同时为了避免TraveBus抓到的数据和实际数据存在差异(比如说电压),所以用示波器来抓取TCK波形,看到的结果如下:

在不接FPGA的时候,TCK会从0拉高到3.3V 并且有时钟信号输出。

接了FPGA板子后的TCK时钟如上面的三个图,其中每一个正方形代表的是1V(最下面的光标2中显示的值)。

图1可以看出该信号线最大的电压为1V,存在时钟的变化

图2是图1最开始位置处的跳变,可以看出,在开始的瞬间电压有被拉高,但是没有被拉起来,峰值为1.5V左右,最后稳定在0.7V

图3是图1中的几个竖条数据,放大后看到的是类似时钟的信号,但是幅值仍然很低,不超过1V。

至此更加确定,这个时钟引脚是无法被拉高的了,并且从之前的时序上可以看出 TCK 无法拉高就会让仿真器不输出其他引脚的数据,不过再三思考,是否其他的信号在接到GPIO上之后是无法被拉低的呢?并不是因为TCK没有起来就不发了?的确如此!因为从仿真器的角度而言,它在接收到查找设备命令的时候就会按照既定的流程在各个PIN脚上轮流发出数据,不会存在某个时钟异常就不发别的数据了,结果的确如此,当我把TCK接到GPIO8(默认上拉输入的引脚)上的时候,再抓到的包时TCK一直处于拉高的状态!

初步结论:复用为JTAG的pin脚无法被拉高拉低,为了与RTL描绘这一现象,可以用后面的方法来做最有效的验证了:

在GPIO2脚和3.3V电之间接一个电阻,形成一个上拉电阻来驱动PIN脚,发现上拉电阻为3K的时候无法拉起GPIO2(TCK),当上拉电阻为1K的时候能拉起来。(这看上去不像是驱动能力不够的问题,正常都是4.7K的上拉电阻)

在GPIO8脚和地之间接一个电阻,形成一个下拉电阻来拉低PIN脚,发现上拉电阻为1K的时候无法拉下来GPIO8的高电平。

结论:该sof文件的GPIO无法被拉高拉低,吴晨认可这个结论后修改了sof文件。

再次测试的时候就成功找到了ARM中的JTAG/SWD的IDCODE。

问题二:JTAG/SWD模块已经连接上,但是无法进行联调

keil在复位的时候ARM内核能复位,但是在单步执行的时候无法正常执行,表现出来的现象是在点击单步运行的时候,本来要运行的是下一行的代码,但是突然会跳到另外一个函数中去,或者点击单步运行的时候光标一直停在这一行,FPGA不能和keil同步联调。

这个现象和一种情况比较类似:(以STM32为例,当keil工程代码和芯片内部的代码不一致的时候就会出现这种情况,点击单步运行会跳到一个随机的位置,软件操作与实际发生的不一样)

解决思路:

1、去抓仿真器上的波,分析初始化、单步运行的时候,仿真器会做些什么,分析单个步骤的信号

该方案不具备可行性,其一是因为keil是驱动内核的,驱动端口发的指令不对是导致内核异常的主要原因,其二是每次单步调试的时候都会发几百条交互命令,兼职人员无法解析我们这边来解析需要很长的时间,不切实际

2、keil工程调试FPGA是否需要什么特殊的配置才能行?

尝试了很多的方法去进入Debug模式,最后发现停止、复位、全速运行可行、也能准确地读取到外部硬件的状态(FLASH 的nLoadmode ==0和ff)和全局变量的值,并且在全速运行的时候的用xchiptool工具进行寄存器的写操作时,值可以同步被修改到keil的memory表中,改变memory表中的值,xchiptool工具读回来的值也是一样。唯独无法单步调试。

3、keil工程是否有问题?

根据思路2继续debug,最终发现问题,keil工程是否和在altera内部的工程跑的不一样呢?发现这个问题的原因是这样的,在用xchiptool工具的读写功能的时候,XU单元的交互会有一个数组(g_libusb_cfg.req.databuf[64])来记录上位机发过来的值,首先将这个数组取地址放在memory中,然后对寄存器进行读写,发现在内存中改变的值居然不是g_libusb_cfg.req.databuf的[0]-[4],而是偏移了较远的一个位置!!!这就可以肯定,我手上的这个keil工程和altera上跑的不是一个工程!

之后找吴晨给我他编译的sof文件的keil工程,原封不动地给我,测试结果是能联调了!!!

最后也想清楚了一件事情:

我们的芯片CODE是ROM工艺,不是STM32那样的FLASH工艺,所以我们的产品在调试的时候不需要重新烧录flash,我们综合出来的SOF文件烧录时就已经把程序下载到芯片中去了,我们的keil工具只需要开始在线调试即可。不能调试大概率是keil工程和sof中跑的工程不一致。

问题三:JTAG/SWD模式下用BOOT 和 Application工程调试存在的问题

1、BOOT工程硬件在线调试:

在sof文件对应的keil工程中开始调试。能单步调试,这些功能都可以使用:

但是在调试的时候程序不是一行一行地跑,而是第二行、第一行、第三行、第五行?

对 keil Mdk 优化等级的理解_mdk优化等级-CSDN博客

原因是keil的优化等级造成的影响!优化等级设大了就会导致部分代码在调试过程中不会单步执行,而是会跳着执行,但不影响函数的逻辑,最终的执行结果是一致的。

2、BOOT工程软件调试:

针对问题1,将BOOT工程进行软件调试,发现现象也是如此,软件调试和硬件在线调试的结果一致,也会乱跳,不是一步一步执行的。

降低优化等级是有效的,我把151工程文件的优化等级调节为0,然后删除一些函数节省空间后编译通过,再次软件调试的时候能正常的单步执行。

优化等级为2不会对逻辑造成影响,只是在调试的时候PC指针运行到这里来之后的变量值改变不会立即生效,可能要多运行几步才会生效。

3、Application工程软件调试

从【纯软件仿真的】表现上来看,Application工程是无法进入的,因为调用Application工程中App和Patch都是通过flash加载的方式加入的,所以在BOOT工程中编译出来的lib在patch点和app点都只会进入到默认的patch点或者没有识别到app跳过,不会进入到Application中的这些函数中来。

那如何解决呢?用脚本?将App和Patch的起始地址在lib中赋值?好想法,找到map文件中的patch入口地址,然后手动改入口地址为patch的入口地址,用软件就可以模拟patch的加载!

在翁耀星的电脑上找到了Application工程要软件调试的话需要将Boot工程的SCT文件的中断部分的RO copy过来并把application的RO部分屏蔽,因为最终是运行在Boot工程的中断位置,在Application处的内容是为了不让用户知道我们的中断实现从而防止修改。Application正常跑起来之后将PATCH的入口函数在memory表中手动赋值,仿真跑到了patch内容中!

4、Application工程硬件在线调试:

将application的patch部分和Application部分用0701的bin生成工具的bin可以成功加载patch内容和Application内容。在Application工程的patch入口函数处打断点,光标能停留在该断点处。user_main函数while(1) 中打断点,光标能停留在while(1)的断点处。再次单步执行的时候能正常往下走。

但是在调试的时候发现加入app和patch之后会迅速跳入到application的工程中去,但是在user_main()的while(1)之前的初始化函数并不能打断点,打的断点是无效的,进入user_main()的时候,在这段函数的断点都是无效的!

解决方法是先跳到调用user_main()函数的位置打一个断点,因为函数在main函数中,所以在reset之后可以全速运行到这停下,然后单步运行就可以跳到user_main()的第一行,用手动方法实现keil进入user_main()的操作。

在客户使用Aplliction工程的时候将user_main()被调用处的汇编代码行数告诉他们,然后通过show Disassembly at address即可跳到user_main()的调用处

    

输入汇编代码的行数

然后在该行的汇编代码处打上一个断点,注册成功了APP,reset后固件会跑到这个位置来,再点击下面的代码框,点击单步执行即可跑到user_main()的第一行。

5、SWD/JTAG功能复测

由于代码改动, jtag和swd功能进行复测。

其中 GPIO19-> GPIO7    GPIO18->GPIO6 FPGA映射到顶层的管脚保持不变,所以无需改动FPGA板上测试线连接位置。 配置需要改动的地方:

原来配置GPIO19的寄存器改为配置GPIO7

     REG_GPIO19 = 3  (0x8050_00f4[7:6])   ->   REG_GPIO7 = 3  (0x8050_00f1[7:6])

原来配置GPIO18 的寄存器改为配置GPIO6

     REG_GPIO18 = 3  (0x8050_00f4[5:4])   ->   REG_GPIO6 = 3  (0x8050_00f1[5:4])

修改后能找到 jtag和swd的设备ID、能在KEIL上进行联调(包括断点、全速、复位等操作)、能在memory中读到20 分页下的RAM数据、80分页下的硬件寄存器数据。和未修改之前的正常现象一致。

测试环境为测试机二号下的工程文件和sof

6、SWD/JTAG功能第二次复测(20230905)

由于代码改动, jtag和swd功能进行复测。

其中 GPIO19-> GPIO7    GPIO18->GPIO6 FPGA映射到顶层的管脚保持不变,所以无需改动FPGA板上测试线连接位置。 配置需要改动的地方:

原来配置GPIO19的寄存器改为配置GPIO7

     REG_GPIO19 = 3  (0x8050_00f4[7:6])   ->   REG_GPIO7 = 3  (0x8050_00f1[7:6])

原来配置GPIO18 的寄存器改为配置GPIO6

     REG_GPIO18 = 3  (0x8050_00f4[5:4])   ->   REG_GPIO6 = 3  (0x8050_00f1[5:4])

修改后能找到 jtag和swd的设备ID、能在KEIL上进行联调(包括断点、全速、复位等操作)、能在memory中读到20 分页下的RAM数据、80分页下的硬件寄存器数据。和未修改之前的正常现象一致。

测试环境为测试机二号下的工程文件和sof

7、调试报错记录

***JLink Error: CPU is not halted

**JLink Warning: CPU could not be halted

***JLink Error: Can not read register 15 (R15) while CPU is running

***JLink Error: Can not read register 16 (XPSR) while CPU is running

***JLink Error: Can not read register 0 (R0) while CPU is running

***JLink Error: Can not read register 1 (R1) while CPU is running

***JLink Error: Can not read register 2 (R2) while CPU is running

***JLink Error: Can not read register 3 (R3) while CPU is running

***JLink Error: Can not read register 4 (R4) while CPU is running

***JLink Error: Can not read register 5 (R5) while CPU is running

***JLink Error: Can not read register 6 (R6) while CPU is running

***JLink Error: Can not read register 7 (R7) while CPU is running

***JLink Error: Can not read register 8 (R8) while CPU is running

***JLink Error: Can not read register 9 (R9) while CPU is running

***JLink Error: Can not read register 10 (R10) while CPU is running

***JLink Error: Can not read register 11 (R11) while CPU is running

***JLink Error: Can not read register 12 (R12) while CPU is running

***JLink Error: Can not read register 13 (R13) while CPU is running

***JLink Error: Can not read register 14 (R14) while CPU is running

***JLink Error: Can not read register 15 (R15) while CPU is running

***JLink Error: Can not read register 16 (XPSR) while CPU is running

***JLink Error: Can not read register 17 (MSP) while CPU is running

***JLink Error: Can not read register 18 (PSP) while CPU is running

***JLink Error: Can not read register 20 (CFBP) while CPU is running

//出现这个问题的原因在于JTAG和SWD的GPIO口的功能选择被修改了,可能SWD调试口被修改为了I2S、PWM等GPIO功能,这时候KEIL就无法通过GPIO口控制内核,这时候就会报如上的错

四、参考文章

// SWD原理 

一文帮你彻底搞懂ARM Debug Interface之SWD - 知乎

//通俗易懂的文章,便于理解SWD的结构和配置 

ARM调试接口——PART B.4 SWD协议解析 - 知乎

//SWD时序精讲 

【MDK】- STM32内部 SRAM 调试详细配置_stm32将变量放到sram中-CSDN博客

// 如果不想在flash上调试,那么可以试试在SRAM上 调试部分代码,调试方法如上 

SWD 协议入门指引及实际寄存器读写波形示例_swd读取核心寄存器-CSDN博客

// SWD在逻辑分析仪上的时序分析 

调试笔记--keil 断点调试小技巧_keil5断点调试教程-CSDN博客

//keil 全局变量 调试技巧

我佛慈悲,永无 BUG、永不修改 | KEIL 调试系列总结篇 - 知乎

//最全调试技巧

  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值