一
话说人间事物的学习,不外两类:一类入门易,精通难比如矿石机,再比如练书法谁都会写两下,可写好却不是一朝一夕的事儿。另一类则入门难精通易,比如超外差收音机,学会需要各种放大、振荡、反馈、混频、中频、统调等等,但是一旦你学会,做出一个高质量的收音机,确也不是难事。单片机属于后者,所以无论如何,你得用些时间,学习一些新名词并理解,甚至还得花一些小钱。但是,一旦你掌握,其乐趣也像收音机一样,很有吸引力的。
单片机不同于收音机,收音机的任务最终就是听好声音一个,单片机可以做很多事情,要你来安排它的工作。安排它就先的认识它,熟悉它。大家自己查看一下单片机的外形,不妨将它看成是一个8条腿的小螃蟹,这个8条腿的小螃蟹就是我们的第一顿饭,只要把它吃下去,以后的大餐就好办了。
第1、8条腿接电源 +5V和地线。头两条腿是螃蟹钳子,好吃的很。现在已经剩下6条腿了。
第2、3条腿使用时外接一个叫作晶振的东西,我们接一个4MHz的晶振(这还不知道?还是看一下:单片机之路(www.mcuway.com)里面对晶振的介绍吧)。
第4条腿是复位脚,是一个信号输入脚。单片机正常运行时接高电平。当有一个低电平脉冲输入到这个脚时单片机就复位。所谓复位就是单片机内部所有的工作部件统统回到规定的状态,程序也复位到头一句上开始逐条运行。例如,你设计的一个报警锁定的LED红灯亮后,当需要解除报警时,用一个按钮给这个脚瞬时接地一下,相当于给它一个夫脉冲,系统就复位了,led灯就熄灭了,程序从头开始。
以上5个脚,几乎所有单片机都有,包括世界上最复杂的,和世界比较简单的单片机-PIC12CE519
轮到底几条腿啦?奥,是第5条腿,这条叫单片机的I/O脚。就是输入输出脚。你可通过程序动态地控制它作为输入或输出,作为输出时可以程序控制它的输出电平为高1或低0。所以,他的工作状态有四种:
输入0,输入1,输出0,输出1。
剩下的两条腿和第5脚功能一个样。
二
在PIC系列单片机入门并不难(1)中,我们已经把8条腿消化掉了,其实我们要弄明白的也就3只腿,我们再简单一些,先整明白两条腿,即GP0,GP1.这两条腿低级一点的用法,可以控制继电器,LED灯,高级一些的用法可以进行I2C总线,RS232总线的通信,作为扩展输入可以模拟出来A/D转换器(6--7bit),可以测量一个电阻的粗略值。作为输出也可以直接推动扬声器奏出音乐。这是后话暂且不提。
现在要控制使用这两只腿了,这不得不首先要讲一下软件,要想讲明白软件又不得不涉及到单片机的内部结构。可能有朋友说啦,可别提这软件和结构了,以前俺就是让它们打败的,现在听到这个心里就打鼓。嘿嘿,不要紧,果真如你所说,那你就不妨跟着我再失败一次,反正多一次失败又不纳税吗,嘿嘿。不过你也要有思想准备,彻底弄明白是个渐进的过程。
要说起这程序和单片机内部结构,还真是老大难,不过蟹黄蟹肉可都在里面。我现在要是给你说PIC单片机是哈佛结构的,51系列是冯-诺伊曼结构的话,恐怕你要立马走人了。所以我得用点心思不让你溜号。
好在PIC系列的制造商(microchip 微芯公司)理解我等苦衷,全部只有35条指令,而且有一些指令我们一般很少使用,常用的也就十几句,用的时候查手册,无需记忆。就算我们两天学习一句,也就两三个月时间,总比到老了还怕它们强啊。废话少说先看下面的两个例语:
my_name006: movlw 02h '常数2进入w
movwf GPIO 'W的数进入寄存器GPIO
这就是我们编的程序里的两个句子,也叫源程序。有以下特点:
每行只能写一句话;
每句话由四部分组成:
标号: 操作指令 操作数 '程序注释
下面我结合例子把这四部分解释一下。
第一部分 my_name006: 叫做标号,它是由字母或数字组成,由冒号结束。标号可有可无,比如第二句就没有标号。
第二部分movlw 叫做操作指令。它是必须有的,不能省略。PIC 系列的单片机共有 35 条指令。
第三部分02h 叫做操作数。有的指令没有操作数或者操作数是默认的,也不用写。
第四部分是程序注释,必须以单引号开头,主要作用是提醒和备忘。注释也是可有可无。
第二个例句中,省略了标号,当然注释也可以省略。他的指令是movwf, 操作数是GPIO。操作数不一定是数字,也可能是一个由字母组成的字符串
知道了语句格式以后,我们下面就学习一些常用语句。我们先把这两个例句弄清楚。
这两句话的作用是把 2 这个常数写入到 GPIO 这个寄存器里。
单片机里有一些部件需要我们使用和操作,都是通过读写寄存器来实现的。每个部件都对应有操控它的寄存器,例如我们要控制使用的管脚GP0,GP1 这两个管脚对应的寄存器就叫做GPIO。对GPIO寄存器读操作,实际等效察看管脚电平的高低;对GPIO寄存器相应的位写1操作,实际等效让管脚输出高电平。写0,输出低电平。
每个寄存器可以储存一个八位的二进制数。这八个位的每一位都有名称,从左向右的名称是:
左端第首位名称叫D7,
左端第二位名称叫D6,
左端第三位名称叫D5,
左端第四位名称叫D4,
左端第五位名称叫D3,
左端第六位名称叫D2,
左端第七位名称叫D1,
最后一位叫D0,
而每一个位对应一个管脚的电平,例如当GPIO寄存器的D0位等于1时表示管脚GP0的电平是高电平。D0位等于0时表示管脚GP0的电平是低电平。常数2的八位二进制表示是“00000010” 所以,GPIO寄存器存放的8位2进制数的每个位的值以及管脚电平是:
D7对应于内部总线管脚的电平 D7=0 内部总线管脚输出低电平
D6对应于内部总线管脚的电平 D6=0 内部总线管脚输出低电平
D5对应于GP5 管脚的电平 D5=0 GP5 管脚输出低电平
D4对应于GP4 管脚的电平 D4=0 GP4 管脚输出低电平
D3对应于GP3 管脚的电平 D3=0 GP3 管脚输出低电平
D2对应于GP2 管脚的电平 D2=0 GP2 管脚输出低电平
D1对应于GP1 管脚的电平 D1=1 GP1 管脚输出低电平
D0对应于GP0 管脚的电平 D0=0 GP0 管脚输出低电平
D7、D6对应的内部时钟和数据总线我们现在暂且不要管它。以后本事大了再调教它们。在我们的例句中,向GPIO寄存器写入了2,常数2的八位二进制表示是“00000010”,如果此时GP0, Gp1等都已经被定义成输出的话,那么GP1输出高电平(接LED灯亮),GP0输出低电平(所接LED灯熄) 。
截止到现在,你已经学会如何控制管脚的电平高低了。尽管还有一些疑问,比如怎样定义管脚为输出脚(以后会说),我得说,如果事先GP1,GP0这两个管脚处于输入状态,这两个例句无效,因为在这种状态下它们是控制不了电平的。
无论如何,这一会儿,你就学会了两个指令,35条我看也没啥难的。
下面再加深一下对寄存器的认识:
要把一个常数存储到寄存器,或者说写到一个寄存器中,仅用一条指令是办不到的,必须通过一个特殊
的寄存器W,把数据倒过去. 这就应该使用到下面两个语句:
movlw 02H 指令的意思是把一个常数存入特殊寄存器W, 这个常数是3,后面的H是表示十六进制
movwf GPIO 指令的意思是把特殊寄存器W的数值存入寄存器,这个寄存器的名称是GPIO
这里涉及到两个概念,常数和寄存器:
常数好说,比如说十进制数35,26。但要注意,在单片机系统里我们一般不用十进制,而使用十六进制。有
关数制转换方面的知识,是计算机的基础,必须会熟练地在二进制、十六进制、十进制之间转换,此处不再
罗索。
寄存器也叫单片机的内存,一个寄存器可以存储的数值范围是0--255,用十六进制表示就是0--FFH.用二
进制表示就是00000000----11111111。“单片机之路”提醒您:以后要养成习惯用十六进制表示数。
那么,一个单片机里有多少个这样的寄存器哩,pic12ce512里面有1024个这样的寄存器可以供你使用,为
了使用方便生产商已经给它们编上了号码,第一号码是000H,往下依照次序为 001H,002H........3FFH.(怎
么样,开始用十六进制说事了吧,如果你不熟悉熟制转换赶紧补课还来得及。)
有了编号就像我们居住的房间有了房间号码,使用就方便的多了.房间号码在邮政行业叫地址,因此我们称这些号码叫做寄存器地址,或称地址数。例如:名称为GPIO的寄存器,它的地址或地址数是06H。所以我们的
两个例句完全等同于:
my_name006: movlw 02h '常数2进入w
movwf 06H 'W的数进入寄存器GPIO
有两个寄存器比较特殊,它们没有地址,一个名字叫做W,另一个叫做TRIS.所以他们两个在存储数据的时候比较快,一个指令就可以解决问题,例如:movlw 03H 一条指令就把常数3写入到W寄存器了。关于TRIS寄存器,我们以后用到它再说。除了他们两个以外的其他所有寄存器,在写入数据时一般都要用两条指令进行。
本文虽然只有两个指令,但主要目的是让大家接触一下指令,建立寄存器的概念以及他们同硬件部件的联系。增强学习的信心。能有这些体会,这一节就算过关了。
随着以后的深入,你会发现小小单片机里面是一个大世界,兴趣也由此而生。
三
在PIC系列单片机入门并不难(2)中讲的是两个指令如何控制管脚电平的高低。前提是所有管脚已经被定义成输出了(OUT)如果被定义成了输入,则上次的指令虽然也能运行,但运行后丝毫不能改变管脚电平高低,因为此时管脚是输入状态,电平取决于外部输入,指令无法改变。
在PIC单片机系列中,改变I/O口的输入输出依靠写入寄存器TRIS的值,相应位写0,表示对应管脚被定义成了输出,写1,就是输入。
现在假如预把GP1、GP2管脚定义成输出,其他脚全是输入。那就应该向TRIS寄存器写入二进制数 11111001,换算成十六进制就是F9H.
依照以前我们学到的知识,在PIC系列单片机里,本来应该用下列的语句来完成我们的设定:
movlw 0F9H '常数进W,以字母开头的常数前面必须加0
movwf TRISA '把W内的数复制到TRIS
实际上PIC系列的单片机也都是这么写的,后面加的A,表示第一个8位的口(有的单片机不仅一个口,还有好几个8位的I/O口如TRISB、TRISC、TRISD等等).
但是,记住了,PIC12系列的单片机必须改写成为:
movlw 0F9H '常数进W,以字母开头的常数前面必须加0
tris GPIO '把W内的数复制到TRIS,以后凡见到这个指令一律理解成 movwf TRISA
写法不同,意思是一样的.这样你就又学了一个指令TRIS,不过这个指令的实质还是你曾经学过的movwf 只是写法不同罢了.在PIC12系列里TRIS作为指令, 在其他系列(PIC16\17\18)里把TRIS作为普通寄存器看待.因为我们现在讲的就是PIC12CE519,所以我们暂时用tris GPIO 这个格式,等以后进入PIC16C877 我们再写成 movwf TRISA,至于理解按照后者进行.
如果我们要控制GP1、GP2管脚的输出电平, 其他管脚作为输入.并且让GP1输出低电平,GP2输出高电平.完整的程序如下:
movlw 0F9H '常数进W
tris GPIO '把W内的数复制到TRIS ,GP1 GP2为输出,其他为输入
'此行无命令,起到的作用是容易读懂程序
movlw 04H '常数4的二进制是 00000100 ,GP1=0 GP2=1
movwf GPIO 'W内的数进GPIO输出生效,原来定义成输入的脚的电平,不会受该句影响
四
前面已经学会了三条指令,至此,8位寄存器的概念概念一定要建立起来,程序通过写入寄存器不同的数据控制管脚作为输入使用还是输出使用,作为输出时是输出高电平还是低电平。这样操作的又一个特点就是每次写入数据,同时控制的往往不是一个管脚,而是好几个,最多一次可以控制8个管脚.在单片机里往往每8个脚叫做一个口,如口A、口B,用英文表示就是GPIO、PORTA、PORTB、PORTC 等.
更多的情况是:某个口内的某一个管脚需要改变电平,其他脚电平不变.例如我们仅需要GPIO口上的GP1这个管脚的电平拉高,其他管脚电平不发生变化.这时候位操作指令为我们提供了方便,假如我们事先已经把GP1管脚定义过输出了(方法见前面讲过的):
bcf GPIO,GP1 '注释:GPIO口上GP1管脚电平拉低,我们行话叫清除。
bSf GPIO,GP1 '注释:GPIO口上GP1管脚电平拉高,我们行话叫置位。
怎么样,这样控制某一个管脚的电平就方便多了,你的编程效率将会大大提高.
记住:PIC所有单片机所有寄存器都是可以位操作的,这在51的单片机上是不能完全实现的.不仅如此,PIC所有单片机所有管脚的单腿驱动输出电流可以高达25mA,所以如果你驱动一个5到10mA电流的LED发光二极管,根本不用加三极管,串个电阻直接挂在单片机上就得了,这在51的单片机上也是不能实现的,要加驱动三极管或驱动芯片.
怎么样?学PIC有好处吧.别急,好处还有。。。。。。
一不小心,已经学会 5 个指令了,还有30个,加油啊。
五
单片机的大部分指令,或者说单片机所做的大部分工作,多数在写入或读出寄存器。关于寄存器的初步概念我想我们已经建立起来了,它是一个能够存储8位二进制数据(最大255=0FFH)的单元,每个单元都有它的编号,我们叫做它的地址或地址编码.地址编码也是十六进制的.另外寄存器里的数据掉电就会丢失。
寄存器的英文是RAM.
PIC12CE519里面共有48个寄存器供我们操作使用,它们每一个都有固定的地址编码。地址编码并不是连续的号码,而是分成了两段:
第一段: 从00H 开始, 依次是01H, 02H, 03H ....0AH, 0BH......到1FH 结束. 计32个寄存器
第二段: 从30H 开始, 依次是31H, 32H, ......................到3FH 结束 计16个寄存器
这种地址不是连续编号,而是要跳过一段的做法, 对于我们新手来说很是不习惯.为了让我们容易入门, 我们暂时先不管第二段RAM, 只当它不存在,所有程序我们只涉及到第一段连续的RAM地址.等我们熟练的掌握好了RAM的使用,再涉及第二段地址的RAM, 那时,你就会理解单片机设计者把它们分成两段的苦心了.
为了规范,我们今后一律把RAM的分段, 叫做分页.第一地址段叫00页面,第二地址段叫01页面.例如:我们学过的I/O口电平控制寄存器GPIO,它的地址编码是06H,属于00页面.
所有这32+16=48个寄存器除了在地址上分成了两个页面以外,又把它们分成两类:
一类专用寄存器,一类通用寄存器.
所谓专用,就是这个寄存器的功能已经由系统分配好了.例如:地址为06H的名称就做GPIO寄存器的功能,它的每个位都对应到一个I/O脚的电平.另一类是通用寄存器,你可以理解成它的功能系统没有事先预定,而是由你在编程序的时候随机使用.
pic12ce519的专用寄存器有7个,位置在我们第00页面的最前面.这7个专用寄存器的地址编码是:00H,01H, 02H, ----06H,剩下的所有寄存器包括所有第01页面,全部都是通用寄存器.
例子:在两个通用寄存器09H、0AH内写入常数FCH
movlw 0FCH '常数进W
movwf 9H '复制W内的数到通用寄存器09H
movwf 0AH '复制W内的数到通用寄存器0AH,由于此时W内并没有改变,W不用再进常数.
从00H到06H都已经起好了名称,它们是专用寄存器,用处各有不同。以后我们会逐个介绍它们.剩下的都是通用寄存器或者叫普通寄存器(General Purpose Registers),意思是一般用途的寄存器。
内存图谱,不要求记下来,但是应该有个大体印象,用的时候会察看就可以了。等编程时间一长,就那么几个字节,自然就记住了。
所谓字节是衡量二进制数据长度的一个单位。一个寄存器刚好能记住一个字节的数据。如果你要存储的数据比较大超过了255,那就要占2个存储器甚至更多。描述的时候通常我们不说这个数值占了多少个寄存器
,而是说这个数据是几个字节的。
字节的英文是byte,一个二进制数的一位,叫比特,英文bit。1byte包含8bit。
六
下面我们学习一条新指令,叫做空操作指令.
nop '什么事情也不做,但执行这个指令也要消耗掉一点时间。它没有操作数。
'不要理解成程序停了,实际上程序仍在正常运行。执行一连串的空操作指令,单片机
'白耗费时间,什么活也不干,往往用于延时。
如果你需要一个很短时间的延时,可以采用一连串的空操作。注意每个nop也是占一行,例如:
movlw 0F9H '常数进W
tris GPIO '把W内的数复制到TRIS ,GP1 GP2为输出,其他为输入
bsf GPIO,GP1 '管脚GP1输出高电平点亮LED灯(如果你已经接上灯的话)
nop
nop
nop
nop
nop
... .
bcf GPIO,GP1 '管脚GP1输出低电平关闭LED灯
nop
nop
nop
nop
...
运行的效果是接在管脚GP1上的LED灯先亮一段时间,再熄灭一段时间的闪烁。
七
下面我们学习两个新指令incf和decf,它们都是对某一个寄存器进行增1或减1操作,例句中假如我们要操作的寄存器是09H。
movlw 02H '常数2进入W
movwf 09H '把w内的数2复制到09H 这个寄存器
'现在09H寄存器内存储的数是2
incf 09H '寄存器09H内存储的数增加1
'现在09H内存储的数变成3
decf 09H '寄存器09H内存储的数减掉1
'现在09H内存储的数变成2
movlw 0FFH '常数255进入W
movwf 09H '把w 内的数255复制到09H这个寄存器
'现在09H 寄存器内存储的数是255
incf 09H '寄存器09H内存储的数 增加1
'现在09H内存储的数变成0
decf 09H '寄存器09H内存储的数 减掉1
'现在09H内存储的数又变成255
如果你事先定义好了地址为09H的这个寄存器里存储的数字大小代表电视机节目频道的话,你会很喜欢这两个指令的。并且当节目频道到达最大值255或最小值0的时候无需担心,寄存器在0时减1会得255,255状态下增1会得0。
至于为什么会这样,学过环形计数器的人不会感到奇怪的。你要是没有学过计数器电路也不要紧,记住一个寄存器的最大存储数值是255=0FFH就可以了,加减法都会导致它“进位”。
当然控制音量时这个程序不能使用,因为它在0和255之间变化,音量忽大忽小怎们行。为解决这个问题,我们必须再学习两条指令incfsz和decfsz。
它们与上两个功能基本相同,不同的是:寄存器增1或减1操作以后,该指令会自动判定寄存器内的结果是否为零,如果不为零,继续正常执行该指令后面的语句.但如果结果为零的话,则程序会"跳一步".绕过紧挨着它下面的一条指令,继续执行更下面的语句,举例子说明。
假定我们操作的寄存器还是09H:
movlw 0FDH '常数253进入W
movwf 09H '把w 内的数253 复制到09H 这个寄存器
'现在09H 寄存器内存储的数是253
incfsz 09H '寄存器09H内存储的数 增加1,结果变成254结果不等于0,故程序继续执行下一指令
nop '该句得到执行(因为上一句寄存器09H的计算结果不等于0)
incf 09H '寄存器09H内存储的数 增加1,结果是255
incfsz 09H '寄存器09H内存储的数 增加1,结果变成0
'因为结果等于0,故程序要跳过下面的一句(不运行下面的一句).
incf 09H '由于上一句的存在并结果为0,该句得不到执行,被忽略
incf 09H '程序跳入这一句继续运行 寄存器09H内存储的数 增加1
nop '因此现在 09H寄存器存储的数是1
nop '继续运行
.
.
.
思考题:设计一段程序代码,当用户连续按下音量减小键后,判定音量寄存器09H的存储音量数值,
防止该寄存器的值从0 变成255,以免震惊到用户。
.
.
.
SMALL_SOUND: nop '标号可以任意写的,此前用户一旦按下音量减,就把程序引导到这一句上来
decfsz 09H '寄存器09H内存储的数 减1,如果结果为0 就跳一步
goto OK '如果上一句结果不为0,执行该句后,程序去了ok语句
movlw 01H '跳到这一步说明寄存器结果是0
movwf 09 '强行把 09H内的数值写成1,仍然是小音量,这样音量不会被因为 减小而变成255
OK: nop '继续运行
.
.
思考题:利用decfsz 指令设计一段延时代码,使得延时时间可以在10个机器周期到65535个机器周期之间,可以通过程序任意控制在这个例子中,设我们要控制的延时时间大约是24086个及其周期,用16进制表是就是 5E16 H. 如果用到通用寄存器,请使用 0AH, 0BH
yanshi: movlw 5EH ' 常数5E进W 标号是延时
movwf 0BH '0B寄存器数为5EH
movlw 16H '常数16进W
movwf 0AH '0A寄存器数为16H
jixu: decfsz 0AH '0A寄存器内的数减1,如果结果为0跳步
goto jixu '结果不为0,继续
decfsz 0BH '0B寄存器内的数减1,如果结果为0跳步
goto jixu '结果不为0,继续
nop '延时完毕
.
.
你现在可以只用这几个简单句子完成任意时间的延时程序了。
八
下面介绍单片机汇编语言里的一个概念“子程序”
先打个比方,如果你做一顿饭,要做汤,炒菜,炖鱼,汆丸子,奥,忘了还有炒小螃蟹,期间有一个动作在我看来不断的重复,这个动作就是放盐,放盐的过程描述是这样的:
放盐: 用一把小勺子深入盐罐
舀出氯化钠适量。
把小勺子里的氯化钠
均匀洒在锅里。
完毕
如果我们把做饭定义为主任务,那么放盐这个动作就叫做子任务。
这样定义的一个好处就是描述主任务的时候比较方便,当你用语言文字描述主任务的时候,无论哪一道菜,到了该加盐的时候不必细说用一把小勺子深入盐罐......,因为很多菜都有同样的这个过程,所以,你用 “放盐” 两个字就可以了。但是在你使用 放盐 这个词之前或者之后,你应该解释一下放盐这个词的具体过程是什么。
我们单片机的程序也是一样的,如果你设计一个电视机的自动搜索频道的程序,程序要求电视机每搜索成功一个频道,它面板上的发光二极管就眨一次眼睛,也就说,先熄灭一段时间然后再点亮。这样就会遇到很多这样的眨眼动作,为了简化主程序我们可以把眨眼这样一个过程定义为一段子程序,以后每次遇到需要眨眼的时候就调用一次子程序就可以了。
子程序的定义是这样的:
Zhayan: bcf GPIO,GP1 '管脚GP1输出低电平关闭LED灯,标号是必须有的,标号就是子程序的名字
nop
nop
nop
nop
nop
... .
bsf GPIO,GP1 '管脚GP1输出高电平点亮LED灯
nop
nop
nop
nop
...
return '这个命令表示子程序的结束 是必需的 否则这个子程序没有结束
这样,子程序就定义完了,如果想在程序的某个位置需要led灯熄灭以下(眨眼一次),只需在那个程序位置调用一下子程序就可以了。调用的方法是用 call 命令。
主程序:
.... '这些点点表示主程序里的语句
....
......
...... '这个位置搜所成功一个台 需要“眨眼”一次
call Zhayan
...... '继续搜索下一个台的命令行
......
......
......
...... '这个位置搜所成功一个台 需要“眨眼”一次
call Zhayan
...... '继续搜索下一个台的命令行
......
......
...... '这个位置搜所成功一个台 需要“眨眼”一次
call Zhayan
...... '继续搜索下一个台的命令行
......
疑问1 我在一个主程序里固然可以调用另一个子程序,而我在一个子程序里能不能调用另一个子程序?
答: 可以的,这叫子程序嵌套,甚至还可以在另一个子程序中再继续调用别的子程序。
疑问2 嗯,那继续往下调用下去,有限制么?
答:有,这叫允许嵌套的层数 每个品牌 型号的单片机允许的嵌套层数都是有规定的 例如pic16f74 允许8层
pic12e519允许两层,也就是说pic12e519的主程序里可以调用子程序,子程序里海可以再调用子程序,到此为止不要再往下调用了,否则程序报错或者超出你预计的结果。
疑问3 在同一层程序空间里,例如在我的某个子程序之中,调用另一个子程序的次数有限制么?
回答: 没有限制,只要你的程序寄存器装得下你的程序。
疑问4 我听说单片机在调用子程序以前,好像需要程序“堆栈”访问什么的,要进行一些程序计数器的保存保护,以保证子程序返回来得时候,程序能够正确回到原来位置和环境。是这样的么?
答: pic单片机不用管这些问题,它是硬件自动完成这些堆栈的事情,我们的指令里不用关心这些。尽管如此,中档pic单片机的例如 pic16等系列,它们的程序存储器地址是分页的,尽量调用本页的子程序,如果子程序不在本页,而是在另一个页面里存放,你还是要告诉单片机你的子程序所在的页面数据的,具体操作指令可以查相关指令说明。我们的pic12c519的程序存储器,没有分页,不用关心这事。
九
学习到这里,就已经初窥门庭了,下一步还有一个重要的关口-------中断
单片机的中断,概念并不难以理解。只是要真正理解运用编程处理一些实际中断的例子,却也不是很容易,甚至是单片机学习、入门的拦路虎。要想学会实际的中断处理编程,也还需要清楚一些程序存储器,程序结构,程序计数器,硬件堆栈,现场保护等这些个另杂碎概念。因此,我们在学习中断以前、以后和学习中断过程中,都有必要介绍回顾复习一些有关上述关键词的概念和知识,否则,尽管你学了中断,用起来可还是不能得心应手,以至于茫然。
还是用比喻的方法介绍一下中断的概念:
你的主程序任务是做一桌可口的饭菜,期间可能要多次调用子程序“撒盐”。尽管子程序下边还有更小的子任务,比如“计算食盐的量”等过程,尽管这些子过程很复杂,但他们的出场时间和顺序是可以预料的,是可以预先安排的。也就说你肯定知道在什么时候放盐。
有一类子程序,他的出场时间是不确定的,突然的,处理他们的时间刻不容缓,必须赶紧的。我们称这一类子程序为中断子程序。也就是我们所说的“中断”。
你正在做菜的过程中,隔壁邻居小孩突然敲门说他的二大爷在他房间里摔倒了,请你帮忙把二大爷扶起来。这是急迫的,必须处理的事务。
你肯定关掉炉子一溜烟跑出去帮忙,等回来以后再点着炉子继续做菜。
这个事件的特点就是发生的时间你无法预先知道,而这个任务必须得停下当前工作去处理,并且是刻不容缓。
从开始关炉子到回来点着炉子的这段时间里以及你的救人行为,就叫做“中断子程序”。
在中断子程序过程中,你关炉子的动作,叫做“中断现场保护”;点着炉子叫做“中断现场恢复”;中间走出去扶起隔壁二大爷到回来叫做“中断任务处理”;小孩子敲门就叫做“中断请求”。这就是中断的基本概念。
在单片机里,中断的例子也是很多的。我举一个你手里的手机的例子,你的GSM手机正工作在赋闲,屏幕上也就显示个时间日期中国电信什么的,表面看没有什么。其实它内部的cpu高速运行忙碌地工作在诸如联络无线网络,查询是否有短消息发来,计算当前信号强度,时间等任务。
你突然按下数字键“8”,此时内部cpu必须停下它正在干的工作来应付你,也就是清屏,显示你按下的数字8,然后再回到它原来的任务接着运行。(当然,这个例子不一恰当,现在有操作系统Windows-ce windows-mobile的手机的工作机制远没有如此的简单)