历史上的今天
返回首页

历史上的今天

今天是:2024年08月24日(星期六)

2019年08月24日 | STM32按键长短按:超强移植性,回调函数按键处理机制

2019-08-24 来源:eefocus

1.1 实验简介

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


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


1.2 硬件设计

 


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


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


 


1.3 软件设计

1.构造按键参数结构体


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的使用如下:


#define GPIO_KEY_NUM  2                                 //定义按键总数

keyTypedef_t singleKey[GPIO_KEY_NUM];                   //定义单按键结构体数组

keysTypedef_t keys;                                     //定义总体按键模型结构变量


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


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


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


 


2.初始化按键参数


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


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外设时钟, keyPort→IO端口,keyGpio→IO管脚,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挂有一个按键Key2、PB10挂有一个按键Key3,则Key2和Key3的初始化代码如下所示:


#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、端口GPIOB、IO管脚GPIO_Pin_10、按键短按回调函数指针key3ShortPress、按键长按回调函数指针 key3LongPress。


59~61行:keyInitOne函数原型在前文已经描述。调用keyInitOne函数,将Key2的按键参数保存到元素singleKey[1] 中。参数包括:外设时钟RCC_APB2Periph_GPIOA、端口GPIOA、IO管脚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函数。


/**

* @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的初始化代码如下:


/**

* @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

推荐阅读

史海拾趣

德力康(DLK)公司的发展小趣事

作为一家有社会责任感的企业,DLK公司始终将社会责任和可持续发展作为企业发展的重要内容。公司积极参与公益事业和社会活动,为当地经济发展和社会进步做出了积极贡献。同时,DLK公司注重环保和节能工作,采用环保材料和生产工艺,减少了对环境的污染和破坏。通过履行社会责任和推动可持续发展,DLK公司赢得了社会的广泛认可和尊重。

请注意,以上故事框架仅供参考,具体的故事内容需要根据公司的实际情况和具体事件进行编写。

亿宝科技(CNIBAO)公司的发展小趣事

随着环保意识的不断提高,亿宝科技积极响应国家绿色发展的号召。公司引进先进的环保设备和技术,减少生产过程中的污染物排放。同时,亿宝科技还注重产品的环保性能设计,推出了一系列绿色电子产品。这些产品不仅符合国家的环保标准,还赢得了消费者的青睐和认可。在绿色发展的道路上,亿宝科技展现了企业的责任和担当。

Califia Lighting公司的发展小趣事

在环保理念日益深入人心的背景下,Califia Lighting积极响应国家号召,致力于推动绿色照明技术的发展。公司研发了一系列环保型LED产品,降低了能源消耗和环境污染。同时,公司还关注未来照明技术的发展趋势,不断探索新的应用领域和市场机会,为公司的可持续发展奠定了坚实基础。

通过以上五个故事,我们可以看到Califia Lighting公司在电子行业里发展起来的艰辛与辉煌。他们凭借技术创新、市场拓展、品质管理和绿色发展等方面的不断努力,逐渐成为了电子照明行业的佼佼者。

海芯科技(AVIA)公司的发展小趣事

面对电子行业的快速变化和市场竞争的加剧,海芯科技始终保持着对技术创新的追求和投入。公司不断引进新技术、新工艺和新材料,对现有产品进行升级和改进,同时也在不断探索和研发新的产品和技术。这些技术升级和创新发展不仅提升了公司的核心竞争力,也为公司在未来市场竞争中保持领先地位提供了有力保障。

这五个故事展示了海芯科技在电子行业中的发展历程和取得的成就。通过不断的努力和创新,海芯科技已经逐渐成为了电子行业中的佼佼者,为行业的发展做出了积极的贡献。

长江连接器(CJT)公司的发展小趣事

随着技术的不断积累和市场的不断扩大,长江连接器开始积极拓展国内外市场。公司通过与国内外知名企业的合作,将产品广泛应用于消费类电子、电气电子、薄膜开关、LED显示器屏/灯饰、家用电器、电脑及电脑周边、通讯设备等领域。同时,长江连接器注重品牌形象的塑造和推广,通过参加各类行业展会、举办技术交流会等方式提升品牌知名度和美誉度。

D3公司的发展小趣事

D3公司非常重视企业文化的建设。公司倡导“以人为本、诚信经营”的企业文化,为员工提供良好的工作环境和发展机会。同时,公司还注重培养员工的团队协作精神和创新意识。在这种企业文化的熏陶下,D3公司的员工们团结一心、锐意进取,共同为公司的发展贡献力量。这种强大的团队力量,是D3公司在电子行业中不断取得成功的关键因素之一。

请注意,以上故事均为虚构,旨在展示电子行业中一家公司可能的发展路径和策略。在实际应用中,企业需要根据自身情况和市场环境来制定合适的发展战略。

问答坊 | AI 解惑

LM358可以做延时不?请教各路高手

请问各为高手 LM358可以做延时不,资料上说可以做比较器,我现在想做一个延时的比较的器,不知道能做不,谢谢各位高手指点 谢谢…

查看全部问答>

CORDIC

上传一些资料…

查看全部问答>

我想做个无线设备,100K的频率,数据量很小(一个开关量),用什么比较好?

我想做个无线设备,100K的频率,数据量很小(一个开关量),用什么比较好?红外还是RF?…

查看全部问答>

关于内核api

     本人接触过一段时间的驱动开发,知道有一些函数是内核专用的,当时用的时候是装了DDK写的,但我想知道那些内核的api在不装DDK时是否存在于windows系统中的一个文件中呢?就像ring3下的api常常在kernel32.dll中一样。高手 ...…

查看全部问答>

显示器的Power Saving Mode是通过软体还是硬件来实现的?

显示器的Power Saving Mode即是显示器休眠省电模式,主要是在电脑没有运行任何程序长时间无人使用时的一种节省电源的一种模式,它是通过软体程序还是通过硬件来控制的呢?…

查看全部问答>

【TPS61085】驱动LED【电流】上不去的问题

大二学生一枚,现为完成一个小实验项目需用tps61085驱动led。 需实现: 1、100mA以上电流(按道理对于61085来说应该是小菜一碟的); 2、3mA步进可控,精度0.2mA(电流采样INA282); 3、瞬时大电流闪光; 4、添加光敏电阻实现自动调光(不过这 ...…

查看全部问答>

最新 ADI的集成电源方案

ADP50xx系列电路,  4.5至15V的输入范围,集成开关型1.2A~4A的BUCK电路,以及LDO等。 是一款性能优良,功能完备的集成电源解决方案。 …

查看全部问答>

开关电源共模噪声 讨论

最近在调一个开关电源 用的是34063 好不容易把纹波调到10mv 但是用示波器测试地电位的时候会有类似于电感输出端形状的 波形 之前有人告诉我是共模噪声 。。。。。。。。。。 我用的极性反转的拓扑 理论上确实是电感通过地线把电流送到开关二极管 ...…

查看全部问答>