在设计分秒时钟时,用到了数码管作为显示,定时器定时,当时钟走到设定时间时蜂鸣器响。过程中遇到了很多问题,程序也是在大佬的指点下不断优化。可能有些描述不对,我是新手,结论也是实践加现有理解写的,不一定对,不对的地方请多多指教!程序亲测有效
我的设计是:
3个按键:1设置键,1时间加键,1时间减键
四个数码管:两个显示分钟,两个显示秒
有源蜂鸣器1:与三级管相连
Question and Answer:
1.为什么设置按键里,将按键加、减功能嵌套进去,设置按键按下的时候,计数停止,但是加、减按键按下加减功能没有反应?.因为程序进入了设置状态却没有进入按键加、减的程序
2.按键按下的时候数码管熄灭?因为程序检测到按键按下,会执行延时函数,延时会导致程序停留在那里,没有及时添加显示函数
3.按键按太快,数码管显示会出问题
本代码将将按键加、减功能设置在设置按下的情况下,设置键按下,当数字不动时,再按一次·设置键即可进行按键加减功能。为什么按下按键到按键抬起后,会干扰到数码管的显示?原因:如果按键扫描函数和数码管显示函数都放在主函数的固定循环内,当按键按下后,按键扫描
函数进行按键扫描,在扫描到按键延时后,CPU被限制在按键延时处进行计时,却无法运行其他程序,从而导致数码管显示函数无法运行,就会出现数码管闪灭显示问题。人能看到数码管闪烁是因为人眼能识别高频率(人眼看不到闪烁的频率为50Hz)低于50Hz,即1/50Hz=0.02秒,因此要想让人眼看不到数码管闪烁,数码管显示函数的显示频率至少是50Hz,即1/50Hz=0.02秒,也不是越高好,还要结合按键扫描函数的按键延时来取出最恰当的显示频率。由于程序内按键扫描函数和数码管显示函数存在大量的不精准延时。定时器解决按键
消抖原理:编写一个类似普通延时函数的定时器延时函数,用来替换掉普通延
时即可。
4.短按加减减实现按一次按键加减 ,但是按一下却加2?因为没有完全消抖,keytime没有清零,进行累加了。
5.在按键进行加减时,让计数器停止?有时候要按好多次按键才进入设置状态? 因为没有正常进入设置状态 ,设置状态标志位set不仅要在keyscan函数中将T0启动器关闭,还要在主函数利用设置状态标志位set使自动计时的函数关闭。
6.计数器会自己停止?
7.定时器中断开启了,但是时钟没有自动计时??
方法一:一直处于0状态?定时器中断子程序和定时器初始化有问题。定时器的计数变量time在中断里定义,不管有没有赋初值0,定时器的计数time都没有执行运算。
方法二:将time在中断程序里设置为静态变量。在定时器初始化的函数里和赋初始值,需要设置为全局变量,,参与运算的变量最好赋初始值,否则初始值会是随机值或者之前使用的值。
8.计数变慢了,显示的时间比设置的时间慢??在中断服务子程序和计时函数内设置标志位,标志位为1时,就开始计时。
9.闹钟函数无法让闹钟关闭,蜂鸣器一直响??难道闹钟和正常走时需要设置在两种状态?
将break 写在if大括号外,不管if的条件成不成立,有没有执行if大括号的while循环退出后buzz仍然为低电平,另外用while循环不好,没有按下按键时程序一直循环在那里,在做项目的时候是不允许的。闹钟函数在后面也有详细分析。
Notice:按键扫描函数中,要注意显示函数的位置,最好在进行自增自减运算后就扫描显示函数,否则有会造成按下按键,数码管熄灭的情况
思路:
功能:实时显示时间,可以进入两种工作模式:设置模式和正常工作模式
1.确认模块:时间模块,按键模块,显示模块,延时。
时间模块:用定时器计时,逢60进1
按键模块:利用状态标志位进入设置模式,按键功能,设置时间,加、减调时,长按、短按实现不同的效果
显示模块:数码管,通过位选和段选显示相应的数字
延时:ms 和 us级别
- 写软件
主函数在执行的时候,定时器中断在前台是一直在工作,定时器不断溢出(执行中断)清零溢出(执行中断)清零(只要你开启了),当定时器产生中断时,就会暂停主函数,这就称为定时器中断。等中断服务函数执行结束后,又会在主函数被打断的地方,继续执行主函数的内容。
1.这个定时器中断哪里有问题,中断不执行
void T0Init()
{
TMOD=0x01; //定时器选择方式1、
TH0=(65536-1000)/256; //设置初始值为65536-1000,定时1毫秒
TL0=(65536-1000)%256;
EA=1; //开总中断
ET0=1; //开启定时器/计数器1中断
TR0=1;
}
void IT_L(void) interrupt 1 using 1 //using 后是寄存器组编号,using
//不写,单片机会自己分配,但是要注意有两个中断时要避免冲突
{
u8 time;
time=0; //这个变量应该设置为全局变量或者在这里将time设置为静态变量
static u8 time=0;
TH0=(65536-1000)/256; //设置初始值为65536-1000,定时1毫秒
TL0=(65536-1000)%256;
time++;
if(time==10)
{
time=0;
t++;
}
闹钟函数分析
例1:将break放到if括号,到设定时间数码管熄灭,蜂鸣器不响说明程序有问题,程序进入了while循环却没有执行while里面的任何语句,程序一直在空循环,没有跳到主函数,所以也不会执行显示函数和按键扫描函数。重新写一遍又可以执行while循环的语句了。但是蜂鸣器一直响,只有按着按键蜂鸣器才不响,原因是按键按着的时候一直在执行if大括号语句的内容,break退出循环后,由于程序之前一直执行Alarm()的循环,没有执行时钟走时,故时钟的时间还等于设定的时间,接着又进入while循环,如此反复循环。
void Alarm()
{
//到设定时间数码管熄灭,蜂鸣器响不停,
{
while(1)
{
Buzz=0;
if(key_set==0)
{
Buzz=1;
break;
}
}
}
}
例2:将break 写在if大括号外,不管if的条件成不成立,有没有执行if大括号的内容, 都会执行break;按键按下,蜂鸣器不响,弹起又响,在没有按键的情况下,蜂鸣器响一分钟就不响了。
void Alarm()
{
if((sec==sec1)&&(min==min1))
{
while(1)
{
Buzz=0;
if(key_set==0)
{
Buzz=1;
}
break;
}
}
}
优化:由于循环函数会使程序一直停在子函数的循环里,不能执行其他函数。另外,delay函数也是,会使程序停在那里。这些都是不好的。尽量少用!!!这里使用if条件语句代替。
void Alarm()
{
if((sec==sec1)&&(min==min1)) //当时钟
{
buzz=0;
}
}
void ClrAlarm()
{
if(key_add==0)
{
buzz=1;
}
}
#include <reg52.h>
#include <intrins.h>
typedef unsigned int u16; //定义数据类型
typedef unsigned char u8;
u8 code shownum[18]={0xBF,0x86,0xDB,0xCF,0xE6,0xEd,0xFD,0x87,0xFF,0xEF,0xF7,0xFC,0xB9,0xF9,0xF1,0x00};//0-F对应的地址编码
u8 code ledbit[4]={0x0E,0x0D,0x0B,0x07}; //1-4位对应的地址编码
//u8 display[4]={show(0x01),show(0x3F),show(0x08),show(0x60)};
u8 sec=0,j=0,min=0,t=0,time;
u8 sec_set=30,min_set=1;
sbit a=P1^2;
sbit f=P1^4;
sbit b=P1^6;
sbit g=P1^7;
sbit d=P2^4;
sbit e=P2^5;
sbit c=P3^0;
sbit dp=P3^1;
sbit bit_1=P2^2;
sbit bit_2=P2^0;
sbit bit_3=P2^1;
sbit bit_4=P2^3;
sbit key_set=P3^3;
sbit key_add=P3^5;
sbit key_sub=P3^6;
sbit buzz=P3^7;
bit set=0; //设置状态标志位
u8 alarm_flag=1; //设置闹铃标志位
void show(u8 showdata);
void delay_us(u8 us);
void delay_ms(u8 ms);
void choosebit(u8 led_bit);
void TimeMode();
void display(sec, min);
void KeyScan();
void Alarm();
/*********************************************/
/* 函数名:T1Int_s() */
/* 功能:定时器及相关中断设置函数 */
/*********************************************/
void T1Int_s()
{
TMOD|=0x01; //定时器选择方式1,用|=防止配置冲突
TH0=(65536-1000)/256; //设置初始值为65536-1000,定时1毫秒
TL0=(65536-1000)%256;
// PT0=1; //设置INT1~为高优先级中断
EA=1; //开总中断
ET0=1; //开启定时器/计数器1中断
TR0=1; //启动定时器1
time=0;
}
/*********************************************/
/* 函数名:main */
/* 功能:主函数 */
/*********************************************/
void main()
{
T1Int_s();
while(1)
{
display(sec, min);
KeyScan();
Alarm();
if(set==0) //如果key_set按下,进入设置模式,停止自动计时
{
TimeMode(); //自动计时
}
}
}
/*********************************************/
/* 函数名:IT_L(void) interrupt 1 using 1 */
/* 功能: 中断服务子程序 */
/*********************************************/
void IT_L(void) interrupt 1 using 1
{
TH0=(65536-1000)/256; //设置初始值为65536-1000,定时1毫秒
TL0=(65536-1000)%256;
time++;
if(time==9)
{
time=0;
t++;
}
}
/*********************************************/
/* 函数名:shownum */
/* 功能: 显示a,b,c,d,e,f,g,dp 段 */
/*********************************************/
void show(u8 showdata)
{
a=showdata&0x01;
b=(showdata&0x02)>>1;
c=(showdata&0x04)>>2;
d=(showdata&0x08)>>3;
e=(showdata&0x10)>>4;
f=(showdata&0x20)>>5;
g=(showdata&0x40)>>6;
dp=(showdata&0x80)>>7;
}
/*********************************************/
/* 函数名:showbit */
/* 功能: 选取要显示的数码管对应的位 */
/*********************************************/
void choosebit(u8 led_bit)
{
bit_1=led_bit&0x01;
bit_2=(led_bit&0x02)>>1;
bit_3=(led_bit&0x04)>>2;
bit_4=(led_bit&0x08)>>3;
}
/*********************************************/
/* 函数名:TimeMode */
/* 功能: 时间处理 */
/*********************************************/
void TimeMode()
{
if(t==100)
{
t=0;
sec++;
if(sec==60)
{
buzz=0;
delay_ms(10);
sec=0;
min++;
if(min==60)
{
buzz=0;
delay_ms(10);
min=0;
}
buzz=1;
}
buzz=1;
}
}
/*********************************************/
/* 函数名:Alarm() */
/* 功能: 闹钟及蜂鸣器 */
/*********************************************/
void Alarm()
{
if(sec==sec_set&&min==min_set)
{
while(1)
{
buzz=!buzz;
if(key_set==0);
break; //break退出循环后buzz的值依然是低电平,所以//会一直响,关不掉
}
}
}
/*****************************************************************/
/* 函数名:delay */
/* 功能: 延时,单片机晶振位12M时,延时T=(us*2+5 )微秒 */
/*******************************************************************/
void delay_us(u8 us) //当us=248时,延时约等于0.5ms
{
while(us--);
}
void delay_ms(u8 ms)
{
while(ms--) //一个while循环,延时1ms
{
delay_us(248);
delay_us(248);
}
}
/*********************************************/
/* 函数名:display() */
/* 功能: 显示 */
/*********************************************/
void display(sec, min)
{
choosebit(ledbit[3]);
show(shownum[sec%10]); //显示秒的个位数
delay_ms(1); //间隔秒扫描一次
choosebit(ledbit[2]);
show(shownum[sec/10]); //显示秒的十位数
delay_ms(1);
choosebit(ledbit[1]);
show(shownum[min%10]); //显示分的个位数
delay_ms(1);
choosebit(ledbit[0]);
show(shownum[min/10]); //显示分的十位数
delay_ms(1);
show(0x00); //消隐
}
/*********************************************/
/* 函数名: KeyScan() */
/* 功能: 按键扫描 */
/*********************************************/
void KeyScan()
{
u8 keytime=0;
if(key_set==0) //如果设置按键按下
{
delay_ms(10); //延时去抖
if(key_set==0) //再次判断按键是否按下
{
buzz=0; //蜂鸣器响
delay_ms(10);
buzz=1; //蜂鸣器关
set=!set; //设置的变量取反,等于1时进入设置状态
TR0=!set; //定时器0会在进入设置状态后关闭,退出设置状态后打开
}
while(key_set==0);//等待按键释放
}
if(key_add==0&&set!=0) //在设置的状态下按下加
{
delay_ms(20);
if(key_add==0&&set!=0)
{
buzz=0; //蜂鸣器响
delay_ms(10);
buzz=1; //蜂鸣器关
while(key_add==0&&set!=0) //再次判断加按键按下
{
keytime++;
delay_ms(50);
if(keytime==20) //keytime加到200需要2s
{
keytime=0;
sec++;
display(sec,min); //实时显示时间
delay_ms(1); //时间不可太长,不然不能实时显示时间
if(sec==60)
{
sec=0;
min++;
if(min==60)
{
min=0;
}
}
}
keytime=0; //若未达到长按时间2s,则清零,防止多次短按累加计时
sec++; //注意sec++的位置是在while大括号内,如果在while大括号外,加1的效果不好
if(sec==60)
{
sec=0;
min++;
if(min==60)
{
min=0;
}
}
display(sec,min);
delay_ms(1); //时间不可太长,不然不能实时显示时间
}
}
while(key_add==0); //等待按键释放
}
if(key_sub==0&&set!=0) //在设置的状态下按下加
{
delay_ms(20);
if(key_sub==0&&set!=0)
{
buzz=0; //蜂鸣器响
delay_ms(10);
buzz=1; //蜂鸣器关
while(key_sub==0&&set!=0) //再次判断加按键按下
{
keytime++;
delay_ms(50);
if(keytime==20) //keytime加到200需要2s
{
keytime=0;
sec--;
display(sec,min); //实时显示时间,将显示函数写在后面会导致数码
//管不能及时显示,按下按键数码管熄灭的情况
delay_ms(1); //时间不可太长,不然不能实时显示时间
if(sec==0)
{
sec=59;
min--;
if(min==0)
{
min=60;
}
}
}
keytime=0; //若未达到长按时间2s,则清零,防止多次短按累加计时
sec--; //注意sec++的位置是在while大括号内,如果在while大括号外,加1的效果不好
if(sec==0)
{
sec=59;
min--;
if(min==0)
{
min=60;
}
}
display(sec,min);
delay_ms(1); //时间不可太长,不然不能实时显示时间
}
}
while(key_sub==0); //等待按键释放
}
}