STM32按键长短按:支持12个按键,超强移植性,回调函数按键处理机制

第1章 按键长短按实验(提升)

完整工程连接:http://download.csdn.net/download/jahol_fan/10175862

1.1 实验简介

按键长按与按键短按在很多产品中都需要应用到,在我们生活中,例如:手机开关机用到的就是按键长按,手机设置音量用的是按键的短按。在本实验平台的综合实验中,也需要用到按键的长短按,所以,我们很有必要学习如何实现按键的程序设计。

设计按键长短按的思路其实很简单,就是计数原理。假设,定时器定时10ms中断一次,在中断函数中,判断按键是否按下,如果按下,然后统计按键按下的时间长度是多少个10ms,如果按下了10010ms,则表明长按了1秒;如果按下了30010ms,表示按下了3秒。

1.2 硬件设计

 

1) KEY2连接到PA8,稳定按下是低电平,稳定松开是高电平。

2) KEY3连接到PB10,稳定按下是低电平,稳定松开是高电平。

 

1.3 软件设计

1.构造按键参数结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

typedef void (*keyCallBackFuction)(void);  //定义回调函数指针

__packed typedef struct
{
    uint8_t          keyNum;          //按键编号
    uint32_t         keyRccPeriph;    //按键时钟
    GPIO_TypeDef     *keyPort;        //按键所在端口
    uint32_t         keyGpio;         //按键所在的IO口
    keyCallBackFuction shortPress;      //回调函数成员,短按回调函数
    keyCallBackFuction longPress;       //回调函数成员,长按回调函数
}keyTypedef_t; 

__packed typedef struct
{
    uint8_t      keyTotolNum;  //按键总数
    keyTypedef_t *pSingleKey;  //该指针单个按键的结构参数
}keysTypedef_t; 

首先,我们一起学习一下keyTypedef_t和keysTypedef_t这两个结构体。

keyTypedef_t的作用是:用于保存一个按键的结构参数,结构参数包括:按键的外设时钟、按键所在的端口、按键所对应的IO口、按键长按的回调函数指针、按键短按的回调函数指针。其中,需要注意的参数是:keyNum,keyNum的作用是为每个按键参数结构体提供一个“编号”,也就是说,每个按键都有自己唯一的“编号”。

本实验主要想实现识别按键的长按与短按,并执行相应的回调函数。回调函数的定义请参考上文代码块的“行1”的定义,该函数指针是无返回值且是无形参的。

keysTypedef_t的作用是:如果有多个按键,首先需要定义类型为keyTypedef_t的结构体数组,而定义一个keysTypedef_t变量来统一管理所有的按键结构体参数。其中16行的指针pSingleKey在初始化按键后,会指向“keyTypedef_t的结构体数组”,通过指针偏移来简介访问所有的按键结构参数,从而实现“统一管理所有的按键”。

在本章节,需要实现两个按键的长短按的识别,keyTypedef_t和keysTypedef_t的使用如下:

1
2
3

#define GPIO_KEY_NUM  2                                 //定义按键总数
keyTypedef_t singleKey[GPIO_KEY_NUM];                   //定义单按键结构体数组
keysTypedef_t keys;                                     //定义总体按键模型结构变量

1:定义一个宏,决定按键的总数

2:定义单按键结构参数数组,有多少个按键,则该数组需要有多少个成员。

3:定义总体按键结构体参数变量,该结构体变量用于统一管理所有所有的按键。

 

2.初始化按键参数

定义好“单按键结构参数数组singleKey”以及“总体按键结构变量keys”后,需要初始化按键的结构参数,结构参数包括:编号、按键的外设时钟、按键所在的端口、按键所对应的IO口、按键长按的回调函数指针、按键短按的回调函数指针。用到的函数是keyInitOnekeyInitOne函数原形如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

static uint8_t          keyTotolNum = 0;       //有与统计有多少个按键
/**
* @brief key init function 按键初始化函数
* @param [in] keyRccPeriph :APB2外设时钟
* @param [in] keyPort:IO端口
* @param [in] keyGpio:IO管脚
* @param [in] short_press :按键短按回调函数
* @param [in] long_press  :按键长按回到函数
* @return key structure pointer
*/
keyTypedef_t keyInitOne(uint32_t keyRccPeriph,
                        GPIO_TypeDef * keyPort, 
                        uint32_t keyGpio, 
                        keyCallBackFuction shortPress, 
                        keyCallBackFuction longPress)
{
  static uint8_t key_total = 0; 

  keyTypedef_t singleKey;       //结构体变量,用于保存按键IO的参数

  //平台定义IO口
  singleKey.keyRccPeriph = keyRccPeriph; //保存按键IO口所在的外设时钟总线
  singleKey.keyPort = keyPort;           //保存按键所在的端口
  singleKey.keyGpio = keyGpio;           //保存按键所在的IO口
  singleKey.keyNum = key_total++;        //保存每个按键都有单独的编号
  //回调函数定义,按键长按和短按各有自己的回调函数
  singleKey.longPress = longPress;       //保存按键长按函数指针
  singleKey.shortPress = shortPress;     //保存按键短按函数指针

  keyTotolNum++;      //全局静态变量,统计有多少个按键

  return singleKey;   //返回初始化的按键IO的参数的结构体变量
}

首先,该keyInitOne函数的形参有:keyRccPeriph APB2外设时钟, keyPortIO端口,keyGpioIO管脚,short_press →按键短按回调函数指针,long_press  →按键长按回调函数指针。

假设,PA8管脚上挂在有一个按键,则需要将PA8的参数保存起来,则形参keyRccPeriph 需要传入的实参是RCC_APB2Periph_GPIOA 形参keyPort需要传入的是GPIOA,形参keyGpio需要传入的实数是GPIO_Pin_8,形参short_press 需要传入的是按键短按回调函数指针,long_press  →按键长按回调函数指针。

下面,我们一起学习一下上文的代码块:

1:全局静态变量,用于统计总共有初始化了多少个按键

17:静态变量,每初始化一个按键,该变量自增1

19:定义keyTypedef_t类型的局部变量 singleKey,用于保存按键的的结构参数(外设时钟、端口、IO管脚、按键短按回调函数指针、按键长按回调函数指针)。

22~24:保存按键所对应的外设时钟总线、端口、IO管脚。

25:保存按键的标号。

27~28:保存按键所对应的长按函数指针和短按函数指针。

30:初始化了的按键总数自增1

32:返回定义keyTypedef_t类型的局部变量 singleKey,返回保存按键的的结构参数(外设时钟、端口、IO管脚、按键短按回调函数指针、按键长按回调函数指针)。

假设PA8挂有一个按键Key2PB10挂有一个按键Key3,则Key2Key3的初始化代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

#define GPIO_KEY_NUM  2                                 //定义按键总数
keyTypedef_t singleKey[GPIO_KEY_NUM];                   //定义单按键结构体数组
keysTypedef_t keys;                                     //定义总体按键模型结构变量


/**
* 按键3短按回调函数
* @param none
* @return none
*/
void key3ShortPress(void)
{
    Led_Reverse(1);   //短按KEY3,则取反LED1
    
}

/**
* 按键3长按回调函数
* @param none
* @return none
*/
void key3LongPress(void)
{
    Led_Reverse(3);  //长按KEY3,则取反LED3
}

/**
* KEY2短按回调函数
* @param none
* @return none
*/
void key2ShortPress(void)
{
    Led_Reverse(1);  //短按KEY2,则取反LED1
}

/**
* 按键2长按回调函数
* @param none
* @return none
*/
void key2LongPress(void)
{
    Led_Reverse(2); //长按KEY2,则取反LED2
}

/**
* 按键初始化函数
* @param none
* @return none
*/
void keyInit(void)
{
    /*将按键端口参数保存到singleKey结构体数组中,
     参数包括:外设端口、IO端口、IO管脚、长按函数指针、短按函数指针*/
    singleKey[0] = keyInitOne(RCC_APB2Periph_GPIOB, 
                              GPIOB, GPIO_Pin_10, 
                              key3ShortPress, key3LongPress);  
    singleKey[1] = keyInitOne(RCC_APB2Periph_GPIOA, 
                              GPIOA, GPIO_Pin_8, 
                              key2ShortPress, key2LongPress);
    keys.pSingleKey = (keyTypedef_t *)&singleKey;   //pSingKey指针指向singleKey数组
    keyParaInit(&keys);    //初始化按键所在的IO口并初始化定时器
}

2行:定义一个keyTypedef_t类型的数组 singleKey[GPIO_KEY_NUM],GPIO_KEY_NUM的值为2。一个按键对应一个数组元素。

11~15行:Key3短按回调函数。

22~25行:Key3长按回调函数。

32~35行:Key2短按回调函数。

42~45行:Key2长按回调函数。

3行:定义一个keysTypedef_t 类型的变量keys,用于统一管理所有的按键参数(外设时钟、端口、IO管脚、按键短按回调函数指针、按键长按回调函数指针)。

56~58行:keyInitOne函数原型在前文已经描述。调用keyInitOne函数,将Key3的按键参数保存到元素singleKey[0] 中。参数包括:外设时钟RCC_APB2Periph_GPIOB、端口GPIOBIO管脚GPIO_Pin_10、按键短按回调函数指针key3ShortPress、按键长按回调函数指针 key3LongPress。

59~61行:keyInitOne函数原型在前文已经描述。调用keyInitOne函数,将Key2的按键参数保存到元素singleKey[1] 中。参数包括:外设时钟RCC_APB2Periph_GPIOA、端口GPIOAIO管脚GPIO_Pin_8、按键短按回调函数指针key2ShortPress、按键长按回调函数指针key2LongPress。

本实验最终要实现的是,获知按键时长按还是短按,根据长按还是短按执行相应的回调函数,每个按键有单独的长按回调函数和短按回调函数。

62行:定义一个keysTypedef_t 类型的变量keys的指针成员pSingleKey指向keyTypedef_t类型的数组 singleKey[GPIO_KEY_NUM],作用是为了实现用变量keys的指针成员pSingleKey来管理所有按键的参数。

63行:按键IO口的初始化需要配置端口上下拉模式、端口速度等,前文讨论的keyInitOne函数只是用于保存按键IO口的参数,并不是用来初始化IO口,真正初始化IO口的是keyParaInit函数。

下面,我们一起来学习keyParaInit函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

/**
* @brief 按键参数初始化函数:按键GPIO初始化,并启动定时器来检测按键状态
* @param [in] pkeyS :按键全局结构体,改指针包含了所有的按键的参数
* @return none
*/
void keyParaInit(keysTypedef_t *pkeyS)
{
  uint8_t i = 0;

  if(NULL == pkeyS)  //判断传入的指针是否有指向
  {
    return ;        //如果没有指向,则直接返回,不往下执行
  }

  pkeyS->keyTotolNum = keyTotolNum;  //获取按键总数
  /*误差判断,限制最多12个按键,可以通过修改宏KEY_MAX_NUMBER来实现支持更多的按键*/
  if( pkeyS->keyTotolNum > KEY_MAX_NUMBER)
  {
    pkeyS->keyTotolNum = KEY_MAX_NUMBER;
  }

  for(i = 0; i < pkeyS->keyTotolNum; i++)   //取出所有的按键的参数,采用的是下标偏移法
  {
    GPIO_InitTypeDef GPIO_InitStructure;  //定义结构体,用于初始化IO参数
        //取出某按键的外设端口时钟,并使能其时钟
    RCC_APB2PeriphClockCmd(pkeyS->pSingleKey[i].keyRccPeriph, ENABLE);  

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  //使能IO口速度
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;      //使能IO口的模式:上拉模式
    GPIO_InitStructure.GPIO_Pin = pkeyS->pSingleKey[i].keyGpio;   //取出某按键的管脚编号
    GPIO_Init(pkeyS->pSingleKey[i].keyPort,&GPIO_InitStructure);//取出某按键的端口号,进行按键初始化
  }

  timer4Init();   //初始化定时器2,每隔1ms进入一次更新中断
}

6行:keyParaInit(keysTypedef_t *pkeyS)函数的形参是keysTypedef_t类型的指针,该指针包含了所有按键的结构参数。

10~13行:判断传入的指针是不是空指针,如果是空指针,则返回,不继续往下执行。

15行: keyTotolNum记录了有多少个按键需要进行初始化,将 keyTotolNum的值保存到pkeyS->keyTotolNum的成员中。

17~20行:误差滤除,最多支持的按键不能超过12个。

22行:通过循环,改变下表i的值,来获得每个按键的结构体参数。

26~31行:取出按键的结构参数来进行按键IO口的初始化。

34行:定时器4初始化,使得1ms产生一次中断。

 

3.按键长短按管理

按键的长短的原理就是通过判断按键按下的时间长度,所以必须使用到定时器。本实验的做法是,通过定时器定时产生中断:每1ms触发一次定时器更新中断,然后在更新中断中进行进行按键长短按的管理。定时4的初始化代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

/**
* @brief 定时器2初始化函数1ms中断一次,TIM2的

*
* @param none
* @return none
*/
void timer4Init(void)
{
  u16 arr = 7199//自动重装载值
  u16 psc = 9;    //预分频值
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  NVIC_InitTypeDef NVIC_InitStructure;

  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //定时器4时钟使能

  /*定时器4初始化*/
  TIM_TimeBaseStructure.TIM_Period = arr;              //设置重载值
  TIM_TimeBaseStructure.TIM_Prescaler = psc;           //设置分频值
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //设置分频因子
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //递增计数模式
  TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);             //将结构参数用于初始化定时器4

  TIM_ITConfig(TIM4, TIM_IT_Update,ENABLE );           //使能定时器4更新中断

  /*设置中断参数*/
  NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;      //定时器4中断通道
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //抢占优先级
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;         //响应优先级
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            //使能中断通道
  NVIC_Init(&NVIC_InitStructure);                            //初始化中断
  TIM_Cmd(TIM4, ENABLE);  //使能定时器
}

定时器4的时钟频率是72MHZ,本实验对72MHZ的内部时钟进行10分频,计数器技术到7200则触发溢出中断,实现1ms触发一次更新中断。

关于定时器4的初始化代码,不作过多的分析,我们来学习一下定时器4的更新中断函数:

1
2
3
4
5
6
7
8
9
10
11
12
13

/**
* @brief 定时器4中断函数,检测按键的状态
* @param none
* @return none
*/
void TIM4_IRQHandler(void)
{
  if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)   //每隔1ms进入一次更新中断
  {
    TIM_ClearITPendingBit(TIM4, TIM_IT_Update);      //清除标志位
    keyHandle((keysTypedef_t *)&keys);               //每1ms检测一次按键的状态
  }
}

11行:在定时器4更新中断函数中,每隔1ms,进行一次按键扫描管理,按键扫描管理工作包括:检测检测按键是否按下,检测按键按下的了多长的时间。接下来我们一起讨论一下 keyHandle函数,需要注意的是 keyHandle函数的实参是keys,该变量包含了所有按键的结构信息,包括按键编号、按键长按回调函数、按键短按回调函数。

首先我们要知道keyHandle函数调用了readKeyValue函数来获取按键是短按还是长按,而readKeyValue函数调用了getKey函数获知哪个按键按下,哪个按键松开。

为了更好的理解keyHandle函数,我们需要先理解getKey函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

/**
* @brief 读取按键的状态,按键按下都是低电平
* @param keys : 按键全局结构体指针Key
* @return 按键的状态
*/
uint16_t getKey(keysTypedef_t *keyS)
{
  uint8_t i = 0;
  uint16_t readKey = 0//使用一个16位的变量的低12位用来保存12个按键的状态值,如果按键按下,位置1

  for(i = 0; i < keys.keyTotolNum; i++)  //通过循环来读取所有的按键值
  {
    if(!GPIO_ReadInputDataBit(
                              (GPIO_TypeDef*)keyS->pSingleKey[i].keyPort,
                              keyS->pSingleKey[i].keyGpio)
                             )
    {
      G_SET_BIT(readKey, keyS->pSingleKey[i].keyNum);  //置位   
    }
  }
  return readKey;
}

getKey函数传入的实参是包含了所有的按键的结构信息(外设时钟、端口、IO管脚、按键短按回调函数指针、按键长按回调函数指针)。

9行:使用一个16位的变量的低12位用来保存12个按键的状态值,如果按键按下,位置1
11行:使用初始化的按键总数keyTotolNum做为循环条件的上限控制,循环控制进行按键初始化。

13行:获取哪个按键按下,如果按键按下,则相应的IO口输入的是低电平,保存到寄存器的是数字信号0,所以13行的条件语句会成立。

18行:获知按下的是按个按键,进行置位操作。每个按键都有一个位域空间与其对应。如果按键按下,则位域置一,如果按键松开,位域清零。

21行:返回所有按键的位域值。

getKey函数是被readKeyValue函数用于获知所有的按键是被按下还是被松开。接下来我们来分析readKeyValue函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

#define KEY_TIMER_MS                            1
#define KEY_MAX_NUMBER                          12      //最多12个按键
#define SCAN_TIME                               30
#define PRESS_LONG_TIME                         3000
/**
* @brief Read the KEY value 读取所有按键的按键值
* @param [in] keys :Key global structure pointer
* @return GPIO status
*/
uint16_t readKeyValue(keysTypedef_t *keyS)
{
  static uint8_t keyScanFlag = 0;
  static uint8_t keyState = 0;
  static uint16_t keyLongPressCnt = 0;
  static uint16_t lastKeyValue = 0;      //last key

  uint16_t newKeyValue = 0;
  uint16_t keyReturnValue = 0;

  keyCountTime ++;     //每1ms加1

  if(keyCountTime >= (SCAN_TIME / KEY_TIMER_MS))     //该条件语句是为了30ms扫描一次按键,改变宏的值可以实现灵活控制按键扫描频率
  {
    keyCountTime = 0;
    keyScanFlag = 1;     //标志位置1,需要进行一次按键扫描,使用标志位来控制按键的扫描速率
  }
  if(1 == keyScanFlag)   
  {
    keyScanFlag = 0;            //按键扫描标志位清零
    newKeyValue = getKey(keyS); //读取按键的值,还需要进行位域判断才能获知按下的按键是哪个
    switch (keyState)
    {
    case 0:
      if(newKeyValue != 0)        //如果这个值不为0,则表示有按键按下
      {
        lastKeyValue = newKeyValue;  //保存按下的值
        keyState = 1;        //切换到下一个case
      }
      break;

    case 1:
      if(newKeyValue == lastKeyValue) //如果按键持续按下,则newKeyPressVal值和上次一样,
      {
        keyState = 2;        //切换到下一个case
        keyReturnValue= lastKeyValue | KEY_PRESS_DOWN;  //置位,表示按键按下了
      }
      else                     //按键松开或者按键抖动,不做任何的响应,又回到case零
      {
        keyState = 0;
      }
      break;

    case 2:
      if(newKeyValue != lastKeyValue)   //按键松开了
      {
        keyState = 0;         //回到case 0
        keyLongPressCnt = 0;     //清零
        keyReturnValue = lastKeyValue | KEY_SHORT_PRESS;       //按键按下,又松开了
        return keyReturnValue;
      }
      if(newKeyValue == lastKeyValue)  //如果按键仍然按下,则这两个值继续相等
      {
        keyLongPressCnt++;      //统计长按按下的次数
        if(keyLongPressCnt >= (PRESS_LONG_TIME / SCAN_TIME))  //长按3秒有效
        {
          keyLongPressCnt = 0;//按键统计次数需要进行清零
          keyState = 3;    //切换到case3
          keyReturnValue= newKeyValue | KEY_LONG_PRESS; //按键长按
          return keyReturnValue;   //返回按键值
        }
      }
      break;

    case 3:
      if(newKeyValue != lastKeyValue)  //长按过后,如果按键松开了,则清零,再回到case 0
      {
        keyState = 0;
      }
      break;
    }
  }
  return  NO_KEY_PRESS_DOWN;
}

readKeyValue函数传入的参数是:keysTypedef_t *keyS,该参数包含了所有按键的信息(编号、外设时钟、端口、IO管脚、按键短按回调函数指针、按键长按回调函数指针)。

readKeyValue函数每隔1毫秒会被调用一次。

3行:SCAN_TIME 用于控制按键扫描频率。

4行:PRESS_LONG_TIME用于控制按键长按的时间长度。

20~27行:keyCountTime静态变量每隔1毫秒会自增1,当如果(keyCountTime >= (SCAN_TIME / KEY_TIMER_MS)),则进行一次按键扫描,这样子写的目的是:为了控制按键的扫描频率。 keyScanFlag1的表示,需要进行一次按键扫描。

31行:调用 getKey(keyS)函数来获取所有按键的按下与松开的状态, getKey(keyS)函数的返回值是一个16位的变量,该变量的低12位的每一个位域对应一个按键的状态。(位域值为1则表示按键按下,位域值为0则表示按键松开)。

本实验识别按键长短按的方法是“四状态法”:

34~39行:状态0。如果有按键按下,则newKeyValue值肯定不为0,如果有按键按下,则进入“状态1”。小结:状态0是用于判断按键是否按下。

42~52行:状态1。适用于判断按键经过抖动器后,按键是否按下的,如果还是按下的,则表示按键进入了“稳定按下”的状态,如果有按键按下,则:keyReturnValue = lastKeyValue | KEY_SHORT_PRESS,则keyReturnValue的值包含了按键位域值以及当前按键的属性:KEY_SHORT_PRESS。小结:“状态1”用于判断按键消抖后是否是稳定按下。

54~73行:状态2。改状态用于判断按键是长按还是短按,其中用变量keyLongPressCnt来统计按键按下的次数,从而知道按键按下的时间长度。

55~61行:如果最新按键位域值newKeyValue和最早的按键位域值lastKeyValue不相等,则表示按键是短按。

62~71行:如果最新按键位域值newKeyValue和最早的按键位域值lastKeyValue一直相等,则变量keyLongPressCnt递增,当如果变量keyLongPressCnt递增到大于(PRESS_LONG_TIME / SCAN_TIME),则表示按键是长按。则 keyReturnValue= newKeyValue | KEY_LONG_PRESS,则keyReturnValue的值包含了按键位域值以及当前按键的属性:KEY_LONG_PRESS。

75~80:“状态3”。如果最新按键位域值newKeyValue和最早的按键位域值lastKeyValue不相等,则表明按键松开了,则重新回到“状态0”。

分析完readKeyValue函数后,我们来一起分析keyHandle(keysTypedef_t *pkeyS)函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

/**
* @brief 检测按键是长按还是短按,并执行相应的回调函数
* @param [in] keys : 全局按键参数结构体
* @return none
*/
void keyHandle(keysTypedef_t *pkeyS)    //定时处理函数,每个10ms执行一次
{
  uint8_t i = 0;
  uint16_t key_value = 0;

  key_value = readKeyValue(pkeyS);    //读取按键的值

  if(!key_value) return//如果按键值为0,则返回,不往下执行

  /*检测是哪个按键短按*/
  if(key_value & KEY_SHORT_PRESS)
  {
    for(i = 0; i < pkeyS->keyTotolNum; i++)  //通过循环来判断所有的按键
    {
      if(G_IS_BIT_SET(key_value, pkeyS->pSingleKey[i].keyNum))   //判断是不是置位了
      {
        if(pkeyS->pSingleKey[i].shortPress)     //如果有短按回调函数
        {
          pkeyS->pSingleKey[i].shortPress();  //执行短按回调函数

        }
      }
    }
  }

  /*检测是哪个按键长按*/
  if(key_value & KEY_LONG_PRESS)
  {
    for(i = 0; i < pkeyS->keyTotolNum; i++)  //循环判断所有按键
    {
      if(G_IS_BIT_SET(key_value, pkeyS->pSingleKey[i].keyNum))  //是不是置位了
      {
        if(pkeyS->pSingleKey[i].longPress)    //如果有按键长按回调函数
        {
          pkeyS->pSingleKey[i].longPress(); //执行按键长按回调函数
        }
      }
    }
  }
}

11行:读取所有按键的位域值和以及长按、短按的属性。

16~29行:判断按键是不是短按,如果是短按,则根据按键的位域,来判断是按个按键按下,并执行相应的短按回调函数。

32~43行:判断按键是不是长按,如果是短按,则根据按键的位域,来判断是按个按键按下,并执行相应的长按回调函数。

4.总结

1) 本实验按键最多支持12个,并且每个按键都有单独的长按回调函数和短按回调函数。

2) 只支持“按下是低电平、松开是高电平的按键”。

1.4 下载验证

短按KEY2KEY3则取反LED1,长按KEY2则取反LED2,长按KEY3则取反LED3

你可能感兴趣的:(STM32)