大家好,我是讯享网,大家多多关注。
今年实验室来了三个大三女生,其中一个之前是物联网专业的。等她进了实验室,老师二话不说:先过STM32单片机,有问题问小弟。还好单片机的弟弟会一点,但是玩不好,大家一起学习吧!
过去的第一个套路就是用钥匙点亮一个LED灯,小子。弟弟擅长灯光。毕竟32,FPGA,Linux的小灯已经被小弟点亮了。哈哈哈!所以今天就来说说重点检测吧!
一、如何进行按键检测
检测按键有两种方式:中断模式和GPIO查询模式。建议您使用GPIO查询方法。
1.从裸机的角度分析中断方式:中断方式可以快速地检测到按键按下,并执行相应的按键程序,但实际情况是由于按键的机械抖动特性,在程序进入中断后必须进行滤波处理才能判定是否有效的按键事件。如果每个按键都是独立的接一个 IO 引脚,需要我们给每个 IO 都设置一个中断,程序中过多的中断会影响系统的稳定性。中断方式跨平台移植困难。查询方式:查询方式有一个最大的缺点就是需要程序定期的去执行查询,耗费一定的系统资源。实际上耗费不了多大的系统资源,因为这种查询方式也只是查询按键是否按下,按键事件的执行还是在主程序里面实现。2.从OS的角度分析中断方式:在 OS 中要尽可能少用中断方式,因为在RTOS中过多的使用中断会影响系统的稳定性和可预见性。只有比较重要的事件处理需要用中断的方式。查询方式:对于用户按键推荐使用这种查询方式来实现,现在的OS基本都带有CPU利用率的功能,这个按键FIFO占用的还是很小的,基本都在1%以下。二、最简单的按键检测程序
首先,我告诉他一个经典的钥匙检测代码。我相信大多数人都见过它使用关键功能。很简单,就不多介绍了!
# define KEY 0 _ PRES 1//KEY 0 # define KEY 1 _ PRES 2//KEY 1 # define WKUP _ PRES 3//WK _ UP u8 KEY _ Scan(u8模式){ static u8 KEY _ UP = 1;//如果(mode)key_up=1,则按下释放标志;//支持双击if(key _ up & &(key 0 = = 0 | | key 1 = = 0 | wk _ up = = 1)){ delay _ ms(10);//去抖key _ up = 0;if(KEY0==0)返回KEY0 _ PRESelse if(KEY1==0)返回KEY1 _ PRESelse if(WK_UP==1)返回WKUP _ PRES} else if(key 0 = = 1 & & key 1 = = 1 & & WK _ UP = = 0)key _ UP = 1;返回0;//没有按键} int main(void){ u8t = 0;delay_init()。delay函数初始化LED _ Init();//初始化LED连接的硬件接口KEY _ Init();//初始化key = 0连接的硬件接口LED = 0;//打开LED的同时(1){ t = KEY _ Scan(0);//获取键值switch(t){ case key 0 _ PRES://If key 0按LED=!LED打破;默认值:delay _ ms(10);}}}如果你在工作中使用这个代码,你可能会被同事嘲笑。当然,我这里并不是说这种代码不好。不管黑猫白猫,抓老鼠就是好猫。只要能满足项目的需求,实现相应的功能,就是好代码。但如果用以下个人感觉可能会更好。
其实没什么玄机,就是用了FIFO机制。参考的是安福莱的按钮例程,但是源代码相对复杂,对初学者不太友好,所以做了轻微修改,仅供参考!
前一部分分享了用系统滴答定时器实现多个软件定时器,key FIFO中也需要这个定时器。在系统开始时,我们将启动一个10ms的软件定时器。在这个10ms的软件定时器中,连续进行按键扫描,对其他任务没有影响。
三、为什么要了解FIFO
要回答FIFO是什么,先回答为什么要用FIFO。只有搞清楚使用FIFO的好处,你才会有意无意地使用FIFO。学习FIFO机制和状态机机制一样,是裸机编程中非常重要的编程思想。编程思路很重要。初级程序员总是关注代码是怎么写的,而高级程序员关注的是程序的框架逻辑,而不是一些细节。只要你的框架逻辑合乎逻辑,就没问题。
四、什么是FIFO
FIFO的意思是先进先出,即谁先进入队列,谁就先出去。例如,我们需要串口打印数据。当我们用缓存保存数据的时候,当我们输出数据的时候,先进入的数据必须先出去。那么如何实现这个机制呢?首先建立一个缓存空,假设为10字节空。
从该图可知,如果要使用FIFO,就要定义一个结构,这个结构至少要有三个成员。数组buf,读指针read,写指针write。
typedef struct { uint 8 _ t Buf[10];/* buffer */uint 8 _ t Read;/*缓冲区读指针*/uint 8 _ t Write;/*缓冲区写指针*/} KEY _ FIFO _ T;一开始缓存中没有数据,一个变量write表示下一个写入缓存的索引地址,其中下一个存储位置为0,另一个变量read表示下一个读出缓存的索引地址,下一个读出数据的索引地址也为0。目前队列中没有数据,即无法读出数据。这里队列为空的判断条件是两个指标值相同。
现在开始存储数据:
这里可以看到,队列中加入了9个数据,每加入一个数据后,队列的尾索引增加1,队列头保持不变。这是向队列添加数据的过程。但是,只有10个缓存空。我们如何知道队列已满?如果只是一次向队列中添加数据,然后再读出数据,这里的判断条件显然是队列尾索引为9。
好了,这就是FIFO的基本原理。让我们来看看关键的FIFO是如何工作的。
这里我们用一个5字节的FIFO空来说明。写变量表示写位置,读变量表示读位置。初始状态下,Read = Write = 0。
当我们依次按下K1和K2键时,FIFO中的数据变为:
如果写!=阅读,那么我们认为有一个新的关键事件。在我们通过函数KEY_FIFO_Get()读取一个键值进行处理后,读取的变量就变成了1。写变量不变。
KEY_FIFO_Get()函数读取并处理三个键值后,读取的变量变成4。此时,读=写= 4。这两个变量已经相等,表明没有新的键事件要处理。
需要特别注意的一点是,如果FIFO 空已满,写操作会被重新赋值为0,即从第一个字节空开始的数据会被重新填充。如果这个地址空的数据没有及时读出,就会被后面的数据覆盖。这一点大家应该注意到了。我们的驱动开辟了一个10字节的FIFO缓冲区,对于一般应用来说已经足够了。
五、按键FIFO的优点可靠地记录每一个按键事件,避免遗漏按键事件。特别是需要实现按键的按下、长按、自动连发、弹起等事件时。读取按键的函数可以设计为非阻塞的,不需要等待按键抖动滤波处理完毕。按键 FIFO 程序在嘀嗒定时器中定期的执行检测,不需要在主程序中一直做检测,这样可以有效地降低系统资源消耗。六、按键 FIFO 的实现1.定义结构体
在我们的key.h文件中定义一个结构类型为KEY_FIFO_T的结构。这就是前面提到的结构。这只是一个类型声明,变量空没有赋值。
typedef struct { uint 8 _ t Buf[10];/* buffer */uint 8 _ t Read;/*缓冲区读指针*/uint 8 _ t Write;/*缓冲区写指针*/} KEY _ FIFO _ T;然后,在key.c中定义s_tKey结构变量,此时编译器会分配一组变量空。
静态KEY _ FIFO _ T s _ tKey/* key FIFO变量,structure */好了,定义了key FIFO的结构数据类型。很简单!
2.将键值写入FIFO
既然已经定义了结构,那么是时候将数据(即键的键值)写入这个FIFO数组来模拟键的动作了。
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *,可以用来模拟一个键。*形参:_KeyCode: key code *返回值:none * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * if(++s _ tKey。写& gt= KEY_FIFO_SIZE) { s_tKey。写= 0;}}}函数的主要作用是将key code _KeyCode写入FIFO,这个FIFO就是我们定义的结构的数组成员。每调用一次KEY_FIFO_Put()函数,指针就写++一次,即向后移动一空格。如果FIFO空空间已满,= KEY_FIFO_SIZE,Write将被重新分配给0。
3.从FIFO读出键值
当然,写键值就是读键值。
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *形参:无*返回值:键码* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * if(s _ tKey。 Read == s_tKey。write){ return KEY _ NONE;} else { ret = s_tKey。Buf[s_tKey。阅读];if (++s_tKey。阅读& gt= KEY_FIFO_SIZE) { s_tKey。read = 0;ret返回;}}
3年嵌入式物联网学习资源整理与分享:C语言、Linux开发、数据结构;软件开发、STM32单片机、ARM硬件开发、物联网通信开发、综合项目开发教程资料;笔试面试问题。点击下方插件,免费领取嵌入式物联网学习资料(头条)。
如果写指针和读指针相等,则返回值为0,表示按键缓冲区为空,所有按键次数都已处理完毕。如果不相等,则FIFO的缓冲区不是空,Buf中的数字读出到ret变量。同样,如果FIFO空读完,没有缓存,即s _ tkey.read >: = KEY_FIFO_SIZE,read也会被重新赋值为0。key的键值在key.h文件中定义,以下是具体内容:
Typedef enum{ KEY_NONE = 0,/* 0 0表示按键事件*/ KEY_1_DOWN,/* 1键按下*/ KEY_1_UP,/* 1键弹出*/ KEY_1_LONG,/* 1键按下*/ KEY_2_DOWN,/* 2键按下。/* 2键弹出*/ KEY_2_LONG,/* 2键按压*/ KEY_3_DOWN,/* 3键按压*/ KEY_3_UP,/* 3键弹出*/ KEY_3_LONG,/* 3键按压*/} KEY _ ENUM;每个键的按下、弹出和长按事件必须按顺序定义,即每个key对象占用3个值。推荐用枚举代替#define,因为方便添加新键值和调整顺序。用{}封装一组相关定义,便于理解。编译器还可以帮助我们避免重复的键值。
4.按键检测程序
它说的是如何在FIFO中存储和读取按键的键值,但既然是按键操作,那肯定涉及到按键抖动消除,按键状态是按下还是弹起,长按还是短按。所以为了表示区别,我们需要为每个键设置很多参数,所以需要定义另一个结构KEY_T,让每个键对应一个全局结构变量。
下面的Typefstruct {/*是一个函数指针,指向确定是否手动按键的函数*/uint 8 _ t(* IsKeyDownFunc)(void);/*按键的判断功能,1表示按下*/ uint8_t计数;/*过滤计数器*/uint 16 _ t long count;/*长按计数器*/uint 16 _ t long time;/*按键持续时间,0表示没有检测到长按*/ uint8_t状态;/*键的当前状态(按下或弹起)*/uint 8 _ t repeat speed;/*连续按键周期*/ uint8_t重复计数;/*连续键计数器*/} KEY _ T;在key.c中定义s_tBtn结构数组变量
static KEY _ ts _ tBtn[3]= { 0 };每个关键对象都被赋予一个结构变量,这些结构变量以数组的形式存在,会方便我们简化程序代码的行数。因为我的硬件有3个键,所以这里的数组元素是3。函数指针IsKeyDownFunc可以用来统一管理每个键的检测和组合键的检测码。
因为函数指针必须先赋值才能作为函数执行。因此,在定期扫描按键之前,必须执行一个初始化函数来设置每个按键的函数指针和参数。函数是void KEY_Init(void)。
void KEY _ Init(void){ KEY _ FIFO _ Init();/*初始化关键变量*/KEY _ GPIO _ Config();/*初始化密钥硬件*/}下面是KEY_FIFO_Init函数的定义:
静态void KEY _ FIFO _ Init(void){ uint 8 _ t I;/*清除key FIFO读写指针*/ s_tKey。read = 0;s_tKey。写= 0;/*为每个键结构成员变量*/分配一组默认值(I = 0;我& lt硬键编号;i++) { s_tBtn[i]。LongTime = 100/*长按时间0表示未检测到长按事件*/ s_tBtn[i]。计数= 5/2;/*计数器设置为滤波时间的一半*/ s_tBtn[i]。状态= 0;/*该键的默认状态,0未按下*/s _ tbtn [i]。重复速度= 0;/*密钥突发的速度,0表示不支持突发*/s _ tbtn [i]。repeate count = 0;/*重复计数器*/}/*确定按键的功能*/s _ tbtn [0]。ISKEYDOWNFUNC = ISKEY1DOWNs_tBtn[1]。IsKeyDownFunc = IsKey2Downs_tBtn[2]。IsKeyDownFunc = IsKey3Down}我们知道按键会有机械抖动。你以为按键的时候就是低电平。其实按压的瞬间会有机械抖动。如果不延迟处理,可能会出错。一般来说,如果检测到按键被按下,然后延时50ms,如果仍然检测到低电平,就可以说明按键真的被按下了。另一方面,按钮弹出来也是一样的。所以我们的程序将按键过滤时间设置为50ms,因为代码每10ms扫描一次按键,所以我们可以将按键单位理解为10ms,过滤次数为5次。这样,只有连续检测到50ms的状态才认为有效,包括弹出和按下两个事件。即使按键电路不做硬件滤波(无容性滤波),这种滤波机制也能保证按键事件的可靠检测。
判断按键是否按下,可以用一个HAL_GPIO_ReadPin来完成。
静态uint 8 _ t is key 1 down(void){ if(HAL _ GPIO _ read PIN(GPIOE,GPIO_PIN_4) == GPIO_PIN_RESET)返回1;否则返回0;} static uint 8 _ t is key 2 down(void){ if(HAL _ GPIO _ read PIN(GPIOE,GPIO_PIN_3) == GPIO_PIN_RESET)返回1;否则返回0;} static uint 8 _ t is key 3 down(void){ if(HAL _ GPIO _ read PIN(GPIOE,GPIO_PIN_2) == GPIO_PIN_RESET)返回1;否则返回0;}以下是KEY_GPIO_Config函数的定义,用来配置具体的key GPIO,不需要过多解释。
静态void KEY _ GPIO _ Config(void){ GPIO _ init typedef GPIO _ init structure;/*第一步:打开GPIO时钟*/_ _ HAL _ RCC _ GPIOE _ CLK _ ENABLE();/*第二步:将所有按键GPIO配置为浮点输入模式(实际上是CPU复位后会处于输入状态)*/GPIO _ init structure . mode = GPIO _ mode _ input;/*设置输入*/gpio _ init structure . pull = gpio _ no pull;/*上拉和下拉电阻不使能*/gpio _ init structure . speed = gpio _ speed _ freq _ very _ high;/* GPIO速度级别*/GPIO _ init structure . pin = GPIO _ pin _ 4;HAL_GPIO_Init(GPIOB,& GPIO _ Init structure);GPIO_InitStructure。PIN = GPIO _ PIN _ 3;HAL_GPIO_Init(GPIOB,& GPIO _ Init structure);GPIO_InitStructure。PIN = GPIO _ PIN _ 2;HAL_GPIO_Init(GPIOB,& GPIO _ Init structure);}5.按键扫描按键扫描功能KEY_Scan()每10ms执行一次。RunPer10ms函数在systick中断服务程序中执行。
void runper 10ms(void){ KEY _ Scan();} void KEY _ Scan(void){ uint 8 _ t I;for(I = 0;我& lt硬键编号;i++){ KEY _ Detect(I);}}/*每10ms将扫描并检测所有按键GPIO。KEY_Detect函数实现如下:
静态void KEY _ Detect(uint 8 _ T I){ KEY _ T * pBtn;pBtn = & s _ tBtn[I];if(pBtn-& gt;IsKeyDownFunc()){///这是按键处理if(pbtn–>:Count & lt;KEY_FILTER_TIME) {//在按键过滤之前为Count设置一个初始值pbtn->:Count = KEY _ FILTER _ TIME;} else if(pBtn-& gt;计数& lt2 * KEY_FILTER_TIME) {//实现KEY_FILTER_TIME pbtn的延时–>:count++;} else { if(pBtn-& gt;state = = 0){ pBtn-& gt;状态= 1;/*发送按钮被按下的消息*/KEY _ FIFO _ Put((uint 8 _ t)(3 * I+1));} if(pBtn-& gt;LongTime & gt0){ if(pBtn-& gt;LongCount & ltpBtn->;LongTime) {/*发送按钮被连续按下的消息*/if(++pbtn–>;long count = = pBtn-& gt;LongTime) {/* key值放入key FIFO */key _ FIFO _ put((uint 8 _ t)(3 * I+3));} } else { if(pBtn-& gt;RepeatSpeed & gt0){ if(++pbtn-& gt;重复计数& gt= pBtn-& gt;repeat speed){ pBtn-& gt;repeat count = 0;/*长时间按键后,每10ms发送1次按键*/KEY _ FIFO _ Put((uint 8 _ t)(3 * I+1));} } } } } } else {///这是释放按键的过程还是不按键的过程if(pbtn–>;Count & gtKEY _ FILTER _ TIME){ pBtn-& gt;Count = KEY _ FILTER _ TIME} else if(pBtn-& gt;数数!= 0){ pBtn-& gt;count-;} else { if(pBtn-& gt;state = = 1){ pBtn-& gt;状态= 0;/*发送按钮弹出的消息*/KEY _ FIFO _ Put((uint 8 _ t)(3 * I+2));} } pBtn-& gt;LongCount = 0;pBtn->;repeat count = 0;}}这个函数还是比较难理解的,主要是结构的操作。所以好好学习结构,不要看到就跑。
解析:首先读取对应键的结构地址,赋给结构指针变量pBtn。因为程序中的每个键都有自己的结构,只有这样才能操作具体的键。(我们之前用软件定时器的时候也用过这个操作,在滴答定时器的中断服务功能里)。
静态KEY _ T s _ tBtn[3];//程序中每个键都有自己的结构,有三个键KEY _ T * pBtn//定义一个结构指针变量pBtnpBtn = & s _ tBtn[I];//将键的结构地址赋给结构指针变量pBtn,然后在过滤键之前设置Count的初始值。如前所述,当密钥初始化时,Count =5/2已经被设置。然后判断的标志位是否被按下。如果键被按下,它在这里被设置为1。如果未按下,此变量的值将始终为0。这里可能不理解的是,按键发送的键值是3 * i+1。弹出键时发送的键值是3 * i+2,长按键时发送的键值是3 * i+3。也就是说,按键发送的键值是1和4和7。按键弹出发送的键值是2和5和8,按键长按发送的键值是3和6和9。看看下面的枚举enum你就明白了。
Typedef enum{ KEY_NONE = 0,/* 0 0表示按键事件*/ KEY_1_DOWN,/* 1键按下*/ KEY_1_UP,/* 1键弹出*/ KEY_1_LONG,/* 1键按下*/ KEY_2_DOWN,/* 2键按下。/* 2键弹出*/ KEY_2_LONG,/* 2键按压*/ KEY_3_DOWN,/* 3键按压*/ KEY_3_UP,/* 3键弹出*/ KEY_3_LONG,/* 3键按压*/} KEY _ ENUM;7.int main(void){ uint8_t KeyCode的实验演示;/*键码*/KEY _ Init();而(1) {/*键过滤和检测由后台systick中断服务程序实现。我们只需要调用KEY_FIFO_Get来读取键值。*/KEY code = KEY _ FIFO _ Get();/*读取键值并返回KEY_NONE = 0 */ if (KeyCode!= key _ none){ switch(key code){ case key _ down _ K1:/* K1按键*/printf(& # 34;K1键被按下\ r \ n & # 34);打破;Case _ up _ k1:/* k1键弹出*/printf(& # 34;K1键弹出\ r \ n & # 34);打破;Key _ down _ K2:/* K2键并按*/printf(& # 34;K2键被按下\ r \ n & # 34);打破;Key _ up _ K2:/* K2键弹出*/printf(& # 34;K2键弹出\ r \ n & # 34);打破;Key _ down _ K3:/* K3键并按*/printf(& # 34;K3键被按下\ r \ n & # 34);打破;Key _ up _ K3:/* K3 key弹出*/printf(& # 34;弹出K3键\ r \ n & # 34);打破;默认值:/*不处理其他键值*/break;}}}}不知道学妹懂不懂。如果没有,就多看几遍。套路已经上传到Gitee了。
Https://gitee.com/zhiguoxin/Wechat-Data.git原创作者:小哥哥曹保果
原标题:一个学妹写的按键检测函数把我的秀翻了!
原文链接:https://mp.weixin.qq.com/s/JKa99sPD8iVkvxL1Rwv6nA
本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://51itzy.com/20117.html