jianhong_wu
发表于 2017-11-26 11:32:35
本帖最后由 jianhong_wu 于 2017-11-26 11:46 编辑
第九十七节: 独立按键按住不松手的连续均匀触发。
【97.1 按住不松手的连续均匀触发。】
上图97.1.1独立按键电路
上图97.1.2灌入式驱动8个LED
上图97.1.3有源蜂鸣器电路
在电脑上删除某个文件某行文字的时候,单击一次“退格按键”,就删除一个文字,如果按住“退格按键”不松手,就会“连续均匀”的触发“删除”的功能,自动逐个把整行文字删除清空,这就是“按住不松手的连续均匀触发”应用案例之一。除此之外,在很多需要人机交互的项目中都有这样的功能,为了快速加减某个数值,按住某个按键不松手,某个数值有节奏地快速往上加或者快速往下减。这种“按住不松手连续均匀触发”的按键识别,在程序上有“3个时间”需要留意,第1个是按键单击的“滤波”时间,第2个是按键“从单击进入连击”的间隔时间(此时间是“单击”与“连击”的分界线),第3个是按键“连击”的间隔时间,
本节例程实现的功能如下:(1)8个受按键控制的跑马灯在某一时刻只有1个LED亮,每触发一次K1按键,“亮的LED”就“往左边跑一步”;相反,每触发一次K2按键,“亮的LED”就“往右边跑一步”。如果按住K1或者K2不松手就连续触发,“亮的LED”就“连续跑”,一直跑到左边或者右边的尽头。(2)按键每“单击”一次蜂鸣器就鸣叫一次,但是,当按键“从单击进入连击”后,蜂鸣器就不鸣叫。
#include "REG52.H"
#define KEY_VOICE_TIME 50
#define KEY_SHORT_TIME25 //按键单击的“滤波”时间25ms
#define KEY_ENTER_CONTINUITY_TIME 300//按键“从单击进入连击”的间隔时间300ms
#define KEY_CONTINUITY_TIME 80//按键“连击”的间隔时间80ms
#define BUS_P0 P0 //8个LED灯一一对应单片机的P0口总线
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);
void DisplayTask(void); //显示的任务函数(LED显示状态)
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;
unsigned char Gu8LedStatus=0; //LED灯的状态
unsigned char Gu8DisplayUpdate=1; //显示的刷新标志
volatile unsigned char vGu8KeySec=0;//按键的触发序号
volatile unsigned char vGu8ShieldVoiceFlag=0;//屏蔽声音的标志
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
KeyTask(); //按键的任务函数
DisplayTask(); //显示的任务函数(LED显示状态)
}
}
/* 注释一:
* Gu8DisplayUpdate这类“显示刷新变量”在“显示框架”里是很常见的,而且屡用屡爽。
* 目的是,既能及时刷新显示,又能避免主函数“不断去执行显示代码”而影响程序效率。
*/
void DisplayTask(void) //显示的任务函数(LED显示状态)
{
if(1==Gu8DisplayUpdate)//需要刷新一次显示
{
Gu8DisplayUpdate=0;//及时清零,避免主函数“不断去执行显示代码”而影响程序效率
//Gu8LedStatus是左移的位数,范围(0至7),决定了跑马灯的显示状态。
BUS_P0=~(1<<Gu8LedStatus);//“左移<<”之后的“取反~”,因为LED电路是灌入式驱动方式。
}
}
/* 注释二:
* 按键“连续均匀触发”的识别过程:
* 第一步:平时只要K1没有被按下,按键的自锁标志Su8KeyLock1、去抖动延时计数器Su16KeyCnt1、
* 连击计数器Su16KeyContinuityCnt1,一直被清零。
* 第二步:一旦K1按键被按下,去抖动延时计数器Su16KeyCnt1开始在定时中断函数里累加,在还没
* 累加到阀值KEY_SHORT_TIME时,如果在这期间由于受外界干扰或者按键抖动,
* 而使IO口突然瞬间触发成高电平,这个时候马上把延时计数器Su16KeyCnt1清零,
* 这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。
* 第三步:如果K1按键按下的时间超过了阀值KEY_SHORT_TIME,则触发一次“单击”, 同时,马上把自锁
* 标志Su8KeyLock1置1防止按住按键不松手后一直触发,并且把计数器Su16KeyCnt1清零为了下
* 一步用来累加“从单击进入连击的间隔时间1000ms”。如果此时还没有松手,直到发现按下的时
* 间超过“从单击进入连击的间隔时间”阀值KEY_ENTER_CONTINUITY_TIME时,从此进入“连击”
* 的模式,连击计数器Su16KeyContinuityCnt1开始累加,每到达一次阀值
* KEY_CONTINUITY_TIME就触发1次按键,为了屏蔽按键声音及时把vGu8ShieldVoiceFlag也置1,
* 同时,Su16KeyContinuityCnt1马上清零为继续连击作准备。
* 第四步:等K1按键松手后,自锁标志Su8KeyLock1、去抖动延时计数器Su16KeyCnt1、
* 连击计数器Su16KeyContinuityCnt1,及时清零,为下一次按键触发做准备。
*/
void KeyScan(void)//此函数放在定时中断里每1ms扫描一次
{
static unsigned char Su8KeyLock1;
static unsigned intSu16KeyCnt1;
static unsigned intSu16KeyContinuityCnt1;//连击计数器
static unsigned char Su8KeyLock2;
static unsigned intSu16KeyCnt2;
static unsigned intSu16KeyContinuityCnt2;//连击计数器
//K1按键
if(0!=KEY_INPUT1)//单个K1按键没有按下,及时清零一些标志。
{
Su8KeyLock1=0; //按键解锁
Su16KeyCnt1=0;//去抖动延时计数器清零,此行非常巧妙,是全场的亮点。
Su16KeyContinuityCnt1=0;//连击计数器
}
else if(0==Su8KeyLock1)//单个按键K1被按下
{
Su16KeyCnt1++; //累加定时中断次数,每一次累加额度是1ms
if(Su16KeyCnt1>=KEY_SHORT_TIME) //按键的“滤波”时间25ms
{
Su8KeyLock1=1; //“自锁”
vGu8KeySec=1; //触发一次K1按键
Su16KeyCnt1=0; //清零,为了下一步用来累加“从单击进入连击的间隔时间300ms”
}
}
else if(Su16KeyCnt1<=KEY_ENTER_CONTINUITY_TIME)//按住不松手累加到300ms
{
Su16KeyCnt1++; //累加定时中断次数,每一次累加额度是1ms
}
else//按住累加到300ms后仍然不放手,这个时候进入有节奏的连续触发
{
Su16KeyContinuityCnt1++; //连击计数器开始累加,每一次累加额度是1ms
if(Su16KeyContinuityCnt1>=KEY_CONTINUITY_TIME)//按住没松手,每0.08秒就触发一次
{
Su16KeyContinuityCnt1=0; //清零,为了继续连击。
vGu8KeySec=1; //触发一次K1按键
vGu8ShieldVoiceFlag=1;//把当前按键触发的声音屏蔽掉
}
}
//K2按键
if(0!=KEY_INPUT2)
{
Su8KeyLock2=0;
Su16KeyCnt2=0;
Su16KeyContinuityCnt2=0;
}
else if(0==Su8KeyLock2)
{
Su16KeyCnt2++;
if(Su16KeyCnt2>=KEY_SHORT_TIME)
{
Su8KeyLock2=1;
vGu8KeySec=2; //触发一次K2按键
Su16KeyCnt2=0;
}
}
else if(Su16KeyCnt2<=KEY_ENTER_CONTINUITY_TIME)
{
Su16KeyCnt2++;
}
else
{
Su16KeyContinuityCnt2++;
if(Su16KeyContinuityCnt2>=KEY_CONTINUITY_TIME)
{
Su16KeyContinuityCnt2=0;
vGu8KeySec=2; //触发一次K2按键
vGu8ShieldVoiceFlag=1;//把当前按键触发的声音屏蔽掉
}
}
}
void KeyTask(void) //按键任务函数,放在主函数内
{
if(0==vGu8KeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}
switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
case 1: //K1触发的任务
if(Gu8LedStatus>0)
{
Gu8LedStatus--;//控制LED“往左边跑”
Gu8DisplayUpdate=1;//刷新显示
}
if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
{
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发一次“长按”后,发出“嘀”一声
vGu8BeepTimerFlag=1;
}
vGu8ShieldVoiceFlag=0;//及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;
case 2: //K2触发的任务
if(Gu8LedStatus<7)
{
Gu8LedStatus++;//控制LED“往右边跑”
Gu8DisplayUpdate=1;//刷新显示
}
if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
{
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发一次“长按”后,发出“嘀”一声
vGu8BeepTimerFlag=1;
}
vGu8ShieldVoiceFlag=0;//及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;
}
}
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();
}
}
}
}
jianhong_wu
发表于 2017-12-7 11:32:42
本帖最后由 jianhong_wu 于 2017-12-7 11:50 编辑
第九十八节: 独立按键按住不松手的“先加速后匀速”的触发。
【98.1 “先加速后匀速”的触发。】
上图98.1.1独立按键电路
上图98.1.2灌入式驱动8个LED
上图98.1.3有源蜂鸣器电路
当“连续加”或者“连续减”的数据范围很大的时候,就需要按键的加速与匀速相结合的触发方式。“加速”是指按住按键不松手,按键刚开始触发是从慢到快的渐进过程,当“加速”到某个特别快的速度的时候,就“不再加速”,而是以该“恒定高速”进行“连续匀速”触发。这种触发方式,“加速”和“匀速”是相辅相成缺一不可的,为什么?假如没有“加速”只有“匀速”,那么刚按下按键就直接以最高速的“匀速”进行,就会跑过头,缺乏微调功能;而假如没有“匀速”只有“加速”,那么按下按键不松手后,速度就会一直不断飙升,最后失控过冲。
本节例程实现的功能如下:
(1)要更改一个“设置参数”(一个全局变量),参数的范围是0到800。
(2)8个受“设置参数”控制的跑马灯在某一时刻只有1个LED亮,每触发一次K1按键,该“设置参数”就自减1,最小值为0;相反,每触发一次K2按键,该“设置参数”就自加1,最大值为800。
(3)LED灯实时显示“设置参数”的范围状态:
只有第0个LED灯亮:0<=“设置参数”<100。
只有第1个LED灯亮:100<=“设置参数”<200。
只有第2个LED灯亮:200<=“设置参数”<300。
只有第3个LED灯亮:300<=“设置参数”<400。
只有第4个LED灯亮:400<=“设置参数”<500。
只有第5个LED灯亮:500<=“设置参数”<600。
只有第6个LED灯亮:600<=“设置参数”<700。
只有第7个LED灯亮:700<=“设置参数”<=800。
(4)按键每“单击”一次蜂鸣器就鸣叫一次,但是,当按键“从单击进入连击”后,蜂鸣器就不鸣叫。
#include "REG52.H"
#define KEY_VOICE_TIME 50
#define KEY_SHORT_TIME25 //按键单击的“滤波”时间
#define KEY_ENTER_CONTINUITY_TIME 300//按键“从单击进入连击”的间隔时间
#define KEY_CONTINUITY_INITIAL_TIME 80//按键“连击”起始的预设间隔时间
#define KEY_SUB_DT_TIME 8 //按键在“加速”时每次减小的时间。
#define KEY_CONTINUITY_MIN_TIME 10//按键时间减小到最后的“匀速”间隔时间。
#define BUS_P0 P0 //8个LED灯一一对应单片机的P0口总线
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);
void DisplayTask(void); //显示的任务函数(LED显示状态)
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;
unsigned int Gu16SetData=0; //“设置参数”。范围从0到800。LED灯反映该当前值的范围状态
unsigned char Gu8DisplayUpdate=1; //显示的刷新标志
volatile unsigned char vGu8KeySec=0;//按键的触发序号
volatile unsigned char vGu8ShieldVoiceFlag=0;//屏蔽声音的标志
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
KeyTask(); //按键的任务函数
DisplayTask(); //显示的任务函数(LED显示状态)
}
}
/* 注释一:
* Gu8DisplayUpdate这类“显示刷新变量”在“显示框架”里是很常见的,而且屡用屡爽。
* 目的是,既能及时刷新显示,又能避免主函数“不断去执行显示代码”而影响程序效率。
*/
void DisplayTask(void) //显示的任务函数(LED显示状态)
{
if(1==Gu8DisplayUpdate)//需要刷新一次显示
{
Gu8DisplayUpdate=0;//及时清零,避免主函数“不断去执行显示代码”而影响程序效率
if(Gu16SetData<100)
{
BUS_P0=~(1<<0);//第0个灯亮
}
else if(Gu16SetData<200)
{
BUS_P0=~(1<<1);//第1个灯亮
}
else if(Gu16SetData<300)
{
BUS_P0=~(1<<2);//第2个灯亮
}
else if(Gu16SetData<400)
{
BUS_P0=~(1<<3);//第3个灯亮
}
else if(Gu16SetData<500)
{
BUS_P0=~(1<<4);//第4个灯亮
}
else if(Gu16SetData<600)
{
BUS_P0=~(1<<5);//第5个灯亮
}
else if(Gu16SetData<700)
{
BUS_P0=~(1<<6);//第6个灯亮
}
else
{
BUS_P0=~(1<<7);//第7个灯亮
}
}
}
/* 注释二:
* 按键“先加速后匀速”的识别过程:
* 第一步:每次按一次就触发一次“单击”,如果按下去到松手的时间不超过1秒,则不会进入
* “连击”模式。
* 第二步:如果按下去不松手的时间超过1秒,则进入“连击”模式。按键触发的节奏
* 不断加快,直至到达某个极限值,然后以此极限值间隔匀速触发。这就是“先加速后匀速”。
*/
void KeyScan(void)//此函数放在定时中断里每1ms扫描一次
{
static unsigned char Su8KeyLock1;
static unsigned intSu16KeyCnt1;
static unsigned intSu16KeyContinuityCnt1;//连击计数器
static unsigned intSu16KeyContinuityTime1=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值
static unsigned char Su8KeyLock2;
static unsigned intSu16KeyCnt2;
static unsigned intSu16KeyContinuityCnt2;//连击计数器
static unsigned intSu16KeyContinuityTime2=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值
//K1按键
if(0!=KEY_INPUT1)//单个K1按键没有按下,及时清零一些标志。
{
Su8KeyLock1=0; //按键解锁
Su16KeyCnt1=0;//去抖动延时计数器清零,此行非常巧妙,是全场的亮点。
Su16KeyContinuityCnt1=0;//连击计数器
Su16KeyContinuityTime1=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值。重装初始值。
}
else if(0==Su8KeyLock1)//单个按键K1被按下
{
Su16KeyCnt1++; //累加定时中断次数,每一次累加额度是1ms
if(Su16KeyCnt1>=KEY_SHORT_TIME) //按键的“滤波”时间25ms
{
Su8KeyLock1=1; //“自锁”
vGu8KeySec=1; //触发一次K1按键
Su16KeyCnt1=0; //清零,为了下一步用来累加“从单击进入连击的间隔时间300ms”
}
}
else if(Su16KeyCnt1<=KEY_ENTER_CONTINUITY_TIME)//按住不松手累加到300ms
{
Su16KeyCnt1++; //累加定时中断次数,每一次累加额度是1ms
}
else//按住累加到300ms后仍然不放手,这个时候进入有节奏的连续触发
{
Su16KeyContinuityCnt1++; //连击计数器开始累加,每一次累加额度是1ms
if(Su16KeyContinuityCnt1>=Su16KeyContinuityTime1)//按住没松手,每隔一会就触发一次
{
Su16KeyContinuityCnt1=0; //清零,为了继续连击。
vGu8KeySec=1; //触发一次K1按键
vGu8ShieldVoiceFlag=1;//把当前按键触发的声音屏蔽掉
if(Su16KeyContinuityTime1>=KEY_SUB_DT_TIME)
{
//此数值不断被减小,按键的触发速度就不断变快
Su16KeyContinuityTime1=Su16KeyContinuityTime1-KEY_SUB_DT_TIME;//变快节奏
}
if(Su16KeyContinuityTime1<KEY_CONTINUITY_MIN_TIME) //最小间隔时间就是“高速匀速”
{
Su16KeyContinuityTime1=KEY_CONTINUITY_MIN_TIME; //最后以此最高速进行“匀速”
}
}
}
//K2按键
if(0!=KEY_INPUT2)
{
Su8KeyLock2=0;
Su16KeyCnt2=0;
Su16KeyContinuityCnt2=0;
Su16KeyContinuityTime2=KEY_CONTINUITY_INITIAL_TIME;
}
else if(0==Su8KeyLock2)
{
Su16KeyCnt2++;
if(Su16KeyCnt2>=KEY_SHORT_TIME)
{
Su8KeyLock2=1;
vGu8KeySec=2; //触发一次K2按键
Su16KeyCnt2=0;
}
}
else if(Su16KeyCnt2<=KEY_ENTER_CONTINUITY_TIME)
{
Su16KeyCnt2++;
}
else
{
Su16KeyContinuityCnt2++;
if(Su16KeyContinuityCnt2>=Su16KeyContinuityTime2)
{
Su16KeyContinuityCnt2=0;
vGu8KeySec=2; //触发一次K2按键
vGu8ShieldVoiceFlag=1;
if(Su16KeyContinuityTime2>=KEY_SUB_DT_TIME)
{
Su16KeyContinuityTime2=Su16KeyContinuityTime2-KEY_SUB_DT_TIME;
}
if(Su16KeyContinuityTime2<KEY_CONTINUITY_MIN_TIME)
{
Su16KeyContinuityTime2=KEY_CONTINUITY_MIN_TIME;
}
}
}
}
void KeyTask(void) //按键任务函数,放在主函数内
{
if(0==vGu8KeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}
switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
case 1: //K1触发的任务
if(Gu16SetData>0)
{
Gu16SetData--; //“设置参数”
Gu8DisplayUpdate=1;//刷新显示
}
if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
{
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
}
vGu8ShieldVoiceFlag=0;//及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;
case 2: //K2触发的任务
if(Gu16SetData<800)
{
Gu16SetData++; //“设置参数”
Gu8DisplayUpdate=1;//刷新显示
}
if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
{
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
}
vGu8ShieldVoiceFlag=0;//及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;
}
}
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();
}
}
}
}
jianhong_wu
发表于 2017-12-10 11:09:03
本帖最后由 jianhong_wu 于 2017-12-10 11:18 编辑
第九十九节: “行列扫描式”矩阵按键的单个触发(原始版)。
【99.1 “行列扫描式”矩阵按键。】
上图99.1.1有源蜂鸣器电路
上图99.1.23*3矩阵按键的电路
上图是3*3的矩阵按键电路,其它4*4或者8*8的矩阵电路原理是一样的,编程思路也是一样的。相对独立按键,矩阵按键因为采用动态行列扫描的方式,能更加节省IO口,比如3*3的3行3列,1行占用1根IO口,1列占用1根IO口,因此3*3矩阵按键占用6个IO口(3+3=6),但是能识别9个按键(3*3=9)。同理,8*8矩阵按键占用16个IO口(8+8=16),但是能识别64个按键(8*8=64)。
矩阵按键的编程原理。如上图3*3矩阵按键的电路,行IO口(P2.2,P2.1,P2.0)定为输入,列IO口(P2.5,P2.4,P2.3)定为输出。同一时刻,列输出的3个IO口只能有1根是输出L(低电平),其它2根必须全是H(高电平),然后依次轮番切换输出状态,列输出每切换一次,就分别读取一次行输入的3个IO口,这样一次就能识别到3个按键的状态,如果列连续切换3次就可以读取全部9个按键的状态。列的3种输出状态分别是:(P2.5为L,P2.4为H,P2.3为H),(P2.5为H,P2.4为L,P2.3为H),(P2.5为H,P2.4为H,P2.3为L)。为什么列输出每切换一次就能识别到3个按键的状态?因为,首先要明白一个前提,在没有任何按键“被按下”的时候,行输入的3个IO口因为内部上拉电阻的作用,默认状态都是H电平。并且,H与H相互短接输出为H,H与L相互短接输出L,也就是,L(低电平)的优先级最大,任何H(高电平)碰到L(低电平)输出的结果都是L(低电平)。L(低电平)就像数学乘法运算里的数字0,任何数跟0相乘必然等于0。多说一句,这个“L最高优先级”法则是有前提的,就是H(高电平)的产生必须是纯粹靠上拉电阻拉高的H(高电平)才行,比如刚好本教程所用的51单片机内部IO口输出的H(高电平)是依靠内部的上拉电阻产生,如果是其它“非上拉电阻产生的高电平”与“低电平”短接就有“短路烧坏芯片”的风险,这时就需要额外增加“三极管开漏式输出”电路或者外挂“开漏式输出集成芯片”电路。继续回到正题,为什么列输出每切换一次就能识别到3个按键的状态?举个例子,比如当列输出状态处于(P2.5为L,P2.4为H,P2.3为H)下,我们读取行输入的P2.2口,行输入的P2.2与列输出P2.5,P2.4,P2.3的“交叉处”有3个按键S1,S2,S3,此时,如果P2.2口是L(低电平),那么必然是S1“被按下”,因为想让P2.2口是L,只有S1有这个能力,而如果S1没有“被按下”,另外两个S2,S3即使“被按下”,P2.2口也是H而绝对不会为L,因为S2,S3的列输出P2.4为H,P2.3为H,H与H相互短接输出的结果必然为H。
本节例程实现的功能:9个矩阵按键,每按下1个按键都触发一次蜂鸣器鸣叫。#include "REG52.H"
#define KEY_VOICE_TIME 50
#define KEY_SHORT_TIME20 //按键去抖动的“滤波”时间
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 ROW_INPUT1=P2^2;//第1行输入口。
sbit ROW_INPUT2=P2^1;//第2行输入口。
sbit ROW_INPUT3=P2^0;//第3行输入口。
sbit COLUMN_OUTPUT1=P2^5;//第1列输出口。
sbit COLUMN_OUTPUT2=P2^4;//第2列输出口。
sbit COLUMN_OUTPUT3=P2^3;//第3列输出口。
volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;
volatile unsigned char vGu8KeySec=0;//按键的触发序号
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
KeyTask(); //按键的任务函数
}
}
/* 注释一:
*矩阵按键扫描的详细过程:
*先输出某1列低电平,其它2列输出高电平,延时等待2ms后(等此3列输出同步稳定),
*再分别判断3行的输入IO口, 如果发现哪一行是低电平,就说明对应的某个按键被触发。
*依次循环切换输出的3种状态,并且分别判断输入的3行,就可以检测完9个按键。矩阵按键的
*去抖动处理方法跟我前面讲的独立按键去抖动方法是一样的,不再重复多讲。
*/
void KeyScan(void)//此函数放在定时中断里每1ms扫描一次
{
static unsigned char Su8KeyLock=0;
static unsigned intSu16KeyCnt=0;
static unsigned char Su8KeyStep=1;
switch(Su8KeyStep)
{
case 1: //按键扫描输出第一列低电平
COLUMN_OUTPUT1=0;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=1;
Su16KeyCnt=0;//延时计数器清零
Su8KeyStep++;//切换到下一个运行步骤
break;
case 2: //延时等待2ms后(等此3列输出同步稳定)。不是按键的去抖动延时。
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyStep++; //切换到下一个运行步骤
}
break;
case 3:
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su8KeyStep++;//如果没有按键按下,切换到下一个运行步骤
Su8KeyLock=0;//按键自锁标志清零
Su16KeyCnt=0; //按键去抖动延时计数器清零,此行非常巧妙
}
else if(0==Su8KeyLock)//有按键按下,且是第一次触发
{
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;//去抖动延时计数器
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su16KeyCnt=0;
Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
vGu8KeySec=1;//触发1号键 对应S1键
}
}
else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;//去抖动延时计数器
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su16KeyCnt=0;
Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
vGu8KeySec=2;//触发2号键 对应S2键
}
}
else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
{
Su16KeyCnt++;//去抖动延时计数器
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su16KeyCnt=0;
Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
vGu8KeySec=3;//触发3号键 对应S3键
}
}
}
break;
case 4: //按键扫描输出第二列低电平
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=0;
COLUMN_OUTPUT3=1;
Su16KeyCnt=0;//延时计数器清零
Su8KeyStep++;//切换到下一个运行步骤
break;
case 5: //延时等待2ms后(等此3列输出同步稳定)。不是按键的去抖动延时。
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyStep++; //切换到下一个运行步骤
}
break;
case 6:
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su8KeyStep++;//如果没有按键按下,切换到下一个运行步骤
Su8KeyLock=0;//按键自锁标志清零
Su16KeyCnt=0; //按键去抖动延时计数器清零,此行非常巧妙
}
else if(0==Su8KeyLock)//有按键按下,且是第一次触发
{
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;//去抖动延时计数器
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su16KeyCnt=0;
Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
vGu8KeySec=4;//触发4号键 对应S4键
}
}
else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;//去抖动延时计数器
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su16KeyCnt=0;
Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
vGu8KeySec=5;//触发5号键 对应S5键
}
}
else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
{
Su16KeyCnt++;//去抖动延时计数器
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su16KeyCnt=0;
Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
vGu8KeySec=6;//触发6号键 对应S6键
}
}
}
break;
case 7: //按键扫描输出第三列低电平
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=0;
Su16KeyCnt=0;//延时计数器清零
Su8KeyStep++;//切换到下一个运行步骤
break;
case 8: //延时等待2ms后(等此3列输出同步稳定)。不是按键的去抖动延时。
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyStep++; //切换到下一个运行步骤
}
break;
case 9:
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su8KeyStep=1;//如果没有按键按下,返回到第一步,重新开始扫描!!!!!!
Su8KeyLock=0;//按键自锁标志清零
Su16KeyCnt=0; //按键去抖动延时计数器清零,此行非常巧妙
}
else if(0==Su8KeyLock)//有按键按下,且是第一次触发
{
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;//去抖动延时计数器
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su16KeyCnt=0;
Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
vGu8KeySec=7;//触发7号键 对应S7键
}
}
else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;//去抖动延时计数器
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su16KeyCnt=0;
Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
vGu8KeySec=8;//触发8号键 对应S8键
}
}
else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
{
Su16KeyCnt++;//去抖动延时计数器
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su16KeyCnt=0;
Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
vGu8KeySec=9;//触发9号键 对应S9键
}
}
}
break;
}
}
void KeyTask(void) //按键任务函数,放在主函数内
{
if(0==vGu8KeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}
switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
case 1: //S1触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 2: //S2触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 3: //S3触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 4: //S4触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 5: //S5触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 6: //S6触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 7: //S7触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 8: //S8触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 9: //S9触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
}
}
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();
}
}
}
}
jianhong_wu
发表于 2017-12-17 11:28:54
本帖最后由 jianhong_wu 于 2017-12-17 11:50 编辑
第一百节: “行列扫描式”矩阵按键的单个触发(优化版)。
【100.1 “行列扫描式”矩阵按键。】
上图100.1.1有源蜂鸣器电路
上图100.1.23*3矩阵按键的电路
写程序,凡是出现“重复性、相似性”的代码,都可以加入“循环,判断,数组”这类语句对代码进行压缩优化。上一节讲的矩阵按键,代码是记流水账式的,出现很多“重复性、相似性”的代码,是没有经过优化的“原始版”,本节的目的是对上一节的代码进行优化,让大家从中发现一些技巧。
多说一句,我一直认为,只要单片机容量够,代码多一点少一点并不重要,只要不影响运行效率就行。而且有时候,代码写多一点,可读性非常强,修改起来也非常方便。如果一味的追求压缩代码,就会刻意用很多“循环,判断,数组”等元素,代码虽然紧凑了,但是可分离性,可更改性,可阅读性就没那么强。因此,做项目的时候,某些代码要不要进行压缩,是没有绝对标准的,能因敌而取胜者谓之神。
本节例程实现的功能:9个矩阵按键,每按下1个按键都触发一次蜂鸣器鸣叫。#include "REG52.H"
#define KEY_VOICE_TIME 50
#define KEY_SHORT_TIME20 //按键去抖动的“滤波”时间
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 ROW_INPUT1=P2^2;//第1行输入口。
sbit ROW_INPUT2=P2^1;//第2行输入口。
sbit ROW_INPUT3=P2^0;//第3行输入口。
sbit COLUMN_OUTPUT1=P2^5;//第1列输出口。
sbit COLUMN_OUTPUT2=P2^4;//第2列输出口。
sbit COLUMN_OUTPUT3=P2^3;//第3列输出口。
volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;
volatile unsigned char vGu8KeySec=0;//按键的触发序号
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
KeyTask(); //按键的任务函数
}
}
/* 注释一:
*矩阵按键扫描的详细过程:
*先输出某1列低电平,其它2列输出高电平,延时等待2ms后(等此3列输出同步稳定),
*再分别判断3行的输入IO口, 如果发现哪一行是低电平,就说明对应的某个按键被触发。
*依次循环切换输出的3种状态,并且分别判断输入的3行,就可以检测完9个按键。矩阵按键的
*去抖动处理方法跟我前面讲的独立按键去抖动方法是一样的,不再重复多讲。
*/
void KeyScan(void)//此函数放在定时中断里每1ms扫描一次
{
static unsigned char Su8KeyLock=0;
static unsigned intSu16KeyCnt=0;
static unsigned char Su8KeyStep=1;
static unsigned char Su8ColumnRecord=0;//本节多增加此变量用来切换当前列的输出
switch(Su8KeyStep)
{
case 1:
if(0==Su8ColumnRecord)//按键扫描输出第一列低电平
{
COLUMN_OUTPUT1=0;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=1;
}
else if(1==Su8ColumnRecord)//按键扫描输出第二列低电平
{
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=0;
COLUMN_OUTPUT3=1;
}
else //按键扫描输出第三列低电平
{
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=0;
}
Su16KeyCnt=0;//延时计数器清零
Su8KeyStep++;//切换到下一个运行步骤
break;
case 2: //延时等待2ms后(等此3列输出同步稳定)。不是按键的去抖动延时。
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyStep++; //切换到下一个运行步骤
}
break;
case 3:
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su8KeyStep=1;//如果没有按键按下,返回到第一个运行步骤重新开始扫描!!!!!!
Su8KeyLock=0;//按键自锁标志清零
Su16KeyCnt=0;//按键去抖动延时计数器清零,此行非常巧妙
Su8ColumnRecord++;//输出下一列
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0; //依次输出完第3列之后,继续从第1列开始输出低电平
}
}
else if(0==Su8KeyLock)//有按键按下,且是第一次触发
{
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;//去抖动延时计数器
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su16KeyCnt=0;
Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
if(0==Su8ColumnRecord)//第1列输出低电平
{
vGu8KeySec=1;//触发1号键 对应S1键
}
else if(1==Su8ColumnRecord)//第2列输出低电平
{
vGu8KeySec=2;//触发2号键 对应S2键
}
else if(2==Su8ColumnRecord)//第3列输出低电平
{
vGu8KeySec=3;//触发3号键 对应S3键
}
}
}
else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;//去抖动延时计数器
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su16KeyCnt=0;
Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
if(0==Su8ColumnRecord)//第1列输出低电平
{
vGu8KeySec=4;//触发4号键 对应S4键
}
else if(1==Su8ColumnRecord)//第2列输出低电平
{
vGu8KeySec=5;//触发5号键 对应S5键
}
else if(2==Su8ColumnRecord)//第3列输出低电平
{
vGu8KeySec=6;//触发6号键 对应S6键
}
}
}
else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
{
Su16KeyCnt++;//去抖动延时计数器
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su16KeyCnt=0;
Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
if(0==Su8ColumnRecord)//第1列输出低电平
{
vGu8KeySec=7;//触发7号键 对应S7键
}
else if(1==Su8ColumnRecord)//第2列输出低电平
{
vGu8KeySec=8;//触发8号键 对应S8键
}
else if(2==Su8ColumnRecord)//第3列输出低电平
{
vGu8KeySec=9;//触发9号键 对应S9键
}
}
}
}
break;
}
}
void KeyTask(void) //按键任务函数,放在主函数内
{
if(0==vGu8KeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}
switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
case 1: //S1触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 2: //S2触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 3: //S3触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 4: //S4触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 5: //S5触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 6: //S6触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 7: //S7触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 8: //S8触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 9: //S9触发的任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
}
}
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();
}
}
}
}
jianhong_wu
发表于 2017-12-31 16:36:01
本帖最后由 jianhong_wu 于 2017-12-31 17:49 编辑
第一百零一节: 矩阵按键鼠标式的单击与双击。
【101.1 矩阵按键鼠标式的单击与双击。】
上图101.1.1有源蜂鸣器电路
上图101.1.2LED电路
上图101.1.33*3矩阵按键的电路
矩阵按键与前面章节独立按键的单击与双击的处理思路是一样的,本节讲矩阵按键的单击与双击,也算是重温之前章节讲的内容。
鼠标的左键,可以触发单击,也可以触发双击。双击的规则是这样的,两次单击,如果第1次单击与第2次单击的时间比较“短”的时候,则这两次单击就构成双击。编写这个程序的最大亮点是如何控制好第1次单击与第2次单击的时间间隔。程序例程要实现的功能是:以S1按键为例,(1)单击改变LED灯的显示状态。单击一次LED从原来“灭”的状态变成“亮”的状态,或者从原来“亮”的状态变成“灭”的状态,依次循环切换。(2)双击则蜂鸣器发出“嘀”的一声。代码如下:#include "REG52.H"
#define KEY_VOICE_TIME 50
#define KEY_SHORT_TIME20 //按键去抖动的“滤波”时间
#define KEY_INTERVAL_TIME80//连续两次单击之间的最大有效时间。因为是矩阵,80不一定是80ms
void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;
void BeepOpen(void);
void BeepClose(void);
void LedOpen(void);
void LedClose(void);
void VoiceScan(void);
void KeyScan(void);
void SingleKeyTask(void); //单击按键任务函数,放在主函数内
void DoubleKeyTask(void); //双击按键任务函数,放在主函数内
sbit P3_4=P3^4; //蜂鸣器
sbit P1_4=P1^4; //LED
sbit ROW_INPUT1=P2^2;//第1行输入口。
sbit ROW_INPUT2=P2^1;//第2行输入口。
sbit ROW_INPUT3=P2^0;//第3行输入口。
sbit COLUMN_OUTPUT1=P2^5;//第1列输出口。
sbit COLUMN_OUTPUT2=P2^4;//第2列输出口。
sbit COLUMN_OUTPUT3=P2^3;//第3列输出口。
volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;
unsigned char Gu8LedStatus=0; //记录LED灯的状态,0代表灭,1代表亮
volatile unsigned char vGu8SingleKeySec=0;//单击按键的触发序号
volatile unsigned char vGu8DoubleKeySec=0;//双击按键的触发序号
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
SingleKeyTask(); //单击按键任务函数
DoubleKeyTask(); //双击按键任务函数
}
}
/* 注释一:
*矩阵按键扫描的详细过程:
*先输出某1列低电平,其它2列输出高电平,延时等待2ms后(等此3列输出同步稳定),
*再分别判断3行的输入IO口, 如果发现哪一行是低电平,就说明对应的某个按键被触发。
*依次循环切换输出的3种状态,并且分别判断输入的3行,就可以检测完9个按键。矩阵按键的
*去抖动处理方法跟我前面讲的独立按键去抖动方法是一样的。
*/
/* 注释二:
* 双击按键扫描的详细过程:
* 第一步:平时没有按键被触发时,按键的自锁标志,去抖动延时计数器一直被清零。
* 如果之前已经有按键触发过1次单击,那么启动时间间隔计数器Su16KeyIntervalCnt1,
* 在KEY_INTERVAL_TIME这个允许的时间差范围内,如果一直没有第2次单击触发,
* 则把累加按键触发的次数Su8KeyTouchCnt1也清零,上一次累计的单击数被清零,
* 就意味着下一次新的双击必须重新开始累加两次单击数。
* 第二步:一旦有按键被按下,去抖动延时计数器开始在定时中断函数里累加,在还没累加到
* 阀值KEY_SHORT_TIME时,如果在这期间由于受外界干扰或者按键抖动,而使
* IO口突然瞬间触发成高电平,这个时候马上把延时计数器Su16KeyCnt
* 清零了,这个过程非常巧妙,非常有效地去除瞬间的杂波干扰,以后凡是用到开关感应器的时候,
* 都可以用类似这样的方法去干扰。
* 第三步:如果按键按下的时间超过了阀值KEY_SHORT_TIME,马上把自锁标志Su8KeyLock置1,
* 防止按住按键不松手后一直触发。与此同时,累加1次按键次数,如果按键次数累加有2次,
* 则认为触发双击按键,并把编号vGu8DoubleKeySec赋值。
* 第四步:等按键松开后,自锁标志Su8KeyLock及时清零解锁,为下一次自锁做准备。并且累加间隔时间,
* 防止两次按键的间隔时间太长。如果连续2次单击的间隔时间太长达到了KEY_INTERVAL_TIME
* 的长度,立即清零当前按键次数的计数器,这样意味着上一次的累加单击数无效,下一次双击
* 必须重新累加新的单击数。
*/
void KeyScan(void)//此函数放在定时中断里每1ms扫描一次
{
static unsigned char Su8KeyLock=0;
static unsigned intSu16KeyCnt=0;
static unsigned char Su8KeyStep=1;
static unsigned char Su8ColumnRecord=0;//用来切换当前列的输出
static unsigned char Su8KeyTouchCnt1; //S1按键的次数记录
static unsigned intSu16KeyIntervalCnt1; //S1按键的间隔时间计数器
switch(Su8KeyStep)
{
case 1:
if(0==Su8ColumnRecord)//按键扫描输出第一列低电平
{
COLUMN_OUTPUT1=0;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=1;
}
else if(1==Su8ColumnRecord)//按键扫描输出第二列低电平
{
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=0;
COLUMN_OUTPUT3=1;
}
else //按键扫描输出第三列低电平
{
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=0;
}
Su16KeyCnt=0;//延时计数器清零
Su8KeyStep++;//切换到下一个运行步骤
break;
case 2: //延时等待2ms后(等此3列输出同步稳定)。不是按键的去抖动延时。
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyStep++; //切换到下一个运行步骤
}
break;
case 3:
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su8KeyStep=1;//如果没有按键按下,返回到第一个运行步骤重新开始扫描!!!!!!
Su8KeyLock=0;//按键自锁标志清零
Su16KeyCnt=0;//按键去抖动延时计数器清零,此行非常巧妙
if(Su8KeyTouchCnt1>=1) //之前已经有按键触发过一次,启动间隔时间的计数器
{
Su16KeyIntervalCnt1++; //按键间隔的时间计数器累加
if(Su16KeyIntervalCnt1>=KEY_INTERVAL_TIME) //达到最大允许的间隔时间,溢出无效
{
Su16KeyIntervalCnt1=0; //时间计数器清零
Su8KeyTouchCnt1=0; //清零按键的按下的次数,因为间隔时间溢出无效
}
}
Su8ColumnRecord++;//输出下一列
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0; //依次输出完第3列之后,继续从第1列开始输出低电平
}
}
else if(0==Su8KeyLock)//有按键按下,且是第一次触发
{
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;//去抖动延时计数器
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
if(0==Su8ColumnRecord)//第1列输出低电平
{
Su16KeyIntervalCnt1=0; //按键有效间隔的时间计数器清零
Su8KeyTouchCnt1++; //记录当前单击的次数
if(1==Su8KeyTouchCnt1) //只按了1次
{
vGu8SingleKeySec=1; //单击任务,触发1号键 对应S1键
}
else if(Su8KeyTouchCnt1>=2)//连续按了两次以上
{
Su8KeyTouchCnt1=0; //统计按键次数清零
vGu8SingleKeySec=1; //单击任务,触发1号键 对应S1键
vGu8DoubleKeySec=1; //双击任务,触发1号键 对应S1键
}
}
else if(1==Su8ColumnRecord)//第2列输出低电平
{
vGu8SingleKeySec=2;//触发2号键 对应S2键
}
else if(2==Su8ColumnRecord)//第3列输出低电平
{
vGu8SingleKeySec=3;//触发3号键 对应S3键
}
}
}
else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;//去抖动延时计数器
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
if(0==Su8ColumnRecord)//第1列输出低电平
{
vGu8SingleKeySec=4;//触发4号键 对应S4键
}
else if(1==Su8ColumnRecord)//第2列输出低电平
{
vGu8SingleKeySec=5;//触发5号键 对应S5键
}
else if(2==Su8ColumnRecord)//第3列输出低电平
{
vGu8SingleKeySec=6;//触发6号键 对应S6键
}
}
}
else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
{
Su16KeyCnt++;//去抖动延时计数器
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
if(0==Su8ColumnRecord)//第1列输出低电平
{
vGu8SingleKeySec=7;//触发7号键 对应S7键
}
else if(1==Su8ColumnRecord)//第2列输出低电平
{
vGu8SingleKeySec=8;//触发8号键 对应S8键
}
else if(2==Su8ColumnRecord)//第3列输出低电平
{
vGu8SingleKeySec=9;//触发9号键 对应S9键
}
}
}
}
break;
}
}
void SingleKeyTask(void) //按键单击的任务函数,放在主函数内
{
if(0==vGu8SingleKeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}
switch(vGu8SingleKeySec) //根据不同的按键触发序号执行对应的代码
{
case 1: //S1按键的单击任务
//通过Gu8LedStatus的状态切换,来反复切换LED的“灭”与“亮”的状态
if(0==Gu8LedStatus)
{
Gu8LedStatus=1; //标识并且更改当前LED灯的状态。0就变成1。
LedOpen(); //点亮LED
}
else
{
Gu8LedStatus=0; //标识并且更改当前LED灯的状态。1就变成0。
LedClose();//关闭LED
}
vGu8SingleKeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
default://其它按键触发的单击
vGu8SingleKeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
}
}
void DoubleKeyTask(void) //双击按键任务函数,放在主函数内
{
if(0==vGu8DoubleKeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}
switch(vGu8DoubleKeySec) //根据不同的按键触发序号执行对应的代码
{
case 1: //S1按键的双击任务
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发双击后,发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8DoubleKeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;
}
}
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)
{
/* 注释三:
* 把LED的初始化放在PeripheralInitial而不是放在SystemInitial,是因为LED显示内容对上电
* 瞬间的要求不高。但是,如果是控制继电器,则应该把继电器的输出初始化放在SystemInitial。
*/
//根据Gu8LedStatus的值来初始化LED当前的显示状态,0代表灭,1代表亮
if(0==Gu8LedStatus)
{
LedClose();//关闭LED
}
else
{
LedOpen(); //点亮LED
}
}
void BeepOpen(void)
{
P3_4=0;
}
void BeepClose(void)
{
P3_4=1;
}
void LedOpen(void)
{
P1_4=0;
}
void LedClose(void)
{
P1_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();
}
}
}
}
jianhong_wu
发表于 2018-1-7 12:24:28
本帖最后由 jianhong_wu 于 2018-1-7 12:39 编辑
第一百零二节: 两个“任意行输入”矩阵按键的“有序”组合触发。
【102.1 “异行输入”“同行输入”“有序”。】
上图102.1.1有源蜂鸣器电路
上图102.1.2LED电路
上图102.1.33*3矩阵按键的电路
“任意行输入”是指能兼容“异行输入”与“同行输入”这两种按键状态。
何谓“异行输入”何谓“同行输入”?如上图矩阵按键,P2.2,P2.1,P2.0是输入行,P2.5,P2.4,P2.3是输出列。以S1按键为例,很明显,S2和S3都是属于S1的“同行输入”,都是属于P2.2的输入行。除了S2和S3以外,其它所有的按键都是S1的“异行输入”,比如S5按键就是S1的“异行输入”,因为S1是属于P2.2的输入行,而S5是属于P2.1的输入行。
何谓“有序”组合触发?就是两个按键的触发必须遵守“先后顺序”才能构成“组合触发”。比如,像电脑的复制快捷键(Ctrl+C),你必须先按住Ctrl再按住C此时“复制快捷键”才有效,如果你先按住C再按住Ctrl此时“复制快捷键”无效。
“异行输入”与“同行输入”,相比之下,“同行输入”更难更有代表性,如果把“同行输入”的程序写出来了,那么完全按“同行输入”的思路,就可以把“异行输入”的程序写出来。因此,只要把“同行输入”的程序写出来了,也就意味着“任意行输入”的程序也就实现了。本节以S1和S2的“同行输入”按键为例,S1是主键,类似复制快捷键的Ctrl键;S2是从键,类似复制快捷键的C键。要触发组合键(S1+S2),必须先按住S1再按S2才有效。功能如下:(1)S1每单击一次,LED要么从“灭”变成“亮”,要么从“亮”变成“灭”,在两种状态之间切换。(2)如果先按住S1再按S2,就认为构造了“有序”组合键,蜂鸣器发出“嘀”的一声。#include "REG52.H"
#define KEY_VOICE_TIME 50
#define KEY_SHORT_TIME20
void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;
void BeepOpen(void);
void BeepClose(void);
void LedOpen(void);
void LedClose(void);
void VoiceScan(void);
void KeyScan(void);
void SingleKeyTask(void);
void DoubleKeyTask(void);
sbit P3_4=P3^4;
sbit P1_4=P1^4;
sbit ROW_INPUT1=P2^2;//第1行输入口。
sbit ROW_INPUT2=P2^1;//第2行输入口。
sbit ROW_INPUT3=P2^0;//第3行输入口。
sbit COLUMN_OUTPUT1=P2^5;//第1列输出口。
sbit COLUMN_OUTPUT2=P2^4;//第2列输出口。
sbit COLUMN_OUTPUT3=P2^3;//第3列输出口。
volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;
unsigned char Gu8LedStatus=0;
volatile unsigned char vGu8SingleKeySec=0;
volatile unsigned char vGu8DoubleKeySec=0;
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
SingleKeyTask();
DoubleKeyTask();
}
}
/* 注释一:
*两个“任意行输入”矩阵按键“有序”触发的两个最关键地方:
*(1)当S1按键被按下单击触发之后, “马上更新输出列的信号状态”,然后切换到后面的步骤。
*(2)在后面的步骤里,进入到S1和S2两个按键的轮番循环监控之中,如果发现S1按键率先
* 被松开了,就把步骤切换到开始的第一步,重新开始新一轮的按键扫描。
*(3)按照这个模板,只需“更改不同的列输出,判断不同的行输入”,就可以实现“任意行输入”
* 矩阵按键的“有序”组合触发。
*/
void KeyScan(void)//此函数放在定时中断里每1ms扫描一次
{
static unsigned char Su8KeyLock=0;
static unsigned intSu16KeyCnt=0;
static unsigned char Su8KeyStep=1;
static unsigned char Su8ColumnRecord=0;
switch(Su8KeyStep)
{
case 1:
if(0==Su8ColumnRecord)
{
COLUMN_OUTPUT1=0;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=1;
}
else if(1==Su8ColumnRecord)
{
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=0;
COLUMN_OUTPUT3=1;
}
else
{
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=0;
}
Su16KeyCnt=0;
Su8KeyStep++;
break;
case 2: //等待列输出稳定,但不是去抖动延时
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyStep++;
}
break;
case 3:
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su8KeyStep=1;
Su8KeyLock=0;
Su16KeyCnt=0;
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
else if(0==Su8KeyLock)
{
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;
if(0==Su8ColumnRecord)
{
vGu8SingleKeySec=1; //单击任务,触发1号键 对应S1键
//“马上更新输出列的信号状态”
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=0; //列2也输出0,非常关键的代码!
COLUMN_OUTPUT3=1;
Su16KeyCnt=0;//去抖动延时清零,为下一步计时做准备
Su8KeyStep++; //切换到下一步步骤
}
else if(1==Su8ColumnRecord)
{
vGu8SingleKeySec=2;
}
else if(2==Su8ColumnRecord)
{
vGu8SingleKeySec=3;
}
}
}
else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;
if(0==Su8ColumnRecord)
{
vGu8SingleKeySec=4;
}
else if(1==Su8ColumnRecord)
{
vGu8SingleKeySec=5;
}
else if(2==Su8ColumnRecord)
{
vGu8SingleKeySec=6;
}
}
}
else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;
if(0==Su8ColumnRecord)
{
vGu8SingleKeySec=7;
}
else if(1==Su8ColumnRecord)
{
vGu8SingleKeySec=8;
}
else if(2==Su8ColumnRecord)
{
vGu8SingleKeySec=9;
}
}
}
}
break;
case 4: //等待列输出稳定,但不是去抖动延时
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyLock=0; //关键语句!自锁清零,为下一步自锁组合按键做准备
Su8KeyStep++;
}
break;
case 5: //判断S2按键
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S2按键没有被按下
{
Su8KeyLock=0;
Su16KeyCnt=0;
//“马上更新输出列的信号状态”
COLUMN_OUTPUT1=0; //列1输出0,非常关键的代码!
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=1;
Su8KeyStep++; //切换到下一个步骤,监控S1是否率先已经松开
}
else if(0==Su8KeyLock)
{
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S2按键被按下
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;//组合按键的自锁
vGu8DoubleKeySec=1; //触发组合按键(S1+S2)
}
}
}
break;
case 6: //等待列输出稳定,但不是去抖动延时
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyLock=0; //关键语句!自锁清零,为下一步自锁组合按键做准备
Su8KeyStep++;
}
break;
case 7: //监控S1按键是否率先已经松开
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt=0;
Su8KeyLock=0;
Su8KeyStep=1; //如果S1按键已经松开,返回到第一个运行步骤重新开始扫描
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
else
{
//“马上更新输出列的信号状态”
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=0; //列2输出0,非常关键的代码!
COLUMN_OUTPUT3=1;
Su8KeyStep=4; //如果S1按键没有松开,继续返回判断S2是否已按下
}
break;
}
}
void SingleKeyTask(void)
{
if(0==vGu8SingleKeySec)
{
return;
}
switch(vGu8SingleKeySec)
{
case 1: //S1按键的单击任务,更改LED灯的显示状态
if(0==Gu8LedStatus)
{
Gu8LedStatus=1;
LedOpen();
}
else
{
Gu8LedStatus=0;
LedClose();
}
vGu8SingleKeySec=0;
break;
default:
vGu8SingleKeySec=0;
break;
}
}
void DoubleKeyTask(void)
{
if(0==vGu8DoubleKeySec)
{
return;
}
switch(vGu8DoubleKeySec)
{
case 1: //S1与S2的组合按键触发,发出“嘀”一声
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;
vGu8BeepTimerFlag=1;
vGu8DoubleKeySec=0;
break;
}
}
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)
{
if(0==Gu8LedStatus)
{
LedClose();
}
else
{
LedOpen();
}
}
void BeepOpen(void)
{
P3_4=0;
}
void BeepClose(void)
{
P3_4=1;
}
void LedOpen(void)
{
P1_4=0;
}
void LedClose(void)
{
P1_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();
}
}
}
}
jianhong_wu
发表于 2018-1-14 10:31:53
本帖最后由 jianhong_wu 于 2018-1-14 10:47 编辑
第一百零三节: 两个“任意行输入”矩阵按键的“无序”组合触发。
【103.1 “无序”组合触发。】
上图103.1.1有源蜂鸣器电路
上图103.1.2LED电路
上图103.1.33*3矩阵按键的电路
“无序”是指两个组合按键不分先后顺序,都能构成组合触发。比如,要触发组合键(S1+S2),先按S1再按S2,或者先按S2再按S1,功能都是一样的。
本节程序功能如下:(1)S1每单击一次,P1.4所在的LED要么从“灭”变成“亮”,要么从“亮”变成“灭”,在两种状态之间切换。(2)S2每单击一次,P1.5所在的LED要么从“灭”变成“亮”,要么从“亮”变成“灭”,在两种状态之间切换。(3)如果先按住S1再按S2,或者先按住S2再按S1,都认为构造了“无序”组合键,蜂鸣器发出“嘀”的一声。#include "REG52.H"
#define KEY_VOICE_TIME 50
#define KEY_SHORT_TIME20
void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;
void BeepOpen(void);
void BeepClose(void);
void LedOpen_P1_4(void);
void LedClose_P1_4(void);
void LedOpen_P1_5(void);
void LedClose_P1_5(void);
void VoiceScan(void);
void KeyScan(void);
void SingleKeyTask(void);
void DoubleKeyTask(void);
sbit P3_4=P3^4;
sbit P1_4=P1^4; //P1.4所在的LED
sbit P1_5=P1^5; //P1.5所在的LED
sbit ROW_INPUT1=P2^2;//第1行输入口。
sbit ROW_INPUT2=P2^1;//第2行输入口。
sbit ROW_INPUT3=P2^0;//第3行输入口。
sbit COLUMN_OUTPUT1=P2^5;//第1列输出口。
sbit COLUMN_OUTPUT2=P2^4;//第2列输出口。
sbit COLUMN_OUTPUT3=P2^3;//第3列输出口。
volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;
unsigned char Gu8LedStatus_P1_4=0;//P1.4所在的LED的状态
unsigned char Gu8LedStatus_P1_5=0;//P1.5所在的LED的状态
volatile unsigned char vGu8SingleKeySec=0;
volatile unsigned char vGu8DoubleKeySec=0;
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
SingleKeyTask();
DoubleKeyTask();
}
}
/* 注释一:
*矩阵按键“无序”触发的两个最关键地方:
*(1)如果是S1按键先被按下并且单击触发之后,“马上更新输出列的信号状态”,然后切换到
* “S1后面所在的步骤里”,进入到S1和S2两个按键的轮番循环监控之中,如果发现S1按键率先
* 被松开了,就把步骤切换到开始的第一步,重新开始新一轮的按键扫描。
*(2)如果是S2按键先被按下并且单击触发之后,“马上更新输出列的信号状态”,然后切换到
* “S2后面所在的步骤里”,进入到S1和S2两个按键的轮番循环监控之中,如果发现S2按键率先
* 被松开了,就把步骤切换到开始的第一步,重新开始新一轮的按键扫描。
*(3)上面两个描述中的两种步骤,“S1后面所在的步骤里”和“S2后面所在的步骤里”是分开的,
* 不共用的,这是本节破题的关键。
*/
void KeyScan(void)//此函数放在定时中断里每1ms扫描一次
{
static unsigned char Su8KeyLock=0;
static unsigned intSu16KeyCnt=0;
static unsigned char Su8KeyStep=1;
static unsigned char Su8ColumnRecord=0;
switch(Su8KeyStep)
{
case 1:
if(0==Su8ColumnRecord)
{
COLUMN_OUTPUT1=0;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=1;
}
else if(1==Su8ColumnRecord)
{
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=0;
COLUMN_OUTPUT3=1;
}
else
{
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=0;
}
Su16KeyCnt=0;
Su8KeyStep++;
break;
case 2: //等待列输出稳定,但不是去抖动延时
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyStep++;
}
break;
case 3:
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su8KeyStep=1;
Su8KeyLock=0;
Su16KeyCnt=0;
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
else if(0==Su8KeyLock)
{
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;
if(0==Su8ColumnRecord)
{
vGu8SingleKeySec=1; //单击任务,触发1号键 对应S1键
//“马上更新输出列的信号状态”
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=0; //列2也输出0,下一步监控S2,非常关键的代码!
COLUMN_OUTPUT3=1;
Su16KeyCnt=0;//去抖动延时清零,为下一步计时做准备
Su8KeyStep=4; //切换到“S1后面所在的步骤里”,破题的关键!!!
}
else if(1==Su8ColumnRecord)
{
vGu8SingleKeySec=2; //单击任务,触发2号键 对应S2键
//“马上更新输出列的信号状态”
COLUMN_OUTPUT1=0;//列1也输出0,下一步监控S1,非常关键的代码!
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=1;
Su16KeyCnt=0;//去抖动延时清零,为下一步计时做准备
Su8KeyStep=8; //切换到“S2后面所在的步骤里”,破题的关键!!!
}
else if(2==Su8ColumnRecord)
{
vGu8SingleKeySec=3;
}
}
}
else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;
if(0==Su8ColumnRecord)
{
vGu8SingleKeySec=4;
}
else if(1==Su8ColumnRecord)
{
vGu8SingleKeySec=5;
}
else if(2==Su8ColumnRecord)
{
vGu8SingleKeySec=6;
}
}
}
else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;
if(0==Su8ColumnRecord)
{
vGu8SingleKeySec=7;
}
else if(1==Su8ColumnRecord)
{
vGu8SingleKeySec=8;
}
else if(2==Su8ColumnRecord)
{
vGu8SingleKeySec=9;
}
}
}
}
break;
/*--------------“S1后面所在的步骤里”------------------*/
case 4: //等待列输出稳定,但不是去抖动延时
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyLock=0; //关键语句!自锁清零,为下一步自锁组合按键做准备
Su8KeyStep++;
}
break;
case 5: //判断S2按键
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S2按键没有被按下
{
Su8KeyLock=0;
Su16KeyCnt=0;
//“马上更新输出列的信号状态”
COLUMN_OUTPUT1=0; //列1输出0,下一步监控S1,非常关键的代码!
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=1;
Su8KeyStep++; //切换到下一个步骤,监控S1是否率先已经松开
}
else if(0==Su8KeyLock)
{
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S2按键被按下
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;//组合按键的自锁
vGu8DoubleKeySec=1; //触发组合按键(S1+S2)
}
}
}
break;
case 6: //等待列输出稳定,但不是去抖动延时
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyLock=0; //关键语句!自锁清零,为下一步自锁组合按键做准备
Su8KeyStep++;
}
break;
case 7: //监控S1按键是否率先已经松开
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt=0;
Su8KeyLock=0;
Su8KeyStep=1; //如果S1按键已经松开,返回到第一个运行步骤重新开始扫描
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
else
{
//“马上更新输出列的信号状态”
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=0; //列2输出0,下一步监控S2,非常关键的代码!
COLUMN_OUTPUT3=1;
Su8KeyStep=4; //如果S1按键没有松开,继续返回判断S2是否已按下
}
break;
/*--------------“S2后面所在的步骤里”------------------*/
case 8: //等待列输出稳定,但不是去抖动延时
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyLock=0; //关键语句!自锁清零,为下一步自锁组合按键做准备
Su8KeyStep++;
}
break;
case 9: //判断S1按键
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S1按键没有被按下
{
Su8KeyLock=0;
Su16KeyCnt=0;
//“马上更新输出列的信号状态”
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=0;//列2输出0,下一步监控S2,非常关键的代码!
COLUMN_OUTPUT3=1;
Su8KeyStep++; //切换到下一个步骤,监控S2是否率先已经松开
}
else if(0==Su8KeyLock)
{
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S1按键被按下
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;//组合按键的自锁
vGu8DoubleKeySec=1; //触发组合按键(S1+S2)
}
}
}
break;
case 10: //等待列输出稳定,但不是去抖动延时
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyLock=0; //关键语句!自锁清零,为下一步自锁组合按键做准备
Su8KeyStep++;
}
break;
case 11: //监控S2按键是否率先已经松开
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt=0;
Su8KeyLock=0;
Su8KeyStep=1; //如果S2按键已经松开,返回到第一个运行步骤重新开始扫描
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
else
{
//“马上更新输出列的信号状态”
COLUMN_OUTPUT1=0; //列1输出0,下一步监控S1,非常关键的代码!
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=1;
Su8KeyStep=8; //如果S2按键没有松开,继续返回判断S1是否已按下
}
break;
}
}
void SingleKeyTask(void)
{
if(0==vGu8SingleKeySec)
{
return;
}
switch(vGu8SingleKeySec)
{
case 1: //S1按键的单击任务,更改P1.4所在的LED灯的显示状态
if(0==Gu8LedStatus_P1_4)
{
Gu8LedStatus_P1_4=1;
LedOpen_P1_4();
}
else
{
Gu8LedStatus_P1_4=0;
LedClose_P1_4();
}
vGu8SingleKeySec=0;
break;
case 2: //S2按键的单击任务,更改P1.5所在的LED灯的显示状态
if(0==Gu8LedStatus_P1_5)
{
Gu8LedStatus_P1_5=1;
LedOpen_P1_5();
}
else
{
Gu8LedStatus_P1_5=0;
LedClose_P1_5();
}
vGu8SingleKeySec=0;
break;
default:
vGu8SingleKeySec=0;
break;
}
}
void DoubleKeyTask(void)
{
if(0==vGu8DoubleKeySec)
{
return;
}
switch(vGu8DoubleKeySec)
{
case 1: //S1与S2的组合按键触发,发出“嘀”一声
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;
vGu8BeepTimerFlag=1;
vGu8DoubleKeySec=0;
break;
}
}
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)
{
if(0==Gu8LedStatus_P1_4)
{
LedClose_P1_4();
}
else
{
LedOpen_P1_4();
}
if(0==Gu8LedStatus_P1_5)
{
LedClose_P1_5();
}
else
{
LedOpen_P1_5();
}
}
void BeepOpen(void)
{
P3_4=0;
}
void BeepClose(void)
{
P3_4=1;
}
void LedOpen_P1_4(void)
{
P1_4=0;
}
void LedClose_P1_4(void)
{
P1_4=1;
}
void LedOpen_P1_5(void)
{
P1_5=0;
}
void LedClose_P1_5(void)
{
P1_5=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();
}
}
}
}
jianhong_wu
发表于 2018-1-22 15:15:05
本帖最后由 jianhong_wu 于 2018-1-22 15:32 编辑
第一百零四节: 矩阵按键“一键两用”的短按与长按。
【104.1 “一键两用”的短按与长按。】
上图104.1.1有源蜂鸣器电路
上图104.1.2LED电路
上图104.1.33*3矩阵按键的电路
矩阵按键与前面章节独立按键的“短按与长按”的处理思路是一样的,本节讲矩阵按键的“短按与长按”,也算是重温之前章节讲的内容。“短按与长按”的原理是依赖“按键按下的时间长度”来区分识别。“短按”是指从按下的“下降沿”到松手的“上升沿”时间,“长按”是指从按下的“下降沿”到一直按住不松手的“低电平持续时间”。本节的例程功能如下:(1)S1每“短按”一次,LED要么从“灭”变成“亮”,要么从“亮”变成“灭”,在两种状态之间切换。(2)S1每“长按”一次,蜂鸣器发出“嘀”的一声。代码如下:
#include "REG52.H"
#define KEY_VOICE_TIME 50
#define KEY_SHORT_TIME20 //按键的“短按”兼“滤波”的“稳定时间”
#define KEY_LONG_TIME400 //按键的“长按”兼“滤波”的“稳定时间”
void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;
void BeepOpen(void);
void BeepClose(void);
void LedOpen_P1_4(void);
void LedClose_P1_4(void);
void VoiceScan(void);
void KeyScan(void);
void KeyTask(void);
sbit P3_4=P3^4;
sbit P1_4=P1^4;
sbit ROW_INPUT1=P2^2;//第1行输入口。
sbit ROW_INPUT2=P2^1;//第2行输入口。
sbit ROW_INPUT3=P2^0;//第3行输入口。
sbit COLUMN_OUTPUT1=P2^5;//第1列输出口。
sbit COLUMN_OUTPUT2=P2^4;//第2列输出口。
sbit COLUMN_OUTPUT3=P2^3;//第3列输出口。
volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;
unsigned char Gu8LedStatus_P1_4=0;
volatile unsigned char vGu8KeySec=0;//短按与长按共用一个全局变量vGu8KeySec来传递按键信息
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
KeyTask();
}
}
/* 注释一:
*本节破题的关键:
*矩阵按键涉及的按键数量很多,但是实际项目上一般只需要少数个别按键具备这种
*“短按”与“长按”的特殊技能,因此,在代码上,必须把这类“特殊技能按键”与
*“大众按键”区分开来,才能相互清晰互不干扰。本节的“特殊技能按键”是S1。
*/
void KeyScan(void)//此函数放在定时中断里每1ms扫描一次
{
static unsigned char Su8KeyLock=0;
static unsigned intSu16KeyCnt=0;
static unsigned char Su8KeyStep=1;
static unsigned char Su8ColumnRecord=0;
static unsigned char Su8KeyShortFlag_S1=0;//S1按键专属的“短按”触发标志
switch(Su8KeyStep)
{
case 1:
if(0==Su8ColumnRecord)
{
COLUMN_OUTPUT1=0;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=1;
}
else if(1==Su8ColumnRecord)
{
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=0;
COLUMN_OUTPUT3=1;
}
else
{
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=0;
}
Su16KeyCnt=0;
Su8KeyStep++;
break;
case 2: //等待列输出稳定,但不是去抖动延时
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyStep++;
}
break;
case 3:
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su8KeyStep=1;
Su8KeyLock=0;
Su16KeyCnt=0;
if(1==Su8KeyShortFlag_S1)//松手的时候,如果“短按”标志有效就触发一次“短按”
{
Su8KeyShortFlag_S1=0; //先清零“短按”标志避免一直触发。
vGu8KeySec=1; //触发S1的“短按”
}
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
else if(0==Su8KeyLock)
{
//以下第1行,直接把S1按键单独扣出来,用“&&0==Su8ColumnRecord”作为筛选条件
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3&&0==Su8ColumnRecord)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME) //“短按”兼“滤波”的“稳定时间”
{
//注意,这里不能“自锁”。后面“长按”触发的时候才“自锁”。
Su8KeyShortFlag_S1=1; //S1的“短按”标志有效,待松手时触发。
}
if(Su16KeyCnt>=KEY_LONG_TIME) //“长按”兼“滤波”的“稳定时间”
{
Su8KeyLock=1; //此时“长按”触发才“自锁”
Su8KeyShortFlag_S1=0;//既然此时“长按”有效,那么就要废除潜在的“短按”。
vGu8KeySec=21; //触发S1的“长按”
}
}
else if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;
//既然S1按键已经被上面几行代码单独扣出来,这里就直接从S2按键开始判断
if(1==Su8ColumnRecord)
{
vGu8KeySec=2;
}
else if(2==Su8ColumnRecord)
{
vGu8KeySec=3;
}
}
}
else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;
if(0==Su8ColumnRecord)
{
vGu8KeySec=4;
}
else if(1==Su8ColumnRecord)
{
vGu8KeySec=5;
}
else if(2==Su8ColumnRecord)
{
vGu8KeySec=6;
}
}
}
else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;
if(0==Su8ColumnRecord)
{
vGu8KeySec=7;
}
else if(1==Su8ColumnRecord)
{
vGu8KeySec=8;
}
else if(2==Su8ColumnRecord)
{
vGu8KeySec=9;
}
}
}
}
break;
}
}
void KeyTask(void)
{
if(0==vGu8KeySec)
{
return;
}
switch(vGu8KeySec)
{
case 1: //S1按键的“短按”任务,更改P1.4所在的LED灯的显示状态
if(0==Gu8LedStatus_P1_4)
{
Gu8LedStatus_P1_4=1;
LedOpen_P1_4();
}
else
{
Gu8LedStatus_P1_4=0;
LedClose_P1_4();
}
vGu8KeySec=0;
break;
//以下S1按键的“长按”直接选择case 21的“21”,是为了不占用前排其它按键的编号。
case 21: //S1按键的“长按”任务,蜂鸣器发出“嘀”一声
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//蜂鸣器发出“嘀”一声
vGu8BeepTimerFlag=1;
vGu8KeySec=0;
break;
default:
vGu8KeySec=0;
break;
}
}
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)
{
if(0==Gu8LedStatus_P1_4)
{
LedClose_P1_4();
}
else
{
LedOpen_P1_4();
}
}
void BeepOpen(void)
{
P3_4=0;
}
void BeepClose(void)
{
P3_4=1;
}
void LedOpen_P1_4(void)
{
P1_4=0;
}
void LedClose_P1_4(void)
{
P1_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();
}
}
}
}
jianhong_wu
发表于 2018-1-28 12:42:30
本帖最后由 jianhong_wu 于 2018-1-28 13:09 编辑
第一百零五节: 矩阵按键按住不松手的连续均匀触发。
【105.1 按住不松手的连续均匀触发。】
上图105.1.1有源蜂鸣器电路
上图105.1.2LED电路
上图105.1.33*3矩阵按键的电路
矩阵按键与前面章节独立按键的“按住不松手的连续均匀触发”的处理思路是一样的。在电脑上删除某个文件某行文字的时候,单击一次“退格按键”,就删除一个文字,如果按住“退格按键”不松手,就会“连续均匀”的触发“删除”的功能,自动逐个把整行文字删除清空,这就是“按住不松手的连续均匀触发”应用案例之一。除此之外,在很多需要人机交互的项目中都有这样的功能,为了快速加减某个数值,按住某个按键不松手,某个数值有节奏地快速往上加或者快速往下减。这种“按住不松手连续均匀触发”的按键识别,在程序上有“3个时间”需要留意,第1个是按键单击的“滤波”时间,第2个是按键“从单击进入连击”的间隔时间(此时间是“单击”与“连击”的分界线),第3个是按键“连击”的间隔时间,
本节例程实现的功能如下:(1)8个受按键控制的跑马灯在某一时刻只有1个LED亮,每触发一次S1按键,“亮的LED”就“往左边跑一步”;相反,每触发一次S9按键,“亮的LED”就“往右边跑一步”。如果按住S1或者S9不松手就连续触发,“亮的LED”就“连续跑”,一直跑到左边或者右边的尽头。(2)按键每“单击”一次S1或者S9蜂鸣器就鸣叫一次,但是,当按键“从单击进入连击”后,蜂鸣器就不鸣叫。代码如下:
#include "REG52.H"
#define KEY_VOICE_TIME 50
#define KEY_SHORT_TIME20 //按键单击的“滤波”时间
#define KEY_ENTER_CONTINUITY_TIME 240//按键“从单击进入连击”的间隔时间
#define KEY_CONTINUITY_TIME 64 //按键“连击”的间隔时间
#define BUS_P0 P0 //8个LED灯一一对应单片机的P0口总线
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);
void DisplayTask(void); //显示的任务函数(LED显示状态)
sbit P3_4=P3^4;
sbit ROW_INPUT1=P2^2;//第1行输入口。
sbit ROW_INPUT2=P2^1;//第2行输入口。
sbit ROW_INPUT3=P2^0;//第3行输入口。
sbit COLUMN_OUTPUT1=P2^5;//第1列输出口。
sbit COLUMN_OUTPUT2=P2^4;//第2列输出口。
sbit COLUMN_OUTPUT3=P2^3;//第3列输出口。
volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;
unsigned char Gu8LedStatus=0; //LED灯的状态
unsigned char Gu8DisplayUpdate=1; //显示的刷新标志
volatile unsigned char vGu8KeySec=0;//按键的触发序号
volatile unsigned char vGu8ShieldVoiceFlag=0;//屏蔽声音的标志
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
KeyTask();
DisplayTask(); //显示的任务函数(LED显示状态)
}
}
/* 注释一:
* Gu8DisplayUpdate这类“显示刷新变量”在“显示框架”里是很常见的,而且屡用屡爽。
* 目的是,既能及时刷新显示,又能避免主函数“不断去执行显示代码”而影响程序效率。
*/
void DisplayTask(void) //显示的任务函数(LED显示状态)
{
if(1==Gu8DisplayUpdate)//需要刷新一次显示
{
Gu8DisplayUpdate=0;//及时清零,避免主函数“不断去执行显示代码”而影响程序效率
//Gu8LedStatus是左移的位数,范围(0至7),决定了跑马灯的显示状态。
BUS_P0=~(1<<Gu8LedStatus);//“左移<<”之后的“取反~”,因为LED电路是灌入式驱动方式。
}
}
/* 注释二:
*本节破题的关键:
*矩阵按键涉及的按键数量很多,但是实际项目上一般只需要少数个别按键具备这种
*“单击”与“连续均匀触发”的特殊技能,因此,在代码上,必须把这类“特殊技能按键”与
*“大众按键”区分开来,才能相互清晰互不干扰。本节的“特殊技能按键”是S1和S9。
*如果觉得本节的讲解不够详细具体,请先阅读一下前面章节“独立按键按住不松手的连续均匀触发”。
*/
void KeyScan(void)//此函数放在定时中断里每1ms扫描一次
{
static unsigned char Su8KeyLock=0;
static unsigned intSu16KeyCnt=0;
static unsigned char Su8KeyStep=1;
static unsigned char Su8ColumnRecord=0;
switch(Su8KeyStep)
{
case 1:
if(0==Su8ColumnRecord)
{
COLUMN_OUTPUT1=0;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=1;
}
else if(1==Su8ColumnRecord)
{
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=0;
COLUMN_OUTPUT3=1;
}
else
{
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=0;
}
Su16KeyCnt=0;
Su8KeyStep++;
break;
case 2: //等待列输出稳定,但不是去抖动延时
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyStep++;
}
break;
case 3:
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su8KeyStep=1; //返回步骤1继续扫描
Su8KeyLock=0;
Su16KeyCnt=0;
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
else if(0==Su8KeyLock)
{
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;
if(0==Su8ColumnRecord)
{
vGu8KeySec=1; //触发一次单击
Su16KeyCnt=0; //计时器清零,为即将来临的计时做准备
Su8KeyStep=4; //跳到S1按键的专属区,脱离大众按键
}
else if(1==Su8ColumnRecord)
{
vGu8KeySec=2;
}
else if(2==Su8ColumnRecord)
{
vGu8KeySec=3;
}
}
}
else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;
if(0==Su8ColumnRecord)
{
vGu8KeySec=4;
}
else if(1==Su8ColumnRecord)
{
vGu8KeySec=5;
}
else if(2==Su8ColumnRecord)
{
vGu8KeySec=6;
}
}
}
else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;
if(0==Su8ColumnRecord)
{
vGu8KeySec=7;
}
else if(1==Su8ColumnRecord)
{
vGu8KeySec=8;
}
else if(2==Su8ColumnRecord)
{
vGu8KeySec=9; //触发一次单击
Su16KeyCnt=0; //计时器清零,为即将来临的计时做准备
Su8KeyStep=6; //跳到S9按键的专属区,脱离大众按键
}
}
}
}
break;
/*---------S1按键的专属区----------------*/
case 4:
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //仅判断S1按键,避免交叉影响
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_ENTER_CONTINUITY_TIME)//该时间是“单击”与“连击”的分界线
{
Su16KeyCnt=0; //计时器清零,为即将来临的计时做准备
Su8KeyStep=5; //S1按键进入有节奏的连续触发
}
}
else //如果期间检查到S1按键已经松手
{
Su8KeyStep=1; //返回步骤1继续扫描
Su8KeyLock=0;
Su16KeyCnt=0;
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
break;
case 5://S1按键进入有节奏的连续触发
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //仅判断S1按键,避免交叉影响
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_CONTINUITY_TIME)//该时间是“连击”的时间
{
Su16KeyCnt=0; //清零,为了继续连击。
vGu8KeySec=1; //触发一次S1按键
vGu8ShieldVoiceFlag=1;//因为连击,把当前按键触发的声音屏蔽掉
}
}
else //如果期间检查到S1按键已经松手
{
Su8KeyStep=1; //返回步骤1继续扫描
Su8KeyLock=0;
Su16KeyCnt=0;
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
break;
/*---------S9按键的专属区----------------*/
case 6:
if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3) //仅判断S9按键,避免交叉影响
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_ENTER_CONTINUITY_TIME)//该时间是“单击”与“连击”的分界线
{
Su16KeyCnt=0; //计时器清零,为即将来临的计时做准备
Su8KeyStep=7; //S9按键进入有节奏的连续触发
}
}
else //如果期间检查到S9按键已经松手
{
Su8KeyStep=1; //返回步骤1继续扫描
Su8KeyLock=0;
Su16KeyCnt=0;
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
break;
case 7://S9按键进入有节奏的连续触发
if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3) //仅判断S9按键,避免交叉影响
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_CONTINUITY_TIME)//该时间是“连击”的时间
{
Su16KeyCnt=0; //清零,为了继续连击。
vGu8KeySec=9; //触发一次S9按键
vGu8ShieldVoiceFlag=1;//因为连击,把当前按键触发的声音屏蔽掉
}
}
else //如果期间检查到S9按键已经松手
{
Su8KeyStep=1; //返回步骤1继续扫描
Su8KeyLock=0;
Su16KeyCnt=0;
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
break;
}
}
void KeyTask(void)
{
if(0==vGu8KeySec)
{
return;
}
switch(vGu8KeySec)
{
case 1: //S1按键的任务
if(Gu8LedStatus>0)
{
Gu8LedStatus--;//控制LED“往左边跑”
Gu8DisplayUpdate=1;//刷新显示
}
if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
{
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
}
vGu8ShieldVoiceFlag=0;//及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;
case 9: //S9按键的任务
if(Gu8LedStatus<7)
{
Gu8LedStatus++;//控制LED“往右边跑”
Gu8DisplayUpdate=1;//刷新显示
}
if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
{
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
}
vGu8ShieldVoiceFlag=0;//及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;
default:
vGu8KeySec=0;
break;
}
}
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();
}
}
}
}
jianhong_wu
发表于 2018-2-4 12:14:19
本帖最后由 jianhong_wu 于 2018-2-4 12:31 编辑
第一百零六节: 矩阵按键按住不松手的“先加速后匀速”触发。
【106.1 按住不松手的先加速后匀速触发。】
上图106.1.1有源蜂鸣器电路
上图106.1.2LED电路
上图106.1.33*3矩阵按键的电路
矩阵按键与前面章节“独立按键按住不松手的先加速后匀速的触发”的处理思路是一样的。 当“连续加”或者“连续减”的数据范围很大的时候,就需要按键的加速与匀速相结合的触发方式。“加速”是指按住按键不松手,按键刚开始触发是从慢到快的渐进过程,当“加速”到某个特别快的速度的时候,就“不再加速”,而是以该“恒定高速”进行“连续匀速”触发。这种触发方式,“加速”和“匀速”是相辅相成缺一不可的,为什么?假如没有“加速”只有“匀速”,那么刚按下按键就直接以最高速的“匀速”进行,就会跑过头,缺乏微调功能;而假如没有“匀速”只有“加速”,那么按下按键不松手后,速度就会一直不断飙升,最后失控过冲。
本节例程实现的功能如下:
(1)要更改一个“设置参数”(一个全局变量),参数的范围是0到800。
(2)8个受“设置参数”控制的跑马灯在某一时刻只有1个LED亮,每触发一次S1按键,该“设置参数”就自减1,最小值为0;相反,每触发一次S9按键,该“设置参数”就自加1,最大值为800。
(3)LED灯实时显示“设置参数”的范围状态:
只有第0个LED灯亮:0<=“设置参数”<100。
只有第1个LED灯亮:100<=“设置参数”<200。
只有第2个LED灯亮:200<=“设置参数”<300。
只有第3个LED灯亮:300<=“设置参数”<400。
只有第4个LED灯亮:400<=“设置参数”<500。
只有第5个LED灯亮:500<=“设置参数”<600。
只有第6个LED灯亮:600<=“设置参数”<700。
只有第7个LED灯亮:700<=“设置参数”<=800。
(4)按键每“单击”一次蜂鸣器就鸣叫一次,但是,当按键“从单击进入连击”后,蜂鸣器就不鸣叫。#include "REG52.H"
#define KEY_VOICE_TIME 50
#define KEY_SHORT_TIME20 //按键单击的“滤波”时间
#define KEY_ENTER_CONTINUITY_TIME 240//按键“从单击进入连击”的间隔时间
#define KEY_CONTINUITY_INITIAL_TIME 64//按键“连击”起始的预设间隔时间
#define KEY_SUB_DT_TIME 6 //按键在“加速”时每次减小的时间。
#define KEY_CONTINUITY_MIN_TIME 8//按键时间减小到最后的“匀速”间隔时间。
#define BUS_P0 P0 //8个LED灯一一对应单片机的P0口总线
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);
void DisplayTask(void); //显示的任务函数(LED显示状态)
sbit P3_4=P3^4;
sbit ROW_INPUT1=P2^2;//第1行输入口。
sbit ROW_INPUT2=P2^1;//第2行输入口。
sbit ROW_INPUT3=P2^0;//第3行输入口。
sbit COLUMN_OUTPUT1=P2^5;//第1列输出口。
sbit COLUMN_OUTPUT2=P2^4;//第2列输出口。
sbit COLUMN_OUTPUT3=P2^3;//第3列输出口。
volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;
unsigned int Gu16SetData=0; //“设置参数”。范围从0到800。LED灯反映该当前值的范围状态
unsigned char Gu8DisplayUpdate=1; //显示的刷新标志
volatile unsigned char vGu8KeySec=0;//按键的触发序号
volatile unsigned char vGu8ShieldVoiceFlag=0;//屏蔽声音的标志
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
KeyTask();
DisplayTask(); //显示的任务函数(LED显示状态)
}
}
/* 注释一:
* Gu8DisplayUpdate这类“显示刷新变量”在“显示框架”里是很常见的,而且屡用屡爽。
* 目的是,既能及时刷新显示,又能避免主函数“不断去执行显示代码”而影响程序效率。
*/
void DisplayTask(void) //显示的任务函数(LED显示状态)
{
if(1==Gu8DisplayUpdate)//需要刷新一次显示
{
Gu8DisplayUpdate=0;//及时清零,避免主函数“不断去执行显示代码”而影响程序效率
if(Gu16SetData<100)
{
BUS_P0=~(1<<0);//第0个灯亮
}
else if(Gu16SetData<200)
{
BUS_P0=~(1<<1);//第1个灯亮
}
else if(Gu16SetData<300)
{
BUS_P0=~(1<<2);//第2个灯亮
}
else if(Gu16SetData<400)
{
BUS_P0=~(1<<3);//第3个灯亮
}
else if(Gu16SetData<500)
{
BUS_P0=~(1<<4);//第4个灯亮
}
else if(Gu16SetData<600)
{
BUS_P0=~(1<<5);//第5个灯亮
}
else if(Gu16SetData<700)
{
BUS_P0=~(1<<6);//第6个灯亮
}
else
{
BUS_P0=~(1<<7);//第7个灯亮
}
}
}
/* 注释二:
*本节破题的关键:
*矩阵按键涉及的按键数量很多,但是实际项目上一般只需要少数个别按键具备这种
*“单击”与“先加速后均匀触发”的特殊技能,因此,在代码上,必须把这类“特殊技能按键”与
*“大众按键”区分开来,才能相互清晰互不干扰。本节的“特殊技能按键”是S1和S9。
*如果觉得本节的讲解不够详细具体,请先阅读一下前面章节“独立按键按住不松手的先加速后匀速触发”。
*/
void KeyScan(void)//此函数放在定时中断里每1ms扫描一次
{
static unsigned char Su8KeyLock=0;
static unsigned intSu16KeyCnt=0;
static unsigned char Su8KeyStep=1;
static unsigned intSu16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值
static unsigned char Su8ColumnRecord=0;
switch(Su8KeyStep)
{
case 1:
if(0==Su8ColumnRecord)
{
COLUMN_OUTPUT1=0;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=1;
}
else if(1==Su8ColumnRecord)
{
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=0;
COLUMN_OUTPUT3=1;
}
else
{
COLUMN_OUTPUT1=1;
COLUMN_OUTPUT2=1;
COLUMN_OUTPUT3=0;
}
Su16KeyCnt=0;
Su8KeyStep++;
break;
case 2: //等待列输出稳定,但不是去抖动延时
Su16KeyCnt++;
if(Su16KeyCnt>=2)
{
Su16KeyCnt=0;
Su8KeyStep++;
}
break;
case 3:
if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su8KeyStep=1; //返回步骤1继续扫描
Su8KeyLock=0;
Su16KeyCnt=0;
Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值。重装初始值。
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
else if(0==Su8KeyLock)
{
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;
if(0==Su8ColumnRecord)
{
vGu8KeySec=1; //触发一次单击
Su16KeyCnt=0; //计时器清零,为即将来临的计时做准备
Su8KeyStep=4; //跳到S1按键的专属区,脱离大众按键
}
else if(1==Su8ColumnRecord)
{
vGu8KeySec=2;
}
else if(2==Su8ColumnRecord)
{
vGu8KeySec=3;
}
}
}
else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;
if(0==Su8ColumnRecord)
{
vGu8KeySec=4;
}
else if(1==Su8ColumnRecord)
{
vGu8KeySec=5;
}
else if(2==Su8ColumnRecord)
{
vGu8KeySec=6;
}
}
}
else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_SHORT_TIME)
{
Su8KeyLock=1;
if(0==Su8ColumnRecord)
{
vGu8KeySec=7;
}
else if(1==Su8ColumnRecord)
{
vGu8KeySec=8;
}
else if(2==Su8ColumnRecord)
{
vGu8KeySec=9; //触发一次单击
Su16KeyCnt=0; //计时器清零,为即将来临的计时做准备
Su8KeyStep=6; //跳到S9按键的专属区,脱离大众按键
}
}
}
}
break;
/*---------S1按键的专属区----------------*/
case 4:
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //仅判断S1按键,避免交叉影响
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_ENTER_CONTINUITY_TIME)//该时间是“单击”与“连击”的分界线
{
Su16KeyCnt=0; //计时器清零,为即将来临的计时做准备
Su8KeyStep=5; //S1按键进入有节奏的连续触发
}
}
else //如果期间检查到S1按键已经松手
{
Su8KeyStep=1; //返回步骤1继续扫描
Su8KeyLock=0;
Su16KeyCnt=0;
Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值。重装初始值。
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
break;
case 5://S1按键进入有节奏的连续触发
if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //仅判断S1按键,避免交叉影响
{
Su16KeyCnt++;
if(Su16KeyCnt>=Su16KeyContinuityTime)//该时间是“刚开始不断减小,最后不变”
{
Su16KeyCnt=0; //清零,为了继续连击。
vGu8KeySec=1; //触发一次S1按键
vGu8ShieldVoiceFlag=1;//因为连击,把当前按键触发的声音屏蔽掉
if(Su16KeyContinuityTime>=KEY_SUB_DT_TIME)
{
//Su16KeyContinuityTime数值不断被减小,按键的触发速度就不断变快
Su16KeyContinuityTime=Su16KeyContinuityTime-KEY_SUB_DT_TIME;//变快节奏
}
//最小间隔时间KEY_CONTINUITY_MIN_TIME就是“高速匀速”
if(Su16KeyContinuityTime<KEY_CONTINUITY_MIN_TIME)
{
//最后以KEY_CONTINUITY_MIN_TIME时间为最高速进行“匀速”
Su16KeyContinuityTime=KEY_CONTINUITY_MIN_TIME;
}
}
}
else //如果期间检查到S1按键已经松手
{
Su8KeyStep=1; //返回步骤1继续扫描
Su8KeyLock=0;
Su16KeyCnt=0;
Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值。重装初始值。
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
break;
/*---------S9按键的专属区----------------*/
case 6:
if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3) //仅判断S9按键,避免交叉影响
{
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_ENTER_CONTINUITY_TIME)//该时间是“单击”与“连击”的分界线
{
Su16KeyCnt=0; //计时器清零,为即将来临的计时做准备
Su8KeyStep=7; //S9按键进入有节奏的连续触发
}
}
else //如果期间检查到S9按键已经松手
{
Su8KeyStep=1; //返回步骤1继续扫描
Su8KeyLock=0;
Su16KeyCnt=0;
Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值。重装初始值。
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
break;
case 7://S9按键进入有节奏的连续触发
if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3) //仅判断S9按键,避免交叉影响
{
Su16KeyCnt++;
if(Su16KeyCnt>=Su16KeyContinuityTime)//该时间是“刚开始不断减小,最后不变”
{
Su16KeyCnt=0; //清零,为了继续连击。
vGu8KeySec=9; //触发一次S9按键
vGu8ShieldVoiceFlag=1;//因为连击,把当前按键触发的声音屏蔽掉
if(Su16KeyContinuityTime>=KEY_SUB_DT_TIME)
{
//Su16KeyContinuityTime数值不断被减小,按键的触发速度就不断变快
Su16KeyContinuityTime=Su16KeyContinuityTime-KEY_SUB_DT_TIME;//变快节奏
}
//最小间隔时间KEY_CONTINUITY_MIN_TIME就是“高速匀速”
if(Su16KeyContinuityTime<KEY_CONTINUITY_MIN_TIME)
{
//最后以KEY_CONTINUITY_MIN_TIME时间为最高速进行“匀速”
Su16KeyContinuityTime=KEY_CONTINUITY_MIN_TIME;
}
}
}
else //如果期间检查到S9按键已经松手
{
Su8KeyStep=1; //返回步骤1继续扫描
Su8KeyLock=0;
Su16KeyCnt=0;
Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值。重装初始值。
Su8ColumnRecord++;
if(Su8ColumnRecord>=3)
{
Su8ColumnRecord=0;
}
}
break;
}
}
void KeyTask(void)
{
if(0==vGu8KeySec)
{
return;
}
switch(vGu8KeySec)
{
case 1: //S1按键的任务
if(Gu16SetData>0)
{
Gu16SetData--; //“设置参数”
Gu8DisplayUpdate=1;//刷新显示
}
if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
{
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
}
vGu8ShieldVoiceFlag=0;//及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;
case 9: //S9按键的任务
if(Gu16SetData<800)
{
Gu16SetData++; //“设置参数”
Gu8DisplayUpdate=1;//刷新显示
}
if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
{
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//发出“嘀”一声
vGu8BeepTimerFlag=1;
}
vGu8ShieldVoiceFlag=0;//及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;
default:
vGu8KeySec=0;
break;
}
}
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();
}
}
}
}
jianhong_wu
发表于 2018-2-11 10:58:35
本帖最后由 jianhong_wu 于 2018-2-11 11:17 编辑
第一百零七节: 开关感应器的识别与软件滤波。
【107.1 开关感应器的识别与软件滤波。】
上图107.1.1独立按键模拟开关感应器
上图107.1.2LED电路
什么叫开关感应器?凡是只能输出0和1这两种状态的感应器都可以统称为开关感应器。前面花了大量的章节讲按键,按键的识别主要是识别电平变化状态的“下降沿”,程序代码中有1个特别的变量标志叫“自锁标志”,还有1个用来消除抖动的“计时器”。本节讲的开关感应器跟按键很相似,差别在于,开关感应器是识别电平变化状态的“电平”,程序代码中没有“自锁标志”,但是多增加了1个用来消除抖动的“计时器”,也就是一共有两个用来消除抖动的“计时器”,这两个“计时器”相互“清零”相互“抗衡”,从而实现了开关感应器的“消抖”处理,专业术语也叫“软件滤波”。消抖的时间跟按键差不多,我的经验值是20ms到30ms之间,我平时在项目中喜欢用20ms。
在显示框架方面,除了之前讲过Gu8DisplayUpdate这类“显示刷新变量”,本节介绍另外一种常用的显示框架,原理是“某数值跟上一次对比,如果发生了变化(两数值不一样),则自动刷新显示,并及时记录当前值”。
本节例程实现的功能如下:用K1独立按键模拟开关感应器,K1独立按键“没有被按下”时是高电平,单片机识别到这种“高电平”,就让P1.4所在的LED灯发亮;K1独立按键“被按下”时是低电平,单片机识别到这种“低电平”,就让P1.4所在的LED灯熄灭。
#include "REG52.H"
#define SENSOR_TIME20 //开关感应器的“滤波”时间
void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;
void VoiceScan(void);
void SensorScan(void);
void DisplayTask(void); //显示的任务函数(LED显示状态)
sbit P1_4=P1^4;
sbit Sensor_K1_sr=P2^2; //开关感应器K1所在的引脚
volatile unsigned char vGu8Sensor_K1=0;//K1开关感应器的当前电平状态。
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
DisplayTask(); //显示的任务函数(LED显示状态)
}
}
/* 注释一:
* 后缀为_Last这类“对比上一次数值发生变化而自动刷新显示”在“显示框架”里是很常见的,
* 目的是,既能及时刷新显示,又能避免主函数“不断去执行显示代码”而影响程序效率。
*/
void DisplayTask(void) //显示的任务函数(LED显示状态)
{
// Su8Sensor_K1_Last初始化取值255,只要不为0或者1就行,目的是让上电就发生第一次刷新。
static unsigned char Su8Sensor_K1_Last=255;//记录K1开关感应器上一次的电平状态。
if(Su8Sensor_K1_Last!=vGu8Sensor_K1)//如果当前值与上一次值不一样,就自动刷新
{
Su8Sensor_K1_Last=vGu8Sensor_K1;//及时记录最新值,避免主函数“不断去执行显示代码”
if(0==vGu8Sensor_K1) //如果当前电平状态为“低电平”,LED熄灭
{
P1_4=1;//LED熄灭
}
else//如果当前电平状态为“高电平”,LED发亮
{
P1_4=0;//LED发亮
}
}
}
/* 注释二:
*本节破题的关键:
*两个“计时器”相互“清零”相互“抗衡”,从而实现了开关感应器的“消抖”处理,
*专业术语也叫“软件滤波”。这种滤波方式,不管是从“高转成低”,还是“低转成高”,
*如果在某个瞬间出现干扰抖动,某个计数器都会及时被“清零”,从而起到非常高效的消抖滤波作用。
*/
void SensorScan(void)//此函数放在定时中断里每1ms扫描一次,用来识别和滤波开关感应器
{
static unsigned int Su16Sensor_K1_H_Cnt=0;//判断高电平的计时器
static unsigned int Su16Sensor_K1_L_Cnt=0;//判断低电平的计时器
if(0==Sensor_K1_sr)
{
Su16Sensor_K1_H_Cnt=0;//在判断低电平的时候,高电平的计时器被清零,巧妙极了!
Su16Sensor_K1_L_Cnt++;
if(Su16Sensor_K1_L_Cnt>=SENSOR_TIME)
{
Su16Sensor_K1_L_Cnt=0;
vGu8Sensor_K1=0; //此全局变量反馈当前电平的状态
}
}
else
{
Su16Sensor_K1_L_Cnt=0; //在判断高电平的时候,低电平的计时器被清零,巧妙极了!
Su16Sensor_K1_H_Cnt++;
if(Su16Sensor_K1_H_Cnt>=SENSOR_TIME)
{
Su16Sensor_K1_H_Cnt=0;
vGu8Sensor_K1=1;//此全局变量反馈当前电平的状态
}
}
}
void T0_time() interrupt 1
{
SensorScan();//开关感应器的识别与软件滤波处理
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)
{
}
jianhong_wu
发表于 2018-2-23 14:15:22
本帖最后由 jianhong_wu 于 2018-2-23 14:44 编辑
第一百零八节: 按键控制跑马灯的启动和暂停和停止。
【108.1 按键控制跑马灯的启动和暂停和停止。】
上图108.1.1独立按键
上图108.1.2LED电路
上图108.1.3有源蜂鸣器的电路
在我眼里,按键不仅仅是按键,跑马灯不仅仅是跑马灯。按键是输入设备,跑马灯是应用程序。本节表面上讲按键控制跑马灯的简单项目,实际上作者用心良苦立意深远,试图通过按键与跑马灯,来分享一种输入设备如何关联应用程序的程序框架。
本节例程实现的功能如下:
(1)【启动暂停】按键K1。按下【启动暂停】按键K1启动之后,跑马灯处于“启动”状态,4个LED灯从左到右依次循环的变亮,给人“跑”起来的感觉。此时如果再按一次【启动暂停】按键K1,则跑马灯处于“暂停”状态,如果再按一次【启动暂停】按键K1,跑马灯又变回“启动”状态。因此,【启动暂停】按键K1是专门用来切换“启动”和“暂停”这两种状态。
(2)【停止】按键K2。当跑马灯处于“启动”或者“暂停”或者“停止”的状态时,只要按下【停止】按键K2,当前的运动状态就终止,强制变回初始的“停止”状态,类似“复位”按键的作用。当跑马灯处于“停止”状态时,此时再按下【启动暂停】按键K1之后,跑马灯又处于“启动”状态。
#include "REG52.H"
#define KEY_VOICE_TIME 50
#define KEY_FILTER_TIME25
#define RUN_TIME200 //跑马灯的跑动速度的时间参数
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);
void RunTask(void); //跑马灯的任务函数
//4个跑马灯的输出口
sbit P1_4=P1^4;
sbit P1_5=P1^5;
sbit P1_6=P1^6;
sbit P3_3=P3^3;
//蜂鸣器的输出口
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;
unsigned char Gu8RunStart=0; //控制跑马灯启动的总开关
unsigned char Gu8RunStatus=0;//标识跑马灯当前的状态。0代表停止,1代表启动,2代表暂停。
volatile unsigned char vGu8RunTimerFlag=0; //用于控制跑马灯跑动速度的定时器
volatile unsigned int vGu16RunTimerCnt=0;
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
KeyTask(); //按键的任务函数
RunTask(); //跑马灯的任务函数
}
}
void T0_time() interrupt 1
{
VoiceScan();
KeyScan();
if(1==vGu8RunTimerFlag&&vGu16RunTimerCnt>0)//用于控制跑马灯跑动速度的定时器
{
vGu16RunTimerCnt--;
}
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)
{
//跑马灯处于初始化的状态
P1_4=0; //第1个灯亮
P1_5=1; //第2个灯灭
P1_6=1; //第3个灯灭
P3_3=1; //第4个灯灭
}
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();
}
}
}
}
void KeyScan(void)//此函数放在定时中断里每1ms扫描一次
{
static unsigned char Su8KeyLock1;
static unsigned intSu16KeyCnt1;
static unsigned char Su8KeyLock2;
static unsigned intSu16KeyCnt2;
//【启动暂停】按键K1的扫描识别
if(0!=KEY_INPUT1)
{
Su8KeyLock1=0;
Su16KeyCnt1=0;
}
else if(0==Su8KeyLock1)
{
Su16KeyCnt1++;
if(Su16KeyCnt1>=KEY_FILTER_TIME)
{
Su8KeyLock1=1;
vGu8KeySec=1; //触发1号键
}
}
//【停止】按键K2的扫描识别
if(0!=KEY_INPUT2)
{
Su8KeyLock2=0;
Su16KeyCnt2=0;
}
else if(0==Su8KeyLock2)
{
Su16KeyCnt2++;
if(Su16KeyCnt2>=KEY_FILTER_TIME)
{
Su8KeyLock2=1;
vGu8KeySec=2; //触发2号键
}
}
}
/* 注释一:
*本节破题的关键:
*在KeyTask和RunTask两个任务函数之间,主要是靠Gu8RunStart和Gu8RunStatus这两个
*全局变量来传递信息。
*/
void KeyTask(void) //按键的任务函数,放在主函数内
{
if(0==vGu8KeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}
switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
case 1: //1号按键。【启动暂停】按键K1
if(0==Gu8RunStatus) //当跑马灯处于“停止”状态时
{
Gu8RunStart=1; //总开关“打开”。
Gu8RunStatus=1;//状态切换到“启动”状态
}
else if(1==Gu8RunStatus) //当跑马灯处于“启动”状态时
{
Gu8RunStatus=2;//状态切换到“暂停”状态
}
else//当跑马灯处于“暂停”状态时
{
Gu8RunStatus=1;//状态切换到“启动”状态
}
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 2: //2号按键。【停止】按键K2
Gu8RunStart=0; //总开关“关闭”。
Gu8RunStatus=0;//状态切换到“停止”状态
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
}
}
void RunTask(void) //跑马灯的任务函数,放在主函数内
{
static unsigned char Su8RunStep=0; //运行的步骤
//当总开关处于“停止”并且“步骤不为0”时,强制把步骤归零,跑马灯初始化。
if(0!=Su8RunStep&&0==Gu8RunStart)
{
Su8RunStep=0; //步骤归零
//跑马灯处于初始化的状态
P1_4=0; //第1个灯亮
P1_5=1; //第2个灯灭
P1_6=1; //第3个灯灭
P3_3=1; //第4个灯灭
}
switch(Su8RunStep) //屡见屡爱的switch又来了
{
case 0:
if(1==Gu8RunStart) //总开关“打开”
{
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=0;//定时器清零
Su8RunStep=1;//切换到下一步,启动
}
break;
case 1:
if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=0; //第1个灯亮
P1_5=1; //第2个灯灭
P1_6=1; //第3个灯灭
P3_3=1; //第4个灯灭
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=RUN_TIME; //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1; //启动定时器
Su8RunStep=2;//切换到下一步
}
break;
case 2:
if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1; //第1个灯灭
P1_5=0; //第2个灯亮
P1_6=1; //第3个灯灭
P3_3=1; //第4个灯灭
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=RUN_TIME; //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1; //启动定时器
Su8RunStep=3;//切换到下一步
}
break;
case 3:
if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1; //第1个灯灭
P1_5=1; //第2个灯灭
P1_6=0; //第3个灯亮
P3_3=1; //第4个灯灭
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=RUN_TIME; //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1; //启动定时器
Su8RunStep=4;//切换到下一步
}
break;
case 4:
if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1; //第1个灯灭
P1_5=1; //第2个灯灭
P1_6=1; //第3个灯灭
P3_3=0; //第4个灯亮
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=RUN_TIME; //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1; //启动定时器
Su8RunStep=1;//返回到第1步,重新开始下一轮的循环!!!
}
break;
}
}
jianhong_wu
发表于 2018-3-1 09:05:48
本帖最后由 jianhong_wu 于 2018-3-1 09:28 编辑
第一百零九节: 按键控制跑马灯的方向。
【109.1 按键控制跑马灯的方向。】
上图109.1.1独立按键
上图109.1.2LED电路
上图109.1.3有源蜂鸣器的电路
之前108节讲到跑马灯的启动、暂停、停止,本节在此基础上,增加一个“方向”的控制,除了加深理解输入设备如何关联应用程序的程序框架之外,还有一个知识点值得一提,就是如何通过灵活切换switch的“步骤变量”来达到随心所欲的过程控制,本节的“方向”的控制就用到这个方法。
本节例程的功能如下:
(1)【启动暂停】按键K1。按下【启动暂停】按键K1启动之后,跑马灯处于“启动”状态,4个LED灯挨个依次循环的变亮,给人“跑”起来的感觉。此时如果再按一次【启动暂停】按键K1,则跑马灯处于“暂停”状态,如果再按一次【启动暂停】按键K1,跑马灯又变回“启动”状态。因此,【启动暂停】按键K1是专门用来切换“启动”和“暂停”这两种状态。
(2)【停止】按键K2。当跑马灯处于“启动”或者“暂停”或者“停止”的状态时,只要按下【停止】按键K2,当前的运动状态就终止,强制变回初始的“停止”状态,类似“复位”按键的作用。当跑马灯处于“停止”状态时,此时再按下【启动暂停】按键K1之后,跑马灯又处于“启动”状态。
(3)【方向】按键K3。跑马灯上电后默认处于“往右跑”的方向。每按一次【方向】按键K3,跑马灯就在“往右跑”与“往左跑”两个方向之间切换。#include "REG52.H"
#define KEY_VOICE_TIME 50
#define KEY_FILTER_TIME25
#define RUN_TIME200 //跑马灯的跑动速度的时间参数
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);
void RunTask(void); //跑马灯的任务函数
//4个跑马灯的输出口
sbit P1_4=P1^4;
sbit P1_5=P1^5;
sbit P1_6=P1^6;
sbit P3_3=P3^3;
//蜂鸣器的输出口
sbit P3_4=P3^4;
sbit KEY_INPUT1=P2^2;//【启动暂停】按键K1的输入口。
sbit KEY_INPUT2=P2^1;//【停止】按键K2的输入口。
sbit KEY_INPUT3=P2^0;//【方向】按键K3的输入口。
volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;
volatile unsigned char vGu8KeySec=0;
unsigned char Gu8RunStart=0; //控制跑马灯启动的总开关
unsigned char Gu8RunStatus=0; //标识跑马灯当前的状态。0代表停止,1代表启动,2代表暂停。
unsigned char Gu8RunDirection=0;//标识跑马灯当前的方向。0代表往右跑,1代表往左跑。
volatile unsigned char vGu8RunTimerFlag=0; //用于控制跑马灯跑动速度的定时器
volatile unsigned int vGu16RunTimerCnt=0;
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
KeyTask(); //按键的任务函数
RunTask(); //跑马灯的任务函数
}
}
void T0_time() interrupt 1
{
VoiceScan();
KeyScan();
if(1==vGu8RunTimerFlag&&vGu16RunTimerCnt>0)//用于控制跑马灯跑动速度的定时器
{
vGu16RunTimerCnt--;
}
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)
{
//跑马灯处于初始化的状态
P1_4=0; //第1个灯亮
P1_5=1; //第2个灯灭
P1_6=1; //第3个灯灭
P3_3=1; //第4个灯灭
}
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();
}
}
}
}
void KeyScan(void)//此函数放在定时中断里每1ms扫描一次
{
static unsigned char Su8KeyLock1;
static unsigned intSu16KeyCnt1;
static unsigned char Su8KeyLock2;
static unsigned intSu16KeyCnt2;
static unsigned char Su8KeyLock3;
static unsigned intSu16KeyCnt3;
//【启动暂停】按键K1的扫描识别
if(0!=KEY_INPUT1)
{
Su8KeyLock1=0;
Su16KeyCnt1=0;
}
else if(0==Su8KeyLock1)
{
Su16KeyCnt1++;
if(Su16KeyCnt1>=KEY_FILTER_TIME)
{
Su8KeyLock1=1;
vGu8KeySec=1; //触发1号键
}
}
//【停止】按键K2的扫描识别
if(0!=KEY_INPUT2)
{
Su8KeyLock2=0;
Su16KeyCnt2=0;
}
else if(0==Su8KeyLock2)
{
Su16KeyCnt2++;
if(Su16KeyCnt2>=KEY_FILTER_TIME)
{
Su8KeyLock2=1;
vGu8KeySec=2; //触发2号键
}
}
//【方向】按键K3的扫描识别
if(0!=KEY_INPUT3)
{
Su8KeyLock3=0;
Su16KeyCnt3=0;
}
else if(0==Su8KeyLock3)
{
Su16KeyCnt3++;
if(Su16KeyCnt3>=KEY_FILTER_TIME)
{
Su8KeyLock3=1;
vGu8KeySec=3; //触发3号键
}
}
}
/* 注释一:
*本节破题的关键:
*在KeyTask和RunTask两个任务函数之间,主要是靠Gu8RunStart、Gu8RunStatus、Gu8RunDirection
*这三个全局变量来传递信息。
*/
void KeyTask(void) //按键的任务函数,放在主函数内
{
if(0==vGu8KeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}
switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
case 1: //1号按键。【启动暂停】按键K1
if(0==Gu8RunStatus) //当跑马灯处于“停止”状态时
{
Gu8RunStart=1; //总开关“打开”。
Gu8RunStatus=1;//状态切换到“启动”状态
}
else if(1==Gu8RunStatus) //当跑马灯处于“启动”状态时
{
Gu8RunStatus=2;//状态切换到“暂停”状态
}
else//当跑马灯处于“暂停”状态时
{
Gu8RunStatus=1;//状态切换到“启动”状态
}
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 2: //2号按键。【停止】按键K2
Gu8RunStart=0; //总开关“关闭”。
Gu8RunStatus=0;//状态切换到“停止”状态
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 3: //3号按键。【方向】按键K3
//每按一次K3按键,Gu8RunDirection就在0和1之间切换,从而控制方向
if(0==Gu8RunDirection)
{
Gu8RunDirection=1;
}
else
{
Gu8RunDirection=0;
}
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
}
}
/* 注释二:
* “方向”的控制,是通过Gu8RunDirection的判断,来灵活切换switch的“步骤变量”来达到
*随心所欲的过程控制。
*/
void RunTask(void) //跑马灯的任务函数,放在主函数内
{
static unsigned char Su8RunStep=0; //运行的步骤
//当总开关处于“停止”并且“步骤不为0”时,强制把步骤归零,跑马灯初始化。
if(0!=Su8RunStep&&0==Gu8RunStart)
{
Su8RunStep=0; //步骤归零
//跑马灯处于初始化的状态
P1_4=0; //第1个灯亮
P1_5=1; //第2个灯灭
P1_6=1; //第3个灯灭
P3_3=1; //第4个灯灭
}
switch(Su8RunStep) //屡见屡爱的switch又来了
{
case 0:
if(1==Gu8RunStart) //总开关“打开”
{
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=0;//定时器清零
Su8RunStep=1;//切换到下一步,启动
}
break;
case 1:
if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=0; //第1个灯亮
P1_5=1; //第2个灯灭
P1_6=1; //第3个灯灭
P3_3=1; //第4个灯灭
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=RUN_TIME; //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1; //启动定时器
//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
Su8RunStep=2;
}
else//往左跑
{
Su8RunStep=4;
}
}
break;
case 2:
if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1; //第1个灯灭
P1_5=0; //第2个灯亮
P1_6=1; //第3个灯灭
P3_3=1; //第4个灯灭
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=RUN_TIME; //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1; //启动定时器
//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
Su8RunStep=3;
}
else//往左跑
{
Su8RunStep=1;
}
}
break;
case 3:
if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1; //第1个灯灭
P1_5=1; //第2个灯灭
P1_6=0; //第3个灯亮
P3_3=1; //第4个灯灭
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=RUN_TIME; //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1; //启动定时器
//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
Su8RunStep=4;
}
else//往左跑
{
Su8RunStep=2;
}
}
break;
case 4:
if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1; //第1个灯灭
P1_5=1; //第2个灯灭
P1_6=1; //第3个灯灭
P3_3=0; //第4个灯亮
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=RUN_TIME; //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1; //启动定时器
//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
Su8RunStep=1;
}
else//往左跑
{
Su8RunStep=3;
}
}
break;
}
}
jianhong_wu
发表于 2018-3-4 10:13:53
本帖最后由 jianhong_wu 于 2018-3-4 10:28 编辑
第一百一十节: 按键控制跑马灯的速度。
【110.1 按键控制跑马灯的速度。】
上图110.1.1独立按键
上图110.1.2LED电路
上图110.1.3有源蜂鸣器的电路
之前109节讲到跑马灯的启动、暂停、停止、方向,本节在此基础上,把原来的“停止”更改为“速度”,
加深理解输入设备如何关联应用程序的程序框架。
本节例程的功能如下:
(1)【启动暂停】按键K1。上电后,按下【启动暂停】按键K1启动之后,跑马灯处于“启动”状态,4个LED灯挨个依次循环的变亮,给人“跑”起来的感觉。此时如果再按一次【启动暂停】按键K1,则跑马灯处于“暂停”状态,如果再按一次【启动暂停】按键K1,跑马灯又变回“启动”状态。因此,【启动暂停】按键K1是专门用来切换“启动”和“暂停”这两种状态。
(2)【速度】按键K2。每按一次【速度】按键K2,跑马灯就在“慢”、“中”、“快”三档速度之间切换。
(3)【方向】按键K3。跑马灯上电后默认处于“往右跑”的方向。每按一次【方向】按键K3,跑马灯就在“往右跑”与“往左跑”两个方向之间切换。
#include "REG52.H"
#define KEY_VOICE_TIME 50
#define KEY_FILTER_TIME25
#define RUN_TIME_SLOW 500 //“慢”档速度的时间参数
#define RUN_TIME_MIDDLE300 //“中”档速度的时间参数
#define RUN_TIME_FAST 100 //“快”档速度的时间参数
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);
void RunTask(void); //跑马灯的任务函数
//4个跑马灯的输出口
sbit P1_4=P1^4;
sbit P1_5=P1^5;
sbit P1_6=P1^6;
sbit P3_3=P3^3;
//蜂鸣器的输出口
sbit P3_4=P3^4;
sbit KEY_INPUT1=P2^2;//【启动暂停】按键K1的输入口。
sbit KEY_INPUT2=P2^1;//【速度】按键K2的输入口。
sbit KEY_INPUT3=P2^0;//【方向】按键K3的输入口。
volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;
volatile unsigned char vGu8KeySec=0;
unsigned char Gu8RunStart=0; //控制跑马灯启动的总开关
unsigned char Gu8RunStatus=0; //标识跑马灯当前的状态。0代表停止,1代表启动,2代表暂停。
unsigned char Gu8RunDirection=0;//标识跑马灯当前的方向。0代表往右跑,1代表往左跑。
unsigned char Gu8RunSpeed=0; //当前的速度档位。0代表“慢”,1代表“中”,2代表“快”。
unsigned intGu16RunSpeedTimeDate=0; //承接各速度档位的时间参数的变量
volatile unsigned char vGu8RunTimerFlag=0; //用于控制跑马灯跑动速度的定时器
volatile unsigned int vGu16RunTimerCnt=0;
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
KeyTask(); //按键的任务函数
RunTask(); //跑马灯的任务函数
}
}
void T0_time() interrupt 1
{
VoiceScan();
KeyScan();
if(1==vGu8RunTimerFlag&&vGu16RunTimerCnt>0)//用于控制跑马灯跑动速度的定时器
{
vGu16RunTimerCnt--;
}
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)
{
//跑马灯处于初始化的状态
P1_4=0; //第1个灯亮
P1_5=1; //第2个灯灭
P1_6=1; //第3个灯灭
P3_3=1; //第4个灯灭
//根据当前的速度档位Gu8RunSpeed,来初始化速度时间参数Gu16RunSpeedTimeDate
if(0==Gu8RunSpeed)
{
Gu16RunSpeedTimeDate=RUN_TIME_SLOW; //赋值“慢”档的时间参数
}
else if(1==Gu8RunSpeed)
{
Gu16RunSpeedTimeDate=RUN_TIME_MIDDLE; //赋值“中”档的时间参数
}
else
{
Gu16RunSpeedTimeDate=RUN_TIME_FAST; //赋值“快”档的时间参数
}
}
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();
}
}
}
}
void KeyScan(void)//此函数放在定时中断里每1ms扫描一次
{
static unsigned char Su8KeyLock1;
static unsigned intSu16KeyCnt1;
static unsigned char Su8KeyLock2;
static unsigned intSu16KeyCnt2;
static unsigned char Su8KeyLock3;
static unsigned intSu16KeyCnt3;
//【启动暂停】按键K1的扫描识别
if(0!=KEY_INPUT1)
{
Su8KeyLock1=0;
Su16KeyCnt1=0;
}
else if(0==Su8KeyLock1)
{
Su16KeyCnt1++;
if(Su16KeyCnt1>=KEY_FILTER_TIME)
{
Su8KeyLock1=1;
vGu8KeySec=1; //触发1号键
}
}
//【速度】按键K2的扫描识别
if(0!=KEY_INPUT2)
{
Su8KeyLock2=0;
Su16KeyCnt2=0;
}
else if(0==Su8KeyLock2)
{
Su16KeyCnt2++;
if(Su16KeyCnt2>=KEY_FILTER_TIME)
{
Su8KeyLock2=1;
vGu8KeySec=2; //触发2号键
}
}
//【方向】按键K3的扫描识别
if(0!=KEY_INPUT3)
{
Su8KeyLock3=0;
Su16KeyCnt3=0;
}
else if(0==Su8KeyLock3)
{
Su16KeyCnt3++;
if(Su16KeyCnt3>=KEY_FILTER_TIME)
{
Su8KeyLock3=1;
vGu8KeySec=3; //触发3号键
}
}
}
/* 注释一:
*本节破题的关键:
*在KeyTask和RunTask两个任务函数之间,主要是靠Gu8RunStart、Gu8RunStatus、Gu8RunDirection、
*Gu16RunSpeedTimeDate这四个全局变量来传递信息。
*/
void KeyTask(void) //按键的任务函数,放在主函数内
{
if(0==vGu8KeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}
switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
case 1: //1号按键。【启动暂停】按键K1
if(0==Gu8RunStatus) //当跑马灯处于“停止”状态时
{
Gu8RunStart=1; //总开关“打开”。
Gu8RunStatus=1;//状态切换到“启动”状态
}
else if(1==Gu8RunStatus) //当跑马灯处于“启动”状态时
{
Gu8RunStatus=2;//状态切换到“暂停”状态
}
else//当跑马灯处于“暂停”状态时
{
Gu8RunStatus=1;//状态切换到“启动”状态
}
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 2: //2号按键。【速度】按键K2
//每按一次K2按键,Gu8RunSpeed就在0、1、2三者之间切换,并且根据Gu8RunSpeed的数值,
//对Gu16RunSpeedTimeDate赋值不同的速度时间参数,从而控制速度档位
if(0==Gu8RunSpeed)
{
Gu8RunSpeed=1;//“中”档
Gu16RunSpeedTimeDate=RUN_TIME_MIDDLE; //赋值“中”档的时间参数
}
else if(1==Gu8RunSpeed)
{
Gu8RunSpeed=2; //“快”档
Gu16RunSpeedTimeDate=RUN_TIME_FAST; //赋值“快”档的时间参数
}
else
{
Gu8RunSpeed=0; //“慢”档
Gu16RunSpeedTimeDate=RUN_TIME_SLOW; //赋值“慢”档的时间参数
}
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
case 3: //3号按键。【方向】按键K3
//每按一次K3按键,Gu8RunDirection就在0和1之间切换,并且对从而控制方向
if(0==Gu8RunDirection)
{
Gu8RunDirection=1;
}
else
{
Gu8RunDirection=0;
}
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
}
}
/* 注释二:
* “速度”是受Gu16RunSpeedTimeDate具体数值大小的影响
*/
void RunTask(void) //跑马灯的任务函数,放在主函数内
{
static unsigned char Su8RunStep=0; //运行的步骤
//当总开关处于“停止”并且“步骤不为0”时,强制把步骤归零,跑马灯初始化。
if(0!=Su8RunStep&&0==Gu8RunStart)
{
Su8RunStep=0; //步骤归零
//跑马灯处于初始化的状态
P1_4=0; //第1个灯亮
P1_5=1; //第2个灯灭
P1_6=1; //第3个灯灭
P3_3=1; //第4个灯灭
}
switch(Su8RunStep) //屡见屡爱的switch又来了
{
case 0:
if(1==Gu8RunStart) //总开关“打开”
{
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=0;//定时器清零
Su8RunStep=1;//切换到下一步,启动
}
break;
case 1:
if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=0; //第1个灯亮
P1_5=1; //第2个灯灭
P1_6=1; //第3个灯灭
P3_3=1; //第4个灯灭
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度
vGu8RunTimerFlag=1; //启动定时器
//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
Su8RunStep=2;
}
else//往左跑
{
Su8RunStep=4;
}
}
break;
case 2:
if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1; //第1个灯灭
P1_5=0; //第2个灯亮
P1_6=1; //第3个灯灭
P3_3=1; //第4个灯灭
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度
vGu8RunTimerFlag=1; //启动定时器
//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
Su8RunStep=3;
}
else//往左跑
{
Su8RunStep=1;
}
}
break;
case 3:
if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1; //第1个灯灭
P1_5=1; //第2个灯灭
P1_6=0; //第3个灯亮
P3_3=1; //第4个灯灭
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度
vGu8RunTimerFlag=1; //启动定时器
//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
Su8RunStep=4;
}
else//往左跑
{
Su8RunStep=2;
}
}
break;
case 4:
if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1; //第1个灯灭
P1_5=1; //第2个灯灭
P1_6=1; //第3个灯灭
P3_3=0; //第4个灯亮
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度
vGu8RunTimerFlag=1; //启动定时器
//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
Su8RunStep=1;
}
else//往左跑
{
Su8RunStep=3;
}
}
break;
}
}
jianhong_wu
发表于 2018-3-11 13:48:05
本帖最后由 jianhong_wu 于 2018-3-11 14:01 编辑
第一百一十一节: 工业自动化设备的开关信号的运动控制。
【111.1 开关信号的运动控制。】
上图111.1.1独立按键
上图111.1.2LED电路
上图111.1.3有源蜂鸣器的电路
本节涉及的知识点有,switch的过程控制,时间延时,开关感应器的软件滤波,工件计数器,以及整体的软件框架。
现在有一台设备,水平方向有一个滑块,能左右移动,滑块上安装了一个能垂直伸缩的“机械手”。按下启动按键后,滑块先从左边往右边移动,移到最右边碰到“右感应器”后,滑块上的“机械手”开始往下移动2秒,移动2秒后开始原路返回,“机械手”向上移动,碰到“上感应器”后,滑块开始往左边移动,移动3秒后默认已经回到原位最左边,此时“计数器”累加1,完成一次过程,如果再按下启动按键,继续重复这个过程。
这个设备用了2个气缸。1个“水平气缸”驱动滑块水平方向的左右移动,当控制“水平气缸”的输出信号为0时往左边跑,当控制“水平气缸”的输出信号为1时往右边跑。另1个“垂直气缸”驱动“机械手”的上下移动,当控制“垂直气缸”的输出信号为0时往上边跑,当控制“垂直气缸”的输出信号为1时往下边跑。
这个设备用了2个开关感应器。分别是“右感应器”和“上感应器”。当感应器没有被碰到的时候信号为1,当感应器被碰到的时候信号为0。
这个设备用了1个独立按键。控制运动的启动。
2个气缸是输出信号,用P1.4和P1.5所控制的两个LED模拟。2个开关感应器是输入信号,用K2和K3这两个独立按键模拟。1个独立按键用K1按键。如上图。
#include "REG52.H"
#define KEY_VOICE_TIME 50
#define KEY_FILTER_TIME25
#define SENSOR_TIME 20 //开关感应器的“滤波”时间
void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;
void BeepOpen(void);
void BeepClose(void);
void GoLeft(void) ; //“水平气缸”往左跑
void GoRight(void); //“水平气缸”往右跑
void GoUp(void); //“垂直气缸”往上跑
void GoDown(void);//“垂直气缸”往下跑
void VoiceScan(void);
void SensorScan(void);//开关感应器的消抖,在定时中断里调用处理
void KeyScan(void);
void KeyTask(void);
void RunTask(void); //运动控制的任务函数
sbit P1_4=P1^4;//水平气缸的输出
sbit P1_5=P1^5;//垂直气缸的输出
sbit P3_4=P3^4;//蜂鸣器的输出口
sbit KEY_INPUT1=P2^2;//【启动】按键K1的输入口。
sbit SensorRight_sr=P2^1; //右感应器的输入口
sbit SensorUp_sr=P2^0; //上感应器的输入口
volatile unsigned char vGu8SensorRight=0;//右感应器经过滤波后的当前电平状态。
volatile unsigned char vGu8SensorUp=0;//上感应器经过滤波后的当前电平状态。
volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;
volatile unsigned char vGu8KeySec=0;
unsigned char Gu8RunStart=0; //启动的总开关
unsigned char Gu8RunStatus=0; //运动的状态,0为停止,1为运行
unsigned intGu16RunCnt=0; //计数器
unsigned intGu16ReturnLeftTime=3000; //水平往左跑的延时变量,默认为3秒
unsigned intGu16GoDownTime=2000; //垂直往下跑的延时变量,默认为2秒
volatile unsigned char vGu8RunTimerFlag=0; //用于控制运动过程中的延时的定时器
volatile unsigned int vGu16RunTimerCnt=0;
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
KeyTask(); //按键的任务函数
RunTask(); //运动控制的任务函数
}
}
/* 注释一:
*两个“计时器”相互“清零”相互“抗衡”,从而实现了开关感应器的“消抖”处理,
*专业术语也叫“软件滤波”。这种滤波方式,不管是从“高转成低”,还是“低转成高”,
*如果在某个瞬间出现干扰抖动,某个计数器都会及时被“清零”,从而起到非常高效的消抖滤波作用。
*/
void SensorScan(void)//此函数放在定时中断里每1ms扫描一次,用来识别和滤波开关感应器
{
static unsigned int Su16SensorRight_H_Cnt=0;//判断高电平的计时器
static unsigned int Su16SensorRight_L_Cnt=0;//判断低电平的计时器
static unsigned int Su16SensorUp_H_Cnt=0;//判断高电平的计时器
static unsigned int Su16SensorUp_L_Cnt=0;//判断低电平的计时器
//右感应器的滤波
if(0==SensorRight_sr)
{
Su16SensorRight_H_Cnt=0;//在判断低电平的时候,高电平的计时器被清零,巧妙极了!
Su16SensorRight_L_Cnt++;
if(Su16SensorRight_L_Cnt>=SENSOR_TIME)
{
Su16SensorRight_L_Cnt=0;
vGu8SensorRight=0; //此全局变量反馈经过滤波后“右感应器”当前电平的状态
}
}
else
{
Su16SensorRight_L_Cnt=0; //在判断高电平的时候,低电平的计时器被清零,巧妙极了!
Su16SensorRight_H_Cnt++;
if(Su16SensorRight_H_Cnt>=SENSOR_TIME)
{
Su16SensorRight_H_Cnt=0;
vGu8SensorRight=1;//此全局变量反馈经过滤波后“右感应器”当前电平的状态
}
}
//上感应器的滤波
if(0==SensorUp_sr)
{
Su16SensorUp_H_Cnt=0;
Su16SensorUp_L_Cnt++;
if(Su16SensorUp_L_Cnt>=SENSOR_TIME)
{
Su16SensorUp_L_Cnt=0;
vGu8SensorUp=0; //此全局变量反馈经过滤波后“上感应器”当前电平的状态
}
}
else
{
Su16SensorUp_L_Cnt=0;
Su16SensorUp_H_Cnt++;
if(Su16SensorUp_H_Cnt>=SENSOR_TIME)
{
Su16SensorUp_H_Cnt=0;
vGu8SensorUp=1;//此全局变量反馈经过滤波后“上感应器”当前电平的状态
}
}
}
void T0_time() interrupt 1
{
VoiceScan();
KeyScan();
SensorScan();//用来识别和滤波开关感应器
if(1==vGu8RunTimerFlag&&vGu16RunTimerCnt>0)//用于控制运动延时的定时器
{
vGu16RunTimerCnt--;
}
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)
{
//上电初始化气缸的开机位置
GoLeft() ; //“水平气缸”往左跑,上电初始化时滑块处于左边
GoUp(); //“垂直气缸”往上跑,上电初始化时“机械臂”处于上方
}
void BeepOpen(void)
{
P3_4=0;
}
void BeepClose(void)
{
P3_4=1;
}
void GoLeft(void) //“水平气缸”往左跑
{
P1_4=0;
}
void GoRight(void) //“水平气缸”往右跑
{
P1_4=1;
}
void GoUp(void)//“垂直气缸”往上跑
{
P1_5=0;
}
void GoDown(void) //“垂直气缸”往下跑
{
P1_5=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();
}
}
}
}
void KeyScan(void)//此函数放在定时中断里每1ms扫描一次
{
static unsigned char Su8KeyLock1;
static unsigned intSu16KeyCnt1;
//【启动】按键K1的扫描识别
if(0!=KEY_INPUT1)
{
Su8KeyLock1=0;
Su16KeyCnt1=0;
}
else if(0==Su8KeyLock1)
{
Su16KeyCnt1++;
if(Su16KeyCnt1>=KEY_FILTER_TIME)
{
Su8KeyLock1=1;
vGu8KeySec=1; //触发1号键
}
}
}
/* 注释二:
*在KeyTask中只改变Gu8RunStart的值,用于总启动开关。而运动状态Gu8RunStatus是在运动函数
*RunTask中改变,用于对外反馈实时的运动状态。
*/
void KeyTask(void) //按键的任务函数,放在主函数内
{
if(0==vGu8KeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}
switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
case 1: //1号按键。【启动】按键K1
if(0==Gu8RunStatus) //根据当前运动的状态来决定“总开关”是否能受按键的控制
{
Gu8RunStart=1; //总开关“打开”。
}
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
default:
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;
}
}
/* 注释三:
*本节故意引入三个变量:计数器Gu16RunCnt,左延时Gu16ReturnLeftTime,下延时Gu16GoDownTime。
*在人机界面的场合,这三个变量可以用来扩展实现设置参数的功能。比如,如果有数码管,可以通过
*显示Gu16RunCnt的数值来让客户看到当前设备的计数器。如果有数码管和按键,可以通过切换到某个
*界面下,修改Gu16ReturnLeftTime和Gu16GoDownTime的数值,让客户对设备进行延时参数的设置。
*/
void RunTask(void) //运动控制的任务函数,放在主函数内
{
static unsigned char Su8RunStep=0; //运行的步骤
//当总开关处于“停止”并且“步骤不为0”时,强制把步骤归零。
if(0!=Su8RunStep&&0==Gu8RunStart)
{
Su8RunStep=0; //步骤归零
}
switch(Su8RunStep) //屡见屡爱的switch又来了
{
case 0:
if(1==Gu8RunStart) //总开关“打开”
{
Gu8RunStatus=1;//及时设置Gu8RunStatus的运动状态为“运行”
GoRight() ; //“水平气缸”往右跑。P1.4的LED灯“灭”。
Su8RunStep=1;//切换到下一步
}
break;
case 1:
if(0==vGu8SensorRight) //直到碰到了“右感应器”(按下K2),“机械臂”才往下移动。
{
GoDown();//“垂直气缸”往下跑。P1.5的LED灯“灭”。
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=Gu16GoDownTime; //向下移动3秒的延时赋值
vGu8RunTimerFlag=1; //启动定时器
Su8RunStep=2;//切换到下一步
}
break;
case 2:
if(0==vGu16RunTimerCnt) //当定时的3秒时间到,“机械臂”才往上移动,开始原路返回。
{
GoUp(); //“垂直气缸”往上跑。P1.5的LED灯“亮”。
Su8RunStep=3;//切换到下一步
}
break;
case 3:
if(0==vGu8SensorUp) //直到碰到了“上感应器”(按下K3),滑块才往左移动。
{
GoLeft() ; //“水平气缸”往左跑。P1.4的LED灯“亮”。
vGu8RunTimerFlag=0;
vGu16RunTimerCnt=Gu16ReturnLeftTime; //向左移动2秒的延时赋值
vGu8RunTimerFlag=1; //启动定时器
Su8RunStep=4;//切换到下一步
}
break;
case 4:
if(0==vGu16RunTimerCnt) //当定时的2秒时间到,完成一次过程。
{
Gu16RunCnt++; //计数器加1,统计设备运行的次数
Gu8RunStatus=0;//及时设置Gu8RunStatus的运动状态为“停止”
Gu8RunStart=0;//总开关“关闭”,为下一次启动作准备
Su8RunStep=0; //步骤变量清零,为下一次启动作准备
}
break;
}
}
jianhong_wu
发表于 2018-3-30 10:11:35
本帖最后由 jianhong_wu 于 2018-3-30 10:23 编辑
第一百一十二节: 数码管显示的基础知识。
【112.1 数码管显示的基础知识。】
上图112.1.1数码管
上图112.1.2等效图
如上图112.1.1,一个数码管内部有8个段码,每个段码内部对应一颗能发光的LED灯,把相关位置的段码点亮或熄灭就可以显示出不同的数字或者小数点。比如,要显示一个数字“1”,只需要点亮b和c这两个段码LED即可,其它6个a,d,e,f,g,h段码LED熄灭,就可以显示一个数字“1”。再进一步深入分析数码管内部的等效图(上图112.1.2),com4是右起第1位数码管内部8个段码LED的公共端,要点亮任何一个段码LED的前提必须是公共端com4为低电平(P1.0输出0信号)。如果公共端com4为高电平(P1.0输出1信号),则不管段码端P0口的8个IO口输出什么信号,8个段码LED都是熄灭的(无正压差,则无电流无回路)。因此,公共端(比如com4,com3,com2,com1)就是某个数码管的“总开关”。比如,右起第1位数码管要显示数字“1”,要点亮b和c,则P0.1和P0.2必须输出“1”高电平,其它P0.0,P0.3,P0.4,P0.5,P0.6,P0.7必须输出“0”低电平,把这8个IO口二进制的信号转换成十六进制,则整个P0口总线只需输出一个十六进制的0x06,最后,“总开关”打开,公共端com4输出“0”,即可显示一个数字“1”。如果需要显示其它的不同数字,只需要改变段码端P0口的十六进制输出数值即可,如果提前把要显示的数字放在一个数组里,这个数组就是编码转换表,类似于一个字库表。现在编写一个程序例子,右起第1个和第3个数码管循环显示从0到9的数字,另外右起第2个和第4个数码管则关闭不显示,程序代码如下:
#include "REG52.H"
#define CHANGE_TIME1000 //数码管切换显示数字的时间
void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;
void DisplayTask(void); //数码管显示的任务函数
sbit P1_0=P1^0;//右起第1位数码管的公共端com4
sbit P1_1=P1^1;//右起第2位数码管的公共端com3
sbit P1_2=P1^2;//右起第3位数码管的公共端com2
sbit P1_3=P1^3;//右起第4位数码管的公共端com1
//根据原理图得出的共阴数码管编码转换表,类似于一个字库表
code unsigned char Cu8DigTable[]=
{
0x3f,//0 序号0
0x06,//1 序号1
0x5b,//2 序号2
0x4f,//3 序号3
0x66,//4 序号4
0x6d,//5 序号5
0x7d,//6 序号6
0x07,//7 序号7
0x7f,//8 序号8
0x6f,//9 序号9
0x00,//不显示序号10
};
volatile unsigned char vGu8ChangeTimerFlag=0;//控制切换数字的时间的定时器
volatile unsigned int vGu16ChangeTimerCnt=0;
unsigned char Gu8Number=0; //从0到9依次循环显示的数字
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
DisplayTask(); //数码管显示的任务函数
}
}
void DisplayTask(void) //数码管显示的任务函数
{
static unsigned char Su8GetCode;//从编码转换表中提取出来的编码。
if(0==vGu16ChangeTimerCnt)//定时的时间到,更新显示下一个数字,依次循环显示
{
Su8GetCode=Cu8DigTable; //从编码转换表中提取出来的编码。
P0=Su8GetCode; //段码端输出需要显示的编码
P1_0=0;//右起第1位数码管的公共端com4,“总开关”打开,输出低电平0
P1_1=1;//右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=0;//右起第3位数码管的公共端com2,“总开关”打开,输出低电平0
P1_3=1;//右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1
Gu8Number++;//显示的数字不断从0到9累加
if(Gu8Number>9)
{
Gu8Number=0;
}
vGu8ChangeTimerFlag=0;
vGu16ChangeTimerCnt=CHANGE_TIME;
vGu8ChangeTimerFlag=1;//启动新一轮的定时器
}
}
void T0_time() interrupt 1
{
if(1==vGu8ChangeTimerFlag&&vGu16ChangeTimerCnt>0)//数码管显示切换时间的定时器
{
vGu16ChangeTimerCnt--;
}
TH0=0xfc;
TL0=0x66;
}
void SystemInitial(void)
{
//初始化上电瞬间数码管的状态
P1_0=1;//右起第1位数码管的公共端com4,“总开关”关闭,输出低电平1
P1_1=1;//右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=1;//右起第3位数码管的公共端com2,“总开关”关闭,输出低电平1
P1_3=1;//右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1
TMOD=0x01;
TH0=0xfc;
TL0=0x66;
EA=1;
ET0=1;
TR0=1;
}
void Delay(unsigned long u32DelayTime)
{
for(;u32DelayTime>0;u32DelayTime--);
}
void PeripheralInitial(void)
{
}
jianhong_wu
发表于 2018-4-12 09:24:56
本帖最后由 jianhong_wu 于 2018-4-12 19:31 编辑
第一百一十三节: 动态扫描的数码管显示数字。
【113.1 动态扫描的数码管。】
上图113.1.1数码管
上一节,看到打开显示的数码管右起第1个(com4)和第3个(com2)在任意时刻显示的数字是一样的,为什么?因为四个数码管的8个段码a,b,c,d,e,f,g,h所连接的单片机IO口是共用的,如果把四个数码管全部打开(com1,com2,com3,com4全部输出低电平),会发现四个数码管在任意时刻显示的四个数字也是一样的!实际应用中,要四个数码管能各自独立显示不同的数字,就需要用到“分时动态扫描”的方式。所谓分时,就是在任意时刻只能显示其中一个数码管(某个com输出低电平),其它三个数码管关闭(其它三个com输出高电平),每个数码管显示停留的时间固定一致并且非常短暂,四个数码管依次循环的切换显示,只要切换画面的速度足够快,人的视觉就分辨不出来,感觉八个数码管是同时亮的(实际不是同时亮),跟动画片“1秒钟动态切换显示多少幅画面”的原理一样。现在编写一个程序例子,四个数码管要显示四个不同的数字“1234”,程序代码如下:
#include "REG52.H"
/* 注释一:
*SCAN_TIME是每个数码管停留显示的短暂时间。这里称为“扫描时间”。这个时间既不能太长也不能
*太短,要调试到恰到好处。太长,则影响其它数码管的显示,会让人觉得画面不连贯不是同时亮;
*太短,又会影响显示的亮度。具体的时间应该根据实际项目不断调试修正而得到最佳显示的数值。
*/
#define SCAN_TIME1
void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;
void DisplayScan(void); //数码管的动态扫描函数,放在定时中断里。
sbit P1_0=P1^0;//右起第1位数码管的公共端com4
sbit P1_1=P1^1;//右起第2位数码管的公共端com3
sbit P1_2=P1^2;//右起第3位数码管的公共端com2
sbit P1_3=P1^3;//右起第4位数码管的公共端com1
//根据原理图得出的共阴数码管编码转换表,类似于一个字库表
code unsigned char Cu8DigTable[]=
{
0x3f,//0 序号0
0x06,//1 序号1
0x5b,//2 序号2
0x4f,//3 序号3
0x66,//4 序号4
0x6d,//5 序号5
0x7d,//6 序号6
0x07,//7 序号7
0x7f,//8 序号8
0x6f,//9 序号9
0x00,//不显示序号10
};
volatile unsigned char vGu8ScanTimerFlag=0;//动态扫描的定时器
volatile unsigned int vGu16ScanTimerCnt=0;
/* 注释二:
*vGu8Display_Righ_4,vGu8Display_Righ_3,vGu8Display_Righ_2,vGu8Display_Righ_1,这四个
*全局变量用来传递每位数码管需要显示的数字,作为对上面应用层调用的接口变量。
*/
volatile unsigned char vGu8Display_Righ_4=1;//右起第4位数码管显示的变量。这里显示“1”
volatile unsigned char vGu8Display_Righ_3=2;//右起第3位数码管显示的变量。这里显示“2”
volatile unsigned char vGu8Display_Righ_2=3;//右起第2位数码管显示的变量。这里显示“3”
volatile unsigned char vGu8Display_Righ_1=4;//右起第1位数码管显示的变量。这里显示“4”
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
}
}
/* 注释三:
*DisplayScan数码管的动态扫描函数,之所以放在定时中断里,是因为动态扫描数码管对时间均匀度
*要求很高,如果放在main主函数中,期间稍微出现一些延时滞后或者超前执行的情况,都会导致
*数码管出现“闪烁”或者“忽暗忽亮”的显示效果。
*/
void DisplayScan(void)
{
static unsigned char Su8GetCode;//从编码转换表中提取出来的编码。
static unsigned char Su8ScanStep=1;//扫描步骤
if(0==vGu16ScanTimerCnt)//定时的时间到,切换显示下一个数码管,依次动态快速循环切换显示
{
/* 注释四:
*在即将切换显示到下一个新的数码管之前,应该先关闭显示所有的数码管,避免因关闭不彻底而导致
*数码管某些段位出现“漏光”,也就是数码管因程序处理不善而出现常见的“鬼影”显示情况。
*/
P0=0x00; //输出显示先清零,先关闭显示所有的数码管
//先关闭所有的com口,先关闭显示所有的数码管
P1_0=1;//右起第1位数码管的公共端com4,“总开关”关闭,输出低电平1
P1_1=1;//右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=1;//右起第3位数码管的公共端com2,“总开关”关闭,输出低电平1
P1_3=1;//右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1
switch(Su8ScanStep)
{
case 1: //显示右起第1个数码管
Su8GetCode=Cu8DigTable; //从编码转换表中提取出来的编码。
P0=Su8GetCode; //段码端输出需要显示的编码
P1_0=0;//右起第1位数码管的公共端com4,“总开关”打开,输出低电平0
P1_1=1;//右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=1;//右起第3位数码管的公共端com2,“总开关”关闭,输出高电平1
P1_3=1;//右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1
break;
case 2: //显示右起第2个数码管
Su8GetCode=Cu8DigTable; //从编码转换表中提取出来的编码。
P0=Su8GetCode; //段码端输出需要显示的编码
P1_0=1;//右起第1位数码管的公共端com4,“总开关”关闭,输出高电平1
P1_1=0;//右起第2位数码管的公共端com3,“总开关”打开,输出低电平0
P1_2=1;//右起第3位数码管的公共端com2,“总开关”关闭,输出高电平1
P1_3=1;//右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1
break;
case 3: //显示右起第3个数码管
Su8GetCode=Cu8DigTable; //从编码转换表中提取出来的编码。
P0=Su8GetCode; //段码端输出需要显示的编码
P1_0=1;//右起第1位数码管的公共端com4,“总开关”关闭,输出高电平1
P1_1=1;//右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=0;//右起第3位数码管的公共端com2,“总开关”打开,输出低电平0
P1_3=1;//右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1
break;
case 4: //显示右起第4个数码管
Su8GetCode=Cu8DigTable; //从编码转换表中提取出来的编码。
P0=Su8GetCode; //段码端输出需要显示的编码
P1_0=1;//右起第1位数码管的公共端com4,“总开关”关闭,输出高电平1
P1_1=1;//右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=1;//右起第3位数码管的公共端com2,“总开关”关闭,输出高电平1
P1_3=0;//右起第4位数码管的公共端com1,“总开关”打开,输出低电平0
break;
}
Su8ScanStep++;
if(Su8ScanStep>4) //如果扫描步骤大于4,继续从第1步开始扫描
{
Su8ScanStep=1;
}
vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;
vGu8ScanTimerFlag=1;//启动新一轮的定时器
}
}
void T0_time() interrupt 1
{
DisplayScan(); //数码管的动态扫描函数
if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)//数码管显示切换时间的定时器
{
vGu16ScanTimerCnt--;
}
TH0=0xfc;
TL0=0x66;
}
void SystemInitial(void)
{
//初始化上电瞬间数码管的状态,关闭显示所有的数码管
P0=0x00;
P1_0=1;//右起第1位数码管的公共端com4,“总开关”关闭,输出低电平1
P1_1=1;//右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=1;//右起第3位数码管的公共端com2,“总开关”关闭,输出低电平1
P1_3=1;//右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1
TMOD=0x01;
TH0=0xfc;
TL0=0x66;
EA=1;
ET0=1;
TR0=1;
}
void Delay(unsigned long u32DelayTime)
{
for(;u32DelayTime>0;u32DelayTime--);
}
void PeripheralInitial(void)
{
}
jianhong_wu
发表于 2018-4-16 12:47:37
本帖最后由 jianhong_wu 于 2018-4-16 12:58 编辑
第一百一十四节: 动态扫描的数码管显示小数点。
【114.1 动态扫描的数码管显示小数点。】
上图114.1.1数码管
如上图,小数点的段码是h,对应单片机的P0.7口。数码管编码转换表(类似字库)的11个以字节为单位的数据,把它们从十六进制转换成二进制后,可以发现第7位(对应P0.7口)都是0。因此,从转换表里取数据后,得到的数据默认是让数码管的小数点不显示的。如果想显示这个小数点,就需要用到“或(|)”语句操作。比如,本节程序需要显示“1.234”这个带小数点的数值,代码如下:
#include "REG52.H"
#define SCAN_TIME1
void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;
void DisplayScan(void);
sbit P1_0=P1^0;
sbit P1_1=P1^1;
sbit P1_2=P1^2;
sbit P1_3=P1^3;
//转换表,里面的11个数据,转换成二进制后,第7位数据都是0默认不显示小数点
code unsigned char Cu8DigTable[]=
{
0x3f,//0 序号0
0x06,//1 序号1
0x5b,//2 序号2
0x4f,//3 序号3
0x66,//4 序号4
0x6d,//5 序号5
0x7d,//6 序号6
0x07,//7 序号7
0x7f,//8 序号8
0x6f,//9 序号9
0x00,//不显示序号10
};
volatile unsigned char vGu8ScanTimerFlag=0;
volatile unsigned int vGu16ScanTimerCnt=0;
volatile unsigned char vGu8Display_Righ_4=1;//右起第4位数码管显示的变量。这里显示“1”
volatile unsigned char vGu8Display_Righ_3=2;//右起第3位数码管显示的变量。这里显示“2”
volatile unsigned char vGu8Display_Righ_2=3;//右起第2位数码管显示的变量。这里显示“3”
volatile unsigned char vGu8Display_Righ_1=4;//右起第1位数码管显示的变量。这里显示“4”
/* 注释一:
*vGu8Display_Righ_Dot_4,vGu8Display_Righ_Dot_3,vGu8Display_Righ_Dot_2,
*vGu8Display_Righ_Dot_1,这四个全局变量用来传递每位数码管是否需要显示它的小数点,如果是1
*代表需要显示其小数点,如果是0则不显示小数点。这四个变量作为对上面应用层调用的接口变量。
*/
volatile unsigned char vGu8Display_Righ_Dot_4=1;//右起第4位数码管的小数点。1代表打开显示。
volatile unsigned char vGu8Display_Righ_Dot_3=0;//右起第3位数码管的小数点。0代表关闭显示。
volatile unsigned char vGu8Display_Righ_Dot_2=0;//右起第2位数码管的小数点。0代表关闭显示。
volatile unsigned char vGu8Display_Righ_Dot_1=0;//右起第1位数码管的小数点。0代表关闭显示。
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
}
}
void DisplayScan(void)
{
static unsigned char Su8GetCode;
static unsigned char Su8ScanStep=1;
if(0==vGu16ScanTimerCnt)
{
P0=0x00;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=1;
switch(Su8ScanStep)
{
case 1:
Su8GetCode=Cu8DigTable;
/* 注释二:
*这里是本节的关键。通过判断全局的接口变量的数值,来决定是否打开显示小数点。
*从转换表取出字模数据后再跟0x80进行“或”运算即可把第7位数据改为1。
*/
if(1==vGu8Display_Righ_Dot_1) //如果打开了需要显示第1个数码管的小数点
{
Su8GetCode=Su8GetCode|0x80;//把第7位数据改为1,显示小数点
}
P0=Su8GetCode;
P1_0=0;
P1_1=1;
P1_2=1;
P1_3=1;
break;
case 2:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_2) //如果打开了需要显示第2个数码管的小数点
{
Su8GetCode=Su8GetCode|0x80;//把第7位数据改为1,显示小数点
}
P0=Su8GetCode;
P1_0=1;
P1_1=0;
P1_2=1;
P1_3=1;
break;
case 3:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_3) //如果打开了需要显示第3个数码管的小数点
{
Su8GetCode=Su8GetCode|0x80;//把第7位数据改为1,显示小数点
}
P0=Su8GetCode;
P1_0=1;
P1_1=1;
P1_2=0;
P1_3=1;
break;
case 4:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_4) //如果打开了需要显示第4个数码管的小数点
{
Su8GetCode=Su8GetCode|0x80;//把第7位数据改为1,显示小数点
}
P0=Su8GetCode;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=0;
break;
}
Su8ScanStep++;
if(Su8ScanStep>4)
{
Su8ScanStep=1;
}
vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;
vGu8ScanTimerFlag=1;
}
}
void T0_time() interrupt 1
{
DisplayScan();
if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)
{
vGu16ScanTimerCnt--;
}
TH0=0xfc;
TL0=0x66;
}
void SystemInitial(void)
{
P0=0x00;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=1;
TMOD=0x01;
TH0=0xfc;
TL0=0x66;
EA=1;
ET0=1;
TR0=1;
}
void Delay(unsigned long u32DelayTime)
{
for(;u32DelayTime>0;u32DelayTime--);
}
void PeripheralInitial(void)
{
}
jianhong_wu
发表于 2018-4-22 11:40:14
本帖最后由 jianhong_wu 于 2018-4-22 12:13 编辑
第一百一十五节: 按键控制数码管的秒表。
【115.1 按键控制数码管的秒表。】
上图115.1.1数码管
上图115.1.2独立按键
本节通过一个秒表的小项目,让大家学会以下4个知识点:
(1)上层的界面显示框架几乎都要用到更新变量,更新变量包括整屏更新和局部更新,本节只用到整屏更新。更新变量是用全局变量在函数之间传递信息。作用是,当有某个需要显示的数据发生改变的时候,就要给更新变量置1,让显示函数重新更新一次显示,确保最新的数据能及时显示出来,平时没有数据更新改变的时候不用频繁更新显示避免占用CPU过多的时间。
(2)凡是需要显示数字的地方,都必须涉及如何把一个数据按“个十百千万...”的位逐个提取出来的算法。这个算法比较简单,主要用“求余”和“求商”这两个运算语句就可以随心所欲的把数据位提取出来。除此之外,还要学会如何用if语句判断数据的范围,来把高位尚未用到的某个数码管屏蔽,让该位数码管只显示一个“不显示”的数据(避免直接显示一个0)。
(3)我常把单片机程序简化成4个代表:按键(人机输入),数码管(人机界面),跑马灯(应用程序),串口(通信)。本节的“应用程序”不是跑马灯,而是秒表。不管是跑马灯,还是秒表,都要用到一个总启动Gu8RunStart和一个总运行步骤Gu8RunStep。建议大家,总启动Gu8RunStart和总运行步骤Gu8RunStep应该成双成对的出现(这样关断响应更及时,并且结构更紧凑,漏洞更少),比如,凡是总启动Gu8RunStart发生改变的时候,总运行步骤Gu8RunStep都复位归零一下。
(4)一个硬件的定时器中断,可以衍生出N个软件定时器,之前跟大家介绍的是“递减式”的软件定时器,而且实际应用中,“递减式”的软件定时器也是用得最多。本节因为项目的需要,需要用到的是“累加式”的软件定时器。不管是哪种软件定时器,大家都要注意定时器变量在定义时所用到的数据类型,这个数据类型决定了定时时间的长度,比如在51单片机中,unsigned int的范围是0到65535,最大一次性定时65.535秒。而unsigned long的范围是0到4294967295,最大一次性定时4294967.295秒。本节秒表的时间超过65.535秒,因此需要用到unsigned long类型的定时器变量。
本节秒表程序的功能:K1按键是复位按键,每按一次,秒表都停止并且重新归零。K2按键是启动和暂停按键,当秒表处于复位后停止的状态时按一次则开始启动,当秒表处于正在工作的状态时按一次则处于暂停状态,当秒表处于暂停的状态时按一次则继续处于工作的状态。本节4位数码管,显示的时间是带2位小数点的,能显示的时间范围是:0.00秒到99.99秒。代码如下:
#include "REG52.H"
#define KEY_FILTER_TIME25
#define SCAN_TIME1
void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;
void KeyScan(void);
void KeyTask(void);
void DisplayScan(void);//底层显示的驱动函数
void DisplayTask(void);//上层显示的任务函数
void RunTask(void);//秒表的应用程序
sbit KEY_INPUT1=P2^2;
sbit KEY_INPUT2=P2^1;
sbit P1_0=P1^0;
sbit P1_1=P1^1;
sbit P1_2=P1^2;
sbit P1_3=P1^3;
//数码管转换表
code unsigned char Cu8DigTable[]=
{
0x3f,//0 序号0
0x06,//1 序号1
0x5b,//2 序号2
0x4f,//3 序号3
0x66,//4 序号4
0x6d,//5 序号5
0x7d,//6 序号6
0x07,//7 序号7
0x7f,//8 序号8
0x6f,//9 序号9
0x00,//不显示序号10
};
//数码管底层驱动扫描的软件定时器
volatile unsigned char vGu8ScanTimerFlag=0;
volatile unsigned int vGu16ScanTimerCnt=0;
//秒表的软件定时器,注意,这里是unsigned long类型,范围是0到4294967295毫秒
volatile unsigned char vGu8StopWatchTimerFlag=0;
volatile unsigned long vGu32StopWatchTimerCnt=0;
//数码管上层每10ms就定时刷新一次显示的软件定时器。用于及时更新显示秒表当前的实时数值
volatile unsigned char vGu8UpdateTimerFlag=0;
volatile unsigned int vGu16UpdateTimerCnt=0;
unsigned char Gu8RunStart=0;//应用程序的总启动
unsigned char Gu8RunStep=0;//应用程序的总运行步骤。建议跟vGu8RunStart成双成对出现
unsigned char Gu8RunStatus=0; //当前秒表的状态。0代表停止,1代表正在工作中,2代表暂停
unsigned char Gu8WdUpdate=1;//开机默认整屏更新一次显示。此变量在显示框架中是非常重要的变量
volatile unsigned char vGu8Display_Righ_4=10;//开机默认最高位数码管显示一个“不显示”数据
volatile unsigned char vGu8Display_Righ_3=0;
volatile unsigned char vGu8Display_Righ_2=0;
volatile unsigned char vGu8Display_Righ_1=0;
volatile unsigned char vGu8Display_Righ_Dot_4=0;
volatile unsigned char vGu8Display_Righ_Dot_3=1; //开机默认保留显示2个小数点
volatile unsigned char vGu8Display_Righ_Dot_2=0;
volatile unsigned char vGu8Display_Righ_Dot_1=0;
volatile unsigned char vGu8KeySec=0;
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
KeyTask(); //按键的任务函数
DisplayTask();//数码管显示的上层任务函数
RunTask(); //秒表的应用程序
}
}
void KeyTask(void) //按键的任务函数
{
if(0==vGu8KeySec)
{
return;
}
switch(vGu8KeySec)
{
case 1: //复位按键
Gu8RunStatus=0; //秒表返回停止的状态
Gu8RunStart=0;//秒表停止
Gu8RunStep=0;//总运行步骤归零。建议跟vGu8RunStart成双成对出现
vGu8StopWatchTimerFlag=0;
vGu32StopWatchTimerCnt=0; //秒表的软件定时器清零
Gu8WdUpdate=1;//整屏更新一次显示
vGu8KeySec=0;
break;
case 2: //启动与暂停的按键
if(0==Gu8RunStatus) //在停止状态下
{
Gu8RunStatus=1;//秒表处于工作状态
vGu8StopWatchTimerFlag=0;
vGu32StopWatchTimerCnt=0;
vGu8StopWatchTimerFlag=1;//启动秒表的软件定时器
Gu8RunStart=1; //秒表总开关启动
Gu8RunStep=0; //总运行步骤归零。建议跟vGu8RunStart成双成对出现
}
else if(1==Gu8RunStatus) //在工作状态下
{
Gu8RunStatus=2;//秒表处于暂停状态
}
else//在暂停状态下
{
Gu8RunStatus=1;//秒表处于工作状态
}
Gu8WdUpdate=1;//整屏更新一次显示,确保在暂停的时候能显示到最新的数据
vGu8KeySec=0;
break;
}
}
void DisplayTask(void) //数码管显示的上层任务函数
{
//需要借用的中间变量,用来拆分数据位
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量
/* 注释一:
*此处为什么要多加4个中间过渡变量Su8Temp_X?是因为vGu32StopWatchTimerCnt分解数据的时候
*需要进行除法和求余数的运算,就会用到好多条指令,就会耗掉一点时间,类似延时了一会。我们
*的定时器每隔一段时间都会产生中断,然后在中断里驱动数码管显示,当vGu32StopWatchTimerCnt
*还没完全分解出4位有效数据时,这个时候来的定时中断,就有可能导致显示的数据瞬间产生不完整,
*影响显示效果。因此,为了把需要显示的数据过渡最快,所以采取了先分解,再过渡显示的方法。
*/
if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示
//先分解数据
//Su8Temp_4提取“十秒”位。
Su8Temp_4=vGu32StopWatchTimerCnt/10000; //实际精度是0.0001秒,但显示精度是0.01秒
Su8Temp_4=Su8Temp_4%10;
//Su8Temp_3提取“个秒”位。
Su8Temp_3=vGu32StopWatchTimerCnt/1000; //实际精度是0.0001秒,但显示精度是0.01秒
Su8Temp_3=Su8Temp_3%10;
//Su8Temp_2提取“百毫秒”位。
Su8Temp_2=vGu32StopWatchTimerCnt/100; //实际精度是0.0001秒,但显示精度是0.01秒
Su8Temp_2=Su8Temp_2%10;
//Su8Temp_1提取“十毫秒”位。
Su8Temp_1=vGu32StopWatchTimerCnt/10; //实际精度是0.0001秒,但显示精度是0.01秒
Su8Temp_1=Su8Temp_1%10;
//判断数据范围,来决定最高位数码管是否需要显示。
if(vGu32StopWatchTimerCnt<10000) //10.000秒。实际4位数码管最大只能显示99.99秒
{
Su8Temp_4=10;//在数码管转换表里,10代表一个“不显示”的数据
}
//上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
vGu8Display_Righ_4=Su8Temp_4;//过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;
vGu8Display_Righ_2=Su8Temp_2;
vGu8Display_Righ_1=Su8Temp_1;
vGu8Display_Righ_Dot_4=0;
vGu8Display_Righ_Dot_3=1; //保留显示2位小数点
vGu8Display_Righ_Dot_2=0;
vGu8Display_Righ_Dot_1=0;
}
}
void RunTask(void)//秒表的应用程序
{
if(0==Gu8RunStart)
{
return;// 如果秒表处于停止状态,则直接退出当前函数,不执行该函数以下的其它代码
}
switch(Gu8RunStep)
{
case 0://在这个步骤里,主要用来初始化一些参数
vGu8UpdateTimerFlag=0;
vGu16UpdateTimerCnt=10;//每10ms更新显示一次当前秒表的时间
vGu8UpdateTimerFlag=1;
Gu8RunStep=1; //跳转到每10ms更新显示一次的步骤里
break;
case 1://每10ms更新一次显示,确保实时显示秒表当前的时间
if(0==vGu16UpdateTimerCnt) //每10ms更新显示一次当前秒表的时间
{
vGu8UpdateTimerFlag=0;
vGu16UpdateTimerCnt=10;//重置定时器,为下一个10ms更新做准备
vGu8UpdateTimerFlag=1;
Gu8WdUpdate=1;//整屏更新一次显示当前秒表的时间
}
break;
}
}
void KeyScan(void)//按键底层的驱动扫描函数,放在定时中断函数里
{
static unsigned char Su8KeyLock1;
static unsigned intSu16KeyCnt1;
static unsigned char Su8KeyLock2;
static unsigned intSu16KeyCnt2;
if(0!=KEY_INPUT1)
{
Su8KeyLock1=0;
Su16KeyCnt1=0;
}
else if(0==Su8KeyLock1)
{
Su16KeyCnt1++;
if(Su16KeyCnt1>=KEY_FILTER_TIME)
{
Su8KeyLock1=1;
vGu8KeySec=1;
}
}
if(0!=KEY_INPUT2)
{
Su8KeyLock2=0;
Su16KeyCnt2=0;
}
else if(0==Su8KeyLock2)
{
Su16KeyCnt2++;
if(Su16KeyCnt2>=KEY_FILTER_TIME)
{
Su8KeyLock2=1;
vGu8KeySec=2;
}
}
}
void DisplayScan(void) //数码管底层的驱动扫描函数,放在定时中断函数里
{
static unsigned char Su8GetCode;
static unsigned char Su8ScanStep=1;
if(0==vGu16ScanTimerCnt)
{
P0=0x00;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=1;
switch(Su8ScanStep)
{
case 1:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_1)
{
Su8GetCode=Su8GetCode|0x80;
}
P0=Su8GetCode;
P1_0=0;
P1_1=1;
P1_2=1;
P1_3=1;
break;
case 2:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_2)
{
Su8GetCode=Su8GetCode|0x80;
}
P0=Su8GetCode;
P1_0=1;
P1_1=0;
P1_2=1;
P1_3=1;
break;
case 3:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_3)
{
Su8GetCode=Su8GetCode|0x80;
}
P0=Su8GetCode;
P1_0=1;
P1_1=1;
P1_2=0;
P1_3=1;
break;
case 4:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_4)
{
Su8GetCode=Su8GetCode|0x80;
}
P0=Su8GetCode;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=0;
break;
}
Su8ScanStep++;
if(Su8ScanStep>4)
{
Su8ScanStep=1;
}
vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;
vGu8ScanTimerFlag=1;
}
}
void T0_time() interrupt 1
{
KeyScan(); //按键底层的驱动扫描函数
DisplayScan();//数码管底层的驱动扫描函数
if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)
{
vGu16ScanTimerCnt--;//递减式的软件定时器
}
//每10ms就定时更新一次显示的软件定时器
if(1==vGu8UpdateTimerFlag&&vGu16UpdateTimerCnt>0)
{
vGu16UpdateTimerCnt--;//递减式的软件定时器
}
//秒表实际走的时间的软件定时器,注意,这里是“累加式”的软件定时器
if(1==vGu8StopWatchTimerFlag&&1==Gu8RunStatus) //秒表处于工作的状态
{
vGu32StopWatchTimerCnt++;//累加式的软件定时器
}
TH0=0xfc;
TL0=0x66;
}
void SystemInitial(void)
{
P0=0x00;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=1;
TMOD=0x01;
TH0=0xfc;
TL0=0x66;
EA=1;
ET0=1;
TR0=1;
}
void Delay(unsigned long u32DelayTime)
{
for(;u32DelayTime>0;u32DelayTime--);
}
void PeripheralInitial(void)
{
}
jianhong_wu
发表于 2018-4-30 11:42:08
本帖最后由 jianhong_wu 于 2018-4-30 11:59 编辑
第一百一十六节: 按键控制数码管的倒计时。
【116.1 按键控制数码管的倒计时。】
上图116.1.1数码管
上图116.1.2独立按键
上图116.1.3有源蜂鸣器
上一节讲“累加式”的秒表,这一节讲“递减式”的倒计时。通过此小项目,加深理解在显示框架中常用的更新变量(整屏更新和局部更新),以及应用程序与按键是如何关联的框架。同时,在拆分“个十百千万...”的时候,有一处地方必须注意,“先整除后求余”必须用一行代码一气呵成,不能拆分成两行代码“先整除;后求余”,否则会有隐患会有bug。除非,把四个临时变都改成unsigned long类型。上一节在此处残留了一个bug我发帖后还来不及修改过来,敬请大家注意。比如:
以下这样是对的:
static unsigned char Su8Temp_1;
Su8Temp_1=vGu32CountdownTimerCnt/10%10; //一气呵成,没毛病。
以下这样是有隐患有bug的(除非把Su8Temp_1改成unsigned long类型):
static unsigned char Su8Temp_1;
Su8Temp_1=vGu32CountdownTimerCnt/10;
Su8Temp_1=Su8Temp_1%10; //拆分成两行代码后,有隐患有bug。数据溢出的原因引起。
本节倒计时程序的功能:K1按键是复位按键,每按一次,倒计时停止并且重新恢复初始值10.00秒。K2按键是启动按键,当秒表处于复位后停止的状态时按一次则开始启动倒计时,当倒计时变为0.00秒的时候,蜂鸣器发出一次“滴”的提示声。此时,如果想启动新一轮的倒计时,只需按一次K1复位键,然后再按K2启动按键,就可以启动新一轮10.00秒的倒计时。代码如下:
#include "REG52.H"
#define KEY_FILTER_TIME25
#define SCAN_TIME1
#define VOICE_TIME 50 //蜂鸣器一次“滴”的声音长度 50ms
void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;
void KeyScan(void);
void KeyTask(void);
void VoiceScan(void);//蜂鸣器的驱动函数
void DisplayScan(void);//底层显示的驱动函数
void DisplayTask(void);//上层显示的任务函数
void RunTask(void);//倒计时的应用程序
void BeepOpen(void);
void BeepClose(void);
sbit KEY_INPUT1=P2^2;
sbit KEY_INPUT2=P2^1;
sbit P1_0=P1^0;
sbit P1_1=P1^1;
sbit P1_2=P1^2;
sbit P1_3=P1^3;
sbit P3_4=P3^4;
//数码管转换表
code unsigned char Cu8DigTable[]=
{
0x3f,//0 序号0
0x06,//1 序号1
0x5b,//2 序号2
0x4f,//3 序号3
0x66,//4 序号4
0x6d,//5 序号5
0x7d,//6 序号6
0x07,//7 序号7
0x7f,//8 序号8
0x6f,//9 序号9
0x00,//不显示序号10
};
//数码管底层驱动扫描的软件定时器
volatile unsigned char vGu8ScanTimerFlag=0;
volatile unsigned int vGu16ScanTimerCnt=0;
//倒计时的软件定时器,注意,这里是unsigned long类型,范围是0到4294967295毫秒
volatile unsigned char vGu8CountdownTimerFlag=0;
volatile unsigned long vGu32CountdownTimerCnt=10000;//开机默认显示初始值10.000秒
//数码管上层每10ms就定时刷新一次显示的软件定时器。用于及时更新显示秒表当前的实时数值
volatile unsigned char vGu8UpdateTimerFlag=0;
volatile unsigned int vGu16UpdateTimerCnt=0;
//蜂鸣器的软件定时器
volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;
unsigned char Gu8RunStart=0;//应用程序的总启动
unsigned char Gu8RunStep=0;//应用程序的总运行步骤。建议跟vGu8RunStart成双成对出现
unsigned char Gu8RunStatus=0; //当前倒计时的状态。0代表停止,1代表正在工作中
unsigned char Gu8WdUpdate=1;//开机默认整屏更新一次显示。此变量在显示框架中是非常重要的变量
volatile unsigned char vGu8Display_Righ_4=1;//显示“1”,跟vGu32CountdownTimerCnt高位一致
volatile unsigned char vGu8Display_Righ_3=0;
volatile unsigned char vGu8Display_Righ_2=0;
volatile unsigned char vGu8Display_Righ_1=0;
volatile unsigned char vGu8Display_Righ_Dot_4=0;
volatile unsigned char vGu8Display_Righ_Dot_3=1; //开机默认保留显示2个小数点
volatile unsigned char vGu8Display_Righ_Dot_2=0;
volatile unsigned char vGu8Display_Righ_Dot_1=0;
volatile unsigned char vGu8KeySec=0;
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
KeyTask(); //按键的任务函数
DisplayTask();//数码管显示的上层任务函数
RunTask(); //倒计时的应用程序
}
}
void KeyTask(void) //按键的任务函数
{
if(0==vGu8KeySec)
{
return;
}
switch(vGu8KeySec)
{
case 1: //复位按键
Gu8RunStatus=0; //倒计时返回停止的状态
Gu8RunStart=0;//倒计时的运行步骤的停止
Gu8RunStep=0;//总运行步骤归零。建议跟vGu8RunStart成双成对出现
vGu8CountdownTimerFlag=0; //禁止倒计时开始(而Gu8RunStart是启动开关,不矛盾)
vGu32CountdownTimerCnt=10000; //倒计时的软件定时器恢复初始值10.000秒
Gu8WdUpdate=1;//整屏更新一次显示
vGu8KeySec=0;
break;
case 2: //启动的按键
if(0==Gu8RunStatus) //在停止状态下
{
vGu8CountdownTimerFlag=0;
vGu32CountdownTimerCnt=10000;//初始值是10.000秒
vGu8CountdownTimerFlag=1; //允许倒计时的软件定时器的启动
Gu8RunStatus=1;//倒计时处于工作状态(并且,这一瞬间才正式启动倒计时)
Gu8RunStart=1; //倒计时的运行步骤的总开关开启
Gu8RunStep=0; //总运行步骤归零。建议跟vGu8RunStart成双成对出现
Gu8WdUpdate=1;//整屏更新一次显示,确保在启动的时候能显示到最新的数据
}
vGu8KeySec=0;
break;
}
}
void DisplayTask(void) //数码管显示的上层任务函数
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量
if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示
//先分解数据,注意,这里分解的时候,“先整除后求余”必须用一行代码一气呵成,不能拆
//分成两行代码,否则会有隐患会有bug。除非,把四个临时变都改成unsigned long类型。
//Su8Temp_4提取“十秒”位。
Su8Temp_4=vGu32CountdownTimerCnt/10000%10; //实际精度是0.001秒,但显示精度是0.01秒
//Su8Temp_3提取“个秒”位。
Su8Temp_3=vGu32CountdownTimerCnt/1000%10; //实际精度是0.001秒,但显示精度是0.01秒
//Su8Temp_2提取“百毫秒”位。
Su8Temp_2=vGu32CountdownTimerCnt/100%10; //实际精度是0.001秒,但显示精度是0.01秒
//Su8Temp_1提取“十毫秒”位。
Su8Temp_1=vGu32CountdownTimerCnt/10%10; //实际精度是0.001秒,但显示精度是0.01秒
//判断数据范围,来决定最高位数码管是否需要显示。
if(vGu32CountdownTimerCnt<10000) //10.000秒。实际4位数码管最大只能显示99.99秒
{
Su8Temp_4=10;//在数码管转换表里,10代表一个“不显示”的数据
}
//上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
vGu8Display_Righ_4=Su8Temp_4;//过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;
vGu8Display_Righ_2=Su8Temp_2;
vGu8Display_Righ_1=Su8Temp_1;
vGu8Display_Righ_Dot_4=0;
vGu8Display_Righ_Dot_3=1; //保留显示2位小数点
vGu8Display_Righ_Dot_2=0;
vGu8Display_Righ_Dot_1=0;
}
}
void RunTask(void)//倒计时的应用程序
{
if(0==Gu8RunStart)
{
return;// 如果总开关处于停止状态,则直接退出当前函数,不执行该函数以下的其它代码
}
switch(Gu8RunStep)
{
case 0://在这个步骤里,主要用来初始化一些参数
vGu8UpdateTimerFlag=0;
vGu16UpdateTimerCnt=10;//每10ms更新显示一次当前倒计时的时间
vGu8UpdateTimerFlag=1;
Gu8RunStep=1; //跳转到每10ms更新显示一次的步骤里
break;
case 1://每10ms更新一次显示,确保实时显示当前倒计时的时间
if(0==vGu16UpdateTimerCnt) //每10ms更新显示一次当前倒计时的时间
{
vGu8UpdateTimerFlag=0;
vGu16UpdateTimerCnt=10;//重置定时器,为下一个10ms更新做准备
vGu8UpdateTimerFlag=1;
Gu8WdUpdate=1;//整屏更新一次显示当前倒计时的时间
if(0==vGu32CountdownTimerCnt) //如果倒计时的时间到,则跳转到结束的步骤
{
Gu8RunStep=2; //跳转到倒计时结束的步骤
}
}
break;
case 2://倒计时结束的步骤
//Gu8RunStatus=0; //这行代码注释掉,让每次新启动之前都必须按一次K1复位按键才有效
Gu8RunStart=0;//倒计时的运行步骤的停止
Gu8RunStep=0; //总运行步骤归零。建议跟vGu8RunStart成双成对出现
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=VOICE_TIME;//蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;
Gu8WdUpdate=1;//整屏更新一次显示当前倒计时的时间
break;
}
}
void KeyScan(void)//按键底层的驱动扫描函数,放在定时中断函数里
{
static unsigned char Su8KeyLock1;
static unsigned intSu16KeyCnt1;
static unsigned char Su8KeyLock2;
static unsigned intSu16KeyCnt2;
if(0!=KEY_INPUT1)
{
Su8KeyLock1=0;
Su16KeyCnt1=0;
}
else if(0==Su8KeyLock1)
{
Su16KeyCnt1++;
if(Su16KeyCnt1>=KEY_FILTER_TIME)
{
Su8KeyLock1=1;
vGu8KeySec=1;
}
}
if(0!=KEY_INPUT2)
{
Su8KeyLock2=0;
Su16KeyCnt2=0;
}
else if(0==Su8KeyLock2)
{
Su16KeyCnt2++;
if(Su16KeyCnt2>=KEY_FILTER_TIME)
{
Su8KeyLock2=1;
vGu8KeySec=2;
}
}
}
void DisplayScan(void) //数码管底层的驱动扫描函数,放在定时中断函数里
{
static unsigned char Su8GetCode;
static unsigned char Su8ScanStep=1;
if(0==vGu16ScanTimerCnt)
{
P0=0x00;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=1;
switch(Su8ScanStep)
{
case 1:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_1)
{
Su8GetCode=Su8GetCode|0x80;
}
P0=Su8GetCode;
P1_0=0;
P1_1=1;
P1_2=1;
P1_3=1;
break;
case 2:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_2)
{
Su8GetCode=Su8GetCode|0x80;
}
P0=Su8GetCode;
P1_0=1;
P1_1=0;
P1_2=1;
P1_3=1;
break;
case 3:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_3)
{
Su8GetCode=Su8GetCode|0x80;
}
P0=Su8GetCode;
P1_0=1;
P1_1=1;
P1_2=0;
P1_3=1;
break;
case 4:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_4)
{
Su8GetCode=Su8GetCode|0x80;
}
P0=Su8GetCode;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=0;
break;
}
Su8ScanStep++;
if(Su8ScanStep>4)
{
Su8ScanStep=1;
}
vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;
vGu8ScanTimerFlag=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();
}
}
}
}
void BeepOpen(void)
{
P3_4=0;
}
void BeepClose(void)
{
P3_4=1;
}
void T0_time() interrupt 1
{
VoiceScan(); //蜂鸣器的驱动函数
KeyScan(); //按键底层的驱动扫描函数
DisplayScan();//数码管底层的驱动扫描函数
if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)
{
vGu16ScanTimerCnt--;//递减式的软件定时器
}
//每10ms就定时更新一次显示的软件定时器
if(1==vGu8UpdateTimerFlag&&vGu16UpdateTimerCnt>0)
{
vGu16UpdateTimerCnt--;//递减式的软件定时器
}
//倒计时实际走的时间的软件定时器,注意,这里还附加了启动状态的条件“&&1==Gu8RunStatus”
if(1==vGu8CountdownTimerFlag&&vGu32CountdownTimerCnt>0&&1==Gu8RunStatus)
{
vGu32CountdownTimerCnt--;//递减式的软件定时器
}
TH0=0xfc;
TL0=0x66;
}
void SystemInitial(void)
{
P0=0x00;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=1;
TMOD=0x01;
TH0=0xfc;
TL0=0x66;
EA=1;
ET0=1;
TR0=1;
}
void Delay(unsigned long u32DelayTime)
{
for(;u32DelayTime>0;u32DelayTime--);
}
void PeripheralInitial(void)
{
}