做啊网站,用php做的企业网站作业,一个做flash的网站,wordpress特效主题免费下载其实笔者以前学51的时候按键功能就包含非阻塞式的#xff0c;而且还包括矩阵按键的非组塞式按键实现。开关的长短键功能笔者在之前的51博文中笔者自己尝试写过#xff0c;功能是有了但写的其实很混乱#xff0c;几乎没有移植的价值。这次江科大刚好出了新的教程#xff0c;… 其实笔者以前学51的时候按键功能就包含非阻塞式的而且还包括矩阵按键的非组塞式按键实现。开关的长短键功能笔者在之前的51博文中笔者自己尝试写过功能是有了但写的其实很混乱几乎没有移植的价值。这次江科大刚好出了新的教程又重新学习了一下。刚好学到江科大关于串口通信部分了解了状态机的形式思路相比以前突然打开了以前自己写代码总觉得状态标志是个很好的参数因为他可以帮助区分工作流程的各个状态。 这次江科大的代码没了注释笔者自己注释了一下。
key.c
#include stm32f10x.h // Device header
#include Key.h/*映射区还有一些在头文件Key.h文件里*/
#define KEY_PRESSED 1 //按键按下
#define KEY_UNPRESSED 0//按键松开#define KEY_TIME_DOUBLE 200
#define KEY_TIME_LONG 2000
#define KEY_TIME_REPEAT 100
/* Key_Flag bit6~bit0 分别代表REPEATbit6、Long、Double、Single、UP、Down、HOLDbit7是空位 */
uint8_t Key_Flag[KEY_COUNT];//定义全局变量标志位每个标志位互相独立,不同的标志位代表不同的事件void Key_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);/*按键使能GPIO初始化常态是高电平按下是低电平*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin GPIO_Pin_1 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOB, GPIO_InitStructure);/*按键使能GPIO初始化常态是低电平按下是高电平*/GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPD;GPIO_InitStructure.GPIO_Pin GPIO_Pin_13 | GPIO_Pin_15;GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOB, GPIO_InitStructure);
}uint8_t Key_GetState(uint8_t n) //检测当前按键的电平并返回相应的电平信息
{if (n KEY_1){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) 0){return KEY_PRESSED;}}else if (n KEY_2){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) 0){return KEY_PRESSED;}}else if (n KEY_3){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) 1){return KEY_PRESSED;}}else if (n KEY_4){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_15) 1){return KEY_PRESSED;}}return KEY_UNPRESSED;
}
/*** 函 数开关状态检测函数* 参 数uint8_t n 指定开关编号可在 KEY_1 KEY_2 KEY_3 KEY_4选择如需扩展可在KEY.h中添加KEY_1的值是0 KEY_2的值是1看头文件的定义* 参 数uint8_t Flag指定开关的状态可在下面参数中现在
// KEY_HOLD 按住开关
// KEY_DOWN 按下开关
// KEY_UP 开关弹起 这三项一般不作为参数检测KEY_SINGLE 单击KEY_DOUBLE 双击KEY_LONG : 长按KEY_REPEAT 重复即一直按住。 后4项状态是互斥的但是和前三项可以
* 返 回 值如果开关状态是例举uint8_t Flag的状态则返回1如果不是则返回0
* 注意事项开关状态Key_Flag[n]不一定和uint8_t Flag*/
uint8_t Key_Check(uint8_t n, uint8_t Flag)//开关状态检测
{if (Key_Flag[n] Flag)//开关的7个状态FLAG是互斥一个状态只占一位比如单击 0000 1000只有开关确实是处于单击状态开关的结果才是非0的{if (Flag ! KEY_HOLD) //这个函数就是保证不清除bit0位置的状态{Key_Flag[n] ~Flag; //相应的bit控制位清0其它位保持}return 1;}return 0;
}void Key_Tick(void)
{static uint8_t Count, i;//定义静态变量i和Countstatic uint8_t CurrState[KEY_COUNT], PrevState[KEY_COUNT];//定义现态和前态static uint8_t S[KEY_COUNT];//定义状态static uint16_t Time[KEY_COUNT];//for (i 0; i KEY_COUNT; i )//KEY_COUNT的值在Key.H中定义了{if (Time[i] 0){Time[i] --; //对应开关的时间减1}}Count ;if (Count 20)//每20ms进入一次这个函数{Count 0;for (i 0; i KEY_COUNT; i )//历遍所有的开关状态{PrevState[i] CurrState[i];//当Key_GetState(i)获得新状态时代表着CurrState里面的状态就是前态了CurrState[i] Key_GetState(i);//把开关状态赋值给现态if (CurrState[i] KEY_PRESSED)//如果检测到开关按下则标志位HOLD置1{Key_Flag[i] | KEY_HOLD; //KEY_HOLD 0000 0001则bit 0 置1其它位保持。}else//如果没检测到开关按下则标志位HOLD置0{Key_Flag[i] ~KEY_HOLD;//~KEY_HOLD 1111 1110 则bit 0置0其它位保持。}if (CurrState[i] KEY_PRESSED PrevState[i] KEY_UNPRESSED)//如果现态是按下前态是没有按下{Key_Flag[i] | KEY_DOWN; // KEY_DOWN 0000 0010 则bit 1置1其它位保持}if (CurrState[i] KEY_UNPRESSED PrevState[i] KEY_PRESSED)//如果现态是弹起前态是按下{Key_Flag[i] | KEY_UP; // KEY_UP 0000 0100则bit 2 置1其它位保持}if (S[i] 0) //如果对应开关处于空闲状态{if (CurrState[i] KEY_PRESSED) //如果现态的开关状态时按下{Time[i] KEY_TIME_LONG; //对应开关的时间设置为2000即长按检测S[i] 1;//开关由0态进入1态}}else if (S[i] 1) //如果对应开关处于状态1{if (CurrState[i] KEY_UNPRESSED)//如果现态开关状态是弹起{Time[i] KEY_TIME_DOUBLE; //对应开关时间设置为200S[i] 2;//开关由1态进入2态}else if (Time[i] 0) //如果对应开关时间为0{Time[i] KEY_TIME_REPEAT; //开关时间设置为100Key_Flag[i] | KEY_LONG;//KEY_LONG 0010 0000则对应标志位bit5 置1其它位保持S[i] 4; //开关由1态进人4态}}else if (S[i] 2) //如果对应开关处于2态{if (CurrState[i] KEY_PRESSED)//如果检测到开关处于按住状态{Key_Flag[i] | KEY_DOUBLE;//对应开关设置标志位 KEY_DOUBLE 0001 0000bit4 置1其它位保持S[i] 3;//开关进入状态3说明按键已然双击}else if (Time[i] 0)//否则的话检测对应开关的时间是否为0{Key_Flag[i] | KEY_SINGLE; //对应开关设置标志位 KEY_SINGLE 0000 1000,bit3 置1其它位保持S[i] 0; //开关状态由2态回到状态0}}else if (S[i] 3)//如果对应开关处于状态3{if (CurrState[i] KEY_UNPRESSED)//如果对应开关现态是弹起{S[i] 0;//开关由3态回到状态0}}else if (S[i] 4)//如果对应开关处于状态4{if (CurrState[i] KEY_UNPRESSED)//如果对应开关现态是弹起{S[i] 0;//开关由4态回到状态0}else if (Time[i] 0)//如果对应时间是0{Time[i] KEY_TIME_REPEAT; //对应开关设置为重复按键时间100Key_Flag[i] | KEY_REPEAT; //对应开关标志位设置为 KEY_REPEAT 0100 0000 bit6 置1其它位保持S[i] 4;//开关已然处于4态}}}}
}添一下编程思路 当然这篇也不是来分享注释的纵观江科大的代码在开关稳态的判断上稍显简陋。因此笔者扩展了一下代码1现在代码对稳态有了更强的判断 2对数组的边界进行了判断防止超过边界程序不工作。 对应代码移植的注意点1确定KEY_COUNT的值以降低资源的消耗不一定要一直设置为4用几个设置几个就行 2添加了按键开关宏定义 3添加了GPIO口宏定义
在这几个宏定义里修改参数无需在模块中修改即可拿来使用。一般来说双击是很少用的功能关于如果屏蔽这个功能宏定义中#define KEY_TIME_DOUBLE 1原先的200改成1程序就不会检测到双击就只剩单击和长按功能了。
笔者的稳态加强判断代码
/*有历史的开关状态判定*/
uint16_t Key_SteadyState(uint8_t n)
{if (n KEY_COUNT) {return 0; // 索引越界时直接返回0不执行后续操作}
if (n KEY_1){if (Keybuf[KEY_1] 0xFFFF){return KEY_PRESSED;}}if (n KEY_2){if (Keybuf[KEY_2] 0xFFFF){return KEY_PRESSED;}}if (n KEY_3){if (Keybuf[KEY_3] 0xFFFF){return KEY_PRESSED;}} if (n KEY_4){if (Keybuf[KEY_4] 0xFFFF){return KEY_PRESSED;}} return KEY_UNPRESSED;
} 这也是笔者学51时候宋老师给出的编程思路按下开关获得的状态是1每1ms进入中断一次并移位一次因此只要判断Keybuf的值就能知道过去的16ms是不是处于稳态。如果keybuf[i] 0x0000则说明前16ms开关一直松开如果keybuf[i] 0xffff,则说明开关一直按着。
key.c
#include stm32f10x.h // Device header
#include Key.h
/*映射区*/#define KEY_PRESSED 1 //按键按下
#define KEY_UNPRESSED 0//按键松开/*开关阈值时间*/
#define KEY_TIME_DOUBLE 200//把该处的值调低比如设置为1那么程序就不可能检测到双击变相的屏蔽了双击功能
#define KEY_TIME_LONG 2000
#define KEY_TIME_REPEAT 100
/*GPIO口宏定义开关KEY_1 KEY_2都是GPIOB,第二组KEY_3 KEY_4也是GPIOB*/
#define GPIO_KEY_1 GPIO_Pin_1
#define GPIO_KEY_2 GPIO_Pin_11
#define GPIOX_KEY1_KEY2 GPIOB
#define GPIO_KEY_3 GPIO_Pin_13
#define GPIO_KEY_4 GPIO_Pin_15
#define GPIOX_KEY3_KEY4 GPIOB/* Key_Flag bit6~bit0 分别代表REPEATbit6、Long、Double、Single、UP、Down、HOLDbit7是空位 */
uint8_t Key_Flag[KEY_COUNT] {0};//定义全局变量标志位每个标志位互相独立,不同的标志位代表不同的事件
uint16_t Keybuf[KEY_COUNT] {0x0000};void Key_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);/*按键使能GPIO初始化常态是高电平按下是低电平*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin GPIO_KEY_1 | GPIO_KEY_2;GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOX_KEY1_KEY2, GPIO_InitStructure);/*按键使能GPIO初始化常态是低电平按下是高电平*/GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPD;GPIO_InitStructure.GPIO_Pin GPIO_KEY_3 | GPIO_KEY_4;GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOX_KEY3_KEY4, GPIO_InitStructure);
}
/*检测当前按键的电平并返回相应的电平信息即开关状态*/
uint16_t Key_GetState(uint8_t n)
{if (n KEY_COUNT) {return 0; // 索引越界时直接返回0不执行后续操作}if (n KEY_1){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) 0){return KEY_PRESSED;}}else if (n KEY_2){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) 0){return KEY_PRESSED;}}else if (n KEY_3){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) 1){return KEY_PRESSED;}}else if (n KEY_4){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_15) 1){return KEY_PRESSED;}}return KEY_UNPRESSED;
}
/*有历史的开关状态判定*/
uint16_t Key_SteadyState(uint8_t n)
{if (n KEY_COUNT) {return 0; // 索引越界时直接返回0不执行后续操作}
if (n KEY_1){if (Keybuf[KEY_1] 0xFFFF){return KEY_PRESSED;}}if (n KEY_2){if (Keybuf[KEY_2] 0xFFFF){return KEY_PRESSED;}}if (n KEY_3){if (Keybuf[KEY_3] 0xFFFF){return KEY_PRESSED;}} if (n KEY_4){if (Keybuf[KEY_4] 0xFFFF){return KEY_PRESSED;}} return KEY_UNPRESSED;
}/*** 函 数开关状态检测函数* 参 数uint8_t n 指定开关编号可在 KEY_1 KEY_2 KEY_3 KEY_4选择如需扩展可在KEY.h中添加KEY_1的值是0 KEY_2的值是1看头文件的定义* 参 数uint8_t Flag指定开关的状态可在下面参数中选择
// KEY_HOLD 按住开关
// KEY_DOWN 按下开关
// KEY_UP 开关弹起 这三项一般不作为参数检测KEY_SINGLE 单击KEY_DOUBLE 双击KEY_LONG : 长按KEY_REPEAT 重复即一直按住。 后4项状态是互斥的但是和前三项可以共存
* 返 回 值如果开关状态是例举uint8_t Flag的状态则返回1如果不是则返回0
* 注意事项开关状态Key_Flag[n]不一定和uint8_t Flag相同*/
uint8_t Key_Check(uint8_t n, uint8_t Flag)//开关状态检测
{/* 添加边界检查确保 n 在有效索引范围内 */if (n KEY_COUNT) {return 0; // 索引越界时直接返回0不执行后续操作}if (Key_Flag[n] Flag)//开关的7个状态FLAG一个状态只占一位比如单击 0000 1000只有开关确实是处于单击状态开关的结果才是非0的{if (Flag ! KEY_HOLD) //这个函数就是保证不清除bit0位置的状态{Key_Flag[n] ~Flag; //相应的bit控制位清0其它位保持}return 1;}return 0;
}void Key_Tick(void)
{static uint8_t Count, i;//定义静态变量i和Countstatic uint8_t CurrState[KEY_COUNT], PrevState[KEY_COUNT];//定义现态和前态static uint8_t S[KEY_COUNT] {0};//定义状态static uint16_t Time[KEY_COUNT] {0};//定义开关时间for (i 0; i KEY_COUNT; i )//遍历所有的开关KEY_COUNT的值在Key.H中定义了{Keybuf[i] (Keybuf[i] 1) | Key_GetState(i) ;//当前的开关状态赋值给状态监控数组if (Time[i] 0){Time[i] --; //对应开关的时间减1}}Count ;if (Count 20)//每20ms进入一次这个函数{Count 0;for (i 0; i KEY_COUNT; i )//历遍所有的开关状态{PrevState[i] CurrState[i];//当Key_GetState(i)获得新状态时代表着CurrState里面的状态就是前态了CurrState[i] Key_SteadyState(i) ;//把稳定的开关状态赋值给现态if (CurrState[i] KEY_PRESSED)//如果检测到开关按下则标志位HOLD置1{Key_Flag[i] | KEY_HOLD; //KEY_HOLD 0000 0001则bit 0 置1其它位保持。}else//如果没检测到开关按下则标志位HOLD置0{Key_Flag[i] ~KEY_HOLD;//~KEY_HOLD 1111 1110 则bit 0置0其它位保持。}if (CurrState[i] KEY_PRESSED PrevState[i] KEY_UNPRESSED)//如果现态是按下前态是没有按下{Key_Flag[i] | KEY_DOWN; // KEY_DOWN 0000 0010 则bit 1置1其它位保持}if (CurrState[i] KEY_UNPRESSED PrevState[i] KEY_PRESSED)//如果现态是弹起前态是按下{Key_Flag[i] | KEY_UP; // KEY_UP 0000 0100则bit 2 置1其它位保持}if (S[i] 0) //如果对应开关处于空闲状态{if (CurrState[i] KEY_PRESSED) //如果现态的开关状态时按下{Time[i] KEY_TIME_LONG; //对应开关的时间设置为2000即长按检测S[i] 1;//开关由0态进入1态}}else if (S[i] 1) //如果对应开关处于状态1{if (CurrState[i] KEY_UNPRESSED)//如果现态开关状态是弹起{Time[i] KEY_TIME_DOUBLE; //对应开关时间设置为200S[i] 2;//开关由1态进入2态}else if (Time[i] 0) //如果对应开关时间为0{Time[i] KEY_TIME_REPEAT; //开关时间设置为100Key_Flag[i] | KEY_LONG;//KEY_LONG 0010 0000则对应标志位bit5 置1其它位保持S[i] 4; //开关由1态进人4态}}else if (S[i] 2) //如果对应开关处于2态{if (CurrState[i] KEY_PRESSED)//如果检测到开关处于按住状态{Key_Flag[i] | KEY_DOUBLE;//对应开关设置标志位 KEY_DOUBLE 0001 0000bit4 置1其它位保持S[i] 3;//开关进入状态3说明按键已然双击}else if (Time[i] 0)//否则的话检测对应开关的时间是否为0{Key_Flag[i] | KEY_SINGLE; //对应开关设置标志位 KEY_SINGLE 0000 1000,bit3 置1其它位保持S[i] 0; //开关状态由2态回到状态0}}else if (S[i] 3)//如果对应开关处于状态3{if (CurrState[i] KEY_UNPRESSED)//如果对应开关现态是弹起{S[i] 0;//开关由3态回到状态0}}else if (S[i] 4)//如果对应开关处于状态4{if (CurrState[i] KEY_UNPRESSED)//如果对应开关现态是弹起{S[i] 0;//开关由4态回到状态0}else if (Time[i] 0)//如果对应时间是0{Time[i] KEY_TIME_REPEAT; //对应开关设置为重复按键时间100Key_Flag[i] | KEY_REPEAT; //对应开关标志位设置为 KEY_REPEAT 0100 0000 bit6 置1其它位保持S[i] 4;//回到状态4}}}}
}key.h
#ifndef __KEY_H
#define __KEY_H/*使能开关的个数根据工程的需要更该参数*/
#define KEY_COUNT 4
/*开关命名索引宏命令取的值与for循环的i有关且是一一对应的因此取值要连续不能随意错位跳过某个数取值的方式比如0,4,1,3这种顺序是不允许的*/
#define KEY_1 0
#define KEY_2 1
#define KEY_3 2
#define KEY_4 3
/* 参数修改区按工程需要重命名开关名字*/
#define Key_Safe KEY_1 // 安全开关
#define Key_Start KEY_2 // 启动开关
#define Key_Stop KEY_3 // 停止开关
#define Key_Reset KEY_4 // 复位开关#define KEY_HOLD 0x01 //0000 0001
#define KEY_DOWN 0x02 //0000 0010
#define KEY_UP 0x04 //0000 0100
#define KEY_SINGLE 0x08 //0000 1000
#define KEY_DOUBLE 0x10 //0001 0000
#define KEY_LONG 0x20 //0010 0000
#define KEY_REPEAT 0x40 //0100 0000void Key_Init(void);
uint8_t Key_Check(uint8_t n, uint8_t Flag);
void Key_Tick(void);#endif从逻辑上来讲后面的程序对稳态的判断更严格不同场景的使用要求可以按需调整比如稳态判断采用8位的那么时间就是 8mscount的值改小一点。