基于HCS12的嵌入式系统设计
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第3章 S12指令系统

3.1 概述

一台计算机必须有软件的支持才能发挥其运算和控制功能,而软件中最基础的部分就是计算机的指令系统,所有软件都必须翻译成计算机能直接识别和执行的代码,才能由计算机去执行。任何计算机所能识别的代码是二进制编码,它按照自己固定的规则对编码进行解释和执行,这种计算机能直接识别和执行的代码,即每个有效的编码组称为指令。一种计算机所能执行的全部指令的集合称为其指令系统,不同计算机有不同的指令系统。

计算机指令系统在很大程度上决定了它的运算能力、功能和使用是否方便灵活等特性,高效的指令系统不仅具有编程方便,运行速度快,而且占用内存少等优点。由指令码组成的程序称为指令程序或机器语言,一般比高级语言编写的程序效率高得多。当然,高级语言编写的程序经过编译最终产生的也是机器语言,交给计算机执行时都是机器码的形式,但这与直接用指令编写的指令程序含义是不同的。由于指令程序效率高,所以在实时控制系统中被广泛采用。单片机最主要用途之一就是用于实时控制,需要经常使用指令程序。指令系统对于用户十分重要,这就是介绍指令系统的原因。

指令本身是二进制代码,记忆很困难。为了便于记忆,可将指令用一组有一定含义的字符表示,一般都采用相关的英文单词缩写,这种字符称为指令助记符(Instruction Mnemonic Symbol)。利用指令助记符编写的程序称为汇编语言源程序,它经过编译器翻译,便可成为计算机可执行的目标代码。

指令系统与计算机有关,一条同样功能的指令,在不同计算机指令系统中,指令助记符可能不同,机器码则更不相同。虽然现在许多编译器支持用高级语言编写程序,但是与单片机硬件相关的部分,如寄存器的设置、初始化堆栈指针、开中断、关中断等操作,一般采用汇编语言编写,因为汇编语言才是唯一与硬件相关的语言。在一些I/O操作涉及时序的部分,特别是一些实时性要求高的中断服务程序,也需要使用汇编语言编写。

3.2 S12汇编指令的格式和符号说明

汇编程序每行表示一条汇编指令,以回车符结束。每条汇编指令最多由五部分组成,一般格式为

                  [标号] 操作码  [操作数1],[操作数2][;注释]

标号为该指令所在地址的符号表示,它与指令操作码之间用空格或“:”分开。当转移或调用该指令为目标地址时,需要使用标号。

操作码为汇编指令助记符(指令符号),编译后生成指令操作码部分,是汇编指令不可缺少的部分。

操作数部分表示汇编指令的操作对象,操作数与操作码之间用空格分开,操作数之间用“,”分开。操作数根据汇编指令操作要求不同,可以有一个或两个操作数,隐含寻址没有操作数部分。

注释部分与汇编指令之间用“;”隔开,方便汇编程序阅读理解。一般放在汇编指令后面,也可以单独占一行。

为了使用汇编语言进行编程,必须了解汇编指令系统中指令符号的含义、数据类型、表示规则和各种指令符号的定义。

3.2.1 操作码和操作数

一条汇编指令通常由两部分构成,即操作码(Opcode)和操作数(Operand)。操作码规定了一条指令完成什么操作,例如,是加法还是减法,是数据传送还是数据移位等。操作数则表示这条指令所完成操作的对象,即对谁进行操作。操作数可以是一个数,或一个数据所在的地址,即在指令中并未直接给出所操作的数据,而是指出数据存放的位置。指令执行时,再从指定的地址中取出操作数。操作码和操作数都是二进制代码,8位二进制数构成一个字节,故指令由指令字节组成。不同指令的字节数是不同的。

S12MCU采用8位操作码,每一种操作码确定一个特定的指令和相应的寻址模式,因此,不同的操作和寻址模式将对应有不同的操作码。为了给每条指令提供寻址范围,需要采用几个操作码。如果要把操作码的数量限制在能用8位二进制数表示的范围内,那么能够使用的操作码只有256个。为了增加操作码的数量,在操作码表中又加入了第二页,这些操作码称为次字节操作码。位于第二页的操作码之前均冠以前导字节(Pre Byte)$18,利用这种方式,可将操作码的数量增加到512个,但仍用8位二进制数来表示操作码,只不过用前导字符加以区分。为了提高寻址的灵活性,操作码后面可以跟一个后置字节(Post Byte)和1~2个扩展字节(Extension Byte)。后置字节提供包括变址寻址、传送、交换和循环的基址寄存器和偏移量等信息,扩展字节包含附加信息,例如地址、偏移量和立即数等。

3.2.2 数据类型

S12共支持8种数据类型:位数据(1位)、5位带符号整数、8位带/无符号整数、字节型组合BCD数(8位)、9位带符号整数、16位带/无符号整数、16位有效地址、32位带/无符号整数。其中,8位二进制数(字节)可以通过任何地址单元存取,16位二进制数(字)由两个连续的字节组成,高位字节在低位地址单元,低位字节在高位地址单元。指令或操作数在内存中是否对齐无严格要求,但除了内部RAM外,非规范字的存取比规范字的存取要多花费1个周期。

负数总是用二进制补码的形式表示的。5位和9位带符号整数仅仅在变址寻址方式下作为偏移量,16位有效地址在寻址方式计算时生成。32位整数用于扩展除法指令的被除数、扩展乘法指令的乘积以及扩展乘-累加指令的乘积-累加和。

3.2.3 数据表示方法

十进制整数可直接按书写习惯在程序中使用;程序中的十六进制数应在数字前面加“$”,例如,$32、$F0分别表示十六进制的32H、0F0H。这种表示方法的特点是,无论第一个数字是否为A~F,都不必再加前导0,同类型的数据无论数值大小,表示出来宽度一致、整齐。

指令中的立即数应在数据前加“#”,如#$32、#$F0、#$8321等,若没有“#”时一般表示地址。二进制数应在数据前加“%”。

3.2.4 寄存器和存储器表示法

A、B、D(a、b、d)表示累加器,X/x、Y/y表示变址寄存器,PC/pc/p表示程序指针,SP/sp/s表示堆栈指针,CCR/C表示程序状态字寄存器。

M(Memory Location)表示由指令有效地址指向的8位存储单元,R(Result)表示算术或逻辑运算结果,I(Intermediate Result)表示算术或逻辑运算的中间结果。

当上述寄存器和存储器符号加注下标“n”时,表示是它的第n位,例如An、Bn、Dn、Xn、Yn、SPn、Mn、Rn、In。若在16位寄存器符号加注下标H或L时,则表示其高位字节或低位字节,例如,XH、XL、YH、YL、PCH、PCL等。RTNH、RTNL则分别表示返回地址的高位字节和低位字节。

M:M+1表示16位存储器位置,由M和M+1相邻两个存储单元组成。M~M+3表示32位存储器位置,由M、M+1、M+2、M+3四个相邻的存储单元组成,用于扩展大于64 KB的存储器。PPAGE表示程序重叠页面编号,Page表示程序重叠页面。

符号“( )”表示内容,如M(x)、M(SP)分别是由变址寄存器X和堆栈指针SP所指向的存储单元,M(y+3)是由变址寄存器Y加3后指向的存储单元。(M:M+1)表示两个相邻存储单元的内容组成的1个字,(M)为高位字节,(M+1)为低位字节。

3.3 寻址方式

寻址就是寻找操作数地址的过程,操作数所在的地址称为有效地址(Effective Address)。在用高级语言编程时,编程者不必关心参与运算数据(操作数)的存放问题,也不必关心这些运算在哪里(哪个寄存器中)完成的。编程者只需关心语句的使用是否正确,结果是否正确。但在用汇编语言编程时,数据的存放、传送、运算都要通过指令来完成,编程者必须自始至终十分清楚这些操作数的存放位置,以及如何将它们传送至适当的寄存器中去运算。因此,如何从各个存放操作数的区域去寻找和提取操作数就变得十分重要。

所谓寻址方式(Addressing Mode),就是如何通过确定操作数所在的位置(地址),提取操作数的方法。寻址方式是汇编语言程序设计中最基本的内容之一,必须十分熟悉、牢固掌握。根据操作数的来源不同,S12共有6类寻址方式,即固有寻址、立即寻址、直接寻址、扩展寻址、相对寻址和变址寻址。

3.3.1 隐含/固有寻址

当使用固有寻址方式时,有效地址包含在操作码中,因此,也称为隐含寻址(Implied Addressing),又因为操作数总是处于MCU的寄存器之中,还可称为寄存器寻址(Register Addressing)。

隐含寻址的指令只有操作码没有操作数,或操作数隐含在操作码之中。这种寻址方式的操作数实际上就是寄存器的内容。因此,在指令助记符中会出现寄存器的名称,有时在助记符中甚至连寄存器的名称都不出现。例如

第1条指令是累加器A清零指令,因为操作码是$87,MCU知道目的寄存器是A。第2条指令是将累加器B清零,虽然操作类似,但对象不同,因此操作码不同。由此可见,由于有效地址是操作码所隐含的寄存器,因此MCU很容易确定要清零的寄存器。第3指令是将B寄存器内容取反后再送回,指令的意义明显,也不需要操作数。

第4条指令是乘法指令,虽然指令助记符并未指出源地址(Source Address)和目的地址(Destination Address),但由于指令的操作对象非常明确,就是将A和B中的内容相乘,结果送到D寄存器。显然在这条指令中,固有寻址既用于源操作数,又用于目的操作数。

第5条指令是执行减法,A中内容减去B中内容,结果送到A寄存器,操作对象仍然很明确,源操作数和目的操作数均为固有寻址。虽然在操作码列表中有2字节,但第一个字节是前导字符,第二个才是操作码,这是因为该指令采用的是操作码表中第二页中的操作码。与此类似的还有第6条指令,将A的内容传送到B中。

固有寻址的指令大多数为单字节指令,也有一部分为双字节(带前导字符),特点是无操作数或操作数在寄存器中。

3.3.2 立即寻址

若指令的操作数是一个8位或16位二进制数,则称为立即寻址。指令中出现的操作数称为立即数,它表示一个实实在在的数值。为了与直接地址相区别,立即数前必须加“#”号。如果在立即数前未加“#”,则指令就会被错误解译为直接寻址方式。编程时需要注意这一点。

当采用立即寻址方式时,有1字节或2字节的操作数紧跟在操作码之后。因此,有效地址为紧跟在操作码之后的操作数所在的地址,操作数的字节数总是与目的寄存器的长度相匹配。如果目的寄存器是16位,则立即数也是16位;如果目的寄存器为16位,而指令中给出的立即数只有8位,则汇编时自动默认立即数的高8位为$00。但若立即数长度大于目的寄存器的长度,则汇编出错。例如

第1条指令将立即数$0D赋给A寄存器,因MAXCNT用伪指令定义为十进制数13。第2条指令是给堆栈指针赋值,INITSP=$02FF。第3条指令是将$1234赋给X寄存器。在第4条指令中,Y寄存器为16位,指令隐含要求16位立即数,但指令中只给出8位立即数,则汇编器将产生1个16位的数值$0067,赋给Y。第5条指令是A中的内容与二进制数00001011($0B)相或,结果送回到A中。指令中允许用二进制数来表示立即数,数据前面要加%。

例如,设F00是内存单元地址名称,THERE为转移地址。

          BRSET    F00,  #$03,  THERE;

若(F00)·$03=0,即F00单元的第0、1位均为1,则转移到THERE。

本例中采用了多种寻址方式:扩展、立即、相对寻址。这里,扩展寻址方式用来访问F00,立即寻址方式用来访问屏蔽值$03,相对寻址方式用来判定在转移条件满足时转移的目的地址。尽管也采用了立即寻址和相对寻址方式,但BRSET仍列为扩展寻址指令。

立即寻址通常是最快的寻址方式之一,常用来给寄存器赋值,也常用于算术运算指令。但是,其操作数必须是常量,因为汇编程序在编译时需要知道这个数值。若需要使加载到寄存器中的是一个变量,就不能采用立即寻址方式了。

3.3.3 直接寻址

指令中直接给出操作数地址的寻址方式称为直接寻址。此时,指令的操作数部分就是操作数地址,但实际上是要用这个地址单元中的内容作为真正的操作数参与运算或指令的操作。

由于直接寻址的范围为$00~$FF,即用于访问地址范围在$0000~$00FF的操作数,因此,直接寻址有时也称为0页寻址或首页寻址。存储空间的这个区域传统上称为直接页(Direct Page)。既然这些地址总是从$00开始,因此指令中只需包含低位地址,从而节省程序空间并缩短执行时间。可以将经常访问的数据放在存储器这个区域,使程序得到优化,提高程序运行速度。在直接寻址方式中,操作数地址的低位字节包含在指令中,而地址的高位字节被认为是$00。例如

在第1条指令中,$55作为位于$0000~$00FF范围内的地址低位字节,地址的高位字节默认为$00。在指令执行过程中,MCU把指令中的$55与$00组合起来,形成地址$0055,并将该地址单元中的数据送入累加器A。

在第2条指令中,由于LDX指令要求一个16位数据,因此指令中的低位地址$20与默认的高位地址$00组合起来形成地址$0020,MCU将从地址$0020和$0021读取一个字的数据,送入寄存器X。指令执行后,从$0020读取的数据放在X寄存器的高8位,从$0021读取的数据放在X寄存器的低8位。

在S12单片机默认的内存分配中,$00~$FF这一段是I/O寄存器地址,在不改变寄存器地址空间分配时,对I/O寄存器的访问可以使用直接寻址方式,利用直接寻址能够节省一个字节的代码长度。

3.3.4 扩展寻址

指令中直接给出操作数16位地址的寻址方式,称为扩展寻址。此时,指令的操作数部分是16位地址。

扩展寻址和直接寻址都属于绝对寻址(Absolute Addressing)。当采用绝对寻址时,有效地址紧随操作码之后。扩展寻址与直接寻址的差别在于,作为有效地址的操作数长度不同。扩展寻址采用16位操作数,而直接寻址采用8位操作数。因此,扩展寻址是绝对寻址的一种通用形式。

在扩展寻址中,指令给出了16位有效地址,因此能够访问64 KB存储空间中的任何单元。例如

          STAA     $3050    ;(A)→$3050

注意,这里十六进制数字前什么也不加,表示的是一个存储单元的地址,若加了“#”号,则变成立即寻址了。由于存储指令没有立即寻址方式。因此,STAA #$3050是非法指令,程序汇编将出错。

扩展寻址与直接寻址的语法相同,指令执行所用的周期数也相同,为了使汇编程序运用指定的两种方式中的一种,可在操作数前添加强制字符(Forcing Character)。若在地址前加大于号“>”,则表示强制使用扩展寻址;若在地址前加小于号“<”,表示强制使用直接寻址。例如

            机器码          指令                操作:($0040)→A
            86 40          LDAA   $40         ;直接寻址
            B6 00 40        LDAA   $0040       ;使用扩展寻址
            B6 00 40        LDAA   >$0040      ;强制使用扩展寻址
            B6 00 40        LDAA   >$40        ;强制使用扩展寻址
            86 40          LDAA   <$0040      ;强制使用直接寻址

为了优化代码长度,通常当知道有效地址落入直接页$0000~$00FF范围时,可选择直接寻址方式。在实际编程时,有时无法确定操作数所在的位置,因此大量使用地址标号,以方便程序修改。但是,若代码中采用的是前向引用标号(Forward Referenced Label),则汇编程序在进行第一遍编译时,还不知道存放地址是什么。所谓前向引用标号是指该标号含义是在引用它的指令之后说明,即标号先引用后定义。例如

                              机器码       指令               操作
                                          ……
                      B6 00 20   LDAA      VAR1         ;(VAR1)→A
                                          ……
                                      VAR1  RMB  1     ;用伪指令RMB为变量VAR1保留一个字节

汇编程序在进行第一遍扫描时,首先遇到LDAA VAR1,然后才遇到后面的伪指令行,伪指令定义VAR1。 在这种情况下,汇编程序开始并不知道有效地址,所以它必须采用扩展寻址方式,汇编程序使用扩展寻址操作码$B6,同时保留2字节作为以后存放有效地址之用。于是,汇编程序继续对程序扫描,到达标号后才计算出其地址为$0020。当进行第二遍扫描时,汇编程序再次遇到LDAA VAR1行,将$0020值插入第一遍扫描时所保留的两个字节中。在这种情况下,汇编程序不得不使用扩展寻址方式,尽管例子中的地址是在直接页中。

有两种途径使得MCU利用直接寻址方式。第一种途径标号定义在先,使用在后。例如

              机器码       指令                   操作
              VAR1    RMB  1            ;定义变量VAR1
               ......
               96 20   LDAA  VAR1         ;(VAR1)→A
              ......

在第一遍扫描时,汇编程序先遇到RMB行,计算出VAR1的地址,然后才遇到LDAA VAR1指令,由于VAR1的地址已知,假设仍为$0020,则可采用直接寻址方式,操作码是$96。紧随其后的是有效地址的最低有效字节$20,作为单字节操作数。显然,这里不得不将程序代码的顺序改变,变量定义在指令之前。这违反了一般的程序组织结构,变量定义在前的做法,可能会引起潜在问题:若使用单一起点伪指令,则每当添加或删除一个变量定义时,都会改变程序的起始地址。

第二种途径就是使用强制直接寻址方式。例如

                    机器码        指令
                    96 20        LDAA   <VAR1
              ......
                                VAR1  RMB  1

若前方标号确实落在直接页中,则强制利用直接寻址当然没问题。但如果汇编程序计算出VAR1并非落入直接页中,则汇编程序编译时就会出错。

3.3.5 相对寻址

相对寻址只出现在相对转移指令(Relative Branch Instruction)中。在高级语言中常有go to或go sub之类的语句完成程序转移功能。在一般MCU指令系统中,也都专门设有转移指令。转移指令分为直接转移指令和相对转移指令。在相对转移指令中,寻址方式采用相对寻址,指令的操作数部分给出的是地址的相对偏移量。在S12指令系统中,地址偏移量用rel8、rel9和rel16表示。relx(x=8,9,16)是一个带符号数,可正也可负,用补码表示。

相对转移指令执行时是以当前的PC值再加上指令中规定的地址偏移量relx,构成操作数的实际地址。这里所说的当前PC值是指,相对转移指令下一条指令的地址。一般将相对转移指令所在的地址称为源地址(Source Address),转移后的地址称为目的地址(Destination Address),则

目的地址=源地址+转移指令字节数+ relx

通常地址偏移量不需要计算,程序中只要写出目的地址的标号,具体操作数由编译程序在汇编中自动计算。

条件短转移指令和条件长转移指令只能采用相对寻址方式,但位操作指令BRSET(若测试位置1则转移)和BRCLR(若测试位为0则转移)的转移形式则采用了多种寻址方式,包括相对寻址。

条件短转移指令由一个8位操作码和跟在操作码之后8位偏移量组成,条件长转移指令则由前导字节$18、一个8位操作码再加上2字节的偏移量构成。每种条件转移指令都先测试程序状态字寄存器中特定的状态位,如果该位处于指定状态,那么偏移量之后的下一个存储单元的地址与偏移量相加,形成有效地址,程序在该地址处继续执行。如果该位不是指定的状态,则程序从转移指令的下一条指令处继续执行。

8位、9位和16位偏移量均为带符号二进制补码值,支持在存储器中向上或向下转移。短转移偏移量的数值范围为$80~$7F(-128~+127)。循环控制指令支持9位偏移量,其数值范围为$100~$0FF(-256~+255)。长转移偏移量的数值范围为$8000~$7FFF (-32 768~32 767)。如果偏移量为0,则MCU立即执行紧接着转移指令的下一条指令。

由于偏移量是在转移指令的末尾,因此,采用负的偏移量值可以使PC指向操作码并开始循环。例如,BRA转移指令由两个字节组成,用偏移量$FE可以形成一个死循环。

            操作码          标号                 指令             操作
            20 FE          TRAP           BRA TRAP      ;PC+2→PC

程序利用转移指令返回到指令首地址,所以程序就在该行落入陷阱,称为“原地踏步”。这是一种终止程序的普通方法,广泛应用于程序调试中。对于条件长转移指令LBRA (Branch Always),利用地址偏移量$FFFC,可以达到同样的效果。

3.3.6 变址寻址

变址寻址是以某个寄存器的内容为基本地址,然后在这个基本地址上加上地址偏移量,形成操作数地址或存放操作数地址的地址,并将这个地址单元的内容作为指令的操作数。

变址寻址是S12的主要寻址方式,S12共有4类不同的变址寻址方式:常数偏移变址寻址、自动递增/递减变址寻址、累加器偏移变址寻址和间接变址寻址,这4类又可进一步分解成12种不同的变址寻址方式。

S12的变址寻址指令由一个操作码字节、一个后置(随)字节(Post Byte)和0~2个扩展字节(Extension Byte)组成。操作码表明了指令功能和变址寻址方式,后置字节(在指令代码表中用xb描述)提供了变址寻址方式的具体信息,例如所使用的变址寄存器、偏移量的符号、变址寄存器的自动递增/递减方式等。当偏移量大于5位二进制数时,需要采用扩展字节,用来表示偏移量的大小。

所有变址寻址方式都采用一个16位寄存器和附加信息来产生一个有效地址。在大多数情况下,有效地址由操作影响的存储单元值确定。在某些间接变址寻址方式下,有效地址给出的是指向由操作影响的存储单元值的位置。在变址寻址指令中,后置字节用来指定X、Y、SP、PC中哪一个用作基址寄存器,并进一步将有效地址的产生方法进行分类。只有一组特殊的指令需要将计算的有效地址加载到变址寄存器中作进一步计算,例如,LEAS将有效地址加载到SP中,LEAX和LEAY将有效地址分别加载到X、Y中。

1.常数偏移变址寻址

常数偏移变址寻址(Constant Offset Indexed Addressing)方式是将变址寄存器的内容加上一个常数形成操作数地址,有三种不同的偏移量:5位、9位和16位。偏移的大小取决于指令操作码后的后置字节的数值和扩展字节数。指令语法为

          operation   ,  r ;无偏移
          operation  0,  r ;无偏移
          operation  n,  r ;正常数偏移量
          operation  -n, r ;负常数偏移量

其中,r是变址寄存器(IX,IY,SP,PC),n/-n是带符号偏移量(5位、9位和16位)。

(1)5位常数偏移变址寻址IDX(5 bit Constant Offset Indexed Addressing)

偏移量为5位带符号数,偏移量范围为-16~+15,这5位偏移量隐含在后置字节中,有效地址为变址寄存器内容加上5位地址偏移量,指令执行后变址寄存器中内容不变。该指令为两个字节:操作码和后置字节,后置字节内容是:

其中,rr为变址寄存器,00为X,01为Y,10为SP,11为PC;nnnnn为5位偏移量。例如,若X= $1000,Y =$2000

          LDAA   0 ,X ;($1000)→A
          STAB   -8 ,Y ;(B)$1FF8,$2000-$8=$1FF8

(2)9位常数偏移变址寻址IDX1(9 Bit Constant Offset Indexed Addressing)

9位常数偏移变址寻址方式的指令增加一个扩展字节,用来表示偏移的大小,为3字节指令。9位带符号偏移量的地址偏移范围为-256~+255,偏移量的符号位在后置字节中,偏移量大小在扩展字节中,变址寄存器内容加上9位带符号常数偏移量作为操作数地址。指令执行后,变址寄存器中的内容不变。其后置字节的内容为

其中,rr为变址寄存器,00为X,01为Y,10为SP,11为PC;s为偏移的符号,1表示负值,0表示正值。例如

          X=$1000 , Y=$2000
          LDAA   $FF , X          ;($10FF)→A($1000+$FF=$10FF)
          LDAB   -20 , Y          ;($1FEC)→B($2000-$14=$1FFC)

指令执行后,X、Y寄存器的内容仍分别为$1000和$2000。

(3)16位常数偏移变址寻址IDX2(16-Bit Constant Offset Indexed Addressing)

16位常数偏移变址指令为4字节指令,包括一个字节操作码、一个后置字节和两个扩展字节。有效地址由变址寄存器内容加上两个偏移量扩展字节形成,可以访问64 KB地址空间中的任何单元。由于地址总线和偏移量都是16位的,因此,无论偏移量被看做带符号数还是无符号数都无关紧要,即$FFFF可以认为是+65 535或-1。指令执行后,变址寄存器的内容不变。其后置字节的内容为

其中,rr为变址寄存器,00为X,01为Y,10为SP,11为PC。例如

          机器码             指令                操作
    1     A6 04          LDAA   $04 ,X  ;($04+X)→A
    2     EC E8 10       LDD   $10 ,Y   ;($10+Y):($10+Y+1)→D
    3     A6 FA 10 00     LDAA  $1000,PC ;($1000+PC)→A

第1条指令采用5位常数偏移,后置字节是$04;第2条指令采用9位常数偏移,后置字节是$E8;第3条指令采用16位常数偏移量,后置字节是$FA。

这种带有不同位数偏移量的变址寻址方式,非常适合于数组、结构等的访问。

2.自动递增/递减变址寻址IDX

自动递增/递减变址寻址IDX(Autoincrement/Autodecrement Indexed Addressing)是指在循环程序中,使指针沿着数据对象的数组而行进,这是一种常用的方法。每次循环,指针必须以数据对象的大小为步距进行递增或递减。例如,若对象就是单字节,则每次循环时,指针必须递增1或递减1;若对象是双字节,则每次循环时,指针必须递增2或递减2。因此,自动递增与自动递减寻址方式可以很方便地达到这个目的。同时,这种方式在数据块传送中也特别有用。

在S12的变址寻址方式中,提供了4种方法自动改变基址寄存器的内容,并作为指令执行的一部分。变址寻址前或变址寻址后,变址寄存器中的数值可以加上或减去一个整数值,增加值的范围是0000~0111(+1~+8),减少值的范围是1111~1000(-1~-8)。加或减操作可以发生在变址寻址前,也可以发生在变址寻址后。因此,对应4种不同的指令:先递增、后递增、先递减、后递减。汇编指令语法为

          operation   n , +r      ;先递增
          operation   n , r+      ;后递增
          operation   n , -r       ;先递减
          operation   n , r-       ;后递减

其中,n =调节值,1≤n≤8,r为变址寄存器(IX,IY,SP)。

在自动递增和自动递减变址寻址方式中,变址寄存器中的内容即为操作数的地址。对于先递增或先递减的指令,变址寄存器内容在访问内存单元之前变化,相当于前面常数偏移变址寻址。而对于后递增或后递减指令,则是用变址寄存器中的初值访问内存单元,然后才改变变址寄存器中的内容。调整值与当前指令的操作数长度无关,因此,这些指令可以用来把变址调节与现行指令结合起来,而不是采用一个附加指令和增加执行时间。

例如,CPX指令是将X寄存器中的内容与指定地址处的16位数值进行比较,然后根据比较结果,对程序状态字寄存器的状态进行相应设置。如果采用扩展寻址,则指令需要3个字节,如果比较完成后需要将X的内容指向数据的下一个字,则还需要执行两条加1指令或1条加法指令。而如果采用后递增变址寻址方式,则只需要一条指令CPX 2, X+,指令仅仅两字节,不仅减少了程序代码长度,而且节省了执行时间。

自动递增和自动递减变址寻址方式的指令由一个字节操作码(有的指令在操作码前有一个前导字符$18)和一个后置字节组成,后置字节的编码为

其中,rr为变址寄存器,00为X,01为Y,10为SP。p表示在寻址前或寻址后调整,若p = 0,则先递增或先递减;若p = 1,则后递增或后递减。nnnn为4位调节值。0000=+1, 0001 = +2,…,0111=+8(用于递增调节);1000 = -8,1001 = -7,…,1111 = -1(用于递减调节)。

注意:① 调节值没有零值;② 不能用PC作为变址寄存器,当选择PC时无意义(无效)。

例如

          STAA   1, -SP    ; (SP)-1→SP,(A)→(SP),等效于PSHA
          STX   2, -SP    ; (SP)-2→SP,(XH:XL)→MSP:MSP+1,等效于PSHX
          LDX   2, SP     ; (MSP:MSP+1)→XH:XL,(SP)+2→SP,等效于PULX
          LDAA  1, SP+    ; (MSP)→A,(SP)+1→SP,等效于PULA

第1条指令堆栈指针先减1,然后将A的内容存储到SP所指向的地址单元中;第2条指令是堆栈指针先减2,然后将X中的16位数据存储到SP所指向的地址单元和它的高位地址单元中;第3条指令是将堆栈指针所指向的地址单元及其高位地址单元中的内容装载到X中;第4条指令是将SP所指向的地址单元中的内容装载到A中。

自动递增/自动递减变址寻址方式也可以用来对存储器中的一系列数据结构进行操作,例如

            机器码         指令                   操作
            18 02 33 71   MOVW  4,X+,2,Y+ ;(X:X+1)→Y:Y+1,X+4→X,Y+2→Y

操作码是2字节$18和$02,然后接两个后置字节。第一个后置字节用于源操作数寻址,另一个用于目的操作数寻址,源操作数和目的操作数都采用后递增变址寻址方式。第一个后置字节$33表示变址寄存器为X,后递增,递增值是4;第二个后置字节是$71,表示变址寄存器为Y,后递增,递增值是2。指令将X寄存器指向的宽度为4字节的列表中数据传给Y寄存器指向的宽度为2字节的列表中。这个例子说明了自动加/减变址寻址方式如何处理大于字节和字的数据结构,在程序循环中使用这样的指令,就可以从每个记录为一个字的列表中传送一个字的数据到每个表元素为4字节的另一个表格中。例如,将上面的指令改成

            MOVW  2,X+, 4,+Y

这条指令在从存储器读完数据之后,源指针更新。在访问存储器之前,目的指针更新。

由于这种寻址方式的指令执行后,变址寄存器中保留的是变化后的值。因此,用SP、X、Y分别作为变址寄存器时,若采用先递增/递减变址寻址方式,指令LEAS、LEAX、LEAY执行时,这种递增/递减操作改变了正在加载的变址寄存器的数值。若采用后递增/递减变址寻址方式时,对变址寄存器中的数值没有影响。反之,如果变址寄存器中的最终值要求是已经访问过的操作数,那么在先递增/递减方式中,这些操作数最后在变址寄存器中是可见的。而后递增/递减方式,这些操作数现在变址寄存器中已不可见了。

这种带有指针自动调整功能的变址寻址,对数组等连续数据区的操作十分方便。由于步距可在1~8之间变化,因此,适合字节、字、双字、4字变量类型。

3.累加器偏移变址寻址IDX(Accumulator Offset Indexed Addressing)

累加器偏移变址寻址方式是将变址寄存器中的内容与累加器中的无符号偏移量相加,构成有效地址。在这种寻址方式中,偏移量是可变的,取决于累加器中的内容。指令执行后,不改变变址寄存器中的内容。为了使用这种寻址方式,偏移量必须先置于累加器中,累加器可以是A、B、D。语法为

          operation  A , r  ; 偏移量在A中
          operation  B , r  ; 偏移量在B中
          operation  D , r  ; 偏移量在D中

其中,A、B、D为累加器(偏移寄存器);r为变址寄存器(X、Y、SP、PC)。

累加器偏移变址寻址指令由操作码加上一个后置字节组成,后置字节编码为

其中,rr为变址寄存器,00 = X,01 =Y,10 = SP,11 = PC。aa为偏移寄存器,00 = A, 01 = B,10 = D。例如

          LDAA  B , X

B寄存器中的偏移量与基址寄存器X中内容相加后形成有效地址,并将该地址单元中的内容装入A,B和X的内容都不变化。再如

            LDY  #SEG_TBL     ;SEG_TBL→Y
            LDAA  DispVar       ;(DispVar)→A
            LDAA  A,Y        ;(A+Y)→A
            STAA DISP_REG      ;A→DISP_REG

这段程序是在SEG_TBL表中查一个显示码,用变量DispVar作为偏移,然后将结果输出到显示缓冲区DISP_REG中。第1条指令将显示码所在地址赋给Y寄存器;第2条指令将偏移值装入A中;第3条指令将A中内容与Y中内容相加,形成操作数地址,并取出显示码装入A中,指令中的A既可作为偏移寄存器又可作为目的寄存器;第4条指令将A中显示码存储到显示缓冲区。

4.间接变址寻址(Indirect Indexed Addressing)

若操作数地址是以寄存器名称的形式间接给出,则称为寄存器间接寻址。

间接变址寻址是将变址寄存器中的内容与一个16位偏移量相加,形成一个地址,但该地址并不是操作数的地址,它所指向的地址单元中的内容才是操作数的有效地址,即变址寄存器中的数值与偏移量相加之和所指向的仍然是一个指针,而该指针才指向操作数。因此,间接变址寻址也称为间接指针寻址。这种变址寻址方式中的偏移量既可以是一个16位的常数偏移量,也可以是累加器中的内容。指令语法为

          operation   [D,r]    ;偏移量在累加器D中
          operation   [n,r]    ;n是16位常数偏移量

其中,D为含有偏移量的寄存器D;n为16位常数偏移量;r为变址寄存器(X、Y、SP、PC)。

间接变址寻址方式的指令操作码之后需要一个后置字节, 用来规定偏移量的来源和变址寄存器的名称。对于常数偏移量的方式,后置字节之后还需要跟两个扩展字节,用于存放16位偏移量常数。后置字节编码为

其中,rr为变址寄存器。00 = X,01 =Y,10 = SP,11 = PC。x为偏移类型。常数偏移= 0,累加器D偏移 = 1。

(1)16位常数偏移间接变址寻址[IDX2](16 Bit Constant Indirect Indexed Addressing)

这种寻址方式是将由指令提供的16位偏移量与基址变址寄存器(Base Indexing Register)的内容相加,形成一个地址(指针),指向一个存放操作数地址的单元。指令本身并不指向操作数的地址,而是形成一个指针,指向操作数所存放的地址。指令执行时,根据由基址和偏移量所形成的地址指针,首先到指定的地址单元中取得操作数地址,然后再根据操作数地址提取操作数。其有效地址为

          effective address =((two offset extension bytes)+(X、Y、SP or PC))

为了将这种寻址方式与16位常数偏移变址寻址方式加以区别,在指令助记符的操作数部分,用一个方括号括起来。

例如,设指令执行前X=$1000,($100A)=$20,($100B)=$00,($2000)=$3A, ($2001)=$1F。

比较以下两条指令的执行情况:

                LDAA   $0A,X   ;((X)+$0A)→A
                LDAA   [$0A,X]   ;(((X)+$0A))→A

第1条指令是16位常数偏移变址寻址方式IDX2,偏移量$0A与X中内容$1000相加,形成操作数地址$100A,指令执行后A=$20。

第2条指令是16位常数偏移间接变址寻址方式[IDX2],偏移量$0A与X中内容相加,形成地址指针$100A,先从$100A和$100B两个单元中取出操作数地址$2000,再从存储单元$2000处取得操作数$3A送到累加器A,指令执行后A = $3A。

间接变址寻址方式添加了额外层次,初看起来似乎有些混乱。对两类指令的差别和用法一定要区分清楚,如图3.1所示。

图3.1 变址寻址与间接变址寻址

例如,利用这种寻址方式读取一个转移表中的地址,并根据该地址转向相应的子程序。

          JMPTBL   EQU  $1000
          CMD10FF   EQU  2
           操作码             指令                  操作
          CE 10 00       LDX  #JMPTBL       ;JMPTBL→X
          15 E3 00 02     JSR  [CMD10FF,X]    ;((CMD10FF+X))→PC
          15 E7          JSR  [D,X]          ;((D+X))→PC

JSR为跳转到子程序指令,即子程序调用,指令首先调整堆栈指针,将返回地址压入堆栈,然后将子程序入口地址送入PC。

程序第1行是使X指向转移表,表中存放有各个命令程序的起始地址。该表是指针数组,其中指针指向各子程序入口地址(命令子程序);第2行中16位常数偏移CMD10FF用于指向所需的指针,该行需要4字节的机器码;第3行用累加器D存放相对于转移表首的可变偏移,但是该行只需两个字节的机器码,当然,这里没有包括将偏移值加载到B中所需的代码。这两行各有一个跟随字节($E2和$E7),符合跟随字节的说明。例中假设CMD10FF=$2,X=$1000,地址($1002:$1003)=$2000,地址$2000和$2001的内容是$8000,如图3.2所示。

图3.2 查表转移

首先,将X内容与常数偏移CMD10FF相加,和为$1002。地址$1002与$1003的内容合并到一起,形成有效地址$2000;再将$2000与$2001的内容合并到一起,形成操作数的有效地址,将其内容$8000装入程序计数器中,从而完成转移。

(2)累加器D间接变址寻址[D, IDX](Accumulator D Indexed Indirect Addressing)

这种寻址方式是将累加器D中的内容与基址寄存器的内容相加,形成一个地址指针,指向存放操作数地址的存储单元。指令本身并不指向操作数的地址,而是形成一个指针指向操作数所存放的地址,指令所形成的地址的内容才是最终操作数的有效地址。指令执行时,根据基址寄存器和偏移寄存器D中的内容形成一个地址,到该地址单元中找到操作数地址,再由操作数地址取得操作数。其有效地址为

          effective address = ((D)+(X,Y,SP or PC))

为了把这种寻址方式与累加器D偏移变址寻址方式区别开来,用一个方括号将指令助记符的操作数部分括起来,例如

          设 D=$0002
          JMP  [D,PC]
          GO1  DC.W    PLACE1
          GO2  DC.W    PLACE2
          GO3  DC.W    PLACE3

本例在计算Go To时采用了累加器D间接变址寻址方式。在GO1开始的值是跳转指令可能的目的地址,在执行跳转指令JMP [D,PC]时,PC指向地址GO1,累加器D中可能是$0000、$0002、$0004中的一个,由JMP指令前的程序决定。假定D=$0002,则JMP指令把D中内容与PC中内容相加,形成GO2的地址,然后MCU从GO2处读取到地址PLACE2,跳转到PLACE2。PLACE1~PLACE3的地址在程序汇编时是已知的,跳转的目的地址取决于程序执行过程中寄存器D中的值。

间接变址寻址理解起来较为复杂一些,但在编程时却是极为有用的一种寻址方式。

3.4 S12汇编指令系统

S12汇编指令系统汇总表见附录A,汇编指令系统表按字母顺序排列,供编写汇编程序时查阅,指令系统表从左到右分别按照指令助记符(指令形式)、指令操作说明(助记符含义)、寻址方式、机器码、指令周期/访问细节和程序状态字节等信息;指令系统汇总解释说明见附录B;S12汇编指令机器码汇总表见附录C;机器码组织解释说明见附录D。

按指令助记符分类,S12共有169种不同的汇编指令。考虑到不同类型的寻址方式,指令总数达594种。按照指令功能,可将指令分为以下几大类:数据传送指令、算术运算指令、逻辑运算指令、转移与子程序调用指令、中断指令、MCU控制指令、高级函数指令、模糊运算指令等。

3.4.1 数据传送指令

数据传送指令将数据从一处复制到另一处,包括立即加载、寄存器之间的传送与交换、内存传送到寄存器、寄存器传送到内存、内存之间的传送和堆栈操作指令等。

1.寄存器加载指令

寄存器加载指令用于将立即数或内存中的操作数传送给寄存器,共6条指令,如表3.1所示。每条指令支持8种源操作数寻址方式,派生出48条指令。

表3.1 寄存器加载指令

例如

          LDAA  #$5A   ;$5A→A,立即寻址IMM
          LDAB$4000    ;($4000)→B,扩展寻址EXT
          LDD  4,X     ;((X)+4):((X)+4+1)→A:B,5位常数偏移量变址寻址IDX
          LDX  A,PC    ;((PC)+(A)):((PC)+(A)+1)→XH:XL,累加器偏移变址寻址IDX
          LDS  [$1238,X];(($1238)+(X)):(($1238)+(X)+1)→SPH:SPL,16位常数偏移
                        量间接变址寻址[IDX2]
          LDY  2,SP+   ;(SP):(SP+1)→YH:YL,SP+2→SP,相当于PULY,自动递增变址寻址

2.有效地址加载指令

有效地址加载指令可以用来将操作数地址分别传送到寄存器SP、X和Y中,共3条指令,如表3.2所示。采用3种变址寻址,派生出9条指令。

指令可将SP、PC、X或Y的值加/减5位、9位或16位常数,或者加上A、B或D的内容,传送到寄存器SP、X和Y中。

表3.2 有效地址加载指令

注:EA表示有效地址(Effective Address)。

例如

          LEAS 4,SP    ;(SP)+4→SP,5位常数偏移量变址寻址IDX
          LEAS 5,X     ;(X)+5→SP,5位常数偏移量变址寻址IDX
          LEAS $0200,SP ;(SP)+$1000→SP,16位常数偏移量变址寻址IDX2,相当于SP的加法
                        运算,S12没有针对SP的加法指令。
          LEAX B,Y     ;(Y)+(B)→X,累加器偏移变址寻址IDX
          LEAX$1000,Y  ;(Y)+$1000→X,16位常数偏移量变址寻址IDX2
          LEAY D,Y     ;(Y)+(D)→Y,累加器偏移变址寻址IDX
          LEAY D,SP    ;(SP)+(D)→Y,累加器偏移变址寻址IDX

可以看出,该组指令既可以将一个寄存器的值按要求调整后送到另一个寄存器,也可以用来调整一个寄存器自身的值。

注意以下两条指令。

          LEAX 4,+X    ;X+4→X,(X)→X,自动递增变址寻址IDX
          LEAX 4,X+    ;(X)→X,由于指令中两种隐含的操作针对同一个寄存器,且加载的是操
                        作时的有效地址,因此X+4被忽略

3.寄存器存储指令

寄存器存储指令仅限于将寄存器内容送入内存单元,共6条指令,如表3.3所示。每条指令支持7种源操作数寻址方式,派生出42条指令。

表3.3 寄存器存储指令

例如

          STAA     $5A         ;(A)→$005A,直接寻址DIR
          STAB     $4000,X      ;(B)→(X)+$4000,16位偏移量变址寻址IDX2
          STD      -$1000,PC    ;A:B→(PC)-$1000:(PC)-$0FFF,16位偏移量变址寻址IDX2
          STX      $D000       ;XH:XL→$D000:$D001,扩展寻址EXT
          STS      [D,PC]       ;SPH:SPL→((PC)+(D)):((PC)+(D)+1),累加器D
                                间接变址寻址[D, IDX]
          STY      2,-SP        ;SP-2→SP,YH:YL→(SP):(SP+1),相当于PSHY,自动递减
                                变址寻址IDX

4.寄存器数据传送指令

将源寄存器的内容传送到目的寄存器,共9条指令,如表3.4所示,均为固有寻址方式。实际上该组指令只有3条,其余指令是为了兼容Freescale(前Motorola公司)68HC11而保留的伪指令,由汇编程序自动汇编成对应的TFR指令,如表3.4中阴影部分所示。

表3.4 寄存器数据传送指令

注:R1和R2分别是寄存器A、B、CCR、D、X、Y、SP、TMP2、TMP3之一,但TMP3只能作为源寄存器, TMP2只能作为目的寄存器。

源寄存器和目的寄存器可以在9个寄存器中任意选择,包括MCU内部的两个暂存器TMP2和TMP3,其中涉及TMP2和TMP3的指令是为了以前产品保留的指令,有些汇编程序可能视其为无效指令,但可以通过DC.B和DC.W在程序中直接嵌入这样的指令。

对于TFR指令,突出的特点是数据可以在16位与8位寄存器之间传送,规则是:

● 8位到8位或16位到16位,直接传送;

● 8位到16位,通过符号扩展成16位后传送,“TFR A,X”等同于“SEX A,X”;

● 16位到8位,舍弃高位,只传送低位。

需要说明的是,数据从8位寄存器传送到16位寄存器,8位寄存器的内容传送到16位寄存器的低8位,S12自动在目的寄存器的最高有效字节中执行一次符号扩展。符号扩展的原则是,如果8位寄存器的最高位是0,则16位寄存器的高8位为$00;如果8位寄存器的最高位是1,则16位寄存器的高8位为$FF。

例如:若A=$57,B=$81

          TFR   A,X    ;SEX:A→X,(X)=$0057
          TFR   B,Y    ;SEX:B→Y,(Y)=$FF81

如果传送的数据是带符号数,这样做没问题。但如果数据是无符号数,如地址或计数值,则发生符号扩展毫无疑义。因此,可以采用如下方法将无符号数从8位寄存器传送到16位寄存器。

(1)用寄存器传送指令:A→D

          TFR   A,D    ;A→D,符号→A
          CLRA         ;$00→A或CCR→X
          TFR   CCR,B  ;CCR→B
          CLRA         ;$00→A
          TFR   D,X        ;D→X

(2)用有效地址加载指令:B→X

          LDX  #0      ;$0000→X
          LEAX B,X     ;(B+X)→X

(3)用寄存器交换和传送指令:B→X

          EXG  B,X     ;$00:B←→X
          TFR   X,B    ;XL→B

(4)用堆栈操作指令:CCR→X

          PSHC         ;SP-1→SP,CCR→(SP)
          DES          ;SP-1→SP
          CLR    0,SP   ;$00→SP
          PULX         ;(SP:SP+1)→X,SP+2→SP

5.寄存器指令

源寄存器内容与目的寄存器内容进行交换共3条指令,如表3.5所示,均为固有寻址方式。实际上该组指令只有1条EXG指令,XGDX和XGDY指令是为了兼容68HC11而保留的伪指令,由汇编程序自动汇编成对应的EXG指令,如表3.5中阴影部分所示。

表3.5 寄存器数据交换指令

注:R1和R2分别是寄存器A、B、CCR、D、X、Y、SP、TMP2、TMP3之一,但TMP3只能作为第1操作数寄存器,TMP2只能作为第2操作数寄存器。

该指令可以在任意16位或8位寄存器之间交换数据,规则是:

● 8位←→8位或16位←→16位,直接交换;

● 8位←→16位,将8位寄存器内容高8位零扩展成16位后,传送到16位寄存器, 16位寄存器的低8位传送到8位寄存器;

● 16位←→8位,16位寄存器的低8位传送到8位寄存器,8位寄存器高8位补$00或$FF后,传送到16位寄存器,其中8位寄存器是A时,高8位补$00;8位寄存器是B或CCR时,高8位补$FF。

寄存器数据交换指令一览表和功能说明如表3.6和表3.7所示。

表3.6 寄存器数据交换指令一览表

表3.7 寄存器数据交换指令功能说明

注:*eb的逻辑编码总结于上表。列表示高位源数字,行表示低位目的数字,用十六进制表示。

由于A、B是D的两个组成部分,因此在A、B与D寄存器之间的数据交换不完全符合上述规则。例如,以下几对指令操作并不对应

          EXG  D,B     ;B→B,$FF→A
          EXG  B,D     ;$FF:B→D
          EXG  A,D     ;$00:A→D
          EXG  D,A     ;B↔A

6.内存数据移动指令

可以将源操作数(一个字或字节)送到目地地址,源操作数不变。这是S12仅有的两条不涉及内部寄存器而直接进行内存单元之间数据传送的指令,支持6种寻址方式,如表3.8所示,由此可派生出12条指令。

表3.8 内存数据移动指令

例如

          MOVB  #$0F,PORTT     ;立即数$0F→PORTT寄存器,IMM-EXT寻址方式
          MOVW  0,X,0,Y       ;(X:X+1)→Y:Y+1,IDX-IDX寻址方式
          MOVB  #$35,  9,Y      ;立即数$35→(Y)+9单元,IMM-IDX寻址方式
          MOVW  #S1234,$1100    ;立即数$1234→$1100:$1101连续两个单元,IMM-EXT寻址方式
          MOVB  $1000,$2000     ;($1000)→$2000,EXT-EXT寻址方式
          MOVB  $1000,  5,SP    ;($1000)→(SP)+5单元,EXT-IDX寻址方式
          MOVB  3,X,  2,Y      ;((X)+3)→(Y)+2单元,IDX-IDX寻址方式

7.堆栈操作指令

S12堆栈操作仅限于寄存器与堆栈之间的数据传送,使用堆栈指针进行间接寻址。堆栈操作分为进栈和出栈两种,操作循序正好相反,即指针先递减,后压栈;先弹出,指针后递增。指令共计12条,入栈和出栈各6条,均为固有寻址方式。指令如表3.9所示。

表3.9 堆栈操作指令

例3.1 从BLOCK单元开始有一个无符号数据块,其长度存于LEN单元,试编写程序,求出数据块中最大的数,并存入DATDMAX单元。

          LDX   #BLOCK         ;数据块首地址赋给X寄存器
          LDAB  LEN            ;LEN单元的数据块长度装入B寄存器
          LDAA  BLOCK         ;取数据块中第一个数装入A中
          DECB                 ;取出第一个数后,数据块长度减1
    DONE:MAXA$01,X+         ;max[(A),(M(X))]→A,(X)+1→X
          DECB                 ;(B)-1→B,计数器减1
          BNE   DONE           ;B≠0,转移,即z=0,继续;计数器不为零,循环
          STAA  DATDMAX       ;A→DATDMAX
    HERE:BRA   HERE           ;原地踏步

例3.2 试编写程序,查找在内部RAM的$1020~$1050单元中是否有$AA这一数据,若有这一数据,则将$1051单元置为$01;若未找到,则使$1051单元置0。

          LDX   #$1020          ;$1020→X
          LDAB  #$31            ;数据块长度装入B寄存器
          LDAA  #$AA           ;$AA→A
    DONE:CMPA  $01,X+         ;(A)=(M(X))?,(X)+1→X
          BNE   NEXT           ;结果不为零转移
          MOVB  #$01,$1051     ;结果为零,有$AA这一数据,$01→$1051
          BRA   HERE
    NEXT:DECB                 ;(B)-1→B,计数器减1
          BNE   DONE           ;B≠0,转移,即z=0,继续;计数器不为零,循环
          CLR  $1051            ;$00→$1051
    HERE: BRA   HERE           ;原地踏步

例3.3 试编写程序,查找在内部RAM的$1020~$1050单元内出现$AA的次数,并将查找的结果存入$1051单元。

          LDX   #$1020          ;$1020→X
          LDAB  #$31            ;数据块长度装入B寄存器
          LDAA  #$AA           ;$AA→A
          CLR   $1051           ;$00→$1051
    DONE:CMPA  $01,X+        ;(A)=(M(X))?,(X)+1→X
          BNE   NEXT           ;结果不为零转移
          INC   $1051            ;结果为零,有$AA这一数据,$01→$1051
    NEXT:DECB                 ;(B)-1→B,计数器减1
          BNE   DONE           ;B≠0,转移,即z=0,继续;计数器不为零,循环
          HERE: BRA   HERE    ;原地踏步

例3.4 阅读下列程序,要求:①说明程序的功能;②写出X、Y寄存器的最后结果。

          ORG   $E000
          LDX   #$C000              ;$C000→X,初始化源指针
          LDY   #$C500              ;$C500→X,初始化目的指针
          LDAB  #$10                ;$10→B,初始化计数器
    DOWH:LDAA   $01,X+           ;((X))→A,X+1→X,从源地址移动字节
          STAA   $01,Y+            ;A→(Y),Y+1→Y,存储到目的地址
          DECB                     ;(B)-1→B,计数器减1
          BNE   DOWN              ;B≠0,转移,即z=0,继续;计数器不为零,循环
    HERE: BRA   HERE               ;原地踏步

程序功能:把从地址$C000开始的16个字节数据传送到从$C500开始的地址单元中。程序执行后:X=$C010,Y=$C510。

注意:采用“MOVW 2,X+,2,Y+”指令一次可以传送一个字,且避免通过累加器A作为中间寄存器,使程序精简和节省程序运行时间,请读者自行考虑。

采用类似的方法,可使累加器A左移B中所指定的次数。

    ORG   $E000
    CMPB  #$00           ;判别B中是否为零
    BEQ   HERE          ;如果B=0,则不进入循环
DONE:LSLA         ;循环开始,算术左移。Acc.7→c,0→Acc.0
    DECB                 ;(B)-1→B,计数器减1
    BNE   DONE           ;B≠0,转移,即z=0,继续;计数器不为零,循环
HERE: BRA   HERE   ;原地踏步

3.4.2 算术运算指令

S12具有加、减、乘、除、比较、十进制调整等算术运算指令,支持商小于1的除法运算,并设置了符号扩展指令。除了加1和减1指令的操作结果可位于内存中以外,算术运算结果通常保存在寄存器中。

1.加法指令

S12加法运算分为两类,一类是两个寄存器内容的加法,另一类是寄存器内容与立即数或内存单元内容的加法,运算结果保存在寄存器中,所有加法指令都影响标志位。

两个寄存器内容的加法如表3.10所示,有3条指令,但ABX和ABY指令是为兼容68HC11而保留,由汇编程序自动汇编成对应的LEAX指令,如表3.10中阴影部分所示。

表3.10 加法指令[1]

寄存器内容与立即数或内存单元内容的加法,共有5条指令,支持8种寻址方式,如表3.11所示,由此派生出40条指令,其中2条为带进位的加法指令,3条为不带进位的加法指令。例如

          ADDA  #$53           ;(A)+$53→A
          ADCB  3,Y            ;(B)+((Y)+3)+C→B
          ADDD  $1000          ;(D)+($1000)→D

表3.11 加法指令[2]

例3.5 阅读下列程序,要求:①说明程序的功能;②写出程序执行后涉及的寄存器和内存单元的最后结果。程序运行前,($0800)=$30,($0801)=$8B,($0802)=$52, ($0803)=$96,($1000)=$47,($1001)=$28,($1002)=$D6,($1003)=$40。

          LDD   $0802              ;($0802:$0803)→D
          ADDD  $1002              ;(D)+($1002:$1003)→D
          STD    $2002              ;(D)→$2002:$2003
          LDD   $0800              ;($0800:$0801)→A:B
          ADCB  $1001              ;(B)+($1001)+c→B
          ADCA  $1000              ;(A)+($1000)+c→A
          STD    $2000              ;(D)→$2000:$2001
    LOOP:BRA   LOOP               ;原地踏步

程序功能:双字加法。

将内存单元$0800~$0803中的内容与内存单元$1000~$1003中的内容相加,结果存放在$2000~$2003中,高位字节在低地址单元。

注意:由于S12没有带进位字的加法指令(无ADCD指令),程序中高位字相加采用了单字节的带进位加法指令来实现。

程序执行后存放被加数和加数的地址单元内容不变。D=$77B4,($2000)=$77, ($2001)=$B4,($2002)=$28,($2003)= $D6。

例3.6 阅读下列程序,要求:①说明程序的功能;②写出X、Y寄存器的最后结果。

            ORG $8000
  ADD_8BT:LDX       #$0A07       ;$0A07→X,X指向被加数(8字节整数)的最低位
            LEAY      8,X        ;Y=(X)+$8=$0A07+$8=$0A0F,指向加数(8字节整数)的最
                                    低位
            LDAB      #8          ;$8→B,计数器(共8字节)
            CLC                    ;清除进位标志
  LOOP_EX:LDAA      ,X         ;取1个字节被加数
            ADCA      ,Y+        ;(A)+((Y))+c→A,加数连同上一字节运算后的进位一起加
                                    到A,;Y+1→Y指向下一字节
            STAA      ,X+        ;(A)→(X),结果保存到被加数的位置,Y+1→Y指向下一字节
                                      被加数
            DBNE  B   LOOP_EX    ;循环8次,不破坏进位位的状态
  LOOP:   BRA       LOOP        ;原地踏步

程序功能:将两个8字节数相加,结果保存在原被加数所在的位置。

程序执行后,X=$0A10,Y=$0A17。

2.减法指令

S12减法运算也分为两类,一类是两个寄存器内容的减法,另一类是寄存器内容与立即数或内存单元内容的减法,运算结果保存在寄存器中,所有减法指令都影响标志位。

两个寄存器内容的减法指令为SBA,计算A、B之差,结果存放在A中。寄存器内容与立即数或内存单元内容的减法,共有5条指令,支持8种寻址方式,如表3.12所示,由此派生出40条指令。其中2条为带借位的减法指令,3条为不带借位的减法指令。

表3.12 减法指令

例如

          SUBA  $53         ;(A)-($0053)→A
          SBCB  $1000,X     ;(B)+((X)+$1000)-C→B
          SUBD  #$1000      ;(D)-$1000→D

例3.7 阅读下列程序段,要求:①说明程序的功能。②写出程序执行后涉及的寄存器和内存单元的最后结果。程序运行前,($0800)=$87,($0801)=$D5,($0802)=$3C, ($0803)=$62,($1000)=$54,($1001)=$F8,($1002)=$76,($1003)=$39。

    LDD      $0802        ;($0802:$0803)→D
    SUBD     $1002        ;(D)-($1002:$1003)→D
    STD      $2002        ;(D)→$2002:$2003
    LDD      $0800        ;($0800:$0801)→A:B
    SBCB     $1001        ;(B)-($1001)-c→B
    SBCA     $1000        ;(A)-($1000)-c→A
    STD      $2000        ;(D)→$2000:$2001
LOOP: BRA      LOOP        ;原地踏步

程序功能:双字减法。

将内存单元$0800~$0803中的内容减去内存单元$1000~$1003中的内容,结果存放在$2000~$2003中,高位字节在低地址单元。

注意:由于S12没有带借位的字减法指令(无SBCD指令),程序中高位字的减法采用了单字节的带借位减法指令来实现。

程序执行后:存放被减数和减数的地址单元内容不变,D=$C629,($2000)=$32, ($2001)=$DC,($2002)=$C6,($2003)= $29。

3.加1指令和减1指令

S12分别具有一组用于指针调整或循环控制的加1指令和减1指令,每组有6条指令,操作对象是寄存器A、B、X、Y、SP或内存单元,如表3.13和表3.14所示,其中,寄存器A、B、X、Y加1或减1指令为固有寻址,对内存单元的操作则支持6种寻址方式。需要注意的是,INS和DES指令是为兼容68HC11而保留的,由汇编程序自动汇编成对应的LEAS指令,为变址寻址IDX方式,如表3.13和表3.14中阴影部分所示。

表3.13 加1指令

表3.14 减1指令

4.十进制调整指令

S12只有1条十进制调整指令DAA,根据标志H、C和A中数值的大小作BCD调整,调整规则是:

● 如果A中的低4位>9或标志H=1,则A=A+$06;

● 如果A中的高4位>9或标志C=1,则A=A+$60,同时重置标志C。

例3.8 阅读下列程序段,要求:①说明程序的功能;②写出程序执行后D寄存器的最后结果。

          LDD   #$7527  ;$7527→D
          ADDB  #$75   ;(B)+$75→B,B=$9C,c=0,H=0,低半字节大于10,需进行+6调整
          EXG   A,B   ;A←→B,将B内容交换到A,以便进行十进制调整
          DAA          ;低位字节十进制调整,结果A=$02,c=1
          EXG   A,B   ;A←→B,将低位字节调整结果放回到B,将高位字节换回到A
          ADDC  #$10   ;A+$10+c→A,结果A=$86,c=0,H=0,无需进行十进制调整
    LOOP:BRA   LOOP  ;原地踏步

程序功能:利用BCD运算求十进制数7 527与1 075的和,结果放在累加器D中。

程序执行后,D=$8602。

5.清零指令

清零指令用来向寄存器或内存单元中写入$00,功能上与加载立即数$00的指令相同,共有3条指令,如表3.15所示。寄存器清零只针对A和B,为固有寻址方式。内存单元清零指令支持6种寻址方式。

表3.15 清零指令

6.符号扩展指令

单字节无符号数通过高位补0可以扩展成任意宽度,而对于有符号数,由于采用补码表示,高位可能需要补0或补1,因此,S12设置了符号扩展指令SEX,为固有寻址方式。符号扩展过程是,将原数据符号位复制到已扩展数据的所有高位。指令要求源操作数是A、B或CCR中的内容,扩展后的结果存放在D、X、Y或SP中。指令格式为

          SEX  R1,R2            ;R1=A、B或CCR,R2=D、X、Y或SP,相当于TFR  R1,R2

例如

          LDAA    #$75         ;A=$75(=+117)
          SEX      A,X         ;X=$0075
          LDAB     #$83         ;A=$83(=-$7D=-125)
          SEX      B,SP        ;SP=$FF83

7.乘法指令

S12乘法全部在寄存器内完成,可以实现两个8位或16位的硬件乘法运算,共有3条指令,均为固有寻址方式,如3.16所示。

表3.16 乘法指令

MUL指令将A、B内容按无符号数相乘,结果存在寄存器D中,同时将结果的D7位(累加器B中的最高位)送到标志C中,作为按四舍五入原则舍弃低8位、保留高8位的依据,后跟一条ADCA #$00指令便可实现四舍五入。因此,这时的进位位C与加法时的含义完全不同。

EMUL和EMULS指令将D、Y内容分别按无符号数和有符号数相乘,结果存在寄存器Y:D中,同时将结果的D15位(累加器D的最高位)送到标志C中,按四舍五入的原则舍弃低16位、保留高16位的依据。利用EMUL和加法指令可以实现多字节无符号数的乘法运算。对于多字节有符号数的乘法,可以先进行其绝对值的乘法运算,然后再调整乘积的符号。

EMUL和EMULS指令的区别在于,EMULS指令的结果用补码表示。

例如,分别用无符号和有符号乘法指令计算$8000×$4000

          LDD  #$8000   ;32 768
          LDY  #$4000   ;16 384
          EMUL        ;Y=$2000,D=$0000,按无符号数运算32 768×16 384=536 870 912=$20000000
          LDD  #$8000   ;-32 768
          LDY  #$4000   ;+16 384
          EMUL        ;Y=$E000,D=$0000,按无符号数运算
                        ;-32 768×16 384=-536 870 912=$E0000000

8.除法指令

S12除法全部在寄存器内完成,可以实现16/16位或32/16位的硬件除法运算,共有5条指令,均为固有寻址方式,如表3.17所示。

表3.17 除法指令

EDIV和EDIVS为常规32/16除法指令,分别按无符号数和有符号数相除,被除数在Y:D中,除数在X中,商在Y中,余数在D中。多字节无符号数的除法运算可以通过移位相减的办法实现。对于多字节有符号数的除法,可以先进行其绝对值的除法运算,然后再调整商和余数的符号。

FDIV、IDIV、IDIVS 3条是16/16除法指令,被除数在D中,除数在X中,商在X中,余数在D中。

FDIV是小数除法指令,实际上是求真分数D/X的值,其中D、X都是无符号整数或小数点位置相同的小数,因此,商是一个小于1的二进制小数。运算过程相当于先将D内容乘以216,然后进行32位除以16位(X内容)的除法运算,商在X中,余数在D中,然后商(X内容)再除以216,相当于将小数点移到最高位的左边。保存商,然后再恢复除数X,可以让余数(D)再次除以X,如此循环反复,可以使商的位数向小数点后面不断延伸,直到满足要求为止。

IDIV和IDIVS分别是无符号和有符号整数16/16除法指令,区别在于IDIVS指令的商和余数用补码表示。

例3.9 阅读下列程序,要求:①说明程序的功能;②写出D、X寄存器和相关内存单元的最后结果。已知程序执行前($A000)=$80,($A001)=$03,($A002)=$00,($A003)=$53。

          LDD  $A000   ;($A000)→D,装载分子
          LDX  $A002   ;($A002)→X,装载分母
          IDIV          ;16位除16位无符号整数除法,(D)÷(X)→X,商→X,余数→D
          STX  $A004    ;X→$A004,保存整数结果
          LDX  $A002   ;($A002)→X,重新装载分母
          FDIV         ;除以前面的余数,得到小数。16位除16位小数除法,(D)÷(X)→X,
                        商→X,余数→D
          STX  $A006    ;X→$A004,保存小数
          LAST:  BRA  LAST  ;原地踏步

程序功能:将内存单元$A000:$A001中的2字节数据除以$A002:$A003中的2字节数据,把整数结果保存在$A004:$A005单元中,小数保存在$A006:$A007单元中。被除数为$8003(32 771),除数为$0053(83),结果为$018A.D4D1,即十进制的394.831,327 71/83=394.831,0.831=54 481/65 536,D4D1=54 481。

程序执行后:X=$ D4D1,D=$003D,($A004)=$01,($A005)=$8A,($A006)=$D4, ($A007)=$D1。

注意:IDIV是无符号整数除法指令,被除数和除数分别位于寄存器D、X中,运算结果商在X中,余数在D中。FDIV是小数除法指令,被除数和除数分别位于寄存器D、X中,商在X中,余数在D中,其中D、X都是无符号整数或小数点位置相同的小数,因此商是一个小于1的二进制小数。运算过程相当于先将D内容乘以216,然后进行32位除以16位(X内容)的除法运算,商在X中,余数在D中,然后商(X内容)再除以216,相当于将小数点移到最高位的左边。保存商,然后再恢复除数X,如此循环反复可以使商的位数向小数点后面不断延伸,直到满足要求为止。

在本例中,只进行了一次小数除法。整数除法完成后,商X=$018A(394),余数为D=$45(69)。然后进行小数除法,69×65 536÷83=54 481.7349,商X=$D4D1(54 481),余数为D=$3D(61)。余数=69×65 536-54 481×83=4 521 984-4 521 923=61。

9.比较指令

比较指令用于将寄存器的内容与立即数或内存单元内容进行比较,实际上进行的是两个操作数的减法运算,因此将其划归在算术运算类指令。但它们并不回送运算结果,只是根据差值设置相应的状态位,作为跳转、循环等的判断条件。这类指令影响标志位N、Z、V、C。

比较指令共有7条,除了CBA为固有寻址方式外,其余6条指令允许使用8种寻址方式,可派生出48条指令,如表3.18所示。

表3.18 比较指令

例3.10 下列程序是用68HC11指令编写的,虽然由于缺少S12的一些高效指令,程序不那么简洁,但程序中所用的指令与S12指令兼容。阅读该程序段,要求:①说明程序的功能;②写出程序执行后涉及的寄存器和内存单元的最后结果。程序运行前, ($2005)=$0A,($2006)=$5C,($2007)=$71,($2008)=$3E,($2009)=$89,($200A)=$15, ($200B)=$B2,($200C)=$00,($200D)=$B6,($200E)=$6C,($200F)=$CD。

                ORG  $E000      ;程序起始地址
                LDS   #$FF       ;初始化SP,$00FF→SP
                LDAB  $2005     ;($2005)→B,读取计数值,数据块长度在$2005中
                CLRA            ;清累加器A,初始比较值为0
                LDX   #$2006     ;$2006→X,指向数据块的起始地址
      THERE:   CMPA  0,X      ;(A)>(M)?,当前最大值>当前值吗?
                BHI   NOCHG    ;如果C|Z=0,则PC+2+rel→PC,是,保持最大值
                LDAA  0,X      ;不是,用当前值替代最大值,((X))→A
      NOCHG:  INX             ;X+1→X,指向下一个数据
                DECB            ;B-1→B,计数器减1
                BNE   THERE     ;如果B≠0,则转移;未查找完,则循环
                STAA  $2004      ;保存最大值
      HERE:    BRA   HERE      ;原地踏步

程序功能:从地址为$2006开始的10个单元中,找出数据块中最大的数,并存入$2004单元。

程序执行后,($2004)=$CD,A=$CD,X=$2010。

10.测试指令

测试指令用于检测累加器A、B或内存单元的内容是否≥0,操作后,源操作数不变,也不保存结果,只是根据结果设置标志位。由于测试指令相当于一次减0操作,因此,V、C标志被清0,N、Z意义不变,分别反映结果最高位状态和结果是否为0。

测试指令有3条,如表3.19所示。其中,TSTA和TSTB为固有寻址,TST允许使用6种寻址方式。

表3.19 测试指令

3.4.3 逻辑运算指令

S12的逻辑运算指令包括基本逻辑运算、取反和求补、移位、位操作、位逻辑运算和位测试等指令。

1.基本逻辑运算指令

基本逻辑运算指令按字节进行“与”、“或”、“异或”逻辑运算,用于将寄存器中的某些位清0或置1,共有8条指令,如表3.20所示。其中,针对CCR的操作只允许立即寻址,针对累加器A、B的操作支持8种寻址方式。

表3.20 基本逻辑运算指令

2.取反、求补指令

取反、求补指令将累加器A、B或内存单元内容取反和求补,结果放回原处,共有6条指令,如表3.21所示。其中,针对累加器A、B的操作为固有寻址,针对内存单元的操作支持6种寻址方式。

表3.21 取反、求补指令

取反指令相当于执行一次$FF-(r)或$FF-(M)操作,显然V=0,C也应该为0,但为了兼容以前M6800系列而将C置1,N、Z标志意义不变。

求补指令实际上进行一次$00-(r)或$00-(M)的减法操作,可以理解为取反加1操作,因此,对标志位的影响与减法一致,其中,N、Z标志的意义不变,但V、C则比较特殊:

● 当操作数=$80时,执行结果仍为$80,此时,V=1;当操作数≠$80时,执行后V=0;

● 当操作数=0时,C=0;否则,C=1,因为0减去任何不为0的数,都要产生借位。

例3.11 阅读下列程序段,要求:①说明程序的功能;②若程序执行前($3000)=$D8,写出程序执行后A、B寄存器的最后结果。

    FD_ABS:LDAA  $3000        ;($3000)→A
    CMPA   #$00                ;(A)-$00,检验A中是否为正数。以下3行是求绝对值
    BGE   A_PLUS           ;如果N⊕V=0,即A>$00,是正数,转移;是负数继续执行
    NEGA                      ;$00-(A)→A,是负数,求其相反数
    A_PLUS: TAB                ;(A)→B,得到A的绝对值并放在B中
    LOOP:   BRA   LOOP        ;原地踏步
    M_2_P:  LDAA  $3000        ;($3000)→A,A=$D8,这两行程序求补数$64-(A)的值
    NEGA                      ;$00-(A)→A,是负数,求其相反数
    ADDA   #$64                ;(A)+$64→A,$64+$D8=$3C,c=1
    LOOP:   BRA   LOOP        ;原地踏步

程序功能:分别求一个对应两位十进制数的十六进制数的负数的绝对值和其补数。

注意:通过被减数加上减数的补数,可以将减法转换成加法。两位十进制数是对100取补的,例如,90-40=50,也可以改为被减数与减数的补数相加,即90+(100-40)=150,丢掉进位后,就得到正确的结果。这里模值是100,即$64。

程序执行后:B=$28,A=$3C。

3.位操作与位测试指令

位操作与位测试指令如表3.22所示,共有7条指令。

表3.22 位操作与位测试指令

位操作指令5条,用于将内存单元中的某1位或某几位清0或置1,其中,CCR特征位操作指令CLC、CLI、CLV的助记符是为了兼容68HC11而保留的,汇编程序自动汇编成ANDCC指令,为立即寻址方式。内存单元清0、置1指令BCLR和BSET支持5种寻址方式。

BCLR指令将一个8位屏蔽字节取反后,与内存单元中的内容作“逻辑与”运算,结果送回原处,实际上8位屏蔽字节被用于指定内存单元的哪些位为0,即若8位屏蔽字节的某位为1,则内存单元的对应位被清0。

BSET指令将一个8位屏蔽字节与内存单元中的内容作“逻辑或”运算,结果送回原处,相当于将8位屏蔽字节中的1送到内存单元的对应位。

位测试指令只有2条,即BITA、BITB,支持8种寻址方式。位测试指令将累加器A、B内容与立即数或内存单元内容进行“逻辑与”运算,但结果并不回送,仅仅根据运算结果设置标志位N、Z、V,目的是检测累加器的特征,如通过将不需要测试的位加以屏蔽,通过检测Z特征位的状态,以判定未屏蔽的位是否为0,根据N的状态,判断最高位是否为0,因为不可能溢出,所以V被清0。

例如

          BITA   #$83       ;$83=10000011B,检测A的第0、1、7位是否为0,若为0,则Z=1
          LDAB  #$0F       ;$0F=00001111B
          BITB   $0C00      ;检测($0C00)单元的低4位是否全为0,若为0,则Z=1

4.逻辑移位指令

逻辑移位指令如表3.23所示,共有8条指令,分为逻辑左移LSL和逻辑右移LSR两种,其中,针对累加器A、B、D的操作为固有寻址,针对内存单元的操作支持6种寻址方式。

表3.23 逻辑移位指令

5.算术移位指令

算术移位指令如表3.24所示,共有7条指令,分为算术左移ASL和算术右移ASR两种,其中,针对累加器A、B、D的操作为固有寻址,针对内存单元的操作支持6种寻址方式。

表3.24 算术移位指令

6.循环移位指令

循环移位指令如表3.25所示,共有6条指令,分为循环左移(ROL)和循环右移(ROR)两种,其中,针对累加器A、B的操作为固有寻址,针对内存单元的操作支持6种寻址方式。

表3.25 循环移位指令

注意:逻辑左移指令LSL与算术左移ASL指令操作相同,但右移指令却有所不同,逻辑右移LSR指令b7位补0,而算术右移ASR指令b7位不变。

3.4.4 高级函数指令

S12的高级函数指令具有高级语言中某些函数的功能,例如求最大值、最小值、乘积累加、内插值运算等。实际上利用前面介绍的指令也可以实现这些功能,但需要编写一个程序段。S12在微代码层次上实现这些功能,可以大大简化用户程序。

1.求最大值、最小值指令

求最大值、最小值指令如表3.26所示,用于在累加器A、D与内存操作数之间取最大或最小的一个,共有8条指令,求最大值和最小值各有4条指令,支持5种寻址方式,其中,以字母M开头的4条为8位指令,以字母E开头的4条为16位指令。虽然这些指令自动完成数据的比较和传送,但标志位显示了比较的结果和是否发生了数据传送。表3.26给出了指令执行后标志位的设置规则。

表3.26 求最大值、最小值指令

2.乘积累加指令

乘积累加指令只有1条EMACS,采用特殊寻址方式。可实现带符号数的16位乘法和32位累加运算,隐含使用寄存器X和Y,指令功能为

          EMACS   opr16a   ;(M(X):M(X+1))×(M(Y):M(Y+1))+(M~M+3)→M~M+3

操作过程是:先将X和Y所指向的两个16位内存操作数相乘,然后将32位乘积与一个扩展寻址的32位内存操作数相加,结果仍然保存在该地址。指令要求两个乘数及32位内存被加数均为有符号数。指令执行前,必须另外设置X和Y寄存器。指令执行后,影响标志位N、Z、V、C,其中N、Z、V的意义不变,但C的意义特殊,反映最终结果的低位字是否产生进位,有进位时C=1,否则C=0,C可用于最终结果的四舍五入操作。乘积累加指令常用于多字节的乘法运算。

例3.12 计算x1 × y1+x2 × y 2+…+xn × yn,n=8,xi,yi为数据字。

      MAC_EX: LDX     #MA_TB         ;X指向第一个乘数
                LDY     #MB_TB          ;Y指向第二个乘数
                MOVW  MR_TB,#0        ;首先将累加和清0,共4个单元
                MOVW  MR_TB+2,#0      ;
                LDB     #8              ;循环计数器,共8对乘数
      MAC_LP:  EMACS  MR_TB          ;求乘积并累加到MR_TB
                BVS     MAC_ERR        ;检查是否溢出,是,则转出错处理
                LEAX   2,X             ;指向下一个被乘数
      LEAY      2,Y                     ;指向下一个乘数
                DBNE   B,MAC_LP       ;B-1→B,B=0?,若B≠0,未完,继续循环
                ANDCC  #$FE            ;等同于CLC,表示无溢出
                RTS                     ;结束返回
      MAC_ERR:ORCC    #$01             ;C=1,表示溢出
                RTS                     ;出错返回
                ORG    $0800            ;被乘数、乘数、结果在RAM区
      MA_TB    RMB    16              ;第一个乘数
      MB_TB    RMB    16              ;第二个乘数
      MR_TB    RMB    4               ;累加和

3.查表与插值指令

查表与插值指令有2条,采用变址寻址IDX方式。其中,TBL为8位内插值操作,结果保存在累加器A中;ETBL为16位内插值操作,结果保存在累加器D中。指令功能为

          TBL   oprx0_xysppc     ;(M)+{(B)×[(M+1)-(M)]}→A
          ETBL   oprx0_xysppc     ;(M:M+1)+{(B)×[(M+2:M+3)-(M:M+1)]}→D

为了说明指令的执行过程,先介绍插值运算的原理。

设有一条曲线y= fx),定义域为axb,要确定定义域内任意一点xL对应的函数,虽然可以直接利用函数关系式来计算 fxL),但在单片机中实现起来计算时间长、程序设计复杂,很不方便。为此,可将曲线数字化,即在曲线上选取n+1个点(x0y0), (x1y1),…,(xnyn),其中,(x0y0)和(xnyn)为曲线的两个端点。使各个点在横轴上的投影间距相等,即

只要给出x0Δn,便可很容易地推算出x0xn之间各点。因此,用有限的数据点可以近似描述函数y= f(x),而数据点以外的函数值被舍弃。但需要时,可以通过线性插值近似求取函数值,即给定某个xLxixLxi+1),对应的函数值yL

这实际上是用一条折线近似代替原来的曲线,适当选取n,可以将误差控制在允许的范围内。

在内存中建立一个具有n个记录的数据表格myTAB,各个记录分别是曲线在x0x1,…,xn的函数值y0y1,…,yn,当需要确定上述数据点以外的任意一点的函数值时,可以按照如下步骤:

① 确定i,使xixLxi+1

② 计算

③ 计算yL =yi+k(yi+1-yi),显然k<1。

TBL指令自动进行步骤③的计算,但要求如下准备工作:

① 数据表格myTAB有效,表中记录为带符号字节型整数。

② 通过变址寻址,使有效地址指向yi(表中下一个元素即为yi+1),允许使用5位偏移量或累加器偏移量的变址寻址,变址寄存器可以是X、Y、SP或PC。

③ 计算k,并保存到B中。

k是一个纯小数,因此,TBL指令将假设B中数据的小数点在最高位的左边,这恰好可以用FDIV指令实现。这时,实际上插值精度k=B/256,相当于将一条线段的两个端点的y值之差yi+1-yi分成256份,取其中的(B)份。

例3.13 假设数据表格为TBL_TAB,表格中的xi为10,20,30,…,120共11个数,求xL =24对应的y值。

ETBL与TBL操作相同,区别有两点:①数据表格myTAB中的记录为带符号字型整数;②操作结果保存在累加器D中。虽然处理数据的范围大了,但由于仍然使用寄存器B保存比例因子k,因此,精度并未提高,仍然是1/256。

3.4.5 程序控制指令

程序控制指令通过改变程序计数器PC的内容,使MCU从正常的执行顺序改变到不同的程序流程,分为转移和调用两大类,前者为永久转移,不再返回;后者执行完新的任务后,返回原地址继续执行。其中,转移指令又分为无条件转移和有条件转移两种。

1.无条件转移指令

如果不加约束地改变程序执行路线,可以使用无条件转移指令。

无条件转移指令如表3.27所示,共有5条指令,前4条(两对)指令采用相对寻址方式,指令中需给出地址偏移量,其中,BRA为无条件固定短转移,使用8位地址偏移量;BRN为不转移,相当于空操作指令。无条件固定长转移,也配套有相应的LBRA、LBRN指令,采用16位地址偏移量。

表3.27 无条件转移指令

两条不转移指令BRN和LBRN实际上主要用于程序的调试。由于一个应用程序中往往使用很多分支指令,每个分支指令会产生两条不同的程序执行顺序,因此,整个程序的实际运行路线有多种可能,而且每次运行到底走哪条路线,取决于运行环境、内部状态或外部输入,有时无法预知。在程序调试阶段,为了查找问题,可以避开分支,简单地用BRN或LBRN代替实际的分支指令,使整个程序的执行时间和代码位置不发生任何变化。

JMP指令支持6种寻址方式,允许直接或间接给定目标地址,然后将目标地址直接赋给PC。JMP指令的转移范围是64 KB空间,执行时不影响标志位,但将清除指令队列。

例如

          JMP      $1280            ;EXT
          JMP      $05,X           ;IDX
          JMP      $1000,SP         ;IDX2
          JMP      [D,X]            ;[D,IDX]

2.条件短转移指令

条件短转移指令共有18条指令,分为两类:一类是根据标志位的状态决定转移还是继续执行,但指令只检查有关标志位状态,而不管这些状态如何生成,也不影响任何标志位,该类指令共有16条,采用相对寻址方式,地址偏移量为8位有符号数,最大转移范围是-128~+127,如表3.28所示;另一类指令包含产生转移条件的操作,且并不影响标志,也不依赖标志,而是根据操作结果决定是否转移,虽然指令中也出现了8位地址偏移量,但却并不单纯为相对寻址,而是包含多种寻址方式,按对源操作数的寻址,有5种寻址方式,该类指令只有2条,如表3.29所示。

表3.28 条件短转移指令[1]

表3.29 条件短转移指令[2]

注意:① BEQ和BNE指令仅仅以Z特征位作为判别基础,因此,既可以用于有符号数,也可用于无符号数。

② 指令BHS与指令BCC转移条件相同,而指令BLO与指令BCS转移条件相同。实际上,两对指令具有相同的操作码。为了改善代码的可读性,在使用中应采用正确的助记符指令。通常,若要比较两个无符号数,则利用BHS或BLO指令,但如果将C特征位用于某些其他功能,例如作为出错特征位,则利用BCC或BCS指令。所能引起的唯一问题出现在反汇编时,由于反汇编程序不知道源程序的意图,因此,总是取一个助记符指令,例如会将BHS指令翻译成BCC指令。

例如

          BRH_LP:  CPA     #$88         ;(A)与$88比较,与136比较还是与-120比较?
                                        ;取决于后面的指令
                    BHI     BRH_EX1     ;用BHI,认为是无符号数比较,根据C、Z判断(A)是否
                                          大于136,
                                        ;大于则转移
                    …
          BRH_EX1: CPA     #$88         ;
                    BGT BRH_LP          ;用BGT,认为是有符号数比较,根据Z、N、V判断(A)
                                          是否大于-120,大于则转移
                    …
          BRH_EX2: LSLA                ;将A最高位移入标志c,利用指令BCS判断是否为1
                    BCS     BRH_EX1     ;若c=1,则转移到BRH_EX1,c=0,则继续往下执行
                    …

3.条件长转移指令

条件长转移指令如表3.30所示,共有16条指令,采用相对寻址方式,与表3.28条件短转移指令相对应,区别在于转移距离可达-32 768~+32 767,即可转移到64 KB空间的任意位置。

表3.30 条件长转移指令

4.循环控制指令

S12具有6条循环控制指令,均为相对寻址,如表3.31所示,其中4条自动调整循环计数器,可实现类似高级语言中的for循环;另外2条为条件转移,可以实现while循环。

表3.31 循环控制指令

上述6条指令均不影响标志位,即使存在调整计数器的操作也不例外。

5.子程序调用与返回指令

S12设置了两套子程序调用与返回指令,如表3.32所示。BSR/JSR用于64 KB空间以内的调用,对应的返回指令为RTS;CALL用于64 KB以外的扩展空间,对应的返回指令为RTC。

表3.32 子程序调用与返回指令

BSR与JSR的主要区别是目标地址的确定方式与调用范围不同,BSR采用相对寻址方式,调用范围为-128~+127;而JSR支持7种寻址方式,除了直接寻址的子程序入口地址必须在$00~$FF范围内之外,其他寻址方式的调用范围是整个64 KB地址空间。

对于超过64 KB地址空间的寻址,S12只提供了一种访问手段,即通过CALL指令调用其中的子程序,然后用RTC指令返回。CALL指令支持6种寻址方式。

3.4.6 S12控制指令

S12控制指令主要负责MCU的软件中断管理和工作状态的改变。

1.中断指令

与中断相关的指令有3条,均为固有寻址方式,如表3.33所示。SWI是软件中断指令,在程序中设置SWI指令,通过中断响应方式进入服务程序,利用RTI指令返回。该指令用于Freescale自己的一些软件产品,用户可利用该指令进行程序调试。

表3.33 中断指令

TRAP是指令陷阱,如果MCU读到非法指令,就会落入指令陷阱,产生一个中断。只要MCU取指遇到$18 $30~$18 $39、$18 $40~$18 $FF之间的双字节机器码,即认定是非法指令,自动进入TRAP中断响应过程。用户设计的TRAP服务程序可以根据具体情况确定处理方案。

2.S12控制指令

S12控制指令有3条,均为固有寻址方式,如表3.34所示。

表3.34 S12控制指令

STOP、WAI和BGND指令用于改变MCU的状态。STOP指令使MCU进入待机状态,整个MCU停止运行,功耗最低。但当S=1时,STOP被禁止,这时STOP指令相当于2个周期的空操作指令。

WAI指令使MCU进入中断等待状态。注意到STOP指令和WAI指令都关闭MCU时钟,使MCU停止运行,进入节电状态,区别是STOP指令关闭MCU所有时钟,而WAI指令仅仅关闭MCU时钟,复位和中断将重新恢复MCU时钟。

BGND指令使MCU进入后台调试模式,但必须事先通过单线接口将BDM状态寄存器的ENBDM位置1,使BDM允许,否则MCU不会进入该状态,然后便可以利用各种BDM命令进行系统调试。由于BDM状态寄存器不允许在普通运行模式下写入,只能在BDM方式下通过串行BDM口写入,故BGND指令在普通运行模式下不起作用。MCU进入后台调试模式后,便可以用各种BDM命令进行系统调试。该指令采用隐含寻址方式,不影响标志寄存器CCR。

NOP是空操作指令,通常用于短时间的延迟,在软件延时子程序中经常使用。

3.4.7 模糊运算指令

模糊运算指令是S12的特色指令,这是一组专门面向模糊控制的模糊逻辑运算指令,共有4条指令,均为特殊寻址方式,如表3.35所示。指令执行过程中允许MCU响应中断,中断返回后可继续运行。

表3.35 模糊逻辑运算指令

模糊控制器主要包括知识库和模糊推理机两部分,知识库中包括输入、输出隶属度函数库和规则库3部分,模糊推理机主要包括模糊化接口、推理运算和解模糊接口3部分,它们分别由知识库中对应的部分提供知识与规则的支持。控制过程分为3步:

① 根据隶属度函数将过程变量变成模糊输入,这部分工作在模糊化接口中完成,结果存入存储器;

② 根据规则库和模糊输入,推理运算部分计算模糊输出,也存入存储器;

③ 根据隶属度函数和相关规则,解模糊部分将模糊输出变成控制输出,送往执行机构,实现对象的控制。

S12为这3个步骤分别设置了相应的指令,即MEM、REV和REVW、WAV,并为每条指令规定了必要的知识表达式,从而实现实时输入变量的模糊化、规则判定与运算、反模糊化给出精确输出。以这几条指令为核心,再加上一些辅助指令,使应用程序从知识库中向逻辑推理机提供输入隶属度函数、规则表等,分别用于模糊化、规则判定、反模糊化,即可实现模糊控制器。

与不使用这组指令相比,利用这组指令可使程序代码量减少到原来的20%,运算速度可提高几十倍。

1.模糊化指令MEM

MEM是单个隶属度函数计算的核心指令,用于将输入变量模糊化。该指令执行的前提条件是:

● 建立一个隶属度函数;

● 在内存建立描述该隶属度函数的数据结构;

● 指定保存运算结果(模糊输入)的内存空间。

隶属度函数的数值描述应满足下列条件:

● 每个隶属度函数采用4个字节无符号数的数据结构来描述,即{起点的x坐标,终点的x坐标,前沿斜率,后沿斜率}。

● $00≤起点的x坐标<终点的x坐标≤$FF。起点和终点的x坐标均采用类似归一化的数值,即将输入范围映射到$00~$FF后的对应值。

● 前、后沿所在直线交点的y坐标≥$FF。前沿、后沿斜率符号固定不变,只需给出具体数值,分别为。当斜率为无穷大(即前沿或后沿为一条直线)时,用$00来表示。因为真正的斜率不可能为0。

例如

      INPUT_MFS:       FCB      $40     ;起点的x坐标
                      FCB      $D0     ;终点的x坐标
                      FCB      $08     ;前沿斜率
                      FCB      $04     ;后沿斜率
                      …

MEM指令执行前,累加器A中为输入变量经映射后的数值。X、Y寄存器存放上述数据结构的首地址和结果存储单元地址,其中,X寄存器指向梯形隶属度函数4个顶点的第1个,即指向知识库中的1个隶属函数;Y寄存器指向用于保存模糊量输出结果队列(隶属度)的存储单元。

执行模糊化指令MEM,按梯度法求出A中实时输入变量的隶属度,并存入Y寄存器指向的单元,然后X=X+4,指向知识库中的下一个隶属度函数,Y=Y+1,指向下一个结果单元,为计算下一个函数做好准备。

MEM指令每次执行只确定单个隶属度函数值,但一个变量所在的集合一般分为多个模糊子集,即用n个隶属度函数来模糊化,得到n个模糊量,这就需要连续执行n次MEM指令,进行n次循环,n的取值通常在3~9之间。

2.模糊推理指令REV和REVW

REV和REVW分别是普通和加权推理运算指令,区别在于,REV指令认为所有推理规则具有同样的权重,使用8位偏移量;REVW指令则对推理规则进行加权处理,使用16位偏移量。

(1)REV指令

按模糊逻辑规则求值,根据模糊输入确定模糊输出,但必须按照事先约定创造必要的条件,包括:

● 按照需要,提供若干个描述模糊输入、输出关系的规则;

● 在内存中建立描述该规则的数据结构;

● 指定保存运算结果(模糊输出值)的内存空间,并事先清0;

● 设置有关寄存器。

所谓规则,实质上是一个模糊逻辑表达式。在规则库中,各条规则的一般形式为

          if A1 and A2 then C  或  if A1 and A2 and A3 then C1 and C2

其中,Ai为模糊输入,称为规则前件;Ci为模糊输出,称为规则后件。它们具体体现为隶属度值。运算符and是“模糊逻辑与”,其意义是取两个操作数(模糊输入)的最小值,结果为模糊输出。S12也允许“模糊逻辑或”操作,其意义是取两个操作数(模糊输入)的最大值。但规定,各个前件之间只进行“模糊与”运算,而如果几个规则的逻辑后件影响到同一个模糊输出,则它们之间隐含进行“模糊或”运算。这样规定后,各前件和各后件之间的运算不再需要另外声明。因此,REV指令规定的数据结构为:前件与后件之间用$FF分隔,规则之间用$FE分隔。每个前件中的内容是一个8位无符号偏移量,基地址加上该偏移量指向模糊输入量。后件中的偏移量加上基地址指向推理结果。例如

    RULE_START:FCB I00,I01,I02,…,$FE,O00,O01,O02,…,$FE ;规则0,字节数=MI0+MO0+2
                FCB I10,I11,I12,…,$FE,O10,O11,O12,…,$FE ;规则1,字节数=MI1+MO1+2
                        ......
                FCB In0,In1,In2,…,$FE,On0,On1,On2,…,$FF ;规则n,字节数=MIn+MOn+2
                                                    ;最后一个规则,故末尾用$FF
                        ......                          ;其他程序指令、数据等
    FUZ_INS:    RMB MI0+MI1+…+MIn+MO0+MO1+…+MOn   ;模糊输入/输出,在RAM中

上述RULE_START中,每一行为一个规则,其中的值是FUZ_INS中各个对应单元的地址偏移量(即指针),如I00表示第一个规则的第一个模糊输入的内存地址偏移量(相对于FUZ_INS,下同),O00表示第一个规则的第一个模糊输出的内存地址偏移量,以此类推。FUZ_INS是各个规则对应的模糊输入/输出总表,各个输入/输出位置可以随意安排,只要在RULE_START中正确指明即可。但当FUZ_INS或其中的某些元素位置发生变化时,RULE_START的内容也相应发生变化。实际应用中,为了便于与其他指令(如MEM、WAV等)衔接,在FUZ_INS中,可以将所有模糊输入排在前半部分,模糊输出排在后半部分。例如第一行表示的规则为

          if I00 and I01 and I02 and … then O00 and O01and O02 and…

运算时取I00、I01、I02…所指向的各模糊输入的最小值,然后依次与O00、O01、O02、…所指向的各模糊输出进行“模糊或”运算,结果更新O00、O01、O02…所指向的模糊输出。所有偏移量均为8位无符号数,但不能是$FE和$FF,指令执行时通过(Y)+偏移量寻址模糊输入和模糊输出。

在REV指令执行前,规则表存放在Flash存储器中,推理结果(模糊输出)存放在RAM中。令X寄存器指向推理规则表的首地址,Y寄存器指向模糊输入/输出表的首地址。用LDAA #$FF指令初始化累加器A,该指令同时将标志V清0。然后清0模糊输出的缓冲区队列,即假设所有输出均不可能。

REV指令开始执行时,读取X寄存器指向的规则表中的第一个前件,然后X自动加1指向规则表的下一个元素。逐一提取前件偏移量,与Y中的基地址相加,指向模糊输入值,进行“模糊与”运算。累加器A用于存放处理过程的中间结果,处理方式是,每次总是找出前件中最小的那个隶属度,存入模糊输出缓冲区队列,直到出现$FF。

前件处理结束后,该极小值就是该条规则的规则强度(模糊输出)。处理后件时,遇到$FE结束,累加器A自动恢复成$FF,并重新进行模糊推理。

REV指令执行后,模糊输出自动存放在模糊输入/输出表对应的单元,X指向规则库中$FF后的第1个字节,Y不变。

如果指令执行期间发生中断,通过现场保护和恢复,中断返回后可以继续运行,其结果与没有发生中断时相同。这时S12为了保证中断响应的实时性而采取的措施,同时, S12响应中断时,将所有寄存器内容压入堆栈,也有利于REV指令恢复运行。

(2)REVW指令

REVW是加权规则推理指令,与REV指令功能类似,差别在于:

● REVW指令处理每个规则时,将前件表达式的运算结果乘以一个权值后,作为模糊后件,而REV指令直接将结果作为模糊后件。

● REVW指令执行前需要准备权值数据表,而REV指令则不需要。

● REVW指令的规则表中数值是各个模糊输入/输出的16位直接地址,每个表项为一个16位无符号字,而REV指令则为8位的偏移量。

● REVW指令的分隔标志为$FFFE,结束标志为$FFFF;而REV指令的分隔标志分别为$FE和$FF。

● REVW指令执行前要求X指向规则表,Y指向权值数据表,累加器A置为$FF, V标志清0,模糊输出单元清0;而REV指令要求X指向规则表,Y指向输入/输出表,累加器A置为$FF,V标志清0,模糊输出单元清0;两者对Y寄存器的使用不同。

● 由于REVW指令要求每个元素为1个字,因此,对于同样的规则,REVW指令的规则表比REV指令大一倍,另外,要进行权值乘法运算,因此执行时间要长一些。

REVW指令规定的数据结构如下:

    RULE_START: FCB  I00,I01,I02,…,$FFFE,O00,O01,O02,…,$FFFE
                      ;规则0,字节数=2×(MI0+MO0+2)
                FCB  I10,I11,I12,…,$FFFE,O10,O11,O12,…,$FFFE
                      ;规则1,字节数=2×(MI1+MO1+2)
                …
                FCB  In0,In1,In2,…,$FFFE,On0,On1,On2,…,$FFFF
                      ;规则n,字节数=2×(MIn+MOn+2)
    ;最后一个规则,故末尾用$FFFF
…
    ;其他程序指令、数据等
FUZ_INS:     RMB MI0+MI1+…+MIn+MO0+MO1+…+MOn ;模糊输入/输出,在RAM中
FUZ_WT:     FCB     W0,W1,W2,…,Wn              ;加权值数据表

上述数据结构与REV指令类似,注意权值数据表项数与规则数相等,即每个规则对应一个权值,且表中权值均为0~1之间的小数,小数点位于最高位左边。

REVW指令执行前,规则表RULE_START首地址和权值数据表FUZ_WT首地址分别放入X、Y寄存器,然后累加器A、标志V和所有模糊输出清0。REVW指令执行后,模糊输出自动存放在模糊输入/输出表FUZ_INS对应的单元,X指向规则库中$FFFF后的第1个字节,Y不变。

如果指令执行期间发生中断,其处理过程与REV相同,但现场保护比REV指令复杂。

3.解模糊指令WAV和WAVR

WAV是解模糊指令,用于实现控制输出的精确化,它与EDIV指令配合完成模糊输出到控制输出的变换。解模糊算法不止一种,简单的反模糊化可以采用最大隶属度法,即利用最大值、最小值指令实现。重心法(加权平均法)是一种较为常用的算法,该方法将模糊输出作为加权值,计算所有与控制输出有关的模糊输出的加权平均,由于推理机把输出隶属度函数定义成单点(Singleton),因此,控制输出为

式中,S i是来自知识库的第i个模糊输出的单点位置;Fi是来自RAM的第i个模糊输出;n是输出模糊子集数(控制输出序号的数量)。

WAV指令完成重心法的主要工作,即计算控制输出的分子和分母,分别存放在寄存器Y:D和X中。WAV指令之后跟一条EDIV指令进行除法计算,即可得到控制输出。

WAV指令执行前提条件是

● 提供所有的模糊输出,并按顺序连续存放在内存中;

● 提供所有的模糊输出对应的单点值列表,并按顺序连续存放在内存中;

● 提供模糊输出数量;

● 初始化相应的寄存器,包括寄存器X、Y和累加器A。

模糊输出和所对应的单点值均为8位二进制数,控制输出的分子(乘积之和)和分母(权值之和)分别为24位和16位。WAV指令执行之前,X指向单点值列表(S数组)的首地址,Y指向模糊输出表(F数组)的首地址,B寄存器初始化为模糊输出的数量n (通常为5~9)。WAV本身是一个循环n次的指令,执行结果为

接着执行无符号除法指令EDIV,即(Y:D)/(X),将在寄存器Y中得到控制输出,余数在累加器D中。

如果指令执行期间发生中断,通过现场保护和恢复,中断返回后可以继续运行,其结果与没有发生中断时相同,但过程较为复杂。事实上,当中断发生时,MCU将保存所有中间结果和内部寄存器的内容,返回地址入栈前,程序指针PC自动调整为“PC-1→PC”,指向WAV指令的第二个字节$3C(WAV指令的机器码为$18 $3C)。这样,当中断返回时,MCU取得$3C,而$3C正是WAVR指令的代码,开始执行WAVR指令,恢复中断响应过程中保护的现场,然后继续执行WAV指令。

通常,伪指令由汇编程序解释,并不生成机器码,但WAVR指令并非如此,它具有自己的机器码和规定的操作,几乎与正常的指令相同,更像中断或子程序返回指令,但有一个重要区别,就是不可能用其他正常的指令为其营造一个虚拟现场,在没有WAV指令参与的情况下让WAVR指令直接运行而得到正确的结果。

WAV指令可以多次被中断,并不影响运行结果,甚至在中断服务程序中,还可以再次使用WAV指令,因为所有的中断现场都保存在堆栈中。

例3.14 编写以MEM、REV和WAV为核心的模糊推理机程序。

        FUZZIFY:     LDX     #INPUT_MFS          ;X指向输入隶属度函数表
                    LDY     #FUZ_INS            ;Y指向模糊输入/输出表
                    LDAA   CURRENT_INS        ;取第1个过程变量
                    LDAB   #7                   ;每个输入有7个模糊子集
        GRAD_LOOP: MEM                        ;确定1个模糊子集的隶属度
                    DBNE   B,GRAD_LOOP        ;循环完成7个模糊子集隶属度的计算
                    LDAA   CURRENT_INS+1       ;取第2个过程变量
                    LDAB   #7                   ;每个输入有7个模糊子集
        GRAD_LOOP1:MEM                        ;确定1个模糊子集的隶属度
                    DBNE   B,GRAD_LOOP1       ;循环完成7个模糊子集隶属度的计算
                    LDAB   #7                   ;循环计数器
        RULE_EVAL:  CLR 1,Y+                    ;清除模糊输出
                    DBNE   B,RULE_EVAL        ;循环
                    LDX     #RULE_START         ;X指向规则表
                    LDY     #FUZ_INS            ;Y指向模糊输入/输出表
                    LDAA   #$FF                 ;A初始化,并清除标志V
                    REV                         ;规则推理
        DEFUZ:      LDY     #FUZ_OUT            ;Y指向模糊输出表
                    LDX     #SGLTN_POS          ;X指向模糊单点值列表
                    LDAB   #7                   ;每个控制输出对应7个模糊输出
                    WAV                         ;分别计算分子和分母
                    EDIV                        ;计算分数值,得到控制输出
                    TFR     Y,D                 ;计算结果保存到A:B
                    STAB    COG_OUT            ;舍弃低位,将高位保存到控制输出
                    RTS

本例是一个以MEM、REV和WAV为核心的模糊推理机程序,程序中没有给出规则库等原始数据,而是直接引用相应的地址标号,其中INPUT_MFS为输入隶属度函数表, FUZ_INS为模糊输入/输出表,CURRENT_INS为过程变量表,RULE_START为推理规则库表,FUZ_OUT为模糊输出表,SGLTN_POS为模糊单点集数据表,COG_OUT为控制输出表。

例3.15 倒立钟摆系统的模糊控制如图3.3所示,通过向左或向右移动平衡点来平衡钟摆,系统输入是角度和δ角,角度是钟摆在垂直方向的度数,δ角是钟摆的角速度(°/s)。一个传感器装在钟摆支点上,可以对输入进行测量,电位计、互感式线性位移传感器或光电编码器可以作为测量和计算两个输入的传感器。控制电动机的电流使平衡点向左或向右移动,重力产生的扭矩使钟摆往下掉,该扭矩是一个圆周力,它与重力的大小、钟摆的长度、力的方向(这里是垂直的)和钟摆棒之间的角度有关。因此,系统需要控制电动机的扭矩来抵消重力产生的扭矩,在磁通量不变的情况下,电动机扭矩与电枢电流成正比。倒立钟摆系统为双输入/单输出系统,每个都具有7个隶属度函数,来定义电平的范围。

图3.3 倒立钟摆系统的模糊控制

程序清单:

    FUZZIFY:     LDAA   ANGLE              ;获得角度模拟输入
                LDX     #INMEMFCN          ;X指向输入隶属度函数
                LDY     #FUZINPUT           ;Y指向模糊输入表
                LDAB   #7                   ;初始化隶属度函数计算
    GRDANGLE:  MEM                        ;计算所有角度函数的隶属度
                DECB                        ;
                BNE     GRDANGLE           ;
                LDAA   DELTA               ;获得δ角模拟输入
                LDAB   #7                   ;初始化隶属度函数计算
    GRDDELTA:   MEM                        ;计算所有δ角函数的隶属度
                DECB                ;
                BNE     GRDDELTA   ;
    RULEEVAL:   LDAB   #7          ;以下是规则计算部分,前面的模糊输出清0
    CLROUT:     CLR     1,Y         ;
                INY                 ;
                DECB                ;
                BNE     CLROUT     ;
                LDX     #RULESTART ;以下设置推理规则,然后用REV推理计算
                                    ;X指向规则表
                LDY     #FUZINPUT   ;Y指向模糊输入表
                LDAA    #$FF        ;REV指令要求初始化累加器A、清除V标志
                REV                 ;按模糊逻辑规则计算产生模糊输出
    DEFUZZY:    LDY     #FUZOUTPUT ;以下是反模糊化,生成模拟输出。Y指向模糊输出表
                LDX     #SGLTNPOSN ;X指向单点位置
                LDAB    #7          ;初始化独立位置计数
                WAV                 ;计算加权平均。Y:D=24位乘积之和,X=16位权值之和
                EDIV                ;计算控制输出
                XGDY               ;
                STAB                ;保存模拟输出
                ......               ;后面是处理I/O的代码

3.5 汇编程序伪指令

前文所介绍的是S12指令系统中的每一条指令,都是用意义明确的助记符来表示的,但是用汇编语言编写的源程序,计算机不能直接执行,必须要通过汇编程序翻译成机器语言(称为目标代码)后计算机才能执行,这个翻译过程称为编译。

汇编程序对用汇编语言编写的源程序进行编译时,还要提供一些汇编用的指令,这些指令称为伪指令。伪指令用来对汇编过程进行某种控制或者对符号、标号进行赋值。例如,指定程序或数据有效的起始地址,要给一些连续存放的数据确定单元等,但伪指令与指令完全不同,它既不控制计算机的操作,也不产生可执行的目标代码,它不影响程序的执行,对于大部分伪指令甚至不会影响存储器中的内容,只能被汇编程序识别并指导汇编如何进行,故称为伪指令。

S12单片机常用的伪指令有以下几类。

3.5.1 段定义指令

段定义指令包括ORG、SECTION和OFFSET。ORG指令通过设置一个单元计数器LC(Location Counter)来定义一个绝对的段(Absolute Section)。SECTION指令用来定义一个可重定位(浮动)的段(Relocatable Section)。OFFSET指令通过产生绝对符号(Absolute Symbols)来定义一个偏移量段(Offset Section)。

1.汇编起始指令(ORG)

ORG(Origin)总是出现在每段源程序或数据块的开始,用来规定目标程序或数据块的起始地址。语法为:

          [标号:]   ORG   16位地址

其中,标号是选择项,可有可无。例如,ORG $nnnn。

通常,在汇编语言源程序的开始,用一条ORG伪指令来规定程序存放的起始地址。但在一个源程序中,也可以多次使用ORG指令,以规定不同的程序段的起始位置。但所规定的地址应该是从小到大的,而且不允许有重叠,即不同的程序段之间不能有重叠。一个源程序若不用ORG指令开始,则编译器自动从$C000开始存放目标代码。

2.声明可重定位段指令(SECTION)

SECTION指令用来声明可以重定位的段信息,如指定段的名称,某一段是代码还是数据,放在什么位置等。其语法为

          名称:SECTION   [SHORT][数值表达式]

其中,[ ]中的内容是选择项。“名称”是分配给段的名字,两个具有相同名称的SECTION指令指的是相同的段,后面出现的同名段中的内容将被放在前一个同名段的最后一条语句或指令之后。[SHORT]表示本段可以采用直接寻址(DIR)模式。[数值表达式]是与MASM编译器兼容的可选参数。

一个段的第1条SECTION指令将单元计数器设置为0,随后的SECTION指令使单元计数器重置为该段最后一个代码后的地址对应的数值。

至少包含一条汇编指令的段为代码段(Code Section)。如果只包含DC、DCB指令,则被认为是常量段(Constant Section)。当段内至少包含一条DS指令或为空时,则该段被认为是数据段(Data Section)。如果一个段中包含DC和DS或汇编语句,则该段不是数据段,默认被定位在ROM中。

例3.16 包含同名段的汇编结果。

            相对地址    内存内容(十六进制)              指令
                                              aaa: SECTION  4
            000000         A7                  xx:  NOP
                                              bbb: SECTION  5
            000000         A7                  yy:  NOP
            000001         A7                      NOP
            000002         A7                      NOP
                                              aaa: SECTION  4
            000001         A7                  zz:  NOP

本例中aaa段被bbb段分成了两块,与标号zz相关的单元计数器内容为1,因为在该段的标号xx处已经定义了1条NOP指令,汇编程序会把后面一个aaa段的NOP指令放在前一个aaa段的NOP指令之后。

例3.17 用[SHORT]参数来直接寻址。

            相对地址    内存内容(十六进制)              指令
                                          dataSec:  SECTION   SHORT
            000000                        data:    DS.B      1
                                          codeSec: SECTION
                                          entry:
            000000         87                        CLRA
            000000         5A xx                     STAA   data     ;使用DIR寻址方式

3.产生绝对符号指令(OFFSET)

OFFSET指令用来定义一个偏移量段,并且将单元计数器初始化为表达式中规定的数值。其语法为

          OFFSET   表达式

表达式必须是绝对的,不包含外部引用的、未定义的或前面定义的标号。对于数据结构或堆栈框架的仿真,偏移量段非常有用。

例3.18 用OFFSET指令访问结构中的元素。

除了在OFFSET指令后遇到DS等指令外,当语句影响单元计数器时,偏移量段结束。重新使用以前的段,单元计数器数值恢复到该段中下一个可供使用的单元地址。

3.5.2 常量赋值指令

常量赋值指令有2条,即EQU和SET指令,用于给表达式分配一个名称。两者的区别是,EQU指令给表达式分配的名称不能重新定义,而SET指令分配的名称可以重新定义。

1.等值指令(EQU)

EQU(Equate Symbol Value)用来将一个数或特定的汇编符号赋予规定的字符名称,即将伪指令右面的值赋给左面用户定义的符号。语法为

          字符名称:   EQU   表达式(数或汇编符号)
          字符名称    EQU   表达式(数或汇编符号)

用EQU指令赋值以后的字符名称可以用做数据地址、代码地址、位地址或直接当做一个立即数使用。因此,给字符名称所赋的值可以是8位数,也可以是16位二进制数。例如

          BUFFER   EQU   $4058              ;定义缓冲器的地址
          PORTP    EQU   $56                ;PORTP 的地址
          BIT0      EQU   %000000001         ;第0位的位置

可以用EQU指令设置变量,例如

                    MaxElement:  EQU   20                ;最大元素数20
                    MaxSize:     EQU   MaxElement*4       ;最大占用空间80
                    Time:        DS.B   3                 ;为变量预留3字节的内存空间
                    Hour:        EQU   Time               ;第1字节地址
                    Minute:      EQU   Time+1             ;第2字节地址
                    Second:      EQU   Time+2             ;第3字节地址

注意:

① 如果字符名称不用“:”作为分隔符,则伪指令前必须有空格,否则,汇编程序会报错。

② 使用EQU给一个字符名称赋值后,这个字符名称在整个源程序中的值是固定的。字符名一旦被EQU赋值,其值就不能再重新定义,即在一个源程序中,任何一个字符名称只能赋值一次。

③ 使用EQU指令时必须先赋值后使用(所定义的字符名称必须先定义后使用),故该语句通常放在程序开头,并且表达式不能是未定义或后面定义的符号。

2.设置符号值指令(SET)

SET(Set Symbol Value)指令与EQU指令相似,同样用来定义字符名称与常量的关系,其语法为

          字符名称:   SET   表达式

其中,表达式不能是未定义或后面定义的符号。用SET指令定义的字符名称的值是临时的,以后还可以用SET指令重新赋值。例如

          Count:SET   2         ;定义Count=2
          One: DC.B  Count      ;给表达式Count分配一个字节地址,地址标号为One
          Count:SET   Count-1    ;重定义Count=1
                DC.B    Count    ;给表达式Count分配一个字节地址
                IFNE    Count    ;如果Count≠0,则汇编随后的语句,否则,汇编ENDIF之后的语句
          Count:SET   Count-1    ;重定义Count=0
                ENDIF           ;
                DC.B  Count      ;给表达式Count分配一个字节地址

3.5.3 常量存储指令

常量存储(Stored Constant)指令主要用于在MCU的ROM区创建查询表(Lookup Table)和变量的初始化值,包括以前M6800系列伪指令FCB、FDB、FCC和通用型伪指令DC和DCB两类。

1.定义字节常量指令(FCB)

FCB(Form Constant Byte)指令用于定义一个或多个单字节存储常量,从指定的地址单元开始,定义若干个8位内存单元的内容。其语法为

        [标号:]  FCB  表达式

其中,标号是选择项,可有可无。FCB指令用来告诉汇编程序,从指定的地址单元开始定义若干个字节存储单元的内容。这些存储单元中的内容可以是8位二进制数、十进制数、十六进制数或表达式。当FCB指令定义多个单字节常量时,其间用逗号分隔。

FCB指令所确定的单元地址可以由下述两种方法之一来确定:

① 若FCB指令紧接着其他源程序,则由源程序最后一条指令的地址加上该指令的字节数来确定。

② 由ORG指令来规定首地址。

例如

                    NUM     EQU  10
                              ORG  $0100
                    TABLE:  FCB  $10,17,NUM+6

上述伪指令FCB创建了3个常量,依次存放在内存的3字节中,其中第2行是ORG语句,因此第1个字节的地址是$0100,存放的内容为$10。随后两个单元存放的内容分别为$11和$10。

2.定义双字节常量指令(FDB)

FDB(Form Double Byte)指令用于定义一个或多个双字节常量。功能是从指定地址开始,定义若干个16位数据,依次存放在存储单元中。其语法为

        [标号:]  FDB  表达式

其中,标号是选择项,可有可无。表达式为双字节常量。当FDB指令定义多个双字节常量时,其间用逗号分隔。例如

          ORG   $2000
      LIST:FDB  $C13,$1000,LIST+$FF,50

上述伪指令FDB创建4个双字节常量,依次存放在内存的8个单元中。第一行是ORG语句,所以第1个字节的地址为$2000。前两个表达式是常量$0C13和$1000,它们被依次存放在内存单元$2000~$2003中,高位在前、低位在后。

第三个表达式涉及标号LIST,它等于所在行的地址,即$2000,因此LIST+$FF便等于$20FF,存放于单元$2004~$2005中。最后一个表达式仅为单字节50($32),FDB指令将其扩展,使用内存的两个字节,即($2006)=$00,($2007)=$32。

3.定义字符常量指令(FCC)

FCC(Form Constant Character)指令用于定义ASCII字符串常量,一个单字节常量对应一个字符,依次存放在存储单元中。其语法为

        [标号:]  FCC  字符串

其中,标号是选择项,可有可无。字符串两端带有定界字符(Delimiting Character),典型的定界字符是斜杠(/)、单引号(‘’)或双引号(“”)。例如

              ORG   $1000
      STRING: FCC   /Hello/

本例中创建了5个字节的字符常量,第一个字节的地址为$1000,因为ORG语句定义了起点地址,每个字符的ASCII码依次按序存放在存储单元$1000~$1004中。

4.定义常量指令(DC)

DC(Define Constant)指令比以前M6800系列伪指令FCB、FDB和FCC更为通用,用来定义内存中的单字节、双字节或四字节常量,既可以是数值型的常量,也可以是ASCII字符型的常量。其语法为

          [标号:]  DC[.size]  表达式

其中,标号是选择项,可有可无。常量的长度由size定义,可以是B、D、L,分别表示1字节、2字节、4字节。如果指令中未给出长度修饰符size,则默认为B。指令中可以有多个表达式,中间用逗号分隔。表达式部分可以是字符或由编译器来计算的绝对或简单的浮动表达式。如果表达式为字符串,需用单引号或双引号在两端定界。例如

          Hello   DC.B  $'Hello' ,$04,0

以十六进制表示的存储器内容为

                    ASCII 字符           内存内容            内存地址
                        H                 48                 1000
                        e                  65                 1001
                        l                  6C                 1002
                        l                  6C                 1003
                        o                  32                 1004
                      EOT                04                 1005
                      NUL                00                 1006

$04是文本结束字符(End-Of-Text,EOT)的ASCII码,末端的$00是NULL(空),是C语言字符串的结束符。

对于不同的长度修饰符,DC指令的规则是:

● DC.B:对于数值表达式,分配1字节,对于字符串,每个ASCII码字符分配1字节,DC.B可以代替FCB;

● DC.W:对于数值表达式,分配2字节,对于ASCII码字符串,在2字节边界处右对齐,DC.W可以代替FDB和DCW;

● DC.L:对于数值表达式,分配4字节,对于ASCII码字符串,在4字节边界处右对齐,DC.L可以代替FQB和DCL。

例如,用DC.B指令

            相对地址    内存内容(十六进制)             伪指令
            000000        4142 4344        Label:    DC.B  "ABCDE"
            000004        45
            000005        0A0A 010A                DC.B   %1010,@12,1,$A

用DC.W指令

            相对地址    内存内容(十六进制)               伪指令
            000000        0041 4243        Label:    DC.W  "ABCDE"
            000004        4445
            000006        000A 000A                 DC.W  %1010,@12,1,$A
            00000A        0001 000A
            00000E        xxxx                     DC.W  Label

用DC.L指令

            相对地址    内存内容(十六进制)             伪指令
            000000        0000 0041        Label: DC.L  "ABCDE"
            000004        4243 4445
            000008        0000 000A              DC.L   %1010,@12,1,$A
            00000C        0000 000A
          000010        0000 0001
          000014        0000 000A
          000018        xxxx xxxx               DC.L  Label

5.定义常量块指令(DCB)

DCB(Define Constant Block)指令用于定义常量块,其语法为

          [标号:]   DCB[.size]   数量,数值表达式

该指令使编译器分配一个用规定的数值初始化的存储器块,可以很方便地将某段内存空间初始化为某个固定的数值,其中标号是选择项,可有可无;size可以是B、D和L,分别表示1字节、2字节和4字节。如果指令中未给出长度修饰符size,则默认为B。存储器块的长度为size×数量,数量的范围是1~4 096,不可重定位,也不能是未定义的、后面定义的或外部引用的数。每个分配的存储单元的数值是一个符号扩展的表达式数值,可以以后定义。

对于不同的长度修饰符size,DCB指令的规则是:

● DCB.B:给数值表达式分配1字节;

● DCB.W:给数值表达式分配2字节;

● DCB.L:给数值表达式分配4字节。

例如

      Label1:  DCB.B     3,$00
      Label2:  DCB.W    3,$FFFE
      Label3:  DCB.L     3,$FFFE

第1条伪指令将Label1开始的3字节全部清零,第2条伪指令将Label2开始的3字均初始化为$FFFE,第3条伪指令将Label3开始的3个双字均初始化为$0000FFFE。

3.5.4 分配变量指令

分配变量(Allocating Variables)指令主要用于在MCU的RAM区为变量预留一定的存储单元,包括以前M6800系列伪指令RMB、RMD、RMQ和通用型伪指令DS两类。

由于变量必须是可写的,因此当伪指令指示汇编程序保留空间以便存放变量时,汇编程序只是留出所需空间,并不存入任何值,即没有任何内容装入所保留的内存空间,也没有任何引用指向这些单元。上电时RAM中这些单元的内容是未知的,所以某些变量可能要求初始化。变量分配伪指令的另一功能是,将标号设置为等于所保留内存空间的地址,于是该标号将在程序中将作为变量名而使用。

1.保留内存字节指令(RMB)

保留内存字节伪指令(Reserve Memory Byte,RMB)用于给变量保留所需的内存字节空间,其语法是:

          [变量名]   RMB  数值表达式

其中,数值表达式规定所需保留的字节数。类似的还有RMD和RMQ指令,只不过数值表达式的每个数值分别表示2字节和4字节。例如

          VAR1 RMB  1
          VAR2 RMD  1
          VAR3 RMD  2

以上伪指令相当于分别定义了3个变量,其数据宽度分别是1字节、2字节和4字节。

2.定义空间指令(DS)

DS(Define Space)指令用于给变量预留内存空间。其语法为

          [标号:]   DS[.size]   数量表达式

其中,标号是选择项,可有可无;size可以是B、D和L,分别表示1字节、2字节和4字节。如果指令中未给出长度修饰符size,则默认为B。预留的内存空间长度为size×数量表达式,数量表达式的范围是1~4 096,不能是未定义的、前向或外部引用的数。

DS指令用于告诉汇编程序,从指定的地址单元开始保留由表达式指定的若干个字节内容空间作为备用空间。例如

      Counter:DS.B   2       ;在存储器中保留2个连续的字节,只能通过标号Counter访问
              DS.W  5       ;在存储器中保留5个连续的字

在汇编以后,将根据数量表达式的值与长度修饰符来决定从指定的地址开始留出多少个字节空间,保留的空间将由程序的其他部分决定它们的用途。其中,标号Counter引用所定义的存储区域的最低地址。

DS.B等价于RMB,DS.W等价于RMD,DS.L等价于RMQ。

利用DS指令可以在不同的段中为变量、常数和代码预留空间,例如

      DataSect:      SECTION         ;单独的变量段
      Counter:       DS  1           ;保留1字节
      ConstSect:     SECTION         ;单独的常量段
      InitialCounter:   DC.B  $F5        ;常量$F5
      CodeSect:      SECTION         ;代码段
      Main:         NOP             ;NOP指令

3.5.5 汇编控制指令

1.汇编结束指令(END)

END(End Assembly)是汇编语言源程序的结束标志,在END以后的程序,汇编程序都不予以处理。一个源程序只能有一个END指令。在同时包含有主程序和子程序的源程序中,也只能有一个END指令,并放到所有指令的最后,否则,就有一部分指令不能被汇编。其语法为

          [标号:]     END

其中,标号是选择项,可有可无。

2.包含其他文件文本指令(INCLUDE)

INCLUDE指令使被包含的文件插入到源输入流中。其语法为

          INCLUDE  文件规约

其中,文件规约不区分大小写,必须用引号(“ ”)在两端定界。例如

          INCLUDE  “...\LIBRARY\macros.inc”

3.5.6 符号链接指令

1.ABSENTRY指令

ABSENTRY指令用于定义程序的入口点,语法为

          ABSENTRY  标号

使用这个指令时,汇编程序的入口点被写在生成的绝对文件的头部。当编译器产生目标文件时,该指令被忽略。该指令仅仅影响调试器加载应用程序,它告诉编译器应该用哪一个初始PC,为了开始运行应用程序,初始化复位矢量。

例3.19 用ABSENTRY指令规定程序入口。

          ABSENTRY entry
          ORG      $FFFE
      reset:DC.W entry
          ORG     $70
      entry:NOP
          NOP
      main:LDS      #$1FFF
          NOP
      BRA:main

根据ABSENTRY指令,入口点将被设置成绝对文件头部的入口地址。

2.外部符号定义指令(XDEF)

XDEF指令用来定义外部变量,相当于C语言中的Global、Public,规定当前模块中定义的标号可以被其他模块或文件引用。其语法为

          XDEF [.size] 标号

其中,size可以是B、W和L,分别表示1字节、2字节和4字节。如果指令中未给出长度修饰符size,则默认为W。指令中允许有多个标号,其间用逗号分隔。

例3.20 用XDEF产生一个用于其他文件的变量。

            XDE F   Count,main
      Count:DS.W 2
      code: SECTION
      main: DC.B 1

变量Count可以被其他的C语言程序或外部模块调用。编译器区分大小写,即Count≠count。

3.外部符号引用指令(XREF)

XDEF指令用来指定在当前模块中引用但却是在其他模块中定义的符号。其语法为

          XDEF [.size] 符号

其中,size可以是B、W和L,分别表示1字节、2字节和4字节。如果指令中未给出长度修饰符size,则默认为W。例如

          XREF    OtherGlobal

其中,OtherGlobal在其他模块定义。符号列表和对应的32位值被传送给链接器。其他伪指令请参阅Freescale汇编语言手册HC(S)12 Assembler Manual。