【蓝桥杯】【嵌入式组别】第五节:按键设计

GPIO输入

GPIO输入端口的两个二极管是为了防止太高的电压或者过低的电压流过,从而对二极管产生伤害。
经过这个之后,会接到一个stm32内部的TTl电平的施密特触发器,TTL电平一般认为大于2.4V是高电平,低于0.4V是低电平。那么这个施密特触发器的话,针对正向电压的阈值是2.4V,针对负向电压的阈值是0.4V。是为了防止外部的电压波动导致的误触发。经过施密特触发器消除干扰后
在这里插入图片描述
GPIO输入一共有三种模式:浮空输入(就是不接任何电阻,在一般情况下电压是不确定状态),上拉电阻输入(配置成上拉模式的话会通过一个电阻连接到高电平,此时如果GPIO处于悬空,没有接任何设备的时候,其电平就总是高电平),下拉电阻输入(GPIO悬空时,就是低电平)。
有时候板子上面也会把外设接一个上拉或者下拉电阻,以防止GPIO浮空电平,但其实如果把GPIO在初始的时候就设置成上下拉电阻,那么外设就不用接这个电阻了。一般用上拉电阻比较多。

不建议用浮空输入,这样会导致电压大小一直不确定

按键原理

通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动,如图所示。
按键稳定闭合时间长短是由操作人员决定的,通常都会在100ms 以上,刻意快速按的话能达到 40-50ms 左右,很难再低了。
抖动时间是由按键的机械特性决定的,一般都会在 10ms以内,为了确保程序对按键的一次闭合或者次断开只响应一次,必须进行按键的消抖处理!
【注】按键按下,IO口读到低电平;按键弹起,IO口读到高电平。

独立按键编程注意事项

编程实现:
统计S4按下的次数:
每按下一次S4,数码管的数字+1(默认数字是0)
思路:
在while(1)内循环 读取PB3状态:
①如果是0,则等待10ms; 再读取PB3状态;如果不是1,继续读取PB3状态:
②如果还是0,确定有按键按下;则执行number++; 程序,让数码管数字+1.
④等待按键弹起后,再等待10ms,方可从①再开始执行:(如果不等待,则在循环内1~3会一直执行,让数字疯狂+1)
根据上面的思路可能写出的就是如下这样的代码:

while(1)
{
    if(54 == 0)
	{
		Delay_Ms(10);
		if(S4 == 0)
		{
			number++:
		}
	while(S4 == 0);
		}
}

但是这样的代码有很大的问题:

  1. 在while里面有delay会使cpu执行效率下降
  2. 程序等待按键变为高电平才结束,那如果我一直按着,那cpu会一直等着,不会做其他事情,相当于程序堵塞了。

所以要注意的是,按键程序应该:

  1. 每次按下按键需要单次出发
  2. 需要有松手检测
  3. 不阻塞程序(不要有delay,更不要有while(!PB3),等待按键弹起)
  4. 最好有长按功能

编程

程序设计步骤:

  1. 【模板】作为STM32CUBEMX生成代码的工程

  2. 设置按键相关的GPIO为【输入】模式 (无需配置上下拉,因为可以看到按键电路那里已经配置好了下拉电阻) ; (PA0,PBO,PB1,PB2)

  3. 复制【GPIO初始化部分】部分到【编程工程】,并测试端口读取是否正确

  4. 编写key.c和key.h文件,并添加到工程中,编写函数Key_Read用于读取按键。(三行按键法)

  5. 在main.c添加key.h,并调用Key Read函数,完成按键读取。

首先打开模板工程,把各个引脚配置好,把按键的引脚配置为输入模式,然后生成代码。
生成好的就是gpio.c文件,我们把这个新生成的gpio.c文件复制到之前的编程工程中去。(这样做是为了防止直接在编程工程生成cube代码会把我们自己写的代码覆盖掉)。
然后我们写一个程序测试按键读取是否正常:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"
#include "led.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
//Led执行程序
__IO uint32_t ledTick =0;
u8 led_ctrl=0xff;
void LED_Process(void)
{
	if(uwTick-ledTick<500)return;
	ledTick=uwTick;
	LED_Control(led_ctrl);
	led_ctrl=~led_ctrl;
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */
	
	LCD_Init();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
	
	LCD_Clear(Blue);
	LCD_SetBackColor(Blue);
	LCD_SetTextColor(White);
	
	LCD_DisplayStringLine(Line0, (uint8_t *)"                    ");
	LCD_DisplayStringLine(Line1, (uint8_t *)"                    ");
	LCD_DisplayStringLine(Line2, (uint8_t *)"      LCD Test      ");
	LCD_DisplayStringLine(Line3, (uint8_t *)"                    ");
	LCD_DisplayStringLine(Line4, (uint8_t *)"                    ");
	
	LCD_SetBackColor(White);
	LCD_SetTextColor(Blue);

	LCD_DisplayStringLine(Line5, (uint8_t *)"                    ");
	LCD_DisplayStringLine(Line6, (uint8_t *)"       HAL LIB      ");
	LCD_DisplayStringLine(Line7, (uint8_t *)"                    ");
	LCD_DisplayStringLine(Line8, (uint8_t *)"         @80        ");
	LCD_DisplayStringLine(Line9, (uint8_t *)"                    ");
	
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    //LED_Process();
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==0)
		{
			LED_Control(0x01);
		}
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==0)
		{
			LED_Control(0x02);
		}
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** Initializes the CPU, AHB and APB busses clocks
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV2;
  RCC_OscInitStruct.PLL.PLLN = 20;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */

  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

发现读取是正常的。但是这个简陋的代码是不足以成为最终我们使用的程序的,因为他没有办法判断连续按下,如果我们一直按下的话,会一直停留在函数中,但是实际上一直按下的效果和按一下就弹起来效果应该是一样的。并且该程序也无法区别长按和短按。
所以我们此处引入一个三行按键程序:

#define KB1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define KB2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define KB3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define KB4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
#define KEYPORT KB1|(KB2<<1)|(KB3<<2)|((KB4<<3))|0xf0
unsigned char Trg;//全局变量,单次触发
unsigned char Cont;//全局变量,长按
void Key_Read(void)
{
	unsigned char ReadData =(KEYPORT)^0xff;
	Trg=ReadData&(ReadData^Cont);
	Cont=ReadData;
}

注:
该按键程序也是利用定时器,10ms执行一次
“异或”:不同即为1
用if(trg&0x01)可以进行按位判断
unsignde char也可以写为u8,都是无符号8位的意思

下面一句一句解释一下:
首先是定义每一个口读入的值为一个变量,这个没什么难的。
然后是将每个口的变量联合起来组成一个8位的变量:
KB1作为第一位(低位),KB2通过移1位变道第二位,KB3是第三位,KB4是第四位,组成这样一个4位的二进制数:(KB4)(KB3)(KB2)(KB1),然后和0xf0进行异或运算。0xf0是(11110000),是一个8位的二进制数,而异或运算指的是”不同则为1“.
假设我们所有的按键都还没有按下,则四个数(KB4)(KB3)(KB2)(KB1)都是1,构成的数为(1111),然后和0xf0相异或的话就是0xff,也就是(11111111)。
之后我们定义两个8位的变量Cont和Trg,用于判断单次触发还是长按。
接下来就是程序的核心部分。
我们假设四个按键都没有被按下,那么第一行代码的结果就是(11111111)和0xff进行异或运算,由于异或运算是”不同为1“,所以结果是00000000,那么第二行的结果就也是00000000,第三行cont的结果也是00000000.
如果有一个按键被按下,比如是KB1被按下,那么KEYPORT的值就是11111110,然后运行第一行代码的结果就是11111110和11111111进行异或运算,结果就是00000001,就检测出了是KB1被按下。
第二行就是用00000001先与上次的cont值(也就是00000000)相异或运算,结果是00000001,然后再与readdata的值也就是00000001相与,结果还是00000001,然后第三行的值就是把readdata赋值给cont。
那么我们就可以通过对Trg的判断来确定哪一个按键被按下了。如:if (Trg==0x01),就是第一个按键被按下。诸如此类。
那么cont有时如何判断长按的呢?
我们进行第三次程序执行:
由于我们按键检测是要有一定频率的,就是隔多长时间要检测一次。我们在编程时一般使用10ms检测一次。而一次按键按下一般是在50ms左右(包含抖动)。
所以我们如果如上面所说按下了KB1按键,那么在50ms内检测到的KEYPORT都应该是11111110.
,那么readdata就会一直是0x01,所以cont也是一直都是0x01,但是trg的值就不一样了,trg之前之所以是0x01,是因为那时cont是0x00,现在cont变为0x01了,然后trg就会变为0x00。所以trg就是单次触发,也就是按下的一瞬间改变了值,然后再一次执行该程序时(即使按键还是按下的状态),trg也会直接变回0x00。
总结一下,我们假设这么一个过程:
一开始没有按键被按下,之后KB1被按下,50ms之后又回到没有按键按下的状态。
(以10ms为间隔描述是因为我们默认按键检测程序10ms执行一次)

  1. 0ms:没有按键按下,KEYPORT=0xff,ReadData=Trg=Cont=0x00.
  2. 10ms:KB1按键按下,KEYPORT=11111110,ReadData=Trg=Cont=0x01.
  3. 20ms-50ms:KB1按键按下,KEYPORT=0x11111110,ReadData=Cont=0x01.但Trg=0x00.
  4. 50ms之后:没有按键按下,KEYPORT=0xff,ReadData=Trg=Cont=0x00.

代码展示

key.h:

#ifndef __KEY_H
#define __KEY_H
#include "main.h"
extern unsigned char Trg;
extern unsigned char Cont;
void Key_Read(void);
#endif



key.c:

#include "key.h"
#define KB1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define KB2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define KB3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define KB4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
#define KEYPORT KB1|(KB2<<1)|(KB3<<2)|(KB4<<3)|0xf0
unsigned char Trg;
unsigned char Cont;
void Key_Read(void)
{
	unsigned char ReadData =(KEYPORT)^0xff;
	Trg=ReadData&(ReadData^Cont);
	Cont=ReadData;
}


main.c:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"
#include "led.h"
#include "key.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
//Led执行程序
__IO uint32_t ledTick =0,keyTick=0;
u8 led_ctrl=0xff;
void LED_Process(void)
{
	if(uwTick-ledTick<500)return;
	ledTick=uwTick;
	LED_Control(led_ctrl);
	led_ctrl=~led_ctrl;
}
void KEY_Process(void)
{
	if(uwTick-keyTick<10)return;
	keyTick=uwTick;
	Key_Read();
//	if(Trg&0x01)
//	{
//	LED_Control(0x01);
//	}
	if(Trg)
	{
		LED_Control(Trg);
	}
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */
	
	LCD_Init();
	LED_Control(0x00);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
	
	LCD_Clear(Blue);
	LCD_SetBackColor(Blue);
	LCD_SetTextColor(White);
	
	LCD_DisplayStringLine(Line0, (uint8_t *)"                    ");
	LCD_DisplayStringLine(Line1, (uint8_t *)"                    ");
	LCD_DisplayStringLine(Line2, (uint8_t *)"      LCD Test      ");
	LCD_DisplayStringLine(Line3, (uint8_t *)"                    ");
	LCD_DisplayStringLine(Line4, (uint8_t *)"                    ");
	
	LCD_SetBackColor(White);
	LCD_SetTextColor(Blue);

	LCD_DisplayStringLine(Line5, (uint8_t *)"                    ");
	LCD_DisplayStringLine(Line6, (uint8_t *)"       HAL LIB      ");
	LCD_DisplayStringLine(Line7, (uint8_t *)"                    ");
	LCD_DisplayStringLine(Line8, (uint8_t *)"         @80        ");
	LCD_DisplayStringLine(Line9, (uint8_t *)"                    ");
	
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    //LED_Process();
		KEY_Process();
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** Initializes the CPU, AHB and APB busses clocks
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV2;
  RCC_OscInitStruct.PLL.PLLN = 20;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */

  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

兜兜里有好多糖

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

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

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

打赏作者

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

抵扣说明:

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

余额充值