STM32串口通信第二部曲


因为感觉STM32串口通信第一部写的不是很好,所以这里重新写一遍。

一、实验

(一)实验一

设置单片机异步收发器波特率为9600,1位停止位,无校验位;STM32系统给上位机(win10)连续发送“hello windows!”。win10采用“串口助手”工具接收。

1.代码部分

(1)配置GPIO控制器和USART控制器的代码(中规中矩没啥好说的)

//开启GPIOA和USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	
	//实例化控制器的对象
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	
	//先配置GPIO控制器
	//1.设置PA9为复用推挽输出模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置为复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
	
	//2.设置PA10为浮空输入模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置为浮空输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	//3.初始化GPIOA
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	
	//再配置USART控制器
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //硬件流控制:选择无流控制
	USART_InitStructure.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;   //模式选择既能发送又能接收
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	
	USART_Init(USART1,&USART_InitStructure);  //初始化串口1
	USART_Cmd(USART1,ENABLE);			//使能串口1

(2)数据的发送
在这里插入图片描述

USART_SendData(USART1,data);//发送字符
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//等待发送完成	

(3)数据的接收
在这里插入图片描述

while(1)
{
    // 检查是否接收到新数据
    if(USART_GetFlagStatus(USART1, USART_IT_RXNE) == SET)//接收(读取)数据寄存器不为空,证明收到数据
    {
        // 读取数据
        uint8_t receivedChar= USART_ReceiveData(USART1);
        // 这里可以根据需要处理接收到的数据,例如存储、分析或显示等
        // 示例:简单地将接收到的字符打印到串口(如果需要)
        // 注意:这一步是可选的,取决于您的具体需求
        // USART_SendData(USART1, receivedChar);
        // 进一步处理receivedChar...
    }
}
}

(3)完整代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"


int main()
{	
	//开启GPIOA和USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);  //但是为啥这两句分开写,它的参数又不会覆盖呢
	
	
	//实例化控制器的对象
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	
	//先配置GPIO控制器
	//1.设置PA9为复用推挽输出模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置为复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
	GPIO_Init(GPIOA,&GPIO_InitStructure);   //老子草了没写这一句,以为只GPIO_Init()一次就可以了,结果忘了这些参数会覆盖的。
	
	//2.设置PA10为浮空输入模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置为浮空输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	//3.初始化GPIOA
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	
	//再配置USART控制器
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //硬件流控制:选择无流控制
	USART_InitStructure.USART_Mode = USART_Mode_Tx ; //模式选择发送
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	
	USART_Init(USART1,&USART_InitStructure);  //初始化串口1
	USART_Cmd(USART1,ENABLE);			//使能串口1
	
	
	
	char transmitArray[17]={"hello windows! \r\n"};   //windows系统串口发送时,用回车换行组合 (\r\n) 来实现换行
	
	while(1)
	{	
		for(int i=0;i<=16;i++)
		{
		USART_SendData(USART1,transmitArray[i]);
		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)== RESET);  //RESET就是0,表示不符合,TXE的E是empty,表示发送寄存器不符合为空,即里面有东西,还在发送。}
			//一个字符有8位,每发送一个字符(8位),就检查是否发送完了
		}
		Delay_s(1);
		
	}
	
}


问题分析:为啥分别开启时钟能正常运行,而初始化GPIO引脚一次就会覆盖。
我只初始化了一次GPIO引脚。
(1)只初始化一次GPIO引脚

GPIO_InitTypeDef结构体的定义
在这里插入图片描述
引脚参数的定义
在这里插入图片描述
同一个结构体的引脚参数不同,赋值之后后面的会把前面的覆盖,只写一个GPIO_Init()函数的话,就只会传入覆盖后的参数值。

(2)分别开启时钟参数
我的第一反应是:第二次调用时钟函数RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);时,运行的结果会覆盖掉第一次调用时钟函数RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);的结果。首先我只把第一个时钟的代码:RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);给注释掉了,想着它也会被后者覆盖,然后运行,预计也能正常发送数据。因为我是在知道,先后调用两次时钟函数发现能运行的情况下,做出的预计。然而并不能发送数据。
时钟函数的定义
在这里插入图片描述

时钟函数的第一个参数的定义,本质上是外设,只不过被宏定义成了数而已。
在这里插入图片描述

小结:实际上两个时钟的开启函数和两个GPIOA引脚的void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)函数的内部都是采用了或与运算符进行运算,因此它们的效果可以叠加。甚至可以用同一个 GPIO_InitTypeDef定义的GPIO_InitStructure进行三个参数的配置。 ①再说直白一点就是你同样地一个函数调用的次数越多效果只会越叠加; ②并且同样一个函数传入的参数用 | 连接,与调用很多次每次传入用 | 连接的参数,最后运行的效果是一样的。

2.实验现象

在这里插入图片描述

(二)实验二

采用标准库的查询方式,参考教材7.4.3节中的应用实例三的案例或者网上资料,完成以下要求:
STM32以查询方式接收上位机(win10)串口发来的数据,如果接收到“Y”则点亮链接到stm32上的一个LED灯;接收到“N”则熄灭LED灯。

1.代码部分

(1)完整代码1(不太正确)
#include "stm32f10x.h"                  // Device header
#include "Delay.h"

int main()
{
	//开启GPIOA和USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	//实例化控制器的对象
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	
	//先配置GPIO控制器
	
	//1.GPIO
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	
	
	//2.USART
	//USART_DeInit(USART1); 		//复位串口1

	//2.1设置PA9为复用推挽输出模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置为复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
	GPIO_Init(GPIOA,&GPIO_InitStructure);   //老子草了没写这一句,以为只GPIO_Init()一次就可以了,结果忘了这些参数会覆盖的。
	
	//2.2设置PA10为浮空输入模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置为浮空输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	
	//再配置USART控制器
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //硬件流控制:选择无流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx ; //模式选择发送和接收
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	
	USART_Init(USART1,&USART_InitStructure);  //初始化串口1
	USART_Cmd(USART1,ENABLE);			//使能串口1
	
	
	GPIO_WriteBit(GPIOA,GPIO_Pin_1,1);   //先熄灭LED灯
	char rData = 0;
	while(1)
	{
		
			rData=USART_ReceiveData(USART1);
			
			while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET);//RXNE的N是not,E是empty,表示接收寄存器不符合为空,给它赋值1(SET),说明里面确实不为空,有东西接收到数据了
			if(rData == 'Y')
			{
			GPIO_WriteBit(GPIOA,GPIO_Pin_1,0);  
			}
			else if(rData == 'N')
			{
			GPIO_WriteBit(GPIOA,GPIO_Pin_1,1);
			}
			while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//一定加上这个判断,单片机发送寄存器不为空时,一直循环,直到发送寄存器为空,将数据发送出去。
			//但是我不理解,我是从电脑发数据给单片机,单片机根本没有发送数据,为啥还要加上这个判断
			
	}
	
}

解释一下为什么一定要加上while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);这段代码,作为能否开始下一次接收数据的判断。
STM32的USART在接收和发送上共享同一个数据寄存器。当USART正在处理接收操作(即将接收到的数据移入接收寄存器)时,它需要确保发送寄存器是空闲的,以避免数据混淆或处理冲突。
尽管从电脑向STM32发送数据时,STM32的发送功能并未主动使用,但USART硬件设计要求在进行某些操作前(如读取接收到的新数据),确认发送寄存器当前状态,以保证通信协议的正确执行和内部状态机的正常转换。因此,检查TXE标志(发送数据寄存器为空)是一种同步机制,确保USART内部处理流程的有序进行,即使在只接收不发送的情况下也不例外。
总结来说,即便您当前的应用主要关注接收数据,并不直接涉及数据发送,等待TXE标志置位的循环仍然是USART正常操作流程的一部分,确保USART内部状态管理的准确性,从而维护通信的稳定性与可靠性。
我试了一下,不加这段代码最多执行一次Y,N点亮熄灭小灯。加上了能执行四次左右小灯亮灭,之后才没有效果。

(2)重点:串口发送和接收数据的规律

一下书看到了一个串口发送和接收数据的规律如下:
在这里插入图片描述
串口发送数据:先发送,再判断标志位。
串口接收数据:先判断标志位,再接收。

(3)改进代码2
#include "stm32f10x.h"                  // Device header
#include "Delay.h"

int main()
{
	//开启GPIOA和USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	//实例化控制器的对象
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	
	//先配置GPIO控制器
	
	//1.GPIO
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	
	
	//2.USART
	//USART_DeInit(USART1); 		//复位串口1

	//2.1设置PA9为复用推挽输出模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置为复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
	GPIO_Init(GPIOA,&GPIO_InitStructure);   //老子草了没写这一句,以为只GPIO_Init()一次就可以了,结果忘了这些参数会覆盖的。
	
	//2.2设置PA10为浮空输入模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置为浮空输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	
	//再配置USART控制器
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //硬件流控制:选择无流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx ; //模式选择发送和接收
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	
	USART_Init(USART1,&USART_InitStructure);  //初始化串口1
	USART_Cmd(USART1,ENABLE);			//使能串口1
	
	
	GPIO_WriteBit(GPIOA,GPIO_Pin_1,1);   //先熄灭LED灯
	char rData = 0;
	while(1)
	{
			
			while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET);//RXNE的N是not,E是empty,表示接收寄存器不符合为空,给它赋值1(SET),说明里面确实不为空,有东西接收到数据了
			{
				rData=USART_ReceiveData(USART1);       //先判断接收区不为空,再执行接收。
			
				if(rData == 'Y')
				{
				GPIO_WriteBit(GPIOA,GPIO_Pin_1,0);  
				}
				else if(rData == 'N')
				{
				GPIO_WriteBit(GPIOA,GPIO_Pin_1,1);
				}
			
			}
	}
}

分析错误
在这里插入图片描述
两种改进方法

(4)正确代码1

while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET);中的SET改为RESET,while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET);让接收区为空的时候,执行空语句,直到接收区不为空,才执行下面的接收数据和判断字符’Y’、'N’的语句。
完整代码为:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

int main()
{
	//开启GPIOA和USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	//实例化控制器的对象
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	
	//先配置GPIO控制器
	
	//1.GPIO
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	
	
	//2.USART
	//USART_DeInit(USART1); 		//复位串口1

	//2.1设置PA9为复用推挽输出模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置为复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
	GPIO_Init(GPIOA,&GPIO_InitStructure);   //老子草了没写这一句,以为只GPIO_Init()一次就可以了,结果忘了这些参数会覆盖的。
	
	//2.2设置PA10为浮空输入模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置为浮空输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	
	//再配置USART控制器
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //硬件流控制:选择无流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx ; //模式选择发送和接收
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	
	USART_Init(USART1,&USART_InitStructure);  //初始化串口1
	USART_Cmd(USART1,ENABLE);			//使能串口1
	
	
	GPIO_WriteBit(GPIOA,GPIO_Pin_1,1);   //先熄灭LED灯
	char rData = 0;
	while(1)
	{
			
		     //正确代码
			while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET);//RXNE的N是not,E是empty,表示接收寄存器不符合为空,给它赋值1(SET),说明里面确实不为空,有东西接收到数据了
			{
				rData=USART_ReceiveData(USART1);       //先判断接收区不为空,再执行接收。
			
				if(rData == 'Y')
				{
				GPIO_WriteBit(GPIOA,GPIO_Pin_1,0);  
				}
				else if(rData == 'N')
				{
				GPIO_WriteBit(GPIOA,GPIO_Pin_1,1);
				}
			
			}
	}
}

(5)正确代码2

while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET);中的" ; "去掉,while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET),即接收区不为空时,才执行下面的接收数据和判断字符’Y’、'N’的语句。
完整代码为:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

int main()
{
	//开启GPIOA和USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	//实例化控制器的对象
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	
	//先配置GPIO控制器
	
	//1.GPIO
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	
	
	//2.USART
	//USART_DeInit(USART1); 		//复位串口1

	//2.1设置PA9为复用推挽输出模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置为复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
	GPIO_Init(GPIOA,&GPIO_InitStructure);   //老子草了没写这一句,以为只GPIO_Init()一次就可以了,结果忘了这些参数会覆盖的。
	
	//2.2设置PA10为浮空输入模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置为浮空输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	
	//再配置USART控制器
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //硬件流控制:选择无流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx ; //模式选择发送和接收
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	
	USART_Init(USART1,&USART_InitStructure);  //初始化串口1
	USART_Cmd(USART1,ENABLE);			//使能串口1
	
	
	GPIO_WriteBit(GPIOA,GPIO_Pin_1,1);   //先熄灭LED灯
	char rData = 0;
	while(1)
	{
			
		     //正确代码
			while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET)//RXNE的N是not,E是empty,表示接收寄存器不符合为空,给它赋值1(SET),说明里面确实不为空,有东西接收到数据了
			{
				rData=USART_ReceiveData(USART1);       //先判断接收区不为空,再执行接收。
			
				if(rData == 'Y')
				{
				GPIO_WriteBit(GPIOA,GPIO_Pin_1,0);  
				}
				else if(rData == 'N')
				{
				GPIO_WriteBit(GPIOA,GPIO_Pin_1,1);
				}
			
			}
	}
}

(6)小结

通过(4)和(5),我发现似乎也并不需要接收数据时,似乎也可以并不需要判断发送数据寄存器是否为空。

2.实验现象

20240519

(三)Keil波形分析

还是没有波形

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值