找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 585|回复: 0
打印 上一主题 下一主题
收起左侧

从单片机基础到程序框架 资料下载

[复制链接]
跳转到指定楼层
楼主
ID:140489 发表于 2025-10-17 09:48 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
吴大拿 从单片机基础到程序框架 资料下载

“非阻塞”,在处理消抖的时候,必须用到延时,如果此时用阻塞的delay延时就会影响其它任务的运行效率,因此,用非阻塞的定时延时更加有优越性。
“清零式滤波”,在消抖的时候,有两种境界,第一种境界是判断两次电平的状态,中间插入“固定的时间”延时,这种方法前后一共判断了两次,第一次是识别到低电平就进入延时的状态,第二次是延时后再确认一次是否继续是低电平的状态,这种方法的不足是,“固定的时间”全凭经验值,但是不同的按键它们的抖动时间长度是不同的,除此之外,前后才判断了两次,在软件的抗干扰能力上也弱了很多,“密码等级”不够高。第二种境界就是“清零式滤波”,“清零式滤波”非常巧妙,抗扰能力超强,它能自动过滤不同按键的“抖动时间”,然后再进入一个“稳定时间”的“N次识别判断”,更加巧妙的是,在“抖动时间”和“稳定时间”两者时间内,只要发现一次是高电平的干扰,就马上自动清零计时器,重新开始计时。“稳定时间”一般取20ms到30ms之间,而“抖动时间”是隐藏的,在代码上并没有直接描写出来,但是却无形地融入了代码之中,只有慢慢体会才能发现它的存在。
具体的代码如下,实现的功能是按一次K1或者K2按键,就触发一次蜂鸣器鸣叫。
#include "REG52.H"  
#define KEY_VOICE_TIME   50 //按键触发后发出的声音长度  
#define KEY_FILTER_TIME  25  //按键滤波的“稳定时间”25ms
void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;
void BeepOpen(void);   
void BeepClose(void);
void VoiceScan(void);
void KeyScan(void);    //按键识别的驱动函数,放在定时中断里
void KeyTask(void);    //按键任务函数,放在主函数内
sbit P3_4=P3^4;  
sbit KEY_INPUT1=P2^2;  //K1按键识别的输入口。
sbit KEY_INPUT2=P2^1;  //K2按键识别的输入口。
volatile unsigned char vGu8BeepTimerFlag=0;  
volatile unsigned int vGu16BeepTimerCnt=0;  
volatile unsigned char vGu8KeySec=0;  //按键的触发序号,全局变量意味着是其它函数的接口。
void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
   KeyTask();    //按键任务函数
    }
}
void T0_time() interrupt 1     
{
VoiceScan();  
KeyScan();    //按键识别的驱动函数
TH0=0xfc;   
TL0=0x66;   
}
void SystemInitial(void)
{
TMOD=0x01;  
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}
void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}
void PeripheralInitial(void)
{

}
void BeepOpen(void)
{
P3_4=0;  
}
void BeepClose(void)
{
P3_4=1;  
}
void VoiceScan(void)
{
   static unsigned char Su8Lock=0;  
if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
   {
    if(0==Su8Lock)
    {
     Su8Lock=1;  
BeepOpen();
     }
    else  
{     
         vGu16BeepTimerCnt--;  
     if(0==vGu16BeepTimerCnt)
     {
      Su8Lock=0;     
BeepClose();  
     }
}
   }  
}
/* 注释一:
* 独立按键扫描的详细过程,以按键K1为例,如下:
* 第一步:平时没有按键被触发时,按键的自锁标志,去抖动延时计数器一直被清零。
* 第二步:一旦有按键被按下,去抖动延时计数器开始在定时中断函数里累加,在还没累加到
*         阀值KEY_FILTER_TIME时,如果在这期间由于受外界干扰或者按键抖动,而使
*         IO口突然瞬间触发成高电平,这个时候马上把延时计数器Su16KeyCnt1清零了,这个过程
*         非常巧妙,非常有效地去除瞬间的杂波干扰。以后凡是用到开关感应器的时候,
*         都可以用类似这样的方法去干扰。
* 第三步:如果按键按下的时间达到阀值KEY_FILTER_TIME时,则触发按键,把编号vGu8KeySec赋值。
*         同时,马上把自锁标志Su8KeyLock1置1,防止按住按键不松手后一直触发。
* 第四步:等按键松开后,自锁标志Su8KeyLock1及时清零(解锁),为下一次自锁做准备。
* 第五步:以上整个过程,就是识别按键IO口下降沿触发的过程。
*/
void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
{
   static unsigned char Su8KeyLock1; //1号按键的自锁
   static unsigned int  Su16KeyCnt1; //1号按键的计时器
   static unsigned char Su8KeyLock2; //2号按键的自锁
   static unsigned int  Su16KeyCnt2; //2号按键的计时器

   //1号按键
   if(0!=KEY_INPUT1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
   {
      Su8KeyLock1=0; //按键解锁
      Su16KeyCnt1=0; //按键去抖动延时计数器清零,此行非常巧妙,是全场的亮点。      
   }
   else if(0==Su8KeyLock1)//有按键按下,且是第一次被按下。这行很多初学者有疑问,请看专题分析。
   {
      Su16KeyCnt1++; //累加定时中断次数
      if(Su16KeyCnt1>=KEY_FILTER_TIME) //滤波的“稳定时间”KEY_FILTER_TIME,长度是25ms。
      {
         Su8KeyLock1=1;  //按键的自锁,避免一直触发
         vGu8KeySec=1;    //触发1号键
      }
   }
   //2号按键
   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;      
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_FILTER_TIME)
      {
         Su8KeyLock2=1;  
         vGu8KeySec=2;    //触发2号键
      }
   }
}
void KeyTask(void)    //按键任务函数,放在主函数内
{
if(0==vGu8KeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}
switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
   case 1:     //1号按键
        vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发按键后,发出固定长度的声音
        vGu8BeepTimerFlag=1;  
vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
   case 2:     //2号按键
        vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发按键后,发出固定长度的声音
        vGu8BeepTimerFlag=1;  
vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
}
}
92.2   专题分析:else if(0==Su8KeyLock1)。
疑问:
   if(0!=KEY_INPUT1)
   {
      Su8KeyLock1=0;
      Su16KeyCnt1=0;      
   }
   else if(0==Su8KeyLock1)//有按键按下,且是第一次被按下。为什么?为什么?为什么?
   {
      Su16KeyCnt1++;
      if(Su16KeyCnt1>KEY_FILTER_TIME)
      {
         Su8KeyLock1=1;  
         vGu8KeySec=1;   
      }
   }
解答:
首先,我们要明白C语言的语法中,
if(条件1)
{
}
else if(条件2)
{
}
以上语句是一对组合语句,不能分开来看。当(条件1)成立的时候,它是绝对不会判断(条件2)的。当(条件1)不成立的时候,才会判断(条件2)。
回到刚才的问题,当程序执行到(条件2) else if(0==Su8KeyLock1)的时候,就已经默认了(条件1) if(0!=KEY_INPUT1)不成立,这个条件不成立,就意味着0==KEY_INPUT1,也就是有按键被按下,因此,这里的else if(0==Su8KeyLock1)等效于else if(0==Su8KeyLock1&&0==KEY_INPUT1),而Su8KeyLock1是一个自锁标志位,一旦按键被触发后,这个标志位会变1,防止按键按住不松手的时候不断触发按键。这样,按键只能按一次触发一次,松开手后再按一次,又触发一次。
92.3   专题分析:if(0!=KEY_INPUT1)。
疑问:为什么不用if(1==KEY_INPUT1)而用if(0!=KEY_INPUT1)?
解答:其实两者在功能上是完全等效的,在这里都可以用。之所以本教程优先选用后者if(0!=KEY_INPUT1),是因为考虑到了代码在不同单片机平台上的可移植性和兼容性。很多32位的单片机提供的是库函数,库函数返回的按键状态是一个字节变量来表示,当被按下的时候是0,但是,当没有按下的时候并不一定等于1,而是一个“非0”的数值。

92.4   专题分析:把KeyScan函数放在定时器中断里。
疑问:为什么把KeyScan函数放在定时器中断里?
解答:中断函数里放的函数或者代码越少越好,但是KeyScan函数是特殊的函数,是涉及到IO口输入信号的滤波,滤波就涉及到时间的及时性与均匀性,放在定时中断函数里更加能保证时间的一致性。比如,蜂鸣器驱动,动态数码管驱动,按键扫描驱动,我个人都习惯放在定时中断函数里。
92.5   专题分析:if(0==vGu8KeySec)return。
疑问:if(0==vGu8KeySec)return是不是多此一举?
解答:在KeyTask函数这里,if(0==vGu8KeySec)return这行代码删掉,对程序功能是没有影响的,这里之所以多插入这行判断语句,是因为,当按键多达几十个的时候,避免主函数每次进入KeyTask函数,都挨个扫描判断switch的状态进行多次判断,如果增加了这行if(0==vGu8KeySec)return代码,就可以直接退出省事,在理论上感觉更加运行高效。其实,不同单片机不同的C编译器可能对switch语句的翻译不一样,因此,这里的是不是更加高效我不敢保证。但是可以保证的是,加了这行代码也没有其它副作用。

从单片机基础到程序框架_pdf教程_word教程_原理图_PCB文件_四件套.zip

9.12 MB, 下载次数: 0, 下载积分: 黑币 -5

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏3 分享淘帖 顶 踩
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表