您的位置 首页 观点

《手把手教你学51单片机-C言语》之六 中止与数码管动态显示

中断是单片机系统重点中的重点,因为有了中断,单片机就具备了快速协调多模块工作的能力,可以完成复杂的任务。本章将首先带领大家学…

中止单片机体系关键中的关键,由于有了中止,单片机就具有了快速和谐多模块作业的才干,可以完结杂乱的使命。本章将首要带领咱们学习一些必要的C言语基础知识,然后解说数码管动态显现的原理,并终究借助于中止体系来完结有用的数码管显现程序。咱们对本章节内容要多多研讨,要完全把握并能娴熟运用。

1.1C言语的数组

1.1.1数组的根本概念

第四章现已学过变量的根本类型,比方char、int等等。这种类型描绘的都是单个具有特定含义的数据,当咱们要处理具有同类含义可是却包括许多个数据的时分,就可以用到数组了,比方咱们上节课那个数码管的真值表,便是用一个数组来表达的。

从概念上讲,数组是具有相同数据类型的有序数据的组合,一般来讲,数组界说后满意以下三个条件。

1、具有相同的数据类型;

2、具有相同的姓名;

3、在存储器中是被接连寄存的。

比方咱们上节课界说的那个数码管真值表,假设咱们把关键字code去掉,数组元素将被保存在RAM中,在程序中可读可写,一起咱们也可以在中括号里边标明这个数组所包括的元素个数,比方:

unsignedcharLedChar[16]={

0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,

0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E

};

在这个数组中的每个值都称之为数组的一个元素,这些元素都具有相同的数据类型便是unsignedchar型,他们有一个一起的姓名LedChar,不论放到RAM中仍是FLASH中,他们都是寄存在一块接连的存储空间里的。

有一点要特别留意,这个数组一共有16(中括号里边的数值)个元素,可是数组的单个元素的表达办法——下标是从0开端,因而实践上上边这个数组的首个元素LedChar[0]的值是0xC0,而LedChar[15]的值是0x8E,下标从0到15一共是16个元素。

LedChar这个数组只需一个下标,咱们称之为一维数组,还有两个下标和多个下标的,咱们称之为二维数组和多维数组。比方unsignedchara[2][3];表明这是一个2行3列的二维数组。在大多数状况下咱们运用的是一维数组,关于初学来说,咱们先来研讨一维数组,多维数组等遇到了再来了解。

1.1.2数组的声明

一维数组的声明格局如下:

数据类型数组名[数组长度];

1、数组的数据类型声明的是该数组的每个元素的类型,即一个数组中的元素具有相同的数据类型。

2、数组名的声明要契合C言语固定的标识符的声明要求,只能由字母、数字、下划线这三种符号组成,且榜首个字符只能是字母或许下划线。

3、方括号中的数组长度是一个常量或常量表达式,而且有必要是正整数。

1.1.3数组的初始化

数组在进行声明的一起可以进行初始化操作,格局如下:

数据类型数组名[数组长度]={初值列表};

仍是以上节课咱们用的数码管的真值表为例来解说留意事项。

unsignedcharLedChar[16]={

0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,

0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E

};

1、初值列表里的数据之间要用逗号离隔;

2、初值列表里的初值的数量有必要等于或小于数组长度,当小于数组长度时,数组的后边没有赋初值的元素由体系主动赋值为0。

3、若给数组的悉数元素都赋初值,那么可以省掉数组的长度,上节课的比方中咱们实践上现已省掉了数组的长度。

4、体系为数组分配接连的存储单元的时分,数组元素的相对次第由下标来决议,便是说LedChar[0]、LedChar[1]……LedChar[15]是依照次序紧挨着顺次排下来的。

1.1.4数组的运用和赋值

在C言语程序中,是不能一次运用整个数组的,只能运用数组的单个元素。一个数组元素相当于一个变量,运用数组元素的时分与运用相同数据类型的变量的办法是相同的。比方LedChar这个数组,假设没加code关键字,那么它可读可写,咱们可以写成a=LedChar[0]这样来把数组的一个元素的值送个a这个变量,也可以写成LedChar[0]=a这样把a这个变量的值送给数组中的一个元素,以下三点要留意:

1、引证数组的时分,那个方括号里的数字代表的是数组元素的下标,而数组初始化的时分方括号里的数字代表的是这个数组中元素的总数。

2、数组元素的方括号里的下标可以是整型常数,整型变量或许表达式,而数组初始化的时分方括号里的数字有必要是常数不能是变量。

3、数组全体赋值只能在初始化的时分进行,程序履行代码中只能对单个元素赋值。

1.2if句子

到目前为止,咱们对if句子应该现已不陌生了,前边程序已用过屡次了,这儿咱们体系的介绍一下,便利后边的深化学习。if句子有两个关键字:if和else,把这两个关键字翻译一下便是:“假设”和“不然”。if句子一共有三种格局,咱们别离来看。

1、if句子的默许办法:

if(条件表达式)

{

句子1;

}

其履行进程是,if(即假设)条件表达式的值为“真”,则履行句子1;假设条件表达式的值为“假”,则不履行句子1。真和假的概念不再赘述,参阅第五章。

这儿要提示咱们一点,C言语一个分号表明一条句子的结束,因而假设if后边只需一条履行句子的时分,可以省掉大括号,可是假设有多条履行句子的话,有必要加上大括号。

那么现在,咱们上节课的句子就很好了解了:

if(sec>=16)

{

sec=0;

}

当sec的值大于或等于16的时分,括号里的值才是“真”,那么就履行sec=0这一句,当sec的值小于16时,那么括号里就为“假”,就不履行这一句。

2、if…else句子

有些状况下,咱们除了要在括号里条件满意时履行相应的句子外,在不满意该条件的时分,也要履行一些别的的句子,这时分就用到了if…else句子,它的根本语法办法是:

if(条件表达式)

{

句子1;

}

else

{

句子2;

}

比方上节课的终究一段程序咱们也可以写成:

P0=LedChar[sec];

if(sec>=15)

{

sec=0;

}

else

{

Sec++;

}

这个程序咱们可以修正下载到单片机里验证一下,程序逻辑咱们自己动脑筋剖析,留意条件表达式内16到15的改动,想一下为什么,我就不多解说了。

3、if….elseif句子

if…esle句子是一个二选一的句子,或许履行if分支后的句子,或许履行else分支后的句子。还有一种多选一的用法便是if…elseif句子。他的根本语法格局是:

if(条件表达式1){句子1;}

elseif(条件表达式2){句子2;}

elseif(条件表达式3){句子3;}

……

else{句子n;}

他的履行进程是:顺次判别条件表达式的值,当呈现某个值为“真”时,则履行相对应的句子,然后跳出整个if的句子块,履行“句子n”后边的程序;假设悉数的表达式都为“假”,则履行else分支的“句子n”后,再履行“句子n”后边的程序。

if句子在C言语编程中运用频率很高,用法也不杂乱,所以有必要要娴熟把握。

1.3switch句子

用if….else句子在处理多分支的时分,分支太多就会显得不便利,且简略呈现if和else配对呈现过错的状况,在C言语中供给了别的一种多分支挑选的句子——switch句子,它的根本语法格局如下:

switch(表达式)

{

case常量表达式1:句子1;

case常量表达式2:句子2;

……

case常量表达式n:句子n;

default:句子n+1;

}

它的履行进程是:首要核算“表达式”的值,然后从榜首个case开端,与“常量表达式x”进行比较,假设与其时常量表达式的值不持平,那么就不履行冒号后边的句子x,一旦发现和某个常量表达式的值持平了,那么它会履行之后悉数的句子,假设直到终究一个“常量表达式n”都没有找到持平的值,那么就履行default后的“句子n+1”。请特别留意一点,当找到一个持平的case分支后,会履行该分支以及之后悉数分支的句子,很明显这不是咱们想要的成果。

在C言语中,有一条break句子,作用是跳出其时的循环句子,包括for循环和while循环,一起,它还能用来结束switch句子块。switch的分支句子一共有n+1种,而咱们一般期望的都是挑选其间的一个分支来履行,履行完后就结束整个switch句子,而持续履行switch后边的句子,此时就可以经过在每个分支后加上break句子来完结了。如下:

switch(表达式)

{

case常量表达式1:句子1;break;

case常量表达式2:句子2;break;

……

case常量表达式n:句子n;break;

default:句子n+1;break;

}

加了这个break句子后,一旦“常量表达式x”与“表达式”的值持平了,那么就履行“句子x”,履行结束后,由于有了break则直接跳出switch句子,持续履行switch句子后边的程序了,这样就可以防止履行不必要的句子。了解了这个switch句子后,咱们马上会在本章程序中运用稳固它。

1.4数码管的动态显现

1.4.1动态显现的根本原理

咱们在上一章学习数码管静态显现的时分提到,74HC138只能在同一时刻导通一个三极管,而咱们的数码管是靠了6个三极管来操控,那咱们怎样来让数码管一起显现呢?这就用到了动态显现的概念。

多个数码管显现数字的时分,咱们实践上是轮番点亮数码管(一个时刻内只需一个数码管是亮的),运用人眼的视觉暂留现象(也叫余辉效应),就可以做到看起来是悉数数码管都一起亮了,这便是动态显现,也叫做动态扫描。

例如:有2个数码管,咱们要显现“12”这个数字,先让高位的位选三极管导通,然后操控段选让其显现“1”,延时必守时刻后再让低位的位选三极管导通,然后操控段选让其显现“2”。把这个流程以必定的速度循环运转就可以让数码管显现出“12”,由于替换速度十分快,人眼识别到的便是“12”这两位数字一起亮了。

那么一个数码管需求点亮多长时刻呢?也便是说要多长时刻完结一次悉数数码管的扫描呢(很明显:全体扫描时刻=单个数码管点亮时刻*数码管个数)?答案是:10ms以内。当电视机和显现器还处在CRT(电子显像管)年代的时分,有一句很盛行的广告语——“100Hz无闪烁”,没错,只需改写率大于100Hz,即改写时刻小于10ms,就可以做到无闪烁,这也便是咱们的动态扫描的硬性方针。那么你或许会问,有最小值的约束吗?理论上没有,但实践上做到更快的改写却没有任何前进的含义了,由于现已无闪烁了,再快也仍是无闪烁,只是徒然添加CPU的负荷罢了(由于1秒内要履行更屡次的扫描程序)。所以,一般咱们规划程序的时分,都是取一个挨近10ms,又比较规整的值就行了。咱们开发板上有6个数码管,那么咱们现在就来着手写一个数码管动态扫描的程序,完结兼验证上面讲的动态显现原理。

咱们的方针仍是完结秒表功用,只不过这次有6个位了,最大可以计到999999秒。那么现在要完结的这个程序相关于前几章的例程来说就要杂乱的多了,既要处理秒表计数,又要处理动态扫描。在编写这类稍杂乱的程序时,主张初学者们先用程序流程图来把程序的整个流程理清,在动手写程序之前先把整个程序的结构结构搭好,把每一个环节要完结的功用先细化出来,然后再用程序代码一步一步的去完结出来。这样就可以防止无处着笔的苍茫感了。如图6-1便是本例的程序流程图,咱们先依据流程图把程序的履行经过在大脑里走一遍,然后再看接下来的程序代码,领会一下流程图的作用,看是不是能协助你更顺利的理清程序流程。

图6-1数码管动态显现秒表程序流程图

#include

sbitADDR0=P1^0;

sbitADDR1=P1^1;

sbitADDR2=P1^2;

sbitADDR3=P1^3;

sbitENLED=P1^4;

unsignedcharcodeLedChar[]={//数码管显现字符转化表

0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,

0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E

};

unsignedcharLedBuff[6]={//数码管显现缓冲区,初值0xFF确保发动时都不亮

0xFF,0xFF,0xFF,0xFF,0xFF,0xFF

};

voidmain()

{

unsignedchari=0;//动态扫描的索引

unsignedintcnt=0;//记载T0中止次数

unsignedlongsec=0;//记载经过的秒数

ENLED=0;//使能U3,挑选操控数码管

ADDR3=1;//由于需求动态改动ADDR0-2的值,所以不需求再初始化了

TMOD=0x01;//设置T0为形式1

TH0=0xFC;//为T0赋初值0xFC67,守时1ms

TL0=0x67;

TR0=1;//发动T0

while(1)

{

if(TF0==1)//判别T0是否溢出

{

TF0=0;//T0溢出后,清零中止标志

TH0=0xFC;//并从头赋初值

TL0=0x67;

cnt++;//计数值自加1

if(cnt>=1000)//判别T0溢出是否到达1000次

{

cnt=0;//到达1000次后计数值清零

sec++;//秒计数自加1

//以下代码将sec按十进制位从低到高顺次提取并转为数码管显现字符

LedBuff[0]=LedChar[sec%10];

LedBuff[1]=LedChar[sec/10%10];

LedBuff[2]=LedChar[sec/100%10];

LedBuff[3]=LedChar[sec/1000%10];

LedBuff[4]=LedChar[sec/10000%10];

LedBuff[5]=LedChar[sec/100000%10];

}

//以下代码完结数码管动态扫描改写

if(i==0)

{ADDR2=0;ADDR1=0;ADDR0=0;i++;P0=LedBuff[0];}

elseif(i==1)

{ADDR2=0;ADDR1=0;ADDR0=1;i++;P0=LedBuff[1];}

elseif(i==2)

{ADDR2=0;ADDR1=1;ADDR0=0;i++;P0=LedBuff[2];}

elseif(i==3)

{ADDR2=0;ADDR1=1;ADDR0=1;i++;P0=LedBuff[3];}

elseif(i==4)

{ADDR2=1;ADDR1=0;ADDR0=0;i++;P0=LedBuff[4];}

elseif(i==5)

{ADDR2=1;ADDR1=0;ADDR0=1;i=0;P0=LedBuff[5];}

}

}

}

这段程序,咱们自己抄到Keil中,然后边抄边结合程序流程图来了解,终究下载到试验板上看一下运转成果。其间下边的if…else句子便是每1ms快速的改写一个数码管,这样6个数码管全体改写一遍的时刻便是6ms,视觉感官上便是6个数码管一起亮起来了。

在C言语中,“/”等同于数学里的除法运算,而“%”等同于咱们小学学的求余数运算,这个前边已有介绍。假设是123456这个数字,咱们要正常显现在数码管上,个位显现,便是直接对10取余数,这个“6”就出来了,十位数字便是先除以10,然后再对10取余数,以此类推,就把6个数字悉数显现出来了。

关于多选一的动态改写数码管的办法,咱们假设用switch会有更好的作用,咱们来看一下咱们用switch句子完结的状况。

#include

sbitADDR0=P1^0;

sbitADDR1=P1^1;

sbitADDR2=P1^2;

sbitADDR3=P1^3;

sbitENLED=P1^4;

unsignedcharcodeLedChar[]={//数码管显现字符转化表

0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,

0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E

};

unsignedcharLedBuff[6]={//数码管显现缓冲区,初值0xFF确保发动时都不亮

0xFF,0xFF,0xFF,0xFF,0xFF,0xFF

};

voidmain()

{

unsignedchari=0;//动态扫描的索引

unsignedintcnt=0;//记载T0中止次数

unsignedlongsec=0;//记载经过的秒数

ENLED=0;//使能U3,挑选操控数码管

ADDR3=1;//由于需求动态改动ADDR0-2的值,所以不需求再初始化了

TMOD=0x01;//设置T0为形式1

TH0=0xFC;//为T0赋初值0xFC67,守时1ms

TL0=0x67;

TR0=1;//发动T0

while(1)

{

if(TF0==1)//判别T0是否溢出

{

TF0=0;//T0溢出后,清零中止标志

TH0=0xFC;//并从头赋初值

TL0=0x67;

cnt++;//计数值自加1

if(cnt>=1000)//判别T0溢出是否到达1000次

{

cnt=0;//到达1000次后计数值清零

sec++;//秒计数自加1

//以下代码将sec按十进制位从低到高顺次提取并转为数码管显现字符

LedBuff[0]=LedChar[sec%10];

LedBuff[1]=LedChar[sec/10%10];

LedBuff[2]=LedChar[sec/100%10];

LedBuff[3]=LedChar[sec/1000%10];

LedBuff[4]=LedChar[sec/10000%10];

LedBuff[5]=LedChar[sec/100000%10];

}

//以下代码完结数码管动态扫描改写

switch(i)

{

case0:ADDR2=0;ADDR1=0;ADDR0=0;i++;P0=LedBuff[0];break;

case1:ADDR2=0;ADDR1=0;ADDR0=1;i++;P0=LedBuff[1];break;

case2:ADDR2=0;ADDR1=1;ADDR0=0;i++;P0=LedBuff[2];break;

case3:ADDR2=0;ADDR1=1;ADDR0=1;i++;P0=LedBuff[3];break;

case4:ADDR2=1;ADDR1=0;ADDR0=0;i++;P0=LedBuff[4];break;

case5:ADDR2=1;ADDR1=0;ADDR0=1;i=0;P0=LedBuff[5];break;

default:break;

}

}

}

}

程序完结的功用是一模相同的,但咱们看一下,switch句子是不是比if…else句子显得要规整清新呢。

1.4.2数码管显现消隐

不知道同学们是否发现了,咱们的这两个数码管动态显现程序的运转作用好像并不是那么完美,榜首个小问题,咱们仔细看,数码管的不应该亮的段,好像有微微的发亮,这种现象叫做“鬼影”,这个“鬼影”严峻影响了咱们的视觉作用,咱们该怎样处理呢?

同学们在往后或许会遇到各式各样的实践问题,或许许多都是咱们没有讲过的,遇到问题怎样办呢?咱们要信任,你作为初学者,遇到的问题必定不是榜首个遇到的,必定有长辈现已遇到过相同的或相似的问题,他们一般都会在网上宣布各种帖子,各种评论,所以咱们遇到问题,首要就应该构成一个到网上查找的条件反射,这个问题咱们可以到网上搜:“数码管消隐”或许“数码管鬼影处理”,多找相关关键词查找试试,会查找也是一种才干。

咱们在网上搜了一下会发现,处理这类问题的办法有两个,其间之一是延时,延时之后咱们肉眼就或许看不到这个“鬼影”了。可是延时是一个十分低劣的手法,且不说延时多久能让咱们看不到“鬼影”,延时后,咱们的数码管亮度会遍及下降。咱们处理问题呢,不能只知其然,还要知其所以然,那么咱们首要就来弄了解为什么会呈现“鬼影”。

“鬼影”的呈现,主要是在数码管位选和段选产生的瞬态构成的。举个简略比方,咱们在数码管动态显现的那部分程序中,实践上每一个数码管点亮的持续时刻是1ms的时刻,1ms后进行下个数码管的切换。在进行数码管切换的时分,比方咱们从case5要切换到case0的时分,case5的位选用的是ADDR0=1;ADDR1=0;ADDR2=1;假设此时case5也便是最高位数码管对应的值是0,咱们要切换成的case0的数码管位选是ADDR0=0;ADDR1=0;ADDR2=0;而对应的数码管的值假设是1。又由于C言语程序是一句一句次序往下履行的,每一条句子的履行都会占用必定的时刻,即便这个时刻十分十分时刻短。可是当咱们把“ADDR0=1”改动成“ADDR0=0”的时分,这个瞬间存在了一个中间状态ADDR0=0;ADDR1=0;ADDR2=1;在这个瞬间上,咱们就给case4对应的数码管DS5瞬间赋值了0。当咱们悉数写完了ADDR0=0;ADDR1=0;ADDR2=0;后,这个时分,咱们的P0还没有正式赋值,而P0此时却坚持了前一次的值,也便是在这个瞬间,咱们又给case0对应的数码管DS1赋值了一个0。直到咱们把case0后边的句子悉数完结后,咱们的改写才正式完结。而在这个改写进程中,有2个瞬间咱们给过错的数码管赋了值,尽管很弱(由于亮的时刻很短),可是咱们仍是可以发现。

那么搞了解了原理后,处理起来就不是困难的作业了,咱们只需避开这个瞬间过错就可以了。不产生瞬间过错的办法是,在进行位选切换期间,防止悉数数码管的赋值即可。办法有两个,一个办法是改写之前封闭悉数的段,改动好了位选后,再翻开段即可;第二个办法是封闭数码管的位,赋值进程都做好后,再从头翻开即可。这个不是很难,答案我都发布一下。

封闭段:在switch(i)这句程序之前,加一句P0=0xFF;这样就把数码管悉数的段都封闭了,当把“ADDR”的值悉数搞定后,再给P0赋对应的值即可。

封闭位:在switch(i)这句程序之前,加上一句ENLED=1;比及把ADDR2=0;ADDR1=0;ADDR0=0;i++;P0=LedBuff[0];这几条改写程序悉数写完后,再加上一句ENLED=0;然后再进行break操作即可。

这个当地逻辑思路上略微有点杂乱,咱们必定要了解深化,深化了解,完全弄了解,把这个瞬间的问题弄了解了,后边许多牵扯到此类状况的问题,咱们都可以一起搞定。

上边的数码管程序还有第二个问题,咱们仔细看,咱们的数码管上的数字每一秒改动一次,改动的时分,不参与改动的数码管或许呈现一次颤动,这个颤动没有什么专业的姓名,咱们就称之为数码管颤动吧。这种数码管颤动是什么原因构成的呢?为何在数据改动的时分才颤动呢?

来剖析一下咱们的程序,程序在守时到1秒的时分,履行了“秒数+1并转化为数码管显现字符”这个操作,一个32位整型数的除法运算,实践上是比较消耗时刻的,至于这一段程序终究消耗了多少时刻,咱们可以经过第四章讲的调试办法来看看这段程序运转用了多少时刻。由于每次守时到1秒的时分,程序都多运转了这么一段,导致了某个数码管的点亮时刻比其他状况下要长一些,总时刻就变成了1ms+本段程序运转时刻,于此一起,其它的数码管就平息了5ms+本段程序运转时刻,假设这段程序运转时刻十分短,那么可以忽略不计,但很明显,现在这段程序运转时刻现已比较长了,以致于严峻影响到视觉作用了,所以咱们要采纳别的一种思路去处理这个问题。

1.5单片机中止体系

1.5.1中止的产生布景

请想象这样一个场景:此时我正在厨房用煤气烧一壶水,而烧开一壶水刚好需求10分钟,我是一个主体,烧水是一个意图,而且我只能时时刻刻在这儿烧水,由于一旦水开了,溢出来浇灭煤气的话,有或许引发一场灾祸。但就在这个时分呢,我又听到了电视里传来《天龙八部》的主题歌,马上就要开演了,我真想夺门而出,去看我最喜爱的电视剧。可是,听到这个水壶宣布的“咕嘟”的声响,我清楚:除非等水烧开了,不然我是无法享用我喜爱的电视剧的。

这儿边主体只需一个我,而我要做的有两件作业,一个是看电视,一个是烧水,而电视和烧水是两个独立的客体,它们是一起进行的。其间烧水需求10分钟,但不需求了解烧水的进程,只需求得到水烧开的这样一个成果就行了,提下水壶和封闭煤气只需求几秒的时刻罢了。所以咱们采纳的办法便是:烧水的时分,定上一个闹钟,守时10分钟,然后我就可以安心看电视了。当10分钟时刻到了,闹钟响了,此时水也烧开了,我就过去把煤气灭掉,然后持续回来看电视就可以了。

这个场景和单片机有什么关系呢?

在单片机的程序处理进程中也有许多相似的场景,当单片机正在聚精会神的做一件作业(看电视)的时分,总会有一件或许多件急迫或许不急迫的作业产生,需求咱们去重视,有一些需求咱们停下手头的作业去马上去处理(比方水开了),只需处理完了,才干回头持续完结方才的作业(看电视)。这种状况下单片机的中止体系就该发挥它的强壮作用了,合理奇妙的运用中止,不只可以使咱们取得处理突发状况的才干,而且可以使单片机可以“一起”完结多项使命。

1.5.2守时器中止的运用

在第五章咱们学过了守时器,而实践上守时器一般用法都是采纳中止办法来做的,我是故意在第五章用查询法,便是运用if(TF0==1)这样的句子先用守时器,意图是清晰告知同学们,守时器和中止不是一回事,守时器是单片机模块的一个资源,确确实实存在的一个模块,而中止,是单片机的一种运转机制。尤其是初学者们,许多人会误以为守时器和中止是一个东西,只需守时器才会触发中止,但实践上许多作业都会触发中止的,除了“烧水”,还有“有人按门铃”,“来电话了”等等。

规范51单片机中操控中止的寄存器有两个,一个是中止使能寄存器,另一个是中止优先级寄存器,这儿先介绍中止使能寄存器,如表6-1和表6-2所示。跟着一些增强型51单片机的面世,或许会有添加的寄存器,咱们了解了咱们这儿所讲的,其它的经过自己研读数据手册就可以了解了解而且用起来了。

本帖子中包括更多资源

您需求登录才干够下载或检查,没有帐号?当即注册

x

我关键赞0

保藏1告发

这个家伙好懒,什么都没留下回复修正
金沙滩作业室

  • 发消息
宣布于 2014-6-9 08:47:22
2#

表6-1IE——中止使能寄存器的位分配(地址0xA8、可位寻址)

7

6

5

4

3

2

1

0

符号

EA

ET2

ES

ET1

EX1

ET0

EX0

复位值

0

0

0

0

0

0

0

表6-2IE——中止使能寄存器的位描绘

符号

描绘

7

EA

总中止使能位,相当于总开关

6

5

ET2

守时器2中止使能

4

ES

串口中止使能

3

ET1

守时器1中止使能

2

EX1

外部中止1使能

1

ET0

守时器0中止使能

0

EX0

外部中止0使能

中止使能寄存器IE的位0~5操控了6个中止使能,而第6位没有用到,第7位是总开关。总开关就相当于咱们家里或许学生宿舍里的那个电源总闸口,而0~5位这6个位相当于每个分开关。那么也便是说,咱们只需用到中止,就要写EA=1这一句,翻开中止总开关,然后用到哪个分中止,再翻开相对应的操控位就可以了。

咱们现在就把前面的数码管动态显现的程序改用中止再完结出来,一起数码管显现颤动和“鬼影”也一起处理掉了。程序运转的流程跟图6-1所示的流程图是根本共同的,但由于加入了中止,所以整个流程被分成了两部分,秒计数和转化为数码管显现字符的部分还留在主循环内,而动态扫描部分则移到了中止函数内,并加入了消隐的处理。下面来看程序:

#include

sbitADDR0=P1^0;

sbitADDR1=P1^1;

sbitADDR2=P1^2;

sbitADDR3=P1^3;

sbitENLED=P1^4;

unsignedcharcodeLedChar[]={//数码管显现字符转化表

0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,

0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E

};

unsignedcharLedBuff[6]={//数码管显现缓冲区,初值0xFF确保发动时都不亮

0xFF,0xFF,0xFF,0xFF,0xFF,0xFF

};

unsignedchari=0;//动态扫描的索引

unsignedintcnt=0;//记载T0中止次数

voidmain()

{

unsignedlongsec=0;//记载经过的秒数

EA=1;//使能总中止

ENLED=0;//使能U3,挑选操控数码管

ADDR3=1;//由于需求动态改动ADDR0-2的值,所以不需求再初始化了

TMOD=0x01;//设置T0为形式1

TH0=0xFC;//为T0赋初值0xFC67,守时1ms

TL0=0x67;

ET0=1;//使能T0中止

TR0=1;//发动T0

while(1)

{

if(cnt>=1000)//判别T0溢出是否到达1000次

{

cnt=0;//到达1000次后计数值清零

sec++;//秒计数自加1

//以下代码将sec按十进制位从低到高顺次提取并转为数码管显现字符

LedBuff[0]=LedChar[sec%10];

LedBuff[1]=LedChar[sec/10%10];

LedBuff[2]=LedChar[sec/100%10];

LedBuff[3]=LedChar[sec/1000%10];

LedBuff[4]=LedChar[sec/10000%10];

LedBuff[5]=LedChar[sec/100000%10];

}

}

}

/*守时器0中止服务函数*/

voidInterruptTimer0()interrupt1

{

TH0=0xFC;//从头加载初值

TL0=0x67;

cnt++;//中止次数计数值加1

//以下代码完结数码管动态扫描改写

P0=0xFF;//显现消隐

switch(i)

{

case0:ADDR2=0;ADDR1=0;ADDR0=0;i++;P0=LedBuff[0];break;

case1:ADDR2=0;ADDR1=0;ADDR0=1;i++;P0=LedBuff[1];break;

case2:ADDR2=0;ADDR1=1;ADDR0=0;i++;P0=LedBuff[2];break;

case3:ADDR2=0;ADDR1=1;ADDR0=1;i++;P0=LedBuff[3];break;

case4:ADDR2=1;ADDR1=0;ADDR0=0;i++;P0=LedBuff[4];break;

case5:ADDR2=1;ADDR1=0;ADDR0=1;i=0;P0=LedBuff[5];break;

default:break;

}

}

咱们可以先把程序抄下来,编译下载到单片机里运转,看看实践作用。是否可以看到,近乎完美的显现作用经过咱们的尽力总算做成功了。下面咱们还要再来解析一下这个程序。

在这个程序中,有两个函数,一个是主函数,一个是中止服务函数。主函数main()咱们就不用说了,关键着重一下中止服务函数,它的书写格局是固定的,首要中止函数前边void表明函数回来空,即中止函数不回来任何值,函数名是InterruptTimer0(),这个函数名在契合函数命名规矩的前提下可以随意取,咱们取这个姓名是为了便利差异和回忆,然后是interrupt这个关键字,必定不能错,这是中止特有的关键字,别的后边还有个数字1,这个数字1怎样来的呢?咱们先来看表6-3。

表6-3中止查询序列

中止

函数编号

中止称号

中止

标志位

中止

使能位

中止

向量地址

默许

优先级

0

外部中止0

IE0

EX0

0x0003

1(最高)

1

T0中止

TF0

ET0

0x000B

2

2

外部中止1

IE1

EX1

0x0013

3

3

T1中止

TF1

ET1

0x001B

4

4

UART中止

TI/RI

ES

0x0023

5

5

T2中止

TF2/EXF2

ET2

0x002B

6

这个表格相同不需求咱们记住,需求的时分过来查就可以了。咱们现在看第二行的T0中止,要使能这个中止那么就要把它的中止使能位ET0置1,当它的中止标志位TF0变为1时,就会触发T0中止了,那么这时就应该来履行中止函数了,单片机又怎样找到这个中止函数呢?靠的便是中止向量地址,所以interrupt后边中止函数编号的数字x便是依据中止向量得出的,它的核算办法是x*8+3=向量地址。当然表中都现已给算好放在榜首栏了,咱们可以直接查出来用就行了。到此为止,中止函数的命名规矩咱们就都搞清楚了。

中止函数写好后,每逢满意中止条件而触发中止后,体系就会主动来调用中止函数。比方咱们上面这个程序,平常一直在主程序while(1)的循环中履行,假设程序有100行,当履行到50行时,守时器溢出了,那么单片机就会马上跑到中止函数中履行中止程序,中止程序履行结束后再主动回来到方才的第50行处持续履行下面的程序,这样就确保了动态显现距离是固定的1ms,不会由于程序履行时刻不共同的原因导致数码管显现的颤动了。

1.1.1中止的优先级

中止优先级的内容,咱们先经过我的介绍大约了解一下即可,后边实践运用的时分咱们再详细了解。

在讲中止产生布景的时分,咱们只是讲了看电视和烧水的比方,可是实践生活傍边还有更杂乱的,比方我正在看电视,这个时分来电话了,我要进入接电话的“中止”程序傍边去,就在接电话的一起,听到了水开的声响,水开的“中止”也产生了,咱们就有必要要放下手上的电话,先把煤气关掉,然后再回来听电话,终究听完了电话再看电视,这儿就产生了一个优先级的问题。

还有一种状况,咱们在看电视的时分,这个时分听到水开的声响,水开的“中止”产生了,咱们要进入关煤气的“中止”程序傍边,而在关煤气的一起,电话声响响了,而这个时分,咱们的处理办法是先把煤气封闭,再去接听电话,终究再看电视。

从这两个进程中,咱们可以得到一个定论,便是最最紧迫的作业,一旦产生后,咱们不论其时处在哪个“程序”傍边,咱们有必要先去处理最最紧迫的作业,处理结束后再去处理其它作业。在咱们的单片机程序傍边有时分也是这样的,有一般紧迫的中止,有特别紧迫的中止,这取决于详细的体系规划,这就涉及到中止优先级和中止嵌套的概念,在本章节咱们先简略介绍一下相关寄存器,不做例程阐明。

中止优先级有两种,一种是抢占优先级,一种是固有优先级,先介绍抢占优先级。来看表6-4和表6-5。

表6-4IP——中止优先级寄存器的位分配(地址0xB8、可位寻址)

7

6

5

4

3

2

1

0

符号

PT2

PS

PT1

PX1

PT0

PX0

复位值

0

0

0

0

0

0

表6-5IP——中止优先级寄存器的位描绘

符号

描绘

7

保存

6

保存

5

PT2

守时器2中止优先级操控位

4

PS

串口中止优先级操控位

3

PT1

守时器1中止优先级操控位

2

PX1

外部中止1中止优先级操控位

1

PT0

守时器0中止优先级操控位

0

PX0

外部中止0中止优先级操控位

IP这个寄存器的每一位,表明对应中止的抢占优先级,每一位的复位值都是0,当咱们把某一位设置为1的时分,这一位的优先级就比其它位的优先级高了。比方咱们设置了PT0位为1后,当单片机在主循环或许任何其它中止程序中履行时,一旦守时器T0产生中止,作为更高的优先级,程序马上就会跑到T0的中止程序中来履行。反过来,当单片机正在T0中止程序中履行时,假设有其它中止产生了,仍是会持续履行T0中止程序,直到把T0中的中止程序履行结束今后,才会去履行其它中止程序。

当进入低优先级中止中履行时,如又产生了高优先级的中止,则马上进入高优先级中止履行,处理完高优先级级中止后,再回来处理低优先级中止,这个进程就叫做中止嵌套,也称为抢占。所以抢占优先级的概念便是,优先级高的中止可以打断优先级低的中止的履行,然后构成嵌套。当然反过来,优先级低的中止是不能打断优先级高的中止的。

那么已然有抢占优先级,天然就也有非抢占优先级了,也称为固有优先级。在表6-3中的终究一列给出的便是固有优先级,请留意,在中止优先级的编号中,一般都是数字越小优先级越高。从表中可以看到一共有1~6共6级的优先级,这儿的优先级与抢占优先级的一个不同点便是,它不具有抢占的特性,也便是说即便在低优先级中止履行进程中又产生了高优先级的中止,那么这个高优先级的中止也只能比及低优先级中止履行完后才干得到呼应。已然不能抢占,那么这个优先级有什么用呢?

答案是多个中止一起存在时的裁定。比方说有多个中止一起产生了,当然实践上产生这种状况的概率很低,但别的一种状况就常见的多了,那便是出于某种原因咱们暂时封闭了总中止,即EA=0,履行完一段代码后又从头使能了总中止,即EA=1,那么在这段时刻里就很或许有多个中止都产生了,但由于总中止是封闭的,所以它们其时都得不到呼应,而当总中止再次使能后,它们就会在一起恳求呼应了,很明显,这时也必需有个先后次序才行,这便对错抢占优先级的作用了——如表6-3中,谁优先级最高先呼应谁,然后按编号排队,顺次得到呼应。

抢占优先级和非抢占优先级的协同,可以使单片机中止体系有条有理的作业,既不会无休止的嵌套,又可以确保必要时紧迫使命得到优先处理。在后续的学习进程中,中止体系会与咱们如影随形,处处都有它的身影,跟着学习的深化,信任你对它的了解也会愈加的深化。

1.1练习题

1、把握C言语数组的概念、界说和运用。

2、把握if句子和switch句子的用法及差异,编程的时分可以正确挑选运用哪个句子。

3、完全了解中止的原理和运用办法,封闭教程自己独立把本章节程序编写结束而且下载到试验板上实践。

4、测验修正程序,让咱们的数码管只显现有用位,也便是高位的0不显现。

5、测验写一个从999999开端倒计时的程序,而且改用守时器T1的中止来完结,经过写这个程序,娴熟把握守时器和中止的运用。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/news/guandian/266009.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部