锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

STC51入门笔记(郭天祥C语言)---第三节:数码管显示原理及应用实现

时间:2023-09-18 08:07:00 t08lc二极管

这里是引用

声明:本文只是对个人知识盲点、知识弱点和关键部分的总结。我希望你不喜欢它。梳理顺序是根据书的实际顺序进行梳理。请注明转载的来源。


作者:sumjess


一、数字管显示原理

先来看几张数码管的图片, 左图为单位数码管,中图为双位数码管,右图为四位数码管。 还有右下角没有点的数码管,最右边还有米字数码管等。

在这里1图片描述
无论连接多少数字管,数字管的显示原理都是一样的,都是通过点亮内部的发光二极管来发光的。让我们解释一个数字管是如何点亮的。数码管内部电路如图3所示.1.4所示,从图3.1.4(a)可以看出,数字管引脚是10,显示8字需要7个8字需要7个小段,还有一个小数点,所以里面有8个小发光二极管,最后还有一个公共端。为了统一包装,制造商在单位数字管中包装了10个引脚,其中第三一起。它们的公共端可以分为共阳极和共阴极,图3.1.4(b)图3为共阴极内部原理图.1.4?共阳极内部原理图。

对于共阴极数码,它的个发光二极管的阴极都连接在数字管中,因此被称为共阴,其阳极是独立的,通常在设计电路时接地阴极。当我们给数码管的任何阳极加一个高电平时,相应的发光二极管就会点亮。如果想要显示出一个8字,并且把右下角的小数点也点亮的话,可以给8个阳极全部送高电平,如果想让它显示出一个0字,那么我们可以除了给第"g,dp"这两个人送低电平,其余的引脚都送高电平,这样显示0字。如果你想让它显示几个,给相应的发光二极管一个高电平,所以当我们显示数字时,我们做的第一件事就是给0~90个数字编码,直接把这个编码送到字时,直接将这个编码发送到它的阳极。
共阳极数码管内部8个发光二极管的所有阳极都连接在一起。当电路连接时,公共端连接高电平。因此,如果我们想点亮发光二极管,我们需要给阴极低电平。此时,显示的数字编码与共阳极编码相反。当数字管内部发光二极管点亮时,也需要5mA以上电流,电流不宜过大,否则会烧坏发光二极管。因为单片机I/O因此,当数码管与单片机连接时,需要增加驱动电路,可采用上拉电阻法或专用数码管驱动
芯片,TX-lC实验板使用74HC573定器输出电流大,电路接口简单,可借鉴。
数位一体、四位一体的数码管,当多位一体时,它们的内部公共端是独立的,负责显示的所有数字段都连接在一起。独立的公共端可以控制多位一体中哪个数字管点亮,连接在一起的段可以控制可以点亮数字管点亮的数字。通常我们称公共端为选线,连接在一起的段称为选线。有了这两条线,任何数字管都可以通过单片机和外部驱动电路显示任何数字。
一般单位数码管有10个引脚,二位数码管也有10个引脚,四位数码管有12个引脚,关千具体引脚及段、位标号可查询相关资料,最简单的方法是用数字万用表测量。如果没有数字万用表,也可以用5V直流电源串联1KΩ测量电阻后,记录测量结果,通过统计绘制引脚标号。

知识点:如何用万用表检测数字管的引脚排列

对于数字万用表,红色表笔连接表内部电池正极,黑色表笔连接表内部电池负极。当数字万用表放置在二极管块中时,两个表笔之间的开路电压约为1.5V,在发光二极管两端正确添加两表笔时,可以点亮发光二极管。
如下图所示,将数字万用表放在二极管挡上,红表笔连接①然后用黑表笔接触其他引脚,假设只有当接触到时⑨脚时,数字管a段发光,接触其他引脚时不发光。由此可见,被测数码管是共阴极结构类型,⑨脚是公共阴极,①脚是数码管的a段。接下来,检测各段引脚,仍然使用数字万用表二极管挡,固定黑表笔⑨脚,用红表笔依次接触②~⑩引脚时,数码管的其他部分分别发光,可绘制数码管的内部结构和引脚布置图。


在测试过程中,如果测量的数字管是共阳极类型,则需要调整红色和黑色来测量上述结果。在判断结构类型时,应灵活掌握操作,反复测试,直到找到公共端,只要我们理解原则, 检测每个引脚都不是问题。

二、数字管静态显示

当多个数字管应用于某个系统时,它们的位选可以独立控制,段选连接在一起。我们可以通过位选信号控制哪些数字管亮度。同时,所有位选数字管上显示的数字总是一样的,因为它们的段选是连接在一起的,所以发送到所有数字管的段选信号是相同的,所以它们必须显示相同的数字,这种显示方法称为静态显示。
TX- IC 单片机实验板上数码管与单片机的连接如图所示 3.2.1 所示。

原理图中标号相同的节点在查看原理图时,物理电气在实际电路中相连。为了使原理图看起来简洁整洁,我们通常在绘制原理图时使用相同的
标签来表示电气连接,以免使用连接使电路令人眼花缭乱。TX-lC图3中使用的数字管是共阴极.2.1.顶排是6个单位的数字管,可以看到所有数字管的阳极,即标有a,b,c,d,e,f,g,h所有的引脚都连接在一起,然后与下面的引脚连接U1元件73HC573锁存器的数据输出端相连,锁存器的数据输入端与单片机相连P0口,P0口同时增加上拉电阻。数码管中WEl,WE2,WE3,WE4,WE5,WE6是它们的位选端,每个数字对应一个位选端,与以下数字对应U2元件74HC数据输出端573的低6位相连,U2的数据输入端也连接到单片机P0口。两个锁定器的锁定端分别与单片机P2.6和P2.7相连,锁存器原理我们在前面已经讲过,因为用单片机可以控制锁存器的锁存端,进而控制锁存器的数据输出,这种分时控制的方法便可方便地控制任意数码管显示任意数字。
让我们用C语言写一个程序,让第一个数字管显示一个8字。 分析如下:让第一个数码管显示8字,然后关闭其他数码管的位选,即只打开第一个数码管的位选。我们先给操作U2锁定器的锁定端具有高电平,然后从单片机中提取数据P0直接送出锁存器U然后关闭2的数据输出端U2锁存端。由于数字管是共阴极,选择通常为低电平,选择关闭时为高电平,即只有W一端对应数据为0,其他数据为1,因此P.口要输出的数据为0xFE(二进制为11111110)选择确定后,再确定段选,显示8,所以只有h段为0,其余段为1,然后使用操作U给出相同的方法U1数据输出端发送0x7F(二进制为01111111)
【例3.2.1】新建文件part2.l_l.c, 程序代码如下:

#include          //52系列单片机头文件 sbit dula = P2^6;     //申明U锁定器的锁定端 sbit wela = P2^7;	   //申明U2锁存器的锁存端
void main()			  //主函数
{
    wela=1;	      //打开U2锁存器
    P0=0xFE	;	      //送入位选信号
    wela=0;		  //关闭U2锁存器

    dula=1;	      //打开U1锁存器
    P0=0x7F	;	      //送入段选信号
    dula=0;		  //关闭U1锁存器
	while(1);		  //程序停止到这里
  }				

       将代码编译下载到实验板后, 显 示效果如图 3.2.2 所示。
       从例 3.2.1中程序和实际显示效果我们进一步认识到, U1锁存器控制数码管显示内容,U2锁存器控制哪一位数码管显示。

/-----------------------------------共阳极如下--------------------------------------/

#include          //52系列单片机头文件
sbit dula = P2^5;		   //申明U1锁存器的锁存端
sbit wela = P2^6;		   //申明U2锁存器的锁存端
void main()			  //主函数
{
    wela=1;	      //打开U2锁存器
    P0=0x01	;	      //送入位选信号
    wela=0;		  //关闭U2锁存器

    dula=1;	      //打开U1锁存器
    P0=0x80	;	      //送入段选信号
    dula=0;		  //关闭U1锁存器
	while(1);		  //程序停止到这里
  }				

       下面介绍一种编码方法,在数码管显示数字时通常都要用到这种编码方法,刚才显示的数字是8,我们给U1送入的数据是0x7f,这是我们根据实际电路图自己给出的编码,不同的电路,编码可能不同, 共阳极的编码与共阴极的编码也是不同的,因此大家一定要掌握编码原理,也就是要明白数码管显示的原理。根据本课程电路图,我们将0—F如表3.2.1所示进行编码。

在用C 语言编程时, 编码定义方法如下:
unsigned char code table[]={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71};
       编码定义方法与 C 语言中的数组定义方法非常相似,不同的地方就是在数组类型后面多了一个 code关键字, code即表示编码的意思。需要注意的是, 单片机C语言中定义数组时是占用内存空间的,而定义编码时是直接分配到程序空间中,编译后编码占用的是程序存储空间,而非内存空间。
       上面unsigned char是数组类型, 也就是数组中元素变量类型, table 是数组名, 我们可以自由定义它,但是不要和关键字重名; table 后面必须加中括号[]中括号内部要注明当前数组内的元素个数, 也可不注明, C51 编译器在编编译时能够自动计算出来, 通常我们不注明。等号右边用一个大括号包含所有元素, 大括号后面加一个分号,大括号内部元素与元素之间用逗号隔开,注意,最后一个元素后面不要加逗号。
       调用数组方法如下:
              P0=table[3];
       即将 table 这个数组中的第 4 个元素直接赋给P0 口 , 即
              P0=0x66;
       需要注意的是, 在调用数组时, table 后面中括号里的数字是从 0 开始的,对应后面大括号里的第 1 个元素。有了这种编码方法, 我们在写数码管显示程序时就会方便很多。
       【例3.2.2】下面我们结合前一章讲过的延时程序, 实现这样一个功能, 让实验板上 6 个数码管同时点亮, 依次显示0 到F, 时间间隔为 0.5s, 循环下去。新建文件Sumjess2_2.c, 程序代码如下:

#include          //52系列单片机头文件
#define uchar unsigned char
#define uint unsigned int
sbit dula = P2^6;		   //申明U1锁存器的锁存端
sbit wela = P2^7;		   //申明U2锁存器的锁存端
uchar num;
uchar code table[]={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71};
void delayms(uint xms);
void main()			  //主函数
{
    wela=1;	      //打开U2锁存器
    P0=0xc0	;	  //送入位选信号
    wela=0;		  //关闭U2锁存器
	while(1){
	for(num=0;num<16;num++){
		dula=1;
		P0=table[num];
		dula=0;
		delayms(500);
		}
	}

}
void delayms(uint xms)
{
	uint i,j;
	for(i=xms;i>0;i--)
		for(j=110;j>0;j--);
	}

/-----------------------------------共阳极如下--------------------------------------/

#include          //52系列单片机头文件
#define uchar unsigned char
#define uint unsigned int
sbit dula = P2^5;		   //申明U1锁存器的锁存端
sbit wela = P2^6;		   //申明U2锁存器的锁存端
uchar num;
uchar code table[]={
0xC0, /*/"0"*/
0xF9, /*/"1"*/
0xA4, /*/"2"*/
0xB0, /*/"3"*/
0x99, /*/"4"*/
0x92, /*/"5"*/
0x82, /*/"6"*/
0xF8, /*/"7"*/
0x80, /*/"8"*/
0x90, /*/"9"*/
0x88, /*/"A"*/
0x83, /*/"B"*/
0xC6, /*/"C"*/
0xA1, /*/"D"*/
0x86, /*/"E"*/
0x8E, /*/"F"*/
0x89, /*/"H"*/
0xC7, /*/"L"*/
0xC8, /*/"n"*/
0xC1, /*/"u"*/
0x8C, /*/"P"*/
0xA3, /*/"o"*/
0xBF, /*/"-"*/
0xFF, /*/熄灭*/
0xFF /*/自定义*/
};
void delayms(uint xms);
void main()			  //主函数
{
    wela=1;	      //打开U2锁存器
    P0=0x3f	;	  //送入位选信号
    wela=0;		  //关闭U2锁存器
	while(1){
	for(num=0;num<16;num++){
		dula=1;
		P0=table[num];
		dula=0;
		delayms(500);
		}
	}

}
void delayms(uint xms)
{
	uint i,j;
	for(i=xms;i>0;i--)
		for(j=110;j>0;j--);
	}

       将代码编译下载到实验板后, 可看到 6 个数码管上的数字依次从 0~F 变换显示, 实际显示效果如下图所示。

       例 3.2.2 中程序有两个方面需要注意:
       第一, 在刚进入主函数后, 执行了一次位选锁存命令, 然后接着便进入了大循环。因为我们题目的意思是让 6 个数码管同时显示, 所以位选命令只执行一次, 将所有数码管位选全部选中, 也就是同时开启所有数码管显示, 然后再不需要操作位选。
       第二, 在 while(l )大循环中, 使用了一个 for 循环语句, 原来我们仅用 for 做延时, 而在这里我们首次遇见用 for 语句来实现一个规定数目的有限循环, 大家要掌握它的用法, 以后会经常用到。

三、数码管动态显示

       数码管的动态显示又叫做数码管的动态扫描显示,为了能让大家更容易地理解数码管动态扫描的概念, 我们先来看例程, 通过感观认识, 再加上理论分析, 大家很容易就可掌握。
       【例3.3.1】在TX-IC 实验板上实现如下现象: 第一个数码管显示 1, 时间为 0.5s , 然后关闭它, 立即让第二个数码管显示 2, 时间为 0.5s , 再关闭它……一直到最后一个数码管显示6, 时间同样为 0.5s, 关闭它后再回来显示第一个数码管,一直循环下去。新建文件 Sumjess2_3.c,
程序代码如下:

#include          //52系列单片机头文件
#define uchar unsigned char
#define uint unsigned int
sbit dula = P2^6;		   //申明U1锁存器的锁存端
sbit wela = P2^7;		   //申明U2锁存器的锁存端
uchar num;
uchar code table[]={
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"*/
0x89, /*/"H"*/
};
void delayms(uint xms);
void main()			  //主函数
{ 
    while(1){
	dula=1;
	P0=table[1];  //送段选数据
	dula=0;
	
	P0=0xff	;	  //送位选数据前关闭所有显示,防止打开位选锁存时
	
    wela=1;	      //原来段选数据通过位选锁存器造成混乱
    P0=0xfe	;	  //送位选数据
    wela=0;		  //关闭U2锁存器
	delayms(500); //延时

	dula=1;
	P0=table[2];  //送段选数据
	dula=0;
	
	P0=0xff	;	  //送位选数据前关闭所有显示,防止打开位选锁存时
	
    wela=1;	      //原来段选数据通过位选锁存器造成混乱
    P0=0xfd	;	  //送位选数据
    wela=0;		  //关闭U2锁存器
	delayms(500); //延时
		
	dula=1;
	P0=table[3];  //送段选数据
	dula=0;
	
	P0=0xff	;	  //送位选数据前关闭所有显示,防止打开位选锁存时
	
    wela=1;	      //原来段选数据通过位选锁存器造成混乱
    P0=0xfd	;	  //送位选数据
    wela=0;		  //关闭U2锁存器
	delayms(500); //延时

	dula=1;
	P0=table[4];  //送段选数据
	dula=0;
	
	P0=0xff	;	  //送位选数据前关闭所有显示,防止打开位选锁存时
	
    wela=1;	      //原来段选数据通过位选锁存器造成混乱
    P0=0xf7	;	  //送位选数据
    wela=0;		  //关闭U2锁存器
	delayms(500); //延时

	dula=1;
	P0=table[5];  //送段选数据
	dula=0;
	
	P0=0xff	;	  //送位选数据前关闭所有显示,防止打开位选锁存时
	
    wela=1;	      //原来段选数据通过位选锁存器造成混乱
    P0=0xef	;	  //送位选数据
    wela=0;		  //关闭U2锁存器
	delayms(500); //延时

	dula=1;
	P0=table[6];  //送段选数据
	dula=0;
	
	P0=0xff	;	  //送位选数据前关闭所有显示,防止打开位选锁存时
	
    wela=1;	      //原来段选数据通过位选锁存器造成混乱
    P0=0xdf	;	  //送位选数据
    wela=0;		  //关闭U2锁存器
	delayms(500); //延时
	}

}
void delayms(uint xms)
{
	uint i,j;
	for(i=xms;i>0;i--)
		for(j=110;j>0;j--);
	}


/-----------------------------------共阳极如下--------------------------------------/

#include          //52系列单片机头文件
#define uchar unsigned char
#define uint unsigned int
sbit dula = P2^5;		   //申明U1锁存器的锁存端
sbit wela = P2^6;		   //申明U2锁存器的锁存端
uchar num;
uchar code table[]={
0xC0, /*/"0"*/
0xF9, /*/"1"*/
0xA4, /*/"2"*/
0xB0, /*/"3"*/
0x99, /*/"4"*/
0x92, /*/"5"*/
0x82, /*/"6"*/
0xF8, /*/"7"*/
0x80, /*/"8"*/
0x90, /*/"9"*/
0x88, /*/"A"*/
0x83, /*/"B"*/
0xC6, /*/"C"*/
0xA1, /*/"D"*/
0x86, /*/"E"*/
0x8E, /*/"F"*/
0x89, /*/"H"*/
0xC7, /*/"L"*/
0xC8, /*/"n"*/
0xC1, /*/"u"*/
0x8C, /*/"P"*/
0xA3, /*/"o"*/
0xBF, /*/"-"*/
0xFF, /*/熄灭*/
0xFF /*/自定义*/
};
void delayms(uint xms);
void main()			  //主函数
{
while(1){
	dula=1;
	P0=table[1];  //送段选数据
	dula=0;
	
	P0=0xff	;	  //送位选数据前关闭所有显示,防止打开位选锁存时
	
    wela=1;	      //原来段选数据通过位选锁存器造成混乱
    P0=0x01	;	  //送位选数据
    wela=0;		  //关闭U2锁存器
	delayms(500); //延时

	dula=1;
	P0=table[2];  //送段选数据
	dula=0;
	
	P0=0xff	;	  //送位选数据前关闭所有显示,防止打开位选锁存时
	
    wela=1;	      //原来段选数据通过位选锁存器造成混乱
    P0=0x02	;	  //送位选数据
    wela=0;		  //关闭U2锁存器
	delayms(500); //延时
		
	dula=1;
	P0=table[3];  //送段选数据
	dula=0;
	
	P0=0xff	;	  //送位选数据前关闭所有显示,防止打开位选锁存时
	
    wela=1;	      //原来段选数据通过位选锁存器造成混乱
    P0=0x04	;	  //送位选数据
    wela=0;		  //关闭U2锁存器
	delayms(500); //延时

	dula=1;
	P0=table[4];  //送段选数据
	dula=0;
	
	P0=0xff	;	  //送位选数据前关闭所有显示,防止打开位选锁存时
	
    wela=1;	      //原来段选数据通过位选锁存器造成混乱
    P0=0x08	;	  //送位选数据
    wela=0;		  //关闭U2锁存器
	delayms(500); //延时

	dula=1;
	P0=table[5];  //送段选数据
	dula=0;
	
	P0=0xff	;	  //送位选数据前关闭所有显示,防止打开位选锁存时
	
    wela=1;	      //原来段选数据通过位选锁存器造成混乱
    P0=0x10	;	  //送位选数据
    wela=0;		  //关闭U2锁存器
	delayms(500); //延时

	dula=1;
	P0=table[6];  //送段选数据
	dula=0;
	
	P0=0xff	;	  //送位选数据前关闭所有显示,防止打开位选锁存时
	
    wela=1;	      //原来段选数据通过位选锁存器造成混乱
    P0=0x20	;	  //送位选数据
    wela=0;		  //关闭U2锁存器
	delayms(500); //延时
	}
}
void delayms(uint xms)
{
	uint i,j;
	for(i=xms;i>0;i--)
		for(j=110;j>0;j--);
	}

       例3.3.1程序中需要注意的是:在每次送完段选数据后,在送入位选数据之前,需要加上一句"P0=0xff;",这条语句的专业名称叫做“消影”。解释如下:在刚送完段选数据后,P0口仍然保持着上次的段选数据,若不加"P0=0xff;"再执行接下来的打开位选锁存器命令后,原来保持在P0口的段选数据将立即通过位选锁存器直接加在数码管上,接下来才是再次通过P0口给位选锁存器送入位选数据,虽然这个过程非常短暂,但是在数码管高速显示状态下,我们仍然可以看见数码管出现显示混乱的现象,加上“消影”后,在开启位选锁存器后,P0口数据全为高电平,所以哪个数码管都不会亮,因此这个“消影“动作是很重要的。
       编译代码下载程序后观察,上面的代码实现了题目的要求,但还没有体现出来我们本节的重点,下面将每个数码管点亮的时间缩短到l00ms,编译下载,可看见数码管变换显示的速度快多了;我们再缩短至10ms,编译下载,此时已经可隐约看见6个数码管上同时显示着数字123456字样,但是看上去有些晃眼;我们再缩短至lms,编译下载,这时6个数码管上非常稳定、清晰地显示着123456字样,如下下图所示。


       到这里,相信大家已经很清楚数码管动态扫描显示的概念和实现原理,所谓动态扫描显示,即轮流向各位数码管送出字形码和相应的位选,利用发光管的余辉和人眼视觉暂留作 用,使人的感觉好像各位数码管同时都在显示, 而实际上多位数码管是一位一 位轮流显示的, 只 是轮流的速度非常快, 人眼已经无法分辨出来。

四、中断概念

       中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的,中断功能的存在,很大程度上提高了单片机处理外部或内部事件的能力。它也是单片机最重要的功能之一,是我们学习单片机必须要掌握的。很多初学者被困在中断中,学了很久仍然不知道中断究竟是个什么东西,大家千万不要认为它有多难,其实只要掌握正确的学习方法,没有哪个知识点是学不会的。
       51单片机内部一共有5个中断源,也就是说,有5种情况发生时,会使单片机去处理中断程序。在本章我们只讲解其中的一种中断情况——定时器中断,只要大家从理论和实践中真正明白了中断的概念,其他几种情况便能轻松掌握。
       为了能让大家更容易理解中断概念,我们先来举一个生活事例:你打开火,烧上一壶水,然后去洗衣服,在洗衣服的过程中,突然听到水壶发出水开的报警声,这时,你停止洗衣服动作,立即去关掉火,然后将开水灌入暖水瓶中,灌完开水后,你又回去继续洗衣服。这个过程中实际上就发生了一次中断,其流程图如图3.4.1所示。
       对于单片机来讲,中断是指CPU在处理某一事件A时,发生了另一事件B,请求CPU迅速去处理(中断发生);CPU暂时停止当前的工作(中断响应),转去处理事件B(中断服务);待CPU将事件B处理完毕后,再回到原来事件A被中断的地方继续处理事件A(中断返回),这一过程称为中断,其流程图如图3.4.2所示。

       再回来看前面讲的生活事例,与单片机中断结合分析,你的主任务是洗衣服,水开报警 这是一个中断请求, 这一时刻相当于断点处, 你响应中断去关火, 然后将开水灌入暖水瓶中, 这 一动作实际上就是处理中断程序, 灌完开水后再回去继续洗衣服, 相当千处理完中断程序后再返回主程序继续执行主程序。这里需要注意的是,水开是随时都有可能的,但是无论什么时候开,只 要一开你将立即去处理它,处理完后再回来继续接着洗刚才那件衣服。单片机在执行程序时,中断也随时有可能发生,但无论何时发生,只要一旦发生,单片机将立即暂停当前程序,赶去处理中断程序,处理完中断程序后再返回刚才暂停处接着执行原来的程序。单片机在执行程序时其程序流程图如图 3.4.3 所示。

       引起CPU 中断的根源, 称为中断源,中断源向 CPU 提出中断请求, CPU暂时中断原来的事务A, 转去处理事件B, 对事件 B 处理完毕后, 再回到原来被中断的地方(即断点), 称为中断返回实现上述中断功能的部件称为中断系统(中断机构)
       中断的开启与关闭、设置启用哪一个中断等都是由单片机内部的一些特殊功能寄存器来决定的, 在以前的学习中我们仅对单片机内部的特殊功能寄存器I/O口寄存器设置过,从下一节起我们将会设置单片机内部更多的特殊功能寄存器。
       与中断有关的知识点还有一个叫中断嵌套,意思是说:如果单片机正在处理一个中断程序, 此时, 又有另一个中断现象发生, 单片机将会停止当前的中断程序, 而转去执行新的中断程序,新中断程序处理完毕后再回到刚才停止的中断程序处继续执行,执行完这个中断后再返回主程序继续执行主程序, 流程图如图 3.4.4 所示。
       联系前面的生活事例,如果你在往暖水瓶中灌开水的时候,突然你家的电话响起,此时, 你将先停止灌开水,去接电话,接完电话后,再回去灌开水,灌完开水继续回去洗衣服。流程图如图 3.4.5 所示。

       当涉及中断时,还有一个很重要的关键词——中断优先级。假如你在洗衣服的时候,突然水开了,同时电话也响起了,接下来你只能去处理一件事,那你该处理哪件事呢?你将会根据自己的实际情况来选择其中一件更重要的事先处理,在这里,你认为更重要的事就是优先级较高的事情。单片机在执行程序时同样也会遇到类似的状况,即同一时刻发生了两个中断,那么单片机该先执行哪个中断呢?这取决于单片机内部的一个特殊功能寄存器中断 优先级寄存器的设置情况,通过设置中断优先级寄存器,我们可以告诉单片机,当两个中断同时出现时先执行哪个中断程序。若没有人为操作优先级寄存器,单片机会按照默认的一套优先级自动处理, 我们在具体使用时详细讲解。
       52单片机一共有 6 个中断源, 它们的符号、名称及产生的条件分别解释如下:
       INT0— 外部中断 0, 由 P3.2 端口线引入, 低电平或下降沿引起。
       INT1— 外部中断 1, 由 P3.3 端口线引入, 低电平或下降沿引起。
       T0一定时器/计数器 0 中断, 由T0 计数器计满回零引起。
       T1一定时器/计数器 1 中断, 由 T1 计数器计满回零引起。
       T2一定时器/计数器 2 中断, 由 T2 计数器计满回零引起。
       TI/ RI—串行口中断, 串行端口完成一帧字符发送/接收后引起。

       以上 6 个中断源中, T2 是 52 单片机特有的。它们默认的中断级别如表 3.4.1 所示。

       单片机在使用中断功能时, 通常需要设置两个与中断有关的寄存器:中断允许寄存器IP和中断优先级寄存器 IP

知识点:中断允许寄存器 1E

       中断允许寄存器用来设定各个中断源的打开和关闭, IE在特殊功能寄存器中,字节地址为 A8H , 位地址(由低位到高位 )分别是 A8H~AFH, 该寄存器可进行位寻址, 即可对该寄存器的每一位进行单独操作。单片机复位时IE全部被清 0, 各位定义见表 3.4.2。

EA 一 全局中断允许位。
EA= 1 ,打开全局中断控制,在此条件下,由各个中断控制位确定相应中断的打开或关闭。
EA= 0, 关闭全部中断。
**–**一 无 效 位 。
ET2 —定时器/计数器 2 中断允许位。
ET2 = 1,打开 T2 中断。
ET2 = 0,关闭 T2 中断。
ES—串行口中断允许位。
ES= 1 ,打开串行口中断。
ES= 0 ,关闭串行口中断。
ET1 —定时器/计数器 l 中断允许位。
ETl = 1, 打开Tl 中断。
ETI = 0 , 关闭 Tl 中断。
EXl—外部中断 l 中断允许位。
EXl = 1, 打开外部中断 l 中断。
EXl = 0, 关闭外部中断 1 中断。
ET0— 定 时器/计数器 0 中断允许位。
ET0 = 1 , 打开 TO 中断。
ET0 = 0 , 关闭 TO 中断。
EX0— 外 部 中断 0 中断允许位。
EX0 = 1 , 打开外部中断 0 中断。
EX0 = 0, 关闭外部中断 0 中断。

知识点: 中断优先级寄存器 IP

       中断优先级寄存器在特殊功能寄存器中, 字节地址为 B8H, 位地址(由低位到高位)分别是 B8H BFH, IP 用来设定各个中断源属于两级中断中的哪一级。该寄存器可进行位寻址, 即可对该寄存器的每一位进行单独操作。单片机复位时 IP 全部被清 0, 各位定义见表 3.4.3。

—无效位。
PS — 串行口中断优先级控制位。
PS= 1, 串行口中断定义为高优先级中断。
PS= 0, 串行口中断定义为低优先级中断。
PT1 —定时器/计数 器 1 中断优先级控制位。
PT1 = 1, 定时器/计数器 1 中断定义为高优先级中断。
PT1 = 0, 定时器/计数器 1 中断定义为低优先级中断。
PX1 — 外部中断1中断优先级控制位。
PX1 = 1, 外部中断 1 定义为高优 先级中断。
PX1 = 0, 外部中断 1 定义为低优先级中断。
PT0— 定 时器/计数器 0 中断优先级控制位。
PT0= 1, 定时器/计数 器 0 中断定义为高优先级中断。
PT0= 0, 定时器/计数 器 0 中断定义为低优先级中断。
PX0— 外部中断 0 中断优先级控制位。
PX0 = 1, 外部中断 0 定义为高优 先级中断。
PX0 = 0, 外部中断 0 定义为低优先级中断。
       在 51 单片机系列中,高优先级中断能够打断低优先级中断以形成中断嵌套, 同优先级中断之间,或低级对高级中断则不能形成中断嵌套。若几个同级中断同时向 CPU 请求中断响应, 在没有设置中断优先级情况下,按照默认中断级别响应中断,在设置中断优先级后,则按设置顺序确定响应的先后顺序

五、单片机的定时器中断

       我们先来了解一下单片机的定时器系统。51单片机内部共有两个16位可编程的定时器/计数器,即定时器T0和定时器T1。52单片机内部多一个T2定时器/计数器。它们既有定时功能又有计数功能,通过设置与它们相关的特殊功能寄存器可以选择启用定时功能或计数功能。需要注意的是,这个定时器系统是单片机内部一个独立的硬件部分,它与CPU和晶振通过内部某些控制线连接并相互作用,CPU一旦设置开启定时功能后,定时器便在晶振的作用下自动开始计时,当定时器的计数器计满后,会产生中断,即通知CPU该如何处理。结合生活事例,还是以烧开水为例,也就是说,当你打开火时,就注定不久就会响起水开的警报,这时你必定需要对该警报做出处理。烧开水是独立运行的一件事,但通过你打开火或者听到警报声来处理它。
       定时器/计数器的实质是加1计数器(16位),由高8位和低8位两个寄存器组成。TMOD是定时器/计数器的工作方式寄存器,确定工作方式和功能;TCON是控制寄存器,控制T0、T1的启动和停止及设置溢出标志。其结构框图如图3.5.1所示。

       加1计数器输入的计数脉冲有两个来源,一个是由系统的时钟振荡器输出脉冲经12分频后送来;另一个是T0或T1引脚输入的外部脉冲源,每来一个脉冲计数器加1,当加到计数器为全1时,再输入一个脉冲就使计数器回零,且计数器的溢出使TCON寄存器中TF0或TF1置1,向CPU发出中断请求(定时器/计数器中断允许时)。如果定时器/计数器工作于定时模式,则表示定时时间已到;如果工作于计数模式,则表示计数值已满。
       由此可见,由溢出时计数器的值减去计数初值才是加1计数器的计数值
       设置为定时器模式时,加1计数器是对内部机器周期计数 (1个机器周期等于12个振荡周期,即计数频率为晶振频率的1/12)。计数值N乘以机器周期Tcy就是定时时间t。
       **设置为计数器模式时,外部事件计数脉冲由T0或T1引脚输入到计数器。**在每个机器周期的S5P2(请查看10.5节图10.5.1介绍)期间采样T0、T1引脚电平。当某周期采样到一高电平输入,而下一周期又采样到一低电平时,则计数器加1,更新的计数值在下一个机器周期的S3P1期间装入计数器。由于检测一个从1~0的下降沿需要2个机器周期,因此要求被采样的电平至少要维持一个机器周期。当晶振频率为12MHz时,最高计数频率不超过112MHz,即计数脉冲的周期要大于2µs。
       单片机在使用定时器或计数器功能时,通常需要设置两个与定时器有关的寄存器:定时器/计数器工作方式寄存器TMOD与定时器/计数器控制寄存器TCON。

知识点:定时器/计数器工作方式寄存器 TMOD

       定时器/计数器工作方式寄存器在特殊功能寄存器中, 字节地址为 89H , 不能位寻址,TMOD 用来确定定时器的工作方式及功能选择。单片机复位时TMOD全部被清 0。其各位的定义如表 3.5.1。

       由表 3.5.1 可知,TMOD的高4位用于设置定时器 1 ,低 4 位用于设置定时器 0 ,对应 4位的含义如下:
       GATE——门控制位。
       GATE=0,定时器/计数器启动与停止仅受TCON寄存器中TRX(X=0,1)来控制。
       GATE=1,定时器/计数器启动与停止由TCON寄存器中TRX (X=0,1) 和外部中断引脚(INT0或 INT1)上的电平状态来共同控制。
       C/——定时器模式和计数器模式选择位。
       C/= 1 ,为计数器模式;
       CT=0,为定时器模式。
       MlM0——工作方式选择位。
       每个定时器/计数器都有 4 种工作方式, 它们由MlMO 设 定 ,对应关系如表 3.5.2 所示。

知识点: 定时器/计数器控制寄存器TCON

       定时器/计数器控制寄存器在特殊功能寄存器中,字节地址为88H,位地址(由低位到高位)分别是88H8FH,该寄存器可进行位寻址。TCON寄存器用来控制定时器的启、停,标志定时器溢出和中断情况。单片机复位时TCON全部被清0。其各位定义如表3.5.3。其中,TF1、TR1、TF0和TR0位用于定时器/计数器;IE1、IT1、IB0和IT0位用于外部中断,在这里一并做介绍。

       TF1—定时器1溢出标志位。
       当定时器1计满溢出时,由硬件使TF1置1,并且申请中断。进入中断服务程序后,由硬件自动清0。 需要注意的是,如果使用定时器的中断,那么该位完全不用人为去操作,但是如果使用软件查询方式的话,当查询到该位置1后,就需要用软件清0。
       TR1一定时器1运行控制位。
       由软件清0关闭定时器1。当GATE=1,且INT1为高电平时,TR1置1启动定时器1;当GATE=0时,TR1置1启动定时器1。
       TF0—定时器0溢出标志,其功能及操作方法同TF1。
       TR0—定时器0运行控制位,其功能及操作方法同TR1。
       IE1—外部中断1请求标志。


       当IT1=0时,为电平触发方式,每 个机器周期的S5P2采样INT1引脚,若INT1脚为低电平,则置1,否则IE1清0。
       当IT1=1时,INT1为跳变沿触发方式,当笫一个机器周期采样到INT1为低电平时,则IE1置1。IE1=1,表示外部中断1正在向CPU申请中断。当CPU响应中断,转向中断服务程序时,该位由硬件清0。
       IT1—外部中断1触发方式选择位。
       IT1=0,为电平触发方式,引脚INT1上低电平有效。
       IT1=1,为跳变沿触发方式,引脚INT1上的电平从高到低的负跳变有效。
       IE0—外部中断0请求标志,其功能及操作方法同IE1。
       IT0—外部中断0触发方式选择位,其功能及操作方法同IT1。
       从上面的知识点可知,每个定时器都有4种工作方式,可通过设置TMOD寄存器中的M1M0位来进行工作方式选择,在本节我们只讲了其中一个定时器的一种工作方式——定时器0的工作方式1:16位定时器。
       方式1的计数位数是16位,对T0来说,由TL0寄存器作为低8位、TH0寄存器作为高8位,组成了16 位加1计数器,其逻辑结构框图如图3.5.2所示。

       分析上面的逻辑图,当 GATE=0, TR0=1时,TL0便在机器周期的作用下开始加1计数,当TL0计满后向TH0进一位,直到TH0也计满,此时计数器溢出,置TF0为1,接着向CPU申请中断,接下来CPU进行中断处理。在这种情况下,只要TR0为1,那么计数就不会停止。这就是定时器0的工作方式1的工作过程,其他8位定时器、13位定时器的工作方式都大同小异。
       接下来讲解**如何计算定时器的初值问题。定时器一旦启动,它便在原来的数值上开始加1计数,若在程序开始时,我们没有设置TH0和TL0,它们的默认值都是0,假设时钟频率为12MHz,12个时钟周期为一个机器周期,那么此时机器周期就是1µs,计满TH0和TL0就需要2^16-1个数,再来一个脉冲计数器溢出,随即向CPU申请中断。因此溢出一次共需65536µs,约等于65.5ms,如果我们要定时50ms的话,那么就需要先给TH0和TL0装一个初值,在这个初值的基础上计50000个数后,定时器溢出,此时刚好就是50ms中断一次,当需定时1s时,我们写程序时当产生20次50ms的定时器中断后便认为是ls,这样便可精确控制定时时间了。要计50000个数时,TH0和TL0中应该装入的总数是65536-50000=15536,把15536对256求模:15536/25 6=60装入TH0中,把15536对256求余:15536%256=176装入TL0中。
       以上就是定时器初值的计算方法,总结后得出如下结论:当用定时器的方式1时,设机器周期为Tcy,定时器产生一次中断的时间为t,那么需要计数的个数
N=t/Tcy,装入THX和TLX中的数分别为THX=(65536-N)/256,TLX=(65536-N)%256
       要计算机器周期Tcy,就需要知道系统时钟频率,也就是单片机的晶振频率,TX-IC实验板上
时钟频率为11.0592MHz,那么机器周期为12X(1/11059200)1.09µs,若t=50ms,那么N=50000/1.0945872**,这是晶振在11.0592M压下定时50ms时初值的计算方法,当晶振为12MHz时,计算起来就比较方便了,用同样方法可算得N=50000。

知识点:中断服务程序的写法

C51的中断函数格式如下:

void 函数名()interrupt 中断号 using 工作组

{

       中断服务程序内容

       中断函数不能返回任何值,所以最前面用void;后面紧跟函数名,名字可以随便起,但不要与C语言中的关键字相同;中断函数不带任何参数,所以函数名后面的小括号内为空;中断号是指单片机中几个中断源的序号,请查看3.4节讲解中断时的表3.4.1。这个序号是编译器识别不同中断的唯一符号,因此在写中断服务程序时务必要写正确;最后面的"using工作组”是指这个中断函数使用单片机内存中4组工作寄存器中的哪一组,C51编译器在编译程序时会自动分配工作组,因此最后这句话我们通常省略不写,但大家以后若遇到这样的程序代码时要知道是什么意思。一个简单中断服务程序写法如下:
voidT1_time()interrupt3

       TH1=(65536-10000)/256;
       TL1=(65536-10000)%256;

       上面这个代码是一个定时器1的中断服务程序,定时器1的中断序号是3,因此我们要写成interrupt3,服务程序的内容是给两个初值寄存器装入新值。
       在写单片机的定时器程序时,在程序开始处需要对定时器及中断寄存器做初始化设置,通常定时器初始化过程如下:
       ① 对TMOD赋值,以确定T0和T1的工作方式。
       ② 计算初值,并将初值写入TH0、TL0或TH1、TL1。
       ③ 中断方式时,则对IE赋值,开放中断。
       ④ 使TR0或TR1置位,启动定时器/计数器定时或计数。

       下面我们通过两个实例来讲解定时器 0 和定时器1方式1的具体用法。
        【例 3.5.1】利用定时器 0 工作方式 1, 在 TX-lC 实验板上实现第一个发光管以 ls 亮灭闪烁。新建文件Sumjess1_4.c, 程序代码如下:

#include          //52系列单片机头文件
#define uchar unsigned char
#define uint unsigned int
sbit led1 = P1^0;		  
uchar num;
void main()
{
  TMOD=0x01;			//设置定时器0为工作方式1(M1M0为01)
  TH0=(65536-45872)/256;//装初值11.0592M晶振定时50ms数为45872
  TL0=(65536-45872)%256;
  EA=1;      //开总开关
  ET0=1;	 //开定时器0中断
  TR0=1;	 //启动定时器0
  while(1);	 //等待中端发生
}

void T0_time()interrupt 1
{
  TH0=(65536-45872)/256;//重装初值
  TH0=(65536-45872)%256;
  num++;				//num每加一次判断一次是否到了20次
  if(num==20)
  {
    num=0;
	led1=~led1;			//让发光管状态取反
	}
}

       编译程序下载到实验板, 我们可以看到实验板上第一个发光管以 1s 间隔闪动。
       分析:进入主程序后,首先是对定时器和中断有关的寄存器初始化,我们按照上面讲到 的通常的初始化过程来操作。定时50ms 的初值我们在前面已讲过为 45872。启动定时器后主程序停止在 while(1)处, 这里通常有很多人会有疑问:程序都停止在这里了,那么这个中断程序何时执行呢?主程序既然停止了为什么发光管却在闪烁呢?解释如下:一旦开启定时器, 定时器便开始计数,当计数溢出时,自动进入中断服务程序执行代码,执行完中断程序后再 回到原来处继续执行,也就是继续等待。就相当于你一旦打开火烧上开水后,不管你是洗衣服还是洗袜子,过一会儿水都会开,那么你就要停止当前活去处理开水问题,处理完后再回 来继续洗你的衣服或是袜子。
       为了确保定时器的每次中断都是 50ms, 我们需要在中断函数中每次为 TH0 和TL0 重新装入初值, 因为每进入一次中断需要时间 50ms,在中断程序中做一判断是否进入了 20 次, 也就是判断时间是否到了1 s , 若时间到则执行相应动作。
       注意:一般我们在中断服务程序中不要写过多的处理语句,因为如果语句过多,中断服务程序中的代码还未执行完毕,而下一次中断又来临,这样我们就会丢失这次中断,当单片机循环执行代码时,这种丢失累积出现,程序便完全乱套。一般我们遵循的原则是:能在主程序中完成的功能就不在中断函数中写,若非要在中断函数中实现功能,那么一定要高效、简洁。这样一来, 例 3.5.1 中的 20 次判断我们就可写在主程序中, 实现如下。

while(1);处改为
while(1)
{
if(num==20)   //如果到了 20 次, 说 明 1 秒 时 间 到
{
num=0;	      //然后把 num 清 0 重新再计 20 次
ledl=~ledl;   //让发光管状态取反

中断函数中改为
void T0_time() interrupt 1

TH0=(65535-45872)1256;	//重装初值TL0=(65535-45872)%256;
num++;

【例 3.5.2】在 TX-l C 实验板上完成如下功能: 用 定时器 0 的方式1 实现第一个发光管以200ms间隔闪烁,用定时器 1 的方式1 实现数码管前两位59s循环计时。新建文件Sumjess2.1_5.c, 程序代码如下:数码管为共阳极代码

#include          //52系列单片机头文件
#define uchar unsigned char
#define uint unsigned int
sbit led1 = P1^0;
sbit dula = P2^5;		   //申明U1锁存器的锁存端
sbit wela = P2^6;		   //申明U2锁存器的锁存端
uchar code table[]={
0xC0, /*/"0"*/
0xF9, /*/"1"*/
0xA4, /*/"2"*/
0xB0, /*/"3"*/
0x99, /*/"4"*/
0x92, /*/"5"*/
0x82, /*/"6"*/
0xF8, /*/"7"*/
0x80, /*/"8"*/
0x90, /*/"9"*/
0x88, /*/"A"*/
0x83, /*/"B"*/
0xC6, /*/"C"*/
0xA1, /*/"D"*/
0x86, /*/"E"*/
0x8E, /*/"F"*/
0x89, /*/"H"*/
0xC7, /*/"L"*/
0xC8, /*/"n"*/
0xC1, /*/"u"*/
0x8C, /*/"P"*/
0xA3, /*/"o"*/
0xBF, /*/"-"*/
0xFF, /*/熄灭*/
0xFF /*/自定义*/
};
void delayms(uint xms);	
void dispaly(uchar,uchar);	  
uchar num,num1,num2,shi,ge;
void main()
{
  TMOD=0x11;			//设置定时器0为工作方式1(M1M0为01)
  TH0=(65536-45872)/256;//装初值11.0592M晶振定时50ms数为45872
  TL0=(65536-45872)%256;
  TH1=(65536-45872)/256;//装初值11.0592M晶振定时50ms数为45872
  TL1=(65536-45872)%256;
  EA=1;      //开总开关
  ET0=1;	 //开定时器0中断
  ET1=1;	 //开定时器1中断
  TR0=1;	 //启动定时器0
  TR1=1;	 //启动定时器1
  while(1)	 //程序在这里不停的对数码管动态扫描同时等待中断发生
  dispaly(shi,ge);
}
void dispaly(uchar shi,uchar ge)
{
    dula=1;
	P0=table[shi];  //送段选数据
	dula=0;
	
	P0=0xff	;	  //送位选数据前关闭所有显示,防止打开位选锁存时
	
    wela=1;	      //原来段选数据通过位选锁存器造成混乱
    P0=0x01	;	  //送位选数据
    wela=0;		  //关闭U2锁存器
	delayms(5); //延时

	dula=1;
	P0=table[ge];  //送段选数据
	dula=0;
	
	P0=0xff	;	  //送位选数据前关闭所有显示,防止打开位选锁存时
	
    wela=1;	      //原来段选数据通过位选锁存器造成混乱
    P0=0x02	;	  //送位选数据
    wela=0;		  //关闭U2锁存器
	delayms(5); //延时
}
void delayms(uint xms)
{
	uint i,j;
	for(i=xms;i>0;i--)
		for(j=110;j>0;j--);
	}  
void T0_time()interrupt 1
{
  TH0=(65536-45872)/256;//重装初值
  TH0=(65536-45872)%256;
  num1++;				//num每加一次判断一次是否到了20次
  if(num1==4)
  {
    num1=0;
	led1=~led1;			//让发光管状态取反
	}
}

void T1_time()interrupt 3
{
  TH1=(65536-45872)/256;//重装初值
  TH1=(65536-45872)%256;
  num2++;				//num每加一次判断一次是否到了20次
  if(num2==20)
  {
    num2=0;
	num++;
	if(num==60)
	num=0;
	shi=num/10;
	ge=num%10;
	led1=~led1;			//让发光管状态取反
	}
}

       编译后下载, 实验现象如题目所述。实际效果图如下图所示。

       分析:例 3.5.2 中用了两个中断函数,单片机在区分进入哪个中断服务程序时是靠 interrupt 后面的序号来决定的, 两个定时器各自产生中断时都会有各自的中断服务程序。另外, 主程序初始化定时器和中断寄存器后便进入数码管动态扫描大循环中不停地显示数码管,因为数码管是动态显示,所以不能停止扫描程序,同时也是在等定时器中断的到来。这里需要注意两点:
       在例 3.5.2 中我们不能判断发光管亮灭时间是否到达的语句写在主程序中,若 写在主程序中,有可能有会发生如下错误情况:当主程序运行在数码管显示语句当中时,此时恰好定时器 0 进入中断并且num1刚好也加到 4,当定时器 0 中断再次进入时,主程序仍未退出数码管显示语句,那么此时 num1 的值便成了 5,这样的话, num1=4 这个点便永远检测不到了, 因此发光管的闪烁便失去了控制,虽然本例中这种情况不会发生,因为数码管显示语句的执 行总时间约为 10 多 ms, 小于定时器 0 中断一次的时间。但写程序搞研究一定要严格, 绝对不能抱侥幸心理, 若要这种情况发生,大家可自行测试,将显示数码管代码里的 delayms(5) 延长至delayms(30), 或缩短定时器 0 中断一次的时间。
       在这里我们把数码管的显示部分写成了一个带参数的函数,两个参数分别为要显示的十位数和个位数,以后我们操作数码管时都可以写成类似这样的带参数函数,调用起来会非常 方便。在定时器 1 的中断服务程序中, 最后面有两条语句:
shi=num/10; //求模运算, 也就是求出num中有多少个整数倍 10
ge=num%10; //求余运算, 也就是求出num中除去整数倍 10 的后的余数
       这两句的作用是把一个两位数分离成两个一位数,因为数码管在显示的时候只能是一位一位的显示,不可能在一个数码管上同时显示两位数,因此这个操作是必须的,如果我们要 把一个 3 位数分离成 3 个两位数, 同 样可用这样的方法:
Bai=num/100;
Shi=num%100110;
Ge=num%10;
       大家可自己用笔算一算,再多写几个这样的程序下载到实验板观察效果,唯有多练习、多实践才是学好单片机的唯一捷径。

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章