STM32:USART串口通信笔记

硬件平台:stm32f10xZET6
开发环境:keil MDK uVision v4.10
开发语言:C、ST_lib_3.5固件库


【串口通信】

typedef struct
{
  u32 USART_BaudRate;
  u16 USART_WordLength;
  u16 USART_StopBits;
  u16 USART_Parity;
  u16 USART_Mode;
  u16 USART_HardwareFlowControl;  
} USART_InitTypeDef;


typedef struct
{
  u16 USART_Clock;
  u16 USART_CPOL;
  u16 USART_CPHA;
  u16 USART_LastBit;
} USART_ClockInitTypeDef;


串口外设主要由三个部分组成,分别是:波特率的控制部分、收发控制部分及数据存储转移部分。


CR1、 CR2、 CR3、SR,即 USART 的三个控制寄存器(Control Register)及一个状态寄存器(Status Register)


当我们需要发送数据时,内核或 DMA 外设把数据从内存(变量)写入到发送数据寄存器 TDR 后,发送控制器将适时地自动把数据从 TDR 加载到发送移位寄存器,然后通过串口线Tx,把数据一位一位地发送出去,在数据从 TDR 转移到移位寄存器时,会产生发送寄存器TDR 已空事件 TXE,当数据从移位寄存器全部发送出去时,会产生数据发送完成事件 TC,这些事件可以在状态寄存器中查询到。
而接收数据则是一个逆过程,数据从串口线 Rx 一位一位地输入到接收移位寄存器,然后自动地转移到接收数据寄存器 RDR,最后用内核指令或 DMA读取到内存(变量)中。


调用了库函数 RCC_APB2PeriphClockCmd()初始化了USART1 和 GPIOA 的时钟,这是因为使用了 GPIOA 的 PA9 和 PA10 的默认复用USART1 的功能,在使用复用功能的时候, 要开启相应的功能时钟 USART1。
/* config USART1 clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);


在使用外设时,不仅要使能其时钟,还要调用此函数使能外设才可以正常使用。
/* config USART1 clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
/* 使能串口1接收中断 */
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Cmd(USART1, ENABLE);


USART重要库函数:
USART_SendData()
USART_ReceiveData()
USART_GetFlagStatus()
USART_Init()
USART_ITConfig()
USART_Cmd()


【USART_GetFlagStatus()】
USART_FLAG_CTS: CTS Change flag (not available for UART4 and UART5) 
USART_FLAG_LBD: LIN Break detection flag 
USART_FLAG_TXE: Transmit data register empty flag 
USART_FLAG_TC: Transmission Complete flag 
USART_FLAG_RXNE: Receive data register not empty flag 
USART_FLAG_IDLE: Idle Line detection flag 
USART_FLAG_ORE: OverRun Error flag 
USART_FLAG_NE: Noise Error flag 
USART_FLAG_FE: Framing Error flag 
USART_FLAG_PE: Parity Error flag 


【USART_ITConfig()】
USART_IT_CTS: CTS change interrupt (not available for UART4 and UART5) 
USART_IT_LBD: LIN Break detection interrupt 
USART_IT_TXE: Transmit Data Register empty interrupt 
USART_IT_TC: Transmission complete interrupt 
USART_IT_RXNE: Receive Data register not empty interrupt 
USART_IT_IDLE: Idle line detection interrupt 
USART_IT_PE: Parity Error interrupt 
USART_IT_ERR: Error interrupt(Frame error, noise error, overrun error)


串口的发送中断有两个,分别是:
>>发送数据寄存器空中断(TXE)
>>发送完成中断(TC)
一般来说我们会使用发送数据寄存器空中断,用这个中断发送的效率会高一些。


keil的虚拟串口Debug调试:
需要设置为 Use Simulator模式
开启 View-command window输入命令

MODE COM1 115200,0,8,1
ASSIGN COM1 <S1IN> S1OUT
技巧: 【Debug】选项卡下左侧 Initialization File 中点击【...】 新增一个默认调试命令的.ini文件,如debug.ini

虚拟串口软件 VSPD 开启两个COM,使用secureCRT连接另外一个COM口,查看接收情况


第一个AT指令是“ATE0Q0V1”,很是迷惑了一阵,后来才明白这是三个指令的合并:”ATE0+ATQ0+ATV1“。
ATE0:不回显字符。
ATE1:回显字符。
ATQ0:  返回结果码。
ATQ1:不返回结果吗。
ATV0:返回数字码。
ATV1: 返回文字码。




记录下接线颜色对应管脚


>>usart1连USB-TTL
    重定向printf到usart1
    往usart1发AT
    终端测试
>>usart1连GPRS,usart2连USB-TTL
    往usart1发AT
    逐个字符收数据到公共缓冲区
    使用usart2重定向的printf输出缓冲区内容
    终端测试






// 中断处理函数需:时钟配置、中断配置、USART中的接收中断使能、中断处理函数
void USART1_IRQHandler(void)
{
uint8_t ch;

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
   //ch = USART1->DR;
ch = USART_ReceiveData(USART1);
  printf( "%c", ch );    //将接受到的数据直接返回打印

 
}
-----------------------------------------------------------
char buf[70] = {0}; // 全局缓冲区,extern调用
void USART1_IRQHandler(void)
{
uint8_t ch;
    int index = 0;

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
   //ch = USART1->DR;
buf[index] = USART_ReceiveData(USART1);
  printf( "%c", buf[index]);    //将接受到的数据通过usart1的printf打印显示
        index++;
    }  

}


// usart.c

#include "usart.h"

extern sdstring atcmd_recv_buff;

 /**
  * @ USART1 GPIO 配置,工作模式配置。115200 8-N-1
  */
void USART1_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	
	/* 配置 USART1 时钟 */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	/* USART1 GPIO 配置 */
	/* 配置 USART1 Tx (PA9) 为复用推挽输出模式,速度50MHZ */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	/* 配置 USART1 Rx (PA10) 为浮空输入模式 */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
		
	/* USART1 工作参数配置:
        - BaudRate = 115200 baud  
        - Word Length = 8 Bits
        - One Stop Bit
        - No parity
        - Hardware flow control disabled (RTS and CTS signals)
        - Receive and transmit enabled
	*/
	USART_InitStructure.USART_BaudRate = 115200;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_Parity = USART_Parity_No ;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_Init(USART1, &USART_InitStructure); // 写入初始化信息

	USART_Cmd(USART1, ENABLE);             /* 使能 USART1  */
}

 /**
  * @ USART1 NVIC 中断配置
  */
void NVIC_Config(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	/* 配置 NVIC 优先组0 (4位的16级优先级可选 - 0~15) */  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
	
	/* 使能USART1中断 */
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;	 
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 0
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 1~15
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}

// usart1中断处理函数在 stm32f10x_it.c line:156

 /**
  * @ USART2 GPIO 配置,工作模式配置。115200 8-N-1
  */
void USART2_Config(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	
	sds_clean(&atcmd_recv_buff); // 接收前初始化清理一次公共缓冲区
	
	/* 使能 USART2 时钟 */
	RCC_APB1PeriphClockCmd (RCC_APB1Periph_USART2 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); 
	
	/* 配置 USART2 Tx (PA2) 为复用推挽输出模式,速度50MHz */
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	/* 配置 USART2 Rx (PA3) 为输入浮空模式 */
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	/* USART2 工作参数配置:
	    - BaudRate = 115200 baud  
	    - Word Length = 8 Bits
	    - One Stop Bit
	    - No parity
	    - Hardware flow control disabled (RTS and CTS signals)
	    - Receive and transmit enabled
	*/
	USART_InitStructure.USART_BaudRate            = 115200;
	USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits            = USART_StopBits_1;
	USART_InitStructure.USART_Parity              = USART_Parity_No;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;
	USART_Init(USART2, &USART_InitStructure); // 写入初始化信息
	
	/* 使能串口2接收中断 */
	USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);

	USART_Cmd(USART2, ENABLE);            /* 使能 USART2  */	
}


/* USART1 - 发送字符、接收字符 (轮询也用于printf重定向)    */
int SendChar (int ch)  {                							/* 发送字符 8bit/byte     */ 
  USART_SendData(USART1, (unsigned char) ch);   					/* 将1字节数据发往串口寄存器 */ 	
  // while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);  	/* 轮询等待传输结束 */
  while (!(USART1->SR & USART_FLAG_TXE));
  return ch;
}

int GetData (void)  {                    							/* 读取字符 8bit/byte    */
  while (!(USART1->SR & USART_FLAG_RXNE));
  return (USART_ReceiveData(USART1));
}

/* USART2 - 发送字符、接收字符 (轮询也用于printf重定向)    */
int SendCharSerail2 (int ch)  {                						/* 发送字符 8bit/byte     */
  USART_SendData(USART2, (unsigned char) ch);
  while (!(USART2->SR & USART_FLAG_TXE));
  return (ch);
}

int GetDataSerial2 (void)  {                    					/* 读取字符 8bit/byte    */

  while (!(USART2->SR & USART_FLAG_RXNE));
  return (USART_ReceiveData(USART2));
}



/*********************************************END OF FILE**********************/

// retarget.c
		  
#include <stdio.h>
#include <stdarg.h>
#include "usart.h"

/*
 * 函数名: fputc
 * 描述 :	重定向 c 库函数 printf 到 USART1
 */
int fputc(int ch, FILE *f)
{
#if 1  // 1-USART1 : 0-USART2	
	USART_SendData(USART1, (unsigned char) ch); // 将1字节数据发往串口寄存器,printf测试usart2回显  	
	while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); // 轮询是否发送完成,标志位TC		
#else
	USART_SendData(USART2, (unsigned char) ch); // 将1字节数据发往串口寄存器  	
	while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET); // 轮询是否发送完成,标志位TC
#endif
	return (ch);
}

// 从USART中的数据
int fgetc(FILE *f) 
{
#if 1
	return (SendChar (GetData ()));
#else
	return (SendCharSerail2 (GetData ()));
#endif
}


// at_cmd.c

#include "at_cmd.h"
sdstring atcmd_recv_buff; //atcmd_recv_buff.buff 全局缓冲区,存放返回数据

// 逐个发送AT命令集
void send_all_at_cmd (void)
{
	char *p_res = NULL;
	char *p_str = "hello";

	send_at_cmd (AT_AT);
	p_res = receive_data ();
	print_log_info ("recv data");
	// if "OK"/0
	if (! strstr (p_res, "OK")) {
		return ;
	}

	send_at_cmd (AT_CGATT);
	p_res = receive_data ();
	print_log_info ("recv data");
	// if "OK"/0

	send_at_cmd (AT_CGATT_1);
	p_res = receive_data ();
	print_log_info ("recv data");
	// if "OK"/0

	send_at_cmd (AT_CON_TCP);
	p_res = receive_data ();
	print_log_info ("recv data");
	// if "CONNECT OK"
		  
	send_at_cmd (AT_CIPSEND);
	p_res = receive_data ();
	print_log_info ("recv data");
	// if ">"

	send_at_cmd (p_str);
	p_res = receive_data ();
	print_log_info ("recv data");
	// if "HTTP/1.1 400 Bad Request"

#if 0
	send_at_cmd (AT_CIPCLOSE); // 关闭TCP连接命令
	p_res = receive_data ();
	print_log_info ("recv cmd");
	// if "OK"/0
#endif
}

// 发送单个AT命令,打印提示日志
void send_at_cmd (char *p_cmd)
{
	//int i = 0;
#if 1
	send_USART2 (p_cmd, strlen (p_cmd));
	send_USART2 (AT_CONFIRM, strlen (AT_CONFIRM)); // \r\n
	strcpy (atcmd_recv_buff.buff, p_cmd);
	print_log_info ("send cmd");
#else
	// 发送什么指令-回显到屏幕(USART1重定向了printf)单向传输测试
	printf ("\r\n%s", p_cmd);
#endif
}

// 接收发送命令后GPRS通过串口端自动回复的数据
char* receive_data (void)
{
	int i;
	memset (atcmd_recv_buff.buff, 0, sizeof (atcmd_recv_buff.buff)); 
#if 1
	// 轮询读取USART2接收寄存器的信息到atcmd_recv_buff.buff[]
	do {            
		while(USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET);
		atcmd_recv_buff.buff[i] = USART2->DR;
		// 如果在USART2的接收寄存器中接收到 \n 字符,作为结束标志
		if(atcmd_recv_buff.buff[i] == '\n') {
			break;
		}
		i++;
	} while(1);
	return (atcmd_recv_buff.buff);
#else // used to test... 测试返回数据
	memset (atcmd_recv_buff.buff, 0, sizeof (atcmd_recv_buff.buff));
	strcpy (atcmd_recv_buff.buff, "OK\r\n");
	return (atcmd_recv_buff.buff);
#endif		
}

// 往连接GPRS串口的USART2串口中发送字符串数据
void send_USART2 (char *pdata, int len)
{
	int index = 0;
	for(index = 0; index < len; index++)
	{
		SendCharSerail2(pdata[index]);
	}
}

/*
// 测试IP:123.58.34.245  测试端口:80
void send_connect_cmd (char *ip,int port) 
{
	char buf[40]={0};
	send_at_cmd (AT_CIPSTART);
	snprintf(buf, sizeof(buf), "\"%s\",%d", ip, port);
	send_at_cmd (buf);	
}
*/

// 打印输出全局缓冲区内容,即日志,通过USART1重定向了printf后显示到串口终端
void print_log_info (char *p_str)
{
	printf ("\r\n%s:%s", p_str, atcmd_recv_buff.buff);
}

// 字符串清理
void sds_clean(sdstring *psds)
{
	memset(psds->buff, 0, sizeof(psds->buff));
	psds->pos=0;
	
}
// 字符串长度
int  sds_length(sdstring *psds)
{
	return psds->pos;
}
// 字符串追加
int sds_append_char(sdstring *psds, char ch)
{
	if(psds->pos >= sizeof(psds->buff)-1)
	{
		return -1;
	}	
	psds->buff[psds->pos]=ch;
	psds->pos++;
	return 0;
}


// at_cmd.h
#pragma once

#include <stdio.h>
#include <string.h>
#include "usart.h"
#include "setup.h"

/*--------------发送-----------------*/
// at命令头
#define AT_CMD_PREFIX   	"AT+"

#define CHAR_PLUS			'+'
#define CHAR_CR				'\r'
#define CHAR_POWER  		'^'

#define AT_CONFIRM  		"\r\n"

#define AT_FORMAT			"ATE0Q0V1"				// ATE1/0 回显/不回显字符; ATQ1/0 返回/不返回结果码; ATV1/0 返回/不返回结果码
													// ATV1/0 决定了收到GPRS模块回复的数据是 <字符串> OK 或 <数字> 0

#define AT_AT				"AT"				   	// AT命令测试,返回OK
#define AT_ATI				"ATI"				   	// 查询固件版本信息
#define AT_CCID				"AT+CCID"			   	// 查询SIM ,CCID用于判断是否插卡
#define	AT_CREG  			"AT+CREG?"			   	// 查询网络注册情况
#define	AT_CGATT			"AT+CGATT=1"		   	// 1.附着网络,返回OK
#define	AT_CGATT_1			"AT+CGACT=1,1"		   	// 2.激活PDP,返回OK,即可正常上网
#define AT_CIPSTART     	"AT+CIPSTART=\"TCP\","	// 连接服务端

#define AT_CON_TCP     		"AT+CIPSTART=\"TCP\",\"123.58.34.245\",80" // 连接服务端,IP和PORT目前为测试取值
#define AT_CON_IP			"123.58.34.245"
#define AT_CON_PORT			80

#define AT_CIPSEND			"AT+CIPSEND=20"			// 返回>,就可以输入要发送的内容20表示有20个字节
#define AT_TMP_STRING		"1234567890123456789"	// 19个字符有效字符

#define AT_CIPCLOSE 		"AT+CIPCLOSE=1"	    	// 关闭连接
#define AT_CIPSHUT			"AT+CIPSHUT"		    // 关闭 GPRS/CSD PDP 场景

   
#define AT_CIPTMODE_START  	"AT+CIPTMODE=1"	  	// 启动透传模式
#define AT_CIPTMODE_QUIT   	"AT+CIPTMODE=0"		// 停止透传模式

#define PARSE_CMD_OK	    0
#define PARSE_CMD_ERR      	-1
#define PARSE_CMD_NEEDMORE  1

typedef struct _sdstring {
	int pos;
	char buff[128];
} sdstring;

// 逐个发送AT命令集
void  send_all_at_cmd (void);
// 发送单个AT命令,打印提示日志
void  send_at_cmd (char * p_cmd);
// 接收发送命令后GPRS通过串口端自动回复的数据
char* receive_data (void);
// 往连接GPRS串口的USART2串口中按字节(8bit)逐个发送数据
void  send_USART2 (char *pdata, int len);
// 打印输出全局缓冲区,即每次数据收发的日志信息
void  print_log_info (char *p_str);

// void  send_connect_cmd (char *ip,int port);

// 字符串处理
void  sds_clean(sdstring *psds);
int   sds_append_char(sdstring *psds, char ch);
int   sds_length(sdstring *psds);

// 自定义的格式化输出
// void USART1_printf(USART_TypeDef* USARTx, uint8_t *Data,...)



// setup.c
  
#include "Setup.h"

static __IO u32 TimingDelay;
 
/**
  * @brief  	启动系统滴答定时器 SysTick
  */
void SysTick_Init(void)
{
	/* SystemFrequency / 1000    1ms中断一次
	 * SystemFrequency / 100000	 10us中断一次
	 * SystemFrequency / 1000000 1us中断一次
	 */
//	if (SysTick_Config(SystemFrequency / 100000))	// ST3.0 库版本
	if (SysTick_Config(SystemCoreClock / 100000))	// ST3.5 库版本
	{ 
		/* Capture error */ 
		while (1);
	}
	// 关闭滴答定时器  
	SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
}

/**
  * @brief   	us延时程序,10us为一个单位
  *	@arg nTime	Delay_us( 1 ) 则实现的延时为 1 * 10us = 10us
  */
void Delay_us(__IO u32 nTime)
{ 
	TimingDelay = nTime;	

	// 使能滴答定时器  
	SysTick->CTRL |=  SysTick_CTRL_ENABLE_Msk;

	while(TimingDelay != 0);
}

/**
  * @brief  	获取节拍程序
  * @attention  在 SysTick 中断函数 SysTick_Handler()调用
  */
void TimingDelay_Decrement(void)
{
	if (TimingDelay != 0x00)
	{ 
		TimingDelay--;
	}
}
/*********************************************END OF FILE**********************/

// led.h
#ifndef __LED_H
#define	__LED_H

#include "stm32f10x.h"

#define ON  0
#define OFF 1

/* 带参宏,可以像内联函数一样使用 */
#define LED1(a)	if (a)								\
					GPIO_SetBits(GPIOB,GPIO_Pin_5);	\
					else							\
					GPIO_ResetBits(GPIOB,GPIO_Pin_5)

#define LED2(a)	if (a)								\
					GPIO_SetBits(GPIOE,GPIO_Pin_5);	\
					else							\
					GPIO_ResetBits(GPIOE,GPIO_Pin_5)

void LED_Config(void);

#endif // __LED_H 

// led.c
  
#include "led.h"   

 /*
  * @ LED 初始化I/O控制
  */
void LED_Config(void)
{		
		/*定义一个GPIO_InitTypeDef类型的结构体*/
		GPIO_InitTypeDef GPIO_InitStructure;

		/*开启GPIOB的外设时钟*/
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOE, ENABLE); 

		/*设置引脚模式为通用推挽输出*/
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   
		/*设置引脚速率为50MHz */   
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

		/*选择要控制的GPIOB引脚*/															   
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;			 
		/*调用库函数,初始化GPIOB5*/
		GPIO_Init(GPIOB, &GPIO_InitStructure);

		/*选择要控制的GPIOE引脚*/															   
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;			 
		/*调用库函数,初始化GPIOE5*/
		GPIO_Init(GPIOE, &GPIO_InitStructure);			
		
		/* 关闭led灯 */
		GPIO_SetBits(GPIOB, GPIO_Pin_5);
		GPIO_SetBits(GPIOE, GPIO_Pin_5);
}
/*********************************************END OF FILE**********************/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

姜源Jerry

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

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

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

打赏作者

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

抵扣说明:

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

余额充值