仿真环境:电路仿真软件: Proteus
HEX可执行文件编写软件: keil uVision4
keil uVision4新建项目简略步骤:
可用芯片:AT89C51,AT89C52等,它们的区别如下所示:
(1)、RAM 空间增大:AT89C51 有128 字节的内部 RAM,称之为 DATA 存储区。AT89C52 的内部 RAM 扩展为 256 字节,其中高 128 字节,位于从 80H 开始的地址空间中,称之为 IDATA 存储区,但IDATA 区的访问只能是间接寻址方式。
(2)、内部 FLASH 变大:AT89C51 有 4K 字节的内部 FLASH PERAM,而。AT89C52 的内部 FLASH PERAM 增加1倍,达到8K。
(3)、中断源增加:在AT89C52 中P1.0和P1.1还可分别作为定时器/计数器2的外部计数输入(P1.0/T2)和(P1.1/T2EX),也就是说,P1.0同时可作为定时器/计数器 T2 的外部计数输入,和输出占空比 50% 的时钟脉冲端口,P1.1同时可作为定时器/计数器 T2 捕获/重新装载触发和方向控制端口。故,AT89C52 除了具备 AT89C51 的定时器/计数器 T0 和定时器/计数器 T1,还额外增加了一个定时器/计数器 T2。而定时器/计数器 T2 的控制和状态位单独位于T2CON、T2MOD,定时器/计数器 T2 在 16 位捕获方式或自动重新装载方式下的捕获/重载寄存器组是(TCAO2H、RCAP2L)。
这是一个简单来理解51单片机定时和计数的例子。
(1)先来介绍定时功能,当作为定时使用时,配置步骤:模式设置,配置TMOD寄存器;定时器初值设置 假设10ms中断;开定时器中断;开总中断;打开定时器。流程图如下图所示:
图16 定时器中断方式计时流程图
仿真电路图绘制如下图所示,实现每一秒点亮LED一次:
所含电路元器件:单片机AT89C51(频率12M),LED灯,电阻,电容,电源和地等(CRYSTAL和电容组成不可编程模块,可不添加)
图17 定时点亮LED仿真电路
LED-RED接引脚P1.0,程序中需要这样配置:
sbit led=P1^0;
采用的是定时器0,16位定时器/计数器工作模式(工作与式1),参照图 TMOD格式
图18 TMOD格式
程序中需要这样配置:
TMOD=0X01;
因为:1s=50ms*20,对于12M的晶振单片机,12MHz除12为1MHz,也就是说一秒=1000000次机器周期,50ms=50000次机器周期;所以计数值为50000,每中断20次,则点亮LED灯。
TH0=(65536-50000)/256;
TL0=(65536-50000)%256;
........
//中断服务子程序
void time0() interrupt 1 {
TH0=(65536-50000)/256; //重装初值
TL0=(65536-50000)%256;
a++;
}
a每加到20,则点亮LED灯,同时置0,需要注意每次触发中断后,都需要重新装入初值。
图19 LED点亮
(2)定时计数器作为计数器使用,配置步骤如下:
1.模式设置,配置TMOD寄存器。
2.开计数器中断
3.开总中断
4.打开计数器
注意: 计数器可以不开中断,这样溢出时只是不会进去中断服务程序。
仿真电路图绘制如下图所示,实现每按五次按键(P3.4引脚)点亮LED一次:
图20 计数方式点亮LED灯
LED灯接法和定时方式下一样,但是TMOD寄存器配置不一样了,本设计中TMOD的D2位C/T为设为1,选择计数功能:
sbit led=P1^0;
sbit s=P3^4;
TMOD=0x05; //模式设置,00000101,采用的是计数器0,工作模式1
比较定时方式下的电路,在这边的P3.4/T0口接了一个简单的开关电路,对P3.4口进行下降沿的脉冲计数。因为TR0:T0充许计数控制位,为1时充许T0计数(定时),所以初始化时需要将 TR0置1。
TR0=1;
本次的设计是用count存储计数值,到了5次,则人为让计数器进入中断,使得TH0和TL0溢出,进行中断处理。
count=(TH0<<8)|TL0;
if((count*10000)==50000)//按5下按键led状态取反
{
led=0;
TH0=0XFF;
TL0=0XFF; //人为的让计数器进入中断
}
中断服务程序中,使LED灯亮。
图21 LED灯点亮
使用定时器中断控制获得按键值,实现按下S1,D8-D1循环点亮;按下S2,D1-D8循环点亮;按下S3,全部熄灭;按下S4,D1-D8同频率闪烁。仿真电路图设计如下:
图22 按键控制流水灯方式
D1-D8接AT89C51的P3口,按键电路接P1口。
设计要点如下:
1.考虑到实际电路中按键的不稳定性,需要对按键进行消抖设计。
2.计数初值的选择,检测按键需要迅速。
3.LED需要一定的延时,不然看不到灯亮。
4.LED点亮方式P3由口的值进行控制。
基于以上的设计要点,消抖设计的时间为20ms,每次检测到有按键按下时,都先进行消抖延时:
void delay20ms(void)
{ unsigned char i,j;
for(i=0;i<100;i++)
for(j=0;j<60;j++);
}
计数初值选择1000,检测的比较迅速。P3口直接改变值,再适当延长时间来控制LED灯的流水状况。
P3=0xfe; //第一个灯亮
led_delay();
P3=0xfd; //第二个灯亮
led_delay();
P3=0xfb; //第三个灯亮
led_delay();
P3=0xf7; //第四个灯亮
led_delay();
P3=0xef; //第五个灯亮
led_delay();
P3=0xdf; //第六个灯亮
led_delay();
P3=0xbf; //第七个灯亮
led_delay();
P3=0x7f; //第八个灯亮
led_delay();
运行结果如下图所示(左上为S1方式,右上为S2方式,左下为S3方式,右下为S4方式):
图23 流水灯结果
基于51单片机实现计时(24小时制),并显示在LCD1602屏幕上。先来简单介绍下LCD1602液晶显示屏。
LCD1602液晶显示器是广泛使用的一种字符型液晶显示模块。它是由字符型液晶显示屏(LCD)、控制驱动主电路HD44780及其扩展驱动电路HD44100,以及少量电阻、电容元件和结构件等装配在PCB板上而组成。它的引脚说明如下表所示:
表4 LCD引脚说明
编号 |
符号 |
引脚说明 |
标号 |
符号 |
引脚说明 |
1 |
VSS |
电源地 |
9 |
D2| |
数据 |
2 |
VDD |
电源正极 |
10 |
D3 |
数据 |
3 |
VL |
液晶显示偏压 |
11 |
D4 |
数据 |
4 |
RS |
数据/命令选择 |
12 |
D5 |
数据 |
5 |
R/W |
读/写选择 |
13 |
D6 |
数据 |
6 |
E |
使能信号 |
14 |
D7 |
数据 |
7 |
D0 |
数据 |
15 |
BLA |
背光源正极 |
8 |
D1 |
数据 |
16 |
BLK |
背光源负极 |
引脚的具体功能不再进行展开,简单地介绍下我们本次设计中用到的几个指令(具体指令设计不再阐述):
表5 LCD指令
序号 |
指令 |
RS |
R/W |
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
1 |
清屏 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
2 |
初始化 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
0 |
0 |
3 |
开显示,无光标,不闪烁 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
0 |
0 |
4 |
光标右移,字符不移 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
0 |
仿真电路图设计如下图:
LCD1602的D0-D7连接AT89C52的P0端口,将RS位,定义为P2.0引脚,将RW位定义为P2.1引脚,将E位定义为P2.2引脚,将BF(忙碌标志位)位定义为P0.7引脚。
BF是忙碌标志位,每次写数据前都需检查显示屏是否忙碌,端口为1,则是忙碌,等待。为0则空闲,直接写数据。读时需注意:RS为低电平,RW为高电平时,可以读状态,即RS=0,RW=1,E=1
才允许读写。读完BF,将E恢复低电平,使E=0;
LCD的初始化如下:
WriteInstruction(0x38); //确保初始化成功
delay(5);
WriteInstruction(0x0c); //显示模式设置:显示开,无光标,光标不闪烁
delay(5);
WriteInstruction(0x06); //显示模式设置:光标右移,字符不移
delay(5);
WriteInstruction(0x01); //清屏幕指令,将以前的显示内容清除
delay(5);
将字符写进LCD中时,需要指定字符的位置,我们需要写两个函数,先指定位置,再写数据。
指定字符的实际地址函数为:
void WriteAddress(unsigned char x)
{
WriteInstruction(x|0x80); //显示位置的确定方法规定为"80H+地址码x"
}
写数据时,RS为高电平,RW为低电平时,可以写入数据, 即RS=1,RW=0,E从0到1发生正跳变,将数据送入P0口,即将数据写入液晶模块,当E由高电平跳变成低电平时,液晶模块开始执行命令。
时间采用24小时制,控制程序就是中断服务程序,如下所示:
count++; //每产生1次中断,中断累计次数加1
if(count==20) //如果中断次数计满20次
{count=0; //中断累计次数清0
s++; } //秒加1
if(s==60) //如果计满60秒
{s=0; //秒清0
m++; } //分钟加1
if(m==60) //如果计满60分
{ m=0; //分钟清0
h++; //小时加1
}
if(h==24) //如果计满24小时
{ h=0; //小时清0
}
计数器的初值:
TH0=(65536-46083)/256; //定时器T0高8位重新赋初值
TL0=(65536-46083)%256; //定时器T0低8位重新赋初值
因为晶振为11.059Mhz,11.0592MHz除12为921600Hz,就是一秒921600次机器周期,10ms=9216次机器周期。所以1s=20*50ms,而50ms=46083,所以计数初值为46083,可以实现比较精确的秒计时。
写时分秒分别写一个函数,时的函数如下:
void DisplayHour()
{
unsigned char i,j;
i=h/10; //取整运算,求得十位数字
j=h%10; //取余运算,求得各位数字
WriteAddress(0x44); //写显示地址,将十位数字显示在第2行第5列
WriteData(digit[i]); //将十位数字的字符常量写入LCD
WriteData(digit[j]); //将个位数字的字符常量写入LCD
}
分和秒的类似,只是写的地址不同(在屏幕上显示的位置不同),不在具体阐述。
结果如下图所示:
图25 液晶时钟
和精确时钟进行对比,还是很精准的。短时间内没有任何问题。如果想改开始时间,则可以直接在源程序中改,加日期也是一样的道理。