说明:此文章仅是我学习过程中的一些记录,如有侵权,请联系我删除,文章中难免有遗漏错误之处,欢迎指出。
目录
1.首先是开CPU开中断(允许),即打开总中断,肯定要的一步,EA = 1;
2.开你所选择的中断源的中断允许位,例如现在我选择的中断源是外部中断0,则要写EX0 = 1;
一、显示器介绍
单片机系统中常用的显示器有:
发光二极管LED(Light Emitting Diode)显示器、液晶LCD(Liquid Crystal Display)显示器、CRT显示器等。LED、LCD显示器有两种显示结构:段显示(7段、米字型等)和点阵显示(5×8、8×8点阵等)。
二、LED显示器
1.LED显示器介绍
LED显示器分为两种,分别是共阴极和共阳极。
共阴极 共阳极
使用LED显示器时,要注意区分这两种不同的接法。
为了显示数字或字符,必须对数字或字符进行编码。例如为了显示1,需要让b,c管点亮,以共阴极为例,需要b,c所亮的引脚输出高电平,其他的引脚输出低电平。
七段数码管加上一个小数点,共计8段。因此为LED显示器提供的编码正好是一个字节。本次实验板用共阴LED显示器,根据电路连接图显示16进制数的编码已列在下表。
2.LED数码显示方式及电路
LED显示器工作方式有两种:静态和动态。本文讲的是静态显示方法,动态的在下一篇讲解。
静态显示的特点是每个数码管的段选必须接一个8位数据线来保持显示的字形码。当送入一次字形码后,显示字形可一直保持,直到送入新字形码为止。这种方法的优点是占用CPU时间少,显示便于监测和控制。缺点是硬件电路比较复杂,成本较高。
先附上实验板上关于数码管的电路
分别是单片机的连接引脚图,以及与其相连的两个74HC573锁存器,下面是数码管的电路部分
可以看到每个LED数码管所有引脚几乎相同,唯有第8位不同,这是数码管的位选端。
已知本实验板所用的是共阴极数码管,因此想要LED数码管亮,需要数码管的输入是高电平,观察电路板可以知道,数码管的输入来自74HC573锁存器,而74HC573锁存器的输入来自单片机的P0口。因此我们所要做的,就是操作单片机的P0口。
这里介绍一下位选和段选,以及与之有关的电路引脚。(以共阴极为例)
位选:选择哪个数码管,即只要该数码管的位选引脚为低电平即可。因为该引脚即数码管的公共端,因为共阴极的公共端是地,所以该引脚需为地,而其他的引脚根据要显示的内容为高电平即可,即下面所说的段选。
段选:数码管显示哪个数字,根据数码管的电路原理图,对应的引脚的输入为高电平,即可显示对应的数字。
3.点亮第一个数码管
现在来具体介绍如何使该电路板上的数码管亮。
可以看到P0口同时连到了2个锁存器,一个用于段选(显示哪些数字),一个用于位选(哪个数码管显示),为了不产生干扰,必须应用锁存器的功能。还是看真值表。
这里对这个真值表做一些解释,读者们即可轻松理解。这里不需要使用到高阻态Z的功能,默认是低电平,事实上上电后也默认是低电平。
可以看到当LE是高电平且D也是高电平时,Q是高电平,而D是低电平时,则Q是低电平,即当LE是高电平时,锁存器是直通的,即D为什么值,Q为什么值,我们需要这个功能来改变Q的值。
而我们同时也注意到,当LE是低电平时,不管D是什么值,Q默认是Q0,即Q保持原来的值,即锁存。我们同样需要这个功能来保持住Q的值。
现在,我们来点亮第一个LED数码管,并且使其显示的数字为1。
其实总共就两步,先位选,再段选。
我们观察到位选的LE引脚连接的是单片机的P2^7脚,段选的则为P2^6脚,由于不能直接修改IO口的值,我们应该定义两个位变量来对应这两个脚。
sbit wela = P2^7;
sbit dula = P2^6;
然后是利用锁存器的功能,进行位选。
首先令wela = 1,即锁存器的LE = 1,根据真值表可以知道,此时D为什么值,Q就为什么值,因此我们对单片机的P0口进行赋值(因为P0口与D口直接相连),选择第1个数码管,即该数码管的位选引脚为低电平,即WE1 = 0,以十六进制进行赋值,即P0 = 0xfe,此时由于锁存器的直通功能,WE1是低电平,现在我们要令wela = 1,来保持住这个值。
然后就是段选了,
同样的步骤,dula = 1,
接着是对P0口进行赋值,参考编码表或直接看数码管原理图可以知道想要显示1,需要输出P0 = 0x06,
最后就是,dula = 0,编译运行上传到单片机开发板上,即可显示出结果。
具体代码如下:
#include <reg52.h>
#define uint unsigned int
sbit wela = P2^7;
sbit dula = P2^6;
void main()
{
wela = 1;
P0 = 0xfe;
wela = 0;
dula = 1;
P0 = 0x06;
dula = 0;
while(1); //让程序停在这里,让数码管一直亮着
}
补充:对于该型号的单片机,P0口没有内接上拉电阻,需要在电路中外接一个上拉电阻,即电路原理图中的P1,P1是一个排阻,具体电路可以上网搜索,也很简单。为什么需要一个上拉电阻呢,首先是点亮二极管的电路总要一个电阻吧,参见第一讲的文章,然后最主要是,单片机IO口的输出电流太小,不到1mA,并不够点亮LED灯,LED灯的工作电流是3~10mA,因此接上一个上拉电阻。具体的原理我也不太清楚, 这个应该也可以搜到很多文章介绍。
4.数码管从0显示到F
现在我们来尝试让前4个数码管间隔1s显示0~F。
创建一个数组,来存放0到F的编码值,这里注意一点,我们用code来定义这个数组,使该数组成为一个编码表的格式。
注:单片机的存储空间是有限的,据郭天祥老师介绍,该型号的单片机的数据存储器(或者叫:随机存储器)只有128个字节(这个应该可以在芯片手册上了解到),数据存储器是何物呢,就是你程序运行中创建的变量都会存放到数据存储器中,因此我们在编程中常常要注意选择合适的数据类型,避免空间不够。所以我们创建一个存放16个8位的数组时会浪费很多空间(虽然在这里还是够的),而当我们在定义数组的时候添加上code,则会让数组的数据存储在程序存储器中,即最终存放在
hex文件中,51单片机支持的最大空间为4k,52单片机是8k,可以看到是后面的数字乘上4k,这个空间是足够的。
据此我们就可以编写程序了。
代码如下:
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
uchar code table[] = { //注意添加code
0x3f , 0x06 , 0x5b , 0x4f , 0x66 , 0x6d ,
0x7d , 0x07 , 0x7f , 0x6f , 0x77 , 0x7c ,
0x39 , 0x5e , 0x79 , 0x71 }; //编码表,存放使数码管显示0~F的编码值
void delay(uint z);
uchar num;
sbit wela = P2^7;//位选
sbit dula = P2^6;//段选
void main()
{
wela = 1;
P0 = 0xf0;//选择前4个数码管
wela = 0;
while(1)
{
for(num = 0;num < 16;num++)
{
dula = 1;
P0 = table[num];
dula = 0;
delay(1000);//让数码管亮一会儿
}
}
}
void delay(uint z) //延时函数,z的取值为这个函数的延时ms数,如delay(200);大约延时200ms.
{ //delay(500);大约延时500ms.
uint x,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--);
}
三、中断
中断系统是微处理器的重点,一定要熟练掌握。
1.中断的概念
CPU在处理某一事件A时,发生了另一事件B请求CPU迅速去处理(中断发生);
CPU暂时中断当前的工作,转去处理事件B(中断响应和中断服务);
待CPU将事件B处理完毕后,再回到原来事件A被中断的地方继续处理事件A(中断返回),这一过程称为中断 。
中断技术的好处:
1.解决了快速主机与慢速I/O设备的数据传送问题;
2.分时操作。CPU可以分时为多个I/O设备服务,提高了计算机的利用率;
3.实时响应。CPU能够及时处理应用系统的随机事件,系统的实时性大大增强;
4.可靠性高。CPU具有处理设备故障及掉电等突发性事件能力,从而使系统可靠性提高。
2.80C51中断系统的结构
80C51的中断系统有5个中断源(8052有6个),2个优先级,可实现二级中断嵌套 。
有中断请求标志位、触发方式控制位、中断允许位,优先级设定位,分别由不同的寄存器控制,我猜测每个型号的单片机可能略有不同,这些对应控制的寄存器可以在芯片手册上找到,我们不必去记住这些,我们要会的是如何根据这些内容去写一个属于自己的中断函数。
3.中断优先级的三条原则:
CPU同时接收到几个中断时,首先响应优先级别最高的中断请求。
正在进行的中断过程不能被新的同级或低优先级的中断请求所中断。
正在进行的低优先级中断服务,能被高优先级中断请求所中断。
4.编写一个中断源是外部中断的程序
中断响应的条件:
①中断源有中断请求;
②此中断源的中断允许位为1;
③CPU开中断(即EA=1)。
以上三条同时满足时,CPU才有可能响应中断。
根据以上条件,即可编写一个带有中断函数的程序。(以外部中断0为例,其他的中断源有其他的步骤需要补充,见其他文章其他部分)
步骤如下:(所有的寄存器可以在芯片手册找到)
1.首先是开CPU开中断(允许),即打开总中断,肯定要的一步,EA = 1;
2.开你所选择的中断源的中断允许位,例如现在我选择的中断源是外部中断0,则要写EX0 = 1;
3.设置触发方式(外部中断才会用到)
共有两种,对于外部中断0来说,一种是电平触发方式,选择此方式,则当P3^2口为低电平时,触发中断,执行中断函数,而选为下降沿触发方式,则当P3^2口经历一个下降沿时触发中断。这里我们令IT0 = 0(对寄存器某一位进行操作),选择电平触发方式。也可以写TCON = 0x00。(对寄存器直接操作)。
4.编写中断函数
注意:中断函数不需要声明
中断函数的有一个固定的模板,如下
void xxxxx() interrupt x
{
函数内容;
}
其中xxxxx是函数的名字,可以自己主观设置,也有一些约定俗成的,例如外部中断0就是exter0,定时器0就是timer0;
x则是中断序号,不同的中断源有不同的序号,例如外部中断0的序号是0,具体如下表
中断源 | 序号 |
---|---|
外部中断0 | 0 |
定时/计数器0 | 1 |
外部中断1 | 2 |
定时/计数器1 | 3 |
串行口 | 4 |
最终我们编写出一个程序,6个数码管间隔1s显示0~F,并且添加了中断功能,外部中断0,中断时(即当P3^2口输入低电平时),触发中断,使发光二极管D1亮起。
具体代码如下:
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
uchar code table[] = {
0x3f , 0x06 , 0x5b , 0x4f , 0x66 , 0x6d ,
0x7d , 0x07 , 0x7f , 0x6f , 0x77 , 0x7c ,
0x39 , 0x5e , 0x79 , 0x71 };
void delay(uint z);
uchar num;
sbit wela = P2^7;
sbit dula = P2^6;
sbit D1 = P1^0;
void main()
{
EA = 1; //第一步,开总中断
EX0 = 1; //第二步,开外部中断0
IT0 = 1; //第三步,设置触发方式
wela = 1;
P0 = 0xc0;
wela = 0;
while(1)
{
for(num = 0;num < 16;num++)
{
D1 = 1; //中断函数结束时,关闭LED灯
dula = 1;
P0 = table[num];
dula = 0;
delay(500);
}
}
}
void delay(uint z) //延时函数,z的取值为这个函数的延时ms数,如delay(200);大约延时200ms.
{ //delay(500);大约延时500ms.
uint x,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--);
}
void exter0() interrupt 0 //中断函数
{
D1 = 0; //函数的内容:点亮D1
}
四、定时器
实现定时功能,比较方便的方法是利用单片机内部的定时/计数器,也是我们这里要学的内容。还有下面这三种方法:
一种是软件定时,就是我们之前讲到的,自己创建一个delay函数,软件定时不占用硬件资源,即不需要占用单片机的IO口,但是它占用了CPU时间,降低了CPU的利用率。另一种是采用时基电路定时,即外接必要的元器件,构成硬件定时电路,但是硬件连接好以后,定时值与定时范围不能由软件进行控制和修改,即不可编程。还有一种是采用可编程芯片定时,可以用软件来确定和修改定时值和定时范围,这种芯片定时强,使用灵活,当单片机的定时/计数器不够用时,可以考虑进行扩展,在一些相对复杂的项目会用到,但是在本次课程学习中不会用到。
1.定时/计数器的结构
定时/计数器的实质是加1计数器(16位,方式1),由高8位和低8位两个寄存器组成。另外,有TMOD寄存器,是定时/计数器的工作方式寄存器,确定工作方式和功能;还有TCON是控制寄存器,控制T0、T1的启动和停止及设置溢出标志。
2.定时/计数器的工作原理
加1计数器输入的计数脉冲有两个来源:
一是由系统的时钟振荡器输出脉冲经12分频后送来,即每12个振荡周期(1个机器周期),计数值加1,这就是定时器的功能,需要重点掌握;
二是T0或T1引脚输入的外部脉冲源,来一个脉冲计数器加1,这是计数器的功能,这里不详细介绍。
当计数器全为1时,再来一个计数脉冲后,计数器回零,且计数器的溢出会使TCON寄存器中的TF0或TF1置1(看选用哪个定时器),同时向CPU发出中断请求(开定时/计数器中断的前提下)。
可见,溢出时计数器的值减去计数初值才是加1计数器的计数值。
这里包括下面均只介绍定时器模式。
从上述的定时计数方式可以知道,计数频率为晶振频率的1/12,且
计数值N*机器周期Tcy=定时时间t,公式如下:
其中,机器周期 = 振荡周期*12
例:假设实验板上的晶振频率是12MHz(虽然实际上是11.0592MHz,但是这里为了计算方便)
因为晶振频率是12MHz,所以振荡周期就是晶振频率的倒数,即1/12μs,机器周期就是1μs,即1次计数1μs。
因此若想定时时间t = 20ms,则
计数值N = 20ms/1μs = 20000。
3.定时/计数器的控制
(寄存器的使用不需要特别记住,用到再查芯片手册即可,这里主要是对其中的内容做一些介绍)
①工作方式寄存器TMOD
低四位用于T0,高四位用于T1
②控制寄存器TCON
TCON的低四位用于控制外部中断,这里就不介绍了
高4位用于控制定时/计数器的启动和中断申请。
4.定时/计数器的工作方式
有方式0、1、2、3共4种,这里只讲第二种,即方式1。
方式1的计数位数是16位,由TL0作为低8位、TH0作为高8位,组成了16位加1计数器 。
16位,所以计数范围从0~65535,最大计数个数是65536个,即2^16。
所以计数初值与计数值的关系如下:
其中X是计数初值,N是计数值。
5.编写一个定时器应用
同中断系统一样,我们需要学会的是如何去使用定时器功能,而不是去记忆寄存器。
首先需要介绍下,计数初值如何赋值到计数位上。
一种办法是直接算出十六进制表示,例如,我要设置计数初值为20000,利用计算器可以得到:
20000的十六进制表示是4E20,因此我要将4E赋值给TH0,20赋值给TL0
TH0 = 0x4E;
TL0 = 0x20;
另一种方法是在程序中去计算,即
TH0 = (65536 - N)/256;
TL0 = (65536 - N)%256;
其中计数值N根据自己的需要进行赋值。
现在我们来编写一个程序实现1s定时,使第1、3、5个数码管间隔1s显示0~F。
步骤如下:
1.设置定时器及工作方式
这里我们设置定时器0为工作方式1,对TMOD进行赋值,再回顾一遍该寄存器的内容
可以看到,GATE = 0即可,也要等于0,工作方式是方式1,所以M1M0 = 01,根据寄存器的内部格式,进行赋值,令TMOD = 0x01;
2.对计数初值赋值
这里我们要注意到,由于计数位只有16位,所以对于晶振频率为12MHz的板子来说,最大的计数值只有65536,即65ms左右就进入一次中断了,因此我们可以利用20*50ms = 1s来实现定时1s,即进入20次中断,每次定时只有50ms,通过一个临时变量来记录中断次数,每当达到20次中断时,再执行我们想要的操作。
3.开中断
开总中断以及中断源的中断(即定时器0的中断)
4.开始定时
通过控制寄存器TCON,来开始定时。
具体代码如下:
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
uchar code table[] = {
0x3f , 0x06 , 0x5b , 0x4f , 0x66 , 0x6d ,
0x7d , 0x07 , 0x7f , 0x6f , 0x77 , 0x7c ,
0x39 , 0x5e , 0x79 , 0x71 };
void delay(uint z);
uchar num,tt; //临时变量用来计数
sbit wela = P2^7;
sbit dula = P2^6;
sbit D1 = P1^0;
void main()
{
num = 0;
tt = 0;
TMOD = 0x01;//设置定时器0为工作方式1
TH0 = (65536-50000)/256; //计数值为50000,即50ms
TL0 = (65536-50000)%256;
EA = 1; //开总中断
ET0 = 1; //开定时器0中断
TR0 = 1; //启动定时器0
wela = 1;
P0 = 0xea;
wela = 0;
dula = 1;
P0 = 0x3f;
dula = 0;
while(1)
{
if(tt == 20) //每当经过20次中断,即1s
{
tt = 0; //重新记录中断次数
num++; //数码管的显示
if(num == 16)//防止显示内容异常
num = 0;
dula = 1;
P0 = table[num];
dula = 0;
}
}
}
void timer0() interrupt 1 //定时器0的中断序号为1
{
TH0 = (65536-50000)/256; //进入中断后,重新赋值,重新计数
TL0 = (65536-50000)%256;
tt++; //对中断次数计数
}
版权声明:本文为CSDN博主「林佳展」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_48021291/article/details/122628908
说明:此文章仅是我学习过程中的一些记录,如有侵权,请联系我删除,文章中难免有遗漏错误之处,欢迎指出。
目录
1.首先是开CPU开中断(允许),即打开总中断,肯定要的一步,EA = 1;
2.开你所选择的中断源的中断允许位,例如现在我选择的中断源是外部中断0,则要写EX0 = 1;
一、显示器介绍
单片机系统中常用的显示器有:
发光二极管LED(Light Emitting Diode)显示器、液晶LCD(Liquid Crystal Display)显示器、CRT显示器等。LED、LCD显示器有两种显示结构:段显示(7段、米字型等)和点阵显示(5×8、8×8点阵等)。
二、LED显示器
1.LED显示器介绍
LED显示器分为两种,分别是共阴极和共阳极。
共阴极 共阳极
使用LED显示器时,要注意区分这两种不同的接法。
为了显示数字或字符,必须对数字或字符进行编码。例如为了显示1,需要让b,c管点亮,以共阴极为例,需要b,c所亮的引脚输出高电平,其他的引脚输出低电平。
七段数码管加上一个小数点,共计8段。因此为LED显示器提供的编码正好是一个字节。本次实验板用共阴LED显示器,根据电路连接图显示16进制数的编码已列在下表。
2.LED数码显示方式及电路
LED显示器工作方式有两种:静态和动态。本文讲的是静态显示方法,动态的在下一篇讲解。
静态显示的特点是每个数码管的段选必须接一个8位数据线来保持显示的字形码。当送入一次字形码后,显示字形可一直保持,直到送入新字形码为止。这种方法的优点是占用CPU时间少,显示便于监测和控制。缺点是硬件电路比较复杂,成本较高。
先附上实验板上关于数码管的电路
分别是单片机的连接引脚图,以及与其相连的两个74HC573锁存器,下面是数码管的电路部分
可以看到每个LED数码管所有引脚几乎相同,唯有第8位不同,这是数码管的位选端。
已知本实验板所用的是共阴极数码管,因此想要LED数码管亮,需要数码管的输入是高电平,观察电路板可以知道,数码管的输入来自74HC573锁存器,而74HC573锁存器的输入来自单片机的P0口。因此我们所要做的,就是操作单片机的P0口。
这里介绍一下位选和段选,以及与之有关的电路引脚。(以共阴极为例)
位选:选择哪个数码管,即只要该数码管的位选引脚为低电平即可。因为该引脚即数码管的公共端,因为共阴极的公共端是地,所以该引脚需为地,而其他的引脚根据要显示的内容为高电平即可,即下面所说的段选。
段选:数码管显示哪个数字,根据数码管的电路原理图,对应的引脚的输入为高电平,即可显示对应的数字。
3.点亮第一个数码管
现在来具体介绍如何使该电路板上的数码管亮。
可以看到P0口同时连到了2个锁存器,一个用于段选(显示哪些数字),一个用于位选(哪个数码管显示),为了不产生干扰,必须应用锁存器的功能。还是看真值表。
这里对这个真值表做一些解释,读者们即可轻松理解。这里不需要使用到高阻态Z的功能,默认是低电平,事实上上电后也默认是低电平。
可以看到当LE是高电平且D也是高电平时,Q是高电平,而D是低电平时,则Q是低电平,即当LE是高电平时,锁存器是直通的,即D为什么值,Q为什么值,我们需要这个功能来改变Q的值。
而我们同时也注意到,当LE是低电平时,不管D是什么值,Q默认是Q0,即Q保持原来的值,即锁存。我们同样需要这个功能来保持住Q的值。
现在,我们来点亮第一个LED数码管,并且使其显示的数字为1。
其实总共就两步,先位选,再段选。
我们观察到位选的LE引脚连接的是单片机的P2^7脚,段选的则为P2^6脚,由于不能直接修改IO口的值,我们应该定义两个位变量来对应这两个脚。
sbit wela = P2^7;
sbit dula = P2^6;
然后是利用锁存器的功能,进行位选。
首先令wela = 1,即锁存器的LE = 1,根据真值表可以知道,此时D为什么值,Q就为什么值,因此我们对单片机的P0口进行赋值(因为P0口与D口直接相连),选择第1个数码管,即该数码管的位选引脚为低电平,即WE1 = 0,以十六进制进行赋值,即P0 = 0xfe,此时由于锁存器的直通功能,WE1是低电平,现在我们要令wela = 1,来保持住这个值。
然后就是段选了,
同样的步骤,dula = 1,
接着是对P0口进行赋值,参考编码表或直接看数码管原理图可以知道想要显示1,需要输出P0 = 0x06,
最后就是,dula = 0,编译运行上传到单片机开发板上,即可显示出结果。
具体代码如下:
#include <reg52.h>
#define uint unsigned int
sbit wela = P2^7;
sbit dula = P2^6;
void main()
{
wela = 1;
P0 = 0xfe;
wela = 0;
dula = 1;
P0 = 0x06;
dula = 0;
while(1); //让程序停在这里,让数码管一直亮着
}
补充:对于该型号的单片机,P0口没有内接上拉电阻,需要在电路中外接一个上拉电阻,即电路原理图中的P1,P1是一个排阻,具体电路可以上网搜索,也很简单。为什么需要一个上拉电阻呢,首先是点亮二极管的电路总要一个电阻吧,参见第一讲的文章,然后最主要是,单片机IO口的输出电流太小,不到1mA,并不够点亮LED灯,LED灯的工作电流是3~10mA,因此接上一个上拉电阻。具体的原理我也不太清楚, 这个应该也可以搜到很多文章介绍。
4.数码管从0显示到F
现在我们来尝试让前4个数码管间隔1s显示0~F。
创建一个数组,来存放0到F的编码值,这里注意一点,我们用code来定义这个数组,使该数组成为一个编码表的格式。
注:单片机的存储空间是有限的,据郭天祥老师介绍,该型号的单片机的数据存储器(或者叫:随机存储器)只有128个字节(这个应该可以在芯片手册上了解到),数据存储器是何物呢,就是你程序运行中创建的变量都会存放到数据存储器中,因此我们在编程中常常要注意选择合适的数据类型,避免空间不够。所以我们创建一个存放16个8位的数组时会浪费很多空间(虽然在这里还是够的),而当我们在定义数组的时候添加上code,则会让数组的数据存储在程序存储器中,即最终存放在
hex文件中,51单片机支持的最大空间为4k,52单片机是8k,可以看到是后面的数字乘上4k,这个空间是足够的。
据此我们就可以编写程序了。
代码如下:
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
uchar code table[] = { //注意添加code
0x3f , 0x06 , 0x5b , 0x4f , 0x66 , 0x6d ,
0x7d , 0x07 , 0x7f , 0x6f , 0x77 , 0x7c ,
0x39 , 0x5e , 0x79 , 0x71 }; //编码表,存放使数码管显示0~F的编码值
void delay(uint z);
uchar num;
sbit wela = P2^7;//位选
sbit dula = P2^6;//段选
void main()
{
wela = 1;
P0 = 0xf0;//选择前4个数码管
wela = 0;
while(1)
{
for(num = 0;num < 16;num++)
{
dula = 1;
P0 = table[num];
dula = 0;
delay(1000);//让数码管亮一会儿
}
}
}
void delay(uint z) //延时函数,z的取值为这个函数的延时ms数,如delay(200);大约延时200ms.
{ //delay(500);大约延时500ms.
uint x,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--);
}
三、中断
中断系统是微处理器的重点,一定要熟练掌握。
1.中断的概念
CPU在处理某一事件A时,发生了另一事件B请求CPU迅速去处理(中断发生);
CPU暂时中断当前的工作,转去处理事件B(中断响应和中断服务);
待CPU将事件B处理完毕后,再回到原来事件A被中断的地方继续处理事件A(中断返回),这一过程称为中断 。
中断技术的好处:
1.解决了快速主机与慢速I/O设备的数据传送问题;
2.分时操作。CPU可以分时为多个I/O设备服务,提高了计算机的利用率;
3.实时响应。CPU能够及时处理应用系统的随机事件,系统的实时性大大增强;
4.可靠性高。CPU具有处理设备故障及掉电等突发性事件能力,从而使系统可靠性提高。
2.80C51中断系统的结构
80C51的中断系统有5个中断源(8052有6个),2个优先级,可实现二级中断嵌套 。
有中断请求标志位、触发方式控制位、中断允许位,优先级设定位,分别由不同的寄存器控制,我猜测每个型号的单片机可能略有不同,这些对应控制的寄存器可以在芯片手册上找到,我们不必去记住这些,我们要会的是如何根据这些内容去写一个属于自己的中断函数。
3.中断优先级的三条原则:
CPU同时接收到几个中断时,首先响应优先级别最高的中断请求。
正在进行的中断过程不能被新的同级或低优先级的中断请求所中断。
正在进行的低优先级中断服务,能被高优先级中断请求所中断。
4.编写一个中断源是外部中断的程序
中断响应的条件:
①中断源有中断请求;
②此中断源的中断允许位为1;
③CPU开中断(即EA=1)。
以上三条同时满足时,CPU才有可能响应中断。
根据以上条件,即可编写一个带有中断函数的程序。(以外部中断0为例,其他的中断源有其他的步骤需要补充,见其他文章其他部分)
步骤如下:(所有的寄存器可以在芯片手册找到)
1.首先是开CPU开中断(允许),即打开总中断,肯定要的一步,EA = 1;
2.开你所选择的中断源的中断允许位,例如现在我选择的中断源是外部中断0,则要写EX0 = 1;
3.设置触发方式(外部中断才会用到)
共有两种,对于外部中断0来说,一种是电平触发方式,选择此方式,则当P3^2口为低电平时,触发中断,执行中断函数,而选为下降沿触发方式,则当P3^2口经历一个下降沿时触发中断。这里我们令IT0 = 0(对寄存器某一位进行操作),选择电平触发方式。也可以写TCON = 0x00。(对寄存器直接操作)。
4.编写中断函数
注意:中断函数不需要声明
中断函数的有一个固定的模板,如下
void xxxxx() interrupt x
{
函数内容;
}
其中xxxxx是函数的名字,可以自己主观设置,也有一些约定俗成的,例如外部中断0就是exter0,定时器0就是timer0;
x则是中断序号,不同的中断源有不同的序号,例如外部中断0的序号是0,具体如下表
中断源 | 序号 |
---|---|
外部中断0 | 0 |
定时/计数器0 | 1 |
外部中断1 | 2 |
定时/计数器1 | 3 |
串行口 | 4 |
最终我们编写出一个程序,6个数码管间隔1s显示0~F,并且添加了中断功能,外部中断0,中断时(即当P3^2口输入低电平时),触发中断,使发光二极管D1亮起。
具体代码如下:
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
uchar code table[] = {
0x3f , 0x06 , 0x5b , 0x4f , 0x66 , 0x6d ,
0x7d , 0x07 , 0x7f , 0x6f , 0x77 , 0x7c ,
0x39 , 0x5e , 0x79 , 0x71 };
void delay(uint z);
uchar num;
sbit wela = P2^7;
sbit dula = P2^6;
sbit D1 = P1^0;
void main()
{
EA = 1; //第一步,开总中断
EX0 = 1; //第二步,开外部中断0
IT0 = 1; //第三步,设置触发方式
wela = 1;
P0 = 0xc0;
wela = 0;
while(1)
{
for(num = 0;num < 16;num++)
{
D1 = 1; //中断函数结束时,关闭LED灯
dula = 1;
P0 = table[num];
dula = 0;
delay(500);
}
}
}
void delay(uint z) //延时函数,z的取值为这个函数的延时ms数,如delay(200);大约延时200ms.
{ //delay(500);大约延时500ms.
uint x,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--);
}
void exter0() interrupt 0 //中断函数
{
D1 = 0; //函数的内容:点亮D1
}
四、定时器
实现定时功能,比较方便的方法是利用单片机内部的定时/计数器,也是我们这里要学的内容。还有下面这三种方法:
一种是软件定时,就是我们之前讲到的,自己创建一个delay函数,软件定时不占用硬件资源,即不需要占用单片机的IO口,但是它占用了CPU时间,降低了CPU的利用率。另一种是采用时基电路定时,即外接必要的元器件,构成硬件定时电路,但是硬件连接好以后,定时值与定时范围不能由软件进行控制和修改,即不可编程。还有一种是采用可编程芯片定时,可以用软件来确定和修改定时值和定时范围,这种芯片定时强,使用灵活,当单片机的定时/计数器不够用时,可以考虑进行扩展,在一些相对复杂的项目会用到,但是在本次课程学习中不会用到。
1.定时/计数器的结构
定时/计数器的实质是加1计数器(16位,方式1),由高8位和低8位两个寄存器组成。另外,有TMOD寄存器,是定时/计数器的工作方式寄存器,确定工作方式和功能;还有TCON是控制寄存器,控制T0、T1的启动和停止及设置溢出标志。
2.定时/计数器的工作原理
加1计数器输入的计数脉冲有两个来源:
一是由系统的时钟振荡器输出脉冲经12分频后送来,即每12个振荡周期(1个机器周期),计数值加1,这就是定时器的功能,需要重点掌握;
二是T0或T1引脚输入的外部脉冲源,来一个脉冲计数器加1,这是计数器的功能,这里不详细介绍。
当计数器全为1时,再来一个计数脉冲后,计数器回零,且计数器的溢出会使TCON寄存器中的TF0或TF1置1(看选用哪个定时器),同时向CPU发出中断请求(开定时/计数器中断的前提下)。
可见,溢出时计数器的值减去计数初值才是加1计数器的计数值。
这里包括下面均只介绍定时器模式。
从上述的定时计数方式可以知道,计数频率为晶振频率的1/12,且
计数值N*机器周期Tcy=定时时间t,公式如下:
其中,机器周期 = 振荡周期*12
例:假设实验板上的晶振频率是12MHz(虽然实际上是11.0592MHz,但是这里为了计算方便)
因为晶振频率是12MHz,所以振荡周期就是晶振频率的倒数,即1/12μs,机器周期就是1μs,即1次计数1μs。
因此若想定时时间t = 20ms,则
计数值N = 20ms/1μs = 20000。
3.定时/计数器的控制
(寄存器的使用不需要特别记住,用到再查芯片手册即可,这里主要是对其中的内容做一些介绍)
①工作方式寄存器TMOD
低四位用于T0,高四位用于T1
②控制寄存器TCON
TCON的低四位用于控制外部中断,这里就不介绍了
高4位用于控制定时/计数器的启动和中断申请。
4.定时/计数器的工作方式
有方式0、1、2、3共4种,这里只讲第二种,即方式1。
方式1的计数位数是16位,由TL0作为低8位、TH0作为高8位,组成了16位加1计数器 。
16位,所以计数范围从0~65535,最大计数个数是65536个,即2^16。
所以计数初值与计数值的关系如下:
其中X是计数初值,N是计数值。
5.编写一个定时器应用
同中断系统一样,我们需要学会的是如何去使用定时器功能,而不是去记忆寄存器。
首先需要介绍下,计数初值如何赋值到计数位上。
一种办法是直接算出十六进制表示,例如,我要设置计数初值为20000,利用计算器可以得到:
20000的十六进制表示是4E20,因此我要将4E赋值给TH0,20赋值给TL0
TH0 = 0x4E;
TL0 = 0x20;
另一种方法是在程序中去计算,即
TH0 = (65536 - N)/256;
TL0 = (65536 - N)%256;
其中计数值N根据自己的需要进行赋值。
现在我们来编写一个程序实现1s定时,使第1、3、5个数码管间隔1s显示0~F。
步骤如下:
1.设置定时器及工作方式
这里我们设置定时器0为工作方式1,对TMOD进行赋值,再回顾一遍该寄存器的内容
可以看到,GATE = 0即可,也要等于0,工作方式是方式1,所以M1M0 = 01,根据寄存器的内部格式,进行赋值,令TMOD = 0x01;
2.对计数初值赋值
这里我们要注意到,由于计数位只有16位,所以对于晶振频率为12MHz的板子来说,最大的计数值只有65536,即65ms左右就进入一次中断了,因此我们可以利用20*50ms = 1s来实现定时1s,即进入20次中断,每次定时只有50ms,通过一个临时变量来记录中断次数,每当达到20次中断时,再执行我们想要的操作。
3.开中断
开总中断以及中断源的中断(即定时器0的中断)
4.开始定时
通过控制寄存器TCON,来开始定时。
具体代码如下:
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
uchar code table[] = {
0x3f , 0x06 , 0x5b , 0x4f , 0x66 , 0x6d ,
0x7d , 0x07 , 0x7f , 0x6f , 0x77 , 0x7c ,
0x39 , 0x5e , 0x79 , 0x71 };
void delay(uint z);
uchar num,tt; //临时变量用来计数
sbit wela = P2^7;
sbit dula = P2^6;
sbit D1 = P1^0;
void main()
{
num = 0;
tt = 0;
TMOD = 0x01;//设置定时器0为工作方式1
TH0 = (65536-50000)/256; //计数值为50000,即50ms
TL0 = (65536-50000)%256;
EA = 1; //开总中断
ET0 = 1; //开定时器0中断
TR0 = 1; //启动定时器0
wela = 1;
P0 = 0xea;
wela = 0;
dula = 1;
P0 = 0x3f;
dula = 0;
while(1)
{
if(tt == 20) //每当经过20次中断,即1s
{
tt = 0; //重新记录中断次数
num++; //数码管的显示
if(num == 16)//防止显示内容异常
num = 0;
dula = 1;
P0 = table[num];
dula = 0;
}
}
}
void timer0() interrupt 1 //定时器0的中断序号为1
{
TH0 = (65536-50000)/256; //进入中断后,重新赋值,重新计数
TL0 = (65536-50000)%256;
tt++; //对中断次数计数
}
版权声明:本文为CSDN博主「林佳展」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_48021291/article/details/122628908
暂无评论