打印
[其他ST产品]

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

[复制链接]
155|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 慢动作 于 2024-1-31 11:24 编辑

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

使用特权

评论回复
沙发
慢动作|  楼主 | 2024-1-31 11:24 | 只看该作者
stm32cubemx配置
将EC11中键引脚配置为输入模式、开启内部上拉模式,其余2个引脚配置为外部中断引脚(一个配置为下降沿中断,另外一个配置为上、下降沿中断,这一点很关键!)。

使用特权

评论回复
板凳
慢动作|  楼主 | 2024-1-31 11:25 | 只看该作者
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;
    // }
}

使用特权

评论回复
地板
慢动作|  楼主 | 2024-1-31 11:26 | 只看该作者
单击、双击、长按功能实现驱动代码
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]);
    }

}


使用特权

评论回复
5
慢动作|  楼主 | 2024-1-31 11:27 | 只看该作者
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


使用特权

评论回复
6
慢动作|  楼主 | 2024-1-31 11:27 | 只看该作者
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 */
}

使用特权

评论回复
7
慢动作|  楼主 | 2024-1-31 11:27 | 只看该作者
工程源码
链接:https://pan.baidu.com/s/1zTMkY8iEGSCu_gSavR_I3A?pwd=4m2q
提取码:4m2q

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

55

主题

517

帖子

0

粉丝