常用单片机编程思想及例程1——按键消抖

所谓按键消抖,其实就是过滤掉按键的抖动信息,单片机开发过程中,按键的消抖处理是比较频繁的,常用的消抖方式有延时或者滤波。说到这里就不得不提一下阻塞和非阻塞的编程思想,一名优秀的软件工程师是绝对不允许自己的工程存在阻塞的(特殊情况除外),例如:_NOP();、while(xxx == 1);的使用,这样写不仅会占用大量的单片机资源,而且在实时性要求较高的场合下使用往往会打乱运行逻辑,造成数据错误等(当然,如果简单的做粗延时使用,_NOP();封装的延时函数还是比较方便的);非阻塞的延时大多用系统定时器来封装,一般时基为1ms(对于系统定时器的使用,不建议把时基设置到1ms以下,通用定时器的时基最好也不要低于100us!!!如果时基过小,频繁的时钟中断会占用太多的系统资源,尤其是对于低功耗的mcu来讲,这会大大减少其使用寿命),中断函数内部计数标志一直自加,通过对计数标志的判断来实现延时的目的。

下边整理了我用过的几种按键消抖方法,希望可以给大家一些启示:

阻塞型延时消抖

#define Delay_nms(x) Delay_nus(x*1000)

/************************
函数名称:Delay_1us
函数作用:nop延时1微秒
函数入口:无
函数出口:无
函数作者:cxj
补充说明:
	主频72MHz 72个nop就是1us
************************/
void Delay_1us(void)
{
	__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
	__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
	__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
	__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
	__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
	__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
	__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
	__NOP();__NOP();
}

/************************
函数名称:Delay_nus
函数作用:nop延时n微秒
函数入口:
	num		延时时长
函数出口:无
函数作者:cxj
************************/
void Delay_nus(uint32_t num)
{
	while(num--)
		Delay_1us();
}

/************************
函数名称:KEY_GetVal
函数作用:获取键值
函数入口:无
函数出口:
	0		无按键按下
	1		KEY1按下
函数作者:cxj
************************/
uint8_t KEY_ret = 0;
uint8_t KEY_GetVal(void)
{
	if(KEY1() == 0)//按键可能有人按下
	{
		Delay_nms(10);	//延时消抖
		if(KEY1() == 0)//的确有人按下
		{
			while(KEY1() == 0);//松手检测
			KEY_ret = 1;
		}
	}
	
	return KEY_ret;  //key_ret为真即为按键按下
}

这段代码是在stm32f103的环境下实现的,其实后来发现在别的mcu(如华大、nxp)上,按照这个写法延时并不准确,甚至相差很大, 所以建议用作粗延时使用。

非阻塞型消抖

这种消抖方式不会使程序造成阻塞,一般配合系统定时器使用,由它派生出很多种消抖滤波方式,但原理都是一样的——定时器每过一段时间对按键电平采样一次,如果采样n次或者大于n/2次都是按下的状态,说明按键按下,这种方式也被常用于数据滤波。使用前需要初始化系统定时器,对于ARM内核的mcu,有一个通用的初始化系统定时器的函数SysTick_Config(uint32_t time);,如果要定义一个1ms的时基,只需要初始化SysTick_Config(CoreClock/1000);即可,CoreClock为系统时钟源。

SysTick_Config(SystemCoreClock/1000); //1ms 一次中断

void SysTick_IRQHandler(void)
{
	gSysTickCount++;
}


状态机消抖1:
/******************************************************************************/
/**
* Description: 循环检测check电平
* @param
* @return
* @throws
*/
/******************************************************************************/
void LoopCheck(void)
{
    if(FALSE == Gpio_GetInputIO(TWR4_UP_CHECK_PORT, TWR4_UP_CHECK_PIN))
    {
        if(cHeckflag==FALSE)  //等待过程中
        {
            cHeckflag=TRUE;
            if(cHeckstatus==CHECK_FREE)
                cHeckstatus=CHECK_FIRST;
            else if(cHeckstatus==CHECK_FIRST)
                cHeckstatus=CHECK_SECOND;
            else if(cHeckstatus==CHECK_SECOND)
                cHeckstatus=CHECK_THIRD;
            else if(cHeckstatus==CHECK_THIRD)
                cHeckstatus=CHECK_OVER;

            if(cHeckstatus==CHECK_OVER)  //当防抖检查完
            {
                cHeckstatus=CHECK_FREE;
                if(FALSE == Gpio_GetInputIO(TWR4_UP_CHECK_PORT, TWR4_UP_CHECK_PIN)) //动作确认
                {
                    KEY_Flag = 1;					
                }

            } 
			else
            {
                gTimebase=gSysTickCount;
                Flag = 1;
            }
        }

    } 
	else
    {
        cHeckstatus=CHECK_FREE;
        cHeckflag=FALSE;
        KEY_Flag = 0;	
    }
}

/******************************************************************************/
/**
* Description: 系统延时函数,依据参数gSysTickCount来参考,gSysTickCount 1ms加1
* LED 延时闪烁函数 单独放在外面。因为LED 是一直需要闪烁,规律闪烁
* 此函数 都是单次延时事件处理,不单独用定时器来做,浪费资源、
* @param
* @return
* @throws
*/
/******************************************************************************/
void LoopSysDelay(void)
{
    uint32_t timesverfy=0;
    //处理按键防抖事件
    if(Flag)
    {
        if(gSysTickCount>=gTimebase) 
            timesverfy=gSysTickCount-gTimebase;
        else  //防止定时器溢出
            timesverfy=(0xffffffff-gTimebase)+gSysTickCount;
        if(timesverfy>20)
        {
            Flag = 0;
            cHeckflag=FALSE;
        }

    }
	
}

状态机消抖2:
/******************************************************************************/
/**
* Description: 系统延时函数,依据参数gSysTickCount来参考,gSysTickCount 1ms加1
* isOverTime2()函数参考延时篇
* @param
* @return
* @throws
*/
/******************************************************************************/
void MenuButton(void)
{
    static uint32_t Delay = 0;
    if(isOverTime2(&ButtonDelay,10))							//按键滤波10ms采样一次
    {
        MenuButtonStatusF[2] = MenuButtonStatusF[1];
        MenuButtonStatusF[1] = MenuButtonStatusF[0];
        MenuButtonStatusF[0] = GetMenuButtonStatus();
        MenuButtonStatus = (MenuButtonStatusF[2] & MenuButtonStatusF[1]) |
                           (MenuButtonStatusF[0] & (MenuButtonStatusF[2] |                         
                            MenuButtonStatusF[1]));               //按键滤波

    MenuButtonStatusP = (~MenuButtonStatusLast) & MenuButtonStatus;		//按键上升沿脉冲
    MenuButtonStatusLast = MenuButtonStatus;
}

这段代码又稍微复杂了一些(加了状态机的思想),整体思路就是:每一个计时事件都有一个对应的标志位(如:Flag)、一个当前记录时间(如:gTimebase),当计时开始时,gTimebase记录当前定时器的值gSysTickCount,gSysTickCount在中断中自加,gSysTickCount-gTimebase的值达到超时值(20)时,切换到下一个状态继续计时,再过20ms又会采样一次,再切换状态,直到状态机运行结束。这个模板不仅可以做消抖使用,一些滤波、采样的操作都可以添加进去,还是很方便的。

其它

网上还有一些三行代码的消抖方式也可以借鉴一下,不管怎么说,适合自己的才是最好的,相信大家也都有一套自己的编程习惯,欢迎留言分享。

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈大本事er

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值