HAL STM32+EC11编码器实现增减调节及单击、双击、长按功能

HAL STM32+EC11编码器实现增减调节及单击、双击、长按功能


  • 📺实现效果演示:
    在这里插入图片描述

📘内容提要

  • 📝本文主要实现,通过STM32 HAL库开发,实现的EC11编码器功能,按键结合状态机思想实现的拓展单击、双击、长按的综合功能。单片机硬件上使用了2个外部中断引脚实现。
  • 🗝该功能可以很方便实现移植,例如使用在OLED屏幕显示菜单上。
  • 🔖验证对象:STM32F401
  • 🍁EC11编码器部分的原理图:
    在这里插入图片描述

🛠stm32cubemx配置

  • 🔧将EC11中键引脚配置为输入模式、开启内部上拉模式,其余2个引脚配置为外部中断引脚(一个配置为下降沿中断,另外一个配置为上、下降沿中断,这一点很关键!)。

在这里插入图片描述

✅EC11编码器增减功能,通过外部中断实现

  • 🌿外部中断回调处理函数:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    /* Prevent unused argument(s) compilation warning */
    UNUSED(GPIO_Pin);
    static uint8_t count = 0;
    static uint8_t b_flag;
    GPIO_PinState a_value = HAL_GPIO_ReadPin(EC11_A_GPIO_Port, EC11_A_Pin);
    GPIO_PinState b_value = HAL_GPIO_ReadPin(EC11_B_GPIO_Port, EC11_B_Pin);

    if(GPIO_Pin == EC11_A_Pin) {
        if(a_value == RESET && count == 0) {
            b_flag = 0;
            if(b_value) b_flag = 1;
            count = 1;
        }

        if(a_value == SET && count == 1) {
            if(b_value == RESET && b_flag == 1) { //开始逆时针转动
                test_num--;
                dir_flag = 1;
            }
            if(b_value && b_flag == 0) { //开始顺时针转动
                test_num++;
                dir_flag = 0;
            }
            count = 0;
        }

    }

    /* EC11中键,按键中断 */
    // if(GPIO_Pin == EC11_KEY_Pin)
    // {
    // key_click_flag = 1;
    // }
}

📓单击、双击、长按功能实现驱动代码

  • 📝KEY.C
#include "key.h"

//代码来源网络 侵权联系删除
/*
 --------------------------------------------------------------------
|  short click                                                       |
|  ______                   ________                                 |
|     |  \                 /  |                                      |
|     |   \_______________/   |                                      |
|     |     |           |     |                                      |
|     |shake|  < long   |shake|                                      |
|                                                                    |
 -------------------------------------------------------------------
|  double click                                                      |
|  ______                   _____________                   ____     |
|     |  \                 /  |       |  \                 /  |      |
|     |   \_______________/   |       |   \_______________/   |      |
|     |     |           |     | < max |     |           |     |      |
|     |shake|  < long   |shake|dclick |shake|  any time |shake|      |
|                                                                    |
 --------------------------------------------------------------------
|  long click                                                        |
|  ______                                           ________         |
|     |  \                                         /  |              |
|     |   \_______________________________________/   |              |
|     |     |                                   |     |              |
|     |shake|             > long click          |shake|              |
|                                                                    |
 --------------------------------------------------------------------
   
*/
 
 
#define KEY_STATUS_DOWN_CHECK                0x00       
#define KEY_STATUS_DOWN_SHAKE                0x01
#define KEY_STATUS_DOWN_HANDLE               0x02
#define KEY_STATUS_LONG_CHECK                0x03
#define KEY_STATUS_SHORT_UP_SHAKE            0x04
#define KEY_STATUS_DOUBLE_CHECK              0x05
#define KEY_STATUS_SHORT_UP_HANDLE           0x06
#define KEY_STATUS_DOUBLE_DOWN_SHAKE         0x07
#define KEY_STATUS_DOUBLE_UP_CHECK           0x08
#define KEY_STATUS_DOUBLE_UP_SHAKE           0x09
#define KEY_STATUS_DOUBLE_UP_HANDLE          0x0a
#define KEY_STATUS_LONG_HANDLE               0x0b
#define KEY_STATUS_CONTINUE_CHECK            0x0c
#define KEY_STATUS_LONG_UP_SHAKE             0x0d
#define KEY_STATUS_LONG_UP_HANDLE            0x0e
 
 
#define KEY_READ_DOWN                        0x00  /* key is pressed          */
#define KEY_READ_UP                          0x01  /* Key isn't pressed       */ 
 
#define KEY_BUF_SIZE                         0x10  /* key value buffer size   */


struct
{
    unsigned short value[KEY_BUF_SIZE];
    unsigned char rd;
    unsigned char wr;
}key_buf;
 
 
struct key_dev
{
    unsigned char status;              /* state machine status                */
    unsigned char num;                 /* number                              */
    unsigned short count_ms;           /* ms counter                          */
    unsigned short long_click_ms;      /* long click check min time           */
    unsigned short shake_filter_ms;    /* shake filter time                   */
    unsigned short max_dclick_ms;      /* double click max interval time      */
    unsigned short continue_send_ms;   /* after long, continue send interval  */
    unsigned char (*read_key)(void);   /* key read pin status function pointer*/
};


/******************************************************************************
                           User Interface [START]
*******************************************************************************/
 
unsigned char key0_read(void)
{
    if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET)
    {
        return KEY_READ_DOWN;
    }
    else
    {
        return KEY_READ_UP;
    }
}
 
 
struct key_dev key_dev[] = {
    {
     KEY_STATUS_DOWN_CHECK,
     KEY0_NUM,
     0,
     1500,
     20,
     300,
     1000,
     (unsigned char (*)(void))key0_read,
    },
    /*
            .
            .
        user add other key 
            .
            .
    */
 
};


/******************************************************************************
                           User Interface [END]
*******************************************************************************/

static void key_write_value(unsigned short key_val);
static void key_status_down_check(struct key_dev *key_dev);
static void key_status_down_shake(struct key_dev *key_dev);
static void key_status_down_handle(struct key_dev *key_dev);
static void key_status_long_check(struct key_dev *key_dev);
static void key_status_short_up_shake(struct key_dev *key_dev);
static void key_status_double_check(struct key_dev *key_dev);
static void key_status_short_up_handle(struct key_dev *key_dev);
static void key_status_double_down_shake(struct key_dev *key_dev);
static void key_status_double_up_check(struct key_dev *key_dev);
static void key_status_double_up_shake(struct key_dev *key_dev);
static void key_status_double_up_handle(struct key_dev *key_dev);
static void key_status_long_hanle(struct key_dev *key_dev);
static void key_status_continue_check(struct key_dev *key_dev);
static void key_status_long_up_shake(struct key_dev *key_dev);
static void key_status_long_up_handle(struct key_dev *key_dev);
 
 
/**
description : write key vaule to buffer
param :  key_val - key value , (KEY_EVENT | KEY_NUMBER<<8) 
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_write_value(unsigned short key_val)
{   
    key_buf.value[key_buf.wr++] = key_val;
    key_buf.wr %= KEY_BUF_SIZE;
 
    /*
        overflow handle
    */
    if(key_buf.wr == key_buf.rd)
    {
        key_buf.rd++;
        key_buf.rd %= KEY_BUF_SIZE;
    }
}
 
/**
description : read key vaule from buffer
param : None
retval : key_val - key value , (KEY_EVENT | KEY_NUMBER<<8)
author : huohongpeng
data : 2017-03-02
*/
unsigned short key_read_value(void)
{
    unsigned short key_val;
 
    if(key_buf.wr == key_buf.rd)
    {
        key_val = KEY_EVENT_NULL;
    }
    else
    {
        key_val = key_buf.value[key_buf.rd++];
        key_buf.rd %= KEY_BUF_SIZE;
    }
 
    return key_val;
}
 
/**
description : check key whether press down
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_status_down_check(struct key_dev *key_dev)
{
    unsigned char key_read;
 
    key_read = key_dev->read_key();
 
    if(key_read == KEY_READ_DOWN)
    {
        key_dev->status = KEY_STATUS_DOWN_SHAKE;
        key_dev->count_ms = 0;
    }
}
 
/**
description : filter shake after key pressed down
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_status_down_shake(struct key_dev *key_dev)
{
    unsigned char key_read;
 
    key_dev->count_ms++;
 
    if(key_dev->count_ms < key_dev->shake_filter_ms)
    {
        return;
    }
 
    key_read = key_dev->read_key();
 
    if(key_read == KEY_READ_DOWN)
    {
        key_dev->status = KEY_STATUS_DOWN_HANDLE;
    }
    else
    {
        key_dev->status = KEY_STATUS_DOWN_CHECK;
    }
}
 
 
/**
description : key press down handle after pressed down filter shake  
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_status_down_handle(struct key_dev *key_dev)
{
    unsigned short key_val = key_dev->num<<8 | KEY_EVENT_DOWN;
    
    key_write_value(key_val);
 
    key_dev->status = KEY_STATUS_LONG_CHECK;
    key_dev->count_ms = 0;
 
}
 
/**
description : check key whether long click   
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_status_long_check(struct key_dev *key_dev)
{
    unsigned char key_read;
 
    key_dev->count_ms++;
    key_read = key_dev->read_key();
    
    if(key_dev->count_ms < key_dev->long_click_ms)
    {
        if(key_read == KEY_READ_UP)
        {
            key_dev->status = KEY_STATUS_SHORT_UP_SHAKE;                 
        }
 
        return;
    }
 
    key_dev->status = KEY_STATUS_LONG_HANDLE;            
    
}
 
/**
description : short cilck key up filter shake   
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_status_short_up_shake(struct key_dev *key_dev)
{
    unsigned char key_read;
    static unsigned short old = 0xffff;
 
    if(old == 0xffff)
    {
        old = key_dev->count_ms;
        key_dev->count_ms = 0;
    }
 
    key_dev->count_ms++;
 
    if(key_dev->count_ms < key_dev->shake_filter_ms)
    {
        return;
    }
 
    key_read = key_dev->read_key();
 
    if(key_read == KEY_READ_UP)
    {
        key_dev->status = KEY_STATUS_DOUBLE_CHECK;
        key_dev->count_ms = 0;
    }
    else
    {
        key_dev->status = KEY_STATUS_LONG_CHECK;
        key_dev->count_ms += old;        
    }
 
    old = 0xffff;
}
 
/**
description : double cilck check. we consider double click event if key pressed 
              down when after short click up and within max double click interval
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_status_double_check(struct key_dev *key_dev)
{
    unsigned char key_read;
 
    key_dev->count_ms++;
    key_read = key_dev->read_key();
    
    if(key_dev->count_ms < key_dev->max_dclick_ms)
    {
        if(key_read == KEY_READ_DOWN)
        {
            key_dev->status = KEY_STATUS_DOUBLE_DOWN_SHAKE;
            key_dev->count_ms = 0;                 
        }
    }
    else
    {
        key_dev->status = KEY_STATUS_SHORT_UP_HANDLE;            
    }
 
}
 
/**
description : short click key up handle
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_status_short_up_handle(struct key_dev *key_dev)
{
    unsigned short key_val;
 
    key_val= key_dev->num<<8 | KEY_EVENT_SHORT;
    
    key_write_value(key_val);
 
    key_val= key_dev->num<<8 | KEY_EVENT_UP_SHORT;
    
    key_write_value(key_val);
 
    key_dev->status = KEY_STATUS_DOWN_CHECK;
}
 
/**
description : double click key down filter shake
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_status_double_down_shake(struct key_dev *key_dev)
{
    unsigned char key_read;
    static unsigned short old = 0xffff;
 
    if(old == 0xffff)
    {
        old = key_dev->count_ms;
        key_dev->count_ms = 0;
    }
 
    key_dev->count_ms++;
 
    if(key_dev->count_ms < key_dev->shake_filter_ms)
    {
        return;
    }
 
    key_read = key_dev->read_key();
 
    if(key_read == KEY_READ_DOWN)
    {
        unsigned short key_val;
 
        key_val= key_dev->num<<8 | KEY_EVENT_DOUBLE;
    
        key_write_value(key_val);
 
        key_dev->status = KEY_STATUS_DOUBLE_UP_CHECK;
        key_dev->count_ms = 0;
    }
    else
    {
        key_dev->status = KEY_STATUS_DOUBLE_CHECK;
        key_dev->count_ms += old;        
    }
 
    old = 0xffff;
}
 
/**
description : double click key up check
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_status_double_up_check(struct key_dev *key_dev)
{
    unsigned char key_read;
 
    key_read = key_dev->read_key();
 
    if(key_read == KEY_READ_UP)
    {
        key_dev->status = KEY_STATUS_DOUBLE_UP_SHAKE;
        key_dev->count_ms = 0;
    }
}
 
/**
description : double click key up filter shake
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_status_double_up_shake(struct key_dev *key_dev)
{
    unsigned char key_read;
 
    key_dev->count_ms++;
 
    if(key_dev->count_ms < key_dev->shake_filter_ms)
    {
        return;
    }
 
    key_read = key_dev->read_key();
 
    if(key_read == KEY_READ_UP)
    {
        key_dev->status = KEY_STATUS_DOUBLE_UP_HANDLE;
    }
    else
    {
        key_dev->status = KEY_STATUS_DOUBLE_UP_CHECK;
    }
 
}
 
/**
description : double click key up handle
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_status_double_up_handle(struct key_dev *key_dev)
{
    unsigned short key_val;
 
    key_val= key_dev->num<<8 | KEY_EVENT_UP_DOUBLE;
    
    key_write_value(key_val);
 
    key_dev->status = KEY_STATUS_DOWN_CHECK;
}
 
/**
description : long click handle after long click check
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_status_long_hanle(struct key_dev *key_dev)
{
    unsigned short key_val;
 
    key_val= key_dev->num<<8 | KEY_EVENT_LONG;
    
    key_write_value(key_val);
 
    key_dev->status = KEY_STATUS_CONTINUE_CHECK;
    key_dev->count_ms = 0;
}
 
/**
description : continue send short click if long click time overflow
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_status_continue_check(struct key_dev *key_dev)
{
    unsigned char key_read;
    unsigned short key_val;
 
    key_dev->count_ms++;
 
    key_read = key_dev->read_key();
 
    if(key_read == KEY_READ_UP)
    {
        key_dev->status = KEY_STATUS_LONG_UP_SHAKE;
    }
 
    if(key_dev->count_ms < key_dev->continue_send_ms)
    {
        return;
    }
 
    if(key_dev->continue_send_ms == 0)
    {
        return;
    }
  
    key_val= key_dev->num<<8 | KEY_EVENT_SHORT;
    
    key_write_value(key_val);
    key_dev->count_ms = 0;
}
 
/**
description : long click key up filter shake
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_status_long_up_shake(struct key_dev *key_dev)
{
    unsigned char key_read;
    static unsigned short old = 0xffff;
 
    if(old == 0xffff)
    {
        old = key_dev->count_ms;
        key_dev->count_ms = 0;
    }
 
    key_dev->count_ms++;
 
    if(key_dev->count_ms < key_dev->shake_filter_ms)
    {
        return;
    }
 
    key_read = key_dev->read_key();
 
    if(key_read == KEY_READ_UP)
    {
        key_dev->status = KEY_STATUS_LONG_UP_HANDLE;
    }
    else
    {
        key_dev->status = KEY_STATUS_CONTINUE_CHECK;
        key_dev->count_ms += old;      
    }
    
    old = 0xffff;
}
 
/**
description : long click key up filter handle
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_status_long_up_handle(struct key_dev *key_dev)
{
    unsigned short key_val;
 
    key_val= key_dev->num<<8 | KEY_EVENT_UP_LONG;
    
    key_write_value(key_val);
 
    key_dev->status = KEY_STATUS_DOWN_CHECK;
}
 
/**
description : run key state machine once every 1ms
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
*/
static void key_check_1ms(struct key_dev *key_dev)
{
    switch(key_dev->status)
    {
        case KEY_STATUS_DOWN_CHECK :
             key_status_down_check(key_dev);
             break;      
        case KEY_STATUS_DOWN_SHAKE : 
             key_status_down_shake(key_dev);
             break;           
        case KEY_STATUS_DOWN_HANDLE : 
             key_status_down_handle(key_dev);
             break;          
        case KEY_STATUS_LONG_CHECK : 
             key_status_long_check(key_dev);
             break;              
        case KEY_STATUS_SHORT_UP_SHAKE :  
             key_status_short_up_shake(key_dev);
             break;
        case KEY_STATUS_DOUBLE_CHECK :
             key_status_double_check(key_dev);
             break;
        case KEY_STATUS_SHORT_UP_HANDLE :
             key_status_short_up_handle(key_dev);
             break;
        case KEY_STATUS_DOUBLE_DOWN_SHAKE : 
             key_status_double_down_shake(key_dev);
             break;
        case KEY_STATUS_DOUBLE_UP_CHECK :
             key_status_double_up_check(key_dev);
             break;
        case KEY_STATUS_DOUBLE_UP_SHAKE :
             key_status_double_up_shake(key_dev);
             break;
        case KEY_STATUS_DOUBLE_UP_HANDLE :
             key_status_double_up_handle(key_dev);
             break;         
        case KEY_STATUS_LONG_HANDLE :
             key_status_long_hanle(key_dev);
             break;
        case KEY_STATUS_CONTINUE_CHECK : 
             key_status_continue_check(key_dev);
             break;
        case KEY_STATUS_LONG_UP_SHAKE : 
             key_status_long_up_shake(key_dev);
             break;
        case KEY_STATUS_LONG_UP_HANDLE :  
             key_status_long_up_handle(key_dev);
             break;
        default:
             key_dev->status = key_dev->status;
    }
}
 
/**
description : run all key state machine once every 1ms
param : key_dev - key device pointer
retval : None
author : huohongpeng
data : 2017-03-02
call :
       timer(1ms) interrupt handle 
*/
void key_check_all_loop_1ms(void)
{
    unsigned char key_num, i;
 
    key_num = sizeof(key_dev)/sizeof(struct key_dev);
 
    for(i = 0; i < key_num; i++)
    {
        key_check_1ms(&key_dev[i]);
    }
 
}


  • 📄KEY.h
#ifndef __key_H
#define __key_H

#ifdef __cplusplus
 extern "C" {
#endif 

#include "main.h"

#define shortmaxclickms (300)
#define shortminclickms (10)
#define longclickms (800)



#define KEY_EVENT_NULL                     0x0000  
#define KEY_EVENT_DOWN                     0x0001
#define KEY_EVENT_UP_SHORT                 0x0002  // 短按后松开事件
#define KEY_EVENT_UP_LONG                  0x0003  // 长按后松开事件
#define KEY_EVENT_UP_DOUBLE                0x0004  // 双击后松开事件
#define KEY_EVENT_SHORT                    0x0005
#define KEY_EVENT_LONG                     0x0006
#define KEY_EVENT_DOUBLE                   0x0007

#define KEY_READ_DOWN                        0x00  /* key is pressed          */
#define KEY_READ_UP                          0x01  /* Key isn't pressed       */ 


/******************************************************************************
                           User Interface [START]
*******************************************************************************/
 
#define KEY0_NUM                           0x0001
 
#define KEY0_DOWN               (KEY_EVENT_DOWN      | KEY0_NUM<<8)
#define KEY0_UP_SHORT           (KEY_EVENT_UP_SHORT  | KEY0_NUM<<8)
#define KEY0_UP_LONG            (KEY_EVENT_UP_LONG   | KEY0_NUM<<8)
#define KEY0_UP_DOUBLE          (KEY_EVENT_UP_DOUBLE | KEY0_NUM<<8)
#define KEY0_SHORT              (KEY_EVENT_SHORT     | KEY0_NUM<<8)
#define KEY0_LONG               (KEY_EVENT_LONG      | KEY0_NUM<<8) 
#define KEY0_DOUBLE             (KEY_EVENT_DOUBLE    | KEY0_NUM<<8)
 
 
 
/******************************************************************************
                           User Interface [END]
*******************************************************************************/


void key_check_all_loop_1ms(void);
unsigned short key_read_value(void);

#ifdef __cplusplus
}
#endif

#endif


🔱main函数中按键处理内容

#include "stdio.h"
#include "key.h"
int32_t test_num = 0;//支持正负数值增减
uint8_t dir_flag = 2; /*  方向标志 0: 顺时 1: 逆时 2: 未动*/
uint8_t key_click_flag = 0;//EC11 中键

unsigned short key_value;



int main(void)
{
    /* USER CODE BEGIN 1 */
    uint32_t tick2, tick3;
    /* 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();
    MX_USART1_UART_Init();
    /* USER CODE BEGIN 2 */
    uint32_t Main_Fosc = HAL_RCC_GetSysClockFreq();
    printf("Main_Fosc:%dHz \r\n", Main_Fosc);
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while(1) {
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
        if(dir_flag != 2) {
            switch(dir_flag) {
                case 0:
                    printf("顺时针转动\r\n");
                    break;
                case 1:
                    printf("逆时针转动\r\n");
                    break;
            }
            dir_flag = 2;
            printf("num: %d\n", test_num);
        }
        if(HAL_GetTick() - tick2 >= 1) {
            tick2 = HAL_GetTick();
            key_check_all_loop_1ms();
        }

        /* Key按键按下查询 */
        if(HAL_GetTick() - tick3 >= 10) {
            tick3 = HAL_GetTick();
            key_value = key_read_value();

            if(key_value == KEY0_UP_SHORT) {	//单击事件
                HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
                // 实现单击KEY
                test_num++;
                printf("Press 单击,num:%d\r\n", test_num);

            } else if(key_value == KEY0_UP_DOUBLE) {	//双击事件
                HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);

                test_num += 2;
                printf("\r\nDouble Press 双击,num:%d\r\n", test_num);
            } else if(key_value == KEY0_LONG) {	//长按事件
                printf("\r\nLong Press 长按\r\n");
                // 实现长按KEY
                test_num = 0;//清除编码器计数值
                printf("按键计数清零,num:%d\r\n", test_num);

            }
        }

    }
    /* USER CODE END 3 */
}

📚工程源码

链接:https://pan.baidu.com/s/1zTMkY8iEGSCu_gSavR_I3A?pwd=4m2q 
提取码:4m2q
  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值