【摘要】采用具有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
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]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;iX2)?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(XX2&&back==0)
{ for(i=0;iX2&&back==0)
{back=1; record(); }
}
//当按下第五次K1键的时候进入上传和格式化EEPROM的操作
while(set_parameter==5)
{
for(i=1;i<9;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]<0) times[0]=23; //此处定义防止数值溢出,否者小于0后取值将会超出实际意义的范围
if(times[1]<0) times[1]=59; //防止溢出,同上
if(times[2]<0) times[2]=59; //防止溢出,同上
}
以上就是全部代码以及细节相关的注释,希望大家找出其中不足之处或者可改进的地方,与我进行交流学习。
作者简介:高杨(1986—),男,河南商水人,黑龙江科技学院在读研究生。