开篇:润墨网以专业的文秘视角,为您筛选了一篇基于STC89C51制作的具有记录天亮天黑时间功能的时钟范文,如需获取更多写作素材,在线客服老师一对一协助。欢迎您的阅读与分享!
【摘要】采用具有4K EEPROM的stc89c51作为微控制器,提出了提高定时器计时精度的简单方法,节省了时钟芯片的开销,采用定时器编写延时程序,使目标代码变得精简,通过软缓冲机制使传感器抗干扰性增强。
【关键词】STC89C51;晶振;EEPROM
前段时间做一个记录天亮天黑时间的仪器,要求能存储时间,并且时钟要精度高,需要的时候可以上传到电脑进行分析处理。传感器可以用光敏二极管和光敏三极管以及光敏电阻,考虑到易于调整最终选择了使用光敏电阻。在数码管的驱动上选择了MAX7219芯片刚好驱动八位共阴极数码管,因为手头上只有三个按键,所以在按键的设计上,采用一键多能的设计方法,按键的功能分布如下:
K1:按动一次可以进行小时的调整,且小时位闪烁给出提示,按动第二次可以对分钟调整,同样给出提示,按下第三次时候可以对秒进行调整,秒位也闪烁一下进行提示,第四次显示传感器读数,第五次按下后进入上传和格式化内部EEPROM的操作,并且都会给出提示。
K2:调整时间时候进行加操作,上传和格式化时候为上传指令键。
K3:调整时间时候进行减操作,上传和格式化时候为格式化指令键。
由于手头没有时钟芯片,又不想去买,就考虑如何用定时器做出高精度的计时器,在进行stc的下载中发现了一个奇怪的现象,显示我单片机的晶振和标称相差很多,更换了几个做测试,结果一样,终于被我发现原来定时器不准的最大原因在晶振本身是不准的,现在我按照STC编程软件提示的频率设计中断程序的定时器初始值,然后把中断跳转的指令消耗的时间考虑进去,进行了两天的测量,尽然与我笔记本的时间误差不到1s,获得较为精准的时间成功,这完全可以和普通的时钟芯片相比了。
考虑到按键的延时函数,以前都是使用for循环那样写太老套了,在各种书上我们只能看到for循环和while循环的延时函数,这种软件延时显然不能有效的控制延时时间,于是我考虑在中断里加上一个延时变量。这样就可以更为精准的控制延时了。而且会使目标代码更为短小。
以前没有使用过STC的内部EEPROM,由于要求能够存储足够多的数据,需要能掉电保护的存储器,查看了STC89C51的手册发现竟然有4K的EEPROM可以使用,心里大喜,把手册上提供的EEPROM读写和擦除操作代码直接复制过来就行了。
传感器选择了光敏电阻,经过万用表的测量,电阻在光照较强的时候大约1K,在黑暗环境达到几百K,于是选择了手头上的一枚10K的电阻进行串联匹配,用于分压比较。因为这样只需要测量定值电阻上的分压比就行了,不用考虑电压源的稳定性。在AD转换上采用ADC0832一片就OK,还多出来一个通道呢。
在通过电脑串口上传数据时候发现每次上传一位需要延时一下,给硬件足够的反应时间,要不接收会出错的,比如没有收到全部数据,数据丢失,等等。
在判断何时记录时间时候,考虑到传感器可能会出现数值震荡或者不稳定情况,那么我就通过设定缓冲区来解决这个问题。定义X1和X2作为触发记录的上下限,其中X1
在EEPROM进行写的时候先查找空白区,在空白区进行按组写入,本程序只写入了小时分钟秒,如果扩展可以加入月份和日期。擦除的时候逐个熄灭数码管提示进度。
其余的设计细节请参考程序注释。
代码如下:
/*代码设计:高杨,2011-10 */
/*晶振:11.0592MHz*/
#include"reg51.h"
#include
sbit DIN=P2^0; //MAX7219串行数据 1脚
sbit LOAD=P2^1; //MAX7219片选 12脚
sbit CLK=P2^2;//MAX7219串行时钟 13脚
sbit k1=P2^3;
sbit k2=P2^4;
sbit k3=P2^5;
sbit AD0832_CLK=P1^0;
sbit AD0832_DIO=P1^1;
sbit AD0832_CS=P1^2;
//寄存器宏定义
#define DECODE_MODE 0x09 //译码控制寄存器
#define INTENSITY 0x0A //亮度控制寄存器
#define SCAN_LIMIT 0x0B //扫描界限寄存器
#define SHUT_DOWN 0x0C //关断模式寄存器
#define DISPLAY_TEST 0x0F //测试控制寄存器
#define X1 15 //这里我把黑夜触发值设置为15
#define X2 20 //把天亮触发值设置为20,实际上天彻底黑的值为0
#define X ADC0832()
//定义全局变量
char times[5]=0; //定义hour,minute,second,秒计数,延时计数到数组times,并初始化为0,此处必须为有符号字符,为了下面调时间方便
bit backup;//定义参考变量
unsigned char set_parameter=0;
unsigned int address=0; //此处为全局变量,对同名局部变量不构成影响。
//函数声明
void Write7219(unsigned char address,unsigned char dat);
void Initial(void);
void Key(void);
void delay(void);
void set();
void up();
void down();
void flicker();
void record(void);
bit ERASE=0;
bit UPLOAD=0;
//通过定时器延时的延时函数
void delay(void)
{
times[4]=0; while(times[4]
}
//数码管驱动芯片的写操作,传递显示为真和显示内容
void Write7219(unsigned char address,unsigned char dat)
{
unsigned char i;
LOAD=0; //拉低片选线,选中器件
for (i=0;i
{
CLK=0; //清零时钟总线
DIN=(bit)(address&0x80); //每次取高字节
address
CLK=1; //时钟上升沿,发送地址
}
for (i=0;i
{
CLK=0;
DIN=(bit)(dat&0x80);
dat
CLK=1;//时钟上升沿,发送数据
}
LOAD=1;//发送结束,上升沿锁存数据
}
//MAX7219初始化,设置MAX7219内部的控制寄存器
void Initial(void)
{
Write7219(SHUT_DOWN,0x01); //开启正常工作模式(0xX1)
Write7219(DISPLAY_TEST,0x00); //选择工作模式(0xX0)
Write7219(DECODE_MODE,0xff); //选用全译码模式
Write7219(SCAN_LIMIT,0x07); //8只LED全用
Write7219(INTENSITY,0x04); //设置初始亮度
}
/*串口初始化*/
void UartInit(void) //9600bps@11.0592MHz
{
PCON =0x80; //波特率不倍速
SCON = 0x40; //8位数据,可变波特率
TMOD=0x20|0x01; //0x01是为了保证TR0的正常运行
TH1 = 0xFA; //设定定时器重装值
TR1 = 1; //启动定时器1
}
//按键设置
void Key(void)
{
if(P2&0xE0==0xE0); //如果没有键按下,就什么都不做,否者进入下面的判断
{
if(k1==0) set();
if(k2==0) up();
if(k3==0) down();
}
}
void set() //K1的功能
{
set_parameter++; //每次按下后就设置参数加一
if(set_parameter>5) set_parameter=0; // 如果超过设定的功能数目就归零。
while(k1==0)
if(k2==1&&k3==1) //如果按键k1被按下没有释放,同时k2和k3也没有被按下,这时候执行
flicker(); //闪烁程序,熄灭可设置的位置,直到k1被释放。
}
void up() //k2的功能
{
if(set_parameter==1) times[0]++;delay(); //小时加加
if(set_parameter==2) times[1]++;delay(); //分钟加加
if(set_parameter==3) times[2]++;delay(); // 秒加加
if(set_parameter==5) UPLOAD=1; //如果在EE模式下按下up键,启动UPLOAD标准。
}
//按键K3的作用是减小选定的变量,另外在特殊功能时候进行擦除的操作确认
void down()
{
if(set_parameter==1) times[0]--;delay(); //小时
if(set_parameter==2) times[1]--;delay(); //分钟
if(set_parameter==3) times[2]--;delay(); //秒
if(set_parameter==5) ERASE=1; //擦除判断为真,进行擦除
}
void flicker() //闪烁程序,用于设置时间时候的告警
{
if(set_parameter==1) { Write7219(1,15); Write7219(2,15);delay();}
if(set_parameter==2) { Write7219(4,15); Write7219(5,15);delay();}
if(set_parameter==3) { Write7219(7,15); Write7219(8,15);delay();}
}
//ADC0832模拟转数字的子函数
unsigned char ADC0832()
{
unsigned char i=0,dat=0;
AD0832_CS=1; //一个转换周期开始
AD0832_CLK=0; //为第一个脉冲作准备
AD0832_CS=0; //AD0832_CS置0,片选有效
AD0832_DIO=1; //AD0832_DIO置1,规定的起始信号
AD0832_CLK=1;AD0832_CLK=0; //第一个脉冲的下降沿,此前AD0832_DIO必须是高电平
AD0832_DIO=1; //AD0832_DIO置1, 通道选择信号
AD0832_CLK=1;AD0832_CLK=0; //第二个脉冲,第2、3个脉冲下沉之前,DI必须跟别
//输入两位数据用于选择通道,这里选通道CH0
AD0832_DIO=0; //DI置0,选择通道0
AD0832_CLK=1;AD0832_CLK=0; //第三个脉冲下降沿
AD0832_DIO=1; //第三个脉冲下沉之后,输入端AD0832
_DIO失去作用,应置1,
AD0832_CLK=1; //第四个脉冲
for(i=0;i
{
AD0832_CLK=1; AD0832_CLK=0; //第四个脉冲
dat
dat|=(unsigned char)AD0832_DIO; //将输出数据AD0832_DIO通过或运算储存在dat最低位
}
AD0832_CS=1; //片选无效
return dat; //将读书的数据返回
}
/*记录程序,把时间小时分钟秒作为三位一个队列的写到eeprom*/
void record()
{
unsigned int addr=0,address; //其中addr为相对偏移地址
BYTE i;
address=IAP_ADDRESS; //把存储器的起始地址赋值给address
while(IapReadByte(address+addr)!=0xff) addr++; //读取绝对地址里面的值,如果不为空,偏移地址取下一位的
for(i=0;i
IapProgramByte(address+i+addr,times[i]);
}
/*上传程序*/
void uploading()
{
BYTE i,j,k;
address=IAP_ADDRESS; //获取EEPROM的起始地址
UartInit(); //使用串口,先初始化串口
Delay(10); //延时几秒使硬件做好准备
Write7219(7, 11);delay(); Write7219(8, 11); //当成功进入上传指令后,在7和8位置逐个显示字母E
while(address
{
for(i=0;i
{
SBUF=IapReadByte(address+i);
for(k=0;k
for(j=0;j
}
address+=3; //三位发送结束再进入下面三位地址空间进行下一轮发送。
}
UPLOAD=0; //发送完成结束上传标志位
}
/*擦除函数*/
void format()
{
unsigned char i;
for(i=0;i
{
IapEraseSector(0x200*i+IAP_ADDRESS);
Write7219(i+1,15); delay(); //每格式化一个扇区就灭掉一个对应的数码管
}
ERASE=0;//结束格式化标志位
}
/*main函数由此开始*/
void main(void)
{
bit back=(X>X2)?1:0; //初始化参考位back
unsigned char i;
TMOD=0x01; //定时器T0工作于方式1,
EA=1;
ET0=1;
TH0=(65536-46067)/256;
TL0=(65536-46067)%256;
TR0=1;
//下面进入死循环,开始所有指令
while(1)
{
Initial(); //MAX7219初始化,在循环体内初始化,增强抗干扰性。
//设置观察传感器数值时候的显示方式,前三位显示,后五位灭掉
while(set_parameter==4)
{
Write7219(1, ADC0832()/100 ); //显示读数的百位
Write7219(2, ADC0832()%100/10);//显示读数的十位
Write7219(3, ADC0832()%10); //显示读数的个位
Write7219(4, 15 ); //其余数码管关闭显示
Write7219(5, 15 );
Write7219(6, 15 );
Write7219(7, 15 );
Write7219(8, 15);
Key(); //此处跳出该循环
}
//下面进行是否触发记录的判断
if(X
{ for(i=0;i
if(X
{back=0; record(); }
}
if(X>X2&&back==0)
{ for(i=0;i
if(X>X2&&back==0)
{back=1; record(); }
}
//当按下第五次K1键的时候进入上传和格式化EEPROM的操作
while(set_parameter==5)
{
for(i=1;i
{
if(i==4||i==5) Write7219(i,11); //在数码管的中间两位4和5上显示EE
else Write7219(i,10); //其余显示——
}
Key(); //为下次按下K1跳出该while循环提供出口
if(UPLOAD) uploading(); //如果UPLOAD为真那么就运行上传指令
if(ERASE ) format(); //如果ERASE为真就运行格式化操作
}
Key(); //此处按键处理程序负责时间的调整
//以下代码负责显示时间:小时,分钟,秒
Write7219(1, times[0]/10 );
Write7219(2, times[0]%10 );
Write7219(3,10 );
Write7219(4, times[1]/10 );
Write7219(5, times[1]%10 );
Write7219(6,10);
Write7219(7, times[2]/10 );
Write7219(8, times[2]%10 );
}
//结束while大循环
}
//结束main函数进入时间中断函数
void timesInterrupt (void) interrupt 1 using 0
{
TH0=(65536-46067)/256; //跳转进入中断一般需要两个周期数,另外根据STC下载软件显示的
TL0=(65536-46067)%256; //实际晶振频率进行计算得到当前使用的晶振46067个指令周期为50ms
times[3]++; times[4]++; //times[4]为延时函数的定时器变量,当需要时候进行初始化为0
if(times[3]==20) //50ms*20=1s
{times[3]=0; times[2]++;} //足够一秒后秒加1,初始化计数变量为0
if(times[2]==60) //足够一分钟后,分加1
{times[2]=0; times[1]++;}
if(times[1]==60) //足够60分钟小时加1
{times[1]=0; times[0]++;}
if(times[0]==24)times[0]=0; //采用通用的24小时制,够一天了清零
if(times[0]
if(times[1]
if(times[2]
}
以上就是全部代码以及细节相关的注释,希望大家找出其中不足之处或者可改进的地方,与我进行交流学习。
作者简介:高杨(1986—),男,河南商水人,黑龙江科技学院在读研究生。