图1-10 显示了HCS08 的 5 个CPU寄存器。HCS08 的CPU寄存器和M68HC08 系列完全一样,包括一个 8 位的累加器A(Accumulator),一个 16 位的变址寄存器H:X(由两个独立的 8 位寄存器H(高半段)和 8 位寄存器X(低半段)组成),一个 16 位的堆栈指示器(SP,Stack Point),一个 16 位的程序计数器(PC,Program Counter)和一个 8 位的条件码寄存器(CCR,Condition Code Register)。CCR具有 5 个处理器状态位(V、H、N、Z和C)和 1 个全局中断屏蔽控制位(I)。CPU寄存器存在于CPU内部,不在MCU存储器中,不占用存储器空间。
累加器A是一个通用的 8 位寄存器,是HCS08 系列MCU主要的数据寄存器,用于存储算术逻辑单元(ALU,Arithmetic Logic Unit)的输入参数或运算结果,通过各种寻址方式,A也能够和存储器、其余CPU寄存器交换数据。
数据可以通过LDA指令从存储器或通过PULA指令从堆栈读入到A。数据通过存数指令STA存入存储器中或通过堆栈指令PSHA将A进栈到累加器A。多种寻址方式给存储器的存取指令带来了更大的灵活性。传送指令可以将数据从累加器A传送到寄存器X中(TAX),或者从寄存器X传送到累加器A中(TXA),或者从累加器A传送到CCR中(TAP),或者从CCR传送到累加器A中(TPA)。TAP和TPA中的P表示处理机状态。A半交换指令(NSA)使A中数据的高 4 位和低 4 位对调。
通常对A中的值通过ADD、SUB、ASLA、RORA、INCA、DECA、AND、ORA、EOR等指令执行算术、移位和逻辑操作。其中有些指令如INCA和ASLA,对A中的内容进行操作后放回A中;另外一种情况如ADD和AND,它们有两个操作数,一个操作数在A中,另一个操作数在存储器中。算术和逻辑操作的结果都是替代累加器内的值。
复位对A中的内容没有影响。
图 1-10 HCS08 的 5 个CPU寄存器
16 位的寄存器H:X可以分成两个独立的 8 位寄存器H和X。H:X通常作为一个 16 位地址指针来用,其中H存放地址的高位字节,X存放地址的低位字节。所有的变址寻址指令都是使用H:X中的 16 位地址值作为变址指针,为了和早期的M68HC08 系列兼容,也有一些指令仅仅使用了X中的地 8 位地址值。
很多指令把X作为第二个 8 位通用寄存器使用,这样X也可用于暂存 8 位数据。X可以被清零、减 1、加 1、求补、取反、逻辑移位、循环移位。数据传送指令可以使得数据在A和X之间灵活传送。
为了与早期的M08HC05 系列兼容,中断不在堆栈中保存 H寄存器。一种好的做法是在中断服务子程序中将PSHH指令作为子程序的第一个指令(保护H)并且在中断程序返回指令RTI之前加上PULH指令作为中断子程序的最后一个指令(恢复H)。如果确信在中断服务子程序中,H不会被用到并且没有AIX指令或没有变址寻址方式中的自增指令,中断子程序中可以不用这两条指令。如果能容许两个字节的额外程序空间、一字节的额外堆栈临时空间和五个总线周期的运行开销,那么推荐的安全做法就是把PSHH和PULH指令对包含到中断服务子程序中作为一种习惯。
为了和早期的M68HC08 系列兼容,H在复位之后初始化为 0x00。复位对X中的值没有影响。
HCS08 系列MCU的堆栈处于RAM空间中,当CPU执行子程序绝对调用(JSR)或相对调用(BSR)指令,或者CPU响应中断请求时,它会自动保存返回地址到堆栈中。当子程序最后执行返回指令(RTS或RTI)时,该返回地址会自动从堆栈中恢复,并由此从先前暂停的指令处继续执行程序。
16 位的SP指示了位于RAM区的后进先出型(LIFO,Last-In-First-Out)堆栈区域的下一个可用空间的位置。SP总是指向堆栈中下一个可用位置。当一个数值要进栈时,它会被写到 SP指向的地址中,随后SP会自动减少而指向下一个可用位置。当一个数值要出栈时,SP首先会自增以指向堆栈中最近进栈的数据,然后从刚被SP指向的地址中读出数据。需要注意的是 SP 指向的数据,在出栈的过程中不会被改变。如果SP指向当前内存的下一个位置,即指向之前最近存储的数据,当新的数据进栈时,会覆盖该位置的数据。
由于堆栈处于RAM区,用户程序中的变量也存储在RAM区,因此要避免堆栈空间和用户变量空间的冲突。通常做法是把用户变量空间定义到整个RAM范围的首段区域,堆栈空间定位到整个RAM范围的末段区域。
为了与早期的M08HC05 系列兼容,在复位时SP的初值为$00FF。但HCS08 应用程序几乎不会把堆栈顶部设到$00FF,因为$00~$FF范围的 0 页RAM区域不仅分布着MCU的众多内部模块寄存器,而且可以通过直接寻址方式快速访问,非常适于那些要被经常访问变量的存储。
在HCS08 程序中,一般初始化SP的值指向片内RAM空间的末字节,以便释放出 0页地址空间的一些存储单元作为通用存储区使用。通常,下面的两个指令序列被包含进复位初始化的头几个指令中。
其中,RAMEnd为符号常量,在MCU的头文件中定义,见附录B,在MC9S08AW60.inc头文件中RAMEnd定义为$086F。上述两条指令执行完之后SP的值为$086F。为了和早期的M68HC05 系列MCU保持兼容,RSP(复位堆栈)指令仍被保留,但在HCS08 中很少使用,因为这条指令仅影响SP的低位字节,使得SP的值为$00FF。
当CPU响应请求中断时,MCU自动地先将CPU寄存器的当前内容保存在堆栈中,在完成中断服务子程序后,再自动地将它们恢复以继续执行之前的程序。响应和退出中断时,CPU寄存器自动入栈、出栈的顺序如图 1-11 所示。在中断前,SP指向堆栈中的下一个可用位置。每个数据被保存到经SP指定的堆栈存储单元中并且SP会自动减 1 以指向堆栈中的下一个可用位置。结束中断时,中断服务子程序中最末指令RTI会自动以相反的顺序出栈来恢复CPU寄存器。关于中断的更多内容请查阅 3.4.6 节的堆栈相关指令和 5.2 节的中断描述。
为了与早期的M08HC05 系列兼容,HCS08 的中断并未自动入栈保存H寄存器。好的方法是在用户中断服务子程序将PSHH指令作为开头指令并且在服务子程序结束指令RTI之前将PULH指令作为最后一条指令。
SP与立即数加法指令AIS可以向SP加上一个 8 位的符号立即数,可用于堆栈位置的调整,为局部变量在堆栈中分配和释放空间。该技术在C语言和汇编语言中很有用。下面的两行代码示范了如何在堆栈中为局部变量分配和释放空间。
SP还可以用于堆栈寻址寄存器,寻址堆栈空间的任何存储单元。与 SP相关的变址寻址方式有 8 位和 16 位偏移量堆栈寻址方式(SP1 和SP2),它们允许许多指令去直接访问存储在堆栈中的信息,这对C语言的编译效率很重要,而且同样适用于汇编语言。
图 1-11 中断堆栈结构
程序计数器是一个 16 位的寄存器,用于存放下一个预取指令或操作数的地址。
通常,每当一个指令码或操作数被预取后,程序计数器中的地址会自动顺序增加,指向下一条指令的地址。但当遇到转移、分支、中断、返回时的操作就不一样,而是会用某个特定的地址去装载程序计数器,具体请参考相关指令描述。
复位时,程序计数器PC自动装入位于$FFFE和$FFFF中的复位向量值,这就意味着CPU退出复位状态后会从复位向量地址处开始执行程序,亦即$FFFE和$FFFF中写入的是首条指令的地址。
注意: 不要误解为PC寄存器的复位初始值就是$FFFE,倘若如此,则$FFFE中的内容应为CPU执行的首条指令的操作码,这显然是错误的。
如图 1-12 所示,条件码寄存器是一个 8 位寄存器,包括 1 个用于MCU全局中断允许/禁止的控制位和 5 个用于表征指令执行完后结果状态的标志位。剩余的第 5 位和第 6 位永远为逻辑 1。
I位是中断屏蔽控制位,MCU复位后的默认值为 1,这使得中断被屏蔽直到用户初始化I位为 0。对I位清零的初始化操作通常在栈指针初始化之后进行。在CCR的 6 个位中,I位也是复位后唯一需要初始化的位。其他 5 个状态位(V、H、N、Z和C)在复位后是未知的,复位后无须强制这些位为特定的值,一旦执行了一个影响它们的指令后,这 5 个状态位的值会发生相应的改变。条件转移指令就是根据状态位的值来决定是否转移的。简单条件转移指令(BCC、BCS、BNE、BEQ、BHCC、BHCS、BMC、BMS、BPL和BMI)的转移取决于CCR中单一位的状态,而其他转移指令则被两三个更复杂的CCR位控制。例如,若布尔表达式[(Z) | (N⊕V)]为真,则将产生小于等于转移(BLE)。
图 1-12 CCR寄存器
中断屏蔽位控制全局中断屏蔽,当 I=1 时表示禁止MCU内部所有模块的可屏蔽中断。MCU复位使I为 1,即禁止中断直到应用程序初始化I位为 0。对I位清零的初始化操作通常在栈指针初始化之后进行,如果在堆栈指针被初始化前允许中断,有可能会使CPU寄存器内容被存储于不适合的内存单元中。用户程序可以用SEI指令置I位为 1 或用CLI指令清I位为 0。
在响应中断时,CCR包括I位在内会按照图 1-12 所示的顺序被自动入栈保存,之后I位被自动置 1 以阻止不必要的中断嵌套。在中断服务子程序中包含CLI指令可允许中断嵌套,尽管中断嵌套能在可控制的方式下运作,但这通常不被推荐,因为它可能导致微小的系统错误,这些错误往往难以寻找和纠正。当执行中断中的RTI指令时,CCR的值会被在指令的第一个循环周期从堆栈中自动恢复。
WAIT和STOP指令会自动对I位清零,以保证中断能将CPU从WAIT或STOP模式中唤醒。这些指令以前并非设计成这样,因此那时在执行WAIT或STOP指令之前必须用CLI指令清零I位。在HCS08 指令系统中这些指令自动清零I位,节省了单独的CLI指令所需的程序空间和执行时间,而且在I被清零后,但在WAIT或STOP指令之前,是不会产生中断的。
当二进制补码运算结果溢出时该位为 1。V位的一个普遍作用是在执行CMP、CPHX、CPX、SBC或SUB指令后支持符号跳转指令(BLT、BLE、BGE和BGT)。关于指令中影响CCR位详述,请参考附录A的指令集。DAA指令也能改变V位,因此不要在 DAA 指令后,但还没有执行比较或减法指令时,尝试符号跳转。
半进位标志位多用于BCD码的操作。H位仅在少数的几个指令中受影响,加法指令ADD和ADC是仅有的影响H位的指令。如果相加结果中的低 4 位向上产生进位,即D3 向D4 有进位,则CPU将半进位标志H置 1,DAA指令能据此将结果转换成有效的BCD结果。
若CPU运算结果的最高位为 1,则N位被自动置为 1。若把结果看成一个符号数,则N为 1 时表示结果为负,故N被称为负标志位。如果一个操作数包含 16 位数(如LDHX或CPHX)并且结果的第 15 位为 1,则N位也被置为 1。N既可以被简单的转移指令,如正转移(BPL)和负转移(BMI)利用,也可被利用于符号转移指令,如BLT、BLE、BGE和BGT等。所有的取数、存数、转移、算术、逻辑、交换和循环指令都将使得N位更新。
若CPU运算结果为 0,则此标志位被置为 1。所有的取数、存数、转移、算术、逻辑、交换和循环指令计算的结果都会影响Z位的状态。
在加法运行后,如果源操作数同时大于等于$80 或者操作数中的一个大于等于$80,但其结果却比$80 小,那么C位将被置为 1,表明有进位。减法或比较运算时,若无符号的被减数小于无符号的减数,那么计算之后C位被置为 1 表明有借位。C位可被用于简单转移指令,如C为 0 则转移(BCC)和C为 1 则转移BCS,也可被用于无符号转移指令,如BLO、BLS、BHS和BHI。加、减、变换和循环指令的运算结果会影响C位状态。在除指令后,如果执行非法除零操作,那么C位也将置 1。C位状态除了被程序指令自动影响外,还可以使用SEC或CLC指令直接对C标志位单独置 1 或清零。
对于CCR中各个位的操作,除了可以使用SEI/CLI指令对I位单独置 1/清零,用SEC/CLC指令对C标志位单独置 1/清零外,还可以使用TPA指令把CCR的值送到累加器A中,或者使用TAP指令把累加器A中的值直接传送到CCR中。有关这些指令的详细描述请参看第 3 章或附录A中的内容。
表 1-12 概述了CCR中各个位的功能,对于每条指令对CCR中各个位的影响,请参考附录A中的指令表。
表 1-12 条件码寄存器各位的定义
(续表)