Linux-ARM裸机开发(三)-LED驱动实验(GPIO配置,汇编+C语言)

Linux-ARM裸机开发(三)-汇编LED驱动实验


一、LED驱动原理分析

1.1 为什么要学习Cortex-A汇编

①需要用汇编初始化一些SOC外设,片上系统(SoC,System on Chip)。
②使用汇编初始化DDR(DDR SDRAM),I.MX6ULL此款芯片不用使用汇编初始化DDR。因为NXP在I.MX6ULL 内部96KB的ROM中存放了自己编写的启动代码,这些启动代码可以读取DDR配置信息,并且完成DDR的初始化。
③设置sp指针,一般指向DDR(Cortex-A系列的内部RAM比较小,一般sp指针不会指向内部RAM),设置好C语言运行环境(C语言运行环境就是指设置sp指针,因为C语言运行环境中需要保护现场—入栈和出栈,而入栈和出栈就要用到sp指针)。

1.2 LED对应的引脚图

以下为LED对应的引脚图,图中LED0与GPIO3相连:
在这里插入图片描述
在这里插入图片描述

1.3 STM32 IO初始化流程

①、使能GPIO时钟。
②、设置IO复用,将其复用为GPIO。
③、配置GPIO的电气属性(上下拉,速度等)。
④、使用GPIO,输出高/低电平。

1.4 I.MX6ULL IO初始化流程(原理分析-重点)

①、使能时钟,CCGR0-CCGR6这7个寄存器控制着6ULL所有外设时钟的使能。(参考手册Chapter18)

每个模块由两个位表示,如下图所示:在这里插入图片描述
下图CCGR0的30-31位控制着gpio2的时钟使能,28-29位控制着uart2的时钟使能。
在这里插入图片描述
将两个位置设为:
00:所有模式下时钟都关闭 01:只有运行模式下,时钟使能
10:还未使用(保留) 11:所有模式下时钟全部开启
在这里插入图片描述
为了简单,设置CCGR0~CCGR6这7个寄存器全部为0XFFFFFFFF,相当于使能所有外设时钟。

②、IO复用,配置MUX寄存器(参考手册Chapter32)

如下图所示,bit31-5位全部保留,只有bit4-0这五个位用到。
在这里插入图片描述
bit4=1:强制输入路径;bit4=0:路径由功能决定
bit3-0:设置IO复用模式。0000:IIC模式,0101为GPIO1_IO03复用为gpio功能。其他的如下图:
在这里插入图片描述

将寄存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03的bit3~0设置为0101=5,这样GPIO1_IO03就复用为GPIO。

③、设置电气属性,配置PAD寄存器,包括压摆率、速度、驱动能力、开漏、上下拉等。(参考手册Chapter32)

需要配置HYS、PUS、PUE,PKE,ODE,SPEED,DSE,SRE块。
在这里插入图片描述
HYS(bit6):用来使能迟滞比较器,当 IO 作为输入功能的时候有效,用于设置输入接收器的施密特触发器是否使能。如果需要对输入波形进行整形的话可以使能此位。此位为 0 的时候禁止迟滞比较器,为 1 的时候使能迟滞比较器。
在这里插入图片描述
PUS(bit15:14): 用来设置上下拉电阻的,一共有四种选项可以选择:
00:100K下拉、01:47K上拉、10:100K上拉、11:22K上拉

在这里插入图片描述
PUE(bit13):当 IO 作为输入的时候,这个位用来设置 IO 使用上下拉还是状态保持器。当为 0 的时候使用状态保持器,当为 1 的时候使用上下拉。状态保持器在IO 作为输入的时候才有用,就是当外部电路断电以后此 IO 口可以保持住以前的状态。
在这里插入图片描述
PKE(bit12):此位用来使能或者禁止上下拉/状态保持器功能,为0 时禁止上下拉/状态保持器,为 1 时使能上下拉和状态保持器。

在这里插入图片描述
ODE(bit11):当 IO 作为输出的时候,此位用来禁止或者使能开路输出,此位为 0 的时候禁止开路输出,当此位为 1 的时候就使能开路输出功能。
在这里插入图片描述
SPEED(bit7:6):当 IO 用作输出的时候,此位用来设置 IO 速度,具体输出速度设置如下:
在这里插入图片描述
DSE(bit5:3):当 IO 用作输出的时候用来设置 IO 的驱动能力,如下图所示,电阻值由上至下减少,输出驱动能力上升:
在这里插入图片描述
SRE(bit0):设置压摆率,当此位为 0 的时候是低压摆率,当为 1的时候是高压摆率。这里压摆率就是 IO 电平跳变所需要的时间,比如从 0 到 1 需要多少时间,时间越小波形就越陡,说明压摆率越高;时间越长波形就越缓,压摆率就越低。如果你的产品要过 EMC 的话那就可以使用小的压摆率,因为波形缓和,如果使用 IO做高速通信的话就可以使用高压摆率。
在这里插入图片描述
IO功能图如下(我看不太懂):
在这里插入图片描述
寄存器IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03是设置GPIO1_IO03的电气属性。包括压摆率、速度、驱动能力、开漏、上下拉等。

④、配置GPIO功能,设置输入输出。(参考手册Chapter28)

**注意:**IOMUXC_SW_MUX_CTL_PAD_XX_XX和IOMUXC_SW_PAD_CTL_PAD_XX_XX 这两种寄存器都是配置 IO 的,注意是 IO!不是 GPIO,GPIO 是一个 IO 众多复用功能中的一种。比如 GPIO1_IO00 这个 IO 可以复为:I2C2_SCL、GPT1_CAPTURE1、ANATOP_OTG1_ID、ENET1_REF_CLK 、 MQS_RIGHT 、 GPIO1_IO00 、 ENET1_1588_EVENT0_IN 、SRC_SYSTEM_RESET 和 WDOG3_WDOG_B 这 9 个功能,GPIO1_IO00 是其中的一种,我们想要把 GPIO1_IO00 用作哪个外设就复用为哪个外设功能即可。

GPIO 结构如下图所示:
在这里插入图片描述
图中左下角的SW_MUX_CTL_PAD和SW_PAD_CTL_PAD这两种寄存器前面说了用来设置 IO 的复用功能和 IO 属性配置。左上角部分的 GPIO 框图就是,当 IO 用作 GPIO 的时候需要设置的寄存器,一共有八个:DR、GDIR、PSR、ICR1、ICR2、EDGE_SEL、IMR 和 ISR。 I.MX6U 一共有GPIO1~GPIO5 共五组 GPIO,每组 GPIO 都有这 8 个寄存器。
下面将介绍一下这8个寄存器:
1.DR寄存器,此寄存器为数据寄存器,其结构图如下:
在这里插入图片描述
该寄存器为32位,一个GPIO组最大只有32个IO,故DR寄存器每个位对应一个GPIO。此寄存器功能为:当GPIO被配置为输出模式后,若要GPIOx_IOx输出高电平,则设置GPIOx.DR对应的bitx=1,反之,若要GPIOx_IOx输出低电平,则设置GPIOx.DR对应的bitx=0;当GPIO被配置为输入模式后,该寄存器用于保存对应的IO的电平,如GPIOx-IOx引脚接地,则GPIOx.DR的bit0位为0。
2.GDIR 寄存器,此寄存器为方向寄存器,其结构图如下:
在这里插入图片描述
GDIR寄存器也是32位,该寄存器主要是设置IO的工作方向,输入或者输出。若要设置GPIOx_IOx为输入时,则设置GPIOx.GDIR对应的bitx=0;若要设置GPIOx_IOx为输出时,则设置GPIOx.GDIR对应的bitx=1;
3.PSR 寄存器, 此寄存器为GPIO 状态寄存器,其结构图如下:
在这里插入图片描述同样的 PSR 寄存器也是一个 GPIO 对应一个位,读取相应的位即可获取对应的 GPIO 的状态,也就是 GPIO 的高低电平值。功能和输入状态下的 DR 寄存器一样。
4.ICR1和ICR2寄存器,两个寄存器都是中断控制寄存器,ICR1结构图如下:
在这里插入图片描述
ICR1 用于 IO0-15 的配置, ICR2 用于 IO16-31 的配置。ICR1 寄存器中一个 GPIO 用两个
位,这两个位用来配置中断的触发方式,配置表如下图所示:
在这里插入图片描述
5.IMR 寄存器,这是中断屏蔽寄存器,结构图如下:
在这里插入图片描述
IMR 寄存器也是一个 GPIO 对应一个位,IMR 寄存器用来控制 GPIO 的中断禁止和使能,如果某个 GPIO 使能中断,那么设置相应的位为 1 即可,反之,如果要禁止中断,那么就设置相应的位为 0 即可。
6.ISR寄存器 ,是中断状态寄存器,结构图如下:
在这里插入图片描述
ISR 寄存器也是 32 位寄存器,一个 GPIO 对应一个位,只要某个 GPIO 的中断发生,那么ISR 中相应的位就会被置1。所以,我们可以通过读取 ISR 寄存器来判断 GPIO 中断是否发生,相当于 ISR 中的这些位就是中断标志位。当我们处理完中断以后,必须清除中断标志位,清除方法就是向 ISR 中相应的位写1,也就是写1清零。
7.EDGE_SEL 寄存器,这是边沿选择寄存器,结构图如下:
在这里插入图片描述
EDGE_SEL 寄存器用来设置边沿中断,这个寄存器会覆盖 ICR1 和 ICR2 的设置,同样是一个GPIO 对应一个位。如果相应的位被置1,那么就相当与设置了对应的 GPIO 是上升沿和下降沿(双边沿)触发。也就是说如果EDGE_SEL对应IO的bit=1时,无论上面的ICR寄存器设置为什么触发,该IO都被设为双边沿触发;若为0时,就是ICR寄存器设置为什么中断触发,则为什么触发。

二、汇编LED驱动实验

2.1 驱动编写

①使能外设时钟

.global _start @全局标号

_start:
/*使能所有时钟 */
    ldr r0, =0x020c4068 @CCGR0寄存器地址
    ldr r1, =0xffffffff @要向CCGR0寄存器中写入的数据
    str r1, [r0]        @向CCGR0的地址写入0xfffffffff

    ldr r0, =0x020c406c @CCGR1寄存器地址
    ldr r1, =0xffffffff 
    str r1, [r0]    

    ldr r0, =0x020c4070 @CCGR2寄存器地址
    ldr r1, =0xffffffff
    str r1, [r0]   

    ldr r0, =0x020c4074 @CCGR3寄存器地址
    ldr r1, =0xffffffff 
    str r1, [r0]      

    ldr r0, =0x020c4078 @CCGR4寄存器地址
    ldr r1, =0xffffffff 
    str r1, [r0]    

    ldr r0, =0x020c407c @CCGR5寄存器地址
    ldr r1, =0xffffffff 
    str r1, [r0]  

    ldr r0, =0x020c4080 @CCGR6寄存器地址
    ldr r1, =0xffffffff 
    str r1, [r0]     

②设置IO复用模式,配置MUX寄存器

    ldr r0, =0x020e0068
    ldr r1, =0x05
    str r1, [r0]

③设置电气属性,配置PAD寄存器

/* bit0:   0   低速
 * bit5:3  110 R0/6驱动能力
 * bit7:6  10  100MHZ速度
 * bit11   0   禁止开路输出
 * bit12   1
 * bit13   0
 *bit15:14 00 100k下拉
 * bit16   0  关闭hys
 */
    ldr r0, =0x020e02f4
    ldr r1, =0x10b0
    str r1, [r0]

④ 配置GPIO为输出模式:GDIR寄存器配置bit3=1,开灯:DR寄存器配置bit3=0

    ldr r0, =0x209c004
    ldr r1, =0x08
    str r1, [r0]

    ldr r0, =0x209c000
    ldr r1, =0x00
    str r1, [r0]   

⑤ 最后加个死循环
loop:
b loop

2.2 编译程序

①、使用arm-linux-gnueabihf-gcc,将.c .s文件变为.o

arm-linux-gnueabihf-gcc -g -c leds.s -o led.o

②、将所有的.o文件链接为elf格式的可执行文件。

链接就是将所有.o文件链接在一起,并且链接到指定的地方。本实验链接的时候要指定链接起始地址。链接起始地址就是代码运行的起始地址。
对于6ULL来说,链接起始地址应该指向RAM地址。RAM分为内部RAM和外部RAM,也就是 DDR。6ULL内部RAM地址范围0X900000-0X91FFFF。也可以放到外部DDR中,对于I.MX6U-ALPHA开发板,512MB字节DDR版本的核心板,DDR范围就是0X80000000-0X9FFFFFFF。对于256MB的DDR来说,那就是0X80000000-0X8FFFFFFF。
本系列视频,裸机代码的链接起始地址为0X87800000。要使用DDR,那么必须要初始化DDR,对于I.MX来说bin文件不能直接运行,需要添加一个头部,这个头部信息包含了DDR的初始化参数,I.MX系列SOC内部boot rom会从SD卡,EMMC等外置存储中读取头部信息,然后初始化DDR,并且将bin文件拷贝到指定的地方。
Bin的运行地址一定要和链接起始地址一致。位置无关代码除外。
确定了链接地址以后就可以使用 arm-linux-gnueabihf-ld 来将前面编译出来的 led.o 文件链接到 0X87800000 这个地址,使用如下命令:

arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf

上述命令中-Ttext 就是指定链接地址,“-o”选项指定链接生成的 elf 文件名,这里我们命名为 led.elf。

③、将elf文件转为bin文件。

led.elf 文件也不是我们最终烧写到 SD 卡中的可执行文件,我们要烧写的.bin 文件,因此还需要将 led.elf 文件转换为.bin 文件。我们需要用arm-linux-gnueabihf-objcopy将 led.elf 文件转换为led.bin 文件,命令如下:

arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin

上述命令中,“-O”选项指定以什么格式输出,后面的“binary”表示以二进制格式输出,选项“-S”表示不要复制源文件中的重定位信息和符号信息,“-g”表示不复制源文件中的调试信息。

④、将elf文件转为汇编,反汇编。

大多数情况下我们都是用 C 语言写试验例程的,有时候需要查看其汇编代码来调试代码,因此就需要进行反汇编,一般可以将 elf 文件反汇编,比如如下命令:

arm-linux-gnueabihf-objdump -D led.elf > led.dis

上述代码中的“-D”选项表示反汇编所有的段,反汇编完成以后就会在当前目录下出现一个名为 led.dis 文件。

2.3 烧写代码

STM32烧写到内部FLASH。
6ULL支持SD卡、EMMC、NAND、nor、SPI flash等等启动。裸机例程选择烧写到SD卡里面。
在ubuntu下向SD卡烧写裸机bin文件。烧写不是将bin文件拷贝到SD卡中,而是将bin文件烧写到SD卡绝对地址上。而且对于I.MX而言,不能直接烧写bin文件,比如先在bin文件前面添加头部。完成这个工作,需要使用正点原子提供的imxdownload软件

① 给予 imxdownload 可执行权限

chmod 777 imxdownload

向 SD 卡烧写 bin 文件

./imxdownload led.bin /dev/sdb

Imxdownlaod会向led.bin添加一个头部,生成新的load.imx文件,这个load.imx文件就是最终烧写到SD卡里面去的。

三、汇编LED驱动实验

3.1 设置处理器模式

设置6ULL处于SVC模式(超级管理员模式,特权模式,供操作系统使用),将CPSR寄存器的bit4~0,也就是M[4:0],设置为10011=0x13。读写状态寄存器需要MRS和MSR指令。
MRS指令将CPSR寄存器数据读出到通用寄存器中,MSR指令将通用寄存器的值写入到CPSR寄存器当中。
在这里插入图片描述

3.2 设置SP指针

SP指针可以指向内部RAM,也可以指向DDR,我们将其指向到DDR上。512M的DDR范围0x80000000-0x9FFFFFFFF。栈大小,0x200000=2MB,处理器栈增长方式,A7是向下增长的。设置SP指向0x80200000。

3.3 跳转到C语言

使用b指令,跳转到C语言,比如main函数。

3.4 代码

start.s

.global _start

_start:
    /* 设置处理器进入SVC模式 */
    mrs r0, cpsr        /* 读取cpsr的值到r0 */
    bic r0, r0, #0x1f   /* 将cpsr寄存器bit0:5清零 */
    orr r0, r0, #0x13   /* 设置为SVC模式 */
    msr cpsr, r0        /* 将r0写入到cpsr */

    /* 设置SP指针 */
    ldr sp, =0x80200000
    b main
    

main.h

#ifndef __MAIN_H
#define __MAIN_H

/* 定义要使用的寄存器 */
#define CCM_CCGR0           *((volatile unsigned int *)0X020C4068)
#define CCM_CCGR1           *((volatile unsigned int *)0X020C406C)
#define CCM_CCGR2           *((volatile unsigned int *)0X020C4070)
#define CCM_CCGR3           *((volatile unsigned int *)0X020C4074)
#define CCM_CCGR4           *((volatile unsigned int *)0X020C4078)
#define CCM_CCGR5           *((volatile unsigned int *)0X020C407C)
#define CCM_CCGR6           *((volatile unsigned int *)0X020C4080)

#define SW_MUX_GPIO1_IO03   *((volatile unsigned int *)0X020E0068)
#define SW_PAD_GPIO1_IO03   *((volatile unsigned int *)0X020E02F4)

#define GPIO1_DR            *((volatile unsigned int *)0X0209C000)
#define GPIO1_GDIR          *((volatile unsigned int *)0X0209C004)
#define GPIO1_PSR           *((volatile unsigned int *)0X0209C008)
#define GPIO1_ICR1          *((volatile unsigned int *)0X0209C00C)
#define GPIO1_ICR2          *((volatile unsigned int *)0X0209C010)
#define GPIO1_IMR           *((volatile unsigned int *)0X0209C014)
#define GPIO1_ISR           *((volatile unsigned int *)0X0209C018)
#define GPIO1_EDGE_SEL      *((volatile unsigned int *)0X0209C01C)

#endif

main.c

#include "main.h"

/* 使能外设时钟 */
void clk_enable(void)
{
    CCM_CCGR0 = 0xFFFFFFFF;
    CCM_CCGR1 = 0xFFFFFFFF;
    CCM_CCGR2 = 0xFFFFFFFF;
    CCM_CCGR3 = 0xFFFFFFFF;
    CCM_CCGR4 = 0xFFFFFFFF;
    CCM_CCGR5 = 0xFFFFFFFF;
    CCM_CCGR6 = 0xFFFFFFFF;
}

void led_init(void)
{
    SW_MUX_GPIO1_IO03 = 0x05;    /*复用为GPIO1_IO3*/
    SW_PAD_GPIO1_IO03 = 0x10b0;  /*设置电气属性*/

    GPIO1_GDIR = 0x08;
    GPIO1_DR = 0X00;
}
/*短延时*/
void delay_short(volatile unsigned int n)
{
    while (n--){}
}

/*1ms延时 主频396MHz时*/
void delay(volatile unsigned int n)
{
    while (n--)
    {
        delay_short(0x7ff);
    }  
}

void led_on(void)
{
    GPIO1_DR &= ~(1<<3);
}

void led_off(void)
{
    GPIO1_DR |= (1<<3);
}

int main(void)
{
    /*使能外设时钟*/
    clk_enable();
    /* 初始化LED */
    led_init();
    /* 设置LED闪烁 */
    while (1)
    {
        led_on();
        delay(500);
        led_off();
        delay(500);
    }
    return 0;
}

Makefile

objs = start.o main.o

ledc.bin : $(objs)
	arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf $^
	arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
	arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis

%.o : %.c
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<

%.o : %.s
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<

clean:
	rm -rf *.o ledc.bin ledc.elf ledc.dis

烧写代码同上面的汇编

3.5 链接脚本

SECTIONS{
    . = 0X87800000;
    .text :
    {
        start.o 
        *(.text)
    }
    .rodata ALIGN(4) : {*(.rodata*)} 
    .data ALIGN(4) : { *(.data) } 
    __bss_start = .; 
    .bss ALIGN(4) : { *(.bss) *(COMMON) } 
    __bss_end = .;
}

Makefile修改为如下

objs = start.o main.o

ledc.bin : $(objs)
	arm-linux-gnueabihf-ld -Timx6u.ids $^ -o ledc.elf
	arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
	arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis

%.o : %.c
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<

%.o : %.s
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<

clean:
	rm -rf *.o ledc.bin ledc.elf ledc.dis
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在SoC开发板上,可以使用汇编语言和C语言混合编写应用程序。这种混合编程的方式可以充分发挥汇编语言的高效性能和C语言的可读性和易用性。以下是一个基于Cortex-A9的LED控制应用程序的示例代码,其中包含了汇编语言和C语言代码: 汇编语言代码: ``` .global _start _start: /* 初始化GPIO控制器 */ ldr r0, =0x48002000 /* GPIO控制器基地址 */ ldr r1, =0x00000C00 /* 将GPIO1设为输出 */ str r1, [r0, #0x400] /* 设置GPIO1为输出模式 */ loop: /* 点亮LED */ ldr r1, =0x00000200 /* 将GPIO1输出设置为高电平 */ str r1, [r0, #0x190] /* 设置GPIO1输出为高电平 */ /* 延时 */ mov r2, #0 delay: add r2, r2, #1 cmp r2, #1000000 bne delay /* 熄灭LED */ ldr r1, =0x00000000 /* 将GPIO1输出设置为低电平 */ str r1, [r0, #0x190] /* 设置GPIO1输出为低电平 */ /* 延时 */ mov r2, #0 delay2: add r2, r2, #1 cmp r2, #1000000 bne delay2 /* 重复循环 */ b loop ``` C语言代码: ``` #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/mman.h> #define GPIO_BASE 0x48002000 #define GPIO_OE 0x134 /* GPIO方向控制寄存器 */ #define GPIO_OUTPUT 0x194 /* GPIO输出寄存器 */ int main(int argc, char *argv[]) { volatile unsigned int *gpio; int fd; /* 打开/dev/mem文件 */ fd = open("/dev/mem", O_RDWR | O_SYNC); if (fd == -1) { perror("open"); exit(EXIT_FAILURE); } /* 映射GPIO控制器到进程内存空间 */ gpio = (unsigned int *) mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO_BASE); if (gpio == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); } /* 将GPIO1设为输出 */ *(gpio + (GPIO_OE / 4)) &= ~0x200; /* 循环控制LED灯亮灭 */ while (1) { /* 点亮LED */ *(gpio + (GPIO_OUTPUT / 4)) |= 0x200; /* 延时 */ volatile int i; for (i = 0; i < 1000000; i++) {} /* 熄灭LED */ *(gpio + (GPIO_OUTPUT / 4)) &= ~0x200; /* 延时 */ for (i = 0; i < 1000000; i++) {} } return 0; } ``` 这个例子中,汇编语言代码用来初始化GPIO控制器和控制LED亮灭,而C语言代码则用来完成/dev/mem文件的打开和GPIO控制器的映射,以及LED灯控制循环。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

HQAQ

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

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

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

打赏作者

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

抵扣说明:

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

余额充值