【蓝桥杯单片机-END】看完这一篇就够了!!!

本文是蓝桥杯备赛的总结,重点讲解了外设如蜂鸣器、LED、数码管、DS1302、AT24C04、PCF8591和DS18B20的使用,以及中断初始化和按键处理。提供了详细的注意事项和函数使用技巧,包括中断函数的设计原则和数码管显示的方法。最后提到了总设计篇的四大板块:按键处理、显示处理、数据处理和中断函数,强调了中断函数在系统中的核心地位。

致谢订阅专栏的朋友

外设篇

1、关闭蜂鸣器、led等外设

//初始化函数,关闭蜂鸣器继电器、led灯
void sys_init()
{
   P2=P2&0X1F|0XA0;	//打开Y5C
   P0=0X00;	 //关闭蜂鸣器继电器
   P2=P2&0X1F; //关闭Y5C

   P2=P2&0X1F|0X80;	//打开Y4C
   P0=0Xff;	 //关闭led灯
   P2=P2&0X1F; //关闭Y4C
}

注意事项

蜂鸣器继电器是高电平开启,而LED灯是低电平开启
Y4C和Y5C不要混淆。一切看着原理图敲代码。不要认为自己背的住。如果写错还要二次调试。一样耽搁时间

2、中断的初始化与中断函数

//定时器0定时初始化
void Timer0Init(void)		//1毫秒@12.000MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0=1;//不可忘记
}
//定时器0计数初始化
void Counter0(void)
{
  AUXR &= 0x7F;		//定时器时钟12T模式
  TMOD=TMOD&0XF0|0X05;
  TL0=0;
  TH0=0;
  TF0=0;
  TR0=1;
}
//定时器1初始化
void Timer1Init(void)		//1毫秒@12.000MHz
{
	AUXR &= 0xBF;		//定时器时钟12T模式
	TMOD &= 0x0F;		//设置定时器模式
	TL1 = 0x18;		//设置定时初值
	TH1 = 0xFC;		//设置定时初值
	TF1 = 0;		//清除TF1标志
	TR1 = 1;		//定时器1开始计时
	ET1=1;//不可忘记
}
外部中断0初始化,这里举例下降沿触发
void Int0Init()
{
  EX0=1;//允许Int0中断
  IT0=1;//下降沿触发,即按下瞬间触发
}
外部中断1初始化,这里举例低电平触发
void Int1Init()
{
  EX1=1;//允许Int0中断
  IT1=0;//低电平触发,即按下持续期间触发
}
//中断号,外部中断0是0,定时器0是1,外部中断1是2,定时器1是3。如果忘记查阅手册
//主函数要写EA=1;开启总中断

注意事项:

1、定时器频率一定要选正确。12MHZ的误差是0,并且符合题目要求。
2、定时器最好选用12T模式,一次计数表示1us,
3、ET0、ET1、EA不要忘记。
4、记得主函数要调用初始化函数。
5、中断interrupt忘记怎么敲不要着急,范例程序有。
6、外部中断和按键有冲突,注意区分。
7、定时器0的计数引脚是P34。用作NE555频率读取,要注意此时矩阵按键后两列将失效不准确。
8、定时器0用作计数模式时,不要开启ET0中断。并且读取完TH0、TL0后要重装初值。
9、中断函数里面的计数变量要注意定义的类型。如果是unsigned char,那最大值是255.如果是unsigned int,那最大值是65536。并且在使用后一定要清零。否则只能使用一次。
10、中断函数最好只用来定时、PWM输出、数码管显示、led显示。不要用来读取温度以及其他外设。

3、数码管显示

/********************** 将字符串转化为相应的段码数组 ************************/
void Conversion(u8 *buf,u8 *duanma)
{
  u8 i,j=0,temp;
  for(i=0;i<8;i++,j++)
  {
    switch(buf[j])
	{
	  //根据要显示的字符获取共阳极数码管编码
			case '0': temp = 0xc0; break;
			case '1': temp = 0xf9; break;
			case '2': temp = 0xa4; break;
			case '3': temp = 0xb0; break;
			case '4': temp = 0x99; break;
			case '5': temp = 0x92; break;
			case '6': temp = 0x82; break;
			case '7': temp = 0xf8; break;
			case '8': temp = 0x80; break;
			case '9': temp = 0x90; break;
			case 'A': temp = 0x88; break;
			case 'B': temp = 0x83; break;
			case 'C': temp = 0xc6; break;
			case 'D': temp = 0xA1; break;
			case 'E': temp = 0x86; break;
			case 'F': temp = 0x8E; break;
			case 'H': temp = 0x89; break;
			case 'L': temp = 0xC7; break;
			case 'N': temp = 0xC8; break;
			case 'P': temp = 0x8c; break;
			case 'U': temp = 0xC1; break;
			case '-': temp = 0xbf; break;
			case ' ': temp = 0xff; break;
			default: temp = 0xff;
	}
	if(buf[j+1]=='.')//如果数组的下一位是小数点,就将它与上一位合并
	{
	  temp = temp&0x7f;
	  j++;
	}
	duanma[i] = temp;
  }
}

/********************** 一位显示函数 ************************/
void display(u8 *duanma,u8 pos)
{
  //消隐
  P0=0Xff;	 //段选全灭
  P2=P2&0X1F|0XE0;	//打开Y7C
  P2=P2&0X1F; //关闭Y7C

  //送位选
  P0=0X01<<pos;	 //选择位置
  P2=P2&0X1F|0XC0;	//打开Y7C
  P2=P2&0X1F; //关闭Y7C

  //送段码
  P0=duanma[pos];	 //段选全灭
  P2=P2&0X1F|0XE0;	//打开Y7C
  P2=P2&0X1F; //关闭Y7C
}

注意事项

1、buf数组是存放待显示字符串的,里面不是段码值。最好定义为10位,防止不能完全装载字符串,比如数组里面有两个小数点。
2、duanma数组是存放待显示字符串的段码的,因为数码管就只有8位,所以定位位8位就可以了。
3、在获得字符串的时候,sprintf函数是在stdio.h库中,因此要记得#include <stdio.h>,
%8bu,表示待显示变量是unsigned char类型的,宽度八位,不足八位补空格。%08bu表示不足八位补0。
%8d表示待显示变量是int类型。宽度为8位,不足8位补空格。%08d表示不足八位补0。
%8.2f表示待显示变量是float类型。宽度为8位,保留两位小数。不足8位补空格。%08.2f表示不足八位补0。
4、将字符串转化为段码数组时,记得i,j,temp;三个变量的区别。i是用来for循环八次,并将temp值装进duanma数组。j是用来switch挨个判断当前位的段码值是多少,以及下一位j+1是不是小数点。如果是temp还需要&0x7f。并且j++;
5、一位字符显示这个函数,要记得段码消隐。并且P0要写在P2之前。也就是说。在你允许P0数据更新之前,就要准备好P0的值。否则会导致数码管亮度不均匀。

4、按键函数

//独立按键
unsigned char button(void)
{
  	static unsigned char value,count=0;
	unsigned char key_press;
	static bit flag;
	key_press=P3&0X0F;
	if(	key_press!=0X0F){count++;}else count=0;

	if(count==2)
	{
	  flag=1;
	  count=0;
	  if(P30==0)value=7;
	  if(P31==0)value=6;
	  if(P32==0)value=5;
	  if(P33==0)value=4;
	}

	if(key_press==0X0F&&flag==1)
	{
	  flag=0;
	  return value;
	}
	return 0;
}
//矩阵按键
unsigned char readkbd()//矩阵按键读取函数
{
  static unsigned char state=0;//state=0,进入第一次扫描;state=1;进入第二次扫描
  static unsigned char value;//按键返回值,直到按键释放后返回该值
  static unsigned char hang;//第一次获得哪一行?
  switch(state)
  {
    //进入第一次检测,检测哪一行被按下
    case 0:
	      P3=0X0F;P42=0;P44=0;//按键的行值置1,列值置0;
		  if(P3!=0X0F)//如果按键值改变了,就判断是哪一行
		  {
		    if(P30==0){hang=1;state=1;}//第一行
			if(P31==0){hang=2;state=1;}//第二行
			if(P32==0){hang=3;state=1;}//第三行
			if(P33==0){hang=4;state=1;}//第四行
		  }else state=0;//无按键按下,停在第一次检测
		  break;
	//第二次检测,检测哪一列被按下
	case 1:
	      P3 = 0XF0;P42=1;P44=1;//列值置1,行值置0
		 if(P44==0)value=(4-hang)+4;//第一列四行的值,7 6 5 4
         if(P42==0)value=(8-hang)+4;//第二列四行的值,8 9 10 11
         if(P35==0)value=(12-hang)+4;//第三列四行的值,12 13 14 15
         if(P34==0)value=(16-hang)+4;//第四列四行的值,16 17 18 19
         break;
  }
  //等待按键释放
   P3=0X0F;P42=0;P44=0;
  if(state==1&&P3==0X0F)
  {
    state =0;
	return value;
  }
  return 0;
}

独立按键注意事项

1、独立按键中返回值、计数值、非抖动标志是static的,而按键状态key_press不是static的。
2、判断是否按下时,如果按下,count要加一,如果没有按下,count要清零。当count=2,要将非抖动标志置1表示人为按下了。并且count要清零,以便下一次判断。
3、返回按键值的前提是按键松开。也就是key_press=0x0f并且非抖动标志flag=1。返回按键值后,flag要清零。
4、如果题目要求按住某个独立按键时间长一秒才能响应该按键,我们可以if(count==100)或者1000ms运行一次独立按键函数。
5、如果题目要求按住某个独立按键才处理某函数,否则停止处理。那我们可以单独处理该按键。比如S4按下执行温度读取函数,如下是具体思路

sbit S4=P3^3;
if(key_flag)//key_flag在定时器中断中控制。每10ms执行一次
{
  key_flag=0;
  if(S4==0){temp_flag=1;}//每10ms判断一次S4的值,如果处于低电平状态,temp_flag=1;
  else temp_flag=0;
}
//当temp_flag=1;执行温度读取函数
if(temp_flag){readtemp();}

矩阵按键注意事项

1、变量全部是是static。一共三个,按键返回值value,按键当前状态state,行值hang。
2、state=0,表示第一次监测,如果监测到按键按下。那么state要等于1,表示进入第二次监测。同时要读取hang值。如果没有监测到按键按下,那么要停在当前状态。直到有键按下。
3、state=1,表示第二次监测,如果监测到按键按下,那么要根据hang值和当前列值得到value。
4、等待按键释放, P3=0X0F;P42=0;P44=0;一定要用这句话,通过判断4行按键有没有变化,来确定是否松开按键。只有松开按键才返回value,并且state状态要清零,方便下一次监测。

共同注意事项

1、中断函数每隔10ms将key_flag置1,主函数检测到key_flag=1后开始执行按键读取函数,并且进行相应的按键处理。同时key_flag清零;

5、DS1302读取&写入函数

/****底层驱动函数略过****/
//读取函数
void ReadTime(unsigned char *GetTime)//读取时钟值,存入数组GetTime中,时:分:秒形式
{
   unsigned char temp;//BCD码转换为十进制

   temp = Read_Ds1302_Byte(0x85);//读出小时值
   GetTime[0] = (temp>>4)*10+(temp&0xf);  

   temp = Read_Ds1302_Byte(0x83);//读出分钟值
   GetTime[1] = (temp>>4)*10+(temp&0xf);
				   
   temp = Read_Ds1302_Byte(0x81);//读出秒值
   GetTime[2] = (temp>>4)*10+(temp&0xf);
}
//写入函数
void SetTime(unsigned char *WriteTime)//{00:00:00}
{
    unsigned char temp;
  	Write_Ds1302_Byte(0x8E,0X00);//允许写操作

	temp = ((WriteTime[0]/10)<<4)+WriteTime[0]%10;//写入时
	Write_Ds1302_Byte(0x84,temp);

	temp = ((WriteTime[1]/10)<<4)+WriteTime[1]%10;//写入分
	Write_Ds1302_Byte(0x82,temp);

	temp = ((WriteTime[2]/10)<<4)+WriteTime[2]%10;//写入秒
	Write_Ds1302_Byte(0x80,temp);

	Write_Ds1302_Byte(0x8E,0X80);//禁止写操作
}

注意事项

1、读取函数,读出来的值是BCD码,要经过转化才能得到十进制的数,
2、写入函数,写进去的数也要是BCD码,
3、控制寄存器(8EH)的BIT7,该位是写入的控制位,WP=0,允许写入;WP=1,禁止写入
4、地址(81H)的BIT7是CH,该位是时钟暂停标志,CH=0,计时结束;CH=1,计时开始。
5、用一个数组来保存这些值,是比较方便的,读取和写入都用一个数组也可以的,
6、命令地址这些不要背,看芯片手册写才是最正确的

6、AT24C04读取&写入函数

/*************底层驱动略***************/
/*************写入函数***************/
void writeRom(unsigned char add,unsigned char dat)
{
  	IIC_Start();//启动总线
	
	IIC_SendByte(0xA0);//向器件发送写命令+器件地址
	IIC_WaitAck();//向单片机发送应答信号
	IIC_SendByte(add);//向器件发送存储首地址
	IIC_WaitAck();//向单片机发送应答信号
	IIC_SendByte(dat);//向器件发送存储数据,这里可以是多个字节,存储地址依次延后
	IIC_WaitAck();//向单片机发送应答信号
	IIC_Stop();//结束总线
	Delay2ms();//短暂延时,写入数据
}
/*************读取函数***************/
unsigned char readRom(unsigned char add)
{	unsigned char dat;
    IIC_Start();//启动总线
	IIC_SendByte(SlaveAddrW);//向器件发送写命令+器件地址
	IIC_WaitAck();//向单片机发送应答信号
	IIC_SendByte(add);//存储地址
	IIC_WaitAck();//向单片机发送应答信号

	IIC_Start();//重启总线,输入命令
	IIC_SendByte(SlaveAddrR);//向器件发送读命令+器件地址
	IIC_WaitAck();//向单片机发送应答信号
	dat=IIC_RecByte();//接收数据
	IIC_SendAck(1); //发送非应答信号,停止接收
	IIC_Stop();//停止总线

	return dat;
}

注意事项

1、启动总线后,第一件事绝对是发送器件地址以及读/写命令。
2、主机向从机发送一个字节后,一定要等待从机发送应答信号。然后才能继续发送或者是其他操作
3、从机向主机发送完数据后,主机一定要发送一个非应答信号,表示接收结束了
4、写入操作结束后,最好短暂延时2ms,别直接用iic的延时函数
5、如果要重新发送读取写入命令,一定要重启总线。
6、要记得读取函数是有返回值的,不要忘记return dat

7、PCF8591读取&写入函数

/**********底层驱动略*********/
/**********读取函数*********/
unsigned char read_rb(unsigned char channel)//选择通道,取值0、1、2、3
{
  unsigned char dat;
  IIC_Start();
  IIC_SendByte(0x90); //写
  IIC_WaitAck();
  IIC_SendByte(channel);
  IIC_WaitAck();
  IIC_Stop();
  
  IIC_Start();
  IIC_SendByte(0x91); //读
  IIC_WaitAck();
  dat = IIC_RecByte();
  IIC_SendAck(1);
  IIC_Stop();
  return dat;
}
/**********写入函数*********/
void write_DAC(unsigned char x)
{
  IIC_Start();
  IIC_SendByte(0x90); //写
  IIC_WaitAck();
  
  IIC_SendByte(0x43); //允许输出
  IIC_WaitAck();
  IIC_SendByte(x);
  IIC_WaitAck();
  IIC_Stop();
 
}

注意事项:

1、PCF8591和AD24C02都是同一总线上的设备,因此注意事项都是一样的,
2、使用DAC功能时,要发送给0x43这条命令,否则不能启用DAC功能
3、读取函数的返回值时0到255,要转化为0到5v电压,要/51.0,注意这里的保存变量应该设为float类型
4、写入函数的入口参数时0到255,在写入之前,最好先看看写入0的时候,输出电压是多少,通过跳线帽短接out和AIN0就能通过读取通道0来获知输出电压了。
5、写入函数,只需要开启一次总线,并且需要持续写入

8、DS18B20读取函数

/***********该驱动延时快了12倍,要调整*********/
//单总线延时函数
void Delay_OneWire(unsigned int t)  //STC89C52RC
{
    unsigned char i;
	while(t--)
	{
	  for(i=0;i<12;i++);//这里是添加的代码
	}
}
/******其他驱动略*****/

//DS18B20温度采集程序:整数
u8 Read_Temp_d(void)
{
    u8 low,high;
  	char temp;  //这是一个有符号的数
  
  	init_ds18b20();
  	Write_DS18B20(0xCC);
  	Write_DS18B20(0x44); //启动温度转换
  	Delay_OneWire(200);

  	init_ds18b20();
  	Write_DS18B20(0xCC);
  	Write_DS18B20(0xBE); //读取寄存器

  	low = Read_DS18B20(); //低字节
  	high = Read_DS18B20(); //高字节
  
  	temp = high<<4;   //只取了整数部分和符号位
  	temp |= (low>>4);
  
  	return temp;
}
//温度采集,含小数
float ReadTemp_Float(void)
{
  u16 temp;
  float temperature;
  u8 LSB,HSB;

  init_ds18b20();
  //写入温度转换指令
  Write_DS18B20(0xcc);//跳过ROM
  Write_DS18B20(0x44);//开始温度转换
  Delay_OneWire(200);

  init_ds18b20();
  Write_DS18B20(0xcc);//跳过ROM
  Write_DS18B20(0xBE);//读取寄存器

  LSB = Read_DS18B20();
  HSB = Read_DS18B20();

  temp = ((HSB&0X0F)<<8)|LSB;
  temperature = temp*0.0625;

  return temperature;

}

注意事项

1、 对返回数据的高八位低八位进行不同的数据处理,可以得到12位分辨率的浮点型和整型数据。
2、整型数据,只取高八位的低四位,低八位的高四位。
3、浮点型数据,一定要注意整合高八位和低八位时,要用unsigned int这种16位的数据类型来保存。否则将出现数据丢失,整合后的数据还需要乘以0.0625.
4、0xcc是跳过rom,如果不写,读取数据将会很慢。
5、0x44是温度转换命令。如果不写,将一直显示温度85.
6、保险起见,发送完温度转化命令后,适当延时200到800ms。
7、发送命令前,记得init_ds18b20();
8、官方提供的延时是有问题的,要扩大12倍。for(i=0;i<12;i++);或者说t=t*12:
9、如果整合数据想要一句话完成,那一定要注意运算符的优先级

9、串口通信

//初始化函数
void Uart1Init(void)		//串口1,9600bps@12.000MHz
{
	SCON = 0x50;		//8位数据,可变波特率
	AUXR |= 0x40;		//定时器1时钟为Fosc,即1T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//设定定时器1为16位自动重装方式
	TL1 = 0xC7;		//设定定时初值
	TH1 = 0xFE;		//设定定时初值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
	TI = 1;//未发送完成
	ES=1;	//这里是自己加的
	EA=1;	//这里是自己加的
}
//串口发送字符 
void uart_send(unsigned char ch)
{
  ES=0;   //关串口中断,
  SBUF=ch;
  while(TI==0);  //等待发送完成
  TI=0;  //清除中断标记
  ES=1;   //开中断
}
//串口1字符串发送
void Uart1_Sendstring(unsigned char *Str) 
{ 
 ES=0;   //关串口中断
 while(*Str != '\0') 
 { 
	 SBUF = *Str; 
	 while(TI == 0); 
	 TI = 0; 
	 Str++; 
 } 
  ES=1;   //开串口中断
}
//串口接收 ( 通过中断 )
void uart1receive() interrupt 4
{
   if (RI==1) 
  { RI=0;
     buf=SBUF;
   }
}

注意事项

1、串口初始化波特率一定要注意频率要设为11.0592,同时IRC的频率也要设置11.0592mhz,否者收发数据将是乱码
2、串口发送最好采用查询法发送,发送数据前关闭ES,发送结束后要打开ES。TI =1,表示一帧数据发送结束。
3、字符串的发送要注意数据帧格式,要有一个结束标记比如\r\n或者’\0’这一类的。
4、中断接收,要注意RI=1,表示一帧数据接收完毕,并申请中断,要求CPU从接收SBUF取走数据。

总体设计篇

大体思路我分为四个板块,四个板块环环相扣。以第十一届为例。
板块1——按键处理模块
按键处理通常以界面为中心,一般不同界面screen下,按键会有不同的处理方式。在不同的界面下。有些按键是无效的,那我们不处理该案件的返回值即可。

 //按键处理
	if(key_flag)
	{
	  key_flag=0;
	  key=readkey();
	  if(key!=0)
	  {	 	    
	     switch(screen)
		 {
		   //默认界面,数据界面
		   	case 0:		
			switch(key)
			{
			  case 12 :if(++screen==3)screen=0;key_count=0;break;
			  default:key_count++;break;//key_count无效按键
			}break;

			//参数界面
		   	case 1:
			switch(key)
			{
			  case 12 :if(++screen==3)screen=0;key_count=0;break;
			  case 16 :Vp=Vp+0.5;if(Vp==5.5)Vp=0;key_count=0;break;
			  case 17 :Vp=Vp-0.5;if(Vp==-0.5)Vp=5.0;key_count=0;break;
			  default:key_count++;break;
			}break;
			
			//计数界面
			case 2:
			wirteROM(0,Vp*10);
			Vp=readROM(0)/10.0;
			switch(key)
			{
			   case 12 :if(++screen==3)screen=0;key_count=0;break;
			   case 13 :count=0;key_count=0;break;
			   default:key_count++;break;
			}break;
		 }
		 if(key_count>=3)
		 {
		   L3=0xfb;
		 }
		 else L3=0xff;
	  }
	}

板块2——显示处理
显示界面的依据是screen,不同的值对应不同的界面。
显示的数据就要涉及到数据处理,也就是板块3

//显示处理
	if(dis_flag)
	{
	  dis_flag=0;
	  switch(screen)
	  {
	     case 0 :sprintf(buf,"U    %3.2f",Vain3);	 		 
		 break;
		 case 1 :sprintf(buf,"P    %3.2f",Vp);		 		 
		 break;
		 case 2 :sprintf(buf,"N  %5d",count); 
		 break;
	  }
	}
	conversion(&duanma,&buf);

板块3——数据处理
一般就处理几个外设的读取写入函数,根据这些数据来控制不同的LED灯亮灭。

 //数据更新模块 100ms
	 if(AD_flag)
	 {
	   AD_flag=0;
	   Vain3=readADC(3)/51.0;//读取电压值
	   if(Vain3>Vp){flag=1;L1_count++;}//如果处于高于参数状态,flag置1,并开始计时
	   if(L1_count>=50){L1=0XFE;} //超过5s,点亮L1
	   else L1=0xff; //否则熄灭
	   if(Vain3<Vp&&flag==1) //如果低于于参数,并上一次是高于参数的
	   {
	     count++;//计数值加一
		 flag=0;//清除标记
		 L1_count=0;
	   }
	   if(count%2==0)
	   {
	     L2=0XFF;
	   }else L2=0xfd;
	 }

板块四——中断函数
中断函数是整个系统运行的核心。它通过定时来实现上面三个模块的定时执行。控制思想是标志法。通过控制标志量来决定是否运行相关函数。

整体思路:
先写按键模块(记得检查按键函数是否正确),然后写显示界面。通过按键来控制显示界面的切换。确定没有问题后,开始进行数据处理。逐渐填充显示界面的数据。中断函数根据主函数需要进行相关编写。

第十三届模拟题(第十一届国赛题)

这是今天刚做的,,大概三个小时,还没检查调试,先发出来看看,变量名随便取的,所以可能看不懂是啥意思.
没调试,最近太忙了,都没时间管蓝桥杯了
链接:https://pan.baidu.com/s/1j6Ljc_q-Njg-9bZvzHe8Ag?pwd=066h
提取码:066h

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小谦·

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

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

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

打赏作者

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

抵扣说明:

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

余额充值