我的第一个ARM汇编程序(霓虹灯~~~)

开始基于S3C2440学习ARM裸机,记录一下第一个ARM汇编程序(比起8086的指令,ARM的指令要难一些,当然ARM指令比起IA32也是稍有逊色,毕竟越是技术更新换代,所需功能越多,复杂度也就越大)

1、纯汇编基础版:

@
@ Time:2019年4月1日23:15:07 
@ Author:Apollon_krj
@ 所有的三个LED点灯(GPF4~GPF6):1为灭,0为亮
@ 流水灯
@ 
@ arm的函数调用与返回,与8086差别还是蛮大的
@ (主要是当前不知道有什么比较类似于x86的call、ret指令,以下指令也是为了一个目的而去搜的)
@ 后者用stack存放函数地址,前者则直接是lr和pc两个寄存器来搞
@ 8086更趋向与软件逻辑,是两步:先call,再ret
@ 而arm则更随心所欲一些,是三步:先保存返回地址,再修改pc指针,执行完恢复pc指针
@ 即call指令相当于“LDR lr, =label_ret”、“LDR lr,=label_call”两条指令
@ 而ret指令相当于“MOV pc,lr”一条指令,也即8086“MOV pc,ss:[sp]”
@ 注意:ARM中,pc寄存器的值,永远是当前执行指令地址+8,而不是当前指令地址
@ 因为:当前执行指令地址为A,A+4指令已经开始被解析,而A+8指令已经开始被读取(即PC)
@

.text
.global _start

_start:
	LDR r0, =0x00				@ 0000 0000,亮亮亮 
	LDR lr, =one_light_delay	@ 点灯完成后的地址,即返回地址
	LDR pc, =_led_light			@ 点灯指令起始地址,等价于 B _led_light
one_light_delay:
	LDR lr, =two_light			@ 延时完成后的指令地址
	B _delay					@ 延时指令起始地址
	
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@	
two_light:
	LDR r0,	=0x70				@ 0111 0000,灭灭灭
	LDR lr, =two_light_delay
	B _led_light
two_light_delay:
	LDR lr, =_start				@ 死循环
	B _delay
	
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
_led_light:
	LDR r1, =0x56000050
	LDR r2, =0x1500				@ choose GPF:0001 0101 0000 0000,选中三个GPIO的输入功能
	LDR r3, [r1]				@ 获取原来的设置
	ORR r2, r3					@ 将新配置(GPF4~GPF6 output)增加上去
	STR r2, [r1]

	LDR r1, =0x56000054
	MOV r2, r0					@ set bit:r0
	STR r2, [r1]
	MOV pc, lr					@ 回复函数返回地址,接着执行
	
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
_delay:
	LDR r3, =0x100				@ 延时256 * 256 = 65536个指令周期
loop_ext:
	LDR r4, =0x100
loop_int:
	sub r4, r4, #1
	cmp r4, #0
	BNE loop_int				@ 内层循环条件判断
	
	sub r3, r3, #1
	cmp r3, #0
	BNE loop_ext				@ 外层循环条件判断
	MOV pc, lr					@ 循环结束,返回执行
	

查看一下elf文件部分内容和bin文件的机器码:
在这里插入图片描述
可以看出,elf文件的组织形式,有一部分连续的地址存放代码,文件末尾前一部分地址存放符号表,虽然没有看多ELF文件的具体组织形式,但是也和PE文件格式差不了太多。
在这里插入图片描述

2、带按键调时Asm/C混合版:

2.1、用到的寄存器描述:

①用到了五个寄存器,GPFCON、GPFDAT、GPGCON、GPGDAT、WTDOG寄存器(用来控制输出点亮LED、按键输入控制延时(非中断版))
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
②GPFDAT、GPGDAT数据寄存器,用来存放pins的输入/输出的数据。
在这里插入图片描述
在这里插入图片描述
③看门狗的控制寄存器:
由于看门狗会在一段时间后复位,使得程序重新运行(流水灯看起来效果可能不连贯,如果延时恰好和复位时间一样,看起来可能没有什么不一样),所以因此需要关闭看门狗的复位功能,或者disable看门狗的定时器。其他bit不再生效。
在这里插入图片描述
注:S3C2440看门狗定时器的计算方法为:

t_watchdog = 1/[ PCLK / (Prescaler value + 1) / Division_factor ]

2.2、原理图:

在这里插入图片描述

2.3、工程目录结构和Makefile:

①目录结构:

Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/03_LED_Nor_Nand_WatchDogTimer_Key$ tree -a
.
├── all.bin
├── all.dis
├── all.elf
├── include
│   └── soc_s3c2440
│       ├── soc_s3c2440_field.h
│       ├── soc_s3c2440_reg.h
│       └── soc_s3c2440_reg_operator.h
├── led.c
├── .led.d
├── led.i
├── led.o
├── Makefile
├── start.o
└── start.S

②Makefile:

INCLUDE := -I ./include/soc_s3c2440/
CFLAGS := $(INCLUDE) 

OBJS = start.o led.o
C_SOURCE = led.c
DEP_FILES := $(patsubst %.c,.%.d,$(C_SOURCE))
DEP_FILES := $(wildcard $(DEP_FILES))

#注意,非命令语句不要Tab缩进
ifneq ($(DEP_FILES),)
include $(DEP_FILES)
endif

all:$(OBJS)
	#链接两个目标文件到一个.elf文件
	#特别注意两个.o文件的顺序,当我把main生成的.o放在前面时其结果是不对的!
	@arm-linux-ld -Ttext 0 $^ -o $@.elf
	#由目标代码生成机器码的bin文件
	@arm-linux-objcopy -O binary -S $@.elf $@.bin
	#由目标代码反汇编得出标准汇编指令(非伪汇编指令)
	@arm-linux-objdump -D $@.elf > $@.dis
	
start.o:start.s
	@arm-linux-gcc -c -o $@ $<	
	
%.o:%.c
	#记录一下预处理结果中对宏的处理,便于观察
	@arm-linux-gcc -E $< $(CFLAGS) -o $(patsubst %.c,%.i,$<)
	#分别编译,生成目标文件,依赖文件存为隐藏文件
	@arm-linux-gcc -c $< $(CFLAGS) -o $@ -MD -MF $(patsubst %.c,.%.d,$<)


.PHONY:clean
clean:
	@rm *.bin *.elf *.o *.dis *.i
	
.PHONY:clean_dep
clean_dep:
	@rm -f $(DEP_FILES) && echo "delete depend file success!" || echo "delete depend files failure!"

2.4、关键部分代码记录:

①汇编程序选择关闭看门狗复位功能和选择Nor启动/Nand启动并设置栈底到高内存地址,设置函数入口地址等:

@
@ Time:2019年4月5日09:40:31
@ Author:Kangruojin
@
.text
.global _start

_start:
	@disable watchDog Timer reset function
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]

	@auto choose start-up by Nor flash or Nand flash use Software
	@According to the position of hardware switch choose to change
	mov r1, #0
	ldr r0, [r1]				@Backup to save old values
	str r1, [r1]				@write 0 to addr 0
	ldr r2, [r1]				@read 0 from addr 0
	cmp r1, r2					@compare r1 with r2(Values at different times of addr 0)
	ldr sp, =0x40000000+4096	@set stack bottom for start-up by Nor flash
	moveq sp, #4096				@if is starp-up by Nand,change stack bottom is 4096
	streq r0, [r1]				@recover values before address 0 of backup

	@led light for C language
	bl led_light
	
_halt:
	b _halt

②功能函数(C部分):

/*
 * Description:
 *     三个按键控制流水灯的延时时间,有增加、减少、回复默认时间三种操作
 *     start.s中存在关闭看门狗和自动选择Nand启动/Nor启动并设置栈的操作
 * Time:2019年4月5日00:18:02
 * Author:Kangruojin
 * Version:1.2
*/

#include <soc_s3c2440_reg_operator.h>

void delay(volatile int time)
{
	while(time--);
}

void init_config()
{
	/* 清空GPF0、GPF2、GPF4~GPF6、GPG3的控制信息,同时设置了GPF0、GPF2、GPG3为输入引脚 */
	GPFCONr &= ~(GPFCON_GPF0_CONF_BITSf \
				| GPFCON_GPF2_CONF_BITSf \
				| GPFCON_GPF4_CONF_BITSf \
				| GPFCON_GPF5_CONF_BITSf \
				| GPFCON_GPF6_CONF_BITSf);
	GPGCONr &= ~(GPGCON_GPG3_CONF_BITSf);

	/* 配置GPF0、GPF2为输入引脚,GPG3为输入引脚,00;配置GPF4~GPF6为输出引脚 */
	GPFCONr |= ((GPFCON_OUTPUT_BITS << 8) \
				| (GPFCON_OUTPUT_BITS << 10) \
				| (GPFCON_OUTPUT_BITS << 12));
	/* 配置GPF0、GPF2、GPG3为输入引脚,上面清空过后,相当于已经配置为输入了,不需要再配一次 */
}

void led_running()
{
	#define DEFAULT_DELAY_TIME		(25000)
	#define DEFAULT_INC_DEALY_TIME	(2000)
	#define DEFAULT_ADD_DEALY_TIME	(2000)
	#define DELAY_TIME_MIN			(5000)
	#define DELAY_TIME_MAX			(50000)
	/*
	 * 注意,此处的临时变量是实时更新的,所以防止CPU做优化,加上volatile,防止被不期望的优化
	 * CPU可能最开始还在取寄存器的值,到后来取了几次发现没变化就不取了,所以被坑了很久
	 */
	volatile unsigned int gpfTemp=0;
	volatile unsigned int gpgTemp=0;

	int delay_time = DEFAULT_DELAY_TIME; 
	unsigned int i = 0;
	unsigned int len = 0;
	const unsigned char led_info[] = {
		0x00, 0x40, 0x60, 0x70, 0x30, 0x10, 0x00, 0x70,
		0x00, 0x70, 0x00, 0x70, 0x00, 0x10, 0x30, 0x70, 
		0x60, 0x40, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70
	};
	len = sizeof(led_info) / sizeof(led_info[0]);

	/* 设置led为流水灯点亮 */
	while(1)
	{
		for(i = 0; i < len; i++)
		{
			gpfTemp = GPFDATr;
			gpgTemp = GPGDATr;

			/* 默认是高电平,按下按键为低电平 */
			if(_TEST_BIT_IS_ZERO(gpfTemp, 0)){
				if(delay_time < DELAY_TIME_MAX){			/* 上限 */
					delay_time += DEFAULT_ADD_DEALY_TIME;	/* 按键EINT0为增加延时 */
				}
			}
			if(_TEST_BIT_IS_ZERO(gpfTemp, 2)){
				if(delay_time > DELAY_TIME_MIN){			/* 下限 */
					delay_time -= DEFAULT_INC_DEALY_TIME;	/* 按键EINT2为减少延时 */
				}
			}
			if(_TEST_BIT_IS_ZERO(gpgTemp, 3)){
				delay_time = DEFAULT_DELAY_TIME;			/* 按键EINT11为回复默认延时 */
			}

			/* 
			 * 要点:不要用或运算直接给这3bit赋值,否则经过几次,这3bit就全被置为1了,不会再改变
			 * 非要或运算的话,每次需要清空,接着立即去或运算, 	 但是灯的切换过程,总会有点闪烁不定
			 * 所以,直接去获取新的状态,然后根据获取的每一位状态设置新的每一位状态即可。
			 */
			__SET_BIT(GPFDATr, 4, __GET_BIT(led_info[i], 4));
			__SET_BIT(GPFDATr, 5, __GET_BIT(led_info[i], 5));
			__SET_BIT(GPFDATr, 6, __GET_BIT(led_info[i], 6));

			delay(delay_time);
		}
	}
}
void led_light()
{
	init_config();
	led_running();
}


③部分寄存器宏定义与字段宏定义:

/* 寄存器定义头文件:soc_s3c2440_reg.h */
#ifndef _SOC_S3C2440_REG_H_
#define _SOC_S3C2440_REG_H_

#define __REG_VALUE(addr) (*((volatile unsigned int *)(addr)))

/* The suffix 'r' means that the macro is a register. */
#define WTCONr	__REG_VALUE(0x53000000)		/* watch dog register */

#define GPFCONr	__REG_VALUE(0x56000050)		/* GPF config register */
#define GPFDATr	__REG_VALUE(0x56000054)		/* GPF data register */
#define GPFUPr	__REG_VALUE(0x56000058)		/* GPF pins Pull-up constrol register,0 is ebable */

#define GPGCONr	__REG_VALUE(0x56000060)		/* GPG config register */
#define GPGDATr __REG_VALUE(0x56000064)		/* GPG data register */
#define GPGUPr	__REG_VALUE(0x56000068)		/* GPG pins Pull-up constrol register,0 is enable */
#endif /* _SOC_S3C2440_REG_H_ */


/* 寄存器部分字段定义头文件:soc_s3c2440_field.h */
#ifndef _SOC_S3C2440_FIELD_H_
#define _SOC_S3C2440_FIELD_H_

/* The suffix 'f' means that the macro is the bits of a field in a register. */

/* GPFCON register fields bits */
#define GPFCON_GPF0_CONF_BITSf	(0x3 << 0)
#define GPFCON_GPF1_CONF_BITSf 	(0x3 << 2)
#define GPFCON_GPF2_CONF_BITSf 	(0x3 << 4)
#define GPFCON_GPF3_CONF_BITSf 	(0x3 << 6)
#define GPFCON_GPF4_CONF_BITSf 	(0x3 << 8)
#define GPFCON_GPF5_CONF_BITSf 	(0x3 << 10)
#define GPFCON_GPF6_CONF_BITSf 	(0x3 << 12)
#define GPFCON_GPF7_CONF_BITSf 	(0x3 << 14)

#define GPFCON_INPUT_BITS	(0x0)
#define GPFCON_OUTPUT_BITS	(0x1)
#define GPFCON_EINT_BITS	(0x2)			/* EINT0~EINT7 at GPFCON GPF0~GPF7 */
#define GPFCON_REVERSE_BITS	(0x3)

/* GPFDAT register fields bits */
#define GPFDAT_GPF0_DATA_BITSf	(0x1 << 0)
#define GPFDAT_GPF1_DATA_BITSf	(0x1 << 1)
#define GPFDAT_GPF2_DATA_BITSf	(0x1 << 2)
#define GPFDAT_GPF3_DATA_BITSf	(0x1 << 3)
#define GPFDAT_GPF4_DATA_BITSf	(0x1 << 4)
#define GPFDAT_GPF5_DATA_BITSf	(0x1 << 5)
#define GPFDAT_GPF6_DATA_BITSf	(0x1 << 6)
#define GPFDAT_GPF7_DATA_BITSf	(0x1 << 7)

//......GPGCON、GPGDAT略
#endif

④操作宏函数:

#ifndef _SOC_S3C2440_REG_OPERATOR_
#define _SOC_S3C2440_REG_OPERATOR_

#include <soc_s3c2440_reg.h>
#include <soc_s3c2440_field.h>

#define __GET_BIT(sValue, bitPos)		(((sValue) >> (bitPos)) & (0x01))
#define __SET_BIT(sValue, bitPos, setBit)	((setBit) ? ((sValue) |= ((0x01) << (bitPos))) \
														: ((sValue) &= ~((0x01) << (bitPos))))

#define _TEST_BIT_IS_ZERO(sValue, bitPos)	(__GET_BIT(sValue, bitPos) ? (0) : (1))
#define _TEST_BIT_IS_ONE(sValue, bitPos)	(__GET_BIT(sValue, bitPos) ? (1) : (0))

#endif

2.5、可以看一下预处理信息:

# 1 "led.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "led.c"
# 10 "led.c"
# 1 "./include/soc_s3c2440/soc_s3c2440_reg_operator.h" 1

# 1 "./include/soc_s3c2440/soc_s3c2440_reg.h" 1
# 5 "./include/soc_s3c2440/soc_s3c2440_reg_operator.h" 2
# 1 "./include/soc_s3c2440/soc_s3c2440_field.h" 1
# 6 "./include/soc_s3c2440/soc_s3c2440_reg_operator.h" 2
# 11 "led.c" 2

void delay(volatile int time)
{
 while(time--);
}

void init_config()
{
 (*((volatile unsigned int *)(0x56000050))) &= ~((0x3 << 0) | (0x3 << 4) | (0x3 << 8) | (0x3 << 10) | (0x3 << 12));
 (*((volatile unsigned int *)(0x56000060))) &= ~((0x3 << 6));
 (*((volatile unsigned int *)(0x56000050))) |= (((0x1) << 8) | ((0x1) << 10) | ((0x1) << 12));
}

void led_running()
{
# 45 "led.c"
 volatile unsigned int gpfTemp=0;
 volatile unsigned int gpgTemp=0;

 int delay_time = (25000);
 unsigned int i = 0;
 unsigned int len = 0;
 const unsigned char led_info[] = {
  0x00, 0x40, 0x60, 0x70, 0x30, 0x10, 0x00, 0x70,
  0x00, 0x70, 0x00, 0x70, 0x00, 0x10, 0x30, 0x70,
  0x60, 0x40, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70
 };
 len = sizeof(led_info) / sizeof(led_info[0]);

 while(1)
 {
  for(i = 0; i < len; i++)
  {
   gpfTemp = (*((volatile unsigned int *)(0x56000054)));
   gpgTemp = (*((volatile unsigned int *)(0x56000064)));

   if(((((gpfTemp) >> (0)) & (0x01)) ? (0) : (1))){
    if(delay_time < (50000)){
     delay_time += (2000);
    }
   }
   if(((((gpfTemp) >> (2)) & (0x01)) ? (0) : (1))){
    if(delay_time > (5000)){
     delay_time -= (2000);
    }
   }
   if(((((gpgTemp) >> (3)) & (0x01)) ? (0) : (1))){
    delay_time = (25000);
   }
   (((((led_info[i]) >> (4)) & (0x01))) ? (((*((volatile unsigned int *)(0x56000054)))) |= ((0x01) << (4))) : (((*((volatile unsigned int *)(0x56000054)))) &= ~((0x01) << (4))));
   (((((led_info[i]) >> (5)) & (0x01))) ? (((*((volatile unsigned int *)(0x56000054)))) |= ((0x01) << (5))) : (((*((volatile unsigned int *)(0x56000054)))) &= ~((0x01) << (5))));
   (((((led_info[i]) >> (6)) & (0x01))) ? (((*((volatile unsigned int *)(0x56000054)))) |= ((0x01) << (6))) : (((*((volatile unsigned int *)(0x56000054)))) &= ~((0x01) << (6))));

   delay(delay_time);
  }
 }
}
void led_light()
{
 init_config();
 led_running();
}

目录 目录 1 摘 要 2 1 设计目的 3 2 设计要求 3 3 设计内容 4 3.1整体设计 4 3.2霓虹灯工作原理 5 3.3器件选择 6 3.3.1 S3C2440简介 6 3.3.2 LED 7 3.4各模块电路 8 3.4.1电源电路的模块 8 3.4.2时钟电路的模块 8 3.4.3复位电路的模块 9 3.4.4串口电路模块 10 3.4.5 LED显示模块 10 3.5 软件设计 11 总结与致谢 12 参考文献 13 附录 14 摘 要 近年来随着科技的飞速发展,霓虹灯正逐渐走进人们的生活,特别是当今充满竞争的时 代,各地政府为吸引游客和投资者,在城市的沿街、沿道、沿河、沿线等地用霓虹灯造 景,实施"亮化工程",以美化环境、树立城市形象。 随着ARM嵌入式系统的应用越来越广泛,功能也越来越强大,对系统中的人机界面的要求 也越来越高,在应用需求的驱使下,许多在Linux下的图形界面软件包的开发和移植工作 中都涉及到底层LED驱动的开发问题。因此选用ARM嵌入式微处理器,并在用其构成的嵌 入式系统中开发LED驱动得以广泛运用。 本章将针对选用的ARM芯片,确定整个控制系统的硬件选型和单元电路的设计方案。 同时绘制系统电路图并进行硬件系统的调试。 关键词: 嵌入式;ARM霓虹灯;硬件; 1 设计目的 本设计要求实现一个智能霓虹灯控制系统。该系统有自动与手动控制两种方式。在自 动方式下,系统可根据持续显示不同的霓虹灯效果,每种效果持续2分钟;在手动方式下 ,可以单独控制每种效果的显示,并且可以控制所有灯的亮灭。并且所有信息都可通过 串口显示在电脑上。 2 设计要求 1、根据设计题目的要求,以及根据已知参数对输入信号特征进行分析、需求分析, 选择确定ARM芯片型号、霓虹灯、串口控制芯片型号,完成系统硬件设计。 2、基本教学要求:每人一台计算机,计算机安装ADS、Protel等软件。 3 设计内容 3.1整体设计 本课程设计采用S3C2440芯片,该芯片采用了非常先进的ARM920T 内核,它是由 ARM(Advanced RISCMachines) 公司研制的,通过详细分析系统的软、硬件设计步骤、实现细节以及调试技巧等,设计 出霓虹灯显示控制电路。 本方案以S3C2440芯片作为硬件控制核心,电源模块、复位模块、时钟模块、以及显 示模块组成。在软件控制方面根据各种亮灯时间的不同需要,在不同时刻输出灯亮或灯 灭的控制信号,然后驱动各种颜色的灯亮或灭。该新型LED霓虹灯实际应用效果较好,亮 灯模式多,用户可以根据不同场合和时间来调节亮灯频率和亮灯时间。与普通LED彩灯相 比,具有体积小,价格低,耗能低、通用性强等优点。 其系统总体框图。如图1所示。 图1.系统整体结构框图 1.复位电路可完成系统上电复位和在系统工作时用户手动按键复位; 2.电源电路为3.3V、1.8V和1.2V的稳压模块,给时S3C2440芯片、内核及其他外围电 路供电; 3.有源晶振为系统提供工作时钟,通过片内PLL电路倍频为50MHZ作为ARM920T微处理 器的工作时钟; 4.扩展的SDRAM存储器作为系统运行时的主要区域,系统及用户数据、堆栈均位于SD RAM存储器中; 5.JTAG接口可对芯片内部的所有部件进行访问,通过该接口可对系统进行调试、编程 等; 3.2霓虹灯工作原理 霓虹灯是一种低气压冷阳极辉光放电发光的光源。气体放电发光是自然界的一种物理 现象。通过气体放电使电能转换为五光十色的光谱线,这是霓虹灯工作重要的基本过程 。在通常情况下,气体是良好的绝缘体,它并不能传导电流。但是在强电场、光辐射、 粒子轰击和高温加热等条件下,气体分子可能发生电离,产生了可以自由移动的带电粒 子,并在电场作用下形成电流,使绝缘的气体成为良导体。这种电流通过气体的现象就 被称为气体放电过程。 在密闭的玻璃管内,充有氖、氦、氩等气体,灯管两端装有两个金属电极,电极一 般用铜材料制作,电极引线接入电源电路,配上一只高压变压器,将10~15kV的电压加 在电极上。由于管内的气体是由无数分子构成的,在正常状态下分子与原子呈中性。在 高电压作用下,少量自由电子向阳极运动,气体分子的急剧游离激发电子加速运动,使 管内气体导电,发出色彩的辉光(又称虹光)。霓虹灯原理的发光颜色与管内所用气体 及灯管的颜色有关;霓虹灯原理如果在淡黄色管内装氖气就会发出金黄色的光,如果在 无色透明管内装氖气就会发出黄白色的光。霓虹灯原理要产生不同颜色的光,就要用许 多不同颜色的灯管或向霓虹灯管内装入不同的气体。 3.3器件选择 3.3.1 S3C2440简介 产品简介: S3C2440A微处理器是一款由Samsung半导体公司推出的高性能、低功耗、高集成度并 具有工业级温度范围和性能的微处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值