22.5.2-多任务创建和调度 FreeRTOS 项目 FreeRTOS学习笔记 FreeRTOS移植 FreeRTOS开源项 目 FreeRTOS面试题汇总FreeRTOS项目实战F103

这个是全网最详细的STM32项目教学视频。
第一篇优快云文章在这里在这里:
75+集视频合集在这里:

https://www.bilibili.com/video/BV16x4y1M7EN/?share_source=copy_web&vd_source=f5d5850ab773377dff308188468fbc77

STM32智能小车V3-STM32入门教程-openmv与STM32循迹小车-stm32f103c8t6-电赛 嵌入式学习 PID控制算法 编码器电机 跟随)]
V3:HAL库开发、手把手教学下面功能:PID速度控制、PID循迹、PID跟随、遥控、避障、PID角度控制、openmv视觉控制、电磁循迹、FreeRTOS、K210视觉智能车(更新中)、K230视觉智能车(更新中)、MSPM0G3507视觉智能车(更新中)
在这里插入图片描述

22.5.2-多任务创建和调度(有示例代码)

创建多任务时候要注意防止 低优先级任务被"饿死"情况,要让高优先级任务主动进入阻塞态才能防止低优先级任务被饿死。

这里我们继续新建一个任务用于执行裸机时候1 2 3 4 5 6 模式的代码
在这里插入图片描述
复制一下22-3_LED_FreeRTOS的工程,命名为22-5_LED_FreeRTOS

然后打开LED.ioc
在这里插入图片描述

Task Name:MultiModeTask 多模式任务的意思

Priority:osPriorityAboveNormal 优先级设置的高于正常优先级

Stack Size (Words):128 这个任务栈 先按照默认

Entry Function:StartMultiModTask 这是任务的函数名字

在这里插入图片描述
把模式 1和2和3 4 5 6 的代码都是复制到任务函数里面

复制到这里

在这里插入图片描述
下面就是复制后的

/* USER CODE BEGIN Header_StartMultiModTask */
/**
* @brief Function implementing the MultiModeTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartMultiModTask */
void StartMultiModTask(void const * argument)
{
  /* USER CODE BEGIN StartMultiModTask */
  /* Infinite loop */
  for(;;)
  {
		if(g_ucMode == 1)
		{
		///****    红外PID循迹功能******************/
		g_ucaHW_Read[0] = READ_HW_OUT_1;//读取红外对管状态、这样相比于写在if里面更高效
		g_ucaHW_Read[1] = READ_HW_OUT_2;
		g_ucaHW_Read[2] = READ_HW_OUT_3;
		g_ucaHW_Read[3] = READ_HW_OUT_4;

		if(g_ucaHW_Read[0] == 0&&g_ucaHW_Read[1] == 0&&g_ucaHW_Read[2] == 0&&g_ucaHW_Read[3] == 0 )
		{
	//		printf("应该前进\r\n");//注释掉更加高效,减少无必要程序执行
			g_cThisState = 0;//前进
		}
		else if(g_ucaHW_Read[0] == 0&&g_ucaHW_Read[1] == 1&&g_ucaHW_Read[2] == 0&&g_ucaHW_Read[3] == 0 )//使用else if更加合理高效
		{
	//		printf("应该右转\r\n");
			g_cThisState = -1;//应该右转
		}
		else if(g_ucaHW_Read[0] == 1&&g_ucaHW_Read[1] == 0&&g_ucaHW_Read[2] == 0&&g_ucaHW_Read[3] == 0 )
		{
	//		printf("快速右转\r\n");
			g_cThisState = -2;//快速右转
		}
		else if(g_ucaHW_Read[0] == 1&&g_ucaHW_Read[1] == 1&&g_ucaHW_Read[2] == 0&&g_ucaHW_Read[3] == 0)
		{
	//		printf("快速右转\r\n");
			g_cThisState = -3;//快速右转
		}
		else if(g_ucaHW_Read[0] == 0&&g_ucaHW_Read[1] == 0&&g_ucaHW_Read[2] == 1&&g_ucaHW_Read[3] == 0 )
		{
	//		printf("应该左转\r\n");
			g_cThisState = 1;//应该左转	
		}
		else if(g_ucaHW_Read[0] == 0&&g_ucaHW_Read[1] == 0&&g_ucaHW_Read[2] == 0&&g_ucaHW_Read[3] == 1 )
		{
	//		printf("快速左转\r\n");
			g_cThisState = 2;//快速左转
		}
		else if(g_ucaHW_Read[0] == 0&&g_ucaHW_Read[1] == 0&&g_ucaHW_Read[2] == 1&&g_ucaHW_Read[3] == 1)
		{
	//	    printf("快速左转\r\n");
			g_cThisState = 3;//快速左转
		}
		g_fHW_PID_Out = PID_realize(&pidHW_Tracking,g_cThisState);//PID计算输出目标速度 这个速度,会和基础速度加减

		g_fHW_PID_Out1 = 3 + g_fHW_PID_Out;//电机1速度=基础速度+循迹PID输出速度
		g_fHW_PID_Out2 = 3 - g_fHW_PID_Out;//电机1速度=基础速度-循迹PID输出速度
		if(g_fHW_PID_Out1 >5) g_fHW_PID_Out1 =5;//进行限幅 限幅速度在0-5之间
		if(g_fHW_PID_Out1 <0) g_fHW_PID_Out1 =0;
		if(g_fHW_PID_Out2 >5) g_fHW_PID_Out2 =5;//进行限幅 限幅速度在0-5之间
		if(g_fHW_PID_Out2 <0) g_fHW_PID_Out2 =0;
		if(g_cThisState != g_cLastState)//如何这次状态不等于上次状态、就进行改变目标速度和控制电机、在定时器中依旧定时控制电机
		{
			motorPidSetSpeed(g_fHW_PID_Out1,g_fHW_PID_Out2);//通过计算的速度控制电机
		}
		
		g_cLastState = g_cThisState;//保存上次红外对管状态	

		}
		if(g_ucMode == 2)
		{
			
			//***************遥控模式***********************//
			//遥控模式的控制在串口三的中断里面
		}
		if(g_ucMode == 3)
		{
			//******超声波避障模式*********************//
	避障逻辑
			if(HC_SR04_Read() > 25)//前方无障碍物
			{
				motorPidSetSpeed(1,1);//前运动
				HAL_Delay(100);
			}
			else{	//前方有障碍物
				motorPidSetSpeed(-1,1);//右边运动 原地	
				HAL_Delay(500);
				if(HC_SR04_Read() > 25)//右边无障碍物
				{
					motorPidSetSpeed(1,1);//前运动
					HAL_Delay(100);
				}
				else{//右边有障碍物
					motorPidSetSpeed(1,-1);//左边运动 原地
					HAL_Delay(1000);
					if(HC_SR04_Read() >25)//左边无障碍物
					{
						 motorPidSetSpeed(1,1);//前运动
						HAL_Delay(100);
					}
					else{
						motorPidSetSpeed(-1,-1);//后运动
						HAL_Delay(1000);
						motorPidSetSpeed(-1,1);//右边运动
						HAL_Delay(50);
					}
				}
			}
		}
		if(g_ucMode == 4)
		{
		//**********PID跟随功能***********//
			g_fHC_SR04_Read=HC_SR04_Read();//读取前方障碍物距离
			if(g_fHC_SR04_Read < 60){  //如果前60cm 有东西就启动跟随
				g_fFollow_PID_Out = PID_realize(&pidFollow,g_fHC_SR04_Read);//PID计算输出目标速度 这个速度,会和基础速度加减
				if(g_fFollow_PID_Out > 6) g_fFollow_PID_Out = 6;//对输出速度限幅
				if(g_fFollow_PID_Out < -6) g_fFollow_PID_Out = -6;
				motorPidSetSpeed(g_fFollow_PID_Out,g_fFollow_PID_Out);//速度作用与电机上
			}
			else motorPidSetSpeed(0,0);//如果前面60cm 没有东西就停止
			HAL_Delay(10);//读取超声波传感器不能过快
		}
		if(g_ucMode == 5)
		{
		//*************MPU6050航向角 PID转向控制*****************//

			sprintf((char *)Usart3String,"pitch:%.2f roll:%.2f yaw:%.2f\r\n",pitch,roll,yaw);//显示6050数据 俯仰角 横滚角 航向角
			HAL_UART_Transmit(&huart3,( uint8_t *)Usart3String,strlen(( const  char  *)Usart3String),0xFFFF);//通过串口三输出字符 strlen:计算字符串大小	
			 
	//	    mpu_dmp_get_data(&pitch,&roll,&yaw);//返回值:0,DMP成功解出欧拉角
	//		while(mpu_dmp_get_data(&pitch,&roll,&yaw)!=0){}  //这个可以解决经常读不出数据的问题
			
			
			g_fMPU6050YawMovePidOut = PID_realize(&pidMPU6050YawMovement,yaw);//PID计算输出目标速度 这个速度,会和基础速度加减

			g_fMPU6050YawMovePidOut1 = 1.5 + g_fMPU6050YawMovePidOut;//基础速度加减PID输出速度
			g_fMPU6050YawMovePidOut2 = 1.5 - g_fMPU6050YawMovePidOut;
			if(g_fMPU6050YawMovePidOut1 >3.5) g_fMPU6050YawMovePidOut1 =3.5;//进行限幅
			if(g_fMPU6050YawMovePidOut1 <0) g_fMPU6050YawMovePidOut1 =0;
			if(g_fMPU6050YawMovePidOut2 >3.5) g_fMPU6050YawMovePidOut2 =3.5;//进行限幅
			if(g_fMPU6050YawMovePidOut2 <0) g_fMPU6050YawMovePidOut2 =0;
			motorPidSetSpeed(g_fMPU6050YawMovePidOut1,g_fMPU6050YawMovePidOut2);//将最后计算的目标速度 通过motorPidSetSpeed控制电机
		
		}
		if(g_ucMode == 6)
		{

			sprintf((char*)OledString, "lHW:%d  ", g_lHW_State);//视觉识别结果
			OLED_ShowString(0,0,OledString,12);//这个是oled驱动里面的,是显示位置的一个函数,
			
			g_fHW_PID_Out = PID_realize(&pidOpenmv_Tracking,g_cThisState);//PID计算输出目标速度 这个速度,会和基础速度加减

			g_fHW_PID_Out1 = 0.5 + g_fHW_PID_Out;//电机1速度=基础速度+循迹PID输出速度
			g_fHW_PID_Out2 = 0.5 - g_fHW_PID_Out;//电机1速度=基础速度-循迹PID输出速度
			if(g_fHW_PID_Out1 >1.2) g_fHW_PID_Out1 =1.2;//进行限幅 限幅速度在0-1.2之间
			if(g_fHW_PID_Out1 <0) g_fHW_PID_Out1 =0;
			if(g_fHW_PID_Out2 >1.2) g_fHW_PID_Out2 =1.2;//进行限幅 限幅速度在0-1.2之间
			if(g_fHW_PID_Out2 <0) g_fHW_PID_Out2 =0;
			if(g_cThisState != g_cLastState)//如何这次状态不等于上次状态、就进行改变目标速度和控制电机、在定时器中依旧定时控制电机
			{
				motorPidSetSpeed(g_fHW_PID_Out1,g_fHW_PID_Out2);//通过计算的速度控制电机
			}
			
			g_cLastState = g_cThisState;//保存上次红外对管状态	

		}
    osDelay(1);
  }
  /* USER CODE END StartMultiModTask */
}

这个osDelay(1)要带着,因为只有高优先级任务阻塞或者挂起状态,低优先级任务才有机会运行。
在这里插入图片描述
并且为了我们方便查看调试任务是否执行和剩余任务栈空间 我们增加调试信息

在这里插入图片描述

		UBaseType_t stackHighWaterMark = uxTaskGetStackHighWaterMark(NULL);// 获取当前任务的栈高水位值
		printf("StartMultiModTask Mark: %u words\n", (unsigned int)stackHighWaterMark);
		size_t freeHeapSize = xPortGetFreeHeapSize();// 获取系统的可用堆空间
		printf("StartMultiModTask Free Heap Size: %u bytes\n", (unsigned int)freeHeapSize);

然后我们需要修改之前写的 几个高水位值的输出
在这里插入图片描述
然后连接DAP进行烧录,烧录教程见:第二章-GPIO与中断 ->2.1-点灯-烧录程序->方法一内容

烧录后,复位,小灯闪烁

然后连接DAP串口,串口连接和设置教程见::第四章-串口实验(简单输出)->4.2串口实验->接线图->使用dap

然后可以关闭串口看一下输出的结果

仔细观察一下输出结果可以找到四个任务都有输出执行。

前面我们 输出的时候 把对应函数名字记得改一下这样方便查看每个变量

然后我们通过串口就可用观察出来几个任务是否在执行,这样会十分方便我们在FreeRTOS中调试
在这里插入图片描述

但是我们发现OLED屏幕上没有显示当前模式,我们从之前的main.c中把当前模式显示的代码找到,增加到OLED显示的任务中
在这里插入图片描述

		sprintf((char *)OledString," g_ucMode:%d",g_ucMode);//显示g_ucMode 当前模式
		OLED_ShowString(0,6,OledString,12);	//显示在OLED上
	
		sprintf((char *)Usart3String," g_ucMode:%d",g_ucMode);//蓝牙APP显示
		HAL_UART_Transmit(&huart3,( uint8_t *)Usart3String,strlen(( const  char  *)Usart3String),50);//阻塞式发送通过串口三输出字符 strlen:计算字符串大小

然后我们按下按键key1 发现屏幕模式更新的非常慢,可能是因为每次变化模式后 StartMultiModTask任务函数里面的,模式1到6中的函数就会有一部分被执行里面调用的无法释放CPU使用权的HAL_Delay 所以我们这里把StartMultiModTask 函数中的HAL_Delay 都换成osDelay

然后StartMultiModTask任务就会变成一下内容:

/* USER CODE BEGIN Header_StartMultiModTask */
/**
* @brief Function implementing the MultiModeTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartMultiModTask */
void StartMultiModTask(void const * argument)
{
  /* USER CODE BEGIN StartMultiModTask */
  /* Infinite loop */
  for(;;)
  {
		UBaseType_t stackHighWaterMark = uxTaskGetStackHighWaterMark(NULL);// 获取当前任务的栈高水位值
		printf("StartMultiModTask Mark: %u words\n", (unsigned int)stackHighWaterMark);
		size_t freeHeapSize = xPortGetFreeHeapSize();// 获取系统的可用堆空间
		printf("StartMultiModTask Free Heap Size: %u bytes\n", (unsigned int)freeHeapSize);
		
		if(g_ucMode == 1)
		{
		///****    红外PID循迹功能******************/
		g_ucaHW_Read[0] = READ_HW_OUT_1;//读取红外对管状态、这样相比于写在if里面更高效
		g_ucaHW_Read[1] = READ_HW_OUT_2;
		g_ucaHW_Read[2] = READ_HW_OUT_3;
		g_ucaHW_Read[3] = READ_HW_OUT_4;

		if(g_ucaHW_Read[0] == 0&&g_ucaHW_Read[1] == 0&&g_ucaHW_Read[2] == 0&&g_ucaHW_Read[3] == 0 )
		{
	//		printf("应该前进\r\n");//注释掉更加高效,减少无必要程序执行
			g_cThisState = 0;//前进
		}
		else if(g_ucaHW_Read[0] == 0&&g_ucaHW_Read[1] == 1&&g_ucaHW_Read[2] == 0&&g_ucaHW_Read[3] == 0 )//使用else if更加合理高效
		{
	//		printf("应该右转\r\n");
			g_cThisState = -1;//应该右转
		}
		else if(g_ucaHW_Read[0] == 1&&g_ucaHW_Read[1] == 0&&g_ucaHW_Read[2] == 0&&g_ucaHW_Read[3] == 0 )
		{
	//		printf("快速右转\r\n");
			g_cThisState = -2;//快速右转
		}
		else if(g_ucaHW_Read[0] == 1&&g_ucaHW_Read[1] == 1&&g_ucaHW_Read[2] == 0&&g_ucaHW_Read[3] == 0)
		{
	//		printf("快速右转\r\n");
			g_cThisState = -3;//快速右转
		}
		else if(g_ucaHW_Read[0] == 0&&g_ucaHW_Read[1] == 0&&g_ucaHW_Read[2] == 1&&g_ucaHW_Read[3] == 0 )
		{
	//		printf("应该左转\r\n");
			g_cThisState = 1;//应该左转	
		}
		else if(g_ucaHW_Read[0] == 0&&g_ucaHW_Read[1] == 0&&g_ucaHW_Read[2] == 0&&g_ucaHW_Read[3] == 1 )
		{
	//		printf("快速左转\r\n");
			g_cThisState = 2;//快速左转
		}
		else if(g_ucaHW_Read[0] == 0&&g_ucaHW_Read[1] == 0&&g_ucaHW_Read[2] == 1&&g_ucaHW_Read[3] == 1)
		{
	//	    printf("快速左转\r\n");
			g_cThisState = 3;//快速左转
		}
		g_fHW_PID_Out = PID_realize(&pidHW_Tracking,g_cThisState);//PID计算输出目标速度 这个速度,会和基础速度加减

		g_fHW_PID_Out1 = 3 + g_fHW_PID_Out;//电机1速度=基础速度+循迹PID输出速度
		g_fHW_PID_Out2 = 3 - g_fHW_PID_Out;//电机1速度=基础速度-循迹PID输出速度
		if(g_fHW_PID_Out1 >5) g_fHW_PID_Out1 =5;//进行限幅 限幅速度在0-5之间
		if(g_fHW_PID_Out1 <0) g_fHW_PID_Out1 =0;
		if(g_fHW_PID_Out2 >5) g_fHW_PID_Out2 =5;//进行限幅 限幅速度在0-5之间
		if(g_fHW_PID_Out2 <0) g_fHW_PID_Out2 =0;
		if(g_cThisState != g_cLastState)//如何这次状态不等于上次状态、就进行改变目标速度和控制电机、在定时器中依旧定时控制电机
		{
			motorPidSetSpeed(g_fHW_PID_Out1,g_fHW_PID_Out2);//通过计算的速度控制电机
		}
		
		g_cLastState = g_cThisState;//保存上次红外对管状态	

		}
		if(g_ucMode == 2)
		{
			
			//***************遥控模式***********************//
			//遥控模式的控制在串口三的中断里面
		}
		if(g_ucMode == 3)
		{
			//******超声波避障模式*********************//
	避障逻辑
			if(HC_SR04_Read() > 25)//前方无障碍物
			{
				motorPidSetSpeed(1,1);//前运动
				osDelay(100);
			}
			else{	//前方有障碍物
				motorPidSetSpeed(-1,1);//右边运动 原地	
				osDelay(500);
				if(HC_SR04_Read() > 25)//右边无障碍物
				{
					motorPidSetSpeed(1,1);//前运动
					osDelay(100);
				}
				else{//右边有障碍物
					motorPidSetSpeed(1,-1);//左边运动 原地
					osDelay(1000);
					if(HC_SR04_Read() >25)//左边无障碍物
					{
						 motorPidSetSpeed(1,1);//前运动
						osDelay(100);
					}
					else{
						motorPidSetSpeed(-1,-1);//后运动
						osDelay(1000);
						motorPidSetSpeed(-1,1);//右边运动
						osDelay(50);
					}
				}
			}
		}
		if(g_ucMode == 4)
		{
		//**********PID跟随功能***********//
			g_fHC_SR04_Read=HC_SR04_Read();//读取前方障碍物距离
			if(g_fHC_SR04_Read < 60){  //如果前60cm 有东西就启动跟随
				g_fFollow_PID_Out = PID_realize(&pidFollow,g_fHC_SR04_Read);//PID计算输出目标速度 这个速度,会和基础速度加减
				if(g_fFollow_PID_Out > 6) g_fFollow_PID_Out = 6;//对输出速度限幅
				if(g_fFollow_PID_Out < -6) g_fFollow_PID_Out = -6;
				motorPidSetSpeed(g_fFollow_PID_Out,g_fFollow_PID_Out);//速度作用与电机上
			}
			else motorPidSetSpeed(0,0);//如果前面60cm 没有东西就停止
			osDelay(10);//读取超声波传感器不能过快
		}
		if(g_ucMode == 5)
		{
		//*************MPU6050航向角 PID转向控制*****************//

			sprintf((char *)Usart3String,"pitch:%.2f roll:%.2f yaw:%.2f\r\n",pitch,roll,yaw);//显示6050数据 俯仰角 横滚角 航向角
			HAL_UART_Transmit(&huart3,( uint8_t *)Usart3String,strlen(( const  char  *)Usart3String),0xFFFF);//通过串口三输出字符 strlen:计算字符串大小	
			 
	//	    mpu_dmp_get_data(&pitch,&roll,&yaw);//返回值:0,DMP成功解出欧拉角
	//		while(mpu_dmp_get_data(&pitch,&roll,&yaw)!=0){}  //这个可以解决经常读不出数据的问题
			
			
			g_fMPU6050YawMovePidOut = PID_realize(&pidMPU6050YawMovement,yaw);//PID计算输出目标速度 这个速度,会和基础速度加减

			g_fMPU6050YawMovePidOut1 = 1.5 + g_fMPU6050YawMovePidOut;//基础速度加减PID输出速度
			g_fMPU6050YawMovePidOut2 = 1.5 - g_fMPU6050YawMovePidOut;
			if(g_fMPU6050YawMovePidOut1 >3.5) g_fMPU6050YawMovePidOut1 =3.5;//进行限幅
			if(g_fMPU6050YawMovePidOut1 <0) g_fMPU6050YawMovePidOut1 =0;
			if(g_fMPU6050YawMovePidOut2 >3.5) g_fMPU6050YawMovePidOut2 =3.5;//进行限幅
			if(g_fMPU6050YawMovePidOut2 <0) g_fMPU6050YawMovePidOut2 =0;
			motorPidSetSpeed(g_fMPU6050YawMovePidOut1,g_fMPU6050YawMovePidOut2);//将最后计算的目标速度 通过motorPidSetSpeed控制电机
		
		}
		if(g_ucMode == 6)
		{

			sprintf((char*)OledString, "lHW:%d  ", g_lHW_State);//视觉识别结果
			OLED_ShowString(0,0,OledString,12);//这个是oled驱动里面的,是显示位置的一个函数,
			
			g_fHW_PID_Out = PID_realize(&pidOpenmv_Tracking,g_cThisState);//PID计算输出目标速度 这个速度,会和基础速度加减

			g_fHW_PID_Out1 = 0.5 + g_fHW_PID_Out;//电机1速度=基础速度+循迹PID输出速度
			g_fHW_PID_Out2 = 0.5 - g_fHW_PID_Out;//电机1速度=基础速度-循迹PID输出速度
			if(g_fHW_PID_Out1 >1.2) g_fHW_PID_Out1 =1.2;//进行限幅 限幅速度在0-1.2之间
			if(g_fHW_PID_Out1 <0) g_fHW_PID_Out1 =0;
			if(g_fHW_PID_Out2 >1.2) g_fHW_PID_Out2 =1.2;//进行限幅 限幅速度在0-1.2之间
			if(g_fHW_PID_Out2 <0) g_fHW_PID_Out2 =0;
			if(g_cThisState != g_cLastState)//如何这次状态不等于上次状态、就进行改变目标速度和控制电机、在定时器中依旧定时控制电机
			{
				motorPidSetSpeed(g_fHW_PID_Out1,g_fHW_PID_Out2);//通过计算的速度控制电机
			}
			
			g_cLastState = g_cThisState;//保存上次红外对管状态	

		}
    osDelay(1);
  }
  /* USER CODE END StartMultiModTask */
}

然后按KEY1 观察当前模式显示发现已经快很多,但是还是感觉有点慢,我们提高一下OLED显示任务的优先级
在这里插入图片描述
设置成正常优先级,然后重新生成代码
在这里插入图片描述
编译烧录测试一下,这次的屏幕刷新效率会比之前高一下些。

这样就完成了四个任务较为合理的调度,下面章节完成,任务中完善内容,先用全局变量 实现之前裸机的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

code_未来

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

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

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

打赏作者

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

抵扣说明:

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

余额充值