OpenOCD(三):学习OpenJTAG Config文件配置

学习OpenJTAGConfig文件配置

本章针对任何需要编写配置文件的用户, 包括OpenOCD的开发人员和集成商以及任何用户 需要让新板顺利运行。 它提供了创建这些文件的指南。

您应该在 $(INSTALLDIR)/script 下找到以下目录,并在上游维护配置文件。用它们在可能的情况下按原样提供;或作为新文件的模型。

  • interface :这些用于调试适配器。指定要使用的配置的文件 特定的JTAG,SWD和其他适配器在这里。
  • board :关于电路板,PWA,PCB,他们有很多名字。主板文件 包含特定于板的初始化项。
  • target :关于芯片。“target ”目录代表JTAG TAP。 在芯片上 OpenOCD应该控制哪个,而不是一个板子。两种常见的目标类型 是ARM芯片和FPGA或CPLD芯片。 当一个芯片有多个TAP(也许它同时具有ARM和DSP内核)时, 目标配置文件定义了所有这些。
  • more :浏览其他可能有用的库文件。 例如,有各种通用和特定于 CPU 的实用程序。
印制电路板(PCB)、印制线路板(PWB)和印制线路组件(PWA),PCB是印刷电路板本身,PCBA是指印刷电路板的组装过程,而PWA是一种现代化的Web应用程序。它们分别代表了不同的概念和应用领域。

用户配置 文件可以通过以下方式覆盖上述任何文件中的功能 在获取目标文件之前设置变量,或通过添加 特定于他们情况的命令。

一、interface文件配置

用户配置文件应该能够使用如下命令获取这些文件之一:

source [find interface/FOOBAR.cfg]

每个调试适配器都应存在预配置的接口文件今天与OpenOCD一起使用。 也就是说,也许其中一些配置文件 仅由创建它的开发人员使用。

另一章提供了有关如何设置这些设置的信息。请参见调试适配器配置。如果您有一种新的硬件接口,并且需要为其提供驱动程序,请阅读OpenOCD源代码(和开发人员指南)。

命令:find’文件名’
根据OpenOCD搜索规则打印文件名的完整路径。

命令:ocd_find’文件名’
根据OpenOCD搜索规则打印文件名的完整路径。这是查找使用的低级函数。通常您想使用find。

二、board配置文件

用户配置文件应能够使用以下命令源这些文件之一:

source [find board/FOOBAR.cfg]

主板配置文件的目的是打包用户配置文件需要知道的有关给定主板的所有内容。总之,主板文件应包含(如果存在)

  • 一个或多个源[find target/…cfg] 语句
  • NOR闪存配置(参见NOR配置)
  • NAND闪存配置(参见NAND配置)
  • SDRAM和I/O配置的目标重置处理程序
  • JTAG适配器重置配置(请参阅重置配置)
  • 所有不是“芯片内部”的东西

目标芯片内部的通用内容属于目标配置文件,而不是单板配置文件。因此,例如,重置初始化事件处理程序应该知道板特定的振荡器和PLL参数,并将这些参数传递给目标特定的实用程序代码。

单板配置文件中最复杂的任务是创建这样的重置初始化事件处理程序。在验证主板配置的其余部分工作后,最后定义这些处理程序。

1-配置文件之间的通信

除了特定于目标的实用程序代码外,主板和目标配置文件通信的另一种方式是遵循如何使用某些变量的约定。

完整的Tcl/Tk语言支持“命名空间”,但Jim-Tcl不支持。因此,我们在OpenOCD中遵循的规则是:以前导下划线开头的变量本质上是临时的,可以在目标配置文件中随意修改和使用。

对于具有三个芯片的主板,复杂的主板配置文件可以执行这样的操作:

# Chip #1: PXA270 for network side, big endian
set CHIPNAME network
set ENDIAN big
source [find target/pxa270.cfg]
# on return: _TARGETNAME = network.cpu
# other commands can refer to the "network.cpu" target.
$_TARGETNAME configure .... events for this CPU..

# Chip #2: PXA270 for video side, little endian
set CHIPNAME video
set ENDIAN little
source [find target/pxa270.cfg]
# on return: _TARGETNAME = video.cpu
# other commands can refer to the "video.cpu" target.
$_TARGETNAME configure .... events for this CPU..

# Chip #3: Xilinx FPGA for glue logic
set CHIPNAME xilinx
unset ENDIAN
source [find target/spartan3.cfg]

该示例过于简单化,因为它没有显示任何闪存,也没有显示用于初始化外部DRAM或(假设它需要)将配置加载到FPGA中的重置初始化事件处理程序。

这些功能通常是许多主板的低级工作所需要的,其中“低级”意味着主板初始化软件可能无法工作。(这是需要JTAG工具的一个常见原因。另一个原因是允许使用基于微控制器的系统,这些系统通常除了JTAG连接器外没有调试支持。)

目标配置文件还可以将实用程序函数导出到主板和用户配置文件。此类函数应使用名称前缀,以帮助避免命名冲突。

主板文件也可以接受用户配置文件中的输入变量。例如,可能有一个J4_跳线设置用于识别开发板使用的闪存类型,或者如何设置其他时钟和外设。

2-变量命名约定

大多数单板只有一个芯片实例。但是,创建一个具有多个此类芯片的主板应该很容易(如上图所示)。因此,我们鼓励这些命名与不同target.cfg文件关联的变量的约定,以促进一致性,以便主板文件可以覆盖目标默认值。

目标配置文件的输入包括:

  • CHIPNAME …这为整个芯片提供了一个名称,并用作抽头标识符点分名称的一部分。虽然默认值通常由芯片制造商提供,但主板文件可能需要区分芯片的实例。
  • ENDIAN…默认情况下,很少–尽管芯片可能会硬连线大。不能改变端序的芯片不需要使用此变量。
  • CPUTAPID …当OpenOCD检查JTAG链时,可以告知它根据JTAG IDCODE寄存器验证芯片。目标文件将保存一个或多个默认值,但有时主板中的芯片将使用不同的ID(可能是较新的版本)。

目标配置文件的输出包括:

  • _TARGETNAME :根据惯例,此变量由目标配置脚本创建。主板配置文件可以使用此变量来配置诸如“重置init”脚本或其他特定于该主板和该目标的东西。如果芯片有2个目标,则名称为_目标名称0、_目标名称1、…等。

3-重置初始化事件处理程序

主板配置文件在OpenOCD配置阶段运行;它们不能使用TAP或目标,因为它们尚未完全设置。这意味着您无法写入内存或访问芯片寄存器;您甚至无法验证闪存芯片是否存在。这将在稍后的事件处理程序中完成,其中目标重置初始化处理程序是最重要的处理程序之一。

除微控制器外,重置初始化事件处理程序的基本工作是设置闪存和DRAM,通常由引导加载程序处理。微控制器很少使用引导加载器;它们直接用片内闪存和SRAM存储器运行。但他们可能也想使用这些处理程序之一,如果只是为了方便开发人员的话。

注意:由于这是非常特定于主板和芯片的,因此此处不包括示例。相反,请查看随OpenOCD分发的主板配置文件。如果您有引导加载程序,其源代码将有所帮助;其他JTAG工具的配置文件也将有所帮助(请参见翻译配置文件)。

其中一些代码可能可以在不同的主板之间共享。例如,设置DRAM控制器通常除了总线宽度(16位或32位)和内存时序外没有太大差异,因此,由target.cfg文件加载的可重用TCL过程可能会将这些作为参数。振荡器、PLL和时钟设置类似;以及禁用看门狗。干净地构建代码,并提供注释以帮助下一个开发人员完成此类工作。(你可能是下一个试图重用init代码的人!)

在重置初始化处理程序中通常做的最后一件事是探测配置的闪存。对于大多数需要在关联目标停止时完成的芯片,要么是因为JTAG内存访问使用CPU,要么是为了防止冲突的CPU访问。

4-JTAG时钟速率

在重置初始化处理程序设置PLL和时钟之前,您可能需要以较低的JTAG时钟速率运行。请参见JTAG速度。然后,在处理程序使使用更快的JTAG时钟成为可能后,您将提高该速率。当初始低速是特定于单板的,例如,因为它取决于特定于单板的振荡器速度,那么您可能应该在单板配置文件中设置它;如果它是特定于目标的,它属于目标配置文件中。

对于大多数基于ARM的处理器,最快的JTAG时钟是CPU时钟的六分之一;
对于ARM11内核,最快的JTAG时钟4是CPU时钟的八分之一。请查阅芯片文档以确定峰值JTAG时钟速率,该速率可能低于该速率。

警告:在大多数ARM上,JTAG时钟检测耦合到核心时钟,因此使用等待中断操作的软件会阻止JTAG访问。自适应时钟提供了部分解决方法,但更完整的解决方案只是避免在JTAG调试器中使用该指令。

如果芯片和单板都支持自适应时钟,请使用jtag_rclk命令,以防您的单板与JTAG适配器一起使用,JTAG适配器也支持自适应时钟。否则,请使用适配器速度。在复位序列开始时设置慢速速率,在时钟全速运行时立即设置较快速率。

5-init_board过程

init_board过程的概念与init_target非常相似(请参见init_target过程。)-它是“线性”配置脚本的替代。此过程旨在在OpenOCD在init_targets之后进入运行阶段(请参阅进入运行阶段,)时执行。

有单独的init_target和init_board过程的想法是允许

  • 第一个过程配置所有特定于目标的过程(内部闪存、内部RAM等),
  • 第二个过程配置所有特定于板的过程(复位信号、芯片频率、复位初始化事件处理程序、外部存储器等)。

此外,当目标配置文件使用init_targets方案时,“线性”板配置文件很可能会失败(“线性”脚本在init和init_targets -之后执行),因此将这两个配置阶段分开非常方便,因为克服此问题的最简单方法是将板配置文件转换为使用init_board过程。当单板配置脚本只需要添加一些细节时,不需要覆盖目标配置文件中定义的init_targets。

就像init_targets一样,init_board过程可以被“下一级”脚本(其来源是原始脚本)覆盖,允许更多的代码重用。

### board_file.cfg ###

# source target file that does most of the config in init_targets
source [find target/target.cfg]

proc enable_fast_clock {} {
    # enables fast on-board clock source
    # configures the chip to use it
}

# initialize only board specifics - reset, clock, adapter frequency
proc init_board {} {
    reset_config trst_and_srst trst_pulls_srst

    $_TARGETNAME configure -event reset-start {
        adapter speed 100
    }

    $_TARGETNAME configure -event reset-init {
        enable_fast_clock
        adapter speed 10000
    }
}

三、Target配置文件

主板配置文件使用如上所述的命名约定与目标配置文件通信,并可能源一个或多个目标配置文件,如下所示:

source [find target/FOOBAR.cfg]

Target配置文件的目的是打包主板配置文件需要知道的关于给定芯片的所有内容。总之,Target文件应包含:

  • 设置默认值
  • 将TAP添加到扫描链
  • 添加CPU目标(包括GDB支持)
  • CPU/芯片/CPU-核心特定功能
  • 片内闪存

根据经验,Target文件只设置一个芯片。对于微控制器,这通常包括一个需要GDB目标的CPU和它的片内闪存。

更复杂的芯片可能包括多个TAP,目标配置文件可能需要定义所有TAP,然后OpenOCD才能与芯片通信。例如,一些电话芯片具有JTAG扫描链,该扫描链包括用于操作系统的ARM内核、DSP、嵌入在图像处理引擎中的另一个ARM内核以及其他处理引擎。

1 Default Value Boiler Plate Code

所有目标配置文件都应该以这样的代码开头,让主板配置文件表达环境特定的设置方式的差异。

# Boards may override chip names, perhaps based on role,
# but the default should match what the vendor uses
if { [info exists CHIPNAME] } {
   set  _CHIPNAME $CHIPNAME
} else {
   set  _CHIPNAME sam7x256
}

# ONLY use ENDIAN with targets that can change it.
if { [info exists ENDIAN] } {
   set  _ENDIAN $ENDIAN
} else {
   set  _ENDIAN little
}

# TAP identifiers may change as chips mature, for example with
# new revision fields (the "3" here). Pick a good default; you
# can pass several such identifiers to the "jtag newtap" command.
if { [info exists CPUTAPID ] } {
   set _CPUTAPID $CPUTAPID
} else {
   set _CPUTAPID 0x3f0f0f0f
}

请记住:主板配置文件可能包含多个目标配置文件,也可能包含同一目标文件多次(至少更改芯片名称)

同样,目标配置文件应定义_TARGETNAME (或_TARGETNAME 0等),并在以后定义调试目标时使用它:

set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME arm7tdmi -chain-position $_TARGETNAME

2 Adding TAPs to the Scan Chain

设置“默认值”后,将每个芯片上的TAP添加到JTAG扫描链中请参见TAP声明和TAP的命名约定

在最简单的情况下,芯片只有一个TAP,可能是用于CPU或FPGA。Atmel AT91SAM7X256的配置文件(部分)如下所示:

jtag newtap $_CHIPNAME cpu -irlen 4 -expected-id $_CPUTAPID

具有两个这样的at91sam7芯片的主板将能够两次源这样的配置文件,并为芯片名称提供不同的值,因此每次都会添加不同的TAP。

如果存在非零的-预期ID值,OpenOCD将尝试根据这些值验证实际的抽头ID。如果存在不匹配,它将发出错误消息,这有助于确定OpenOCD配置中的问题。

JTAG tap: sam7x256.cpu tap/device found: 0x3f0f0f0f
                (Manufacturer: 0x787, Part: 0xf0f0, Version: 0x3)
ERROR: Tap: sam7x256.cpu - Expected id: 0x12345678, Got: 0x3f0f0f0f
ERROR: expected: mfg: 0x33c, part: 0x2345, ver: 0x1
ERROR:      got: mfg: 0x787, part: 0xf0f0, ver: 0x3

还有更复杂的例子,芯片有多个TAP。值得关注的包括:

  • target/omap3530.cfg – with disabled ARM and DSP, plus a JRC to enable them
  • target/str912.cfg – with flash, CPU, and boundary scan
  • target/ti_dm355.cfg – with ETM, ARM, and JRC (this JRC is not currently used)

3 Add CPU targets

为CPU添加TAP后,应设置它,以便GDB和其他命令可以使用它。请参见CPU配置。对于上面的at91sam7示例,命令可以是这样的;请注意,不需要$_ENDIAN,因为OpenOCD默认为小端,而且此芯片不支持更改它。

set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME arm7tdmi -chain-position $_TARGETNAME

工作区域是与CPU目标关联的小RAM区域。OpenOCD使用它们来加快下载速度,并下载小代码片段来编程闪存芯片。如果芯片包括一种形式的“片上RAM”–而且许多芯片都这样做–如果可以的话,定义一个工作区域。再次以at91sam7为例,这可以看起来像:

$_TARGETNAME configure -work-area-phys 0x00200000 \
             -work-area-size 0x4000 -work-area-backup 0

4 定义工作在SMP中的CPU目标

设置目标后,您可以定义在SMP中工作的目标列表。

set _TARGETNAME_1 $_CHIPNAME.cpu1
set _TARGETNAME_2 $_CHIPNAME.cpu2
target create $_TARGETNAME_1 cortex_a -chain-position $_CHIPNAME.dap \
-coreid 0 -dbgbase $_DAP_DBG1
target create $_TARGETNAME_2 cortex_a -chain-position $_CHIPNAME.dap \
-coreid 1 -dbgbase $_DAP_DBG2
#define 2 targets working in smp.
target smp $_CHIPNAME.cpu2 $_CHIPNAME.cpu1

在上面的示例中,2个CPU在SMP中工作。在SMP中,只创建了一个GDB实例,并且:

  • 一组硬件断点在列表中的所有目标上设置相同的断点。

  • 停止命令触发列表中的所有目标的停止。

  • 恢复命令触发写入上下文和列表中所有目标的重新启动。

  • 在断点之后:被断点停止的目标将显示到GDB会话中。

  • 实现专用的GDB串行协议数据包,用于切换/检索GDB会话显示的目标,请参阅将OpenOCD SMP与GDB一起使用。

  • SMP行为可以动态禁用/启用。在cortex_a 上,已实现以下命令。

  • cortex_a smp on:启用SMP模式,行为如上所述。

  • cortex_a smp off:禁用SMP模式,当前目标是GDB会话中显示的目标,现在只有此目标由GDB会话控制。此行为在系统启动期间非常有用。

  • cortex_a smp:显示当前SMP模式。

  • cortex_a smp_gdb:显示/修复GDB会话中显示的核心ID,请参见以下示例。

>cortex_a smp_gdb
gdb coreid  0 -> -1
#0 : coreid 0 is displayed to GDB ,
#-> -1 : next resume triggers a real resume
> cortex_a smp_gdb 1
gdb coreid  0 -> 1
#0 :coreid 0 is displayed to GDB ,
#->1  : next resume displays coreid 1 to GDB
> resume
> cortex_a smp_gdb
gdb coreid  1 -> 1
#1 :coreid 1 is displayed to GDB ,
#->1 : next resume displays coreid 1 to GDB
> cortex_a smp_gdb -1
gdb coreid  1 -> -1
#1 :coreid 1 is displayed to GDB,
#->-1 : next resume triggers a real resume

5 Chip Reset Setup

通常,您应该将reset_config命令放入单板文件中。你认为你知道的关于芯片的大多数事情都可以被董事会调整。

某些芯片有特定的TRST和SRST信号管理方式。在不寻常的情况下,这些是芯片特定的,永远不能通过电路板布线更改,它们可以在这里。例如,有些芯片在没有两个信号的情况下无法支持JTAG调试。

如果可以,提供重置断言事件处理程序。这样的处理程序使用JTAG操作重置目标,让此目标配置用于不提供可选SRST信号的系统,或在不想一次重置所有目标的系统上使用。这样的处理程序可能会写入芯片寄存器以强制重置,使用JRC来执行此操作(最好是-目标可能被楔形!),或者强制看门狗计时器触发。(对于Cortex-M目标,这不是必要的。目标驱动程序知道如何在SRST不可用时使用触发NVIC重置。)

如果某些芯片要与JTAG一起使用,在复位处理期间需要特别注意。例如,可能需要在目标的TAP重置后立即发送一些命令,提供重置-取消断言-后事件处理程序,该处理程序写入芯片寄存器以报告正在完成JTAG调试。另一个方法是重新配置看门狗,以便在调试器中停止内核时停止计数。

JTAG时钟约束在重置期间经常会发生变化,在某些情况下,目标配置文件(而不是主板配置文件)是处理其中一些问题的正确位置。例如,复位后,大多数芯片立即使用比以后使用的时钟慢的时钟运行。这意味着,在重置后(以及可能,当OpenOCD首次启动时),它们必须使用比以后使用的更慢的JTAG时钟速率。请参见JTAG速度。

重要提示:当您调试芯片复位后立即运行的代码时,正确处理这些问题至关重要。特别是,如果您在重置后OpenOCD验证扫描链时看到间歇性故障,请查看您是如何设置JTAG时钟的。

6 The init_targets procedure

目标配置文件可以是“线性”(在配置阶段解析时逐行执行的脚本,请参阅配置阶段,),也可以包含名为init_targets的特殊过程,该过程将在进入运行阶段时执行(在解析所有配置文件后或在init命令之后),请参见进入运行阶段。)此类过程可以被“下一级”脚本(源原始脚本)覆盖。

当基本目标配置文件提供通用配置过程和init_targets过程时,此概念有助于代码重用,然后可以在“更具体”的目标配置文件中获取和增强或更改这些过程。这对于“线性”配置脚本是不可能的,因为为它们提供来源会执行它们提供的每个初始化命令。

### generic_file.cfg ###

proc setup_my_chip {chip_name flash_size ram_size} {
    # basic initialization procedure ...
}

proc init_targets {} {
    # initializes generic chip with 4kB of flash and 1kB of RAM
    setup_my_chip MY_GENERIC_CHIP 4096 1024
}

### specific_file.cfg ###

source [find target/generic_file.cfg]

proc init_targets {} {
    # initializes specific chip with 128kB of flash and 64kB of RAM
    setup_my_chip MY_CHIP_WITH_128K_FLASH_64KB_RAM 131072 65536

将“linear”配置文件转换为init_targets版本的最简单方法是在此过程中包含每一行“代码”(即不是源命令、过程等)。

有关此方案的示例,请参见LPC2000目标配置文件。

init_boards过程是关于单板配置文件的类似概念(请参见init_board过程。)

7 The init_target_events procedure

名为init_target_events 的特殊过程在init_target之后(请参阅init_target过程。)和init_board之前(请参阅init_board过程。)运行。它用于为尚未分配这些事件的目标设置默认目标事件。

8 特定于ARM核心的黑客攻击

如果芯片有DCC,请使能DCC。如果芯片是具有一些特殊高速下载功能的ARM9,请启用它。

如果存在,则应禁用MMU、MPU和CACHE。

一些ARM内核配备了跟踪支持,允许检查指令和数据总线活动。跟踪活动通过核心扫描链之一上的**“嵌入式跟踪模块”(ETM)控制**。ETM通过“跟踪端口”发送大量数据。(请参见ARM硬件跟踪。)如果使用的是外部跟踪端口,请在单板配置文件中配置它。

如果您使用的是片内“嵌入式跟踪缓冲区”(ETB),请在目标配置文件中配置它。

etm config $_TARGETNAME 16 normal full etb
etb config $_TARGETNAME $_CHIPNAME.etb

9 内部闪存配置

这仅适用于内置闪存的微控制器。

永远不要在“目标配置文件”中定义芯片外部的任何类型的闪存。(例如,芯片选择0上的引导闪存。)此类闪存信息位于主板文件中,而不是目标(芯片)文件中。

示例:

  • at91sam7x256 - has 256K flash YES enable it.
  • str912 - has flash internal YES enable it.
  • imx27 - uses boot flash on CS0 - it goes in the board file.
  • pxa270 - again - CS0 flash - it goes in the board file.

四、转换配置文件

如果您有另一个硬件调试器或工具集(Abatron、BDI2000、BDI3000、CCS、Lauterbach、SEGGER、Macraigor等)的配置文件,则将其转换为OpenOCD语法通常相当简单。

创建配置脚本最棘手的部分通常是设置PLL、DRAM等的重置初始化序列。

翻译时可以使用的一个技巧是编写小型Tcl处理,将语法转换为OpenOCD语法。这可以避免手动翻译错误,并使以后更容易转换其他脚本。

将古怪的参数转换为简单的搜索和替换作业的示例:

#   Lauterbach syntax(?)
#
#       Data.Set c15:0x042f %long 0x40000015
#
#   OpenOCD syntax when using procedure below.
#
#       setc15 0x01 0x00050078

proc setc15 {regs value} {
    global TARGETNAME

    echo [format "set p15 0x%04x, 0x%08x" $regs $value]

    arm mcr 15 [expr {($regs >> 12) & 0x7}] \
        [expr {($regs >> 0) & 0xf}] [expr {($regs >> 4) & 0xf}] \
        [expr {($regs >> 8) & 0x7}] $value
}
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OpenOCD是一个开源的软件调试和编程工具,它支持许多不同的硬件平台和芯片,包括第方芯片。为了添加第方芯片的支持,我们需要做下列工作: 1. 了解芯片的调试接口和协议:首先,我们需要获取第方芯片的数据手册,了解它的调试接口和通信协议。这些细节通常包括调试端口的引脚定义、通信协议(如JTAG或SWD)和指令集。 2. 修改OpenOCD配置文件OpenOCD使用配置文件来定义和配置支持的芯片。我们需要编辑配置文件,添加第方芯片的定义和其他必要的参数。这些参数通常包括芯片的ID代码、调试接口类型、频率和通信速度等。 3. 编译和安装OpenOCD:在修改配置文件后,我们需要重新编译OpenOCD,并将其安装到我们的开发环境中。这通常涉及到使用工具链编译源代码、链接库文件,并将可执行文件复制到适当的目录中。 4. 测试和调试:一旦安装完成,我们可以使用OpenOCD来连接并调试我们的第方芯片。通过适当的命令和选项,我们可以初始化和配置调试接口,并执行调试操作,如读写寄存器、设置断点和单步调试等。如果有任何问题,我们可以通过查看OpenOCD的输出日志、调试和修改配置文件来解决。 通过这些步骤,我们就能够将一个第方芯片添加到OpenOCD的支持列表中,并使用OpenOCD进行调试和编程操作。这为开发人员提供了更多的灵活性和工具选择,以便在开发过程中更好地支持各种类型的芯片和设备。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值