裸机工程开发调试

裸机工程开发调试

嵌入式系统的组成

嵌入式系统设备遵循自底向上的基本结构

嵌入式微处理器

image-20200630163206835

嵌入式操作系统

image-20200630161820279

外围硬件设备

image-20200630163419089

用户应用程序

image-20200630163326782 image-20200630164423794 image-20200630164440760

###s5p6818系统主要资源

s5p6818是三星公司推出的64位RISC微处理器, 其CPU采用的是ARM CortexA53内核, 基于ARMv7ARMv8指令集架构

八核, 保守主频1.4GHZ, 向下完全兼容4418核心, 可以做到嵌入式产品硬件的无缝升级

s5p6818结构框图如下:

image-20200630164541935

s5p6818试验仪介绍

s5p6818核心板资源介绍:

image-20200630164859509 image-20200702212432829

s5p6818寻址空间介绍

s5p6818寻址空间采用统一编址方式进行管理

寻址空间映射图:

image-20200630164715355

image-20200630165452080

Normal I/O就是我们常说的特殊功能寄存器

GPIO等内容在这里进行配置

image-20200630170151008

iROMiRAM具体分布:

image-20200630170121195

Internal ROM启动, 是可以由BootMode相关引脚选择的多种程序加载方式, 即是从内部还是从外部等途径来加载程序(P94)

image-20200630170108754

P93P95决定了外部程序的加载顺序

RST_CFGn对应的引脚通过查表和原理图来最终确定程序加载启动流程

裸机开发调试

常用GNU工具介绍

GNU组织不仅给我们带来了许多开源软件工程, 还带来了强大的GNU编译工具

GNU提供的常用工具包括:

  • 预处理器 cpp

  • C编译器 gcc

  • C++编译器

  • g++汇编器 as

  • 链接器 ld

  • 二进制工具集 objcopy、 objdump、 ……

  • ls /usr/local/arm/4.3.2/bin/

下面介绍几个常用的工具

nm : 符号显示器

显示符号 $nm -n main_elf

显示内容:

  • 第一列为 符号地址

  • 第二列为 符号所在段

  • 第三列为 符号名称

image-20200630170831625

各段含义 :

描述
b / B.bss(b静态 / B非静态)未初始化变量
d / D.data(d静态 / D非静态)已初始化变量
r / R.rodata(r静态 / R非静态)只读数据段
t / T.text(t静态 / T非静态)函数
A不可改变的绝对值
C.o中未初始化非静态变量
N调试用的符号
U表示符号只有声明没有定义
W一个弱符号,尚未被明确标记为弱对象符

nm符号显示器总结 ;

静态变量非静态的全局变量, 所分配的段只和是否初始化有关, 如果初始化了则被分配 在.data段中, 否则在.bss段中

函数无论是静态还是非静态的, 总是被分配在.text中,小写 t 表示静态, 大写 T 表示非静态

函数内的局部变量由于是分配在上的, 所以在 nm 中是看不到他们

objdump: 显示二进制文件的信息

  • 查看所有段信息 $objdump -h main_elf

  • 查看文件头信息 $objdump -f main_elf

  • 查看反汇编 $objdump -d main_elf

  • 查看内嵌反汇编 $objdump -S -d main_elf

objcopy : 段剪辑器

  • 去除 elf 格式信息 $objcopy -O binary -S main_elf main.bin
LMA和VMA

从源代码到可执行的代码,一般要经过一下几个过程:

源代码编辑 -> 编译 -> 链接 -> 装载 -> 执行

编译

简单的说就是用编译工具,将你的源码变成可以执行的二进制文件,即目标文件,当然只是对应某一种硬件平台,比如x86,arm

链接

就是将多个目标文件合并为一个目标文件,称作可执行文件

每个目标文件都包含一连串的section,最常见,最基础的有:

.text: 代码段, 就是cpu要运行的指令代码

.data: 数据段, 程序中的一些数据,如初始化的全局变量,静态变量等都放在这个段里

.bss: 未初始化段,记录了程序里未初始化的全局变量,就相当于只记录对应的名字,留着程序运行前去初始化为0,所以此处不占用具体空间。打个比方,只记录人名,没有人站在这里.

而对应的.text 和.data段,都是既有人名(函数或者变量名),又占用对应的地方(包含具体空间记录到底, 是什么指令代码和数据的数值是多少)

section一般分为loadable和allocatable 通俗点说就是:

loadable,可加载 , 就是,原来目标文件里面包含对应的代码或数据,所以,装载器要把这些内容,load 到对应的地址,以便程序可以运行;

allocatable, 可分配的 ,最简单理解就是上面的**.bss段**,那里记录了人名,到时候,你要给这些人名分配空间给你站的地方,对应着也就是变量所要的具体内存空间。

对于目标文件中的 loadable 或 allocatable的section,其都有两个地址:VMA 和 LMA

LMA(Load Memory Address):内存装载地址

load, 装载

如果想要使你的程序(即由你的源码,通过编译器的编译,链接器的链接,形成的可执行文件), 能做内存里面运行,那么肯定要涉及到一点,就是,把你的程序从常见的存储硬盘里面搬到内存里面,然后才有可能运行。而这里的装载就是这这个意思。就是把程序从硬盘里面装载到内存里

对应的,放到内存的什么地方?就是LMA。详细点说,就是把其中的.text代码段,.data数据段等内容,搬到,也就是copy到内存的LMA地址处。

内存

程序运行的本质,就是cpu读取到指令,然后执行

如果想要运行你的程序,首先,你应该把你对应的指令放到合适的位置,cpu才能读到,才能执行。此处合适的地方,有人想到,直接放到硬盘里,cpu过来读取,然后执行不就可以了嘛,还不用这么麻烦将(指令)代码搬来搬去的,多省事。但是实际上,系统就是这么“笨”地搬来搬去,原因在于,从硬盘上直接读取指令,速度比直接从内存要慢很多倍(一般pc上的内存是各种各样的RAM,如ddr,但此处统称为memory/内存),所以系统才会不嫌麻烦,把代码拷贝到内存里面去,然后从内存里面读取指令,再执行,这样速率会高很多。

所以简单的说就是,为了总体效率,对于普通系统,比如pc,程序的执行都是在memory,内存里面执行的。

总结:

代码被装载到内存的某个地方,那个地方的地址,就是LMA

VMA:Virtual Memory Address 虚拟内存地址

简单的说就是,你程序运行时所对应的地址。

此处所谓的虚拟,一般来说,指的是启用了MMU之后,才有了虚拟地址实地址。此处,我们可以简单的理解为,就是内存的实际地址即可。

程序运行前,要把程序的内容,拷贝到对应的内存地址处,然后才能运行。

总结:

代码运行的时候,此时对应的地址,就是VMA

在多数情况下,LMA和VMA是相等的

理解:

如果是普通pc电脑,也就是上面说的,大多数情况下,LMA和VMA是一样的,也就是,程序被加载到内存的什么地方,也就在什么地方运行。

程序被放到了ROM中,比如设置位只读的Nor Flash中,那么LMA的地址就是Nor Flash的地址

随便举例为0x1000000,而程序要运行时的地址是内存地址,比如0x30000000,也就是VMA是0x30000000,这时候,就要我们自己保证,在程序运行之前,把自己的程序,从LMA = 0x10000000拷贝到VMA = 0x30000000 处,然后程序才可以正常运行。

有人会问,反正对应ROM来说,CPU也是可以直接从ROM里面读取代码,然后运行。为何还要前面提到的,弄个LMA和VMA不同,搬来搬去呢?因为ROM,顾名思义,是只读的,只能读取,不能写入。

而程序中的代码段,由于只是被读取,不涉及到修改写入,是没问题的。但是对于数据段和bss位初始化段来说,里面的所有的程序的变量,多数都是在运行的时候,不仅要读取,而且要被修改成新的值,然后写入新的值,所以,如果还是放到ROM里面,就没法修改写入。

而且,另一个原因是,cpu从ROM,比如常见的Nor Flash中读取代码的速度,要远远小于从RAM,比如常见的SDRAM中读取的速度,所以,才会牵涉到将代码烧写到ROM里面,然后代码的最开始,将此部分程序reaload重载,也就是从此处的ROM的地址,即LMA,重新拷贝到SDRAM中去,也就是VMA的地方的地方,然后从那里运行。

关于LMA和VMA

Linker,连接器的作用:

1、将LMA写到(可执行的)二进制文件里面去

2、解析符号。即,把不同的符号,根据符号表中的信息,转换成对应的地址。此处只涉及VMA,即程序运行时的地址。

Loader,装载器的作用:

1、从二进制文件中读出对应的段的信息,比如text,data,bss等段的信息

将内容拷贝到对应的LMA的地址处,所谓,装载(对应内容)到装载地址(LMA)

2、如果发现VMA != LMA, 即 程序运行时候的地址和刚刚把程序内容拷贝到的地址LMA不一样,那么就要把对应的内容,此处主要是data,数据段的内容,从刚刚装载到的位置,LMA处,拷贝到VMA处,这样,程序运行的时候,才能够在执行的时候,找到对应VMA处的变量,才能找到对应的值,程序才能正常运行。

程序的编译链接过程

最后一步是将目标文件交给链接器链接生成可执行文件

  • gcc -E -o main.i main.c
  • gcc -S -o main.S main.i
  • gcc -c -o main.o main.S
  • gcc -o main_elf main.o
image-20200630174113078 image-20200630174207491

一个标准应用程序通常都由链接器采用默认链接脚本(arm-linux-ld --verbose)将“ 用户程序” 和“ ” 共同链接生成可执行程序

假如不采用系统给定的默认链接脚本, 我们应该怎样进行链接 :

ld -o main main.o

结论:

链接过程中需要一个标号( _start ) 作为程序入口

标号(_start)的作用是: 将用户程序从汇编带到了C语言程序入口, 即main()函数, 从此开始我们的应用程序之旅

_start标号所在的汇编文件在编译工具链下面:

4.3.2/arm-none-linux-gnueabi/libc/usr/lib/crt1.o

我们可以自己实现**_start入口, 或者重新指定一个新的入口**

手动链接并生成可执行文件过程如下:

直接通过参数指定程序入口段地址

arm-linux-ld -Ttext=0x30000 -Tdata=0x40000 -e main -o app head.o main.o

通过链接脚本来指定程序入口段地址

arm-linux-ld -Tapp.lds -o app head.o main.o
//连接文件app.lds为:
ENTRY(main)
SECTIONS
{	//“*”号指所有目标, 可以指定.o目标文件, 多个用空格隔开
    .=0x30000;	//“.”指的是当前位置
    .text:{*(.text)}
    .=0x40000;
    .data:{*(.data)}
    .bss:{*(.bss)}
}
裸机工程构建及调试

裸机程序完整编译过程:

  • gcc -c -o main.o main.c
  • gcc -c -o uart.o uart.c
  • ld -Tapp.lds -o app_elf main.o uart.o
  • objcopy -O binary -S app_elf app.bin

裸机工程管理文件

  • 各目录Makefile

  • 链接脚本app.lds

  • 规则文件Rules.mk

最后将app.bin下载到裸板内存中并执行

  • 工程实例: 00-pro-demo
  • 下载命令: update app
  • 运行命令: go
  • 退出程序: crtl+c

基础代码中的Makefile文件

# 剪除elf格式信息, 这样才能在裸机状态下执行
app.bin:$(OBJS)
	$(LD) -v $(LFLAGS) -o app_elf $(OBJS) $(ARMLIBS)
	$(OBJCOPY) -O binary -S app_elf $@
	$(OBJDUMP) -D -m arm app_elf > app.dis
	$(NM) -v -l app elf > app.map

构建嵌入式开发平台

嵌入式 linux 软件系统由 bootloader、kernel、root filesystem 构成,如下:

Bootloader(一次固化) + 内核(多次更新) + 根文件系统制作

资料拷贝:

资料存放路径如下:

6_平台\linux_resource\3.4.39_tools_

6_平台\3.4.39_tools

将上面路径下用到的文件拷到自己虚拟机中,存放在自建目录下:

例如(推荐):/home/edu/share/linux_platform2.6.35.7/

注意: 以上资料一定要拷贝放入 linux 虚拟机目录下, 且目录有一切权限,一定不能放到 /mnt/hgfs 所映射的磁盘中

安装交叉编译器 arm-linux-gcc-4.3.2

进入虚拟机中找到你上步自建的目录中, 找到 arm-linux-gcc-4.3.2.bz2 文件:

cd /home/edu/share/linux_platform2.6.35.7/  

查看自己的虚拟机是否安装交叉编译工具链:

echo $PATH

image-20200702223501699

安装以下交叉编译工具: (4.3.2: 用于交叉编译各种镜像)

# 如果有这个目录就不用建了
mkdir –p /usr/local/arm 

sudo tar jxvf arm-linux-gcc-4.3.2.bz2 –C /usr/local/arm

# 或 将 arm-linux-gcc-4.3.2.bz2 拷贝到/usr/local/arm 直接解压
cd /usr/local/arm
sudo tar jxvf arm-linux-gcc-4.3.2.bz2

其中 4.3.2 需要假加入环境变量

sudo vim ~/.bashrc
# 在最后加入下面这行
export PATH=/usr/local/arm/4.3.2/bin:$PATH

# 使环境变量设置立即生效
source ~/.bashrc

配置编译 u-boot

# 在虚拟机中解压 u-boot-2014.07.tar.bz2 文件
tar jxv 7.tar.bz2

# 进入解压后的文件目录中
cd u-boot-2014.07

# 编译前清除旧的配置文件
make distclean

# 生成新的配置文件
make x6818_config

# 编译生成 u-boot 可执行文件
make
# 在当前目录下会生成一个 ubootpak.bin 的文件
# 将 tools 目录下生成的 mkimage 文件拷贝到/bin 目录中, 后面用于制作 u-boot 所需格式的内核
# 这一步也可以不做, 主要用于生成 uImage
cp u-boot-2014.07/tools/mkimage /bin 

配置编译内核

# 在虚拟机中解压 kernel6818_3.4.39.tar.gz 文件
tar zxvf kernel6818_3.4.39.tar.gz
# 进入解压后的文件目录
cd kernel-3.4.39
# 清除旧的内核配置文件
make distclean
# 重命名内核配置文件
cp sp_config_2017.03.20 .config
# 配置内核
make menuconfig
# 注意: 如果执行“make menuconfig” 提示找不到“ncurses 库”, 那么执行更新命令:
sudo apt-get install libncurses5-dev

# 如果无法连接到 172.20.220.71 , 将虚拟机设置成了静态 IP, 首先将 IP 设置为自动获取, 然后禁用一下虚拟机网络连接再启动即可
# 由于采用的是配置好的脚本, 因此对配置界面不用做任何修改, 直接保存退出即可。

image-20200702225217478

# 生成内核镜像文件
# 在 arch/arm/boot 下会生成 uImage 文件
make uImage
# 或 
make uImage -j2

# 进入内存镜像所在目录
cd arch/arm/boot

# 拷贝 temp.tar.bz2 到内存镜像所在目录 arch/arm/boot 并解压
tar jxvf temp.tar.bz2

# 进入解压出来的 temp 目录
cd temp

# 执行脚本生成 boot.img 下载镜像文件
./mkboot.sh

Ext4 根文件系统的制作

# Ext4 格式的根文件系统是可读可写的文件系统
# 解压根文件系统文件
tar jxvf gtk_rootfs.tar.bz2

# 给 mkyaffs2image 添加可执行权限
chmod +x mkfs_ext4/make_ext4fs

# 拷贝 mkyaffs2image 文件到/bin 下
sudo cp mkfs_ext4/make_ext4fs /bin

# 生成根文件系统镜像
make_ext4fs -s -l 314572800 -a root -L linux gtkfs.img gtk_rootfs

使用 USB 方式进行烧写

先拷贝以下路径的 fastboot 文件夹到本机:

执行步骤:

首先连接上 USB 线,串口线,电源线

然后给开发板上电,并进入 U-boot 命令行模式

在 U-boot 命令行中输入 fastboot 命令后点击回车键

image-20200702230812757

桌面上会自动弹出对话框,提示安装驱动

image-20200702230841005

选择从列表或指定位置安装,单击下一步

image-20200702230914053

选择在搜索中包括这个位置,单击浏览按钮,选择路径

image-20200702230929305

选择完后点击确定,然后点击下一步

image-20200702230939630

弹出此对话框时单击确定即可

image-20200702231019467

安装完成出现下面图片,点击完成即可完成驱动安装

image-20200702231032069

在 fastboot 文件夹中创建文件夹 all_image,把刚才制作的三个镜像文件拷贝到 all_image 中

image-20200702231041155

【注意】红色方框圈起来的 all_image 文件夹中的内容为:

image-20200702231056340

右键编辑上面文件夹中的"sp_linux_image_down.bat"(方框标识的批处理文件),将其内容由

fastboot flash ubootpak ../sp_linux_image/ubootpak.bin
fastboot flash boot ../sp_linux_image/boot.img
fastboot flash gtkfs ../sp_linux_image/gtkfs.img
fastboot flash userdata ../sp_linux_image/userdata.img
fastboot flash cache ../sp_linux_image/cache.img
fastboot flash recovery ../sp_linux_image/recovery.img 
# 改为:
fastboot flash ubootpak ./all_image/ubootpak.bin
fastboot flash boot ./all_image/boot.img
fastboot flash gtkfs ./all_image/gtkfs.img
# 其他内容使用 rem 注释掉即可
# 保存关闭

双击批处理文件"sp_linux_image_down.bat",这时会在我们的 u-boot 命令行中由串口打印出相关信息

镜像安装办法

Ubuntu 下制造 TF 卡

第一步: 准备一张不小于 4GB 的 TF 卡, 启动 ubuntu 虚拟机, 插入 TF 卡到 PC机, 然后点击下图中的红框按钮将 TF 卡连接到虚拟机。

image-20200702213453216
# 安装 gparted 命令
sudo apt-get install gparted

# gparted 工具删除里面所有分区
sudo gparted /dev/sdb

选中列表中的磁盘分区, 右键删除, 再点击菜单上的勾, 应用刚才的操作。

image-20200702213629470

第二步: 新建分区。 选中列表中未分配的磁盘, 再点击分区->新建, 在弹出的对话框中, 在文件系统中选择 fat32, 在之前的空余空间中输入 256, 预留256M 空间, 可用于后面烧写 uboot, 方便后续 TF 卡更新。

image-20200702213728493

第三步: 点击添加, 再点击菜单栏的勾, 稍等之后完成新的分区创建。

image-20200702213828315

第四步: 将 bootpak.bin 拷贝到 Ubuntu 虚拟机下, 执行如下命令制作 TF 卡:

sudo dd if=ubootpak.bin of=/dev/sdb1 bs=512 seek=1 conv=sync

硬件连接

将制作好的 TF 启动卡插入开发板的 TF 卡槽中。

X6818 开发板默认首先从 SD0 通道启动, 如果 SD0 卡槽放有能够启动 6818的 TF 卡, 则从 TF 卡启动, 否则从 EMMC 启动。 当我们将裸机程序烧写到 TF 卡后, 只需要插到 SD0 通道, 即 X6818 开发板右侧的 TF 卡槽, 开机即可运行 U-boot程序, 而不必理会开发板上是否已经烧有映像, 也无需进行任何的跳线设置。

将串口线的一端连接到开发板上, 另一端连接到 PC 机的 USB 口上, 同时准备好 OTG 下载线, 连接好硬件之后就可以进行下一步操作了。

建立超级终端

双击运行超级终端, 或者 Xshell5.exe 等等终端如下所示:

image-20200702214147377

点击新建, 然后设置串口相关信息, 将名称协议设置好, 协议设置为 SERIAL

image-20200702214210417

设置好之后, 点击左边类别拦中的连接选项, 点击 SERIAL, 设置端口号波特率

image-20200702214232263

确定之后点击连接, 进入终端界面

image-20200702214251421

进入 U-Boot, 烧写系统镜像文件(USB 线烧模式)

为系统上电后打印的一段信息。 红色框处是自动引导倒计时, 我们设置的默认时间是 3 秒钟, 在 3 秒后系统将会进入自动引导区。 在倒计时完成之前,我们按下 PC 机任意键就可以停止自动引导, 进入 U-Boot 界面。

image-20200702214416523

在 U-Boot 命令行模式下输入“fast” 命令, 并回车;

image-20200702214515757

将预先准备好的 OTG 下载线的一段连接到开发板上, 另一端连接到 PC 机的USB 口上;

找到资料 sp_linux_image_down.bat(注: 烧写 Linux 镜像时, 选择 fast.bat) , 双击运行批处理文件

image-20200702214815198

烧写过程中会提示一下打印信息, 说明正在正常烧写系统镜像文件

image-20200702214706701

可以观看试验仪的显示屏是会显示烧写进度, 等待系统镜像烧写完成。

烧录(Linux/Android) 双系统步骤:

需要制作 androidgnu/linux 双系统,但两者各需要三个分区和一些非必要分区,如果我们用原来的分区方法,可能会出现分区数不够 或 分区空间不够的情况,因此我们需要对设备存储空间进行统一的重新分区

A53 板子上电启动, 进入 U-boot 界面:

image-20200702215032310
 # 查看分区
 fdisk 2 

七个分区 :

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

# 查看 fastboot 支持的下载内容
fastboot  

要在里面加一个 gtkfs

image-20200702220217083

另外原来的 uboot 不支持我们要的 9 分区数,因此我们需要重新烧写新的 uboot。程序已经改好,我们只需下载新的 uboot 即可,因此,更新

修改下载脚本

fastboot flash ubootpak ./ubotpak.bin
REM fastboot flash boot ./boot.img
REM fastboot flash gtkfs ./gtkfs.img
REM fastboot flash cache ./image6818/android/cache.img
REM fastboot flash userdata ./image6818/android/userdata.img
REM fastboot flash system ./image6818/android/system.img

下载好新的 uboot 后重启设备。

烧写双系统的要求: 都是将文件烧写到 emmc 上, 烧写双系统得重新分区, 所以先分区, 用 fdisk 命令, 命令如下:

fdisk 2 8 100000:4000000 4100000:2f200000 33300000:1ac00000 4e000000:800000 4e900000:1600000 50000000:0xc800000 0x5c900000:0x1f400000 0x7be00000:0x0

image-20200702215107637

 # 查看分区
fdisk 2

9 个分区了,足够双系统使用

image-20200702220917460

fastboot

后仍然看不到 gtkfs 项目,因为这些内容并不会随 uboot 而改变,我们需要手动清除,这样 uboot 才会对其进行重新设置。

# 擦写分区
mmc erase 0x400 0x40
image-20200702215136035

之后重启一下。

fastboot

出现了 gtkfs 选项,表示我们能够把这个镜像下载到新的分区中了

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上述步骤完成之后, 重启板子, 再次进入 U-boot 界面, 用 fast 命令(USB 线烧模式)进行烧写。

由于我们进行了重新分区,因此除了 uboot 以外的内容很可能都不能使用了,因此我们先烧写内核,也就是 boot.img 镜像。

下载脚本修改 :

REM fastboot flash ubootpak ./ubotpak.bin
fastboot flash boot ./boot.img
REM fastboot flash gtkfs ./gtkfs.img
REM fastboot flash cache ./image6818/android/cache.img
REM fastboot flash userdata ./image6818/android/userdata.img
REM fastboot flash system ./image6818/android/system.img

再下载 gtkfs.img 文件系统镜像即可

下载脚本修改成:

REM fastboot flash ubootpak ./ubotpak.bin
REM fastboot flash boot ./boot.img
fastboot flash gtkfs ./gtkfs.img
REM fastboot flash cache ./image6818/android/cache.img
REM fastboot flash userdata ./image6818/android/userdata.img
REM fastboot flash system ./image6818/android/system.img

下载后重启,成功

现在具有下载并启动双系统的能力。

配置 U-Boot 环境变量

待系统镜像烧写完成后, 先拔去 OTG 下载线, 在给试验仪重新上电, 并进入U-Boot 界面;

如果板子烧写的是( Linux/Android) 双系统, 默认启动的是 Linux 系统, 板子上电之后不用任何操作, 启动起来就是 Linux 系统。 如果要启动 Android 系统,板子上电时按住下方按键从左往右的第四个按键, 就是启动 Android 系统, 在U-boot 界面显示如下:

image-20200702215353795
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

onnxrun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值