怎么样让百度收录网站,网站开发工资多少,wordpress sql查询,品牌建设提升摘要#xff1a;今年实验室来了三个学妹#xff0c;其中一个学妹以前是物联网专业的#xff0c;进了实验室老师二话没说#xff1a;先把STM32单片机过一遍上来第一个例程就是使用按键点亮一个LED灯#xff0c;好家伙。点灯小师弟比较在行#xff0c;毕竟32、FPGA、Linux的… 摘要今年实验室来了三个学妹其中一个学妹以前是物联网专业的进了实验室老师二话没说先把STM32单片机过一遍上来第一个例程就是使用按键点亮一个LED灯好家伙。点灯小师弟比较在行毕竟32、FPGA、Linux的小灯都被小师弟点了一遍。哈哈哈所以今天还是来说一说按键检测吧一、如何进行按键检测检测按键有中断方式和GPIO查询方式两种。推荐大家用GPIO查询方式。1.从裸机的角度分析中断方式中断方式可以快速地检测到按键按下并执行相应的按键程序但实际情况是由于按键的机械抖动特性在程序进入中断后必须进行滤波处理才能判定是否有效的按键事件。如果每个按键都是独立的接一个 IO 引脚需要我们给每个 IO 都设置一个中断程序中过多的中断会影响系统的稳定性。中断方式跨平台移植困难。查询方式查询方式有一个最大的缺点就是需要程序定期的去执行查询耗费一定的系统资源。实际上耗费不了多大的系统资源因为这种查询方式也只是查询按键是否按下按键事件的执行还是在主程序里面实现。2.从OS的角度分析中断方式在 OS 中要尽可能少用中断方式因为在RTOS中过多的使用中断会影响系统的稳定性和可预见性。只有比较重要的事件处理需要用中断的方式。查询方式对于用户按键推荐使用这种查询方式来实现现在的OS基本都带有CPU利用率的功能这个按键FIFO占用的还是很小的基本都在1%以下。二、最简单的按键检测程序先给他说了一种经典的按键检测代码相信大多数人使用按键函数都见过它很简单就不过多介绍了#define KEY0_PRES 1 //KEY0
#define KEY1_PRES 2 //KEY1
#define WKUP_PRES 3 //WK_UP u8 KEY_Scan(u8 mode)
{ static u8 key_up1;//按键按松开标志if(mode)key_up1; //支持连按 if(key_up(KEY00||KEY10||WK_UP1)){delay_ms(10);//去抖动 key_up0;if(KEY00)return KEY0_PRES;else if(KEY10)return KEY1_PRES;else if(WK_UP1)return WKUP_PRES; }else if(KEY01KEY11WK_UP0)key_up1; return 0;// 无按键按下
}int main(void)
{ u8 t0; delay_init(); //延时函数初始化 LED_Init(); //初始化与LED连接的硬件接口KEY_Init(); //初始化与按键连接的硬件接口LED0; //点亮LEDwhile(1){tKEY_Scan(0); //得到键值switch(t){ case KEY0_PRES: //如果KEY0按下LED!LED;break;default:delay_ms(10); } }
}如果你在工作中使用这种代码有可能会被同事笑话。当然我这里并不是说这种代码不好不管黑猫白猫能抓住老鼠就是好猫。只要能满足项目需求实现对应的功能就是好代码。但是如果你使用下面这种个人感觉可能会更好。其实也并没有什么神秘感就是使用了FIFO机制。参考的就是安富莱的按键例程不过源代码相对比较复杂对于初学者并不友好所以小小的修改了一下仅供参考在前面分享了使用系统滴答定时器实现了多个软件定时器在按键FIFO中也需要使用这个定时器。在系统的开始我们会启动一个10ms的软件定时器。在这个10ms的软件定时器中不断的进行按键扫描与其他的任务互不影响。三、为什么要了解FIFO要回答什么是FIFO先要回答为什么要使用FIFO。只有搞清楚使用FIFO的好处你才会有意无意的使用FIFO。学习FIFO机制和状态机机制一样都是在裸机编程中非常重要的编程思想。编程思想很重要。初级coder总是在关注代码具体是怎么写高级coder关注的是程序的框架逻辑而不是某个细节。只要你框架逻辑通了则一通百通。四、什么是FIFOFIFO是先入先出的意思即谁先进入队列谁先出去。比如我们需要串口打印数据当使用缓存将该数据保存的时候在输出数据时必然是先进入的数据先出去那么该如何实现这种机制呢首先就是建立一个缓存空间这里假设为10个字节空间进行说明。从这张图就知道如果要使用FIFO就要定义一个结构体而这个结构体至少应该有三个成员。数组buf、读指针read、写指针write。typedef struct
{uint8_t Buf[10]; /* 缓冲区 */uint8_t Read; /* 缓冲区读指针*/uint8_t Write; /* 缓冲区写指针 */
}KEY_FIFO_T;缓存一开始没有数据并且用一个变量write指示下一个写入缓存的索引地址这里下一个存放的位置就是0用另一个变量read 指示下一个读出缓存的索引地址并且下一个读出数据的索引地址也是0。目前队列中是没有数据的也就是不能读出数据队列为空的判断条件在这里就是两个索引值相同。现在开始存放数据在这里可以看到队列中加入了9个数据并且每加入一个数据后队尾索引加 1队头不变这就是数据加入队列的过程。但是缓存空间只有10个如何判断队列已满呢如果只是先一次性加数据到队列中然后再读出数据那这里的判断条件显然是队尾索引为9。好了这就是FIFO的基本原理下面来看一下按键FIFO是怎么操作的。我们这里以5个字节的FIFO空间进行说明。Write变量表示写位置Read 变量表示读位置。初始状态时Read Write 0。我们依次按下按键 K1K2那么FIFO中的数据变为如果 Write Read则我们认为有新的按键事件。我们通过函数KEY_FIFO_Get()读取一个按键值进行处理后Read 变量变为 1。Write 变量不变。继续通过函数KEY_FIFO_Get()读取 3 个按键值进行处理后Read 变量变为 4。此时Read Write 4。两个变量已经相等表示已经没有新的按键事件需要处理。有一点要特别的注意如果 FIFO 空间写满了Write 会被重新赋值为 0也就是重新从第一个字节空间填数据进去如果这个地址空间的数据还没有被及时读取出来那么会被后来的数据覆盖掉这点要引起大家的注意。我们的驱动程序开辟了 10 个字节的 FIFO 缓冲区对于一般的应用足够了。五、按键FIFO的优点可靠地记录每一个按键事件避免遗漏按键事件。特别是需要实现按键的按下、长按、自动连发、弹起等事件时。读取按键的函数可以设计为非阻塞的不需要等待按键抖动滤波处理完毕。按键 FIFO 程序在嘀嗒定时器中定期的执行检测不需要在主程序中一直做检测这样可以有效地降低系统资源消耗。六、按键 FIFO 的实现1.定义结构体在我们的key.h文件中定义一个结构体类型为KEY_FIFO_T的结构体。就是前面说的那个结构体。这只是类型声明并没有分配变量空间。typedef struct
{uint8_t Buf[10]; /* 缓冲区 */uint8_t Read; /* 缓冲区读指针*/uint8_t Write; /* 缓冲区写指针 */
}KEY_FIFO_T;接着在key.c 中定义 s_tKey 结构变量, 此时编译器会分配一组变量空间。static KEY_FIFO_T s_tKey;/* 按键FIFO变量,结构体 */好了按键FIFO的结构体数据类型就定义完了很简单吧2.将键值写入FIFO既然结构体都定义好了接着就是往这个FIFO的数组中写入数据也就是按键的键值用来模拟按键的动作了。/*
**********************************************************
* 函 数 名: KEY_FIFO_Put
* 功能说明: 将1个键值压入按键FIFO缓冲区。可用于模拟一个按键。
* 形 参: _KeyCode : 按键代码
* 返 回 值: 无
**********************************************************
*/
void KEY_FIFO_Put(uint8_t _KeyCode)
{s_tKey.Buf[s_tKey.Write] _KeyCode;if (s_tKey.Write KEY_FIFO_SIZE){s_tKey.Write 0;}
}函数的主要功能就是将按键代码_KeyCode写入到FIFO中而这个FIFO就是我们定义结构体的这个数组成员每写一次就是每调用一次KEY_FIFO_Put()函数写指针write就一次也就是向后移动一个空间如果FIFO空间写满了也就是s_tKey.Write KEY_FIFO_SIZEWrite会被重新赋值为 0。3.从FIFO读出键值有写入键值当然就有读出键值。/*
***********************************************************
* 函 数 名: KEY_FIFO_Get
* 功能说明: 从按键FIFO缓冲区读取一个键值。
* 形 参: 无
* 返 回 值: 按键代码
************************************************************
*/
uint8_t KEY_FIFO_Get(void)
{uint8_t ret;if (s_tKey.Read s_tKey.Write){return KEY_NONE;}else{ret s_tKey.Buf[s_tKey.Read];if (s_tKey.Read KEY_FIFO_SIZE){s_tKey.Read 0;}return ret;}
}如果写指针和读出的指针相等那么返回值就为0表示按键缓冲区为空所有的按键时间已经处理完毕。如果不相等就说明FIFO的缓冲区不为空将Buf中的数读出给ret变量。同样如果FIFO空间读完了没有缓存了也就是s_tKey.Read KEY_FIFO_SIZERead也会被重新赋值为 0。按键的键值定义在key.h 文件下面是具体内容typedef enum
{KEY_NONE 0, /* 0 表示按键事件 */KEY_1_DOWN, /* 1键按下 */KEY_1_UP, /* 1键弹起 */KEY_1_LONG, /* 1键长按 */KEY_2_DOWN, /* 2键按下 */KEY_2_UP, /* 2键弹起 */KEY_2_LONG, /* 2键长按 */KEY_3_DOWN, /* 3键按下 */KEY_3_UP, /* 3键弹起 */KEY_3_LONG, /* 3键长按 */
}KEY_ENUM;必须按次序定义每个键的按下、弹起和长按事件即每个按键对象占用 3 个数值。推荐使用枚举enum, 不用#define的原因是便于新增键值,方便调整顺序。使用{ } 将一组相关的定义封装起来便于理解。编译器也可帮我们避免键值重复。4.按键检测程序上面说了如何将按键的键值存入和读出FIFO但是既然是按键操作就肯定涉及到按键消抖处理还有按键的状态是按下还是弹起是长按还是短按。所以为了以示区分我们用还需要给每一个按键设置很多参数就需要再定义一个结构体KEY_T让每个按键对应1个全局的结构体变量。typedef struct
{/* 下面是一个函数指针指向判断按键手否按下的函数 */uint8_t (*IsKeyDownFunc)(void); /* 按键按下的判断函数,1表示按下 */uint8_t Count; /* 滤波器计数器 */uint16_t LongCount; /* 长按计数器 */uint16_t LongTime; /* 按键按下持续时间, 0表示不检测长按 */uint8_t State; /* 按键当前状态按下还是弹起 */uint8_t RepeatSpeed; /* 连续按键周期 */uint8_t RepeatCount; /* 连续按键计数器 */
}KEY_T;在key.c 中定义s_tBtn结构体数组变量。static KEY_T s_tBtn[3] {0};每个按键对象都分配一个结构体变量这些结构体变量以数组的形式存在将便于我们简化程序代码行数。因为我的硬件有3个按键所以这里的数组元素为3。使用函数指针IsKeyDownFunc可以将每个按键的检测以及组合键的检测代码进行统一管理。因为函数指针必须先赋值才能被作为函数执行。因此在定时扫描按键之前必须先执行一段初始化函数来设置每个按键的函数指针和参数。这个函数是void KEY_Init(void)。void KEY_Init(void)
{KEY_FIFO_Init(); /* 初始化按键变量 */KEY_GPIO_Config(); /* 初始化按键硬件 */
}下面是KEY_FIFO_Init函数的定义static void KEY_FIFO_Init(void)
{uint8_t i;/* 对按键FIFO读写指针清零 */s_tKey.Read 0;s_tKey.Write 0;/* 给每个按键结构体成员变量赋一组缺省值 */for (i 0; i HARD_KEY_NUM; i){s_tBtn[i].LongTime 100;/* 长按时间 0 表示不检测长按键事件 */s_tBtn[i].Count 5/ 2; /* 计数器设置为滤波时间的一半 */s_tBtn[i].State 0;/* 按键缺省状态0为未按下 */s_tBtn[i].RepeatSpeed 0;/* 按键连发的速度0表示不支持连发 */s_tBtn[i].RepeatCount 0;/* 连发计数器 */}/* 判断按键按下的函数 */s_tBtn[0].IsKeyDownFunc IsKey1Down;s_tBtn[1].IsKeyDownFunc IsKey2Down;s_tBtn[2].IsKeyDownFunc IsKey3Down;
}我们知道按键会有机械抖动你以为按键按下就是低电平其实在按下的一瞬间会存在机械抖动如果不做延时处理可能会出错一般如果按键检测到按下后再延时50ms检测一次如果还是检测低电平才能说明按键真正的被按下了。反之按键弹起时也是一样的。所以我们程序设置按键滤波时间50ms, 因为代码每10ms扫描一次按键所以按键的单位我们可以理解为10ms滤波的次数就为5次。这样只有连续检测到50ms状态不变才认为有效包括弹起和按下两种事件即使按键电路不做硬件滤波(没有电容滤波)该滤波机制也可以保证可靠地检测到按键事件。判断按键是否按下用一个HAL_GPIO_ReadPin就可以搞定。static uint8_t IsKey1Down(void)
{if (HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) GPIO_PIN_RESET) return 1;else return 0;
}
static uint8_t IsKey2Down(void)
{if (HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3) GPIO_PIN_RESET) return 1;else return 0;
}static uint8_t IsKey3Down(void)
{if (HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_2) GPIO_PIN_RESET) return 1;else return 0;
}下面是KEY_GPIO_Config函数的定义这个函数就是配置具体的按键GPIO的就不需要过多的解释了。static void KEY_GPIO_Config(void)
{ GPIO_InitTypeDef GPIO_InitStructure;/* 第1步打开GPIO时钟 */__HAL_RCC_GPIOE_CLK_ENABLE();/* 第2步配置所有的按键GPIO为浮动输入模式(实际上CPU复位后就是输入状态) */GPIO_InitStructure.Mode GPIO_MODE_INPUT; /* 设置输入 */GPIO_InitStructure.Pull GPIO_NOPULL; /* 上下拉电阻不使能 */GPIO_InitStructure.Speed GPIO_SPEED_FREQ_VERY_HIGH; /* GPIO速度等级 */GPIO_InitStructure.Pin GPIO_PIN_4;HAL_GPIO_Init(GPIOB, GPIO_InitStructure);GPIO_InitStructure.Pin GPIO_PIN_3;HAL_GPIO_Init(GPIOB, GPIO_InitStructure);GPIO_InitStructure.Pin GPIO_PIN_2;HAL_GPIO_Init(GPIOB, GPIO_InitStructure);
}5.按键扫描按键扫描函数KEY_Scan()每隔 10ms 被执行一次。RunPer10ms函数在 systick中断服务程序中执行。void RunPer10ms(void)
{KEY_Scan();
}void KEY_Scan(void)
{uint8_t i;for (i 0; i HARD_KEY_NUM; i){KEY_Detect(i);}
}
/*每隔10ms所有的按键GPIO均会被扫描检测一次。KEY_Detect函数实现如下static void KEY_Detect(uint8_t i)
{KEY_T *pBtn;pBtn s_tBtn[i];if (pBtn-IsKeyDownFunc()){//这个里面执行的是按键按下的处理if (pBtn-Count KEY_FILTER_TIME){//按键滤波前给 Count 设置一个初值pBtn-Count KEY_FILTER_TIME;}else if(pBtn-Count 2 * KEY_FILTER_TIME){//实现 KEY_FILTER_TIME 时间长度的延迟pBtn-Count;}else{if (pBtn-State 0){pBtn-State 1;/* 发送按钮按下的消息 */KEY_FIFO_Put((uint8_t)(3 * i 1));}if (pBtn-LongTime 0){if (pBtn-LongCount pBtn-LongTime){/* 发送按钮持续按下的消息 */if (pBtn-LongCount pBtn-LongTime){/* 键值放入按键FIFO */KEY_FIFO_Put((uint8_t)(3 * i 3));}}else{if (pBtn-RepeatSpeed 0){if (pBtn-RepeatCount pBtn-RepeatSpeed){pBtn-RepeatCount 0;/* 长按键后每隔10ms发送1个按键 */KEY_FIFO_Put((uint8_t)(3 * i 1));}}}}}}else{//这个里面执行的是按键松手的处理或者按键没有按下的处理if(pBtn-Count KEY_FILTER_TIME){pBtn-Count KEY_FILTER_TIME;}else if(pBtn-Count ! 0){pBtn-Count--;}else{if (pBtn-State 1){pBtn-State 0;/* 发送按钮弹起的消息 */KEY_FIFO_Put((uint8_t)(3 * i 2));}}pBtn-LongCount 0;pBtn-RepeatCount 0;}
}这个函数还是比较难以理解的主要是结构体的操作。所以好好学习结构体不要见了结构体就跑。分析首先读取相应按键的结构体地址赋值给结构体指针变量pBtn 因为程序里面每个按键都有自己的结构体只有通过这个方式才能对具体的按键进行操作。(在前面我们使用软件定时器时也使用了这中操作在滴答定时器的中断服务函数中)。static KEY_T s_tBtn[3];//程序里面每个按键都有自己的结构体,有三个按键
KEY_T *pBtn;//定义一个结构体指针变量pBtn
pBtn 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 表示按键事件 */KEY_1_DOWN, /* 1键按下 */KEY_1_UP, /* 1键弹起 */KEY_1_LONG, /* 1键长按 */KEY_2_DOWN, /* 2键按下 */KEY_2_UP, /* 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();while (1){ /* 按键滤波和检测由后台systick中断服务程序实现我们只需要调用KEY_FIFO_Get读取键值即可。 */KeyCode KEY_FIFO_Get(); /* 读取键值, 无键按下时返回 KEY_NONE 0 */if (KeyCode ! KEY_NONE){switch (KeyCode){case KEY_DOWN_K1: /* K1键按下 */printf(K1键按下\r\n);break;case KEY_UP_K1: /* K1键弹起 */printf(K1键弹起\r\n);break;case KEY_DOWN_K2: /* K2键按下 */printf(K2键按下\r\n);break;case KEY_UP_K2: /* K2键弹起 */printf(K2键弹起\r\n);break;case KEY_DOWN_K3: /* K3键按下 */printf(K3键按下\r\n);break;case KEY_UP_K3: /* K3键弹起 */printf(K3键弹起\r\n);break; default:/* 其它的键值不处理 */break;}}}
}不知道学妹看懂没没看懂就多看几遍。代码例程已上传至Gitee。https://gitee.com/zhiguoxin/Wechat-Data.gitEnd推荐阅读专辑|Linux文章汇总专辑|程序人生专辑|C语言我的知识小密圈关注公众号后台回复「1024」获取学习资料网盘链接。欢迎点赞关注转发在看您的每一次鼓励我都将铭记于心~嵌入式Linux微信扫描二维码关注我的公众号