转移,不等于“0”则顺序执行,转移到什么地方,我们同样可以这样理解:JNC 标号。 (8)判位变量转移指令A.JB bit,rel B.JNB bit,rel
指令说明:第1 条指令是如果指定的bit 位中的值是“1”,则转移;否则就顺序执行,转移到什么地方,同样我们可以这样理解:JB bit,标号;第2 条指令请大家自行分析一下。 (9)判位变量转移并将该位清零:JBC bit,rel
指令说明:这条指令同JB bit,rel 的区别在于判“1”转移的同时清除该位,为什么要这样做呢?后面我们会讲到。 接下来我们做一个这方面的实验:
把上面的程序下载到实验板上,看看有什么现象???按下接在P3.4 上的按键,P1 口的灯全亮了,松开或再按,灯并不熄灭;然后按下接在P3.5 上的按键,灯就全灭了,这像什么?这不就是工业控制中经常用到的启动、停止功能吗?怎么做到的呢?一开始,将0FFH 送入P3 口,这样,P3 口所有的引线都处于高电平,然后执行L1,如果P3.4 是高电平(键没有按下),则顺序执行JNB P3.5,L3 语句;同样,如果P3.5 是高电平(键没有按下),则顺序执行LJMP L1 语句,这样就不停地检测P3.4 和P3.5 。如果有一次P3.4 上的按键按下去了,则转移到L2(执行MOV P1,#00H),使灯全亮,然后又转去L1,再次循环,直到检测到P3.5 为“0”,就转去L3(执行MOV P1,#0FFH),使灯全灭,再转去L1,如此不断地循环就可以了。这里提一个问题,我们这个实验中控制的是一个字节(既整个P1 口),如何来实现一位(比如P1.0)的控制呢?其实很简单,只要把程序改一下就可以了。 程序如下:
ORG 0000H ; LJMP START ; ORG 30H ; START:MOV SP,#5FH ;
尽管实际的程序还要考虑按键的去抖动问题,但程序的基本结构和流程是实用的,这样的程序就能完成我们对工业控制中继电器的控制目的。怎么样,如果现在让您用单片机控制一台电机的正反转应该没有问题了吧,试试看。 单片机经典教程16 单片机程序的设计
程序设计是单片机开发最重要的工作,程序设计就是利用单片机的指令系统,根据应用系统(即目标产品)的要求编写单片机的应用程序,其实我们前面已经开始这样做过了,这一课我们不是讲如何来设计具体的程序,而是教您设计单片机程序的基本方法。不过在讲解之前还是有必要先了解一下单片机的程序设计语言。一.程序设计语言这里的语言与我们通常理解的语言是有区别的,它指的是为开发单片机而设计的程序语言,如果您没有学过程序设计可能不太明白,我给大家简单解释一下,您知道微软的VB,VC 吗?VB,VC 就是为某些工程应用而设计的计算机程序语言,通俗地讲,它是一种设计工具,只不过这种工具是用来设计计算机程序的。要想设计单片机的程序当然也要有这样一种工具(说设计语言更确切些),单片机的设计语言基本上有三类:
1.完全面向机器的机器语言机器语言就是能被单片机直接识别和执行的语言,计算机能识别什么?以前我们讲过--是数字“0”或“1”,所以机器语言就是用一连串的“0”或“1”来表示的数字。比如:MOV A,40H;用机器语言来表示就是11100101 0100000 ,很显然,用机器语言来编写单片机的程序不太方便,也不好记忆,我们必须想办法用更好的语言来编写单片机的程序,于是就有了专门为单片机开发而设计的语言:
2.汇编语言汇编语言也叫符号化语言,它使用助记符来代替二进制的“0”和“1”,比如:刚才的MOV A,40H 就是汇编语言指令,显然用汇编语言写成的程序比机器语言好学也好记,所以单片机的指令普遍采用汇编指令来编写,用汇编语言写成的程序我们就叫它源程序或源代码。可是计算机不能识别和执行用汇编语言写成的程序啊?怎么办?当然有办法,我们可以通过“翻译”把源代码译成机器语言,这个过程就叫做汇编,汇编工作现在都是由计算机借助汇编程序自动完成的,不过在以前,都是靠手工来做的。值得注意的是,汇编语言也是面向机器的,它仍是一种低级语言。每一类计算机都有它自己的汇编语言,比如:51 系列有它的汇编语言,PIC 系列也有它的汇编语言,微机也有它自己的汇编语言,它们的指令系统是各不相同的,也就是说,不同的单片机有不同的指令系统,它们之间是不通用的,这就是为什么世界上有很多单片机类型的缘故。为了解决这个问题,人们想了很多的办法,设计了许多的高级计算机语言,而其中最适合单片机编程的要数C 语言。
3.C 语言—高级单片机语言 C 语言是一种通用的计算机程序设计语言,它既可以用来编写通用计算机的系统程序,也可以用来编写一般的应用程序,由于它具有直接操作计算机硬件的功能,所以非常适合用来编写单片机程序,与其他的计算机高级程序设计语言相比,它具有以下的特点:
(1)。语言规模小,使用简单在现有的计算机设计程序中,C 语言的规模是最小的,ANSIC 标准的C 语言一共只有32 个关键字,9 种控制语句,然而它的书写形式却比较灵活,表达方式简洁,使用简单的方法就可以构造出相当复杂的数据类型和程序结构。
(2)。可以直接操作计算机硬件 C 语言能够直接访问单片机的物理空间地址(KEIL C51 软件中的C51 编译器更具有直接操作51 单片机内部存储器和I/O 口的能力),亦可直接访问片内或片外存储器,还可以进行各种位操作。
(3)。表达能力强,表达方式灵活 C 语言有丰富的数据结构类型,可以采用整型、实型、字符型、数组类型、指针类型、结构类型、联合类型、枚举类型等多种数据类型来实现各种复杂数据结构的运算。利用C 语言提供的多种运算符,我们可以组成各种表达式,还可以采用多种方法来获得表达式的值,从而使程序设计具有更大的灵活性。
(4)。可进行结构化设计单片机教程(MCS-51 系列) 结构化程序是单片机程序设计的组成部分,C 语言中的函数相当于汇编语言中的子程序,KEIL C51 的编译器提供了一个函数库,其中包含有许多标准函数,如各种数学函数、标准输入输出函数等,此外还可以根据用户需要编制满足某种特殊需要的自定义函数。C 语言程序就是由许多个函数组成的,一个函数即相当于一个程序模块,所以C 语言可以很容易地进行结构化程序设计。
(5)。可移植性前面我们讲过,由于单片机的结构不同,所以不同类型的单片机就要用不同的汇编语言来编写程序,而C 语言则不同,
它是通过汇编来得到可执行代码的,所以不同的机器上有80% 的代码是公用的,一般只要对程序稍加修改,甚至不加修改就可以方便地把代码移植到另一种单片机中。这对于已经掌握了一种单片机的编程原理,又想用另一种单片机的人来说,可以大大地缩短学习周期,我们将在教程的下册中专门来讲解C 语言的应用及其编程原理。不过作为单片机初学者想要学会C 语言并不是一件容易的事,因此对于大多数人来说,汇编语言仍是编写单片机程序的主要语言。我们上册的教程将全部以汇编语言来编写单片机的程序。了解了单片机编程的设计语言,下面我们来看单片机编程的基本过程和步骤。
二.单片机程序设计的步骤单片机的程序设计通常包括根据任务建立数学模型、绘制程序流程图、编写程序及汇编三个步骤。 1.建立数学模型数学实在是太有用了,在单片机的程序设计领域,根据任务建立数学模型是程序设计的关键工作。比如,在一个测量系统中,从模拟通道输入的温度、压力、流量等信息与该信号的实际值是非线性关系,这就需要我们对其进行线性化处理,此时就要用到指数和函数等数学变量来进行计算;再比如,在直接数字化控制的系统中,常采用PID 控制算法来进行系统的运算,此时又要用到数学中的微分和积分运算等等。因此,数学模型对于单片机的程序设计是非常重要的。只不过作为初学者,我们还没有复杂到如此程度,因此,详细的内容就不讲解了。下面的绘制程序流程图可是初学者的基本功,请大家务必仔细看一下。
2.绘制流程图所谓流程图,就是用各种符号、图形、箭头把程序的流向及过程用图形表示出来。绘制流程图是单片机程序编写前最重要的工作,通常我们的程序就是根据流程图的指向采用适当的指令来编写的,下面的图形和箭头就是我们绘制流程图用的工具(图中左边所示)。 绘制流程图时,首先画出简单的功能流程图(粗框图),再对功能流程图进行扩充和具体化,即对存储器、标志位等单元做具体的分配和说明,把功能图上的每一个粗框图转化为具体的存储器或单元,从而绘制出详细的程序流程图,即细框图。下面举个例子给大家演示一下,请看下面的程序:主程序: LOOP:SETB P1.0 ; 单片机教程(MCS-51 系
列) LCALL DELAY ; CLR P1.0 ; LCALL DELAY ; LJMP LOOP ;子程序: DELAY:MOV R7,#250; D1:MOV R6,#250; D2:DJNZ R6,D2 ; DJNZ R7,D1 ; RET ; END。 还记得吗,这是我们第四课中做过的LED 灯闪烁的实验,以前我们曾对程序进行过分析,现在让我们用流程图来把这段程序的主程序部分画出来,看上图的右边部分。这就是程序的流程图,在单片机的编程过程中,绘制流程图能看清楚程序执行的步骤以及程序的流向,事实上,程序的编写就是根据流程图的功能完成的。下面我们来把第十五课中的那个程序也用流程图画出来。程序如下: ORG 0000H ;LJMP START ;ORG 30H ;START:MOV SP,#5FH;MOV P1,#0FFH ;MOV P3,#0FFH ;L1:JNB P3.5,L2 ;P3.5 上接有一只按键,它按下时,P3.5=0 JNB P3.6,L3 ;P3.6 上接有一只按键,它按下时,P3.6=0 LJMP L1 ;L2:CLR P1.0 ;亮LED1 LJMP L1 ;L3:SETB P1.0 ;暗LED1 LJMP L1 ;END。先不看图,自己画一下,看是不是同我画的一样。在实际的程序设计中,根据框图,采用适当的指令编写出实现流程图的源程序就是我们编写程序的最后工作。单片机教程(MCS-51 系列)
3.编写程序和汇编程序编写完之后,我们要把它汇编成机器语言,这种机器语言就是十六进制文件,后缀名为*.HEX 文件,以前还要把它转换成二进制文件,后缀名为*.BIN 文件,不过现在的编程器都能直接读入十六进制文件,就不需要转换了,最后用编程器把程序写入单片机。这些以前都讲过了,这里就不重复了。下面来讲本课的主题—程序设计的方法。三.单片机程序设计的方法要想搞清楚程序设计的方法,我们首先要知道单片机到底有哪几类程序?单片机的程序分为结构化程序、子程序和综合程序三个大类,先来看结构化程序。 1.结构化程序的设计方法在单片机的程序中,既有复杂的程序,也有简单的程序,但不论哪种程序,它们都是由一个个基本的程序结构组成的,这些基本结构有顺序结构、分支结构和循环结构。
(1)。顺序结构程序的设计顺序结构的程序一般用来处理比较简单的算术或逻辑问题,它的执行过程是按照程序存储器PC 自动加1 的顺序执行的,主要用数据传递类指令和数据运算类指令来实现。比如我们前面第六课中的I/O 口输入实验就是典型的顺序结构的程序。试试看,把这个程序的流程图写出来。下面再看一个例子:将内部RAM 中20H 单元和30H 单元的无符号数相加,存入R0(高位)和R1(低位)中。先画出流程图: 根据流程图编写源代码如下: MOV A,20H ; ADD A,30H ; MOV R0,A ; CLR A ; ADDC A,#00H ; MOV R0,A ; MOV A,30H ; 单片机教程(MCS-51 系列) ADD A,R1 ; MOV R1,A ; CLR A ; ADDC A,R0 ; MOV R0,A ; 这就是顺序结构程序,程序的原理我就不分析了,我们接着讲分支结构的程序设计。这里说明一点,最近有朋友提出这一课的有些程序看不懂,的确如此,这一课的有几个程序实例我们从来没有学过,之所以放在这里,原本是为了让大家理解程序设计的方法,举几个示例证明一下,没想到反而增加了大家的难度。其实这些示例你不需要刻意的去理解它,只要明白它的设计方法就可以了,因为这一张的主要内容是程序设计的方法,而不是程序执行的原理和结果。如果以后有更好的示例我会修改一下。
(2)。分支结构程序的设计所谓分支结构就是利用条件转移指令,使程序执行某一指令后,根据所给的条件是否满足来改变程序执行的顺序,也就是本条指令执行完后,并不是象顺序结构那样执行下一条指令,而是看本条指令所给的条件是否满足,如果满足条件就跳转到其他的指令,如果不满足就顺序执行;当然也可以是满足条件顺序执行,而不满足条件跳转执行,看十五课实验程序中的下面两条: L1:JNB P3.5,L2 ;P3.5 上接有一只按键,它按下时,P3.5=0 JNB P3.6,L3 ;P3.6 上接有一只按键,它按下时,P3.6=0 这就是分支结构的程序,如果P3.5 为“0”,就转移;反之就顺序执行。当然也可以改成P3.5=0 顺序执行;而P3.5=1 则转移,不过此时的程序就要用JB 指令了。在51 系列单片机中,可以直接用于分支程序的指令有JB(JNB)、JC(JNC)、JZ(JNZ)、CJNE 、JBC 等这几条,它们可以完成诸如正负判断、大小判断和溢出判断等等。在分支结构的指令设计中,大家必须注意?:执行一条判断指令只可以形成两路分支,如果要形成多路分支,就必须进行多次判断,也就是多条指令连续判断。下面给大家举两个例子: A.单分支结构的程序实例假设有两个数在内部RAM 单元的40H 和41H 中,现在要求找出其中较大的一个数,并将较大的数存入40H 中,而将较小的一个数存入41H 中。根据程序的要求,我们先画出程序的流程图(左图)。 单片机教程(MCS-51 系列) 再根据流程图写出程序的源代码如下: MOV A,40H ; CLR C ; SUBB A,41H ; JNC WAIT ; MOV A,41H ; XCH A,41H ; MOV 40H,A ; WAIT:SJMP WAIT;END。程序的原理请大家自行分析一下,接下来再举一个多分支结构的实例,看下面的程序:MOV A,20H ;取数JZ ZERO ;A=0,转移;A=1,顺序执行JB ACC。7,STORE ;A 为负数,转移ADD A,#3 ;A 为正数,则加3 SJMP STORE ;ZERO:MOV A,#20 ;STORE:MOV 21H,A ;自己画一下本例
的流程图,再和上面的右图比较一下,看是不是一样。这里有一条指令给大家解释一下:JB ACC.3,STORE;ACC.3 表示累加器A 中的D3 位,这条指令的意思就是看一下累加器中的D3 位(也就是第四位)是正还是负,第四位是什么呢?在这里就是“0”(20H 的二进制10000000)。明白了吗?接下来再讲第三种循环结构的程序设计。
(3)。循环结构程序的设计循环程序是最常用的程序结构形式,在单片机的程序设计中,有时要碰到一段程序要重复执行多次的情况,此时就要用到循环结构程序,比如第四课中的实验--LED 灯闪烁程序的子程序: DELAY:MOV R7,#250;(1) D1:MOV R6,#250;(2) D2:DJNZ R6,D2 ;(3) DJNZ R7,D1 ;(4)RET ;(5) END。在这段程序中,为了延时需要多次执行DJNZ 指令,此时若用循环结构指令就可以大大地简化程序的设计,减少程序占用的存储器空间。循环结构指令一般有以下四个部分组成: A.初始化部分初始化部分主要用来设置循环的初始值,包括预值数、计数器和数据指针的初值。比如上例中的#250 就是预值数初值。 B.循环处理部分循环处理部分是程序的主体部分,也称为程序体,通过它可以完成程序处理的任务。 C.循环控制部分 循环控制部分可以控制程序循环的次数,并修改预值数或计数器和指针的值,检查该循环是否执行了足够的次数,如果到了足够的次数,就采用条件转移指令或判断指令来控制循环的结束。比如上例中的(3)、(4)指令就是当R6 或R7 中的值为“0”时就转移。 C.循环结束部分循环结束后必须返回,一般用RET 或RETI (中断返回,以后会讲到)指令。这里注意?:以上四个部分中,第一和第四部分只能执行一次,而第二和第三部分可以执行多次。单片机教程(MCS-51 系列) 典型的循环结构程序的流程图可画成如下左图所示,也可以将处理部分和控制部分位置对调,如右图。在循环程序设计中,循环控制部分是程序设计的关键环节,常用的循环控制方式有计数器控制和条件控制两种。计数器控制就是把要循环的次数(即预值数)放入计数器中,程序每循环一次,计数器的值就减1,一直到计数器的内容为零时,循环结束,一般用DJNZ 指令;而条件控制方式常预先不知道要循环的次数,只知道循环的有关条件,此时就可以根据给定的条件标志位来判断程序是否继续,一般参照分支结构方法中的条件来判别指令并执行。下面举几个例子来分别解释一下,希望大家能以此类推。 程序一:用计数器控制的单重循环程序源程序如下: CLR A ; MOV R2,20H ; MOV R1,;22H ; LOOP:ADD A,@R1 ; INC R1 ; DJNZ R2,LOOP ; MOV 21H,A ;这段程序的作用是从22H 单元开始存放一个数据块,其长度存放在20H 单元中,将数据块求和,要求将和存放入21H 单元中,和不超过255。下面再举一个条件控制的循环程序。程序二:用条件控制的单重循环程序设字符串存放在内部RAM 的21H 开始的单元中,以结束作标志,要求计算出该字符串的长度,并将其存放在20H 单元中。源程序如下:CLR A ;MOV R0, #21H ;将地址指针指向21H 单元LOOP:CJNZ @R0,#24H,NEXT ;与比较 SJMP COMP ;找到结束 NEXT:INC A ;不为“0”,计数器加1 INC R0 ;修改地址指针 SJMP LOOP ; COMP:MOV 20H,A ;存放结果试试看,自己把上面两段程序的流程图画出来。下面再看一个例子:单片机教程(MCS-51 系列) DELAY:MOV R7,#250 ; D1:MOV R6,#250 ; D2:DJNZ R6,D2 ; DJNZ R7,D1 ; RET ; END。 这是一段约125mS 的延时程序,现在我们来把它改成下面表格中的程序(右边的程序): DELAY:MOV R7,#250; DELAY:MOV R7,#250; D1:MOV R6,#250; D1:MOV R6,#250; D2:DJNZ R6,D2; D2:MOV R5,#250; DJNZ R7 ,D1; D3:DJNZ R5,D3 ; RET; DJNZ R6,D2 ; END。 DJNZ R7,D1 ; RET; END。 从这里可以引出一个概念:程序的嵌套。什么是嵌套,比如早上我骑自行车从家里到单位去上班,当走到半路上时,太太叫我去孩子学校拿点东西;到了学校,老师又叫我把学校的一台电脑修一下;修好电脑,一个朋友又打电话叫我去他那里拿了一本《单片机与嵌入式系统》杂志,完了之后再去上班;这就是生活中的嵌套。在单片机的程序设计中,也有类似的现象,有时为了达到某个目的,往往要在一段循环程序中再加入另一段循环程序,这就是单片机的程序嵌套。通常我们把一个循环体中不再包含循环的叫做单重嵌套;如果一个循环体中还包括有循环,则叫做多重嵌套。上面的表格中左边的程序就是单重嵌套,而右边的程序则是多重嵌套。另外须注意?:在多重嵌套中,不允许各个循环体互相交叉,也不允许从外循环跳入内循环,否则编译时会出错。了解了结构化程序的设计,下面再来看子程序的设计方法。 2.子程序的设计方法什么是子程序?如何设计子程序?要解释这个问题,让我们先同样从生活中的一个例子说起,请看下面的数学题目:28*(33+65)+47*(33+65)+875*(33+65)。在这道题中,我们一般是怎么算的?也许大家都知道,一般总是先把(33+65)=98 代出来,然后再用(28+47+875)*98 来计算最后的结果,为什么会这样?这是因为在这道题中,我们多次用到了(33+65 )这个中间结果。在单片机的程序设计中,有时也有这样的情况,比如下面的程序:主程序 LOOP:SETB P1.0 ;(1) LCALL DELAY ;(2) CLR P1.0 ;(3) LCALL DELAY ;(4) LJMP LOOP ;(5) 子程序 DELAY:MOV R7,#250 ;(6) D1:MOV R6,#250 ;(7) D2:DJNZ R6,D2 ;(8) DJNZ R7,D1 ;(9) RET ;(10) END。(11)这是大家非常熟悉的LED 灯延时程序,在这段程序中,两次调用到了DELAY 这段程序,为了简化程序的设计,我们就把DELAY 这段程序单独地列了出来,这段列出的程序我们就叫它子程序,而调用子程序的程序我们则叫它主程序(LOOP 的程序段)。在主程序执行时,每当要用到子程序时,我们单片机教程(MCS-51 系列) 就用LCALL 指令来调用子程序,子程序执行完之后,必须返回主程序,返回就用RET 指令,这我们以前都讲过了,这里不再重复。另外,如果子程序执行的过程中,还要再次调用其他的子程序,这种现象我们就称它为子程序的嵌套。看上面右边的图,就是一个两层子程序的嵌套结构图。这里有个问题?在子程序的执行过程中,有时可能要使用到累加器和某些工作寄存器,而在调用子程序前,这些寄存器中可能已经存放有主程序的中间结果,它们在子程序返回后仍要使用,这样就需要在进入子程序之前,将要使用的累加器和寄存器中的内容预先转移到安全的地方保存起来,这叫现场保护;当子程序执行完即将返回主程序之前,还要将这些内容先取出来,送回到累加器和原来的工作寄存器中,这个过程叫恢复现场。保护现场和恢复现场通常使用堆栈,即在进入子程序之前,将需要保护的数据压入堆栈,在返回之前再将压入的数据弹出到原来的工作单元中,恢复原来的状态。看下面的例子: LOOP:PUSH 03H ;将03H 单元中的值压入堆栈保护 PUSH ACC ;将累加器中的值压入堆栈保护 ?? ?? POP ACC ;将ACC 中的值从堆栈弹出 POP 03H ;恢复03H 单元中的内容 RET ;从子程序返回 由于堆栈的操作是“后进先出,先进后出”,所以编写指令时,必须把后压入堆栈的数据先弹出来才能保证恢复到原来的状态。在实际的程序设计中,由于每个应用程序的不同,还必须根据具体的情况来考虑是否需要保护?哪些数据需要保护等等,这就是单片机的堆栈为什么能够变化的原因。关于堆栈的操作先讲这些,后面的实验中我们还将结合具体的实验来分析,接下来再看另一种程序--综合程序的设计方法。 3.综合程序的设计方法综合程序有查表程序、散转程序、数据排序程序、代码转换程序等等,作为初学者,要想全面的掌握也确实有一定的难度,所以只给大家简
单地提一下,详细的内容就留到下则的课程中再来解释。四.本课总结程序设计是单片机开发最重要的工作,掌握程序设计的基本步骤和方法对于单片机的软件编写是至关重要的,这一课的内容较多,对于一时无法搞清的部分,大家可以结合以后的实际应用慢慢去理解,不要急于求成,千万记住一点,学习使用单片机绝不是一朝一夕的事。 单片机经典教程17 单片机的定时/计数器
通过前面十几节课的学习,我们已经掌握了很多的单片机知识,也许您已经可以用它来开发具体的产品了,不过在有些工业及民用控制中,我们往往需要定时检测某个参数或按一定的时间间隔来进行某项控制,比如家里的闹钟定时,电动机的Y/Δ控制等等,此时您就要用到定时器或计数器,因此几乎所有的单片机系统内部都有几个定时/计数器,89C51 有两个16 位的定时/计数器;而89C52 则有3 个。在了解这些定时/计数器之前,让我们先来熟悉几个基本概念: 一、几个基本概念 1.定时/计数的概念
从选票的统计谈起:画“正”,这就是计数,生活中计数的例子处处可见。例:线缆行业在电线生产出来之后要计米,也就是测量长度,怎么测量呢?用尺量?不现实,太长不说,要一边做一边量,怎么办呢?行业中有很巧妙的方法,用一个周长是1 米的轮子,将电缆绕在上面一周,由线带动轮子转,这样轮子转一周不就是线长1 米嘛,所以只要记下轮子转了多少圈,就可以知道走过的线有多少米了。 2.计数器的容量
从生活中的一个例子看起:一个水盆在水龙头下,水龙没关紧,水一滴一滴地滴入盆中。水滴不断落下,盆的容量是有限的,过一段时间之后,水就会逐渐变满,换句话说,计数是有容量的。那么单片机中的计数器有多大的容量呢?89C51 的两个计数器,分别称之为T0 和T1,这两个计数器都是由两个8 位的RAM 单元组成的,即每个计数器都是16 位的计数器,最大的计数容量是216=65536 ,记住是从0-65535,因为在计算机中,往往把0 作为起始点,比如P0,P1.0,X0,Y0 等等。 3.定时器的原理
单片机中的计数器除了可以作为计数用,还可以用作定时器,定时器的用途当然很大,如闹钟的定时,手机的定时开关机等等,那么计数器是如何作为定时器来用的呢?一个闹钟,如果我们将它定时在1 个小时后闹响,就相当于秒针走了3600 次,在这里时间就转化成为了秒针走的次数,可见,计数的次数和时间之间的确有关,那么单片机的定时/计数器是怎么回事呢?请看下面的图:
从图中我们可以得出这样的结论:只要计数脉冲的间隔相等,那么计数值就代表了时间的流逝。其实,单片机中的定时器和计数器是一个东西,只不过计数器记录的是外界发生的事情,而定时器则是
单片机提供一个非常稳定的计数源,然后把计数源的计数次数转化为定时器的时间,图中的C/T 开关就是起这个作用的。那么提供给定时器的计数源又是从哪里来的呢?继续看上面的图,原来它就是由
单片机的晶振经过12 分频后获得的一个脉冲源。我们知道,晶振的频率是很准的,所以这个计数脉冲的时间间隔当然也很准。 这里提个问题:一个12M 的晶振,它提供给计数器的脉冲时间间隔是多少呢?不知大家是否还记得,那就是1us,也就是1 个微秒。 4.溢出的原理
继续让我们看水滴的例子,当盆中的水不断落下,最终会有一滴水使得盆中的水变满,这时如果再有一滴水落下,就会发生什么现象?水会漫出来,用个单片机的术语叫“溢出”。水溢出是流到地上,而计数器溢出后会使得TF0 由“0”变为“1”,(至于TF0 是什么我们稍后再谈),一旦TF0 由“0”变为“1”,就是产生了变化,产生了变化就会引发事件,就象闹钟的定时时间一到,铃声就会响一样,那么单片机溢出会引发什么事件呢?我们下节课再具体介绍,这里我们来研究另一个问题:要有多少个计数脉冲才会使TF0 由“0”变为“1”。
5.任意定时及计数的方法
刚才已经讲过,51 系列单片机的计数器是16 位的,也就是最大的计数值范围是0-65535 ,因此计数器计到65535 就会产生溢出。这个不是问题,问题是我们实际应用中经常会有少于65536 个计数值的要求,如药品生产线上,一箱为50 瓶,一瓶为100 粒,怎么样才能满足这个要求呢?
提示:如果是一个空的盆要1 万滴水滴进去才会满,我们在开始滴水之前就预先放入一勺水,还需要1 万滴嘛?单片机计数也是如此,如果我要计5000 个脉冲,就先放进60535 个,再来5000 个,不就到了65535 了吗?定时器同样如此,每个脉冲是1 微秒,则计满65536 个脉冲需时65.536 毫秒,但现在我只要10 毫秒就可以了,怎么办?10 个毫秒为10000 微秒,所以,只要在计数器里预先放进55536 就可以了,这种计数方法我们把它称之为预置数计数法。那么单片机的定时/计数器是由什么来控制的呢?下面就来讨论这个问题。二.定时/计数器的方式控制字
从上一节我们已经知道,单片机中的定时/计数器可以有两种用途,那么我们怎样才能让它们工作于我们所需要的用途呢?这就需要通过定时/计数器的方式控制字(实际上就是与定时/计数器有关的特殊功能寄存器)来设置。在单片机中有两个特殊功能寄存器与定时/计数器有关,它们是TMOD 和TCON。顺便说一下,TMOD 和TCON 是名称,我们在写程序时既可以直接用这个名称来指定它们,也可以直接用它们的地址89H 和88H 来指定它们(其实用名称也就是直接用地址,只不过汇编软件帮你翻译一下而已),具体使用稍后讲,现在先来看特殊功能寄存器TMOD 的组成,看下表: 1.特殊功能寄存器TMOD(89H)
从表中可以看出,TMOD 被分成两部份,每部份4 位,分别用于控制T1 和T0,至于这里面是什么意思,我们稍后介绍;再另一个与定时/计数器有关的特殊功能寄存器TCON。 2.特殊功能寄存器TCON(88H)
TCON 也被分成两部份,高4 位用于定时/计数器,低4 位则用于中断(我们暂不管),而TF0 我们前面已提到了,当计数溢出后TF0 就由“0”变为“1”,原来TF0 在这儿!那么TR0、TR1 又是什么呢?看前面的图。计数脉冲要想进入计数器还真不容易,有层层关要通过,最起码,就是TR0 要为“1”,开关才能合上,脉冲才能过来,因此,TR0 我们称之为运行控制位,当要使用T0 时必须用指令SETB 来置位以启动计数/定时器工作(用指令CLR 可关闭定时/计数器的工作),这一切就在您的掌握中了。
知道了TF0 和TR0,TF1 和TR1 的作用也清楚了。讲到这里,我们还是没有讲清楚单片机定时/ 计数器是如何来工作的,别着急,接着往下看。三.定时/计数器的四种工作方式
单片机的定时/计数器共有四种工作方式,下面我们分别加以介绍: 1.工作方式0
定时/计数器的工作方式0 称之为13 位定时/计数器方式。它由TL(0/1)的低5 位和TH(0/1)的8 位构成13 位的计数器,此时TL(0/1)的高3 位未用。请看各位的功能: (1)M1M0:
定时/计数器一共有四种工作方式,就是用TMOD 的M1M0 来控制的,两位正好是4 种组合。 (2)C/T:
前面我们说过,定时/计数器既可作定时器用也可作计数器用,到底作什么用,由我们根据需要自行决定,也说是决定权在我们编程者手中。如果C/T=0 就是用作定时器(开关往上打);如果C/T=1 就用作计数器(开关往下打)。顺便提一下:一个定时/计数器同一时刻要么作定时用,要么作计数用,不能同时使用,这一点请大家注意。 (3)GATE:
当我们选择了定时/计数器工作方式后,定时/计数脉冲却不一定能到达计数器端,中间还有一个开关,很显然如果这个开关不合上,计数脉冲就没法通过,那么开关什么时候合上呢?它有两种情况:
A.GATE=0 ,分析一下逻辑,GATE “非”后是“1”,进入“或”门,“或”门总是输出“1”,和“或”门的另一个输入端INT0(中断0,什么是中断,先不去管它)无关,在这种情况下,开关的打开、合上只取决于TR0,只要TR0=1,开关就合上,计数脉冲得以畅通无阻;而如果TR0=0 则开关打开,计数脉冲无法通过,因此定时/计数是否工作,在这里只取决于TR0。
B.GATE=1 ,在这种情况下,计数脉冲通路上的开关不仅要由TR0 来控制,而且还要受到INT0 引脚的控制,只有TR0=1,且INT0 也是高电平,开关才能合上,计数脉冲才得以通过。
那么为什么在这种模式下只用13 位呢?干吗不用16 位,这是为了和51 的前辈48 系列兼容而设的一种工作方式,如果你觉得用起来不顺手,那就干脆用工作方式1 吧。 2.工作方式1
工作方式1 是16 位的定时/计数器方式,将TMOD 的M1M0 设为“01”即可,其它特性与工作方式0 相同,这里就不详细介绍了。 3.工作方式2
在介绍这种工作方式之前先让我们思考一个问题:前面我们提到过任意计数及任意定时的问题,比如我要计5000 个数,可是16 位的计数器要计到65535 才溢出,怎么办呢?我们讨论后得出的办法是采用预置数的办法--先在计数器里放上60535 个,再来5000 个脉冲,不就行了吗?是的,但是计满了之后我们又该怎么办呢?要知道,计数总是不断重复的,流水线上计满后马上又要开始下一次计数,下一次的计数还是5000 吗?当计满并溢出后,计数器里面的值又变成了“0”(为什么,可以参考前面课程的说明),因此下一次将要计满65535 后才会溢出,这可不符合要求,怎么办?当然办法很简单,就是每次一溢出时执行一段程序(这通常是需要的,要不然要溢出干吗?)可以在这段程序中做把预置数60535 送入计数器中的工作。所以采用工作方式0 或1 都要在溢出后做一个重置预置数的工作,做工作当然就得要时间,一般来说这点时间不算什么,可是有一些场合我们还是要计较的,所以就有了工作方式2--自动再装入预置数的工作方式。
既然要自动装入预置数,那么预置数就得放在一个地方,要不然装什么呢?那么预置数放在什么地方呢?它放在T0(或T1)的高8 位中,那么这高8 位不就不能参与计数了吗?是的,在工作方式2 中,只有低8 位参与计数,而高8 位是不参与计数的,用作预置数的存放,这样计数范围就小了(当然做任何事总是有代价的,关键看值不值,如果我根本不需要计那么多数,那就可以用这种工作方式了)。看前面的图,每当计数溢出,就会打开T0(或T1)的高、低8 位之间的开关,预置数就进入低8 位。当然这是由硬件自动完成的,不需要我们去操心。
通常工作方式2 用于波特率发生器(我们将在下册的串行接口中讲解),对于这种用途,定时器就是为了提供一个时间基准,计数溢出后不需做任何的事情,要做的仅仅只有一件,就是重新装入预置数,再开始计数,而且中间不能有任何的延迟,可见这个任务用这种工作方式来完成是最妙不过了。 4.工作方式3
在这种工作方式下,T0 被拆成2 个独立的定时/计数器来用。其中,TL0 可以构成8 位的定时器或计数器工作方式;而TH0 则只能作为定时器用,我们知道定时/计数器使用时需要有控制,计满后溢出需要有溢出标记,T0 被分成两个来用,那就要两套控制及溢出标记了,从何而来呢?TL0 还是用原来的T0 的标记,而TH0 则借用T1 的标记,如此一来T1 不是无标记、控制可用了吗?是的,在一般情况下,只有在T1 以工作方式2 运行时,才让T0 工作于方式3 四.定时器/计数器的定时/计数范围
那么单片机的这四种工作方式的计数范围是如何确定的呢?