一.外部中断
1.编程思想
- 中断准备:中断初始化函数打开中断开关 ,选择中断传输方式
- 中断处理:为了便于观察,让我们知道单片机进入中断处理函数,在这里我们选择打开流水灯
- 电路搭建:由于
P3^3
引脚不便直接接地,我们把P3^3
和P3^7
连接起来,用P3^7
来控制P3^3
的电平变化
2.实践代码
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
sbit key_s2=P3^0;
sbit flag=P3^7;
void delay(uint z)
{
uint x,y;
for(x = z; x > 0; x--)
for(y = 114; y > 0 ; y--);
}
//外部中断1初始化
void clInit()
{
EA = 1; //开总中断
EX1 = 1;//开外部中断1
IT1 = 1;//外部中断1下降沿触发
}
void main()//main函数自身会循环
{
clInit();//外部中断1初始化
while(1)
{
if(key_s2 == 0)//判断S2是否被按下
{
delay(20);//按键消抖
if(key_s2 == 0)
{
flag = 1;
flag = 0;//产生下降沿
while(!key_s2);//松手检测
}
}
}
}
//外部中断1中断服务程序
void cml() interrupt 2
{
P1 = ~P1;
}
注意,为了确保时钟信号有两个时钟周期,按键时请自然一些。
二.定时器
1.了解
51单片机有2个16位定时器/计数器:定时器0 (T0为P3.4
)和定时器1 (T1为P3.5
)
这里所说定时/计数器是因为它有两种功能,既能定时又能计数。
当工作在定时模式时,每经过一个机器周期,内部的16位计数寄存器的值就会加1。当这个寄存器装满时溢出,我们可以算出工作在定时模式时最高单次定时时间为65535*1.085us=时间 (单位us)
。
当工作在计数器模式时,
T0(P3.4引脚)
,T1 (P3.5引脚)
每来一个脉冲计数寄存器加1。
定时器作用:定时计数器可以用于精确事件定时,PWM
脉宽调制,波形发生,信号时序测量的方面。
2.使用步骤
定时/计数器使用步骤:
- 1.启动定时/计数器(通过
TCON
控制器) - 2.设置定时/计数器工作模式(通过
TMOD
控制器) - 3.查询定时。计数器是否一处(读
TCON
内TF
位)
3.了解TMOD控制器
定时和计数功能由特殊功能寄存器TMOD
的控制位C/T进行选择,TMOD
寄存器的各位信息如下表所列。可以看出,2个定时/计数器有4种操作模式,通过TMOD
的M1
和M0
选择。2个定时/计数器的模式0、1和2都相同,模式3不同,各模式下的功能如下所述。
该图选自官方STC89C52
芯片手册,其中要注意的三个点我已经标出:
- 不可位寻址:我们在编写程序时不能再像之前一样令某位直接为0或1。
- 复位值:也就是默认值,如果我们什么也不改的情况下其值全部为0
M1
、M0
:选择0、1这种模式。
三.定时器编程
#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit DU = P2^6; //数码管段选
sbit WE = P2^7; //数码管位选
//共阴数码管段选表0-9
uchar code tabel[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,};
//毫秒级延时函数
void delay(uint z)
{
uint x,y;
for(x = z; x > 0; x--)
for(y = 114; y > 0 ; y--);
}
void display(uchar i)
{
uchar bai, shi, ge;
bai = i / 100;
shi = i % 100 / 10;
ge = i % 10;
//第一位数码管
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = 0XFE; //1111 1110
WE = 0;//锁存位选数据
DU = 1;//打开段选锁存器
P0 = tabel[bai];//
DU = 0;//锁存段选数据
delay(5);
//第二位数码管
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = 0XFD; //1111 1101
WE = 0;//锁存位选数据
DU = 1;//打开段选锁存器
P0 = tabel[shi];//
DU = 0;//锁存段选数据
delay(5);
//第三位数码管
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = 0XFB; //1111 1011
WE = 0;//锁存位选数据
DU = 1;//打开段选锁存器
P0 = tabel[ge];//
DU = 0;//锁存段选数据
delay(5);
}
//定时器初始化程序
void timeclInit()
{
TR0 = 1;//启动定时器0
TMOD = 0x01; //定时器工作模式选择
//假设定时50ms,初值通过计算而来
TH0=0x4b;
TL0=0xfd;
}
void main()
{
uchar msec,sec;
timeclInit();
while(1)
{
if(TF0 == 1) //判断是否溢出,溢出代表50ms完毕
{
TF0 = 0; //软件清零
TH0 = 0x4b;
TL0 = 0xfd; //定时50ms
msec++;
if(msec==20) //20个50ms促进秒加1
{
msec=0;
sec++;
}
}
display(sec);
if(sec>10)
sec=0; //秒清零
}
}
四.计数器
1.编程思路
- 结合上面的定时器,把一个定时/计数器作为定时器使用,一个作为计数器使用
- 计数器显示标示:使用
LED1
作为每加1的标志,需要把单片机的P1.0
和P3.4
进行连接
2.实践代码
#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit DU = P2^6; //数码管段选
sbit WE = P2^7; //数码管位选
sbit LED1 = P1^0;
//共阴数码管段选表0-9
uchar code tabel[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,};
//毫秒级延时函数
void delay(uint z)
{
uint x,y;
for(x = z; x > 0; x--)
for(y = 114; y > 0 ; y--);
}
//数码管显示函数
void display(uchar i)
{
uchar bai, shi, ge;
bai = i / 100;
shi = i % 100 / 10;
ge = i % 10;
//第一位数码管
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = 0XFE; //1111 1110
WE = 0;//锁存位选数据
DU = 1;//打开段选锁存器
P0 = tabel[bai];//
DU = 0;//锁存段选数据
delay(5);
//第二位数码管
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = 0XFD; //1111 1101
WE = 0;//锁存位选数据
DU = 1;//打开段选锁存器
P0 = tabel[shi];//
DU = 0;//锁存段选数据
delay(5);
//第三位数码管
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = 0XFB; //1111 1011
WE = 0;//锁存位选数据
DU = 1;//打开段选锁存器
P0 = tabel[ge];//
DU = 0;//锁存段选数据
delay(5);
}
//计数器T0初始化函数
void countclInit()
{
TR0 = 1;
TMOD |= 0x05;
TH0= 0;
TL0= 0;
}
//定时器T1初始化程序
void timeclInit()
{
TR1 = 1;//启动定时器0
TMOD |= 0x10; //定时器工作模式选择
//假设定时50ms,初值通过计算而来
TH1=(65535 - 46082)/256;
TL1=(65535 - 46082)%256;
}
void main()
{
uchar msec,sec;
countclInit();
timeclInit();
while(1)
{
if(TF1 == 1) //判断是否溢出,溢出代表50ms完毕
{
TF1 = 0; //软件清零
TH1=(65535 - 46082)/256;
TL1=(65535 - 46082)%256;
msec++;
if(msec==5) //20个50ms促进秒加1
{
msec=0;
LED1 = ~LED1;
}
}
display(TL0);
}
}
五.定时/计数器中断
1.理论补充
之前我们使用定时器和计数器使用的都是查询的方式(通过都TF0
或TF1
的状态),而现在我们要使用的是中断的方式,通过之前的外部中断,我们同样需要打开中断开关进行初始化。
2.基础使用
#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit DU = P2^6; //数码管段选
sbit WE = P2^7; //数码管位选
uchar msec,sec;
//共阴数码管段选表0-9
uchar code tabel[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,};
//毫秒级延时函数
void delay(uint z)
{
uint x,y;
for(x = z; x > 0; x--)
for(y = 114; y > 0 ; y--);
}
void display(uchar i)
{
uchar bai, shi, ge;
bai = i / 100;
shi = i % 100 / 10;
ge = i % 10;
//第一位数码管
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = 0XFE; //1111 1110
WE = 0;//锁存位选数据
DU = 1;//打开段选锁存器
P0 = tabel[bai];//
DU = 0;//锁存段选数据
delay(5);
//第二位数码管
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = 0XFD; //1111 1101
WE = 0;//锁存位选数据
DU = 1;//打开段选锁存器
P0 = tabel[shi];//
DU = 0;//锁存段选数据
delay(5);
//第三位数码管
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = 0XFB; //1111 1011
WE = 0;//锁存位选数据
DU = 1;//打开段选锁存器
P0 = tabel[ge];//
DU = 0;//锁存段选数据
delay(5);
}
//定时器初始化程序
void timer0Init()
{
EA = 1;//打开总中断
ET0 = 1;//打开对应中断开关
TR0 = 1;//启动定时器0
TMOD = 0x01; //定时器工作模式选择
//假设定时50ms,初值通过计算而来
TH0=0x4b;
TL0=0xfd;
}
//中断函数
void timer0() interrupt 1
{
TH0=0x4b;
TL0=0xfd;
msec=msec+1;
if(msec==20) //20个50ms促进秒加1
{
msec=0;
sec++;
}
}
void main()
{
timer0Init();
while(1)
{
display(sec);
}
}
该部分同样实现的是秒表的功能,只不过使用的是中断方式的硬件清零。
3.与数码管相结合
之前的独立键盘与数码管结合,已经在独立键盘做过介绍,但是在那里有一个问题,就是每次按下独立按键时,当且只有松开时数码管才会加1,而如果你松开慢点,就会发现按下时显示的只有一位数字,这是为什么呢?因为独立键盘的延时缘故,只有在松手检测完成后才会加1,而我们想要的效果是只要你按下无论是否松开,都会加1。所以这里要用到定时器与独立键盘和数码管相结合。
#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit DU = P2^6; //数码管段选
sbit WE = P2^7; //数码管位选
sbit key_s2 = P3^0; //定义独立键盘S2的IO口
sbit key_s3 = P3^1; //定义独立键盘S2的IO口
uchar num = 0;
//共阴数码管段选表0-9
//数码管段选表
uchar code tabel[]={
0x3F, //"0"
0x06, //"1"
0x5B, //"2"
0x4F, //"3"
0x66, //"4"
0x6D, //"5"
0x7D, //"6"
0x07, //"7"
0x7F, //"8"
0x6F, //"9"
0x77, //"A"
0x7C, //"B"
0x39, //"C"
0x5E, //"D"
0x79, //"E"
0x71, //"F"
0x76, //"H"
0x38, //"L"
0x37, //"n"
0x3E, //"u"
0x73, //"P"
0x5C, //"o"
0x40, //"-"
0x00, //熄灭
0x00 //自定义
};
uchar code SMGwei[] = {0xfe, 0xfd, 0xfb};
//毫秒级延时函数
void delay(uint z)
{
uint x,y;
for(x = z; x > 0; x--)
for(y = 114; y > 0 ; y--);
}
void display(uchar i)
{
static uchar wei;
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = SMGwei[wei];
WE = 0;//锁存位选数据
switch(wei)
{
case 0: DU = 1; P0 = tabel[i / 100]; DU = 0; break;
case 1: DU = 1; P0 = tabel[i % 100 / 10]; DU = 0; break;
case 2: DU = 1; P0 = tabel[i % 10]; DU = 0; break;
}
wei++;
if(wei == 3)
wei = 0;
}
//定时器初始化程序
void timer0Init()
{
EA = 1;//打开总中断
ET0 = 1;//打开对应中断开关
TR0 = 1;//启动定时器0
TMOD = 0x01; //定时器工作模式选择
//假设定时5ms,初值通过计算而来
TH0=0xED;
TL0=0xED;
}
//定时器0中断函数
void timer0() interrupt 1
{
TH0 = 0xED;
TL0 = 0xFF; //定时5ms
display(num); //数码管显示函数
}
void main()//main函数自身会循环
{
timer0Init();//定时器0初始化
while(1)
{
if(key_s2 == 0)//判断S2是否被按下
{
delay(20);//按键消抖
if(key_s2 == 0)
{
if(num != 120)
num++;
while(!key_s2);//松手检测
}
}
if(key_s3 == 0)//判断S3是否被按下
{
delay(20);//按键消抖
if(key_s3 == 0)
{
if(num > 0)
num--;
while(!key_s3);//松手检测
}
}
}
}
注意这里的display()
函数,由于原来的display()
函数采用的是一位一位的表示,中间用5ms
延时来消除其余辉效果,但是如果使用定时器后,每个5ms
来显示num
的值,其值直接给3个对应的数码管赋值,而不需要延时函数,所以我们显示一个值相当于调用display
函数三次,而即使原来的display()
函数去掉5ms
延时任然显示一个数才调用一次,这就是二者的区别。