首页 > 范文大全 > 正文

基于STC89C51制作的具有记录天亮天黑时间功能的时钟

开篇:润墨网以专业的文秘视角,为您筛选了一篇基于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—),男,河南商水人,黑龙江科技学院在读研究生。