轻松学习Zephyr BSP: 31-配置 Company SoC平台

摘要:本文是 Zephyr BSP 移植系列的第 31 篇,聚焦 Kconfig 在 Company SoC 平台化中的核心作用。文章首先厘清 Devicetree 与 Kconfig 的分工——前者描述硬件资源,后者决定软件编译与功能配置;随后系统讲解 Kconfig 的生成链路(.configautoconf.h → C 编译)、SoC/Board/Driver 三层的 Kconfig 组织方式,以及 depends onselectdefault 等关键机制的区别。通过 Company UART 驱动实例,串联起 Devicetree、Kconfig、CMake 与 Device Model 的完整协作链路,最终建立「Kconfig(软件配置)— Devicetree(硬件描述)— CMake(编译组织)」的三角模型,帮助读者把 Company SoC 从固定代码升级为可配置的硬件软件平台。

Kconfig:把 Company SoC 变成可配置平台

前面我们已经完成了:

20 理解 Zephyr
↓
21 SoC Port Skeleton
↓
22 CPU / Architecture
↓
23 Startup
↓
24 Interrupt Controller
↓
25 Clock / Reset
↓
26 Devicetree
↓
27 Binding
↓
28 UART Driver
↓
29 GPIO / SPI / I2C / Timer
↓
30 Board Support Package

31 — Kconfig,我们要解决一个非常关键的问题:

Devicetree 描述「硬件是什么」,Kconfig 决定「软件编译什么、启用什么、配置成什么」。

对于公司自己的 SoC,这一步意味着:

Company SoC
	│
	├── Devicetree
	│	 └── 描述硬件资源
	│
	└── Kconfig
		└── 控制软件功能

最终形成:

┌────────────────────────────────────────────┐
│              Company Zephyr BSP            │
│                                            │
│  Devicetree              Kconfig           │
│  ──────────              ───────           │
│  UART0                   CONFIG_UART       │
│  UART1                   CONFIG_GPIO       │
│  GPIO                    CONFIG_SPI        │
│  SPI0                    CONFIG_I2C        │
│  Timer                   CONFIG_TIMER      │
│  IRQ                     CONFIG_SOC_...    │
│                                            │
└────────────────────────────────────────────┘

一、先搞清楚:Devicetree 和 Kconfig 为什么都需要?

这是 Zephyr BSP 最容易混淆的地方之一。

假设 Company SoC 有:

UART0
UART1
UART2
GPIO
SPI0
SPI1
I2C0
TIMER0
TIMER1

Devicetree 可以描述:

uart0: uart@40000000 {
    compatible = "company,uart";
    reg = <0x40000000 0x1000>;
    interrupts = <5>;
    status = "okay";
};

它回答:
UART0 在哪里?
但是它不回答:
UART 驱动要不要编译?
这就是 Kconfig 的职责。
例如:

CONFIG_SERIAL=y
CONFIG_UART_CONSOLE=y
CONFIG_UART_COMPANY=y

于是:

Devicetree
↓
UART0 存在

Kconfig
↓
UART 驱动参与编译
↓
UART Console 开启

两者共同决定最终系统。

二、一个非常重要的理解

可以先记住这个模型:

Devicetree = Hardware Description
Kconfig = Software Configuration

或者更准确一点:

Devicetree
	→ 描述具体硬件实例、资源、连接关系

Kconfig
	→ 决定功能、驱动、编译选项、系统能力

例如:

Devicetree

uart0 {
    reg = <...>;
    interrupts = <...>;
    clocks = <...>;
};

描述:

UART0
地址
IRQ
Clock

Kconfig

CONFIG_SERIAL=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_CONSOLE=y

描述:

编译 UART
支持 IRQ
把 UART 作为 Console

三、Zephyr Kconfig 最终生成什么?

这一点和前面讲 Devicetree 的过程非常类似。

你执行:

west build -b company_board app

Kconfig 会参与:

Kconfig
   ↓
menuconfig / configuration
   ↓
.config
   ↓
autoconf.h
   ↓
C/C++ 编译

最重要的文件之一是:

build/zephyr/.config

例如:

CONFIG_SOC_SERIES_COMPANY_X=y
CONFIG_SOC_COMPANY_X1=y

CONFIG_SERIAL=y
CONFIG_UART_CONSOLE=y
CONFIG_UART_COMPANY=y

CONFIG_GPIO=y
CONFIG_GPIO_COMPANY=y

然后生成:

build/zephyr/include/generated/zephyr/autoconf.h

里面会出现类似:

# define CONFIG_SERIAL 1
# define CONFIG_UART_CONSOLE 1
# define CONFIG_UART_COMPANY 1
# define CONFIG_GPIO 1

所以最终 C 代码可以:

# ifdef CONFIG_UART_CONSOLE
...
# endif

或者:

# ifdef CONFIG_UART_COMPANY
...
# endif

四、Company SoC 应该在哪里定义 Kconfig?

我们假设公司 SoC:

company_x1

对应:

soc/arm/company_x1/

一个典型结构可以是:

zephyr/
└── soc/
    └── arm/
        └── company/
            └── x1/
                ├── Kconfig
                ├── Kconfig.defconfig
                ├── CMakeLists.txt
                ├── soc.c
                ├── soc.h
                └── ...

Board:

boards/
└── company/
    └── company_board/
        ├── Kconfig.board
        ├── Kconfig.defconfig
        ├── company_board.dts
        └── ...

Driver:

drivers/
├── serial/
│   ├── Kconfig
│   └── uart_company.c
│
├── gpio/
│   ├── Kconfig
│   └── gpio_company.c
│
└── ...

注意:
SoC、Board、Driver 的 Kconfig 并不是全部放在一个 Kconfig 文件里。
而是通过 Kconfig 的 source 机制组合起来。

五、Company SoC 的第一个 Kconfig

最简单可以理解成:

menuconfig SOC_SERIES_COMPANY_X
	bool "Company X SoC series"

然后:

config SOC_COMPANY_X1
	bool "Company X1"
	select SOC_SERIES_COMPANY_X

概念上:

CONFIG_SOC_SERIES_COMPANY_X
	│
	└── CONFIG_SOC_COMPANY_X1

也就是:

Company SoC Series
	│
	├── X1
	├── X2
	└── X3

这就是未来公司有多个 SoC 型号时的基础。

六、为什么不能直接写一个 CONFIG_SOC?

例如你当然可以想象:

CONFIG_COMPANY_SOC=y

但实际大型 BSP 不会这么简单。

因为公司通常会有:

Company X1
Company X2
Company X3
Company X5

它们可能:

CPU 相同
Interrupt Controller 相同
UART 相同
GPIO 相同


但是:
X1:
	256 KB RAM
	1 MB Flash

X2:
	512 KB RAM
	2 MB Flash

X3:
	1 MB RAM
	4 MB Flash

所以 Kconfig 需要表达:

Company SoC Family
	│
	├── X1
	├── X2
	└── X3

而不是一个简单的:

CONFIG_COMPANY_SOC

七、Kconfig 和 CMake 是什么关系?

这是 BSP 中另外一个很重要的关系。

假设:

CONFIG_UART_COMPANY=y

那么 CMake 可以决定:

zephyr_library()
zephyr_library_sources(uart_company.c)

或者更典型地:

zephyr_library_sources_ifdef(
	CONFIG_UART_COMPANY
	uart_company.c
)

于是:

Kconfig
	│
	│ CONFIG_UART_COMPANY=y
	↓
CMake
	│
	│ 加入 uart_company.c
	↓
Compiler
	│
	↓
uart_company.o

所以可以把三者理解成:

Devicetree
	│
	├── 硬件实例
	│
	↓
Kconfig
	│
	├── 软件功能是否启用
	│
	↓
CMake
	│
	├── 哪些源文件进入编译
	│
	↓
Compiler

八、一个真实的 Company UART 例子

假设:

drivers/serial/uart_company.c

我们定义:

CONFIG_UART_COMPANY

Kconfig:

config UART_COMPANY
    bool "Company UART driver"
    default y
    depends on SERIAL
    help
      Enable Company SoC UART driver.

然后 CMake:

zephyr_library()

zephyr_library_sources_ifdef(
    CONFIG_UART_COMPANY
    uart_company.c
)

于是:

CONFIG_UART_COMPANY=n

结果:

uart_company.c
↓
不进入最终编译

而:

CONFIG_UART_COMPANY=y

八点五、Company UART 驱动 Kconfig 配置实战

下面把「八、一个真实的 Company UART 例子」展开成一个可以照着抄的完整实战。我们以 drivers/serial/uart_company.c 为例,从 Kconfig 定义、默认配置、CMake 编译到验证与排错,一次走通。

1. drivers/serial/Kconfig 中 UART_COMPANY 的完整定义

drivers/serial/Kconfig 中追加:

config UART_COMPANY
	bool "Company UART driver"
	default y
	depends on SERIAL
	select UART_INTERRUPT_DRIVEN
	help
	  Enable the Company SoC UART driver.
	  This driver provides UART support for Company X1 SoC.

几个字段的含义:

bool
	→ 这是一个开关,取值 y / n

default y
	→ 只要依赖满足,默认打开

depends on SERIAL
	→ 必须先有通用串口框架,本驱动才可选

select UART_INTERRUPT_DRIVEN
	→ 打开本驱动时,强制打开中断驱动支持

注意:select 在这里是「我打开时,强制别人打开」,和 depends on 的「我需要别人」方向相反。

2. Kconfig.defconfig 中默认配置写法

如果希望 Company Board 默认就启用这个驱动,可以在 Board 的 boards/company/company_board/Kconfig.defconfig 中写:

if BOARD_COMPANY_BOARD

config UART_COMPANY
	default y

config UART_CONSOLE
	default y

endif

也可以放在 SoC 层的 soc/arm/company/x1/Kconfig.defconfig

if SOC_COMPANY_X1

config UART_COMPANY
	default y

endif

区别在于作用范围:

Board 的 Kconfig.defconfig
	→ 只对这块板子生效

SoC 的 Kconfig.defconfig
	→ 对该 SoC 下所有板子生效

3. CMakeLists.txt 中 zephyr_library_sources_ifdef 的用法

drivers/serial/CMakeLists.txt 中:

zephyr_library()
zephyr_library_sources_ifdef(
	CONFIG_UART_COMPANY
	uart_company.c
)

含义:

CONFIG_UART_COMPANY=y
	→ uart_company.c 进入编译

CONFIG_UART_COMPANY=n
	→ uart_company.c 完全不参与编译

如果驱动还依赖某个头文件目录,可以这样写:

zephyr_library_include_dirs_ifdef(
	CONFIG_UART_COMPANY
	.
)

4. 编译验证步骤

在应用目录执行:

west build -b company_board app

编译完成后,检查 .config 是否包含:

grep UART_COMPANY build/zephyr/.config

预期输出:

CONFIG_UART_COMPANY=y

再检查生成的 autoconf.h

grep UART_COMPANY build/zephyr/include/generated/zephyr/autoconf.h

预期输出:

#define CONFIG_UART_COMPANY 1

最后确认目标文件确实被编译:

find build -name "uart_company.o"

如果能看到 uart_company.o,说明 Kconfig → CMake → Compiler 整条链路已经打通。

5. 常见错误排查方法

错误一:CONFIG_UART_COMPANY 没有出现在 .config 中

可能原因与排查:

depends on SERIAL 未满足
	→ 检查 CONFIG_SERIAL 是否为 y

Kconfig 文件没有被 source
	→ 检查 drivers/serial/Kconfig 是否被上层 Kconfig 引入

拼写不一致
	→ 确认 Kconfig 里是 UART_COMPANY,CMake 里也是 UART_COMPANY

错误二:CONFIG_UART_COMPANY=y 但 uart_company.c 没编译

CMakeLists.txt 没写 zephyr_library_sources_ifdef
	→ 补上对应语句

文件路径不对
	→ 确认 uart_company.c 和 CMakeLists.txt 在同一目录

缓存未刷新
	→ 执行 west build -t menuconfig 后重新 west build

错误三:menuconfig 里看不到 Company UART driver

Kconfig 文件未被 source
	→ 检查 drivers/serial/Kconfig 是否被正确引入

depends on SERIAL 未满足
	→ 先打开 Serial Drivers

错误四:改了 Kconfig 但编译结果没变

没有重新生成配置
	→ 执行 west build -t menuconfig 修改后保存

使用了旧的 build 目录
	→ 必要时 west build -p always -b company_board app 强制重建

6. 验证后的完整链路

drivers/serial/Kconfig
	→ CONFIG_UART_COMPANY=y
	→ autoconf.h 生成 #define CONFIG_UART_COMPANY 1
	→ CMakeLists.txt 的 zephyr_library_sources_ifdef 命中
	→ uart_company.c 参与编译
	→ uart_company.o 链接进固件

到这里,Company UART 驱动已经从「代码存在」变成了「可配置、可编译、可验证」的完整闭环。

结果:

uart_company.c
	↓
编译
	↓
uart_company.o
	↓
链接

九、Kconfig 的 depends on 是什么?

这是 Kconfig 最核心的机制之一。

例如:

config UART_COMPANY
	bool "Company UART driver"
	depends on SERIAL

意思是:

UART_COMPANY
	│
	└── requires SERIAL

如果:

CONFIG_SERIAL=n

那么:

CONFIG_UART_COMPANY

不能正常启用。

可以理解成:

UART_COMPANY
	│
	▼
SERIAL

这和前面我们学习的 Driver Dependency 是不同层面的概念。

前面:

UART Driver
	↓
Clock Driver
	↓
Reset Driver

属于:
运行时 / 设备依赖

而:

UART_COMPANY
↓
SERIAL

属于:
编译配置依赖
这两个一定要分开。

十、select 又是什么?

例如:

config SOC_COMPANY_X1
	bool "Company X1"
	select SOC_SERIES_COMPANY

这里:

X1
↓
select
↓
COMPANY_SOC

意味着:
选择 X1 时,Company SoC Series 自动打开。
所以:

CONFIG_SOC_COMPANY_X1=y

会导致:

CONFIG_SOC_SERIES_COMPANY=y

十一、depends on 和 select 不要混淆

简单记:

depends on

是:
我需要别人。
例如:

UART
	depends on
SERIAL

而:

select

是:
我打开时,强制别人打开。
例如:

SOC_X1
	select
SOC_SERIES_COMPANY

下面用一张表格把两者的区别一次性说清楚:

对比维度depends onselect
方向我依赖别人(被依赖项在前,我在后)我打开时强制打开别人(我在前,被打开项在后)
语义「我需要别人」——依赖项不满足时,本选项不可用「我打开时,强制别人打开」——本选项被选中时,目标项自动置为 y
使用场景驱动依赖框架、功能依赖前置能力,如 UART_COMPANY 依赖 SERIALSoC 型号自动打开所属系列、驱动自动打开中断支持,如 SOC_COMPANY_X1 选中 SOC_SERIES_COMPANY
示例代码config UART_COMPANY
 bool "Company UART driver"
 depends on SERIAL
config SOC_COMPANY_X1
 bool "Company X1"
 select SOC_SERIES_COMPANY

一句话记忆:

depends on = 我需要别人
select     = 我强制别人

可以画成:

depends on:
UART
	│
	↓
SERIAL

select:
SOC_X1
	│
	↓
SOC_SERIES_COMPANY

十二、Company Board 的 Kconfig

到了 Board 层:

boards/company/company_board/

可以有:

Kconfig.board
Kconfig.defconfig

例如:

config BOARD_COMPANY_BOARD
	bool "Company Board"
	depends on SOC_COMPANY_X1

这样形成:

BOARD_COMPANY_BOARD
	│
	↓
SOC_COMPANY_X1
	│
	↓
SOC_SERIES_COMPANY

最终:

Board
	↓
SoC
	↓
SoC Family

十三、为什么 Board 不应该决定所有东西?

例如不要把:

CONFIG_UART_CONSOLE=y
CONFIG_SPI=y
CONFIG_I2C=y
CONFIG_GPIO=y

全部硬编码成:

Company Board = 所有功能全部打开

因为不同应用可能需要不同配置。
例如:

Application A

UART Console
GPIO

Application B

UART
SPI

Application C

I2C
GPIO
Timer

同一个:

Company Board

应该能够服务多个 application。
所以:

Board
↓
提供合理默认配置

Application
↓
决定真正需要什么

这是 Kconfig 非常重要的思想。

十四、Kconfig.defconfig 是干什么的?

它主要用于:
给 Board / SoC 提供默认配置。
例如 Company Board 默认需要 UART Console:

config UART_CONSOLE
	default y

但这个默认值只针对:

BOARD_COMPANY_BOARD

那么可以放在 Board 的:

Kconfig.defconfig

概念上:

if BOARD_COMPANY_BOARD

config UART_CONSOLE
	default y

endif

于是:

选择 Company Board
	↓
UART Console 默认开启

注意:
default y 和 select 是两个不同概念。

十五、一个完整的配置链

现在把:

Board
SoC
Driver
Application

全部串起来。
假设:

west build -b company_board app

首先:

company_board
	↓
选择 SoC
	↓
company_x1

然后 Kconfig:

SOC_COMPANY_X1=y
	↓
SOC_SERIES_COMPANY=y

Board 默认配置:

UART_CONSOLE=y

Application:

CONFIG_SERIAL=y
CONFIG_GPIO=y

最终:

build/zephyr/.config

可能是:

CONFIG_SOC_SERIES_COMPANY=y
CONFIG_SOC_COMPANY_X1=y

CONFIG_SERIAL=y
CONFIG_UART_CONSOLE=y
CONFIG_UART_COMPANY=y

CONFIG_GPIO=y
CONFIG_GPIO_COMPANY=y

十六、然后 C 代码看到什么?

Kconfig 最终生成:

autoconf.h

例如:

# define CONFIG_SOC_SERIES_COMPANY 1
# define CONFIG_SOC_COMPANY_X1 1

# define CONFIG_SERIAL 1
# define CONFIG_UART_CONSOLE 1
# define CONFIG_UART_COMPANY 1

# define CONFIG_GPIO 1
# define CONFIG_GPIO_COMPANY 1

于是:

# ifdef CONFIG_UART_COMPANY

/\* Company UART driver \*/

# endif

成立。
如果:

CONFIG_UART_COMPANY=n

那么对应代码可以完全不参与编译。

十七、这和 Devicetree 的关系非常重要

假设:

uart0: uart@40000000 {
    compatible = "company,uart";
    reg = <0x40000000 0x1000>;
    interrupts = <5>;
    status = "okay";
};

这意味着:

硬件存在

但是:

CONFIG_UART_COMPANY

如果没有打开:

驱动可能根本没有编译

所以:

DT status = "okay"

并不等于:

Driver 一定被编译

真正完整的条件更接近:

Devicetree instance
        +
Kconfig driver enabled
        +
Driver source compiled
        ↓
struct device

这就是为什么你之前学的:

DT_NODELABEL
DEVICE_DT_DEFINE
struct device

还不是整个故事。

十八、一个完整的 Company UART 链路

最终我们可以把 UART 画成:

                 Company Board
                       │
                       ▼
                Devicetree
                       │
             uart0 = "okay"
                       │
                       │
                       ▼
                 DT instance
                       │
                       │
Kconfig ───────────────┤
CONFIG_SERIAL=y        │
CONFIG_UART_COMPANY=y  │
                       ▼
                uart_company.c
                       │
                       ▼
              DEVICE_DT_DEFINE()
                       │
                       ▼
                 struct device
                       │
                       ▼
                    printk

这里:

Devicetree

负责:

"UART0 是什么"

Kconfig:

"UART 驱动要不要存在"

CMake:

"uart_company.c 要不要编译"

Driver:

"如何操作 UART0"

Device Model:

"创建 struct device"

十九、Kconfig 不只是「开关」

很多初学者会把 Kconfig 理解成:

CONFIG_xxx=y/n

其实它远不止如此。
Kconfig 可以配置:

Boolean

config UART_CONSOLE

bool

结果:

y / n

Integer

config COMPANY_UART_BAUDRATE
	int "UART baudrate"
	default 115200

结果:

CONFIG_COMPANY_UART_BAUDRATE=115200

Hex
例如:

config COMPANY_FLASH_BASE
	hex "Flash base address"
	default 0x08000000

String

config COMPANY_NAME
	string "Company name"

因此 Kconfig 不仅决定:

有没有功能

还可以决定:

功能怎么配置

二十、一个 Company SoC 的典型 Kconfig 树

最终我们希望得到类似:

Company SoC
│
├── SoC Family
│   └── COMPANY_X
│
├── CPU
│   └── Cortex-M4
│
├── Drivers
│   ├── UART
│   │   ├── UART0
│   │   └── UART1
│   │
│   ├── GPIO
│   ├── SPI
│   ├── I2C
│   └── TIMER
│
└── Features
    ├── Console
    ├── Shell
    ├── Logging
    ├── PM
    └── DMA

用户可以通过:

west build -t menuconfig

看到配置界面。
例如:

[*] Serial Drivers
    [*] UART Console
    [*] Company UART driver
    (115200) Default baudrate

[*] GPIO Drivers
[*] SPI Drivers
[ ] I2C Drivers

[*] Logging
[ ] Power Management

这时候:
你的 Company BSP 已经从「固定的一套代码」变成了「可以配置的硬件软件平台」。

二十一、west build -t menuconfig

这是今天最值得亲手做的实验。

在应用目录:

west build -t menuconfig

你会进入:

┌─────────────────────────────────────┐
│ Zephyr Kernel Configuration         │
│                                     │
│ General Kernel Options              │
│ Device Drivers                      │
│ Serial Drivers                      │
│ GPIO Drivers                        │
│ SPI Drivers                         │
│ I2C Drivers                         │
│ Logging                             │
│ Power Management                    │
│                                     │
└─────────────────────────────────────┘

修改配置以后:

.config

会改变。
然后重新:

west build

Kconfig:

.config
   ↓
autoconf.h
   ↓
C compilation

二十二、真正做 Company BSP 时建议这样组织

如果你未来公司的 SoC BSP 有:

Company X1
Company X2
Company X3

推荐形成:

soc/
└── arm/
    └── company/
        ├── Kconfig
        │
        ├── x1/
        │   ├── Kconfig
        │   ├── Kconfig.defconfig
        │   └── ...
        │
        ├── x2/
        │   ├── Kconfig
        │   └── ...
        │
        └── x3/
            ├── Kconfig
            └── ...

然后:

drivers/
├── serial/
│   └── Kconfig
├── gpio/
│   └── Kconfig
├── spi/
│   └── Kconfig
└── i2c/
    └── Kconfig

Board:

boards/
└── company/
    ├── board_a/
    │   ├── Kconfig.board
    │   └── Kconfig.defconfig
    │
    └── board_b/
        ├── Kconfig.board
        └── Kconfig.defconfig

这样:

Company SoC
    │
    ├── X1
    ├── X2
    └── X3
          │
          ├── Board A
          ├── Board B
          └── Board C

可以形成一个真正可维护的 BSP。

二十三、到这里,你应该建立一个非常重要的「三角模型」

以后看到 Zephyr BSP,不要只想到:

Devicetree

而应该想到:

                 Kconfig
              软件配置/功能
                  ▲
                  │
                  │
                  │
Devicetree ◄──────┼──────► CMake
 硬件描述          │       编译组织
                  │
                  ▼
             Driver / SoC

更完整一点:

             ┌──────────────┐
             │   Kconfig    │
             │ 软件配置     │
             └──────┬───────┘
                    │
                    ▼
             CONFIG_xxx
                    │
                    ▼
             ┌──────────────┐
             │    CMake     │
             │ 编译哪些代码 │
             └──────┬───────┘
                    │
                    ▼
             ┌──────────────┐
             │   Driver     │
             └──────┬───────┘
                    ▲
                    │
             ┌──────┴───────┐
             │ Devicetree   │
             │ 硬件是什么   │
             └──────────────┘

二十四、从 20 → 31,你现在真正学会了什么?

现在整个 Company SoC BSP 已经可以串起来:

20 理解 Zephyr
│
21 SoC Port Skeleton
│
22 CPU / Architecture
│
23 Startup
│
24 Interrupt Controller
│
25 Clock / Reset
│
26 Devicetree
│
27 Binding
│
28 UART Driver
│
29 GPIO / SPI / I2C / Timer
│
30 Board Support Package
│
31 Kconfig
│
▼
可配置 Company BSP

也就是说,我们已经从:

「让 Zephyr 跑起来」

进入:

「让 Zephyr 支持公司的 SoC 产品线」
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值