《STM32从零开始学习历程》——DMA直接存储区访问实验例程

24 篇文章 0 订阅

《STM32从零开始学习历程》@EnzoReventon

DMA—直接存储区访问实验例程

本章节为DMA直接存储区访问的实验例程讲解,以“正点原子”的例程为基础进行讲解,如有不足之处还恳请各位大佬不吝赐教。
参考资料:
[野火EmbedFire]《STM32库开发实战指南——基于野火霸天虎开发板》
[正点原子]STM32F4开发指南-库函数版本_V1.2
[ST]《STM32F4xx中文参考手册》

1. DMA简介

DMA的详细介绍已经在上一讲中进行过详细的介绍:《STM32从零开始学习历程》——DMA直接存储区访问理论知识

2. 本实验历程实现功能介绍

根据《STM32从零开始学习历程》——DMA直接存储区访问理论知识的详细介绍,我们可以知道DMA是一种可以不通过CPU的直接进行数据传输的控制器。本例程主要功能为使用DMA串口通讯将一定量的数据发送出去,使用串口助手接收发送到的数据。程序功能要点如下:

(1). 通过DMA将数据发送到USART1,使用串口助手接收数据。

(2). 使用一个按键控制DMA发送,按下按钮就进行一次DMA数据发送操作。

(3). LCD屏幕显示发送状态与发送进度。(LCD的讲解将在后续blog中讲解,本文只要会用就行)

3. 实验准备

软件:Keil μVision5 v5.33(MDK5),串口助手XCOM V2.6
环境:Windows10 Enterprise x64
芯片:STM32F406ZGT6
设备:正点原子STM32F4探索者开发板,正点原子4.3寸 TFTLCD屏
仿真器:ST-Link
参考手册
[野火EmbedFire]《STM32库开发实战指南——基于野火霸天虎开发板》
[正点原子]STM32F4开发指南-库函数版本_V1.2
[ST]《STM32F4xx中文参考手册》
[ST]《STM32F407xx》

4. 硬件设计

本实验中需要用到USART1,所以我们需要将USART1的TX与RX引脚与相应的GPIO引脚相连接,此处我们使用PB6/PB7引脚进行通讯,关于USART通讯串口的配置与选择问题可以看:《STM32从零开始学习历程》——USART串口通讯实验篇1——中断接收与发送,此处就不多赘述。
硬件连接呢我们还是通过使用杜邦线将USART TX/RX与PB6/PB7向连接,同时将USART1串口连接至电脑。如下图所示:
在这里插入图片描述

5. 程序设计流程

1.DMA配置程序过程

①使能DMA时钟
RCC_AHB1PeriphClockCmd();
② 初始化DMA通道参数
DMA_Init();
③使能串口DMA发送,串口DMA使能函数:
USART_DMACmd();
④查询DMA的EN位,确保数据流就绪,可以配置
DMA_GetCmdStatus();
⑤设置通道当前剩余数据量
DMA_SetCurrDataCounter();
⑥使能DMA1通道,启动传输。
DMA_Cmd();
⑤查询DMA传输状态
DMA_GetFlagStatus();
⑥获取/设置通道当前剩余数据量:
DMA_GetCurrDataCounter();

2. 相关函数介绍

1)使能 DMA2 时钟,并等待数据流可配置 。
DMA的时钟使能是通过 AHB1ENR 寄存器来控制的,这里我们要先使能时钟,才可以配置 DMA相关寄存器。所以先要使能 DMA2 的时钟。另外,要对配置寄存器( DMA_SxCR )进行设置,必须先等待其最低位为 0 (也就是 DMA 传输禁止了),才可以进行配置。
库函数使能DMA2 时钟的方法为:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE); //DMA2 时钟使能等待
DMA 可配置,也就是等待 DMA_SxCR 寄存器最低位为 0 的方法为:
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE) { } // 等待 DMA 可配置

2)初始化 DMA2 数据流 7 ,包括配置通道,外设地址,存储器地址,传输数据量等 。
DMA的某个数据流各种配置参数初始化是通过 DMA_Init 函数实现的:
void DMA_Init(DMA_Stream_TypeDef* DMAy_Streamx, DMA_InitTypeDef* DMA_InitStruct)
函数的第一个参数是指定初始化的 DMA 的数据流编号,这个很容易理解。入口参数范围为:
DMA x _Stream0 DMA x _Stream 7(x=1,2) 。
下面我们主要看看第二个参数。跟其他外设一样,同样是通过初始化结构体成员变量值来达到初始化的目的,下面我们来看看 DMA_InitTypeDef结构体的定义:

typedef struct
{
uint32_t DMA_Channel;
uint32_t DMA_PeripheralBaseAddr;
uint32_t DMA_Memory0BaseAddr;
uint32_t DMA_DIR;
uint32_t DMA_BufferSize;
uint32_t DMA_PeripheralInc;
uint32_t DMA_MemoryInc;
uint32_t DMA_Pe ripheralDataSize;
uint32_t DMA_MemoryDataSize;
uint32_t DMA_Mode;
uint32_t DMA_Priority;
uint32_t DMA_FIFOMode;
uint32_t DMA_FIFOThreshold;
uint32_t DMA_MemoryBurst;
uint32_t DMA_PeripheralBurst;
}
DMA_InitTypeDef;

1) DMA_Channel:
DMA 请求通道选择,可选通道0 至通道7,每个外设对应固定的通道,具体设置值需要查表DMA1 各个通道的请求映像和表DMA2 各个通道的请求映像。

2) DMA_PeripheralBaseAddr:
外设地址,设定DMA_SxPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储区地址。
ADC3 的数据寄存器ADC_DR 地址为((uint32_t)ADC3+0x4C)。

3) DMA_Memory0BaseAddr:
存储器0 地址,设定DMA_SxM0AR 寄存器值;一般设置为我们自义存储区的首地址。我们程序先自定义一个16 位无符号整形数组ADC_ConvertedValue[4]用来存放每个通道的ADC 值, 所以把数组首地址(直接使用数组名即可) 赋值给DMA_Memory0BaseAddr。

4) DMA_DIR:
传输方向选择,可选外设到存储器、存储器到外设以及存储器到存储器。它设定DMA_SxCR 寄存器的DIR[1:0] 位的值。ADC 采集显然使用外设到存储器模式。

5) DMA_BufferSize:
设定待传输数据数目,初始化设定DMA_SxNDTR 寄存器的值。这里ADC是采集4 个通道数据,所以待传输数目也就是4。

6) DMA_PeripheralInc:
如果配置为DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定DMA_SxCR 寄存器的PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。ADC3 的数据寄存器地址是固定并且只有一个所以不使能外设地址递增。

7) DMA_MemoryInc:
如果配置为DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定DMA_SxCR 寄存器的MINC 位的值;我们自定义的存储区一般都是存放多个数据的,所以使能存储器地址自动递增功能。我们之前已经定义了一个包含4 个元素的数字用来存放数据,使能存储区地址递增功能,自动把每个通道数据存放到对应数组元素内。

8) DMA_PeripheralDataSize:
外设数据宽度,可选字节(8 位)、半字(16 位) 和字(32 位),它设定DMA_SxCR 寄存器的PSIZE[1:0] 位的值。ADC 数据寄存器只有低16 位数据有效,使用半字数据宽度。

9) DMA_MemoryDataSize:
存储器数据宽度,可选字节(8 位)、半字(16 位) 和字(32 位),它设定DMA_SxCR 寄存器的MSIZE[1:0] 位的值。保存ADC 转换数据也要使用半字数据宽度,这跟我们定义的数组是相对应的。

10) DMA_Mode:
DMA 传输模式选择,可选一次传输或者循环传输,它设定DMA_SxCR 寄存器的CIRC 位的值。我们希望ADC 采集是持续循环进行的,所以使用循环传输模式。

11) DMA_Priority:
软件设置数据流的优先级,有4 个可选优先级分别为非常高、高、中和低,它设定DMA_SxCR 寄存器的PL[1:0] 位的值。DMA 优先级只有在多个DMA 数据流同时使用时才有意义,这里我们设置为非常高优先级就可以了。

12) DMA_FIFOMode:
FIFO 模式使能,如果设置为DMA_FIFOMode_Enable 表示使能FIFO 模式功能;它设定DMA_SxFCR 寄存器的DMDIS 位。ADC 采集传输使用直接传输模式即可,不需要使用FIFO 模式。

13) DMA_FIFOThreshold:
FIFO 阈值选择,可选4 种状态分别为FIFO 容量的1/4、1/2、3/4 和满;它设定DMA_SxFCR 寄存器的FTH[1:0] 位;DMA_FIFOMode 设置为DMA_FIFOMode_Disable,那DMA_FIFOThreshold 值无效。ADC 采集传输不使用FIFO 模式,设置改值无效。

14) DMA_MemoryBurst:
存储器突发模式选择,可选单次模式、4 节拍的增量突发模式、8 节拍的增量突发模式或16 节拍的增量突发模式,它设定DMA_SxCR 寄存器的MBURST[1:0] 位的值。ADC 采集传输是直接模式,要求使用单次模式。

15) DMA_PeripheralBurst:
外设突发模式选择,可选单次模式、4 节拍的增量突发模式、8 节拍的增量突发模式或16 节拍的增量突发模式,它设定DMA_SxCR 寄存器的PBURST[1:0] 位的值。
ADC 采集传输是直接模式,要求使用单次模式。

3)使能串口 1 的 DMA 发送
进行DMA 配置之后,我们就要开启串口的 DMA 发送功能,使用的函数是:
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口 1的 DMA发送
如果是要使能串口DMA 接受,那么第二个参数修改为 USART_DMAReq_Rx 即可。

4)使能 DMA 2 数据流 7 ,启动传输。
使能 DMA 数据流的函数为:
void DMA_Cmd(DMA_Stream_TypeDef* DMAy_Streamx, FunctionalState NewState)
使能 DMA2_Stream7 ,启动传输的方法为:
DMA_Cmd DMA2_Stream7 ENABLE
通过以上4 步设置,我们就可以启动一次 USART1 的 DMA 传输了。

5)查询 DMA 传输状态
在DMA 传输过程中,我们要查询 DMA 传输通道的状态,使用的函数是:
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)
比如我们要查询DMA数据流 7传输是否完成,方法是:
DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7);
这里还有一个比较重要的函数就是获取当前剩余数据量大小的函数:
int16_t DMA_GetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx);
比如我们要获取DMA数据流 7还有多少个数据没有传输,方法是:
DMA_GetCurrDataCounter(DMA1_Channel4);
同样,我们也可以设置对应的DMA数据流传输的数据量大小 ,函数为:
void DMA_SetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx, uint16_t Counter);

6. 程序详解

USART.C
对于USART串口初始化的子程序我们仍然使用串口通讯中的程序代码。

//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
}; 
FILE __stdout;       

//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ 
	x = x; 
} 


void uart_init(u32 bound){
    //GPIO端口设置
    GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);                //使能GPIOA时钟

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);				//使能USART1时钟
 
	//串口1对应引脚复用映射
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_USART1);				//GPIOB6复用为USART1
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_USART1); 			//GPIOB7复用为USART1
	
	//USART1端口配置
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; 				//GPIOB6与GPIOB7
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;						//复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;					//速度50MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; 						//推挽复用输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; 						//上拉

	GPIO_Init(GPIOB,&GPIO_InitStructure); 								//初始化PB6,PB7

   //USART1 初始化设置
	USART_InitStructure.USART_BaudRate = bound;							//波特率设置
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;			//字长为8位数据格式
	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); 							//初始化串口1
	
    USART_Cmd(USART1, ENABLE);  										//使能串口1 
	
}
//===============================================================================
///重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch , FILE *f)
{
	USART_SendData(USART1,(uint8_t) ch );
	
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
	
	return (ch);
}

//===============================================================================
///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
	
	return (int)USART_ReceiveData(USART1);
}
//===============================================================================

中断服务函数。

void USART1_IRQHandler(void)                	//串口1中断服务程序
{

uint8_t temp;

	if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET)
	{
		temp = USART_ReceiveData(USART1);
		USART_SendData(USART1,temp);
	}

DMA.C
DMA初始化函数。此函数有5个形参。

DMA_Stream_TypeDef *DMA_Streamx: 为选择数据流。
u32 chx: 为通道选择。
u32 par: 为外设地址。
u32 mar: 存储器地址。
u16 ndtr: 为数据传输量。

void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr)
{ 
 
	DMA_InitTypeDef  DMA_InitStructure;
	
	if((u32)DMA_Streamx>(u32)DMA2)									//得到当前stream是属于DMA2还是DMA1
	{
	  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);			//DMA2时钟使能 
		
	}else 
	{
	  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);			//DMA1时钟使能 
	}
      DMA_DeInit(DMA_Streamx);
	
	while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE) { }			//等待DMA可配置 
	
  // 配置 DMA 初始化函数 //
  DMA_InitStructure.DMA_Channel = chx;  											//通道选择
  DMA_InitStructure.DMA_PeripheralBaseAddr = par;									//DMA外设地址
  DMA_InitStructure.DMA_Memory0BaseAddr = mar;										//DMA 存储器0地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;							//存储器到外设模式
  DMA_InitStructure.DMA_BufferSize = ndtr;											//数据传输量 
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;					//外设非增量模式
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;							//存储器增量模式
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;			//外设数据长度:8位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;					//存储器数据长度:8位
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;										// 使用普通模式 
  DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;								//中等优先级
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         					//不使能FIFO模式
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;						//FIFO 阈值选择,此时无效
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;						//存储器突发单次传输
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;				//外设突发单次传输
  DMA_Init(DMA_Streamx, &DMA_InitStructure);										//初始化DMA Stream
  
} 

开启一次DMA数据传输。
其中:DMA_Stream_TypeDef *DMA_Streamx 为DMA数据流;
u16 ndtr为为数据传输量。


void MYDMA_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr)
{
 
	DMA_Cmd(DMA_Streamx, DISABLE);                      //关闭DMA传输 
	
	while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}	//确保DMA可以被设置  
		
	DMA_SetCurrDataCounter(DMA_Streamx,ndtr);          //数据传输量  
 
	DMA_Cmd(DMA_Streamx, ENABLE);                      //开启DMA传输 
}	  

MAIN.C
①进行宏定义

#define SEND_BUF_SIZE 9600	//发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍.

u8 SendBuff[SEND_BUF_SIZE];	//发送数据缓冲区

const u8 TEXT_TO_SEND[]={"DMA串口实验 :Hello World!"};	 
int main(void)
{ 
	u16 i;
	u8 t=0;
	u8 j,mask=0;
	float pro=0;															//进度
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);							//设置系统中断优先级分组2
	delay_init(168);    													//初始化延时函数
	uart_init(115200);														//初始化串口波特率为115200
	LED_Init();																//初始化LED 
 	LCD_Init();																//LCD初始化 
	KEY_Init(); 															//按键初始化 
 	MYDMA_Config(DMA2_Stream7,DMA_Channel_4,(u32)&USART1->DR,(u32)SendBuff,SEND_BUF_SIZE);
 	//DMA2,STEAM7数据流7,CH4通道4,外设为串口1,存储器为SendBuff,长度为:SEND_BUF_SIZE.
	POINT_COLOR=RED; 
	LCD_ShowString(30,50,200,16,16,"STM32F407");	
	LCD_ShowString(30,70,200,16,16,"DMA");	
	LCD_ShowString(30,90,200,16,16,"==========");
	LCD_ShowString(30,110,200,16,16,"2021/4/1");	 
	LCD_ShowString(30,130,200,16,16,"KEY0:Start");	 
	POINT_COLOR=BLUE;														//设置字体为蓝色      	 
 	//显示提示信息	
	j=sizeof(TEXT_TO_SEND);	   
	for(i=0;i<SEND_BUF_SIZE;i++)											//填充ASCII字符集数据
    {
		if(t>=j)															//加入换行符
		{
			if(mask)
			{
				SendBuff[i]=0x0a;
				t=0;
			}else 
			{
				SendBuff[i]=0x0d;
				mask++;
			}	
		}else																//复制TEXT_TO_SEND语句
		{
			mask=0;
			SendBuff[i]=TEXT_TO_SEND[t];
			t++;
		}   	   
    }		 
	POINT_COLOR=BLUE;														//设置字体为蓝色	  
	i=0;
	while(1)
	{
		t=KEY_Scan(0);
		if(t==KEY0_PRES)  													//KEY0按下
		{
			printf("\r\nDMA DATA:\r\n"); 	    
			LCD_ShowString(30,150,200,16,16,"Start Transimit....");
			LCD_ShowString(30,170,200,16,16,"   %") ;     					//显示百分号       
      		USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);  					//使能串口1的DMA发送     
			MYDMA_Enable(DMA2_Stream7,SEND_BUF_SIZE);     					//开始一次DMA传输!	  
		    //等待DMA传输完成,此时我们来做另外一些事,点灯
		    //实际应用中,传输数据期间,可以执行另外的任务
		    while(1)
		    {
				if(DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7)!=RESET)	//等待DMA2_Steam7传输完成
				{ 
					DMA_ClearFlag(DMA2_Stream7,DMA_FLAG_TCIF7);				//清除DMA2_Steam7传输完成标志
					break; 
		        }
				pro=DMA_GetCurrDataCounter(DMA2_Stream7);					//得到当前还剩余多少个数据
				pro=1-pro/SEND_BUF_SIZE;									//得到百分比	  
				pro*=100;      			    								//扩大100倍
				LCD_ShowNum(30,170,pro,3,16);	  
		    }			    
			LCD_ShowNum(30,170,100,3,16);									//显示100%	  
			LCD_ShowString(30,150,200,16,16,"Transimit Finished!");			//提示传送完成
		}
		i++;
		delay_ms(10);
		if(i==20)
		{
			LED0=!LED0;														//提示系统正在运行	
			i=0;
		}		   
	}		    
}
//开启一次DMA传输
//DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7 
//ndtr:数据传输量  
void MYDMA_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr)
{
 
	DMA_Cmd(DMA_Streamx, DISABLE);                      				//关闭DMA传输 
	
	while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}					//确保DMA可以被设置  
		
	DMA_SetCurrDataCounter(DMA_Streamx,ndtr);          					//数据传输量  
 
	DMA_Cmd(DMA_Streamx, ENABLE);                      					//开启DMA传输 
}	  

7. 效果展示

在这里插入图片描述
将程序下载进芯片后,屏幕将出现提示信息,按下按键0将执行一次DMA数据传输操作。
打开串口调试助手,设置好波特率,打开串口,将看到所有传输的数据。
在这里插入图片描述

注意事项:
在传输数据的过程中如果SEND_BUF_SIZE发送数据的长度没有定义准确将导致发送数据的不完整,如下图所示:
在这里插入图片描述
显然最后一行没有将“STM32F4 DMA”完整发送,如果需要完整发送需要对SEND_BUF_SIZE进行合理取值,最好等于sizeof(TEXT_TO_SEND)+2的整数倍。

8. 小结

本实验到此结束,其实实验不难,主要是一个实验的过程,需要对这个过程进行了解,还有就是相关函数的使用标志位的判定等。此外,多查手册仍然很重要。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

EnzoReventon

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

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

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

打赏作者

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

抵扣说明:

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

余额充值