本文主要针对单片机IO口读取到的按键信号,进行处理识别。识别按键的按下、弹起、长按三种状态,不讨论硬件层的配置问题。可应用于所有单片机的按键信号处理。
按键的简单介绍
按键的硬件主要以接地和接电源的方式。
下面这张图是接地的,当按键未按下时,key1电压为低电平,当按键按下时,key1电压为高电平。
下面这张图是接电源的,当按键未按下时,key2电压为高电平,按下时为低电平。
按键信号处理
为了方便起见,不论按下时高电平还是低电平,我们在硬件层编写读取函数时,将按下的状态标志位1,弹起的状态标志位0。
下面以Key1为PA0口和Key2位PB2口为例:
u8_t HW_Read_Key(void)
{
u8_t nKeyBuff = 0;
if (PA_IDR & 0x01)
{
nKeyBuff |= 0x01;
}
if (PB_IDR & 0x04)
{
nKeyBuff |= 0x02;
}
return nKeyBuff;
}
我们知道机械按键在按下和弹起时,会因为产生抖动,出现高低电平快速变化,因此我们必须进行消抖处理。
普遍地做法是使用delay函数延迟50ms左右,但是delay这个延迟函数是for循环构成的,程序会堵塞在里面,不能及时的处理其他信息,且极大的减慢了单片机运行速度。
因此常用地做法是使用时间标志位,利用定时器定时,详细可以查看我的关于stm8s硬件配置的那篇文章。
本文编写了一个按键读取函数KeyReadInfor(),只需要在while(1)中执行这个函数就可以不断检测按键的信息了。
u8_t nKeyCrVal = 0; // 按键当前的值
u8_t nKeyPrVal = 0; // 按键上一次的值
u8_t nKeyPrDat = 0; // 按键50ms前的值
u8_t wTimeCnt = 0; // 时间计时
typedef struct _KEYINFOR
{
u8_t m_nKeyDn;
u8_t m_nKeyUp;
u8_t m_nKeykp;
}KEYINFOR;
void KeyReadInfor(KEYINFOR stKeyInfor)
{
u8_t nMask = 0; // 检测按键值变化
u8_t nKeyDn = 0; // 检测按键按下
u8_t nKeyUp = 0; // 检测按键弹起
u8_t nKeyKp = 0; // 检测按键长按
if (bSysTime10ms) // bSysTime10ms为时间标志位,每10ms会置为True
{
bSysTime10ms = eFALSE;
nKeyCrVal = HW_Read_Key(); // 读取按键的值
if (nKeyCrVal == nKeyPrVal)
{
wTimeCnt++;
if (wTimeCnt >= (2<<16)-1)
{
wTimeCnt = (2<<16)-1;
}
if (wTimeCnt == 5) // 50ms
{
nMask = nKeyCrVal ^ nKeyPrVal;
nKeyDn = nKeyCrVal & nMask;
nKeyUp = nKeyPrVal & nMask;
}
else if (wTimeCnt == 200) //2s
{
nKeyKp = nKeyPrVal;
}
}
else
{
wTimeCnt = 0;
nKeyPrVal = nKeyCrVal;
}
stKeyInfor.m_nKeyDn = nKeyDn;
stKeyInfor.m_nKeyUp = nKeyUp;
stKeyInfor.m_nKeyKp = nKeyKp;
}
}
main函数代码如下:
KEYINFOR s_stKeyInfor = {0};
while(1)
{
/* 按键消息读取 */
KeyReadInfor(s_stKeyInfor);
/* 消息检测 */
if (s_stKeyInfor.m_nKeyDn || s_stKeyInfor.m_nKeyUp || s_stKeyInfor.m_nKeyKp)
{
//消息处理,根据自己需求编写
if (s_stKeyInfor.m_nKeyDn)
{
switch(s_stKeyInfor.m_nKeyDn)
{
case 0x01:
break;
case 0x02:
break;
default:
break;
}
s_stKeyInfor.m_nKeyDn = 0;
}
if (s_stKeyInfor.m_nKeyUp)
{
}
if (s_stKeyInfor.m_nKeyKp)
{
}
}
}
需要注意的是,整个程序中,最好只有消息检测是无延迟的,以系统频率进行检测,其他程序均要有时间标志位延迟,否则当整个程序过大时,会导致单片机工作负载过大,从而使整个程序的循环变慢。