背景
有个项目在实际应用中,采用8个独立按键,每个按键都赋予不同功能,实际使用过程中很多时候都是需要比较特殊的按键操作,例如:长按10s按键、长按5s按键,或者长按需要有快速按键值的反馈,这个情况就类似,我们需要快速增加一个设定值时,按住加号+按键不松手,这个按键值就一直增大。
原理图设计
如下图所示,采用STM32F103C8T6,进行按键设计,这里使用按键较多
KEY_TimeSet ----> 时间设定功能,单击进去时间设定
KEY_Program/Back ----> 返回按键,菜单选择
KEY_ON/OFF ----> 启动按键
KEY_Temp+ ----> 设定温度增加按键,支持快速按键功能
KEY_Temp- ----> 设定温度减少按键,支持快速按键功能
KEY_Threshold+ ----> 参数调整按键,支持快速按键功能
KEY_Threshold- ----> 参数调整按键,支持快速按键功能
KEY_AutoTuning ----> 单击功能
软件设计
软件采用stm32cubemx,如下图所示,时钟选用外部时钟倍频到72Mhz
对应原理图STM32对应管脚应配置为输入模式
这里就不再贴图了,比较简单,配置对应IO输入模式即可。
这里使用FREERTOS功能,可以通过如下配置,添加自己的任务或者信号量,队列等。
使用FREERTOS的目的,是将按键扫描的结果的有效值,放进队列中,供对应的按键处理任务进行使用。
配置按键扫描定时器,20ms扫描一次按键
实际代码如下
按键扫描中断函数,进行按键扫描,并将有效键值放入到队列
void TIM2_IRQHandler(void)
{
uint8_t key;
uint16_t msg;
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
//按键扫描函数
//printf("RK\r\n");//test tim2 20ms 中断
key = ReadKey();
if(key != NO_KEY)
{
msg = MSG_KEY_DOWN | key;
xQueueSendFromISR( segscanQueue, &msg, &xHigherPriorityTaskWoken );
}
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
}
读取按键流程如下
/******************************************************************************
* 函数名称: KeyPro()
* 功能描述: 按键扫描
* 输入参数:
* 输出参数:
* 返 回 值: 无
* 其它说明:
* 修改日期 版本号 修改人 修改内容
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* 2013/02/22 V1.0.0.0
******************************************************************************/
static unsigned char ReadKey(void)
{
static unsigned char KeyState = 0, KeyOldValue = NO_KEY;
static unsigned char KeyOldReturn = 0, KeyTimeCnt = 0;
static unsigned int no_key_times = 0;
unsigned char KeyReturn = NO_KEY, KeyCurValue = NO_KEY;
KeyCurValue = (KEY8_VAL << 7) | (KEY7_VAL << 6) | (KEY6_VAL << 5) | (KEY5_VAL << 4) | (KEY4_VAL << 3) | (KEY3_VAL << 2) | (KEY2_VAL<<1) | KEY1_VAL; //读取6个按键值
switch(KeyState)
{
case 0:
{
if(NO_KEY != KeyCurValue) //检测到有键按下
{
KeyOldValue = KeyCurValue; //保存键值
KeyState++; //转到消抖确认状态
KeyTimeCnt = 0;
no_key_times = 0;
}
else
{
no_key_times++;
if(no_key_times >= 500)//500*20ms=10s
{
no_key_times = 0;
KeyReturn = NO_KEY_10S_DOWN;
KeyOldReturn = NO_KEY;
}
}
break;
}
case 1:
{
if(KeyCurValue == KeyOldValue) //和上次按键相同确认有键按下
{
/*switch(KeyCurValue) //键盘编码 返回编码值
{
case SET_KEY_DOWN:
KeyReturn = SET_KEY_DOWN;
break;
case UP_KEY_DOWN:
KeyReturn = UP_KEY_DOWN;
break;
case DOWN_KEY_DOWN:
KeyReturn = DOWN_KEY_DOWN;
break;
case ENTER_KEY_DOWN:
KeyReturn = ENTER_KEY_DOWN;
break;
case ONOFF_KEY_DOWN:
KeyReturn = ONOFF_KEY_DOWN;
break;
default:
KeyReturn = NO_KEY;
break;
}*/
KeyState++; //转入等待按键释放状态
if(KeyCurValue == KEY7_DOWN)
{
KeyState = 4;//单独对时间按键进行处理
}
else
{
KeyReturn = KeyCurValue;
KeyOldReturn = KeyReturn;
//这里针对按键做优化处理,长按加速、和检测长按释放
if(KeyOldReturn == KEY1_DOWN || KeyOldReturn == KEY2_DOWN)//增加快速按键值
{
KeyState++;
}
}
}
else
KeyState--; //两次键值不同 返回等待按键状态
KeyTimeCnt = 0;
KeyOldValue = KeyCurValue;
break;
}
case 2:
{
if(NO_KEY == KeyCurValue) // 按键已经释放
{
KeyState = 0;
KeyOldReturn = NO_KEY;
}
else
{
}
break;
}
case 3:
{
if(NO_KEY == KeyCurValue) // 按键已经释放
{
KeyState = 0;
KeyOldReturn = NO_KEY;
}
else
{
KeyTimeCnt++;
if(KeyTimeCnt >= 10)
{
KeyReturn = KeyOldReturn; //增加快速键值
KeyTimeCnt = 0;
}
}
break;
}
//增加时间按键的长按、短按检测
case 4:
{
if(NO_KEY == KeyCurValue) // 按键已经释放
{
KeyReturn = KEY7_DOWN;
KeyOldReturn = KeyReturn;
KeyState = 0;
}
else
{
KeyTimeCnt++;
if(KeyTimeCnt >= 100)
{
KeyReturn = KEY7_DOWN_LONG; //检测到长按
KeyOldReturn = KeyReturn;
KeyTimeCnt = 0;
KeyState = 5;
}
}
break;
}
case 5:
{
if(NO_KEY == KeyCurValue) // 按键已经释放
{
KeyState = 0;
KeyOldReturn = NO_KEY;
}
else
{
}
break;
}
default:
{
KeyState = 0;
break;
}
}
return KeyReturn;
}
按键的定义如下
这里根据原理,将按键信息写入到代码中,每次定时器中断到来都对如下管脚进行扫描,用来获取新的按键消息。
//-----------按键配置 配置-------------------------------------------------------------------
#define KEY1_VAL PAin(8)//KEY_Temp+
#define KEY2_VAL PBin(3)//KEY_Temp-
#define KEY3_VAL PBin(15)//KEY_Threshold+
#define KEY4_VAL PAin(15)//KEY_Threshold-
#define KEY5_VAL PBin(14)//KEY_AutoTuning
#define KEY6_VAL PAin(12)//KEY_TimeSet
#define KEY7_VAL PBin(1)//
#define KEY8_VAL PBin(0)
#define KEY1_DOWN 0xFE
#define KEY1_DOWN_LONG (KEY1_DOWN | KEY_DOWN_LONG)
#define KEY2_DOWN 0xFD
#define KEY2_DOWN_LONG (KEY2_DOWN | KEY_DOWN_LONG)
#define KEY3_DOWN 0xFB
#define KEY3_DOWN_LONG (KEY3_DOWN | KEY_DOWN_LONG)
#define KEY4_DOWN 0xF7
#define KEY4_DOWN_LONG (KEY4_DOWN | KEY_DOWN_LONG)
#define KEY5_DOWN 0xEF
#define KEY5_DOWN_LONG (KEY5_DOWN | KEY_DOWN_LONG)
#define KEY6_DOWN 0xDF
#define KEY6_DOWN_LONG (KEY6_DOWN | KEY_DOWN_LONG)
#define KEY7_DOWN 0xBF
#define KEY7_DOWN_LONG 0x61
#define KEY8_DOWN 0x7F
#define KEY8_DOWN_LONG (KEY8_DOWN | KEY_DOWN_LONG)
#define KEY_1_4_DOWN 0xF6
#define NO_KEY_10S_DOWN 0x60
#define NO_KEY 0XFF
对按键消息的处理
如下代码所示,在任务中如果需要按键响应,就直接获取按键消息,如果拿到想要的按键消息,则进行按键动作处理。如下代码中如果监测到1、4按键同时按下,则进行恢复出厂设置的动作处理。
if( xQueueReceive( pq, &Msg, 1000 ) == pdPASS )
{
if(MSG_KEY_DOWN == (MSG_NAME(Msg)))
{
key = MSG_DATA(Msg);
if(key == KEY8_DOWN)//Esc
{
menu1();
}
else if(key == KEY_1_4_DOWN)//回复出厂设置按键,移位按键和加按键同时按下
{
//清屏
Dispay_Clear();
factorySetting();
}
}
}