单片机经典教程 - 图文

更新时间:2024-04-27 13:32:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

单片机经典教程

1 什么是单片机?单片机的结构及组成

单片机,专业名称—Micro Controller Unit(微控制器件),它是由大名鼎鼎的INTEL公司发明的,最早的系列是MCS-48,后来有了MCS-51,现在还有MCS-96系列,我们经常说的51系列单片机就是MCS-51,它是一种8位的单片机,而MCS-96系列则是一种16位的单片机,至于它们之间有何区别,我们以后会讲到。后来INTEL公司把它的核心技术转让给了世界上很多的小公司(不过,再小也有几个亿的销售/年哦),所以世界上就有许多公司生产51系列兼容单片机,比如飞利浦的87 LPC系列,伟邦的W78L系列,达拉斯的DS87系列,现代GSM97系列等等,目前在我国比较流行的就是美国ATMEL公司的89C51它是一种带Flash ROM的单片机(至于什么是Flash ROM,我在这儿先不作介绍,等以后大家学到相关的知识时自然就会明白),我们的讲座就是以该型号的单片机来作实验的。讲到这里,也许有的人会问:我平时在各种书上看到全是讲解8031,8051等型号的单片机,它们又有什么不同呢?其实它们同属于一个系列,只是89C51的单片机更新型一点(事实上,89C51目前正在用89S51代替,我们的实验系统采用就是89S52的,兼容89C52)。这里随便说一下,目前国内的单片机教材都是以8051为蓝本的,尽管其内核也是51系列的,但毕竟8051的单片机已经属于淘汰产品,在市场上也很少见到了,所以由此感

叹,国内的高等教育是如此的跟不上时代的发展需要!这话可能会引起很多人的不满,所以大家千万别传出去哦!

二.主要单片机的分类

再给大家介绍一下我们经常在各种刊物上看到的AVR系列和PIC系列单片机是怎么回事?以便让大家对单片机的发展有一个较全面的认识。在没有学习单片机之前,这是一个令很多初学者非常困惑的问题,这么多的单片机我该先学哪一种呢?

AVR系列单片机也是ATMEL公司生产的一种8位单片机,它采用的是一种叫RISC(精简指令集单片机)的结构,所以它的技术和51系列有所不同,开发设备也和51系列是不通用的,它的一条指令的运行速度可以达到纳秒级(即每秒1000000000次),是8位单片机中的高端产品。由于它的出色性能,目前应用范围越来越广,大有取代51系列的趋势,所以学完了51系列的,看来必须学会AVR的才行,可叹知识爆炸,人生苦短。说完了AVR的,再来说说另一种--PIC系列单片机,它是美国MICROCHIP公司,唉,又是老美,叫微芯公司的生产的另一种8位单片机,它采用的也是RISC的指令集,它的指令系统和开发工具与51系列更是不同,但由于它的低价格和出色性能,目前国内使用的人越来越多,国内也有很多的公司在推广它,不过它的影响力远没有51系列的大,所以作为初学者,51系列当然(不,绝对)是首选。

以上几种只是比较多见的系列,其实世界上还有许多的公司生产各种各样的单片机,比如:MOTOROLA的MC68H系列(老牌的单片机),TI的MSP430C系列(极低功耗的单片机),还有日本的TOSHIBA,日立的HITACH,德国的西门子SIEMENS等等,它们都有各自的结构体系,并不与51系列兼容。为了不搞大家的脑筋,这里就不介绍了,等大家入门了以后自己再去研究它吧!现在我们还是回来了解一下51系列单片机到底是个什么东西,它有那些部分组成,请接着往下看: 三.单片机的结构及组成

单片机到底是一种什么东东,它究竟能做什么呢?其实它就是一种能进行数学和逻辑运算,根据不同使用对象完成不同控制任务的面向控制而设计的集成电路,此话好象有点绕口,没关系,大家都应该知道我们经常使用的电脑吧,在电脑上,我们可以用不同的软件在相同的硬件上实现不同的工作。比如我们用WORD可以打字,用PROTEL可以设计图纸等等,单片机其实也是如此,同样的芯片可以根 们不同的要求做出截然不同的产品,只不过电脑是面向应用的,而单片机是面向控制的,比如控制一个指示灯的亮和灭,控制一台电机的启动和停止等等。 那么它的内部究竟由哪些部件组成的呢?大家都知道我们的电脑有很多的零件,比如CPU(中央处理),RAM(内存),ROM(程序存储器),输入输出设备(并行串行口)等等,在单片机中这些部件都有,而且还把它们全部做到了一块芯片上(这就是单片机名称的由来)。

讲到这里,您一定会想,这么多零件集成在一块芯片上,那为什么单片机的价格会这么便宜(89S51每块才10元左右),其实原因很简单----功能有强弱,就象我们平时用的PLC,控制一台数控机床要用128点的,而控制一台电机有几点的就足够了,另外这种芯片的产量很大,技术也非常的成熟,自然价格也就很低了。那么单片机是如何来工作的,我们学习单片机又需要做哪些准备呢?对于一个初学者来说这是很有必要了解的: 四.学习单片机的准备工作

首先您需要一台电脑,这是最基本的,配置嘛,P2以上的就可以了;然后您需要一套开发单片机的软件,这个软件叫KEIL C51,它是美国Keil Software公司专门为MCS-51系列单片机开发的第三方软件,最新版本是V7.09,安装时选择Uvision2,虽然有2K代码的限制,但足以满足我们学习的需要;其次,您还需要一台编程器,它一种把程序写进单片机芯片的设备,这种设备品种很多,操作也很简单,大家既可以买现成的产品(价格从200多元到2000多元的都有),也可以自己制作;有了这两样东西还不行,为了看到程序执行的结果,我们还需要一块实验板。

好在现在出现了一种支持在线下载的单片机,只要满足一定的外部条件,就能够直接把汇编的程序下载到目标单片机中。经过反复的

实验,我们开发设计了这样的一套实验系统,它采用了一套集源代码编辑、软件汇编、程序下载于一体的专业软件,采用具有在线下载功能的FLASH ROM单片机89S52,配合本教程,可以完成教程中的每一个实验。这样既免去了您添置编程器和仿真器等设备的昂贵费用,又可以直接在实验板上看到程序执行的结果,更由于采用的是FLASH ROM的存储器,烧写次数可以达到1000次以上

单片机经典教程2 单片机引脚功能介绍

1.VCC(40):电源+5V。

2.VSS(20):接地,也就是GND。 3.XTL1(19)和XTL2(18):振荡电路。

单片机是一种时序电路,必须有脉冲信号才能工作,在它的内部有一个时钟产生电路,有两种振荡方式,一种是内部振荡方式,只要接上两个电容和一个晶振即可;另一种是外部振荡方式,采用外部振荡方式时,需在XTL2上加外部时钟信号(详细的内容将在以后的课程中专门介绍)。

4.PSEN(29):片外ROM选通信号,低电平有效。

5.ALE/PROG(30):地址锁存信号输出端/EPROM编程脉冲输入端。

6.RST/VPD(9):复位信号输入端/备用电源输入端。什么是复位信号,为什么要加复位信号?当然也暂时不去管它。

7.EA/VPP(31):内/外部ROM选择端。在30、9脚的功能上不知大家注意没有,都有一个/,什么意思呢?这是引脚的第二功能,也就是说,该引脚既可以作前面的功能,也可以作后面的功能,至于它是如何工作的,我们暂时也别去研究 8.P0口(39-32):双向I/O口。9.P1口(1-8):准双向通用I/0口。

10. P2口(21-28):准双向I/0口。11.P3口(10-17):多用途口。I/O就是英文IN/OUT

的缩写,这些引脚的功能想必大家也都明白了,(就是输入/输出的意思),这32个I/O口就是留给我们作连接外围电路用的,那么它们之间有些什么不同呢?这个问题稍微有点复杂。 二、单片机的电路连接和开发过程

单片机是如何工作的?我们的实验是让一个LED灯亮起来,亮哪一个?这就随便你了,比如我们就让LED1

亮起来吧,仔细看一下电路图,LED1接在什么地方呢?接在单片机的P1.0的引脚(也就是1脚)上,那么按照该电路图的连接方法,当1脚为高电平时,LED1是不亮的;只有当1脚为低电平时,LED1才会亮起来,怎样才能让1脚由高电平变为低电平呢?我们让人做事,就必须对她说一声,也就是发布命令,想让单片机工作,也得发布命令,不过在计算机中那叫指令,我们要让1脚变为低电平的指令是CLR P1.0(让1脚变为高电平的指令是SETB P1.0),这就是我们通常所说的源代码,(这是我们开发产品的第一步—源代码编辑);怎么做呢?我们首先得打开实验软件,屏幕出现一个浏览器的软件窗口,点击左边的扩展实验,选中实验16—自动温度控制器,再点击工具栏里的调试按钮,弹出一个记事本对话框,写入 CLR P1.0; ( ?

分号必须在英文状态下输入 )

,输入完毕后选择文件→保存即可;那么单片机能读懂这条指令吗?当然不能,接下来我们还有一件事情要做,就是把这句指令翻译成单片机能读懂的东西,单片机能读懂什么呢?它其实只懂一样--就是数字,因此,我们就把CLR P1.0翻译成C2H,90H,至于为什么要翻译成这样,这当然是INTEL公司规定好的,我们就不需要去研究它了。这个过程我们叫作编译,(这是我们开发产品的第二步),那么指令是译过来的呢?这就得靠专业的软件了,我们做实验使用的软件就有此功能,只要点击工具栏上的编译按钮,稍等片刻即出现一个编译信息窗口,如果编译通过就会有编译完成,结果如下:0个警告,0个错误的编译信息,如果编译错误则会出现编译错误的信息,并提示错误的行号;编译完了之后通常要进行程序仿真(这是第三步,当然我们的实验程序很简单是不需要仿真的;接下来怎么才能把编译通过的指令写入单片机中呢?这通常需要借助于一种硬件工具,叫编程器(也叫烧录器),不过我们的实验板采用的是具有串行下载功能的单片机,所以您只要直接点击快捷工具栏上的下载按钮,程序就进入了实验(这是第四步—编程)。自此就完成了单片机开发的全过程。 全部工作结束后,我们看到了什么?接P1.0(1脚)的LED1亮了起来;改变源代码,变成SETB P1.0;进 行编译,下载,看看结果是不是LED1不亮了。

单片机经典教程3 单片机内部结构(一)

单片机的内部究竟有哪些部分组成的,它们都有些什么作用呢?让我们先来了解其中的ROM存储器: 一.半导体存储器ROM 1.几个基本概念

上一课我们讲到了把编译后的指令下载到单片机后这条指令一定在单片机内的某个地方,那么它究竟在哪里呢?原来它就放在一个叫程序存储器的地方,英文名称ROM(全称为Read Only Memory),叫只读存储器。它是一个什么东西呢?在讨论这个问题之前,让我们先来看几个物理现象:(1)数和物理现象的关系 不知大家是否还记得,在学习数字电路时我们曾用一盏灯的亮和灭来表示电平的高和低,即用“1”来表示高电平,用“0”来表示低电平,如果现在有两盏灯那它会有几种状态呢?

0 0 0 1 1 0 1 1 两盏灯的组合就是四种状态:00,01,10,11。如此看来灯的亮和灭这种物理现象同数字确实有着某种联系,如果我们把它们按一定的规律排列好,那么电平的高或低就可以用数字来表了,换句话说:不同的数字可以代表不同数量灯的电平高或低。比如:

0000,0001,0010,0011,0100,0101,0110,0111,1000,1001,1010,1011,1100,1101,1110,1111这十六种组合就可以代表四盏灯的状态,能理解吗? (2)位及字节的含义

在单片机中,一盏灯(实际上是一根线)我们称它为一位,它有两种状态(“0”或“1”),分别应电平的高或低,它是单片机最基本的数量单位,用BIT来表示。8盏灯(八根线)有256种状态,这8盏灯(也就是8位)我们把它称为一个字节,用BYTE表示。至于为什么要怎么规定,这就不需要你我操心了,我们只要记住就可以了。那么单片机是如何来储存这些数字所代表的字节的状态的呢?接着往下看:

2.半导体存储器的工作原理

存储器就是用来存放数据的地方,它其实是利用电平的高或低来存放数据的,也就是说,它实际上存放的是电平的高或低的状态,而不是我们所习惯上认为的“1234”这样的数字。那它是如何工作的呢?一个存储器就象一个小抽屉,一个小抽屉里有8个小盒子每个小盒子用来存放1位“电荷”,电荷通过与它相连的电线传进来或释放掉,至于电荷在小盒子里是怎样存放的,这就不用我们操心了,您可以把电线想象成水管,小盒子里的电荷就象是水,那就好理解了存储器中的1个小抽屉我们把它称之为1个“单元”,相当于1个字节,而1个小盒子就相当于1位。有了这么一个构造,我们就可以开始存放数据了,比如我们要放进一个数据“00011010”,我们只要把第2号、第4号和第5号小盒子里存满电荷,而其它小盒子里的电荷给放掉就行了。可是问题又出来了,一个存储器有好多相同的单元,线是并联着的(看D7-D0),在放入电荷的时候,会将电荷放入所有的字节单元中,而释放电荷的时候,会把每个单元中的电荷都放掉,这样的话,不管存储器有多少个字节单元,都只能放同一个数,这当然不是我们所希望的。因此,我们要在结构上稍作变化,看上面的图,在每个单元上有根线与译码器相连,我想要把数据放进哪个单元,就通过译码器给哪个单元发一个信号,由译码器的通过这根线把相应的开关打开,这样电荷就可以自由地进出了。那么这样是不是就能随意地向存储器写入或者读出数据了呢?其实还不能,当我们向存储器写入数据时,必须先把这个开关切换到写入端;而要读出数据时,就得先把开关切换到读出端;而片选端则是为了区分不同的存储器设置的。 3.半导体存储器的译码

简单介绍一下:我们知道,1根线可以代表2种状态;2根线可以代表4种状态;3根线可以代表8种;256种状态又需要几根线代表?8根线,所以一片6264存储器我们只需要16根线就可以了。

4.存储器的选片及总线的概念 至此,译码的问题解决了,让我们再来关注另外一个问题:送入每个字节的8根线又是从什么地方来的呢?它就是从单片机的外部引脚上接过来的,一般这8根线除了接一个存储器之外,还要接其它的器件,这样问题又出来了,这8根线既然不是存储器和单片机之间专用的,如果总是将某个单元接在这8根线上,就不行了,比如这个存储器单元中的数值是“FFH”,另一个存储器的单元是“00H”,那么这根线到底是处于高电平,还是低电平?岂不是要打架看谁历害了?所以我们必须让它们分离。办法当 然也简单,当外面的线接到集成电路的引脚上来后,不直接接到各单元去,中间再加一组开关就行了。这组开关就是前面提到的控制器(看前面的图),平时我们让开关打开着,如果确实是要向这个存储器中写入数据,或要从存储器中读出数据,再让开关切换到相应的位置就行了。这组开关由三根引线选择读控制端、写控制端和片选端,要将数据写入,先由控制器选中该片,然后发出相应的写信号,开关切换到相应的位置,并将传过来的数据(电荷)写入片中;如果要读信号,先选中该片,然后发出读信号,开关也切换到相应的位置上,数据就被送出去了;另外读和写信号还同时受到译码器的控制,由于选端的不同,所以虽有读或写信号,但没有片选信号,所以另一个存储器就不会“误会”而开门,造成冲突,那么会不会同时选中两个存储器呢?只要是设计好的系统就不会,因为它是由计算机来控制的,如 果真的出现同时选中两个存储器的话,那就是电路出故障了。

如此看来,存储器要想写入或者读出数据还真是不简单,不过好在这些都是由计算机自动完成的,不需要我们去操心。从上面的介绍中我们已经看到,用来传递数据的8根线(51单片机是8根)并不是专用的,而是很多器件大家共用的,所以我们把它们称之为数据总线(总线英文名为BUS),即公交道,谁都可以走;而16根地址线(51单片机共有16根地址线,这些以后会讲解,这里不必死记硬背)也是连在一起的,我们把它们称之为地址总线, 5.半导体存储器的分类

第一课中我们提到过,89C51是一种带Flash ROM的单片机,什么是Flash ROM?它到底是一种什么东西呢?ROM我们已经知道,是只读存储器,所谓只读,从字面上理解那就是只可以从里面读出数据,而不能写进去,它类似于我们的书本,发到我们手里之后,我们只能读里面的内容,不可以随意更改书本上的内容。ROM就是单片机中用来存放程序的地方,前面我们下载到单片机的指令就放在这个地方。讲到这里大家也许会感到困惑,既然ROM是只读存储器,那么指令又是如何进入其中的呢?其实所谓的只读只是针对正常工作情况下而言,也就是在使用这块存储器的时候,而不是指制造这块芯片的时候,只要让存储器满足一定的条件就能把数据预先写进去,这个道理也很好理解,书本拿到我们手里是不能改了,但当它还是原材料--白纸的时候,我们完全可以由印刷厂把内容印上去嘛。前面的编程就是这么回事!Flash ROM是一种快速存储式只读存储器,这种程序存储器的特点就是既可以电擦写,而且掉电后程序还能保存,编程寿命可以达到几千至几万次,所以我们的实验系统是可以反复烧写的,您尽管使用。目前新型的单片机都采用这种程序存储器;当然,除了这种程序存储器外,还有两种早期的程序存储器产品,简单介绍一下:PROM EPROM和EEPROM,PROM称之为可编程只读存储器,就象我们的练习本,买来的时候是空白的,可以写东西上去,可一旦写上去,就擦不掉了,所以它只能写一次,要是写错了,就报废了,习惯上我们把带这种程序存储器的单片机称为OTP型单片机,如果您的产品批量生产,又要求价格比较低的话,带这种程序存储器的单片机是非常合适的;EPROM,称之为紫外线擦除的可编程只读存储器,它里面的内容写上去之后,如果觉得不满意,可以用一种特殊的方法去掉后重写,就是用紫外线照射,紫外线就象“消字灵”,可以把字去掉,然后再重写,当然消的次数多了,也就不灵光了,所以这种芯片可以擦除的次数也是有限

的——几百次吧,电脑上的BIOS芯片采用的就是这种结构的存储器;EEPROM,前一种存储器的擦写要用紫外线,而这种存储器可以直接用电擦写,比较方便数据的改写,它有点类似于FLASH存储器,但比FLASH存储器速度要慢,现在新型的外部扩展存储器都是都是这种结构。

了解了ROM,让我们再来简单讲讲另一种存储器,叫随机存取存储器,也叫内存,英文缩写为RAM(Random Access Memory),它是一种既可以随时改写,也可以随时读出里面数据的存储器,类似于我

们上课用的黑板,可以随时写东西上去,也可以用黑板擦随时擦掉重写,它也是单片机中重要的组成部分,单片机中有很多的功能寄存器都与它有关。 二.本课总结

本课主要讲述了单片机的两种半导体存储器—只读存储器ROM和随机存储器RAM的工作原理,它们是单片机的重要组成部分,了解它的内部结构对我们学习单片机是很有帮助的。不过如果您一时对本课的内容还无法搞得很明白,也没有关系,随着学习的深入,我们还会慢慢地讲解相应的基础知识,可千万不要放弃哟?我在没有学会单片机之前也是如此囫囵吞枣的。 一、LED灯闪烁的实验程序

我们要让LED1不断的闪烁,就象大海中用的航标灯。怎样才能让LED1不断的闪烁呢?实际上就是让它亮几秒,再灭几秒,也就是让P1.0交替地输出高电平或低电平,按照前面所学的知识,我们写出下面的程序:CLR P1.0; SETB P1.0;编译后下载到单片机?

这里有两个问题:首先计算机执行指令的速度很快,执行完第1条指令后LED1是灭了,但在极短的时间内又去执行第2条指令,LED1又亮了,我们根本无法看到灯曾经灭过;第二个问题是当执行完第2条指令后,不会再去执行第1条指令了,因为单片机执行指令的过程是一条一条地顺序执行的。

如何解决这两个问题呢?我们可以作如下的设想:第一,执行完第1条指令后让单片机延时一段时间(几秒或零点几秒),然后再去执行第2条指令,这样就可以看到LED1曾经灭过了;第二,让单片机执行完全部指令后再返回去执行第1条指令,如此不断的循环就可以达到我们的要求了。 实验程序如下: 主程序

MAIN:SETB P1.0 ; (1) LCALL DELAY ; (2) CLR P1.0 ; (3) LCALL DELAY ; (4) LJMP MAIN ;

(5) 子程序 DELAY:MOV R7,#250 ; (6) D1:MOV R6,#250 ; (7) D2:DJNZ R6,D2 ; (8) DJNZ R7,D1 ; (9) RET ; (10) END . (11)

还记得软件的使用方法吗?调试,写入源代码,编译,下载到单片机,看看是不是我们想要的结果?? 在分析这段程序之前,先来说明几个标点符号的意义: 1.分号在这里起一个分隔符的作用,表示这条指令到此为止;

2.括号内的数字在这里是为了解释程序用的,实际的编译过程中是没有意义的,也就是说没有也是一样的,只是为了程序的可读性更强,我们一般会在分号的后面加上程序的注释文字(后面我们会用到);

3.特别?:程序中的标点符号只能在英文状态下输入,当使用中文输入时,必须切换到半角状态,不然编译软件会出错。接下来我们分析一下这段程序:按照我们的要求,第1条,让灯灭,第2条应该是延时,第3条是让灯亮,第4条和第2条一样也应该是延时,第5条应当返回去执行第1条指令。看一下上面的程序,第1条我们已经懂了,是让LED1灭,第2条和第4条我们等一下讨论,第5条是LJMP MAIN,LJMP是一条指令,意思是转移,转移到什么地方去呢?看一下LJMP后面跟着什,是MAIN,什么地方有MAIN,在第1条指令的开头就是MAIN,所以第5条指令的意思就是跳转到MAIN(即第1条指令处继续执行),如此一来,就不断地重复执行这些指令。那么MAIN又是什么意思呢?它实际上是我们为这段程序起的

一个名称,专业术语叫标号,既然是一个名称那可不可以用mcu,CHINA等等的其他名字呢?当然可以,这完全取决于您的需要(?:不过也有一些是不能采用的,我们以后再讲)。再来分析第2条和第4条指令,看看它们是如何实现延时的?

LCALL DELAY,LCALL也是一条指令,这条指令叫做调用子程序指令,看看LCALL后面跟着的是什么--DELAY,哪里有DELAY,在第6条指令的开头,很显然这也是一个标号,这条指令的作用就是当执行到这条指令时就转去执行LCALL后面标号所在处的程序,如果在执行程序时遇到RET指令(RET叫返回指令),就返回到LCALL指令的下面一条(即第3条指令)处继续执行,在第9条指令后确实有RET指令,那么在执完第2条指令后就应该去执行第6.7.8.9条指令,之后遇到第10条指令:RET,执行完这条指令后就回去执行第3条指令,将P1.0

清零,也就是让LED1亮,然后再去执行第4条指令,执行完后又回到6.7.8.9.10

条指令,最后执行第5条指令:LJMP MAIN,也就是我们刚才说的跳转到第1条,将P1.0置位,就是LED1 灭掉。如此周而复始,LED1就不断的闪烁。好好理解这段文字,务必把它搞清楚!!!

从标号DELAY处(即第6条)开始到RET的这一段指令我们称之为子程序,它是一段延时程序,至于延时多长时间,我们会在以后的课程中学习。程序的最后一条是END,它不是指令,它只是告诉编译软件整个程序到此结束了,它叫“伪指令”。在大家以后的编程中,写完程序都要加上这一条。

在上面的程序中我们知道了从标号DELAY开始的子程序是一段延时程序,那么它又是如何工作的呢?在了解它的工作过程之前我们必须先知道其中的一些符号,就从R7开始吧,它是单片机内部的一个重要组成部分,叫工作寄存器,什么是工作寄存器?下面我们就来讲解这个问题: 二.工作寄存器

上一课我们已经讲过,在单片机中有许多的功能寄存器和半导体存储器RAM有关,那么工作寄存器又属于哪一部分呢?它是用来干什么的呢?要搞清楚这个问题,让我们先从日常生活中的一个例子说起,如我们要做一道数学题123+456,您会马上得出答案:579,接下来再看一道题:

123+456+789,要你马上得出答案就不那么容易了,通常我们会怎么做呢?一般总是先把123+456的结果

579写在一张纸上,然后再算579+789=1368,这1368就是我们想要的最终结果,而579只是为了得到最终结果而暂时记下来的中间结果,单片机中做运算和我们生活中做运算一样,也需要把中间结果放在某个地方,那么计算机把它放在哪儿呢?前面我们提到的ROM(只读存储器)中,不行!因为ROM是用来存放程序的,它只能写进去,能读出来(再次提醒一下,这只是相对而已),所以只能放在单片机的另一个区域—RAM中(即随机存取存储器)中。R7就是RAM区域中划出的一部分。知道了R7,接下来让我们来分析一下这段子程序(延时程序)。

三.LED灯闪烁程序子程序的分析

首先看第6条,MOV R7,#250,这也是一条指令,意思是传递数据。我们知道在日常生活中,要传递一件东西就必须要有一个传递者,一个接受者和被传递的东西,那么在单片机中是怎么区分它们的呢?在这条指令中,R7是接受者,250就是要传递的东西(单片机中要传递的东西当然是数字了),这

里传递者被省略了(顺便提一下,并不是每条指令都能省略的,事实上大部分的指令都要有传递者)

,这样一来,这条指令的意思也很清楚了:就是把250这个数传递给R7这个工作寄存器(也就是把250个数送入R7中),这样执行完这条指令后R7中的值就应该是250,我们可以用DUBG8051这个软件来验证一下,看是不是符合。讲到这里,不知大家注意没有,在250这个数的前面有个#,它是什么意思呢?这个#就说明250是一个被传递的数的本身,而不是传递者。看懂了MOV R7,#250,那么MOV R6,#250也应该很清楚了。

接着看第8条DJNZ R6,D2,这又是另一条指令,我们来看一下DJNZ后面跟着什么,一个是R6,一个是D2,R6我们已经知道了,再找一下D2,D2在本行的开头,我们已经学过,它是标号。那么这条指令是怎么执行的呢?它的执行过程是这样的:它将后面的值(即工作寄存器R6中的值)减1,然后查一下这个值是否等于“0”,如果等于“0”就往下执行,如果不等于“0”就转移,转移到什么地方去呢?大家应该明白了,实际上这条指令的执行结果就是在原地转250次;当R6中的值等于“0”之后,程序就去执行第9条指令,也就是DJNZ R7,D1,大家自行分析一下这条指令的结果(是不是转去执行MOV R6,#250,同时R7中的值减1),这段子程序的最终执行结果就是DJNZ R6,#250这条指令被 16 执行了250*250=62500次,执行这么多次干吗?就是为了延时。 单片机经典教程5 单片机内部结构(三) 1.时序的由来

已经知道单片机执行指令的过程就是顺序地从ROM(程序存储器)中取出指令一条一条的顺序执行,然后进行一系列的微操作控制,来完成各种指定的动作。它在协调内部的各种动作时必须要有一定的顺序,换句话说,就是这一系列微操作控制信号在时间上要有一个严格的先后次序,这种次序就是单片机的时序。就好比学校上课时用的电铃,为了保证课堂秩序,学校就必须在铃声的统一协调下安排各个课程和活动。那么单片机的时序是如何规定的呢?接着往下看: 2.时序的周期

计算机每访问一次存储器的时间,我们把它称为一个机器周期,它是一个时间基准,就象我们日常生活中使用的秒一样,计算机中一个机器周期包括12 个振荡周期,什么是振荡周期?一个振荡周期是多少时间?振荡周期就是振荡源的周期,也就是我们使用的晶振的时间周期,一个12M 的晶振,它的时间周期是多少,如果电子技术学得好的朋友应该不难算出(T=1/f),也就是1/12 (微秒),那么使用12M 晶振的单片机,它的一个机器周期就应该等于12*1/12(微秒),也就是1μS。

在MCS-51 系列单片机中,有些指令只要一个机器周期,而有些指令则需要两个或三个机器周期,另外还有两条指令需要4 个机器周期,这也不难理解,你在家擦地板的话总比擦桌子的时间要长,不过我可是大男子主义,从来不做家务的。开句玩笑!!!如何衡量指令执行时间的长短?我们就要用到一个新的概念:指令周期—即执行一条指令所需的机器周期,INTEL 公司规定了每一条指令执行的机器周期,当然这不需要我们非把它记住,不过在这里DJNZ 指令我们是要记住的,它是双周期指令,执行一次需要两个机器周期,即2μS。(12M 晶振的话),回到我们上一课的实验,延时的时间就应该算出来了吧,是62500*2μS=125000μS,也就是125mS 。这么大的数字也就0.125S,怪不得LED1 闪烁的这么快。二.单片机的时钟电路,单片机是在一定的时序控制下工作的,那么时序和时钟又有什么关系呢?时钟是时序的基础,单片机本身就如同一个复杂的同步时序电路,为了保证同步工作方式的实现,电路就要在唯一的时钟信号控制下按时序进行工

作。那么单片机内的时钟是如何产生的呢? 1. 内部时钟电路

在MCS-51 单片机的内部有一个高增益的反相放大器,其输入端为引脚XTL1(19),输出端为XTL2

我们只要在外部接上两个电容和一个晶振,就能构成一个稳定的自激振荡器,它的内部电路的工作原理就不介绍了,这里主要讲一下电容和晶振的选择,看上面的图,晶振的大小与单片机的振荡频率有关,我们到串行接口时再详细讲解,电容的大小影响着振荡器振荡的稳定性和起振的快速性,通常选择10-30P 的瓷片电容或校正电容;另外在设计电路时,晶振和电容应尽可能的靠近芯片,以减少PCB 板的分布电容保证振荡器振荡工作的稳定性,提高系统的抗干扰能力 2. 外部时钟电路

除了内部时钟方式外,单片机还可以采用外部引入时钟的振荡方式,什么时候需要采用外部时钟方式呢?当我们的系统由多片单片机组成时,为了保证各单片机之间时钟信号的同步,就应当引入唯一的公用的外部脉冲信号作为各单片机的振荡脉冲,此时应将XTAL2 悬空不用,外部脉冲信号由XTAL1 引入,如上右图所示,外部信号的高低电平持续时间应大于20mS,

单片机经典教程6 单片机内部结构(四) 一、单片机I/O 口的输出实验 1.实验程序 程序如下:

LOOP:MOV P1,#0FFH ; LCALL DELAY ; MOV P1,#00H ; LCALL DELAY ; LJMP LOOP

调试,写入源代码,编译,下载,看到了什么?8 只LED 灯都在闪烁(注意:前面的实验是让一个LED 灯闪烁),分析一下程序: 2.程序分析

这段程序和前面的程序比较,有两处不同,第1 条,原来是SETB P1.0,现在改为MOV P1,#0FFH,第3 条,原来是CLR P1 ,现在改为MOV P1,#00H 。为什么这样改了之后就变成了8 只LED 灯同时闪烁了?原来P1 代表了P1.7-P1.0 的全部,我们把它当作一个存储器单元(即一个字节),不过对一个存储器单元送数就应该用MOV 指令了;在这里P1(P1.7-P1.0 )接的是LED 灯(也就是负载),它起到了一个输出端的作用。那如果把P1 改为P2 或P3 或P4 行不行呢?答案是肯定的,为什么?我们稍后再谈,接着看第2 个实验。二.单片机I/O 口的输入实验 1.实验程序 程序如下:

MAIN:MOV P3,#0FFH ;

把程序下载到单片机,按下第1 个按钮,第1 个LED 灯亮了,按下第2 个按钮,第2 个LED 灯亮了,松开按钮,相应的灯就灭了,是不是有点象工业控制中的点动控制原理。分析一下这个程序: 2.程序分析

看附图的硬件接线图,有4 个按钮分别接到了P3.2,P3.3,P3.4,P3.5 ,引脚上。再来分析一下程序,第1 条,使P3 口(包括P3.7-P3.0 )全部为高电平(为什么MOV P3,#0FFH 能使P3 口全部为高电平,我们在下一课中讨论);第2 条MOV A,P3;MOV 我们已经知道,是送数的意思,这条指令的意思就是把P3 口的数送到A 中去,A 是什么呢?我们也可以把它看成一个中间单元,就象R7 寄存器一样,第3 条指令就是把A 中的数送到P1 口去;第4 条是循环,这些我们都已经见过,当我们按下P3.2 所连接的按钮时,#0FFH 这个数就被送到了A 中,通过程序又送到了P1,使P1.2 输出低电平,LED3 就亮了,按下P3.3-P3.5 连接的按钮,对应的LED4-LED6 也亮了,松开按钮,相应的LED 灯就灭了。如果把按钮接到P2.0-P2.7 或P4.0-P4.7 可不可以呢?当然可以。所以在这里P3 口又起到了一个输入端的作用。 由上面两个实验我们得出结论,凡是以P 开头的管脚都可以用作输入输出口,在89C51 中这32 个管脚我们就称之为并行口。它们实际上就是特殊功能存储器SFR (什么是特殊功能寄存器,我们后面再讲)中的四个,记作P0,P1,P2,P3,它们都是双向通道,即既可以作为输出口,也可以作为输入口,作输出时数据可以锁存,作输入时数据可以缓冲,那么它们是怎么实现输入输出功能的呢?继续往下看。 三.单片机并行口的结构分析先来看看输入结构: 1.输入结构

I/O 口作为输入口时有两种工作方式,即所谓的读端口与读引脚。读端口时实际上并不从外部读入数据,而是把端口锁存器的内容读入到内部总线,经过某种运算或变换后再写回到端口锁存器。比如取反,置位,清零等等指令;而读端口时才真正地把外部的数据读入到内部总线,图中的两个三角形表示的就是输入缓冲器,CPU 将根据不同的指令,分别发出“读端口”或“读引脚”信号,以完成不同的操作,

这是硬件自动完成的,不需要我们操心。

读引脚时,也就是把端口作为外部输入线时,首先要通过外部指令把端口锁存器置“1”,然后再实行读引脚操作,否则就可能读入出错。为什么?看上面的图,如果不对端口置“1”,端口锁存器原来的状态有可能为“0”(Q 端为0,Q^为1)加到场效应管栅极的信号为“1”,该场效应管就导通,对地呈现低阻抗,此时即使引脚上输入的信号为“1”,也会因端口的低阻抗而使信号变低,使得外加的“1”信号读入后不一定是“1”,若先执行置“1”操作,则可以使场效应管截止,引脚信号直接加到三态缓冲器中,实现正确的读入。由于在输入操作时还必须附加一个准备动作,所以这类I/O 口被称为“准双向”口,MCS-51 的P0,P1,P2,P3 口作为输入/输出口时都是“准双向”口。接下来让我们再看另一个问题,从图中可以看出,这四个端口还有一个差别,除了P1 口外,P0,P2,P3 口都还有第二功能,这些第二功能又是作什么用的呢?

2.端口的工作原理(1)。P0 口

先来看P0 口,从图中可以看到,P0 口的内部有一个2 选1 的选择器,受内部信号的控制,如果在图中的位置则处在I/O 口工作方式,此时相当于一个“准双向口”,输入时须先将口置“1”,每根口线可以独立定义为输入或输出,但是须在口线上加上拉电阻。如果将开关往另一个方向,则就是另一个功能—作为地址/数据复用总线用,此时不能逐位定义为输入/输出,它有两种用法:当作数据总线用时,输入8 位数据;而当作地址总线用时,则输出8 位地址。再强调一点,当P0 口作为地址/数据总线用之后,就再也不能作I/O 口使用了。讲到这里,也许大家会感到困惑,什么叫作地址/数据复用?这其实是当单片机的并行口不够用时,需要扩展输入输出口时的一种用法,知道了P0 口,再来看P1 口。 (2)。P1 口

同P0 不同,P1 口只能作为I/O 口使用,但它的内部有一个上拉电阻,所以连接外围负载时不需要外接上拉电阻,这一点P1,P2,P3 都一样,务必请大家注意。 (3)。P2 口

P2 口作为I/O 口线用时,与P0 口一样,当内部开关向另一个方向时,即作地址输出时,可以输出程序存储器或外部数据存储器的高8 位地址,并与P0 口输出的低地址一起构成16 位的地址线,从而可以分别寻址64K 的程序存储器或外部数据存储器,同样地址线是8 位一起自动输出的,不能象I/O 口线那样逐位定义。 (4)。P3 口

P3 口作为I/O 口线用时,同P1 口相同,也是“准双向口”;不同的是,P3 口的每一位都有另一种功能,也叫第二功能,各位的功能如下,它们的具体作用我们用到时再详细解释。

既然单片机的引脚有第二功能,那么CPU 是如何来区分的呢?这是一个令许多初学者困惑的问题,其实单片机的第二功能是不需要人工干预的,也就是说只要CPU 执行到相应的指令,就自动转成了第二功能。了解了各个I/O 口的功能和作用,再来给大家讲解一下单片机I/O 与外围电路的连接方法。 四.单片机I/O 口的连接方法

当单片机的I/O 口作输出时可以直接与外部设备连接,不过由于在实际的应用中,由于其驱动电流是有限的(P0 口10mA,P1,P2,P3 口20mA),所以我们常常需要通过接口电路来扩展它的驱动能力,在单片机的后向通道控制系统中,常用的功率控制器件有机械继电器、晶闸管、固态继电器等等,下面我们将以机械继电器和固态继电器的应用为例介绍其具体的使用方法。 1. 单片机与机械继电器的接口

单片机的一个I/O 口只能灌入20mA 的电流,所以往往不足以驱动一些功率开关(比如稍大一点的机械继电器等),此时,就应该采用必要的扩展电路,如何来实现单片机与机械继电器的接口呢?其实很简单,我们通常采用下面的接法(如图),为了防止前向通道信号的干扰,常采用一些光电隔离器件,比如光电耦合器4N25,PC814 等,当单片机的P1.0 脚输出为低电平时,光藕受电导通,Q1 饱和开通,继电器吸合,负载电路接通。这里请注意?:P0-P3 口作输出控制端时,应尽量采用低电平控制方法,这是因为在低电平时,I/O 口允许灌入的电流比高电平时要大,一般情况下,低电平的灌入电流为高电平的4 倍。

另外为了防止电压间的互相干扰,继电器的工作电压VDD 与单片机的工作电压VCC 不要使用同一个电源,接地端也不要连在一起,即所谓的模拟地与数字地分开,驱动管的电流要大于继电器的工作电流,其他的元件就不讲了,大家自行分析一下。讲了单片机与继电器的接口,再来介绍与固态继电器的接口方法,接着往下看: 2. 单片机与固态继电器的接口

普通继电器由于开关速度慢、易跳火、易机械磨损,通常用于要求不高的场合,在某些特殊应用场合,比如防火、防爆等系统中,则应采用固态继电器。固态继电器是一种无触点的电子继电器,它的输入端只要很小的控制电流,可以与单片机的I/O 口直接连接;输出则采用双向晶闸管控制,其输入输出间均通过内部光电耦合器隔离,可以防止信号间的干扰,是单片机接口的理想器件,随着其技术的成熟,应用的广泛,价格也已经非常的便宜,1A/250V 的目前在10 元左右,它与单片机的连接方法如图所示,当“-”端所接的P1.0 为低电平时,SSR 导通,负载工作。除了以上两种连接方法外,单片机与TTL,CMOS 管等都可以连接。.

单片机经典教程7 单片机内部结构(五)

讲到了指令MOV P3,#0FFH 能使P3 口全部为高电平,而在第四课中LED 灯闪烁程序中给R7 送数用的指令是MOV R7,#250 ,那么这#250 和#0FFH 到底有什么不同?它们又代表什么意思呢? 复习一下数字电路中学过的数制概念:

一.数制

1.十进制数(Decimal Number)

在日常生活中,我们表示数的多少用的是十进制数,即0,1,2,3,4,5,6,7,8,9。它遵循“逢十进一,借一当十”的原则,通常我们把计数符号的个数叫做基数,十进制的基数就是十。

比如一个十进制数5847=5*1000+8*100+4*10+7*1 ,它的每一个数码都有一个系数1000,100,10,1;这个系数叫做权或位权。十进制数虽然非常符合我们的使用习惯,但计算机中却无法采用,因为计算机只能有两种状态:“0”和“1”,所以我们还得应用二进制数。 2.二进制数(Binary Number)

二进制的基数为二,0 和1,它遵循的是“逢二进一,借一当二”的进借位原则。也就是当某位计数到两个数时就向高位进“1”,同时本位变为“0”。

比如二进制数1100=1*23+1*22+0*21+0*20,二进制数只有0 和1 两个数,正好代表了计算机中电路的两种状态,所以它在计算机中被广泛应用。下面是二进制的加法和乘法运算规则: 加法:0+0=0;1+0=0+1=1;1+1=10 乘法:0*0=0;1*0=0*1=0;1*1=1

二进制数虽然在计算机中处理很方便,但当位数较多时,就不容易记忆和书写了,所以计算机中又有了十六进制数。 3.十六进制数(Hexadecimal Number)

十六进制也遵循两个规则,一是有十六个基数,即0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F;另一个规则是“逢十六进一,借一当十六”。

比如我们前面提到的#0FFH 就是一个十六进制数,#--我们已经明白了,它表示的是传递数的本身,“H”叫数制简码,它表示这个数是十六进制数,为什么前面我在标题后面都加了英文注释,相信大家也应该明白了吧(这里随便提一下,二进制简码B 和十进制简码D 通常是可以省略的,我们以后的课程中用到的数都是这样写的),那么0FFH 这个十六进制数的表示方法是怎么样的呢?用十进制表示0FFH=F*161+F*160,即等于255 。(大家也许会疑问?这里的“0”到哪里去了呢?原来,在单片机中,当我们用十六进制格式表示一个数时,如果高位的数字为“A-F ”时,高位前面就得加上个“0”,不然,编译软件会出错,就象#0FFH。二.进制之间的转换

十进制有使用比较习惯的特点,二进制有易于表示和运算方便的特点,十六进制又有表示位数较多的特点,但有时我们常常要把十进制数转换成二进制数或十六进制数来处理;把二进制数逆转换成十六进制数,如何进行这种转换呢?下面就举几个例子: 1. 十进制数与非十进制数之间的转换 (1)非十进制数转换为十进制数

具体做法是:将一个非十进制数按权展开成一个多项式,每项是该位数码与相应权值之积,把多项式按十进制的规则进行计算求和,所得的结果就是该数的十进制形式。

比如:二进制数1011B 转换成十进制为1*23+0*22+1*21+1*20=8+2+1=11D ,再比如:十六进制数FFH 转换成十进制为255D。 (2)十进制数转换为非十进制数

十进制数转换为非十进制数时,可将其分为整数部分和小数部分分别进行转换,最后将结果合并为目的数。为了简单,我这里只讲整数部分的转换。这种转换叫做除基取余法,具体做法是用欲转换数制的基数去除十进制数的整数部分,第一次除所得余数为目的数的最低位,把得到的商再除以该基数,所得余数为目的数的次低位,依次类推,继续上面的过程,直至商位为0,此时所得余数为目的数的最高位。 比如:将十进制数53D 转换成二进制数为53D=110101B。 2└53 2└26??1 2└13??0 2└6??1 2└3??0 2└1??1 0 ??1

2. 二进制数与十六进制数之间的转换

四位二进制共有16 种组合,而这16 种组合正好与十六进制数的16 个基数一致,所以每4 位二进制数对应一位十六进制数,我们只要把二进制数的整数部分自右向左每4 位一组,最后不足4 位的用0 补足;小数部分自左向右每4 位一组,最后不足4 位的在右面补0,再将每4 位二进制数对应的十六进制数写出即可。相反,如果将十六进制数转换为二进制数只需将每位十六进制数写成对应的4 位二进制数即可。

比如:将1101011B 转换成十六进制数为D6H,再比如:将F0FH 转换成二进制数为111100001111B 。 上面的表格就是二进制数、十进制数和十六进制数之间的对应关系。 三.立即数的写法

通过前面一小节的讲解,我们已经懂了,MOV R7,#250 和MOV R7,#OFFH 中#250 和#0FFH 原来是十进制数250D 和十六进制数FFH 的区别,在单片机中,通常我们把这个数称之为立即数,那么如果我在编写指令时把立即数#0FFH 写成二进制数(即11111111 )或用十进制写法(255 )是不是可以呢?当然可以,立即数既可以是二进制数,也可以是十进制数或十六进制数。不过有一点再重复一遍:那就是当

用十六进制格式表示一个立即数时,如果高位的数字为“A-F”时,高位前面要加上个“0”,请大家务必记住了。

这里再讲一下,关于数制以及二进制、十进制和十六进制数的关系,大家可以在以后的实践中慢慢去理解和掌握,如果您一时记不住,千万不要刻意地去死记硬背!下面让我们来讨论另一个问题: 四.存储器的地址

什么是存储器的地址,地址和数据又有什么关系呢?这个问题往往让初学者非常的难以理解,既然单片机存储器内存放的是数据,为什么还要有地址的概念?让我们从生活中的一个例子谈起:大家都知道寄信是怎么回事吧!我们要寄一封信就必须写好信的内容,然后在信的封面写上详细地址,邮局才能按地址把它寄出去;我们给单片机送数也一样,除了要给出立即数(犹如信的内容),还必须知道这个数送达的地址(犹如信的地址或邮政编码),所以就必须给每个寄存器(即半导体存储器)都规定不同的地址,只不过在单片机中地址的编码也是用数字来表示的,那么单片机中有多少个寄存器呢?它们的地址又是如何规定的呢?

前面我们学过,单片机有两种存储器,即只读存储器ROM 和随机存储器RAM,它们都被规定了各自的地址,我们把它称做寻址空间。既然是空间,就必然有一个范围的概念,接下来就让我们看看MCS-51 单片机中程序存储器ROM 的寻址范围: 1.内部R0M 的寻址范围

89C51 的内部有4K 的FLASH ROM 空间,其寻址范围为000H-0FFH(16*16*16),这4K 的ROM 空间就是用来存放我们为单片机编写的程序的,单片机执行指令时就是一条一条地顺序地从ROM 中寻找指令进行执行。了解了ROM 的寻址范围,让我们接着来看另一种存储器:内部RAM 的寻址范围

单片机的内部RAM 共有256 个字节,寻址范围为00H-FFH(即16*16),它被分为两个部分:第一部分从00H-7FH 共128 个字节是真正的RAM 区,可以用来读写各种数据,在这128 个字节中,又分成三个区域:第一个区域00H-1FH 安排了4 组工作寄存器,每组用8 个字节,共32 个字节,分别为R0-R7,当然在同一时刻,只能用其中的一组工作寄存器,怎么来控制它,就要用程序状态字PWS 中的RS0、RS1 两位,(这我们后面再讲);第二个区域20H-2FH 共16 个字节除了可以作为一般的RAM 单元读写外,还可以对每个字节的每一位(即每一个抽屉中的每一个小盒子)进行操作,并且对这些位都规定了固定的位地址:从20H 单元的第0 位开始到2FH 单元的第7 位结束共128 位;第三个区域就是一般的RAM 单元,地址为30H-7FH,共80 个字节;第二部分从80H-FFH 是专门用于特殊功能寄存器(SFR)的,89C51 共用21 个特殊功能寄存器(这些我们都将在下一课中讲解),它们每个也都有8 位,这部分的128 个字节并没有全部用完。

单片机经典教程8 单片机内部结构(六)

我们已经讲过,R7,R6 是工作寄存器,P0,P1,P2,P3 是并行口,那么单片机中还有些什么东西?它们的结构又是怎么样的呢?这就是本课要讨论的问题。一.单片机的特殊功能寄存器,在单片机中,除了前面介绍的RAM,ROM,P0-P3 和CPU 外,方框内的还有许多其他的东西它们被称为特殊功能寄存器,英文简写SFR ,下表例出的就是MCS-51 单片机中几个常用的特殊功能寄存器。这一课我们先来介绍几个:

二.几个常用的特殊功能寄存器 1.累加器ACC

通常用A 表示,它是一个什么东西呢?我们知道单片机在做运算时它的中间结果需要放在某个地方,这个地方就是累加器,它的名字很特殊,功能也很特殊,几乎所有的运算类指令都离不开它。 2.寄存器B

B 寄存器在做乘法时用来存放一个乘数,在做除法时用来存放一个除数,不做乘除法时随你怎么用。 3.程序状态字PSW

它是一个很重要的东西,里面放了CPU 工作时的很多状态,知道它就可以了解CPU 当前的工作状态,它有点象平时看书用的目录,我们浏览它就可以了解一本书的内容。它是一个8 位的寄存器,用到了其中的7 位。其格式如下: 下面来逐位介绍它的功能: (1)CY:进位标志位

MCS-51 是一种8 位的单片机,它的运算结果只能表示到28(即0-255),但我们有时候的运算结果要超过255 ,怎么办呢?就要用CY 位。例如:79H+87H(01111001+01010111)=1 00000000 ,这里的“1”就进到了CY 中去了。 (2)AC:半进位标志位

当D3 位向D4 位进位/借位时,AC=1,通常用于十进制调整运算中。 (3)F0:用户自定义标志位

由编程人员自行决定,什么时候用,什么时候不用。 (4) RS1、RS0:工作寄存器组选择位。

前面讲到单片机共有四个工作寄存器组(0 组-3 组),它们就是由RS1,RS0 来控制,这两位就在这里,它共有四种组合状态,看上面的表格:每个工作寄存器组有8 个字节,分别记为R0-R7 ,当然在某一时刻,CPU 只使用其中的一组。

假设PSW 为“10”(即00010001),那么RS1=1,RS0=0,则用到了第2 组寄存器组(地址10H-17H),RO-R7 即为10H-17H ,用DUBG8051 软件输入数值,看看内部RAM 中地址为10H-17H 中的值是不是为输入值。 (5) 0V:溢出标志位

什么时候溢出,我们讲到定时器时再研究。

(6) P:奇偶检验位

每次运算结束后若A 中二进制数“1”的个数为奇数,则P=1 ,否则P=0 。例:某运算结果是58H (01011000),显然“1”的个数为奇数,所以P=1。

4.DPTR(DPH,DPL):数据指针

数据指针是一个16 位的寄存器,我们可以用它来访问外部RAM ,也可以访问外部ROM 中的表格,具体应用以后再讲。 5.SP:堆栈指针:

让我们先来理解一下堆栈是什么意思?你在家洗碗吗?我们洗好碗之后,是怎么放的呢?一般总是先洗的放在下面,晚洗的放在上面,然后用的时候呢,总是晚放上去的先用,先放上去的后用;如果你不洗碗不要紧,知道码头上仓库里堆的货物吗?一般也是先进去的后出来,而后进去的先出来,这种符合“先进后出,后进先出”存放规则的现象我们就把它叫做“堆栈”。(其实栈在中文中的意思就是码头)。 在单片机中,我们可以在内部RAM 中构造出(注意?:是可以构造)这样一个区域,这个区域存放数据的规则就符合堆栈中“先进后出,后进先出”的原则。为什么要有这样一个区域呢?存储器本身不也同样可以存放数据吗?是的,知道了存储器地址确实可以读出它里面的内容,但如果我们要读出的是一批数据,每一个数据都要给出一个地址就会很麻烦,为了简化操作就可以利用堆栈的存放方法来读取数据,具体的应用我们将在十五课中结合具体实验来讲,这里只是让大家先了解一下。那么堆栈在单片机的什么地方?也就是说把RAM 空间的哪一块区域作为堆栈呢?这就不好定了,因为51 系列单片机是一种通用的单片机,每个人的实际需要各不相同,有人需要多一些堆栈,而有人则不需要那么多堆栈,所以INTEL 公司就干脆不分了,把分的权利让给用户(编程者),也就是说我们可以根据自已的需要来决定,所以单片机中堆栈的位置是可以变化的,而这种变化就体现在SP 中值的变化,看下面的图,SP 中的值等于27H 不就相当于是一个指针指向27H 单元吗?这就是堆栈指针的由来。

当然在MCS-51 单片机中,指针开始所指的位置并非就是数据存放的位置,而是数据存放的前一个位置。例如一开始堆栈指针是指向27H 单元的,那么第一个数据的存放位置就在28H 单元中,而不是27H 单元中,这一点请大家注意。 6.电源控制寄存器PCON

单片机在以电池供电的系统中,有时为了节电,我们需要让它尽量降低电源的消耗,所以单片机就有多种的工作方式,其中一种就是低功耗方式,PCON 寄存器就是用来控制单片机进入低功耗方式的。 单片机经典教程9 单片机的工作方式

单片机共有复位、程序执行、低功耗和编程与加密四种工作方式,下面分别加以介绍。 1.复位方式 (1)为什么要复位

大家知道,单片机执行程序时总是从地址0000H 开始的,所以在进入系统时必须对CPU 进行复位,也叫初始化;另外由于程序运行中的错误或操作失误使系统处于死锁状态时,为了摆脱这种状态,也需要进行复位,就象电脑死机了要重新启动一样。 (2)复位的原理

单片机复位的方法其实很简单,只要在RST 引脚(9 脚)上加一个持续时间为24 个振荡周期(即两个机器周期)的高电平就可以了。如果晶振为12M,计算一下这个持续脉冲需要多长时间? (3)如何进行复位

复位操作有上电自动复位、按键复位和外部脉冲复位3 种方法,上电自动复位是通过外部复位电路的电容充电来实现的,当电源刚接通时电容C 对下拉电阻开始充电,由于电容两边的电压不能突变,所以RTS 端维持高电平,只要这个充电时间不超过1ms,就可以实现对单片机的自动上电复位,即接通电源就完成了系统的初始化,在实际的工程应用中,如果没有特殊要求,一般都采用这种复位方式;按键复位的电路如图2 所示,它其实就是在上电复位的基础上加了R2 和SA,这种电路一般用在需要经常复位的系统中;外部脉冲复位的电路如图3 所示,外部复位通常用于要求比较高的系统,比如希望系统死锁后能自动复位。外部复位是由专门的集成电路来实现的,也就是我们通常俗称的“看门狗”电路,这种电路有很多,它们不但能完成对单片机的自动复位功能,而且还有管理电源、用作外部存储器等功能,比如X25045,MAX813L 等等就是比较常用的此类芯片,

现在让我们先来看看单片机复位后,它的内部会有些什么变化呢?看下面的表: (4)复位后的状态

这就是单片机复位后内部系统的状。 2.程序执行方式

程序执行是单片机的基本工作方式,由于复位后PC=0000 ,所以程序就从地址0000H 开始执行,此时单片机就根据指令的要求完成一系列的操作控制,比如前面讲的让LED 灯闪烁起来,不过在实际使用中,程序并不会从0000H 开始执行,而总是安排一条跳转指令,比如LJMP START ,为什么要这样安排,我们讲到中断时再来解释。 3. 低功耗操作方式

在以电池供电的系统中,有时为了降低电池的功耗,在程序不运行时就要采用低功耗方式,低功耗方式有两种—待机方式和掉电方式。 低功耗方式是由电源控制寄存器PCON (上一课我们提到过的)来控制的。电源控制寄存器是一个逐位定义的8 位寄存器,其格式如下, 其中:SMOD 为波特率倍增位,在串行通讯时用;GF1 为通用标志位1;GF0 为通用标志位0;PD 为掉电方式位,PD=1,进入掉电方式;IDL 为待机方式位,IDL=1 ,进入待机方式。也就是说只要执行一条指令让PD 位或IDL 位为1 就可以了。那么单片机是如何进入或退出掉电工作方式和待机工作方式的。

1 .待机方式 2 .进入待机方式

当使用指令使PCON 寄存器的IDL=1 ,则进入待机工作方式。此时CPU 停止工作,但时钟信号仍提供给RAM,定时器,中断系统和串行口;同时堆栈指针SP,程序计数器PC,程序状态字PSW,累加器ACC 以及全部的通用寄存器都被冻结起来;单片机的消耗电流从24mA 降为3.7mA,这样就可以节省电源的消耗。 ② 退出待机方式

退出待机方式可以采用引入中断的方法,在中断程序中安排一条RETI 的指令就可以了,什么是中断,我们现在还不知道,当然这没关系。其实待机方式和我们使用电脑时的睡眠方式有异曲同工之妙。 (2)掉电方式 ①进入待机方式

当使用指令使PCON 寄存器的PD=1 ,则进入掉电工作方式,此时单片机的一切工作都停止,只有内部RAM 的数据被保持下来;掉电方式下电源可以降到2V,耗电仅50uA 。此时就相当于把显示器和硬盘也关闭了。 ② 退出待机方式

退出掉电工作方式的唯一方法是复位,不过应在电源电压恢复到正常值后再进行复位,复位时间要大于10mS ,在进入掉电方式前,电源电压是不能降下来的,因此可靠的单片机电路最好要有电源检测电路。显然掉电方式和待机方式是两种不同的低功耗工作方式,前者可以在无外部事件触发时降低电源的消耗,而后者则在程序停止运行时才使用。关于单片机的低功耗的方式就简单的讲这些,更详细的内容也留到下册再讲解,因为那都是大虾们的作品。 4. 编程和加密方式

单片机的编程与加密是由专门的设备来完成的,这种设备称为编程器或烧录器,类似的产品有很多,功能也不尽相同。本站的XL2000是集烧录、试验、编程、仿真一体化的产品

单片机经典教程10 什么是单片机的寻址?单片机有几种寻址方式? 什么是单片机的寻址?单片机有几种寻址方式?

我们已经知道,单片机的工作过程就是一条一条地从ROM 存储器中取出指令然后执行相关的操作,那么一条指令究竟有哪几部分组成?它又包括哪些内容?一般来说一条指令总是有操作码字段和操作数字段两部分组成,看下面两条指令,MOV R7,#250;MOV P1,#0FFH ,这是我们以前学过的指令,在这两条指令中MOV 就是操作码字段,R7 和P1 就是操作数地址字段,而#0FFH 我们称为常数(也就是立即数),单片机执行指令时就根据指令中给出的地址寻找实际的操作数,不能理解,没关系,继续往下看。 一.单片机的寻址 先来看下面的实验:

程序一就是我们以前做过的LED 灯闪烁的实验,我们已经知道每次调用延时程序的时间都是相同的(125mS) ,如果现在提出这样的要求:灯亮后延时时间为125mS 灯灭,灯灭后又延时100mS 秒灯亮,如此循环,这样的程序还能满足要求吗?显然不能,怎么办?我们可以把它改成程序二,也就是先把一个数送入30H ,在子程序中R7 中的值并不固定,而是根据30H 单元中传过来的数来确定,这样就可以满足要求,大家自行分析一下这个程序。

从这里我们可以得出结论,在数据传递中要找到被传递的数,很多时候,这个数并不能直接给出,而是需要变化,这就引出了一个概念:如何寻找操作数,我们把寻找操作数所在单元地址的过程称之为寻址。在实验一中,我们直接使用数所在单元的地址找到了操作数,所以称之为直接寻址。而在实验二中,我们是把数先放在工作寄存器中,从工作寄存器中寻找数据,这种方式则称之为寄存器寻址。例如:MOV R7,30H,就是把工作寄存器30H 单元中的数送到R7 中,这就是寄存器寻址。

接下来提一个问题:我们知道,工作寄存器就是内存单元的一部份,如果我们选择工作寄存器组0,则R0 就是RAM 的00H 单元,那么这样一来,MOV A,00H ,和MOV A,R0 不就没什么区别了吗?为什么要加以区分呢?的确,这两条指令执行的结果是完全相同的,都是将00H 单元中的内容送到A 中去,但是执行的过程不同,执行第1 条指令需要2 个周期;而执行第2 条则只需要1 个周期,第1 条指令变成最终的目标码要两个字节(E5H 00H),而第2 条则只要一个字节(E8h)就可以了。也许有朋友会问,不就差了一个周期吗,为什么怎么斤斤计较!如果是12M 晶振的话,也就1 个微秒,一个字节又能有多少呢?当然如果这条指令只执行一次,也许无所谓,但一条指令如果执行上1000 次,就是1 毫秒,如果要执行1000000 次,就是1S 的差别,这就很可观了,单片机要做的就是实时控制,所以必须如此“斤斤计较”。再来看另一个问题,现在我们已经知道,寻找操作数可以通过直接给的方式(立即寻址)和直接给出数所在单元地址的方式(直接寻址),这就够了吗?看下面的问题,要求从30H 单元开始,取20 个数,分别送入累加器A 中。就我们目前掌握的办法,要从30H 单元取数,就用MOV A,30H ,那么下一个数呢?是31H 单元的,怎么取呢?还是只能用MOV A,31H ,那么20 个数,不是得20 条指令才能写完吗?这里只有20 个数,如果要送200 个或2000 个数,那岂不要写上200 条或2000 条命令?这未免太笨了吧。为什么会出现这样的状况?因为我们只会把地址写在指令中,所以就没办法了,如果我们不是把地址直接写在指令中,而是把地址放在另外一个寄存器单元中,根据这个寄存器单元中的数值决定该到哪个单元中取数。比如,当前这个寄存器中的值是30H ,那么就到30H 单元中去取,如果是31H 就到31H 单元中去取,就可以解决这个问题了。怎么个解决法呢?既然看的是寄存器中的值,那么我们就可以通过一定的方法让这里面的值发生变化,比如取完一个数后,将这个寄存器单元中的值加1,还是执行同一条指令,可是取数的对象却不一样了。看下面的例子:

MOV R7,#20 ;(1) MOV R0,#30H ;(2)LOOP:MOV A,@R0 ;(3) INC R0 ;(4) DJNZ R7,LOOP ;(5)

这个例子中的大部份指令我们是能看懂的,第1 条,是将立即数20 送到R7 中,执行完后R7 中的值应当是20;第2 条是将立即数30H 送入R0 工作寄存器中,所以执行完后,R0 单元中的值是30H;第3 条,是看一下R0 单元中是什么值,把这个值作为地址,取这个地址单元的内容送入A 中,此时,执行这条指令的结果就相当于执行MOV A,30H ;第4 条,没学过,就是把R0 中的值加1,因此执行完后,R0 中的值就是31H 了;第5 条,学过,将R7 中的值减1,看是否等于“0”,不等于“0”,则转到标号LOOP 处继续执行,因此,执行完这句后,将转去执行MOV A,@R0 这一条,此时相当于执行了MOV A,31H (因为此时的R0 中的值已是31H 了);如此,直到R7 中的值逐次相减等于“0”,也就是循环20 次为止,就实现了我们的要求:从30H 单元开始将20 个数据送入A 中。这是另一种寻找数据的方法,由于数据是间接被找到的,所以把这种寻址方式称之为寄存器间址寻址。(注意?,在间址寻址中,只能用R0 或R1 来存放等待寻找的数据)。

除了以上几种寻址方法外,单片机还有变址寻址,相对寻址和位寻址共七种寻址方式。这些您暂时可以不去深究它,我们以后会结合具体的实验再来详细介绍,这里只是为了归类,所以才把它们例举在一起。 二.寻址方式举例 1.直接寻址

直接寻址时,指令中的地址码部分直接给出了操作数的有效地址。例如:MOV A,4FH ;A←(4FH)可用于直接寻址的空间有内部RAM 的低128 字节(包括其中的位寻址区与特殊功能寄存器)。 2.寄存器直接寻址

寄存器寻址时,指令中地址码给出的是某一通用寄存器的编号,寄存器的内容为操作数。 例如:MOV A,R7 ;A←(R7)

可用于寄存器寻址的空间有R0-R7,ACC,CY(位),DPTR,B。 3.寄存器间接寻址

寄存器间接寻址时,指令中给出的寄存器的内容为操作数的地址,而不是操作数本身。例如:MOV A,@R0 ;A←[(R1)]可用于寄存器间接寻址的空间只能是R0 和R1, 用DPTR 或PC 可间接寻址64K 字节外部的RAM 或ROM. 4.立即寻址

立寻址时,指令中地址码部分给出的就是操作数本身。 例如:MOV A,#0FFH ;A←0FFH 可用于立即寻址的空间有 5.变址寻址

变址寻址时,指定变址寄存器的内容与指令中给出的偏移量相加DPTR 所得的结果作为操作数的地址。 例如:MOVC A,@A+DPTR ; A←[(A)+(DPTR)]。

无论用DPTR 或PC 作为基准指针,变址寻址只适用于程序存储器(即ROM),通常用于读取数据表。 6.相对寻址

相对寻址时,由程序计数器PC 提供的基准地址与指令中提供的偏移量rel 相加,得到操作数的地址。例如:SJMP rel ;PC←(PC)+2+rel 7.位寻址

位寻址时,操作数是二进制数的某一位,其位地址出现在指令中。 例如:SETB bit ;(bit)←1

可用于位寻址的空间有内部RAM 的可位寻址区和SFR(特殊功能寄存器)中的字节地址可以被8 整除(即地址以0、8、F 结尾)的寄存器空间。 单片机经典教程11 单片机指令(一)

指令就是编程者给单片机下的命令,也就是我们平常所说的单片机软件,前面我们已经陆续地讲到了一些指令,但还远远不够,从这一课开始就要全面的讲解指令了,希望大家多动手实验,巩固所学的知识,说实在的,其实单片机并不难学。为了让大家比较容易记忆,按照常规分类,我把单片机的111 条指令分成了五类—即数据传递类指令、算术运算类指令、逻辑运算类指令、控制转移类指令和位操作指令。这一课先来看数据传递类指令:一.数据传递类指令数据传递类指令是单片机中用的最多的指令,在51 系列单片机的111 条指令中共有28 条是数据传递类指令,前面我们已经学到了几条,比如MOV R1,#250;MOV A,R6 等,那么它们是怎么分类的呢?请往下看: 1. 以累加器为目的操作数的指令 (1)MOV A,Rn (2)MOV Rn,A (3)MOV A,direct (4)MOV A,@Ri (5)MOV A,#data

指令(1)把Rn 中的数送入累加器A,Rn 代表工作寄存器R0-R7(以后我们只要写到Rn 都代表R0-R7 ,这一点请大家记住了); 指令(2)则相反,把工作寄存器中的数送入累加器A 中;

指令(3)是把直接地址中的数送入累加器A 中,driect 就代表直接地址(以后也相同);

指令(4)就是上一课我们讲的寄存器间接寻址,什么意思?这里再重复一遍,就是看一下工作寄存器中是什么值,把这个值作为地址,把这个地址中的数送入累加器A 中,Ri 代表什么意思呢?就是工作寄存器R0 或者R1(以后如果写Ri 都代表R0 或R1);

指令(5)就是把立即数(也叫常数)直接送入累加器A 中,很显然data 就代表立即数(以后也相同),其实这个我们以前提到过,加#的数就代表送入的是这个数的本身。接下来举几个实例加以说明,大家可以用DUBG8051 这个软件验证一下: A.MOV R7,#250;MOV A,R7 ;将工作寄存器R7 中的值250 送入A,R7 中的值保持不变。 B.MOV A,#250;MOV R7,A;将A 中的值250 送入工作寄存器R7,A 中的值保持不变。C.MOV 30H,#250;MOV A,30H ;将内存30H 单元中的值250 送入A,30H 单元中的值保持不变。D.MOV 20H,#250;MOV R0,#20;MOV A,@R0 ;先看R0 中是什么值,把这个值作为地址,并将这个地址单元中的值送入A 中。执行命令前R0 中的值为20H,则是将20H 单元中的值250 送入A 中。E.MOV A,#250 ;将立即数250 送入A 中,执行完本条指令后,A 中的值是250。 2.以寄存器Rn 为目的操作数的指令(1)MOV Rn,A (2)MOV Rn,direct (3)MOV Rn,#data 举几个实例大家自行分析一下:A.MOV R7,A ;B.MOV R7,30H ;C.MOV R7,#20 ;这组指令功能是把源地址单元中的内容送入工作寄存器,源操作数不变。 3.以直接地址为目的操作数的指令 34 _ 单片机教程(MCS-51 系列) (1) MOV direct,A 例如:MOV 30H,A(将累加器A 中的数送入内存单元30H)(2)MOV direct,Rn 例如:MOV 30H,R7(将寄存器R7 中的数送入内存单元30H)(3)MOV direct,direct 例如:MOV 30H,20H(将内存单元20H 中的数送入内存单元30H)(4)MOV direct,@Ri 例如:MOV 30H,@R0 (看一下R0 中是什么值,把这个值作为地址,并将这个地址单元中的值送入A 中。如执行指令前R0 中的值为20H,则是将20H 单元中的值送入A 中)。(5)MOV direct,#data 例如:MOV 30H,#20(将立即数20 送入内存单元30H) 4. 以间接地址为目的操作数的指令 (1)MOV @Ri,A (2)MOV @Ri,direct (3)MOV @Ri,#data16 这三条指令就不介绍了,大家自行分析一下,不过有一点希望大家记住,Ri只能用工作寄存器R0或者R1。 5. 十六位数的传递指令 MOV DPTR,#data16 指令说明:这是51 单片机中唯一的一条16 位立即数传递指令,大家知道51 系列单片机是一种8 位单片机,8 位单片机所能表示的最大数只能是28=0-255 。讲到这里大家应该明白了,为什么我们前面的实验中立即数不能大于255 。如果现在有个数是1234H (即二进制0001 0010 0011 0100),我们要把它送入DPTR ,该怎么办呢?当然有办法,INTEL 公司已经把DPTR 分成了两个寄存器,DPH 和DPL (看一下前面的特殊功能寄存器介绍),我们只要把12H( 高8 位)送入DPH ,把34H( 低8 位)送入DPL 中去就可以了,所以执行指令MOV DPTR,#1234H 和执行指令MOV DPH,#12H(1); MOV DPL,#34H(2);是一样的。二.指令练习请写出下列每条指令的执行结果,并DPL

单片机经典教程12 单片机指令(二)

这一课继续讲解其他的数据类指令。提示:下面的内容我们下册中才会用到,这里只是为了把数据传递类指令讲完,才提前把它们讲一下,

您不知道也没关系。

一.数据传递类指令 6.累加器A 与片外RAM 之间的数据传递类指令什么是片外RAM(即片外数据存储器)呢?单片机不是有内部RAM 吗?为什么还要片外RAM 呢?难道单片机的内部RAM 还不够用吗?的确如此,当单片机的内部RAM 不够时,我们就要扩充RAM 空间。那么单片机能扩充多少的外部RAM空间呢?89C51 单片机的片外RAM可以扩展到64K,即从0000H-FFFFH,那么它是怎样和累加器A 进行数据传递的?它们之间的传递指令共有以下四条: (1) MOVX A,@Ri (2) MOVX @Ri,A (3) MOVX A,@DPTR (4) MOVX @DPTR,A 指令说明: A.在51 系列单片机中,与外部存储器RAM 传递数据的只可以是累加器A,所有要送入或读出外部RAM 的数据必须先送到A 中去,在此我们可以看出内外部RAM 的区别了:内部RAM 间可以直接进行数据的传递,而外部RAM 则不行。比如,要将外部RAM 中某一单元(设为100H 单元的数据)送入另一个单元(设为200H 单元),就必须先将100H 单元中的内容读入A,然后再送到200H 单元中去。在这里有一个问题:CPU 是如何区分内、外部RAM 的?大家看这里的四条指令,其操作码都是MOVX ,而内部RAM 的操作码则是MOV,CPU 就是根据不同的指令来自动区分读写内、外部RAM 的。 B.要读出或写入外部的RAM ,当然还必须知道外部RAM 的地址,在后两条指令中,地址是被直接放在DPTR 中的;而前两条指令由于Ri(即R0 或R1)只是一个8 位的寄存器,所以只能提供低8 位的地址。不过有时我们要扩展的外部RAM 数量比较少(少于或等于256 个字节),提供低8 位的地址也就足够了。 C.使用时应当首先将要读出或写入的地址送入DPTR 或Ri 中,然后再用读写指令。举例:将外部RAM 中100H 单元中的内容送入外部RAM 中200H 单元中。 MOV DPTR,#0100H MOVX A,@DPTR MOV DPTR,#0200H MOVX @DPTR,A 7.累加器A 与片外ROM 之间的数据传递类指令 MOVC A,A+@DPTR 前一小节讲了累加器A 与外部RAM 之间的数据传递类指令,接下来再来讲讲片外ROM 与累加器A 之间的数据传递类指令。在讲解之前,先来了解一下内部ROM 和外部ROM 的组成,89C51 的内部有4K 的FLASH ROM 空间,其地址为000H-FFFH ,片外可以扩展到64K(OOOOH-FFFFH),在这64K 的ROM 空间中,有4K 字节的地址是片内和片外公用的(即000H-FFFH),而1000H-FFFFH 的空间是片外ROM 专用的。讲到这里大家就会问:既然有4K 的地址是公用的,那么CPU 是如何区分的呢?不知大家是否还记得,在第二讲单片机的硬件电路中,有一个引脚EA(即31 脚),当EA=1,CPU 从片内ROM 的4K 字节中取指令,如果地址超过了4K(FFFH),单片机就自动转向片外ROM 取指令,大家注意?:这个过程是自动完成的,不需要人工干预;而当EA=0 时,CPU 只从片外ROM 取指令。讲到这里,不知大家注意?没有,当使用外部ROM 和外部RAM 时,它们的寻址空间都是0000H-FFFFH,也就是说它们在地址上是重叠的,那么CPU 在读取指令时又是如何来区分当前是从ROM 取指令还是从单片机教程(MCS-51 系列) RAM 取指令呢?请大家来看第二课的89C51 单片机硬件电路图,29 脚是PSEN ,当我们置位PSEN 时(即PSEN=1),CPU 就读取外部ROM 指令;而要从外部RAM 读取指令时就置位WR(即16 脚)或RD(即17 脚),这样即使ROM 地址和RAM 地址是重叠的,也不会出现混乱。这里又有一个问题了,16 脚和17 脚不是并行口P3.6 和P3.7 吗?如果我们把它当作第二功能WR 和RD 来使用,CPU 又是如何来区分的呢?这个问题我们前面已经讲过了,这里再重复一遍:单片机引脚的第二功能是不需要人工干预的,也就是说只要CPU 执行到相应的指令,就自动转成了第二功能。了解了片外ROM 的读取指令原理,再来看片外ROM 与累加器A 之间的数据传递指令(注意?:ROM 只能读取指令,而不能写入数据,这一点和RAM 是不同的)。 MOVC A,A+@DPTR 指令说明: A.本指令是将ROM 中的数送入A 中。通常称其为查表指令,常用此指令来查一个已做好在ROM 中的表格。 B.此条指令引出一个新的寻址方法:变址

DUBG8051

1.MOV 12H,#34H 2.MOV R0,#23H 3.MOV R7,#22H 4.MOV R1,12H 5.MOV A,@R0 6.MOV 34H,@R1 7.MOV 45H,34H 8.MOV 12H,DPH 9.MOV R0,

寻址。本指令是要在ROM 的一个地址单元中找出数据,显然必须知道这个单元的地址,这个单元的地址是这样确定的:在执行本指令前DPTR 中有一个数,A 中也有一个数,执行指令时,将A 和DPTR 中的数加起来,就成为要查找的数的单元地址。 C.查找到的结果被放在A 中,因此,本条指令执行前后,A 中的值不一定相同。 举例:有一个数在R0 中,要求用查表的方法确定它的平方值(此数取值范围是0-5) MOV DPTR,#TAB MOV A,R0 MOVC A,@A+DPTR . . TAB: DB 0,1,4,9,16,25 设R0 中的值为?2?,送入A 中,而DPTR 中的值则为?TAB?,则最终确定的ROM 单元的地址就是?TAB+2?,也就是到?TAB+2 ?这个单元中去取数,取到的是?4?(Db 后面的第三个数)。其它数据也可以次类推。从这里可以看出,我们使用了标号(象TAB 等)来代替具体的ROM 单元地址,事实上,标号的真实含义就是地址的数值,在这里它就代表了TAB+0,TAB+1, TAB+25 这几个数据在ROM 中的存放位置;而我们以前学过的如LCALL DELAY 指令,DELAY 代表的是以DELAY 为标号的那段程序在ROM 中存放的起始地址,CPU 就是根据这个起始地址才找到指令的,无法理解是吗?没关系,让我们先来看几个符号的含义就会明白了。二.单片机的伪指令(注意?:伪指令不是单片机指令)我们前面简单提到过,END 是伪指令,那么到底什么是伪指令?它在单片机中有什么作用呢?接下来我们就来讨论这个问题,伪指令是单片机中用来给寄存器定义或者赋值的特殊指令为什么要用伪指令呢?让我们来看下面的实验: 1.DB—定义字节伪指令它的功能是从程序存储器ROM 单元的某个地址开始,存入一组规定好的8 位二进制常数。例如:ORG 2000H; TAB:DB 45H,48H,10;34H;以上指令经汇编后,将对从2000H 开始的若干ROM 单元赋值,即(2000H)=45H,(2001H)=48H,(2002H)=0AH,(2003H)=34H。讲到这里,有的人会问:在这些指令中,我直接用MOV 2000H,45H;MOV 2001H,48H ??不就得了,干吗要用DB 指令呢?是的,从理论上讲,两者的效果是一样的,只是因为我们现在的程序都很短,单片机不可能只做这些简单的工作,当程序比较长时,这些指令的意义就不一样了。除了刚刚提到的END 和DB 伪指令外,单片机中还有那些伪指令呢?下面简单讲解一下: 2.DW—定义字伪指令 37 _ 单片机之友 QQ:280919249 单片机教程(MCS-51 系列) 在计算机中,一个字由两个字节组成,也就是说,如果一个字节可以表示一个8 位数的话,那么一个字就可以表示一个十六位的数(关于这方面的问题我们留到下册中再来讨论,这里就不讲了,以免增加大家的学习难度),如此一来,这条伪指令的功能也就清楚了,就是从指定的ROM 单元开始,定义若干个16 位常数;上一课我们已经讲过,51 系列单片机要存放一个16 位的常数就必须把这个数分成两个8 位数据来存放,通常我们把一个16 位数的高8 位放入低地址,而把低8 位放入高地址(注意?:这两个地址必须是紧挨着的)。例如:ORG 3000H; ABC:DW 2345H,0A859H;程序经汇编后,(3000H)=23H,(3001H)=45H,(3002H)=A8,(3003H)=59H。注意?:象0A859H 这样的数值在写法上,A 的前面一定要加上个0。 3.DS—保留空间伪指令它的功能是从指定的地址开始,保留若干个字节的ROM 空间留作它用。例如:ORG 2000H; ABC:DS0 8H; LOOP:MOV A,30H;汇编以后,从2000H 开始,将保留8 个ROM 单元留作它用,那么以LOOP 为标号的指令就存放在2008H 单元中。这里有一点请大家注意?:这几条伪指令都只能对程序存储器(ROM)起作用,而不能用它们来对数据存储器(RAM)进行赋值或做其他的工作。至于它们到底有什么作用,我们什么时候才需要用到它们?我们将在下册的实验中再作讲解。现在让我们通过一段程序来解释一下查表程序的使用方法,这可是一定要学会的。例如:MOV DPTR,#100H ; MOV A,R0 ; MOVC A,@A+DPTR ; . . ORG 0100H ; DB 0,1,4,9,16,25 ;如果R0 中的值为“2”,则最终地址为“100H+2 ”即“102H”,到102H 单元中找到的是“4”。这个可以看懂了吧,那为什么不这样写程序,要用标号呢?不是增加疑惑吗?如果用标号写的话,在写程序时,就必须确定这张表格在ROM 中的具体位置。如果写完程序后,又想在这段程序前插入一段程序,那么这张表格的位置就又要变了(要改ORG 100H 这条指令),如果我们是经常需要修改程序的,那多麻烦,所以就用标号来替代,只要一编译程序,位置就自动发生变化,我们把这件事交给计算机去做了。 8.堆栈的操作指令什么是堆栈,我们前面已经介绍过了,那么堆栈是如何进行数据传递的呢?对堆栈的操作指令有2 条:(1)PUSH direct (2)POP direct 第1 条指令称之为推入,就是将direct 中的内容送入到堆栈中;第2 条指令称之为弹出,就是将堆栈中的内容送回到direct 中。推入指令的执行过程是:首先将SP 中的值加1,然后把SP 中的值当作地址,将direct 中的值送进以SP 中的值为地址的RAM 单元中。例如:MOV SP,#5FH ; MOV A,#100 ; MOV B,#20 ; PUSH ACC ; PUSH B ; 38 _ 单片机之友 QQ:280919249 单片机教程(MCS-51 系列) 这段指令的执行过程是这样的:将SP 中的值加1,即变为60H ,然后将A 中的值(#100 )送到60H 单元中,因此执行完PUSH ACC 这条指令后,内存60H 单元的值就是100 ,同样,执行PUSH B 时,是将SP+1 ,即变为61H ,然后将B 中的值送入到61H 单元中,即执行完本条指令后,61H 单元中的值变为20。这是推入,那么弹出又是怎么样的呢?请看下面的例子: MOV SP,#5FH ; MOV A,#100 ; MOV B,#20 ; PUSH ACC ; PUSH B ; POP B ; POP ACC ; POP 指令的执行是这样的:首先将SP 中的值作为地址,并将此地址中的数送到POP 指令后面的那个direct 中,然后SP 减1。上面程序的执行过程是:将SP 中的值(现在是61H)作为地址,取61H 单元中的数值(现在是20),送到B 中,所以执行完POP B 指令后B 中的值是20,然后将SP 减1,那么此时SP 的值就变为60H ,然后执行POP ACC ,将SP 中的值(60H )作为地址,从该地址中取数(现在是100),并送到ACC 中,所以执行完本条指令后,ACC 中的值是100。这有什么意义呢?ACC 中的值本来就是100,B 中的值本来就是20,是的,在本例中,的确没有意义,但在实际工作中,推入堆栈结束后(即执行指令PUSH B 后)往往要执行其他的指令,而且这些指令会把A 中的值和B 中的值改掉,所以在程序执行结束后,如果我们要把A 和B 中的值恢复原值,那么这些指令就有意义了,具体应用我们将在以后的课程中讲到。这里还有一个问题,如果不用堆栈,比如说在PUSH ACC 指令处用MOV 60H,A,在PUSH B 处用指令MOV 61H,B,然后用MOV A,60H,MOV B,61H 来替代两条POP 指令,不也一样吗?是的,从结果上看是一样的,但是从过程看是不一样的,PUSH 和POP 指令都是单字节,单周期指令,而MOV 指令则是双字节,双周期指令。更何况,堆栈的作用不止于此,所以一般的单片机上都设有堆栈,而我们在编写子程序,需要保存数据时,通常也采用堆栈的方法来实现。 9.其他的数据传递类指令(1)XCH A,Rn (2)XCH A,direct (3)XCH A,@Ri (4)XCHD A,@Ri (5)MOVC A,A+PC 前面的4 条指令是进行数据交换用的,第1 条,寄存器与累加器交换;第2 条直接地址与累加器交换;第3 条间接RAM 与累加器交换;第4 条间接RAM 与累加器的低4 位交换;第5 条是累加器与代码字节之间的数据传递类指令,这些指令作为初学者可能暂时还用不上,所以就不介绍了,大家只要了解一下就可以了,等下册中我们再来详细的讨论。三.本课总结到本课为止,数据传递类指令全部讲解完了,在单片机的指令中,数据传递类指令是使用最多的指令,因此这部分的内容是必须掌握的,如何来使

用这些指令,我们将在以后的课程中结合具体的实验加以介绍。为了加深印象,大家可以用DUBG8051 软件对上述指令进行反复练习,用

实验结果来加深课堂知识。 单片机经典教程13 单片机指令(三)

算术、逻辑运算类指令也是单片机中极为重要的指令系统,在很多教科书中都把它们归为一类,实际上它们还是有区别的,为了让大家便于记忆,这里把它们分了开来。在单片机中,算术运算类指令有24 条;逻辑运算类指令有25 条。这一课我们先来讲解算术运算类指令,下面我们分别加以讲解: 一.算术运算类指令 1。不带进位的加法指令

(1)ADD A,Rn ;例:ADD A,R7 (2)ADD A,@Ri ;例:ADD A,@R1 (3)ADD A,direct;例:ADD A,30H

(4)ADD A,#data ;例:ADD A,#30H指令说明:这些指令的意思就是把后面的值与A 中的值相加,结果送到A 中去。举例:MOV A,30H ;

ADD A,10H ;执行结果A=40H 2。带进位的加法指令

(1)ADDC A,Rn ;例:ADDC A,R7 (2)ADDC A,@Ri ;例:ADDC A,@R1 (3)ADDC A,direct;例:ADDC A,30H (4)ADDC A,#data ;例:ADDC A,#30H

指令说明:这些指令的作用都是将A 中的值和其后面的值相加,并且加上进位位CY 中的值。为什么要这样做呢?我们知道51 单片机是一种8 位单片机,所以只能做8 位的数学运算,也就是说最大运算的范围只能是0-255 ,这在实际工作中是不够的,因此就要进行扩展,怎么扩展,就是将2 个8 位的数学运算合起来,成为一个16 位的运算,这样可以表达的数的范围就能达到0-65535 。如何合并呢?其实很简单,让我们看一个十进制数的加法例子:66+78 ,这两个数相加,我们根本不会在意它的过程,但事实上我们是这样做的:先做6+8 (低位),然后再做6+7 ,这是高位。做了两次加法,只是我们做的时候并没有刻意分成两次加法来做罢了,或者说我们并没有意识到我们做了两次加法,之所以要分成两次来做,是因为这两个数超过了一位数所能表达的范置(0-9)。在做低位时产生了进位,我们通常的办法是在适当的位置点一下,然后在做高位加法时将这一点加进去;其实计算机中做16 位加法时同样如此,先做低8 位的,如果两数相加产生了进位,也要“点一下”做个标记,这个标记就是进位位CY,在PSW 中,我们前面已经讲过,在进行高位加法时将这个CY 加进去。例如做2 个16 进制数相加:1067H+10A0H ,先做67H+A0H=107H ,而107H 显然超过了0FFH ,因此最终保存在A 中的是7,而1 则进到了PSW 中的CY 位去了,换言之,CY 位就相当于是100H ,然后再做10H+10H+CY ,结果是21H ,所以最终的结果是2107H。 3。带借位的减法指令

(1)SUBB A,Rn ;(2)SUBB A,@Rn ;(3)SUBB A,direct;(4)SUBB A,#data ;

指令说明:没有不带借位的减法指令,如果需要做不带借位的减法指令(在做第一次相减时),只要将CY 清零即可。 4.乘法指令 (1)MUL AB ;

指令说明:此指令的功能是将A 和B 中的两个8 位无符号数相乘,两数相乘结果一般比较大,因此最终结果用1 个16 位数来表达,其中高8 位放在B 中,低8 位放在A 中。在乘积大于FFFFFH(65535)时,PSW 的0V 位置“1”(溢出),否则OV 为“0”,而CY 位总是为“0”。

例:(A)=4EH,(B)=5DH;MUL AB ;乘积是1C56H,所以在B 中放的是1CH,而A 中放的则是56H。 5.除法指令 (1)DIV AB(A/B)

指令说明:此指令的功能是将A 中的8 位无符号数除以B 中的8 位无符号数(什么是无符号数?简单的说就是没有负数的数,也就是整数,比如1,2,3,1.2,4.5 等等这样的数)。除法一般会出现小数,但计算机中可没法直接表达小数,它用的是我们小学生用的商和余数的概念,如13/5 ,其商是2,余数是3。除了以后,商放在A 中,余数放在B 中。CY 位和OV 位都是“0”,如果在做除法前B 中的值是00H,也就是除数为0,那么0V=1。 6.加1 指令

(1)INC A ;例如:A=20H INC A; A=21H (2)INC Rn ;例如:R7=20H INC A; R7=21H (3)INC direct;例如:30H=20H INC 30H; 30H=21H (4)INC @Ri ;例如:

(5)INC DPTR ;例如:DPTR=20H INC DPTR; DPTR=21H

指令说明:从结果上看INC A 和ADD A,#1 差不多,但INC A 是单字节单周期指令,而ADD A,#1 则是双字节双周期指令,而且INC A 不会影响PSW 位,如(A)=0FFH,INC A 后(A)=00H ,而CY 依然保持不变;如果是ADD A ,#1,则(A)=00H ,而CY 一定是“1”。因此加1 指令并不适合做加法,事实上它主要是用来做计数、地址增加等用途。另外,加法类指令都是以A 为核心的,其中一个数必须放在A 中,而运算结果也必须放在A 中,而加1 类指令的对象则广泛得多,可以是寄存器、内存地址、间址寻址的地址等等。 7.减1 指令

(1)DEC A ;例如:A=20H DEC A; A=19H (2)DEC Rn ;例如:R7=20H DEC A; R7=19H (3)DEC direct;例如:30H=20H DEC 30H; 30H=19H (4)DEC @Ri ;例如:

指令说明:既然加1 指令可以用于计数、定时、地址等加1,那么有加也必然有减,所以减1 指令的功能与加1 指令类似,这里就不多说了。

8.十进制加法调整指令

DA A ;这是一条对十进制加法进行调整的指令,等下册用到时再介绍。

另外需要了解的是:在算术运算类指令中,除了加1 和减1 指令外,其他的算术运算类指令都要把结果放到累加器A 中,这与数据传递类指令有所不同。二.逻辑运算类指令

什么是逻辑运算?相信大家不会陌生,在数字电路中我们学过“与门”、“或门”、“非门”等,在单片机中也有类似的运算。那么它们是如何分类的呢?接下来我们就来一一讲解,先来看对累加器A 的逻辑运算指令: 1。对累加器A 的逻辑运算指令 (1)CLR A

指令说明:累加器A 清零。效果同MOV A,#00H 是一样的,只不过它是单周期指令,而MOV A,#00H 是双周期指令。 (2)CPL A

指令说明:将累加器A 逐位取反。相当于数字电路的“非”逻辑,例如:A=12H CPL A ;12H 化为二进制是00010010,逻辑取反后为11101101,即A=EDH。 (3) RL A

指令说明:将累加器A 的值逻辑左移。例如:A=12H RL A;化为二进制为00010010 ,逐位左移后为0010100 ,即24H 。这里把第7 位移到了第0,第0 位移到了第1 位,第1 位移到了第2 位,其余的依次类推。(4)RLC A

指令说明:加上进位位CY 并逻辑左移。例如:CY=1 A=12H RLC A;加上进位位CY 后1 00010010 逻辑左移变为0 00100101(即CY=0,A=25H)。(5)RR A

指令说明:将累加器A 中的值逻辑右移。同RL A 类似。 (6)RRC A

指令说明:加上进位位CY 并逻辑右移。同RLC A 类似。 (7)SWAP A

指令说明:将A 中的值的高、低4 位进行交换。例如:(A)=39H,SWAP A 之后,A 中的值就是93H 。怎么正好是这么前后交换呢?因为这是一个十六进制数,每1 个十六进位数代表4 个二进制数。注意?,如果是这样的:(A)=39D ,后面没加H,执行SWAP A 之后,可不是(A)=93 。要将它化成二进制数再算:39D 化为二进制是10111 ,也就是0001,0111 高4 位是0001 ,低4 位是0111 ,交换后是01110001,也就是71H,即113D。 2。两个寄存器之间的逻辑运算指令

上面的指令都是针对累加器A 的逻辑运算指令,也就是说对一个寄存器的逻辑运算,那么如果两个寄存器之间的逻辑运算又是怎么样的呢?接着往下看:

指令说明:什么是逻辑“与”?数字电路中我们已经学过:就是F=A*B ,简记为“全1 出1,有0 出0”。如果忘了,没关系,找本书再看一下,这里就不详细的阐述了。

例如:71H 和56H 相“与”,将两数写成二进制形式:(71H) 01110001 和(56H) 00100110 。逐位相“与”结果就是00100000 即20H,从上面的例子可以看出,两个参与运算的值只要其中有一个位上是“0”,则这位的结果就是“0”,两个同是“1”,结果才是“1”,是不是符合逻辑“与”的结果?

知道了逻辑“与”指令的功能后,逻辑“或”和逻辑“异或”的功能就很简单了。逻辑“或”是逐位相“或”,即有1 出1,全0 出0。例:71H 和56H 相“或”结果就是77H ;而“异或”则是逐位“异或”,即相同出0,相异出1。仍旧71H 和56H 相“异或”,结果是57H。两个寄存器之间的逻辑“或”以及逻辑“异或”的指令如下:(2)ORL A,Rn ;A 与Rn 中的值按位‘或’,结果送入A 中 ORL A,direct ;A 与direct 中的值按位‘或’,结果送入A 中 ORL A,@Ri ;A 与间址寻址单元@Ri 中的值按位‘或’,结果送入A 中 ORL A,#data ;A 与立即数data 按位‘或’,结果送入A 中

ORL direct,A ;direct 中值与A 中的值按位‘或’,结果送入direct 中

ORL direct,#data ;direct 中的值与立即数data 按位‘或’,结果送入direct 中。(3)XRL A,Rn ;A 与Rn 中的值按位‘异或’,结果送入A 中XRL A,direct ;A 与direct 中的值按位‘异或’,结果送入A 中

XRL A,@Ri ;A 与间址寻址单元@Ri 中的值按位‘异或’,结果送入A 中XRL A,#data ;A 与立即数data 按位‘异或’,结果送入A 中XRL direct,A ;direct 中值与A 中的值按位‘异或’,结果送入direct 中 XRL direct,#data;direct 中的值与立即数data 按位‘异或’,结果送direct 中。

连续好几节课将讲了许多的基本知识,大家是不是又觉得有些枯燥和无聊了,别急,接下来让我们轻松一下,做一个实验来证明一下几节课所学的内容:

START:MOV SP,#5FH ; MOV A,#80H ; LOOP:MOV P1,A ; RL A ; LCALL DELAY ; LJMP LOOP ; DELAY:MOV R7,#255 ; D1:MOV R6,#255 ; D2:NOP NOP NOP NOP

DJNZ R6,D2 ; DJNZ R7,D1 ; RET ; END。

好久没做实验了,大家还记得实验的步骤吗?调试→编译→下载,看到了什么,有一个暗点在

流动;想象一下,如果我们把P1.0-1.7 的LED 换成8 只可控硅来控制霓虹灯,是不是就有点实用价值了。 2.程序分析:

前面的ORG 0000H 、LJMP START 、ORG 30H 我们以后分析。从START 开始,MOV SP,#5FH,这是初始化堆栈,在本程序中有无此句无关紧要,不过我们慢慢开始接触正规的编程,我也就慢慢地给大家培养习惯吧。MOV A,#80H ,将80H 这个数送到A 中去。干什么呢?不知道,往下看:MOV P1,A 将A 中的值送到P1 端口去,此时A 中的值是80H,所以送出去的也就是80H,因此P1 口的值是80H,也就是二进制10000000 ,对应P1.7-P1.0 这8 位。我们应当知道,此时P1.7 接的LED8 是不亮的,而其它的LED 都是亮的,所以就形成了一个“暗点”,继续往下看,RL A;将A 中的值进行左移,算一下,移之后的结果是什么?对了,是01H ,也就是二进制00000001 ,这样,应当是接在P1.0 上的LED1 不亮了,而其它的都亮了,从现象上看就是“暗点”移到了后面;然后是调用延时程序,这里有一条指令NOP,它是空操作指令,也就是什么都不做,用于短暂的延时,其他的指令我们很熟悉了,就是让这个“暗点”暗一会儿,然后又跳转到LOOP 处(LJMP LOOP),请大家计算一下,下面该哪个灯不亮了??对了,应当是接在P1.1 上灯不亮了,这样依次不断的循环,就形成了“暗点流动”的现象。

单片机经典教程14 单片机指令(四) 控制转移类指令

控制转移类指令共有17 条,分为无条件转移指令、条件转移指令和返回及调用指令三大类,下面我们分别加以学习: 1.无条件转移类指令

(1)无条件绝对转移指令:AJMP addr11 (2)无条件长转移指令:LJMP addr16 (3)无条件相对转移指令:SJMP rel

在讲解上面这三条指令之前先来认识一下三个符号:add11 、add16 、rel。其中add11 和add16 表示外部ROM 的16 位和11 位地址,前面我们已经讲过,单片机的外部ROM 可以扩展到64K,add16 就表示64K 程序存储器的任何地址,换句话说LJMP 指令可以跳转到程序的任何地方,而add11 则表示下一条指令的2K 页面,也就是说,SJMP 指令只能跳转到程序的2K 范围之内;rel 表示8 位的偏移量,其范围是下一条指令第一字节的前128 到后127 个字节(即-128-+127B) 。介绍完了三个符号,再看上面的三条转移类指令,如果要仔细分析的话,它们之间其实区别较大,但在初学时,我们可以不理会这么多,统统把它们理解成:(*JMP 标号),比如SJMP LOOP ,就是跳转到有LOOP 标号处。原则上,所有用SJMP 或AJMP 的地方都可以用LJMP 来替代。因此在初学时,需要跳转时可以全用LJMP 代替,除了一个场合,什么场合呢?先看一下AJMP,AJMP 是一条双字节指令,也就说这条指令本身占用存储器(ROM )的两个单元,而LJMP 则是一条三字节指令,即这条指令占用存储器(ROM )的三个单元,这就是区别。下面再来看第4 条跳转指令: (4)无条件间接转移指令:JMP @A+DPTR

这条指令的用途也是跳转,跳转到什么地方去呢?这可不能由标号简单地决定了,让我们从一个实际的例子入手吧: MOV DPTR,#TAB ;将TAB 所代表的地址送入DPTR MOV A,R0 ;从R0 中取数(详见下面说明) MOV B,#2 ; MUL A,B ;

A 中的值乘2 ;(详见下面的说明) JMP A,@A+DPTR ;跳转 TAB: AJMP S1 ;跳转表格 AJMP S2 ; AJMP S3 ;

应用背景介绍:在单片机开发中,经常要用到键盘,见下面的9 个按键的键盘图。我们的要求是:当按下功能键A??G 时去完成不同的功能,这用程序设计语言来表达的话,就是:按下不同的键去执行不同的程序段,以完成不同的功能,怎么样来实现这个功能呢? 看图,前面的程序读入的是按键的值,如按下‘A’键后获得的键值是“0”,按下‘B’键后获得的值是“1”等等,然后根据不同的值进行跳转,如键值为“0”就转到S1 处执行,如键值为“1”就转到S2 处执行,??到底如何来实现这一功能呢?

先从程序的下面看起,是若干条AJMP 语句,这若干条AJMP 语句最后在存储器中是这样存放的(见图),也就是每个AJMP 语句都占用了两个存储器的空间,并且是连续存放的。而AJMP S1 存放的地址是TAB,到底TAB 等于多少,我们不需要知道,把它留给汇编程序来算好了。

下面我们来看这段程序的执行过程:第1 条MOV DPTR,#TAB 执行完了之后,DPTR 中的值就是TAB ,第2 条是MOV A,R0,我们假设R0 是由按键处理程序获得的键值,比如按下‘A’键,R0 中的值是“0”,按下‘B’键,R0 中的值是“1”??以此类推;现在我们假设按下的是‘B’键,则执行完第2 条指令后,A 中的值就是“1”。并且按照我们的分析,按下‘B’后应当执行S2 这段程序,让我们来看一看是否是这样呢?第3 条、第4 条指令是将A 中的值乘“2”,即执行完第4 条指令后A 中的值是“2”,下面就执行JMP @A+DPTR 了,现在

DPTR 中的值是“TAB”,而A+DPTR 后就是“TAB+2”,因此,执行完这条程序后,将会跳到TAB+2 这个地址处继续执行;看一看在TAB+2 这个地址里面放的是什么?就是AJMP S2 这条指令,因此,马上又执行AJMP S2 这条指令,程序将跳到S2 处往下执行,这与我们的要求相符合。请大家自行分析按下键‘A’、‘C’、‘D’??之后的情况。

这样我们用JMP @A+DPTR 这条指令就实现了按下一个键跳转到相应程序段去执行的这样一个要求。再提一个问题,为什么取得键值后要乘“2”呢?如果例程下面的所有指令换成LJMP ,即:LJMP S1,LJMP S2??这段程序还能正确地执行吗?如果不能,应该怎么改? 2.条件转移类指令

条件转移类指令就是在满足一定的条件后进行相对转移。 1 转移指令:JZ rel 2 转移指令:JNZ rel

第1条指令的功能是:如果(A)=0 ,则转移,否则顺序执行(执行本指令的下一条指令),转移到什么地方去呢?如果按照传统的方法,就要算偏移量,很麻烦,好在现在我们可以借助于机器汇编了。因此这条指令我们可以这样理解:JZ 标号,即转移到标号处。下面举一个例子来加以说明:

L1: MOV R1,#0FFH;L2: SJMP L2 ;END

在执行上面这段程序前如果R0 中的值是“0”的话,就转移到L1 标号处执行,因此最终的执行结果是R1 中的值为0FFH ;而如果R0 中的值不等于“0”,则顺序执行,也就是执行 MOV R1,#00H 指令,最终的执行结果是R1 中的值等于“0”。把这个例子中的JZ 改成JNZ 试试吧,看看程序执行的结果是什么?

(3)比较转移指令A.CJNE A,#data,rel B.CJNE A,direct,rel C.CJNE Rn,#data,rel D.CJNE @Ri,#data,rel

指令说明:第1 条指令的功能是将A 中的值和立即数data 比较,如果两者相等,就顺序执行;如果不相等,就转移。同样地,我们可以将rel 理解成标号,即:CJNE A,#data, 标号。这样利用这条指令,我们就可以判断两数是否相等,这在很多场合是非常有用的,但有时还想知道两数比较之后哪个大,哪个小,本条指令也具有这样的功能,如果两数不相等,则CPU 还会反映出哪个数大,哪个数小,这是用CY(进位位)来实现的,如果前面的数(A 中的)大,则CY=0 ;否则CY=1 ,因此在程序转移后再次利用CY 就可判断出A 中的数比data 大还是小了。

例如:MOV A,R0 ; CJNE A,#10H,L1 ; MOV R1,#0FFH ; AJMP L3 ; L1: JC L2 ; MOV R1,#0AAH ; AJMP L3 ; L2: MOV R1,#0FFH ;L3: SJMP L3 ;

上面的程序中有一条指令我们还没学过,即JC,这条指令的原型是JC rel, 作用和上面的JZ 类似,但是它是判CY 是“0”还是“1”进行转移,如果CY=1 ,则转移到JC 后面的标号处执行;如果CY=0 则顺序执行。

分析一下上面的程序,如果(A)=10H ,则顺序执行,即R1=0 ;如果(A)不等于10H ,则转到L1 处继续执行,在L1 处,再次进行判断,如果(A)>10H ,则CY=1 ,将顺序执行,(即执行MOV R1,#0AAH 指令);而如果(A)<10H ,则将转移到L2 处指行,(即执行MOV R1,#0FFH 指令)。因此最终结果是:本程序执行前,如果(R0)=10H ,则(R1)=00H ;如果(R0)>10H ,则(R1)=0AAH ;如果(R0)<10H,则(R1)=0FFH。

弄懂了这条指令,其它的几条就类似了,第2 条是把A 当中的值和直接地址中的值比较,第3 条则是将直接地址中的值和立即数比较,第4 条是将间址寻址得到的数和立即数比较,这里就不解释了,请大家自行分析一下。下面给出几个相应的例子:

CJNE A,10H;把A 中的值和10H 中的值比较(注意和上题的区别)CJNE 10H,#35H;把10H 中的值和35H 中的值比较CJNE @R0,#35H;把R0 中的值作为地址,从此地址中取数并和35H 比较 (4)循环转移指令A.DJNZ Rn,rel B.DJNZ direct,rel

第1条指令在前面的实验中已经有详细的分析,这里就不解释了;第2条指令,只是将Rn 改成直接地址,其它的也一样。 3.调用及返回指令

在前面的实验中,我们已用过了子程序,只是我们并没有明确地介绍。子程序是干什么用的,为什么要用子程序呢?举个例子,我们数学老师布置了10 道算术题,经过观察,每一道题中都包含一个(5+2)

*3 的运算。我们可以有两种选择,第一种选择,每做一道题,都把这个算式算一遍;第二种选择,我们可以先把这个结果算出来(也就是21),放在一边,然后要用到这个算式时就将21 代进去,这两种方法哪种更好呢?那就不必多言了吧。设计程序时也是这样,有时候一个功能会在程序的不同地方反复使用,我们就可以把这个功能设计成一段程序,每次需要用到这个功能时就“调用”一下,这就是子程序的用途所在,后面我们还会更详细地讲解子程序的用法。

主程序调用了子程序,子程序执行完之后必须再返回到主程序继续执行,不能“一去不回头”, 那么回到什么地方呢?就是回到调用子程序的下面一条指令处继续执行(当然啦,要是还回到这条指令,不又要再调用子程序了吗?那可就没完没了了??)。大家可以回去看一下前面的实验,是不是这样做的? 1 调用指令

2 长调用指令:LCALL addr16 3 短调用指令:ACALL addr11

上面两条指令都是在主程序中调用子程序,两者的区别大家结合前面的知识可以自己分析一下。同样作为初学者,我们可以不必加以区分,也就是说可以用LCALL 指令代替ACALL。

1 返回指令 2 子程序返回:RET 3 中断返回:RETI

RET 用于子程序返回,RETI 用于中断程序返回,这是有区别的,可不能用错了,至于什么是中断返回,我们讲到中断时再来解释。如何从子程序返回呢?很简单,就是执行RET 指令,看一下前面的实验。 10。空操作指令:NOP

所谓空操作,就是什么事也不干,停一个机器周期,一般用作短时间的延时,这个我们已经讲过了。 单片机经典教程15 单片机指令(五) 一.位及位操作指令

位操作指令也叫布尔操作指令。什么是布尔指令?它有什么用呢?这个问题稍微有点复杂,我只能给大家简单的介绍一下。在MCS-51 系列单片机中,有一个功能很强的布尔处理器,它实际上是一个独立的一位处理器,它有一套专门处理布尔变量(布尔变量也叫开关变量,就是以位作为单位的运算和操作)的指令子集,以完成对布尔变量的传送、运算、转移、控制等操作,这个子集的指令就是布尔操作指令。那么为什么要有这样的一套的指令系统?它是如何操作的呢?大家接着往下看: 1.位寻址的概念

为什么要位寻址呢?单片机不是可以有多种寻址方式吗?大家是否还记得,我们第十三课做的那个流水灯实验,用的就是“位”操作,也就是对一盏灯的亮和灭进行控制,而之前我们学的指令却全都是用“字节”来介绍的:字节的移动、加减法、逻辑运算、移位等等,用字节来处理一些数学问题(比如控制空调的温度、电视机的音量等等)非常直观,可以直接用数值来表示;可是如果用它来控制一个开关的打开或者合上,灯的亮或者灭,就有些不直接了。比如我们前面课上的那个流水灯的实验,我们把数值送往P1 口之后并不能马上知道是哪个LED 灭了,而是要化成二进制后才能知道。在工业控制中有很多场合需要处理这类单个的开关输出,比如一个继电器的吸合或者释放、一个指示灯的亮或者灭,用字节来处理就显得有些麻烦了,所以在51 系列单片机中就特意引入了一个位处理机制。那么位处理器有多少地址空间?哪些特殊功能寄存器可以直接进行位寻址呢? 2.可位寻址的特殊功能寄存器

在MCS-51 单片机中,位地址的范围在00H-FFH 之间,其中低128 位处于内部RAM 的20H-2FH 字节单元,其位地址从00H-7FH,看下面的表:

在物理实体上它们与原来的以字节寻址的RAM 及端口是完全一样的,换句话说这些RAM 单元及端口都可以有两种用法。

除此之外,从80H 单元开始除了程序计数器PC 和4 个工作寄存器区外,每8 个字节还安排了21 个特殊功能寄存器(89C52 有26 个),这些SFR 都有一个共同的特点:就是其字节地址均可被8 整除,大家回到前面看一下第九课的表格。这些SFR 都是具有位寻址功能的,也就是说这些RAM 单元的每一个位都可以直接用这个地址来对其直接进行操作。了解了位操作的原理,再来看位操作的指令: 4.位操作指令

(1)位传送指令A.MOV C,bit B.MOV bit,C

指令说明:这两条指令的功能是实现进位位和其它位地址之间的数据传递(这里bit 就是位的意思)。例如:MOV P1.0,CY ;将CY 中的状态送到P1.0 引脚上去(如果是做算术运算,我们就可以通过观察知道现在CY 是多少了)。再如:MOV P1.0,CY;将P1.0 的状态送给CY。

(2)位清零指令A.CLR C B.CLR bit

指令说明:第1 条指令使CY=0 ;第2 条指令使指定的位地址等于“0”。例如:CLR P1.0 ,使P1.0 为“0”。 (3)位置1 指令A.SETB C B.SETB bit

指令说明:第1 条使CY=1 ;第2 条使指定的位地址等于“1”,例如:SETB P1.0 ,使P1.0 为“1”。 (4)取反指令A.CPL C B.CPL bit

指令说明:第1 条使CY 等于原来的相反的值,即由“1”变为“0”,由“0”变为“1”;第2 条使指定位的值等于原来相反的值,(相当于做“非”运算)。例如:CPL P1.0 ,以我们做过的实验为例,如果原来灯是亮的,则执行本指令后灯就灭了;反之就是灯亮。 (5)位逻辑“与”指令A.ANL C,bit B.ANL C,/bit

指令说明:第1 条CY 位与指定的位地址的值相“与”,结果送回CY;第2 条先将指定的位地址中的值取出后取反,再和CY 相“与”,结果送回CY,但需注意?,指定的位地址中的值本身并不发生变化。例如:ANL C,/P1.0 设:执行本指令前,CY=1,P1.0 等于“1”(灯灭),则执行完本指令后CY=0,而P1.0 仍等于“1”。可用下列程序进行验证:

START:MOV SP,#5FH ; MOV P1,#0FFH; SETB C ; ANL C,/P1.0 ;MOV P1.1,C ;将做完的结果送P1.1, 结果应当是P1.1 上的灯亮,而P1.0 上的灯还是亮。

(6)位逻辑“或”指令A.ORL C,bit B.ORL bit,C

这两条指令的功能大家自行分析吧,然后对照上面的例程,自己编一个验证程序,看看自己想得对不对? (7)判CY 条件转移指令A.JC rel B.JNC rel

指令说明:这两条指令叫做判CY 转移指令,第1 条指令的功能是如果CY 等于“1”就转移;如果不等于“1”就顺序执行,那么转移到什么地方去呢?我们可以这样理解:JC 标号,即如果等于“1”就转到标号处执行;第2 条指令则和第1 条指令正好相反,即如果CY=0 就

转移,不等于“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 四.定时器/计数器的定时/计数范围

那么单片机的这四种工作方式的计数范围是如何确定的呢?

1.工作方式0

13 位的定时/计数器工作方式。因此,最多可以计到2 的13 次方,也就是8192 次。 2.工作方式1

16 位的定时/计数器工作方式。因此,最多可以计到2 的16 次方,也就是65536 次。 2 3.工作方式2 和3

工作方式2 和工作方式3 都是8 位的定时/计数器工作方式,因此最多可以计到2 的8 次方,也说是256 次。

预置值计算:用最大计数量减去需要的计数次数即可。例如:流水线上一个包装是24 盒,要求每到24 盒就产生一个动作,用单片机的工作方式0 来控制,应当预置多大的值呢?对了,就是8192-24=8168。 以上是计数器的原理,明白了这个道理,定时器也一样。

单片机经典教程18 单片机的中断系统

单片机中断系统的目的是为了让CPU 对内部或外部的突发事件及时地作出响应,并执行相应的程序,在单片机的开发中,它同样有着十分重要的作用,那么单片机的中断是怎么回事?它是如何来工作的呢?这一课就来讨论这个问题,在讲解之前让我们先来介绍一下中断的原理:

一、中断的基本原理

什么是中断?中断的过程是什么?要搞清楚这个问题,我们同样先从生活中的一个例子开始:你正在家中看书,突然电话铃响了,你放下书,去接电话,和来电话的人交谈,通完电话,回来继续看你的书,这就是生活中的“中断”现象—就是正常的工作过程被外部的事件打断了。仔细研究一下生活中的中断,对我们学习单片机中断会很有帮助:

第一,中断源。什么可引起中断,生活中很多事件都可以引起中断,比如:有人按了门铃、电话铃响了、你的闹钟响了、你烧的水开了??等等诸如此类的事件,我们把可以引起中断的事件称之为中断源。单片机中也有一些可以引起中断的事件(比如:掉电、运算溢出、报警等等),89C51 单片机中共有5 个中断源:两个外部中断,两个定时/计数器中断(溢出),一个串行口中断。

第二,中断的嵌套与优先级处理。设想一下,我们正在看书,电话铃响了,同时又有人按了门铃,你该先做那样呢?如果你正在等一个很重要的电话,一般是不会去理会门铃的;而反之,如果你正在等一个很重要的客人,则可能就不会去理会电话了;如果两者都不是(既不等电话,也不等人上门),你可能会按你通常的习惯去处理。总之这里存在一个优先级的问题,单片机中也是如此,也有优先级的问题。优先级的问题不仅仅发生在两个中断同时产生的情况,也发生在一个中断已产生,又有另一个中断产生的情况。比如你正在接电话,又有人按门铃的情况,或者你正开门与人交谈,又有电话响了的情况。考虑一下,我们一般会怎么办?

第三,中断的响应与处理。当有事件产生,进入中断之前我们必须先记住现在的书看到第几页了,或拿一个书签放在当前页的位置,然后去处理不同的事情(因为处理完了,我们还要回来继续看书),电话铃响我们要到放电话的地方去,门铃响了我们要到门那边去,也就是说不同的中断,我们要在不同的地点处理,而这个地点通常还是固定的。单片机中采用的也是这种方法,五个中断源,每个中断产生后都到一个固定的地址去找处理这个中断的程序,当然在去之前首先要保存下面将执行的指令的地址,以便处理完中断后回到原来的地方继续往下执行程序。具体地说,单片机中断响应可以分为以下几个步骤:1、停止主程序运行。当前指令执行完后立即终止现行程序的运行。2、保护断点。把程序计数器PC 的当前值压入堆栈,保存终止的地址(即断点地址),以便从中断服务程序返回时能继续执行该程序,3、寻找中断入口。根据5 个不同的中断源所产生的中断,查找5 个不同的入口地址。4、执行中断处理程序。这就不讲了;5、

中断返回。执行完中断处理程序后,就从中断处返回到主程序,继续往下执行。以上工作是由计算机自动完成的,与编程者无关,在这5 个入口地址处存放有中断处理的程序(这是程序编写时放在那儿的,如果没把中断处理程序放在那儿可就错了,因为中断程序无法被执行到)。有点复杂是吗?没关系,继续往下看。二、实现中断的好处

单片机为什么要有中断系统,使用中断有什么好处呢?日常生活中,我们除了看书,肯定还要做很多其他的事情,比如听电话,接待客人,烧水吃饭等等,单片机实行中断也有很多的好处,具体来说:1。实行分时操作,提高CPU 的效率。只有当服务对象向CPU 发出中断申请时,才去为它服务,这样我们就可以利用中断功能同时为多个对象服务,从而大大提高了CPU 的工作效率。2。实现实时处理。利用中断技术,各个服务对象可以根据需要随时向CPU 发出中断申请,及时发现和处理中断请求并为之服务,以满足实时控制的要求。3。进行故障处理。对难以预料的情况或故障,比如掉电,事故等,可以向CPU 发出请求中断,有CPU 作出相应的处理。那么单片机是如何实现中断处理的呢?要了解这个问题,就让我们先来看看单片机中断系统的内部结构。 三.单片机中断系统的结构:

前面已经提到,89C51 单片机有5 个中断源,那么它们的硬件结构是怎么样的呢?请看下面的图,这就是89C51 单片机的中断系统内部结构图,下面让我们一一来进行分析: 1.中断源: (1)外部中断:

即外中断0 和外中断1,经由外部引脚引入,在单片机的硬件上有两个引脚(12 和13),名称为INT0 和INT1 (第二引脚功能P3.2、P3.3)。在单片机的内部有一个特殊功能寄存器TCON,其中有四位是与外中断有关的。还记得TCON 是什么吗?对了,是定时器控制寄存器,请看下面的表:

A.IT0:中断0(INT0)的触发方式控制位。

可由软件进行置位和复位,IT0=0,中断0 为低电平触发方式;IT0=1,中断0 为负跳变触发方式。这两种方式的差异讲起来有点复杂,这里就不介绍了,作为初学者,只要知道就可以了。至于具体如何使用,我们到后面做实验时再讲解。

B.IE0:中断0( INT0)的中断请求标志位。

当有外部的中断请求时,该位就会置“1”;在CPU 响应中断后,该位就自动清“0”。(注意?:这是由硬件自动完成的)。 IT1、IE1 的用途和IT0、IE0 是类似的。看上面的表: (2)内部中断

即定时器0(T0)和定时器1(T1)中断,与外中断一样,它也是由TCON 中的四位控制的。 A. TF0:定时器T0 的溢出中断标记。

当T0 计数产生溢出时,由硬件置位TF0;当CPU 响应中断后,再由硬件将TF0 自动清“0”。 TF1 :与TF0 类似,这里就不讲了。 (3)串行口中断

单片机教程(MCS-51 系列)

负责串行口的发送、接收中断,具体内容我们到下册学习串行接口时再详细讲解。下面我们再来讲另一个与中断有关的寄存器。 2、中断允许寄存器IE(A8H)

中断的允许或禁止是由片内可进行位(什么是位,大家可别到现在还说不知道哦)寻址的8 位中断允许寄存器IE 来控制的,单片机中断系统中有两种不同类型的中断:一种称为非屏蔽中断;另一种称为屏蔽中断。对于非屏蔽中断,用户不能用软件方法加以禁止,一旦有中断申请,CPU 将根据自然优先级予以响应。这里主要讲屏蔽中断,对于屏蔽中断,我们可以通过软件的方法来予以控制(允许中断我们把它称为中断开放,不允许中断我们把它称之为中断屏蔽),如何操作,说穿了其实很简单,就是通过对IE 的相应位的置“1”或请“0”来允许或禁止某个中断,请看下面的表格:

(1)EA:总中断允许开关。它是个总开关,凡是要设置中端都得先通过它。EA=1,开放所有的

中断;EA=0,则所有中断都被禁止。(2)ES:串行口中断控制位。ES=1,允许中断;ES=0,禁止中断。(3)ET1:定时/计数器1 中断控制位。ET1=1,允许中断;ET1=0,禁止中断。(4)EX1:外中断1 中断控制位。EX1=1,允许中断;EX1=0,禁止中断。(5)ET0:定时器0 中断控制位。ET0=1,允许中断;ET0=0,禁止中断。(6)EX0:外中断0 中断控制位。EX0=1,允许中断;EX0=0,禁止中断。

例如:我们现在要设置INT1 允许,T1 允许,其它不允许,则IE 应该是10001100 (即8CH),也可以直接用位操作指令SETB EA;SETB ET1;SETB EX1 来实现它。看一下下面的表:

这里有一点请大家注意?:当复位CPU 时,IE 将被全部清“0”。

了解了中断的设置,让我们再来看另一个问题:前面我们提到过,中断有优先级和嵌套的问题,那么中断的优先级和嵌套是如何来控制的呢?接着往下看:

3.中断源优先级寄存器IP(D8H)

单片机执行中断的过程和生活中的中断有些类似,它也有一个自然优先级与人工优先级的问题,那么单片机是如何来设置它们的呢?这就要用到中断优先级寄存器IP,它也是一个可位寻址的8 位寄存器。现在让我们先来看五个中断源的自然优先级是如何设置的: 五个中断源的自然优先级由高到低的排列顺序为外中断0→定时器0→外中断1→定时器1→串口中断。如果我们不对其进行设置,单片机就按照此顺序不断的循环检查各个中断标志(就像我们生活中按照习惯处理事物一样),但有时我们需要人工设置高、低优先级,也就 是说由编程者来设定哪些中断是高优先级、哪些中断是低优先级(当然由于只有两级,所以必然只有一些中断处于优先级别,而其他的中断则处于同一级别,处于同一级别的中断顺序就由自然优先级来确定,这一点请大家务必搞清楚了)。

既然可以设定人工优先级,那么它又是如何来设置的呢?其实很简单,我们只要把IP 寄存器的对应位置“1”就可以了,看下面的表: 开机时,每个中断都处于低优先级,我们可以用指令来对优先级进行设置。例如:现在有如下要求,将T0、INT1 设为高优先级,其它为低优先级,求IP 的值。

IP 的首3 位没用,可任意取值,设为000 ,后面根据要求写:00000110 ,即IP=06H,看下面的表。

这里有个问题:如果5 个中断请求同时发生时又会出现什么情况呢?比如在上例中,五个中断同时发生,求中断响应的次序。按照我们学到的内容,响应次序应该为:定时器0→外中断1→外中断0 68

→实时器1→串行中断。是不是符合我们刚才说的除了人工设置的高优先级外,其余的均按照自然优先级来处理。其实这很好理解,如果我在家等待一个很重要的电话,同时又有人来敲门或者烧的水开了,当我放下电话后,还是会按照一般的习惯去处理其他的事情(比如先开门让客人进来,再去处理烧开的水)。 4.串行口控制寄存器SCON(98H)

用于串行口中断及控制,我们留到下册中再详细解释。下面讨论一下另一个问题: 四.中断响应的条件和过程 1、中断响应的原理

讲到这里,我们依然对于单片机响应中断的过程感到神秘,我们人响应外界的事件,是因为我们有多种“传感器”――眼、耳等可以接受不同的信息,那么单片机是如何做到这点的呢?其实说穿了,一点也不稀奇,单片机工作时,在每个机器周期中都去查询一下各个中断标记,看它们是否是“1”,如果是“1”,就说明有中断请求了。所以所谓中断,其实也就是查询,只不过是每个周期都查一下而已。这要换成人来说,就相当于你在看书的时候,每一秒钟都抬起头来看一看,看是不是有人按门铃了,是否有电话来了,烧的水是否开了??很蠢,是吗?本来嘛,计算机它根本就没人聪明。了解了响应中断的原理,就不难理解中断响应的条件了。 2、中断响应的条件

当有下列三种情况之一发生时,CPU 将封锁对中断的响应,而是到下一个机器周期时再继续查询:

(1)CPU 正在处理一个同级或更高级别的中断请求时。 (2)当前的指令没有执行完时。

我们知道,单片机有单周期指令、双周期指令、三周期指令和两个四周期指令,如果当前执行指令是单字节指令也许没关系,如果是双字节或四字节的指令,那就要等整条指令都执行完了,才能响应中断(因为中断查询是在每个机器周期都可能查到的)。 (3)当前正执行的指令是返回指令(RETI)或访问IP、IE 寄存器的指令,则CPU 将至少再执行一条指令才能响应中断。

这些都是与中断有关的寄存器,如果正访问IP、IE 则可能会出现开、关中断或改变中断的优先级;而中断返回指令则说明本次中断还没有处理完,所以就要等本指令处理结束,再执行一条指令才可以响应中断。 3、中断响应的过程:

CPU 响应中断时,首先把当前指令的下一条指令(就是中断返回后将要执行的指令)的地址(也叫

断点地址)送入堆栈,然后根据中断标记,硬件执行长跳转指令,转到相应的中断源入口处,执行中断服务程序,当遇到RETI(中断返回指令),返回到断点处继续执行程序,这些工作都是由硬件自动来完成的。那么中断入口的地址是如何来确定的呢?在51 系列单片机中,五个中断源都有它们各自的中断入口地址,请看下面: (1)外中断0(INT0):0003H (2)定时器0(T0):000BH (3)外中断1(INT1):0013H (4)定时器1(T1):001BH (5)串口中断:0023H

讲到这里,大家应该明白,为什么我们前面的有些程序一开始是这样写的: ORG 0000H ; LJMP START ; ORG 0030H ; START:***** ; ***** ; ***** ; END。

其实这样写的目的,就是为了让出中断源所占用的向量地址,当然,在程序中如果没有用到中断时,直接从地址0000H 开始写理论上不是不可以,但在实际工作中最好不要这样做。这里还有一个问题,大家是否注意到,每个中断向量地址只间隔了8 个字节,如0003H-000BH,在如此少的空间中如果完成不了中断程序,又该这么办呢?其实很简单,您只要在中断处安排一条LJMP 指令,不就可以把中断服务程序跳转到任何地方去了吗?所以一个完整的主程序看起来应该是这样的: ORG 0000H ; LJMP START ; ORG 0003H ;

LJMP AINT0 ;转外中断0 服务程序 ORG 000BH ;

RETI ;没有用定时器0 中断,在此放一条RETI,万一“不小心”产生了中断,也不会有太大的后果。 START:***** ;主程序 ***** ; ***** ;

AINT0:***** ;中断服务程序 ***** ;

RETI ;从中断服务程序返回 END。

中断程序处理完成后,一定要执行一条RETI 指令,执行这条指令后,CPU 将会把堆栈中保存着的地址取出,送回程序计数器PC,那么程序就会根据PC 中的值从主程序的中断处继续往下执行了。从CPU 终止当前程序,且转向另一程序这点看,中断的过程很象子程序,其实它们之间还是有区别的:中断发生的时间是随机的,而子程序调用则是按程序进行的,所以它们的返回命令也是不一样的。RET 是用在返回子程序中的,而RETI 则用在返回中断处理程序中的,这一点千万不能搞错了。另外还有一点需要大家注意?的是:在这里CPU 所做的自动保护工作是很有限的,它只保护了一个地址(就是中断返回后将要执行的指令的地址,即PC 的值),而其它的所有东西都不保护,所以如果你在主程序中用到了如ACC、PSW 等寄存器,而在中断程序中又要用到它们,还要保证回到主程序后这里面的数据还是没执行中断之前的数据,就得自己把它们保护起来。这是一项非常重要的工作,否则程序执行的结果就不是您想象的要求了!(具体如何做,我们将在下一课的实验中详细解释)。

单片机经典教程19 单片机的定时与中断实验

前面的两节课程我们介绍了单片机定时/计数器和中断的原理及结构,这节课我们就来做几个实验验证一下前面所学的内容。实验一、利用定时器实现灯的闪烁

在开始学单片机时我们所做的第一个实验就是LED 灯的闪烁,不过那是用延时程序做的,现在回想起来,这样做不是很恰当,为什么呢?因为我们的主程序做了灯的闪烁,就不能再干其它的事情了,难道单片机只能这样工作吗?当然不是,我们可以用定时器来完成灯的闪烁功能。 程序如下: ORG 0000H ; AJMP START ; ORG 30H ;

START:MOV P1,#0FFH ;关所有的灯

MOV TMOD,#00000001B ; 定时/计数器0 工作于方式1 MOV TH0,#15H ;

MOV TL0,#0A0H ;立即数(15AH+0A0H=5536) SETB TR0 ; 定时/计数器0 开始运行

LOOP:JBC TF0,NEXT ;如果TF0 等于1,则清TF0 并转NEXT 处 此处可以加入其他的任何指令 AJMP LOOP ;否则跳转到LOOP 处运行 NEXT:CPL P1.0 ; MOV TH0,#15H ;

MOV TL0,#9FH ;重置定时/计数器的初值 AJMP LOOP ; END。

把程序下载到实验板,看到了什么?灯开始闪烁了,这可是用定时器做的,不再是主程序的循环了。简单分析一下程序,为什么用JBC 呢?TF0 是定时/计数器0 的溢出标记位,当定时器产生溢出后,该位由“0”变“1”,所以查询该位就可知道定时时间是否已到,该位为“1”后,要用软件将标记位清“0”,以便下一次定时时间到了将该位由“0”变为“1”,所以用了JBC 指令,该指令前面已经学过--判“1”转移的同时,将该位清“0”。

以上程序可以实现灯的闪烁了,可是主程序除了让灯闪烁外,还是不能做其他的事啊!不对,我们可以在LOOP:JBC TF0,NEXT 和AJMP LOOP 指令之间插入一些指令来做其他的事情,只要保证执行这些指令的执行时间少于定时时间就可以了。

提个问题,我们在用软件延时程序的时候不是也可以用DJNZ 替代一些指令吗?是的,但是那就要求你精确计算所用指令的时间,然后再减去相应的DJNZ 循环次数,很不方便,而现在只要求所用指令的时间少于定时时间就行,显然要求低了。当然,这样的方法还不是最好,所以我们常用下面的方法来实现: 实验二、利用中断方式实现灯的延时

ORG 30H ;START:MOV P1,#0FFH ;关所有灯MOV TMOD,#00000001B ; 定时/计数器0 工作于方式1 MOV TH0,#15H ; MOV TL0,#0A0H ;立即数5536 SETB EA ;开总中断允许SETB ET0 ;开定时/计数器0 允许SETB TR0 ;定时/计数器0 开始运行LOOP:AJMP LOOP ;真正工作时,这里可写任意程序TIME0:PUSH ACC ;将ACC 推入堆栈保护PUSH PSW ;将PSW 推入堆栈保护CPL P1.0 ;取反P1.0 MOV TH0,#15H ; MOV TL0,#0A0H ;重置定时常数POP PSW ;POP ACC ;RETI ;END。

上面的例子中,定时时间一到,TF0 由“0”变“1”,就会引发中断,CPU 将自动转至000B 处寻找程序并执行,由于留给定时器中断的地址空间只有8 个字节,显然不足以写下所有的中断处理程序,所以在ORG 000BH 后安排了一条长跳转指令,转到实际处理中断的程序处,这样,中断程序可以写在任意地方,也可以写任意长度了。单片机进入定时中断后,首先要保存当前的一些状态,在这里程序只演示了保存ACC 和PSW,实际工作中应该根据需要将可能会改变的单元的值都推入堆栈进行保护(本程序中实际不需保护任何值,这里只作个演示)。 上面的两个程序运行后,我们发现灯的闪烁非常快,根本分辨不出来,只是视觉上感到灯有些晃动而已,为什么呢?我们可以计算一下,定时器中预置的数是5536 ,所以每计60000(65536-5536 )个脉冲就是定时时间到,这60000 个脉冲的时间是多少呢?我们的晶振是12 兆的,所以就是60000 微秒,即60 毫秒,因此速度是非常快的。如果我想实现一个1 秒的定时器,该怎么办呢?在该晶振频率下,最长的定时也就是65.536 个毫秒啊!请看第三个实验: 实验三、延长定时时间的方法 程序如下:

ORG 0000H ; AJMP START ;ORG 000BH ;定时器

0 的中断向量地址

AJMP TIME0 ;跳转到真正的定时器程序处

ORG 30H ;START:MOV P1,#0FFH ; 关所有的灯MOV 30H,#00H ;软件计数器预清0 MOV TMOD,#00000001B; 定时/计数器0 工作于方式1 MOV TH0,#3CH ;MOV TL0,#0B0H ;立即数(3CH+0BH=15536)SETB EA ;开总中断允许SETB ET0 ;开定时/计数器0 允许SETB TR0 ;定时/计数器0 开始运行

LOOP:AJMP LOOP ;真正工作时,这里可写任意程序TIME0:PUSH ACC ;将ACC 推入堆栈保护PUSH PSW ;将PSW 推入堆栈保护INC 30H ;MOV A,30H ;CJNE A,#20,TIME1 ;30H 单元中的值到了20 了吗? CPL P1.0 ;到了,取反P1.0 MOV 30H,#0 ;清软件计数器

TIME1:MOV TH0,#15H ;给T0 重新赋值MOV TL0,#9FH ;重置定时常数POP PSW ;POP ACC ;RETI ;END。

先自己分析一下程序,看看是怎么实现的?这里采用了软件计数器的概念,思路是这样的,先用T0 做一个50 毫秒的定时器,定时时间到了之后并不是立即取反P1.0,而是将软件计数器中的值加1,如果软件计数器计到了20,就取反一次P1.0,并清掉软件计数器中的值;否则直接返回。这样,就变成了20 次定时中断才取反一次P1.0,因此定时时间就延长了成了20*50mS ,即1000 毫秒了。这个思路在实际的工程应用中是非常有用的,比如有时候我们需要若干个定时器,可89C51 中总共才有2 个,怎么办呢?其实,只要这几个定时器在时间上有一定的公约数,我们就可以用软件定时器加以实现。例如要实现P1.0 口所接灯按1S/次,而P1.1 口所接灯按2S/次闪烁,怎么实现呢?我们可以用两个计数器,一个在它计到20 时,取反一次P1.0,并清“0”,如上面所示;而另一个则计到40 取反一次P1.1,然后清“0”,不就行了吗?看下面的实验: 实验四、软件定时器的实现方法 程序如下:

ORG 0000H ; AJMP START ; ORG 000BH ;定时器

0 的中断向量地址

AJMP TIME0 ;跳转到真正的定时器程序处

ORG 0030H ; START:MOV P1,#0FFH ; 关所有的灯MOV 30H,#00H ;软件计数器预清0 MOV TMOD,#00000001B ; 定时/计数器0 工作于方式1 MOV TH0,#3CH ;MOV TL0,#0B0H ;立即数15536 SETB EA ;开总中断允许SETB ET0 ;开定时/计数器0 允许SETB TR0 ;定时/计数器0 开始运行LOOP:AJMP LOOP ;真正工作时,这里可写任意程序TIME0: PUSH ACC ;将ACC 推入堆栈保护PUSH PSW ;将PSW 推入堆栈保护INC 30H ;INC 31H ;两个计数器都加1

MOV A,30H ; CJNE A,#20,TNEXT ;30H 单元中的值到了20 了吗CPL P1.0 ;到了,取反P1.0

这就是软件定时器的用法,试试看,用软件定时器做一个交通信号灯的实验,要求红灯亮2 分钟,黄灯亮1 分钟,绿灯亮3 分钟,然后红灯再亮2 分钟,黄灯再亮1 分钟,绿灯再亮3 分钟,不断的循环??。通过上面的几个实验,大家对定时器的使用方法是不是有了一个初步认识,接下来让我们共同来做一个更为有趣的实验:用单片机做一个乐曲编奏器。实验五、可编程乐曲演奏器

单片机用作乐曲编奏器的原理是,通过控制定时器的定时时间来产生不同频率的方波,驱动喇叭发出不同音阶的声音,再利用延迟来控制发音时间的长短,就能控制音调中的节拍。编程时,把乐谱中的音符和相应的节拍变换为定时常数和延迟常数,把数据表格存放在存储器中,由查表程序得到相应的定时常数和延迟常数,分别用定时器控制产生方波的频率和发出该频率方波的持续时间,当延迟时间到了之后,再查下一个音符的定时常数和延迟常数。依次进行下去,就可自动演奏出悦耳动听的乐曲来。 程序如下:

ORG 001BH ;定时器T1 的中断入口 MOV TH1,R1 ;重装定时初值 MOV TL1,R0 ;

CPL P3.7 ;P1.0 输出方波 RETI ;中断返回 ORG 100H ;主程序

START:MOV TMOD,#10H ; 定时器T1 工作方式1 MOV IE,#88H ;允许T1 中断 MOV DPTR,#TAB ;表格首地址 LOOP:CLR A ; MOVC A,@A+DPTR ;查表

MOV R1,A ;定时器高8 为存R1 INC DPTR ; CLR A ;

MOVC A,@A+DPTR ;查表

MOV R0,A ;定时器低8 为存R0 ORL A,R1 ;

JZ NEXT0 ;全0 为休止符 MOV A,R0 ; ANL A,R1 ;

CJNE A,#0FFH,NEXT ;全1 表示乐曲结束SJMP START ;从头开始,循环演奏NEXT:MOV TH1,R1 ;装入定时值MOV TL1,R0 ; SETB TR1 ;启动定时器SJMP NEXT1 ; NEXT0:CLR TR1 ;关闭定时器,停止发音NEXT1:CLR A ; INC DPTR ; MOVC A,@A+DPTR ;查延迟常数MOV R2,A ; LOOP1:LCALL D200 ;调用延时200mS 子程序

上述程序是歌曲“新年好”的一段简谱:1=C 1 1 1 5 │ 3 3 3 1 │ 1 3 5 5 │ 4 3 2 —│,如何来实现的呢?。单片机的晶振为12M,则乐曲、频率及定时常数三者之间的对应关系如下表所示:

我们用定时器T1 工作方式1 来产生歌谱中各音符对应频率的方波,由P3.7 输出驱动蜂鸣器,节拍的控制通过调用延时子程序D200 (延

时200mS )的次数来实现,以每拍800mS 的节拍时间为例,一拍需要调用D200 子程序4 次,同样的道理,半拍就需要调用2 次,由此就演奏出了上面的乐曲。

单片机经典教程20 单片机的定时与中断实验

现在来看一看外部计数和外部中断的实验,在实际的工程应用中计数器会有两种要求:第一种,将计数的值显示出来,象录音机上的计数器、汽车上的里程表等等;第二种,计数值到一定值后即中断报警,如前面提到的生产线上的计数、定长定量仪等等。接下来我们先来做一个外部计数器的实验。要将外部计数的值显示出来,最好是用数码管,可我们还没有讲到这一部份,为了避免把问题复杂化,我们用P1 口的8 个LED 来显示计到的数据。为了完成这个实验,我们需要用到一套附件,它的作用就是输出一个宽度为500mS 的方波,这套附件不在我们的实验板上。为了节约大家的学习费用,我特地做了几套供大家借用,各位可以到我这儿来借,免费使用,不过请各位爱惜哦,不要搞坏了。有了这套附件就可以完成我们的实验了。我们把附件的两根线分别连接到实验板的电源接口和单片机的15 脚(也就是T1 的输入端)。实验的程序如下:一.计数器实验一程序如下: ORG 0000H ; AJMP START ; ORG 0030H ; START:MOV SP,#5FH ; MOV TMOD,#01000000B ; 定时/计数器1 作计数用,T0 不用全置“0” SETB TR1 ;启动T1 开始运行 LOOP:MOV A,TL0 ; MOV P1,A ; AJMP LOOP ; END。运行这个程序,看到了什么?随着LED 的闪烁,实验板上的8 个LED 也在不断地变化,注意观察,是不是按二进制:00000000,00000001,00000010,00000011 ,??这样的顺序在变呢?对了,这就是TL0 中的数据。不过这个实验还看不出什么名堂,接着做第二个实验:二.计数器实验二程序如下: ORG 0000H ; AJMP START ;ORG 001BH ; AJMP TIMER1 ;定时器1 的中断处理ORG 0030H ; START:MOV SP,#5FH ; MOV TMOD,#01010000B ; 定时/计数器1 作计数用,工作方式1,T0 不用置“0” MOV TH1,#0FFH ; MOV TL1,#0FAH ;预置值,每计到6 个脉冲即为一个事件 SETB EA ; SETB ET1 ;开总中断和定时器1 中断允许 SETB TR1 ;启动定时/计数器1 开始运行 AJMP $ ; TIMER1:PUSH ACC ; 这个程序完成的工作其实很简单,就是每6 个计数脉冲到来后取反一次P1.0,因此实验的结果应当是:15 脚接的LED 亮、灭6 次,则P1.0 口所接的LED 就亮或灭一次,这就是我们对输入脉冲的计数,也就是每6 个计数产生一次中断。这段程序中有一个符号以前没见过,需要给大家解释一下,AJMP $,“$”我们称为标识符,它的作用是指这段指令的开始处,(注意?:是标号以后的这段指令),在这里,它的意思就是跳转到标号START 处执行,它的作用同AJMP START 是一样的。那为什么要这样写呢?就是为了方便,如果您觉得不习惯,直接写AJMP START 也是一样的。这两个实验需要附件,如果您觉得做起来不大方便,没关系,我们接着来做第三个实验,采用两个定时/计数器合用,一个作为定时器用,一个作为计数器用,来实现P1.1 的延时,这可以直接在我们的实验板上完成。三.两个定时/计数器合用的延时实验采用两个定时/计数器,其中T0 作为定时器用,工作方式为方式1,T1 作为计数器用,计数次数为1000 次。T0 溢出时,产生一个间隔为60mS 的方波(也就是让LED3 各亮灭60mS),然后把P1.2 的输出作为T1 的计数脉冲,T1 计数溢出时(满1000 次),取反一次P1.1,产生一个周期为2 秒的方波(即LED2 每2 秒闪烁一次)。程序如下: ORG 0000H ; AJMP MAIN ; ORG 000BH ;定时器T0 的中断入口 AJMP T_0 ;转T0 中断服务程序 ORG 001BH ;定时器T1 的中断入口 AJMP T_1 ;转T1 中断服务程序 ORG 0030H ; MAIN:MOV TMOD,#51H ;T1 为计数器方式1,T0 为定时器方式1 MOV TH0,#15H ;设置T0 初值 MOV TL0,#0A0H ; MOV TH1,#0FCH ;设置T1 初值 MOV TL1,#18H ; MOV IE,#8AH ;允许 T0、T1 中断 SETB TR0 ;启动定时器T0 SETB TR1 ;启动定时器T1 LL:SJMP LL ;循环T_0:MOV TH0,#15H ;给T0 重新赋值 MOV TL0,#0A0H ; CPL P1.2 ;定时到,取反P1.2 RETI ; T_1:MOV TH1,#0FCH ;给T1 重新赋值 MOV TL1,#18H ; 把程序下载到单片机,看到什么?LED3 在不断的闪烁,这就是T0 的作用,闪烁的周期是多少,请大家计算一下。接下来,把P1.2(也就是3 脚)和P3.5(也就是T1)的输入端相连接,是不是接在P1.1 上的LED2 每2 秒闪烁一次。对了,这就是T1 作计数器的结果。在这段程序里,有一点请大家注意?,第四条—AJMP T_0, 为什么要在T 和0 之间加上一条横线,而不直接用T0 呢?原来在MCS—51 系列单片机中,是不能用T0、T1、INT、RET、IP、PSW 等等内部名称作为标号的,如果这样做的话,编译软件会出错,这点我们好象很早以前曾经提到过。接下来我们再来做一个外部中断的实验:四.外部中断实验程序如下: ORG 0000H ; AJMP START ; ORG 0003H ;外部中断0 地址入口AJMP INTO ;ORG 30H ;START: MOV SP,#5FH ; MOV P1,#0FFH ;灯全灭 MOV P3,#0FFH ;P3 口置高电平 SETB EA ; SETB EX0 ; AJMP $ ; INTO:PUSH ACC ; PUSH PSW ; CPL P1.0 ; POP PSW ; POP ACC ; RETI ; END。本程序的功能就是按一次按键S1(接在P3.2 引脚上的),就引发一次外部中断(INT0=0),取反一次P1.0,因此理论上按一下灯亮,再按一下灯灭,有点象我们工程应用中的自锁开关。不过这段程序在实际的实验中,可能会发觉有时不很“灵”,按了它没反应,但在大部份时候还是对的,这是怎么回事呢?其实这是因为按键没有作“去抖动”处理,也就是说,理论上我们是按了一次键,但由于计算机的处理速度很快,计算机实际上却认为已经按了好多次了,如何来解决这个问题呢?这就需要我们对按键作去抖动处理,什么是按键的去抖动处理,我们下一课讨论键盘接口时再作详细解释。 单片机经典教程21 键盘的工作原理和编程方法

键盘接口和数码管接口是构成单片机人机界面的主要方法,对于一个初学者来说,这部分的内容也是较难的,我们将用四节课的时间来学习这方面的知识。这一课先来讨论键盘的接口原理与编程方法。

键盘是单片机应用系统不可缺少的重要输入设备,主要负责向计算机传递信息,我们可以通过键盘向计算机输入各种指令、地址和数据。它一般由若干个按键组合成开关矩阵,按照其接线方式的不同可分为两种:一种是独立式接法,一种是矩阵式接法(如下面的图),这一课先来讲解独立式键盘的工作原理和编程方法。一.独立式键盘的工作原理和编程方法

独立式键盘具有结构简单,使用灵活等特点,因此被广泛应用于单片机系统中,那么它是如何来工作的呢?我们慢慢往下看: 1.独立式键盘的接线原理

独立式键盘是由若干个机械触点开关构成的,把它与单片机的I/O 口线连起来,通过读I/O 口的电平状态,即可识别出相应的按键是否被按下,看下面的电路图:

如果按键不被按下,其端口就为高电平;如果相应的按键被按下,则端口就变为低电平。在这种键盘的连接方法中,我们通常采用上拉电

阻接法,即各按键开关一端接低电平,另一端接单片机I/O 口线并通过上拉电阻与VCC 相连,如上图所示。这是为了保证在按键断开时,各I/O 口线有确定的高电平,当然,如果端口内部已经有上拉电阻,则外电路的上拉电阻可以省去,想想看,哪几个并行口内部是有上拉电阻的?

通常我们用来做键盘的按键有触点式和非触点式两种,单片机中应用的一般是由机械触点构成的触点式微动开关。这种开关具有结构简单,使用可靠的优点,但当我们按下按键或释放按键的时候它有一个特点,就是会产生抖动,看上图的按键脉冲波形,这种抖动对于人来说是感觉不到的,但对单片机来说,则是完全可以感应到的,因为计算机处理的速度是在微秒级的,而机械抖动的时间至少是毫秒级,对计算机而言,这已是一个很“漫长”的过程了。下面我们通过一个实验来验证一下,实验程序如下: ORG 0000H ; AJMP START ; ORG 0030H ; START:MOV SP,5FH ;

把这个程序下载到单片机,我们会发现,当按下相应的按键时,灯并不是想象中的按一下亮,再按一下就灭,而是有时灵,有时不灵,为什么会这样呢?原来,当你按了一次按键,可是单片机却早已执行了好多次,如果执行的次数正好是奇数次,那么结果正如您所料;如果执行的次数是偶数次,那结果就不对了。为了使CPU 能正确地读出端口的状态,对每一次按键只作一次响应,就必须考虑如何去除按键的抖动。

2.按键的去抖动原则和方法

常用的去抖动的方法有两种:硬件方法和软件方法。硬件去抖动的方法很多,好多书都有介绍,这不在我们的讨论范围。单片机中常用软件去抖动法,软件法其实很简单,就是在单片机获得端口为低电平的信息后,不是立即认定按键已被按下,而是延时10 毫秒或更长一些时间后再次检测该端口,如果仍为低,说明此键的确被按下了,这实际上是避开了按键按下时的抖动时间;而在检测到按键释放后(端口为高电平时)再延时5-10 毫秒,消除后沿的抖动,然后再对按键进行处理,不过一般情况下,我们通常不对按键释放的后沿进行处理,实践证明,也能满足通常的要求。下面我们把前面的程序改一下,看看按键的去抖动是如何实现的。看下面的程序: ORG 0000H ; AJMP START ; ORG 0030H ; START:MOV SP,#5FH ; MOV P1,#0FFH ;

MOV P3,#0FFH ; L1:JB P3.4,L2 ;P3.4 为“1”,不做处理,转P3.5 LCALL D10mS ; 调用延时程序

JB P3.4,L1 ; P3.4 为“0”,说明此键确实被按下了

CPL P1.0 ; 去除抖动后取反P1.0 L3:JNB P3.4,L3 ;直到P3.4 释放后转去判断第二个键L2:JB P3.5,L1 ;P3.5 为“1”,返回去继续处理P3.4

LCALL D10mS ; 调用延时程序

JB P3.5,L2 ; P3.5 为“0”,说明此键确实被按下了

CPL P1.1 ; 去除抖动后取反P1.1 L4:JNB P3.5,L4 ;直到P3.5 释放为止 LJMP L1 ;返回D10mS:MOV R7,#50 ;延时的时间一般为10-20mS D1:MOV R6,#200 ; D2:DJNZ R6,D2 ;

把这段程序写入单片机,试试看,是不是行了,这就是独立式按键去抖动的基本方法。不过这个程序在实际应用中并没有多大的意义,因为如果按键数量比较多的话,程序就会变得很长,为什么会这样呢?因为这里我们采用了直接寻址的方式,如果我们把键值放入一个表格中,再通过查表程序来判断到底是哪个按键被按下了,再去处理相应的程序就会很简单,想想看,该怎么做? 二.独立式键盘的编程方法

我们刚才的程序演示了按键的去抖动原理和基本方法,接下来让我们做一个按键使用的实验来验证一下,大家看附图的电路图,我们的实验板上有4 个按键分别接到了P3 口的P3.3,P3.4,P3.5,P3.6 引脚上,现在我们用P3.3,P3.4,P3.5 和P3.6 这四个按键来做一个实验。实验之前,先定义各个按键的功能:

A.P3.3 开始,按此键则灯开始流动(由左向右) B.P3.4 停止,按此键则停止流动,所有灯为灭C.P3.5 向左,按此键则灯反向流动(由右向左)D.P3.6 向右,按此键则灯正向流动(由左向右) 实验程序如下:

UpDown EQU 00H ; 上下行标志

StartEnd EQU 01H; 起动及停止标志

LampCode EQU 21H; 存放流动的数据代码

ORG 0000H ; AJMP MAIN ; ORG 30H ; MAIN:MOV SP,#5FH ;MOV P1,#0FFH ; CLR UpDown ;启动时处于向上的状态 CLR StartEnd ;启动时处于停止状态 MOV LampCode,#0FEH;单灯流动的代码 LOOP:ACALL KEY ;调用键盘程序 JNB F0,LNEXT ;如果无键按下,则继续 ACALL KEYPROC ;否则调用键盘处理程序 LNEXT:ACALL LAMP ;调用灯显示程序 KEYPROC:MOV A,B ;从B 寄存器中获取键值

JB ACC.2,KeyStart ;分析键的代码,某位被按下,则该位为”1”(在键盘程序中已取反) JB ACC.3,KeyOver ; JB ACC.4,KeyUp ; JB ACC.5,KeyDown ;

AJMP KEY_RET ; KeyStart:SETB StartEnd ;第一个键按下后的处理

AJMP KEY_RET ; KeyOver:CLR StartEnd;第二个键按下后的处理 AJMP KEY_RET ; KeyUp:SETB UpDown ;第三个键按下后的处理 AJMP KEY_RET ; KeyDown:CLR UpDown ;第四个键按下后的处理

K_RET:ORL P3,#01111000B ;此处循环等待键的释放 MOV A,P3 ; ORL A,#10000111B ; CPL A ; JZ K_RET1 ;直到读取的数据取反后为”0”说明键释放了,才从键盘处理程序返回AJMP K_RET ; K_RET1:RET ; D500mS: ;;

LAMP:JB StartEnd,LampStart ;如果MOV A,LAMPCODE ; RL A ;实际就是左移位 MOV LAMPCODE,A ; MOV P1,A ; LCALL D500mS ; AJMP LAMPRET ;

LAMPUP:MOV A,LAMPCODE ; RR A ;向下流动实际就是右移 MOV LAMPCODE,A ; MOV P1,A ; LCALL D500mS ; LAMPRET:RET ; END。

这段程序是我们到目前为止最长的程序,相信大多数指令大家应该能看懂,开始三条,UpDown EQU 00H;StartEnd EQU 01H;LampCode EQU 21H 给大家解释一下,EQU 叫做等值伪指令,它的功能是将一个常数或者特定的符号赋予规定的字符串。什么意思呢?举个例子:

ORG 200H;ABC EQU R6;MOV A,ABC;这里将ABC 等值为寄存器R6,也就是说,在指令中,R6 这个寄存器可以用字符串ABC 来代替, 为什么要这样写呢?当然是为了增加程序的可读性,不过有一点大家要记住了,这里使用的字符串不是标号,不能用“:”来做分隔符,比如这样写ABC:LJMP START ;如果加上“:”汇编程序会出错;当然,用EQU 指令除了可以赋值数据地址外,还可以赋值直接地址或者直接当作一个立即数来使用,例如:

ABC EQU 10H;DELAY EQU 05AFH;MOV A,ABC;LCALL DELAY;这里ABC 赋值以后被当作了直接地址使用;而DELAY 被赋值以后则成了一个16 位的地址,用作 子程序的入口。

如此一来,上面的三条指令也就很清楚了。这里有一个问题大家需注意?:使用EQU 伪指令必须先赋值,后使用。 既然讲到了赋值伪指令,我们再讲一下另外三条赋值伪指令. A.位地址定义伪指令BIT

它的功能是将一个可直接寻址的位地址赋予所规定的字符名称,例如: ABC BIT P1.0;把P1.0 赋值给ABC,即字符串ABC 就是直接寻址位P1.0。

这里注意?:与EQU 不同的是,这条指令只能对位地址赋值,而不能对寄存器或直接地址和立即数赋值。相反,EQU 指令却可以用来定义位地址变量,不过这时所赋的值应当是具体的位地址值。比如P1.0 要用90H 来代替;P2.0 要用AOH 来代替等等。 B.内部RAM 定义伪指令DATA

它的功能是给一个8 位的内部RAM 起一个名称,例如:ABC DATA 20H;把内部RAM 的20H 定义为ABC。 C.外部RAM 定义伪指令XDADT

给一个8 位的外部RAM 起一个名称,例如:

ABC XDATA 0ACH ;由于89C51 的内部RAM 寻址范围为00H-FFH ,所以这个地址必然大于FFH 。讲了赋值伪指令,再回到上面的按键程序,这段程序的功能虽然很简单,但它演示了一个键盘处理程序

的基本思路,程序本身很简单,也不很实用,实际工作中还会有好多要考虑的因素,比如主循环每次都调用了灯的循环程序,会造成按键反应“迟钝”;而如果一直按着键不放,则灯不会再流动,一直要到松开手为止,大家可以仔细考虑一下这些问题,想想有什么好的解决办法。

独立式键盘除了上面介绍的这种连接方法,我们还可以采用上图右边所示的连接方法,用一个“与非”门把四个输入端连接起来,当有任何一个按键按下时,都会使“与非”门输出为低电平,从而引起单片机的中断,它的好处是不用在主程序中不断地循环查询了,如果有键按下,单片机就去作相应的处理。

单片机经典教程22 矩阵式键盘的连接方法和工作原理

StartEnd=1,则启动 MOV P1,#0FFH ; AJMP LAMPRET ;否则关闭所有显示,返

回 LampStart:JB UpDown,LAMPUP ;如果UpDown=1,则向上流动

间 PUSH PSW ; SETB RS0 ; MOV R7,#200 ; D51:MOV R6,#250 ; D52:NOP NOP NOP NOP DJNZ R6,D52 ; DJNZ R7,D51 ; POP PSW ; RET

什么是矩阵式键盘?当键盘中按键数量较多时,为了减少I/O 口线的占用,通常将按键排列成矩阵形式,如下图所示。在矩阵式键盘中,每条水平线和垂直线在交叉处不直接连通,而是通过一个按键加以连接。这样做有什么好处呢?大家看下面的电路图,一个并行口可以构成4*4=16 个按键,比之直接将端口线用于键盘多出了一倍,而且线数越多,区别就越明显。比如再多加一条线就可以构成20 键的键盘,而直接用端口线则只能多出一个键(9 键)。由此可见,在需要的按键数量比较多时,采用矩阵法来连接键盘是非常合理的。 矩阵式结构的键盘显然比独立式键盘复杂一些,识别也要复杂一些,在上图中,列线通过电阻接电源,并将行线所接的单片机4 个I/O 口作为输出端,而列线所接的I/O 口则作为输入端。这样,当按键没有被按下时,所有的输出端都是高电平,代表无键按下,行线输出是低电平;一旦有键按下,则输入线就会被拉低,这样,通过读入输入线的状态就可得知是否有键按下了,具体的识别及编程方法如下所述:二.矩阵式键盘的按键识别方法

确定矩阵式键盘上任何一个键被按下通常采用“行扫描法”或者“行反转法”。行扫描法又称为逐行(或列)扫描查询法,它是一种最常用的多按键识别方法。因此我们就以“行扫描法”为例介绍矩阵式键盘的工作原理: 1.判断键盘中有无键按下

将全部行线X0-X3 置低电平,然后检测列线的状态,只要有一列的电平为低,则表示键盘中有键被按下,而且闭合的键位于低电平线与4 根行线相交叉的4 个按键之中;若所有列线均为高电平,则表示键盘中无键按下。 2. 判断闭合键所在的位置

在确认有键按下后,即可进入确定具体闭合键的过程。其方法是:依次将行线置为低电平(即在置某根行线为低电平时,其它线为高电平),当确定某根行线为低电平后,再逐行检测各列线的电平状态,若某列为低,则该列线与置为低电平的行线交叉处的按键就是闭合的按键。 下面给出一个具体的例子:

单片机的P1 口用作键盘I/O 口,键盘的列线接到P1 口的低4 位,键盘的行线接到P1 口的高4 位,也就是把列线P1.0-P1.3 分别接4 个上拉电阻到电源,把列线P1.0-P1.3 设置为输入线,行线P1.4-P1.7 设置为输出线,4 根行线和4 根列线形成16 个相交点,如上图所示。

检测当前是否有键被按下:检测的方法是P1.4-P1.7 输出全“0”,读取P1.0-P1.3 的状态,若P1.0-P1.3 为全“1”,则说明无键闭合;否则有键闭合。

去除键抖动:当检测到有键按下后,延时一段时间再做下一次的检测判断,若仍有键按下,应识别出是哪一个键闭合,方法是对键盘的行线进行扫描,P1.4-P1.7 按下述4 种组合依次输出:P1.7 1110;P1.6 1101;P1.5 1011;P1.4 0111 ;在每组行输出时读取P1.0-P1.3 ;若全为“1”,则表示为“0”这一行没有键闭合;否则就是有键闭合。由此得到闭合键的行值和列值,然后可采用计算法或查表法将闭合键的行值和列值转换成所定义的键值。为了保证按键每闭合一次CPU 仅作一次处理,必须去除键释放时的抖动。举个实例: 三.矩阵式键盘的实验程序 ORG 0030H ;

SCAN:MOV P1,#0FH ; MOV A,P1 ; ANL A,#0FH ; CJNE A,#0FH,NEXT1 ; SJMP NEXT3 ; NEXT1:ACALL D20Ms ; CJNE A,#0FH,KCODE ; MOV A,R1 ; SETB C ; RLC A ; JC NEXT2 ; NEXT3:MOV R0,#00H ;

RET ; KCODE:MOV B,#0FBH ; NEXT4:RRC A ; INC B ; JC NEXT4 ; MOV A,R1 ;

CJNE A,#0FH,NEXT6; MOV R0,#0FFH ; RET ; END。

单片机经典教程23 单片机的键盘接口与编程

如果说键盘构成的是单片机的输入系统,那么数码管就是单片机的输出系统,和学会使用键盘接口一样,学会数码管的接口与编程对单片

机的开发同样有着十分重要的意义。这一课我们就来讲解数码管的接口与编程方法:一.LED 数码显示器的连接与显示方法 在单片机系统中,通常用LED 数码显示器来显示各种数字或符号,由于它具有显示清晰、亮度高、使用电压低、寿命长的特点,因此使用非常广泛。那么数码管是如何工作的呢?我们学习数字电路时讲过,可能大家都忘得差不多了,这里我再从头给大家讲一遍,您可不要觉

得我烦哦!

还记得我们小时候玩过的“火柴棒游戏”吗,几根火柴棒组合起来,可以拼成各种各样的图形,LED 显示器实际上就是利用这个原理做成

的。

八段LED 显示器由8 个发光二极管组成。其中7 个长条形的发光管排列成一个“日”字形,另一个圆点形的发光管在显示器的右下角作为显示小数点用,它能显示各种数字及部份英文字母。LED 显示器有两种不同的连接形式:一种是8 个发光二极管的正极连在一起,称之为共阳极LED 显示器;另一种是8 个发光二极管的负极连在一起,称之为共阴极LED 显示器。共阳和共阴结构的LED 显示器各笔划段名的安排位置是相同的,当二极管导通时,相应的笔划段就发亮,由发亮的笔划段组合而显示出各种字符。8 个笔划段h(在许多书中用dp 来表示,其实是一个意思)gfedcba 对应于一个字节(8 位)的D7 D6 D5 D4 D3 D2 D1 D0, 于是用8 位二进制码就可以表示欲显示字符的字形代码。例如,对于共阴LED 显示器,当公共阴极接地(零电平),阳极hgfedcba 各段为01110011 时,显示器就显示“P”字符,即“P”字符的字形码是73H ;而如果是共阳极LED 显示器,公共阳极接高电平,显示“P”字符的字形代码应为10001100(8CH),也就是

与73H 的各位相反。这里必须注意?的是:很多产品为了方便接线,常常不按照规则的方法去对应字段与位的关系,这时字形码就必须根据接线来自行设计了。后面我们会给出一个例子让大家参考,那么数码管和单片机的接口又是如何连接的呢?请继续往下看:二.LED 数

码管的静态显示方法

在单片机的应用系统中,数码管显示器的显示常采用两种方法:静态显示和动态扫描显示。所谓静态显示,就是把多个LED 显示器的每一段与一个独立的并行口连接起来,而公共端则根据数码管的种类连接到“VCC ”或“GND ”端,这种连接方式的每一个显示器都要占用一个单独的具有锁存功能的I/O 端口,用于笔划段字形代码,单片机只需把要显示的字形代码发送到接口电路,就不用再管它了,直到要显示新的数据时,再发送新的字形码。因此,使用这种方法当显示位数较多时单片机中I/O 口的开销很大,需要提供的I/O 接口电路也较

复杂,但它具有编程简单,显示稳定,CPU 的效率较高的优点。

MCS-51 单片机串行口方式称为移位寄存器方式,外接6 片74LS164 作为6 位LED 显示器的静态显示接口,我们把单片机的RXD 作为数据输出线,TXD 作为移位时钟脉冲。74LS164 为TTL 单向8 位移位寄存器,可实现串行输入,并行输出。其中A、B(1、2 脚)为串行数据输入端,2 个引脚按逻辑“与”运算规律输入信号,只有一个输入信号时可并接。T(8 脚)为时钟输入端,可连接到串行口的TXD 端。每一个时钟信号的上升沿加到T 端时,移位寄存器移一位,8 个时钟脉冲过后,8 位二进制数全部移入74LS164 中;R(9 脚)为复位端,当R=0 时,移位寄存器各位复“0”,只有当R=1 时,时钟脉冲才起作用。Q1??Q8(3-6 脚和10-13 脚)为并行输出端,分别接到LED 显

示器的hgfedcba 各段对应的引脚上。

关于74LS164 还可以作如下的介绍:所谓时钟脉冲端,其实就是需要高、低、高、低的脉冲,不管这个脉冲是怎么来的,比如,我们用根电线,一端接T,一端用手拿着,分别接高电平、低电平,那也是给出时钟脉冲,在74LS164 获得时钟脉冲的瞬间(再讲清楚点,是在脉冲的前沿),如果数据输入端(1,2 脚)是高电平,则就会有一个“1”进入到74LS164 的内部;如果数据输入端是低电平,则就会有一个“0”进入其内部。在给出了8 个脉冲后,最先进入74LS164 的第一个数据到达了最高位,然后再来一个脉冲会有什么情况发生呢?第

一个脉冲就会从最高位移出,就象车站排队买票,栏杆就那么长,要从后面进去一个人,就必须要从前面走出去一个人才行。 搞清了这一点,让我们再来看电路图(电路图在后面的附录中),6 片74LS164 首尾相串,而时钟端则连接在一起,这样,当第一次输入8 个脉冲时,从单片机RXD 端输出的数据就进入到了第一片74LS164 中了;而当第二次8 个脉冲到来后,这个数据就进入到了第二片74LS164 ,新的数据则进入了第一片74LS164 中??这样,当第六次8 个脉冲完成后,首次送出的数据被送到了最左面的74LS164 中,其他的数据则依次出现在第一、二、三、四、五片74LS164 中。这里有个问题,在第一次8 个脉冲到来时,除了第一片74LS164 中接收数据外,其他各片在干吗呢?它们也在接收数据,因为它们的时钟端都是被接在一起的,可是数据还没有送到其他各片呢,它们在接收什么数据呢?其实所谓数据不过是一种说法而已,它实际上就是电平的高或低。当第一次8 个脉冲到来时,第一片74LS164 固然是从单片机接收数据了,而其它各片也接到前一片的Q8 上,而Q8 是一根电线,在数字电路中它只可能有两种状态:低电平或高电平,也就是“0”和“1”。所以它的下一片74LS164 也相当于是在接收数据,只是接收的全部是“0”或“1”而已。这个问题放在这儿来讲,可能有的朋友不屑一顾,而有的朋友可能还是不清楚,这实际上涉及到数的本质的问题,如果不懂,请并回到前面的内容再仔细的复习一遍,或找一些数字电路方面的书籍看一下。理解了74LS164 的工作原理,再来看这个问题就会变得简单多了,这里就算给大家留个习题吧,希望大家务

必把74LS164 的工作原理搞清楚,搞懂了这一点,您的级别就超过初级了,变成中级了。

接下来让我们做一个实验,做这个实验也需要一套附件,这套附件由于大家只用一次,所以我没有把它卖给大家,各位一样可以到我这儿

来借,同样免费使用,不过还是那句老话,请各位多多爱惜哦,不要把它搞坏了。 我们把附件的两根线连接到实验板的P3 口扩展插座和实验仪的电源接口上,先看实验要求: 输入:把要显示的数分别放在显示缓冲区60H-65H 共6 个单元中,并且分别对应各个数码管LED1-LED6。 输出:将预置在显示缓冲区中的6 个数转换成相应的显示字形码,然后输出到显示器中显示出来。

START:MOV SP,#6FH;

MOV 65H,#0 ; MOV 64H,#1 ; MOV 63H,#2 ; MOV 62H,#3 ; MOV 61H,#4 ; MOV 60H,#5 ; LCALL DISP ; SJMP $ ;

DISP:MOV SCON,#00H ;初始化串行口方式0 MOV R1,#06H ;显示6 位数 MOV R0,#65H ;60H-65H 为显示缓冲区 MOV DPTR,#SETTAB ;字形表的入口地址 LOOP:MOV A,@R0 ;取最高位的待显示数据 MOVC A,@A+DPTR ;查表获取字形码 MOV SBUF,A ;送串口显示 DELAY:JNB TI,DELAY ;等待发送完毕 CLR TI ;清发送标志 DEC R0 ;指针下移一位,准备取下一个待显示数 DJNZ R1,LOOP ;直到6 个数据全显示完 RE

T ;

SETTAB: ;字形表,前面有介绍,后面我们会介绍字形表的制作 DB 03H,9FH,27H,0DH,99H,49H,41H,1FH,01H,09H,0FFH;

END。

如果按图示数码管排列,则以上程序将显示543210。

不过我们的实验板是做不了这个实验的,为什么,我们稍候再讲,先让我们来讲解一下字形表的 制作问题,先就上述标准的图形来做,写出数据位和字形的对应关系并列一个表如下(共阳接法,也就

是输出为“0”时笔段亮)。

怎么样,不算复杂吧,就是这样列个表格,根据要求(“0”亮“1”灭)写出相应位的“0”和“1”就可以了。接着练习一下,写出显示

A-F 的字形码。

不过这是按照上面的标准接线排列的,在实际的程序设计中,有时为了接线方便常常会把接线顺序打乱(我们的实验板就是这样的),那么此时的字形表又该如何做呢?其实也很简单,一样地列表,以我们的实验板为例,同样显示9876543210,共阴极接法(注意:和共阳极

接法的区别)。接线如下:

P2.0 P2.1 P2.2 P2.3 P2.4 P2.5 P2.6 P2.7 对应g f a b h c d e,则字形码如下所示:

(0)11101110/EEH;(1)00101000/28H;(2)11001101/ CDH;(3)01101101/6DH;(4)00101011/ 2BH;(5)01100111/67H;(6)11100111/E7H;(7)00101100/2CH;(8)11101111/EFH;(9)00101111/ 2FH;继续练习,写出显示此时的A-F 的字形码。下面提个问

题:如果是共阳极的接法,字形码又该是怎么样的呢?不用我再说了吧,如果学到现在连这个还不明白,那真的是惨了! 现在让我们来继续上面的实验,把543210 的字形码放入上面的查表程序中(即DB:。 。)后面,如果要显示012345,我们的程序又该

如何修改呢?自己想一下。

本来这里讲的是显示器的静态接口问题,到此应当可以算结束了,但我还是想接着上面讲到的数的本质问题谈一点。单片机中有一些术语、名词本来是帮助我们理解事物的,但有时我们会被这些术语的相关语义所迷惑,以致不能进一步认清他们的本质,由此陷入困惑的境界。为什么会这样呢?因为我们往往只会从词义的本身去理解它,而不是从词义的概念和来历去了解它,所以只有深入地了解74LS164 的工作

特性,才能真正理解何谓串行的数据。如果您还不明白,我们可以课后再交

单片机经典教程24 LED 数码显示器的动态显示方法

由于静态显示占用的I/O 口线较多,CPU 的开销很大,所以为了节省单片机的I/O 口线,常采用动态扫描方式来作为LED 数码管的接口电路。在实际的工程应用中,它是使用最为广泛的一种显示方式,其接口电路是把所有显示器的8 个笔划段h-a 同名端连在一起,而每一个显示器的公共极COM 端与各自独立的I/O 口连接。当CPU 向字段输出口送出字形码时,所有显示器接收到相同的字形码,但究竟是那个显示器亮,则取决于COM 端,而这一端是由I/O 口控制的,所以我们就可以自行决定何时显示哪一位了。而所谓动态扫描就是指我们采用分时的方法,一位一位地轮流控制各个显示器的COM 端,使各个显示器每隔一段时间点亮一次。

在轮流点亮的扫描过程中,每位显示器的点亮时间是极为短暂的(约1ms 左右),由于人的视觉暂留现象及发光二极管的余辉效应,尽管实际上各位显示器并非同时点亮,但只要扫描的速度足够快,给人的印象就是一组稳定的显示数据,不会有闪烁感。

在我们实验板的电路图中,我们把89C51 的P2 口作为位选端(即同名端ABCDEFGH )并联起来,而把它们的片选端分别与P3.5 和P3.6 连接(图中为了增加P3.5 和P3.6 的驱动能力采用了一个三极管)这样由P3.5 和P3.6 控制对应数码管的亮或灭,只要给P2.0 送入不同的字形码,就能显示不同的数了。下面的这个程序,就是用我们实验板上的两个数码管来显示“0”和“1”,电路的连接方法请看后面的实验板电路图。

FIRST EQU P3.6 ;第一位数码管的位控制 SECOND EQU P3.5 ;第二位数码管的位控制 DISPBUFF EQU 5AH ;显示缓冲区为5AH ORG 0000H ; AJMP START ; ORG 30H ;

START:MOV SP,#5FH ;设置堆栈 MOV P0,#0FFH ; MOV P1,#0FFH ;

MOV P2,#0FFH ;初始化所有显示器,LED 灭 MOV DISPBUFF,#0 ;第一位显示0 MOV DISPBUFF+1,#1 ;第二位显示1 LOOP:LCALL DISP ;调用显示程序 AJMP LOOP ;主程序到此结束 DISP:PUSH ACC ;ACC 入栈 PUSH PSW ;PSW 入栈

MOV A,DISPBUFF ;取第一个待显示数 MOV DPTR,#DISPTAB ;字形表首地址 MOVC A,@A+DPTR ;取数字0 的字形码 MOV P2,A ;将字形码送P2 口

SETB FIRST ;打开第一位显示器位选端 LCALL DELAY ;延时1 毫秒

CLR FIRST ;关闭第一位显示器(开始准备第二位的数据)

从上面的例子中可以看出,动态扫描显示必须由CPU 不断地调用显示程序,才能保证持续不断的显示。上面的这个程序虽然可以实现数字的显示,但不太实用,为什么呢?因为这里做了两个数字的显示工作,没有做其他的事情,而在实际的产品中,不可能只显示两个数字,当然肯定还要做其他的事情。这样在两次调用显示程序之间的时间间隔就不能确定了,如果时间间隔比较长,就会使显示不连续,也就是说,在实际的应用中是很难保证所有工作都能在很短的时间内完成的,况且这个显示程序也有点“浪费”,每个数码管显示都要占用1 个毫秒的时间,这在很多场合是不允许的,怎么解决这个问题呢?我们可以借助于定时器,定时时间一到,就产生中断,点亮一个数码管,然后马上返回,这个数码管就会一直亮到下一次定时时间到,而不用再调用延时程序了,这段时间可以留给主程序干其他的事情,直到下一次定时时间到,显示下一个数码管,这样就不会有浪费了。具体这么做,我们就不讲了,大家可以结合以前学过的知识自己编一段程序,就算是这一课的习题吧。

从上面的程序中我们可以看出,与静态扫描相比,动态扫描的程序稍微有点复杂。但这是值得的,因为它可以大大节省单片机的I/O 口线资源,所以在实际的工程应用中几乎都采用动态扫描的方法来进行数码管的显示,我们的这个程序也具有一定的通用性,只要改变端口的值及计数器的值就可以显示更多的位数了。

不过正如我前面所说的那样,单片机的程序设计在实际应用中还会考虑很多的其他问题,所以这一课的内容同样只是给大家以后的学习打一个基础。倒是下面的LED 数码管的选择原则和驱动方法大家有必要了解一下。二.LED 数码管的选择和驱动

LED 数码管是单片机人机界面输出中用的最多也是最简单的显示方式,由于单片机口线的驱动能力是有限的,所以如何来选择和驱动LED 数码管是单片机初学者的基本功,下面就来给大家介绍一下。

前面我们已经讲过,LED 数码管有两种连接方法:一种是共阳接法;一种是共阴接法。在单片机的应用中,对于共阳接法,我们一般把它叫做“接电源”方法,即把COM 端接“VCC”,通过控制COM 端引脚电平的高低来达到选通的目的;而对于共阴接法,则通常叫做“接地”方法,即把COM 端接“GND”。

由于受单片机口线驱动能力的限制,采用直接驱动的方法,只能连接小规格的LED 数码管,目前市场上有一种高亮度的数码管,每段工作电流约为1-2mA ,这样当LED 全亮时,工作电流在10-20mA 左

右,是普通数码管的1/10 ,正好能用单片机的口线直接驱动,因此在条件允许的情况下,应尽量采用这种LED 数码管作为显示器件。 当然如果想用更高亮度或更大尺寸的数码管来作为显示器件,比如户外的电子钟,大型广告牌等等,就必须采用适当的扩展电路来实现与单片机的接口,常用的接口元件可以是三极管、集成电路和专用芯片,

三极管的规格可以根据数码管所需的驱动电流大小进行选择,电流比较小的可以用9013,8550 等小功率晶体管,电流比较大的则可以用BU208 等大功率三极管;而当显示器的位数较多时,一般采用集成电路来作接口,此类集成电路有2003 、7406 、75452 等,它们的功能其实就是由多路晶体管组成的达林顿电路具体电路请大家自己找一些相关的资料查一下;另外还可以使用一种叫译码/驱动器的芯片,这种芯片能将二-十进制码(BCD 码)译成七段码(a-g )以驱动数码管,采用这种芯片的最大好处是编程简单,并且能提高CPU 的运行效率,如CD4511 或74LS47 等就是此类芯片(不过,它们的驱动能力也是有限的,具体数据请参考有关的使用手册)。

近几年来,国内外厂商还开发了许多基于串行总线(SPI )方式的LED 接口芯片,这些芯片采用SPI 总线方式与单片机进行串行通讯,具用占用单片机口线少,程序易于实现的特点,比如美信的MAX7219 、力源的PS7219 等,有些芯片还集成了键盘控制器,可以实现键盘和显示的双重功能,如zlg7289 等。关于这方面的内容请您自己找一些相关的资料来看一下。

其实,除了数码管显示外,在实际的工程应用中,单片机还有很多的显示方法,其中比较常用的就是液晶显示器。液晶显示器是一种低压低功耗的显示器件,它的工作电压一般只要几伏,工作电流仅有几个微安,是任何数码管显示器件所无法比拟的。除此之外,液晶显示器的最大优点就是可以显示文字、图形和曲线,与传统的数码管显示器相比,显示界面有了质的提高,采用点阵式的液晶显示器配合大规模集成电路能够显示大量的信息,目前已经广泛使用在各类中高档仪器仪表及家用电器中,比如数字万用表,手机,数码相机等等。 单片机C语言教程 第一课 建立你的第一个Keil C51项目

随着单片机技术的不断发展,以单片机C语言为主流的高级语言也不断被更多的单片机爱好者和工程师所喜爱。使用C51肯定要使用到编译器,以便把写好的C程序编译为机器码,这样单片机才能执行编写好的程序。KEIL uVISION2 是众多单片机应用开发软件中优秀的软件之一,它支持众多不一样公司的MCS51架构的芯片,它集编辑,编译,仿真等于一体,同时还支持,PLM,汇编和C语言的程序设计,它的界面和常用的微软 VC++的界面相似,界面友好,易学易用,在调试程序,软件仿真方面也有很强大的功能。本站提供的单片机c语言教程都是基于keilc51的。

下面结合8051介绍单片机C语言的优越性:

·无须懂得单片机的具体硬件,也能够编出符合硬件实际的专业水平的程序; ·不懂得单片机的指令集,也能够编写完美的单片机程序; ·不同函数的数据实行覆盖,有效利用片上有限的RAM空间;

·提供auto、static、const等存储类型和专门针对8051单片机的data、idata、pdata、xdata、code等存储类型,自动为变量合理地分配地址;

·C语言提供复杂的数据类型(数组、结构、联合、枚举、指针等),极大地增强了程序处理能力和灵活性; ·提供small、compact、large等编译模式,以适应片上存储器的大小;

·中断服务程序的现场保护和恢复,中断向量表的填写,是直接与单片机相关的,都由C编译器代办;

·程序具有坚固性:数据被破坏是导致程序运行异常的重要因素。C语言对数据进行了许多专业性的处理,避免了运行中间非异步的破坏

·提供常用的标准函数库,以供用户直接使用;

·有严格的句法检查,错误很少,可容易地在高级语言的水平上迅速地被排掉;

·可方便地接受多种实用程序的服务:如片上资源的初始化有专门的实用程序自动生成;再如,有实时多任务操作系统可调度多道任务,简化用户编程,提高运行的安全性等等。

·头文件中定义宏、说明复杂数据类型和函数原型,有利于程序的移植和支持单片机的系列化产品的开发;

以上简单介绍了 KEILC51 软件,要使用KEILC51软件,必需先要安装它,这也是学习单片机编程语言所要求的第一步――建立学习环境。 安装好后,您是不是想建立自己的第一个单片机C语言程序项目呢?下面就让我们一起来建立一个小程序吧,请根据教程一步步的来,你绝对可以在短时间内熟悉c51的。

首先当然是运行KEIL软件,接着按下面的步骤建立您的第一个项目:

(1)点击 Project 菜单,选择弹出的下拉式菜单中的 New Project,如图 1-2。接着弹 出一个标准 Windows 文件对话窗口,如图 1-3。在“文件名”中输入您的第一个 C 程序项 目名称,这里我们用“test”。“保存”后的文件扩展名为 uv2,这是 KEIL uVision2 项目文件扩展名,以后能直接点击此文件以打开先前做的项目。

图 1-2 New Project 菜单

图 1-3 文件窗口

(2)选择所要的单片机,这里选择常用的 Ateml 公司的 AT89c51。而且本单片机c语言教程里的大部分程序都是基于此芯片的,此时屏幕如图 1-4 所示。AT89c51 有什么功能、特点呢?看图中右边有简单的介绍。完成上面步骤后,就可 以进行程序的编写了。 (3)首先在项目中创建新的程序文件或加入旧程序文件。如果您没有现成的程序,那 么就要新建一个程序文件。在 KEIL 中有一些程序的 Demo,在这里我们还是以一个 C 程序 为例介绍如何新建一个 C 程序和如何加到您的第一个项目中吧。点击图 1-5 中 1 的新建文 件的快捷按钮,在 2 中出现一个新的文字编辑窗口,这个操作也能通过菜单 File-New 或 快捷键 Ctrl+N 来实现。好了,现在能编写程序了。下面是经典的一段程序,呵,如果您看过别的程序书也许也有类似的程序: #include #include void main(void) {

SCON = 0x50; //串口方式1,允许接收 TMOD = 0x20; //定时器1定时方式2

TCON = 0x40; //设定时器1开始计数 TH1 = 0xE8; //11.0592MHz 1200波特率 TL1 = 0xE8; TI = 1;

TR1 = 1; //启动定时器

while(1)

printf (Hello World!\\n);; //显示Hello World }

图 1-4 选取芯片

图 1-5 新建程序文件

这段程序的功能是不断从串行口输出“Hello World!”字符,先不管程序的语法和意思吧,先 看看如何把它加入到项目中和如何编译试运行。

(4)点击图 1-5 中的 3 保存新建的程序,也能用菜单 File-Save 或快捷键 Ctrl+S 进行保存。因是新文件所以保存时会弹出类似图 1-3 的文件操作窗口,把第一个程序命名

为 test1.c,保存在项目所在的目录中,这个时候您会发现程序单词有了不一样的颜色,说明 KEIL的C语言语法检查生效了。如图 1-6 鼠标在屏幕左边的 Source Group1 文件夹图标上右击弹出 菜单,在这里能做在项目中增加减少文件等操作。选“Add File to Group ‘Source Group 1’” 弹出文件窗口,选择刚刚保存的文件,按 ADD 按钮,关闭文件窗,程序文件已加到项目中了。这个时候在 Source Group1 文件夹图标左边出现了一个小+号说明,文件组中有了文件,点击它能展开查看。

图 1-6 把文件加入到项目文件组中

(5)C程序文件已被加到了项目中了,下面就剩下编译运行了。这个项目只是用做学 习新建程序项目和编译运行仿真的基本方法,所以使用软件默认的编译设置,它不会生成用 于芯片烧写的 HEX 文件。先来看图 1-7 吧,图中 1、2、3 都是编译按钮,不一样是 1 是用 于编译单个文件。2 是编译链接当前项目,如果先前编译过一次之后文件没有做动编辑改动, 这个时候再点击是不会再次重新编译的。3 是重新编译,每点击一次均会再次编译链接一次,不 管程序是否有改动。在 3 右边的是停止编译按钮,只有点击了前三个中的任一个,停止按钮 才会生效。5 是菜单中的它们。在 4 中能看到编译的错误信息和使用的系统资源情况等, 以后我们要查错就靠它了。6 是有一个小放大镜的按钮,这就是开启\\关闭调试模式的按钮, 它也存在于菜单 Debug-Start\\Stop Debug Session,快捷键为 Ctrl+F5。

图 1-7 编译程序

(6)进入调试模式,软件窗口样式大致如图 1-8 所示。图中 1 为运行,当程序处于停止 状态时才有效,2 为停止,程序处于运行状态时才有效。3 是复位,模拟芯片的复位,程序 回到最开头处执行。按 4 能打开 5 中的串行调试窗口,这个窗口能看到从 51 芯片的串 行口输入输出的字符,这里的第一个项目也正是在这里看运行结果。这些在菜单中也有。首 先按 4 打开串行调试窗口,再按运行键,这个时候就能看到串行调试窗口中不断的打印“Hello World!”。最后要停止程序运行回到文件编辑模式中,就要先按停止按钮再按开启\\关闭调试 模式按钮。然后就能进行关闭 KEIL 等相关操作了。

本文来源:https://www.bwwdw.com/article/09cg.html

Top