1.环境介绍 平台:AT32F415单片机,雅特力公司的AT32系列单片机其实跟STM32系列单片机大同小异,包括库函数等基本都是一样的,所以这款代码无论是AT32还是STM32都是适用的。
开发环境:MDK V5 for arm
简介:最近做项目经常用得到按键模块,包括按键扫描,按键长按,短按,组合按的判定,所以特地把按键模块做了整理,让其模块化更加好,方便于其它项目的移植。 2.代码模块化的思路2.1 在key.h文件中通过枚举类型,枚举各个用得到的按键typedef enum{ KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, NUM_KEY}ENUM_KEY;2.2将每个用得到的按键跟其实际用途做映射//按键实际功能映射#define KEY_Z1LEVUP KEY_1 //区域1档位+#define KEY_Z1LEVDW KEY_2 //区域1档位-#define KEY_Z2LEVUP KEY_3 //区域2档位+#define KEY_Z2LEVDW KEY_4 //区域2档位-#define KEY_POWER KEY_5 //电源键#define KEY_ACARID KEY_6 //其它功能按键这样,当在app逻辑层需要对哪个按键进行操作时,直接用KEY_Z1LEVUP 这些宏定义去获取,如果某些个按键在后续改版中功能进行了互换,只需要在功能映射宏定义这边做一下修改,其它其它地方都不用动,方便又省事。 2.3在key.h文件中定义按键数据结构体,用来描述某个具体按键的各个参数//按键状态枚举类型typedef enum{ KEY_STANONE, //按键没有按下 KEY_STAPRESS,//按键处于按住状态 KEY_STASHORT, //按键短按 KEY_STALONG, //按键长按}ENUM_KEYSTA;//按键数据结构体typedef struct{ ENUM_KEYSTA sta; u16 keyCnt; u8 **Pressed; u8 **Reset; //u16 startTime;//如果用得到组合按键,需要定义这个成员}ST_KEYDAT;- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
2.4在key.c文件中定义数个全局变量,用来作为配置功能,或者存储功能等用途ST_KEYDAT gs_Keydata[NUM_KEY];//按键数据结构体数组//电热毯项目各个按键对应关系//每个按键对应的端口号,根据引脚对应关系自行配置 GPIO_Type* gs_KeyPort[NUM_KEY] = {GPIOA,GPIOB,GPIOC....}; //每个按键对应的引脚号,根据引脚对应关系自行配置 u16 gu16_KeyPins[NUM_KEY] = {GPIO_Pins_0,GPIO_Pins_0.....};//根据引脚对应关系自行配置例如我在按键枚举类型ENUM_KEY中枚举的KEY_1对应的引脚是PA0,那么我就定义:
gs_KeyPort[KEY_1] = GPIOA;
gu16_KeyPins[KEY_1] = GPIO_Pins_0;
因为在枚举类型中KEY_!是第一个枚举的成员变量,编译器会自动对其赋值为0,所以gs_KeyPort[KEY_1]就相当于gs_KeyPort[0],依次类推。 2.5实现按键的几个功能函数1.按键模块初始化函数 void Key_Init(){ ENUM_KEY i=KEY_1; GPIO_InitType GPIOType; //按键引脚循环初始化 for (i = KEY_1; i < NUM_KEY; i++) { GPIO_StructInit(&GPIOType); GPIOType.GPIO_Pins = gu16_KeyPins; //引脚选择 GPIOType.GPIO_Mode = GPIO_Mode_IN_PD; //下拉输入模式 GPIOType.GPIO_MaxSpeed = GPIO_MaxSpeed_50MHz;//最大速度 GPIO_Init(gs_KeyPort,&GPIOType); } //按键结构体数组初始化 memset(gs_Keydata,0,sizeof(ST_KEYDAT)*NUM_KEY);}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
2.获取按键状态 ENUM_KEYSTA Key_GetState(ENUM_KEY keyx){ return gs_Keydata[keyx].sta;}3.按键扫描,循环读取每个按键对应引脚的电平 //按键引脚电平读函数u8 Key_ReadKeyPin(ENUM_KEY keyx){ u8 lev; lev = GPIO_ReadInputDataBit(gs_KeyPort[keyx], gu16_KeyPins[keyx]);}void Key_Scan(){ ENUM_KEY i=KEY_1; u8 lev=1; for (i = KEY_1; i < NUM_KEY; i++) { //读取引脚电平状态 lev =Key_ReadKeyPin(i); //根据引脚按下时的电平状态自行配置, if(lev==Bit_SET) { gs_Keydata.**Pressed = 0;//按键没有按下或者弹起 } else gs_Keydata.**Pressed = 1; //按键处在按住状态 }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
如果想要为了模块化更好,可以在key.h文件中定义几个宏用来描述按键按下和弹起时的电平状态,很简单,这里不再赘述。 4.按键状态更新函数 //按键状态更新函数void Key_UpdateSta(){ u32 curTime=Tim_GetT2MSTime(); //获取系统运行时间 static u32 lastTime=0;//定义上次更新按键状态的时间 ENUM_KEY i=KEY_1; //每隔10ms进行一次按键状态更新 if(curTime-lastTime>=10) { Key_Scan();//调用按键扫描函数,更新当前按键按住弹起状态 //按键轮询计数,判断是长按还是短按 for(i=KEY_1;i<NUM_KEY;i++) { if(gs_Keydata.**Pressed == KEY_STAPRESS)//如果是按住状态cnt就一直+ { gs_Keydata.keyCnt++; } else //只要cnt不为0,说明处理时是按下状态,现在处理时是弹起状态 { //如果计数值处在定义的短按次数,就认为是短按 if((gs_Keydata.keyCnt>KEY_DISTURTHRESHOLD) && (gs_Keydata.keyCnt<KEY_SHORTHRESHOLD)) { gs_Keydata.sta=KEY_STASHORT; gu32_DectedKeyTim = Tim_GetT2MSTime(); } else if(gs_Keydata.keyCnt>=KEY_SHORTHRESHOLD)//长按 { gs_Keydata.sta=KEY_STALONG; //如果需要用到组合按键功能,需要对按键检测到长按的时间进行存储 //gs_Keydata.startTime = Tim_GetT2MSTime(); } else { } gs_Keydata.keyCnt = 0; //清除计数 } } lastTime=curTime; //更新处理时间 }}- 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
按键的状态更新函数需要用到一个ms级的定时器进行精密计时,一次短按的时间大概在几百ms左右,然后短按还需要一个消抖时间,所以限定短按状态的判断有两个条件,一是大于消抖时间,2是小于长按时间。具体阈值时间可以根据自己实际情况进行灵活配置。
另外这里有说道组合按键的检测,比如说定义按键1和按键2同时长按为一个组合按键,这个组合按键具有其它的功能,需要软件上能识别到这个组合按键。当前我写的这套代码按键短按长按状态是在按键弹起时才会做出判断。因为人在按下和弹起组合按键时,不可能是完全同时的,然而单片机可能几个ms就会处理一次按键命令更新函数并更新按键状态为NONE,这时候可能就会出现问题。识别不到组合按键,所以当用到长按组合按键时,需要对长按状态进行一个保持,将一个按键的长按状态保持几百ms,然后在这几百ms内如果检测到了另一个按键也是长按状态,就判定位组合按键生效了。 5.判断按键在当前系统状态下是否有效的函数 //系统状态机枚举,这个原来是写在sys.h文件中的,但是文章中需要用到这个进行讲解,所以贴在此处
typedef enum
{
SYS_STAREADY=0,
SYS_STAWORKING,
SYS_STASTANDBY,
NUM_SYSSTA
}ENUM_SYSSTA; //功能屏蔽位,用来设置某些功能模块在哪种系统状态下有效#define STAREADYMASK (1<<SYS_STAREADY)#define STAWORKMASK (1<<SYS_STAWORKING)#define STASTANDYMASK (1<<SYS_STASTANDBY)//定义具体按键在哪些系统状态下有小 u8 gu8_KeyStaMask[NUM_KEY]={STAREADYMASK|STAWORKMASK, STAREADYMASK|STAWORKMASK, STAREADYMASK|STAWORKMASK, STAREADYMASK|STAWORKMASK, STAREADYMASK|STAWORKMASK|STASTANDYMASK, STAREADYMASK|STAWORKMASK};//用来配置按键命令在哪些工作状态下有效//按键在当前系统状态下有效,返回1,无效返回0u8 Key_IsThisEnable(ENUM_KEY keyx){ ENUM_SYSSTA sta=Sys_GetSysSta(); if ((1<<sta)&&gu8_KeyStaMask[keyx]) { return 1; } else return 0;}- 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
因为有的按键在整个系统逻辑中,不是一直有效的。比如说一般遥控器上的按键,只有当电源键按下后,系统进入了开机状态,其它功能按键才有效。也就是说,待机模式下,只有电源键有效,工作状态下,其它一些按键才能有效。所以特地写了一个这个函数,每次app逻辑层处理具体某个按键的状态时,如果判定此时系统状态下这个按键无效,就不去处理这个按键的动作。 6.app逻辑层的具体按键命令处理函数 void Sys_UpdateKeyCmd(){ u32 time = Tim_GetT2MSTime(); ENUM_KEYSTA keySta = KEY_STANONE; ENUM_KEY i=KEY_1; u8 z1HeatLev = Sys_GetHeatLev(ZONE_1); u8 z2HeatLev = Sys_GetHeatLev(ZONE_2); ENUM_SYSSTA sysSta=Sys_GetSysSta(); //只在工作状态下有效 { //按键在当前系统状态下有效且按键状态为短按 if(Key_GetState(KEY_Z1LEVUP) == KEY_STASHORT && (Key_IsThisEnable(KEY_Z1LEVUP))) { //具体的每个按键对应的命令的处理,自行配置填写 } //更新组合按键的命令 //更新开关机键 if(Key_GetState(KEY_POWER) KEY_STASHORT && (Key_IsThisEnable(KEY_POWER))) { if(sysSta == SYS_STAWORKING || sysSta == SYS_STAREADY) { Sys_SetSysAct(SYS_ACTTURNOFF); } else Sys_SetSysAct(SYS_ACTTURNON); } //清除短按的按键状态,长按状态单独清 for(KEY_1=0;i<NUM_KEY;i++) { if((gs_Keydata.sta == KEY_STASHORT)) { gs_Keydata.sta = KEY_STANONE; } else if((gs_Keydata.sta == KEY_STALONG)) { //if(time-gs_Keydata.startTime > 300) //当需要用到长按组合按键时,要将长按状态保持300ms { gs_Keydata.sta = KEY_STANONE; } } }}- 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
上面这个函数是放在app逻辑层的具体命令的处理函数,比如我有个加热+按键,每次按下加热档位就加1,直到到达定义的最高档位,就不再加档位,这种就属于具体的按键命令处理了。组合按键的实现方法这个项目上没用到,所以代码也就没写,想要实现的话根据我上面写的方法自己去实现就行了,将长按状态保持几百ms后再去做判定,就可以了。 总结用我上面的这一套代码写下来,只要下次还是这个开发平台,换了其它项目,只需要把按键的枚举和引脚端口号的配置数组更新一下就行,其它都不用动,很是方便。即使换了其它的单片机,也只需要把初始化,引脚电平读函数更新一下就行,也很方便。不同项目唯一变动比较大的函数就是Sys_UpdateKeyCmd()函数,因为这个涉及到了具体的app逻辑,需要重新写。
后续我还会继续更新led模块,pwm模块,串口模块,adc模块的模块化代码的文章。如果有任何有**交流各种遇到的问题,可以加qq群869630744,一起交流一起进步。
|