任务1-2 用单片机控制一个LED的亮灭

1.任务目标

(1)单片机内部结构的了解;

(2)单片机输入输出口的基本应用;

(3)伟福软件的作用及使用方法;

(4)编程器的作用及使用方法;

(5)单片机基本连接电路(复位、晶振、EA脚、电源的连接)。

2.任务要求

用单片机控制一个LED发光二极管不断闪烁。

3.相关知识点

我们先给出实现这个任务的硬件电路,见图1-2-1,下面我们来分析一下这个硬件电路。

图1-2-1 单灯闪烁硬件电路图

1)单片机的基本连线

想要使用一块芯片,首先必须要知道应该怎样连线。我们用AT89C51的芯片,它的管脚图见图1-2-2,下面我们就看一看图1-2-1中是如何给它连线的。

图1-2-2 51单片机的管脚图

(1)电源:这当然是必不可少的了,任何芯片要正常工作都要电源。单片机使用的是+5V电源,其中正极接40引脚VCC,负极(地)接20引脚GND。

注意 在Proteus软件中电源脚与接地脚可以不连接。

(2)振荡电路:单片机是一种时序电路,必须提供脉冲信号才能正常工作,单片机才能按照这个时序信号,一步一步地完成相关工作,在单片机内部已集成了振荡器,只要在18、19 脚接上晶振和电容,就可以产生时钟脉冲了。连接方法见图1-2-1 中18、19 脚的连接。其中两个小电容用于提高产生的振荡信号的稳定性,典型值为22pF。

(3)复位引脚(RST):将9 脚按图1-2-1 中画法连好,构成复位电路。让单片机上电就进行复位,进行电路的初始化。

(4)EA引脚:EA引脚接到正电源端。为什么EA脚要接正电源端?可不可以接地?这些问题我们先记在心上,我们后面再慢慢给大家介绍。

至此,一个单片机的基本电路就接好,单片机就开始工作了。注意:单片机应用电路这几个脚的连接方法基本都是这样的。

2)单片机怎样控制灯的闪烁

我们的第一个任务是要用单片机控制一只发光二极管闪烁,实际上就是让单片机控制LED亮和灭,我们先来分析一下,怎样才能让单片机点亮或者弄灭LED。

显然,这个LED必须要和单片机的某个引脚相连,否则单片机就没法控制它了,那么和哪个引脚相连呢?单片机上除了刚才基本连接用掉的6个引脚外,还有34个,其中有32根I/O线,可以将单片机内部的二进制数据(高低电平)输出来,这些I/O脚上输出的高低电平可以对发光二极管的亮灭进行控制。因此我们将D1这个LED连到一根I/O线上,与1脚相连。(见图1-2-1,其中R1是限流电阻)。

按照这个图的接法,当1脚是高电平时,LED亮,只有1脚是低电平时,LED才灭。因此要让这个LED点亮或灭掉,实际上就是要让1 引脚按要求变为高或低电平。既然我们要控制1 脚,就得给它起个名字,总不能就叫它“1 脚”吧?叫它什么名字呢?设计51 芯片的Intel公司已经起好了,就叫它P1.0,这是规定,不可以由我们来更改。P1.0 实际上是32 条I/O脚其中的一根,Intel公司将32 条I/O线分为四组,其中P1.0~P1.7 分为一组,Intel公司给它取名为P1端口,另外三组分别为P0, P2, P3端口,每个端口都有8根I/O线。

名字有了,我们又怎样让P1.0变“高”或变“低”呢?叫人做事,跟他说一声就可以,这叫发布命令,要单片机做事,也得要向单片机发命令,单片机能听得懂的命令称之为单片机的指令。让一个引脚输出高电平的指令是SETB,让一个引脚输出低电平的指令是CLR。因此,我们要P1.0输出高电平,只要写“SETB P1.0”,要P1.0输出低电平,只要写“CLR P1.0”就可以了。

现在我们已经有办法让单片机去操作P1.0输出高或低电平了,但是我们怎样才能让单片机执行这条指令呢?总不能也对单片机说一声了事吧?要解决这个问题,还得有几步要走:

(1)单片机看不懂SETB、CLR之类的指令,我们得把指令翻译成它能懂的形式,再让它去读。单片机能懂什么呢?大家知道,单片机是由数字电路组成的,数字电路里只有两种信号,不是高电平就是低电平,分别用二进制数中1 和0 来表示,所以单片机它只懂一样东西,就是“0101”这些二进制数字。因此我们得把“SETB P1.0”变为(11010010B,10010000B),或者把“CLR P1.0”变为(11000010B,10010000B),至于为什么是这两个数字,这也是由51芯片的设计者Intel规定的,我们不去研究,并且这个翻译过程也不用我们去操心,有相关的软件帮助我们完成这个工作(伟福软件或者Keil C51都可以),我们后面会给大家介绍。顺便说一下,这种单片机能够看懂的二进制数指令我们也称之为机器语言。

(2)在得到这两个数字后,怎样让这两个数字进入单片机的内部呢?这要借助于一个硬件工具“编程器”。通过编程器,我们就可以将单片机能够看得懂的这两个二进制数指令下载到单片机内部去。

我们来思考一个问题:当我们在编程器中把一条指令写进单片机内部,并连上电路后,单片机就可以执行这条指令了,那么这条指令一定保存在单片机的某个地方,并且这个地方在单片机掉电后依然可以保持这条指令不会丢失,这是个什么地方呢?这个地方就是单片机内部的只读存储器即ROM(Read Only Memory),我们也叫它程序存储器。

为什么称它为只读存储器呢?刚才我们不是明明把两个数字写进去了吗?原来在89C51中的ROM是一种电可擦除的ROM,称为FLASH ROM,刚才我们是用编程器,在特殊的条件下由外部设备对ROM进行写的操作,在单片机正常工作条件下,只能从那里面读,不能把数据写进去,所以我们还是把它称为只读存储器。

好了,我们知道怎样让LED亮起来或灭掉了,就是将SETB指令或者CLR指令经过翻译,得到机器语言文件,再通过编程器将机器语言文件下载到单片机内部,然后再接上电路就可以了。现在让我们再来分析一下怎样才能让这个LED闪烁起来。

怎样才能让灯不断地闪烁呢?实际上就是要灯亮一段时间,再灭一段时间,也就是说要P1.0不断地输出高和低电平。怎样实现这个要求呢?请考虑用下面的指令是否可行:

            SETB P1.0
            CLR P1.0

这是不行的,有两个问题:第一,计算机执行指令的时间很短,执行完“SETB P1.0”后,灯是亮了,但在极短时间(微秒级)后,计算机又执行了“CLR P1.0”指令,灯又灭了,由于人眼的反应没有这么快,根本分辨不出灯是否曾亮过;第二,在执行完“CLR P1.0”后,不会再去执行“SETB P1.0”指令,LED只亮、灭了一次,所以以后再也没有机会让灯亮了。

为了解决这两个问题,我们可以作如下设想:

第一,在执行完“SETB P1.0”后,延时一段时间(几秒或零点几秒),让单片机的P1.0保持高电平状态一定时间后,再执行第二条指令,就可以分辨出灯是否曾亮过了。在执行了“CLR P1.0”指令后,也延时一段时间,就可以看出灯是否曾灭过了。

第二,在执行完第二条指令“CLR P1.0”后,让计算机再去执行第一条指令,重复刚才的过程,这样不断地兜圈,我们称之为“循环”,这样就可以实现不断亮灭闪烁了。

以下先给出程序(后面括号中的数字是为了便于讲解而写的指令序号,实际不用输入):

            ; 主程序:
                    ORG   0030H
            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)

按上面的设想分析一下前面的五条指令。

第一条是让P1.0输出高电平,让灯亮。第二条是调用延时子程序,让P1.0保持高电平一段时间。第三条让P1.0输出低电平,让灯灭。第四条和第二条一模一样,也是延时。第五条是跳转到第一条指令去,重新执行第一条指令。

我们先看第五条指令“LJMP LOOP”,LJMP是指令关键字,意思是转移、跳转,有点像C语言中的GOTO语句,它往什么地方转移、跳转呢?后面跟的是LOOP,看一下,什么地方还有LOOP?对了,在第一条指令的前面有一个LOOP,所以很直观地,我们可以认识到,“LJMP LOOP”指令的功能就是跳到标号LOOP所指的那条指令去,也就是第一条指令处。这个第一条指令前面的LOOP被称之为标号,它的用途就是给这一行起一个名字,便于使用。是否一定要给它起名叫LOOP呢?当然不是,起什么名字,完全由编程序的人决定,可以称它为A,X等等,当然,这时,第五条指令LJMP后面的名字也得跟着改了。

第二条和第四条指令的用途是延时,它是怎样实现的呢?指令的形式是“LCALL DELAY”,这条指令称为调用子程序指令,LCALL是关键字,表示调用的意思。再看一下关键字后面跟的是什么?对了,是DELAY,它是一个标号,表示要调用子程序的名字。子程序由第6 条与第10 条之间的5 条指令构成,子程序的最后一条指令是返回指令RET。“LCALL DELAY”这条指令的作用是这样的:当执行LCALL指令时,程序就转到LCALL后面的标号所标定的子程序处执行,如果在执行指令的过程中遇到子程序结束指令RET,则程序就返回到LCALL指令下面的一条指令继续执行。大家现在可以不用理解太多,只要知道,执行一次“LCALL DELAY”指令,就把子程序执行一次,而本例中该子程序的功能就是让单片机I/O口电平保持一段时间,就行了。

小贴士

在标号DELAY标志的这一行到RET这一行之间的所有语句,是一段延时程序,大概延时零点几秒,至于具体的时间,以后我们再学习如何计算。

该程序的指令,大家不用很详细地理解,我们在后面的章节中会系统地给大家介绍。

程序的开头“ORG 0030H”和最后一行“END”,不是指令,它只是告诉我们程序存放到哪里,到哪里结束,它被称为“伪指令”。其中“END”指令是必须要的,放在程序的最后,它们的详细应用我们在后面介绍。

3)单片机的程序和数据的存放

我们刚才说了,指令一定保存在单片机的某个地方,并且这个地方在单片机掉电后依然可以保持这条指令不会丢失。这个地方就是单片机内部的只读存储器,即ROM(Read Only Memory),也叫程序存储器。

(1)程序存储器:一个微处理器能够聪明地执行某种任务,除了依靠它们功能强大的硬件外,还需要使它们运行的软件。其实微处理器并不聪明,它只是完全按照人们预先编写的程序执行。而设计人员编写的程序就存放在微处理器的程序存储器中,俗称只读程序存储器(ROM)。程序相当于给微处理器处理问题的一系列命令。其实程序和数据一样,都是由机器码组成的代码串。只是程序代码存放于程序存储器中。对于AT89C51 单片机来说,内部自带了4K个ROM单元,我们称之为片内程序存储器,地址范围为0000H~0FFFH,一般的程序都能够装下了,如果我们编写的程序指令太多,单片机自带的片内程序存储器装不下,就可以在单片机外面用ROM集成块构建片外ROM(最多60K个单元,地址范围为1000H~FFFFH),我们在前面讲单片机的基本电路连接时讲到过EA脚要连电源,为什么呢?原来EA脚就是用来决定能不能使用单片机自带的片内ROM的,只有EA脚接高电平时,才能使用片内ROM,如果EA接低电平,单片机电路就只能使用片外的ROM(对于内部无ROM的8031单片机,此时单片机的端必须接地)。

(2)数据存储器:在上面的程序中用到的符号R6、R7是什么意思呢?它们被称为工作寄存器,是数据存储器(RAM)的一部分。什么是数据存储器呢?让我们从现实生活中来找找答案。如果出一道数学题:123+567,让你回答结果是多少,你会马上答出是690,再看下面一道题:123+567+562,要让你马上回答,就不这么容易了吧?我们会怎样做呢?如果有张纸,就容易了,我们先算出123+567=690,把690写在纸上,然后再算690+562,得到结果是1252。这其中1252是我们想要的结果,而690并非我们所要的结果,但是为了得到最终结果,我们又不得不先算出690,并记下来,这其实是一个中间结果,计算机运算和这个类似,为了要得到最终结果,往往要做很多步的中间结果,这些中间结果要有个地方放才行,把它们放到哪里呢?放在前面提到过的ROM中可以吗?显然不行,因为计算机要将结果写进去,而ROM在单片机正常工作状态下是不可以写的,所以在单片机中另有一个区域称为RAM区(RAM是“随机存取存储器”的英文缩写),我们也叫它数据存储器,有点像我们的草稿纸,它可以将数据写进去、读出来。特别地,在MCS-51 单片机中,将RAM中分出一块区域,称为工作寄存器区,工作寄存器区有32个存储单元,每8个存储单元分为一组,一共4 组,单片机在工作时通过一定的方式选择其中的一组,来作为工作寄存器,分别用R0, R1, …, R7来表示,共8个数据存储单元,也就是说程序中的R6, R7是代表了一个个的RAM单元,是用来放一些数据的,这8个存储器是我们用得最多的数据存储器。

下面让我们来系统地介绍一下片内RAM。8051的内部RAM共有256个单元,通常把这256个单元按其功能划分为两部分:低128 单元(单元地址00H~7FH)和高128 单元(单元地址80H~FFH)。

(1)内部数据存储器低128单元的配置如表1-2-1所示。

表1-2-1 片内RAM的配置

低128单元是单片机的真正RAM存储器,可由我们任意使用,就相当于我们前面讲的草稿纸,按其用途划分为三个区域:

① 寄存器区:我们在前面给大家介绍了R0~R7的含义,还有印象吧?寄存器区共有4组寄存器,每组8个寄存单元(各为8位),单片机就是选用这4组中其中一组8个寄存单元作为R0, R1, …, R7。我们也称它们为工作寄存器。CPU到底选4组中的哪一组来作R0, R1, …, R7,由一个专用寄存器PSW中RS1, RS0位的状态组合来决定(我们在后面介绍)。

通用寄存器为CPU提供了就近数据存储的便利,有利于提高单片机的运算速度。此外,使用通用寄存器还能提高程序编制的灵活性,因此在单片机的应用编程中应充分利用这些寄存器,以简化程序设计,提高程序运行速度。

② 位寻址区:内部RAM的20H~2FH单元,既可作为一般RAM单元使用,进行字节操作,也可以对单元中每一位进行位操作,每一位都有自己的一个编号(位地址),因此把该区称为位寻址区。位寻址区共有16个RAM单元,计128 位,位地址为00H~7FH。51单片机某些存储器具有位处理功能(也叫布尔处理功能),我们前面介绍的SETB指令实际上就是一条位操作处理指令,这种位处理能力是MCS-51的一个重要特点。表1-2-2为位寻址区的位地址表。

表1-2-2 片内RAM位寻址区的位地址

③ 用户RAM区:在内部RAM低128单元中,通用寄存器占去32个单元,位寻址区占去16个单元,剩下80个单元,这就是供用户使用的一般RAM区,其单元地址为30H~7FH,可由用户自由使用。

(2)内部数据存储器高128单元:内部RAM的高128单元是供给专用寄存器使用的,其单元地址为80H~FFH。因这些寄存器的功能已作专门规定,故而称之为专用寄存器,也可称为特殊功能寄存器(Special Function Register)。它们有什么用呢?在单片机内部有一些设备可供我们使用,比如计数器,串口通信设备等,大家想想在现实生活中我们的设备是怎样使用的呢?一般情况下每个设备都有些控制开关,我们通过这些控制开关来选择电子设备的相关功能,同样的道理,单片机内部的这些设备也需要一些“开关”来对它们的功能进行设置和控制,这些开关的断开和闭合是通过把特殊功能存储器的相应位设置为1或0来实现的,并能通过某些位为0还是为1反映其运行状态。对于51单片机来说,一般有21个SFR,它们有各自不同的控制功能,我们会在后面的课程一一为大家介绍。

注意:虽然内部数据存储器高128 单元共有128个地址,但单片机的SFR只用了其中的21个地址。

4.任务实施

(1)在Proteus中按照图1-2-1连好硬件电路;

(2)用伟福软件编写程序,并进行编译得到HEX格式文件;

(3)将所得的HEX格式文件在Proteus中加载到单片机芯片中;

(4)开始仿真,看数码管显示有怎样的变化;

(5)Proteus中结果正常后,用实际硬件搭接电路,通过编程器将HEX格式文件下载到AT89C51中;

(6)通电看效果,看灯是否闪烁。

说明:(1)伟福软件的使用说明请参见附录B;

(2)Proteus软件使用方法见附录A。

5.知识点的延伸

在这个任务中我们用P1.0这个引脚使灯亮,可以设想:既然P1.0可以让灯亮,那么其他的引脚可不可以呢?看一下图1-2-2(51单片机的管脚图),在P1.0旁边有P1.1, P1.2, …, P1.7,它们是否都可以让灯亮呢?除了以P1开头的外,还有以P0, P2, P3开头的,一共是32个引脚,它们能不能都让灯亮呢?答案是肯定的。事实上,凡以P开头的这32个引脚都是可以点亮灯的,也就是说:这32个引脚都可以作为输出使用,如果不用来点亮LED,也可以用来控制继电器,或者用来控制其他的执行机构。

这32 条线被我们分成了四组,也就是我们在前面提到的单片机的I/O口P0, P1, P2, P3,每个I/O口在单片机内实际对应着一个8位的存储单元,每一位连一根管脚(实际电路要复杂些,我们姑且先这样理解),比如说P1 口对应着P1.0~P1.7,我们在任务中要让P1.0这一根脚为高电平或低电平,只需要用SETB或CLR指令就可以了。

如果想让P1 口对应的8 根I/O线全输出高电平或低电平,当然可以用8 次SETB或CLR指令,但还有一个更简单的方法,就是向P1 口对应的存储单元送一个8位二进制数,向存储单元送数我们用MOV指令,比如我们要想使P1.7~P1.0这8个脚都输出高电平,只需执行指令“MOV P1,#0FFH”就可以了,这条指令的作用是把FFH(11111111B)这个8 位二进制数送P1存储单元,那么连到P1这个存储单元的8根管脚的高低电平情况就与所连的各位的1 值和0 值对应,即8个管脚全部高电平,同样的道理,如果要想使P1.7~P1.0 这8个脚都输出低电平,只需执行指令“MOV P1,#00H”,也就是说把00H(00000000B)这个8 位二进制数送P1 存储单元,这样就可以了。注意:MOV指令的用法我们会在下一章详细地介绍,现在只是了解一下就可以了。

我们称P0, P1, P2, P3为I/O口,既然是I/O口,除了能够输出高低电平至控制端口上连接的LED、继电器或其他的外围电路,它也能够将这些端口所接的外部电路的高低电平输入到单片机内部,但是要注意:要让某个端口作为输入使用,要先做一个“准备工作”,就是让端口输出“1”才行。为什么要这么做?这是由端口的内部电路决定的,我们在后面会详细地讲解,大家先把它作为一个结论记住。正因为要先做这么一个准备工作,所以称单片机的四个端口为“准双向I/O口”。

下面我们就以P1口为例,介绍一下单片机I/O端口的结构及工作原理。

(1)输出结构:图1-2-3 为P1 口其中一位的电路图,P1端口由8个这样的电路组成,P1口为8位准双向口,每一位均可单独定义为输入或输出口,当作为输出口输出“1”时,“1”通过内部数据总线写入锁存器(D端),使Q(非)为0,T2 截止,内上拉电阻将电位拉至高电平,此时该管脚P1.x输出为1,这样就将数据“1”从管脚P1.x输出去了。

图1-2-3 P1口锁存器和缓冲器结构

那这里为什么要有一个锁存器呢?所谓锁存器实际上就是个D触发器,大家知道D触发器可以将二进制数保存下来,所以有锁存器的存在,使得单片机从总线送过来的数据“1”被保存在这个盒子中,此时,即使单片机去做其他事了,不再从内部总线送数据“1”来了,I/O口仍然可以将锁存在盒子中的这个数据“1”送出到管脚上,保证输出数据不会消失。当“0”写入锁存器,Q(非)为1,T2 饱和导通,流过上拉电阻的电流很大,从而在上拉电阻上产生很大的电压降,VCC电压经过这个很大的电压降后,在P1.x上输出为0,同样的道理,由于有锁存器的存在,只要单片机没有向P1送新数据来,这个低电平会一直保持下去。

(2)输入结构:除了输出之外,还有两根线,一根从外部引脚接入,另一根从锁存器的输出接出,分别标明读引脚和读锁存器。这两根线是用于从外部接收信号的,为什么要两根呢?原来,在51 单片机中输入有两种方式,分别称为“读引脚”和“读锁存器”。

读引脚方式:这种方式是将引脚作为输入,那是真正地从外部引脚读进输入的值;

读锁存器方式:这种方式是该引脚处于输出状态时,有时需要改变这一位的状态,则并不需要真正地读引脚状态,而只是读入锁存器的状态,然后进行某种变换后再输出。

那我们怎么知道单片机是输入引脚上的状态还是输入锁存器的状态呢?这就要看用的是什么指令了,这个我们在第2章再来细说。

我们刚才说了,单片机的四个端口在输入前,必须有个准备过程,就是要先向端口输出“1”数据,为什么呢?我们一起来看看。

请注意图1-2-3,如果将这一根I/O线作为输入口使用,我们并不能保证在任何时刻都能得到正确的结果,为什么?因为T2 有两种状态,即截止和饱和导通,它们的等效电路见图1-2-4,当T2 处于饱和状态时,场效应管等效电路为三个管脚连通,P1.x管脚就等效于与地短路了,此时不管管脚上输入的是高电平还是低电平,单片机接收到的数据都是“0”,只有当T2 处于截止状态时,场效应管三个管脚等效于断路,此时,P1.x管脚与地断开,它的输入值才不会受到影响。

图1-2-4 场效应管T2等效电路图

通过上面的分析,我们知道,要使单片机的I/O口能正确输入,应使T2处于截止状态,而T2的状态由锁存器内的数据决定,要使T2截止,必须向锁存器输出数据“1”,正因为要先做这么一个准备工作,所以我们称之为“准双向I/O口”。

好了,讲了这么久的I/O口的结构与工作原理,同学们是不是听得有点累了?其实,这些工作原理只是为了满足某些喜欢刨根问底的同学的需要,大家即使不是很理解也不影响我们对单片机端口的应用,只要记住下面小贴士中相关的结论就可以了。

小贴士

(1)单片机四个端口可以作输入也可作输出,某个时刻作为输入还是输出由所用的指令来决定。

(2)单片机I/O口在输入之前,一定要先“准备”,即要先输出“0FF”这个数。

想一想,做一做

现在我们把本次任务中的要求改一下,要求大家实现单片机对8个LED的亮灭闪烁控制,试试看,你能完成吗?

附表:元件列表