疑难指令解析
一、累加器写成A与ACC有何区别?
累加器写成A是寄存器寻址,写成ACC是直接寻址。对于寄存器寻址,操作数隐含在指令机器码中,不独自占有1个字节的存储空间。对于直接寻址,操作数在指令机器码中独自占用1个字节的存储空间。所以,累加器写成A,不增加机器码长度;写成ACC,机器码长度要加1;进位位写成C和CY亦然。B寄存器在MUL AB和DIV AB指令中是寄存器寻址,在其余指令(如MOV B, #1)中是直接寻址。
例如:指令INC A编译后对应的机器码是1个字节:04H(操作数A隐含在指令机器码中),指令INC ACC编译后对应的机器码是2个字节:05H、E0H,其中E0H是累加器A的地址(操作数A在指令机器码中独自占用1个字节)。
有些指令,必须写成A的,就不能写成ACC。例如:MOVX A,@DPTR,ADD A,#data等。
有些指令,必须写成ACC的,就不能写成A。例如:PUSH ACC,POP ACC等。
二、MOVX A, @Ri和MOVX @Ri, A的寻址范围
MOVX A, @Ri和MOVX @Ri, A用寄存器间接寻址方式读、写片外RAM区,Ri只能是R0或R1,取值是0~255。
这两条指令访问的是分页寻址的片外RAM。这是什么意思呢?图1是片外RAM区分页示意,将64KB的片外RAM区分成256页,每一页的大小是256B。单片机使用MOVX A, @Ri和MOVX @Ri, A访问片外RAM时,具体访问哪一页,由P2口决定;访问页内的哪个单元,由Ri决定。
例如,(P2)=00H时,MOVX A, @Ri和MOVX @Ri, A访问的是片外RAM区第0页0000H~00FFH这256B单元;(P2)=03H时,MOVX A, @Ri和MOVX @Ri, A访问的是片外RAM区第3页0300H~03FFH这256B单元。

图1 片外RAM区分页示意
三、查表指令MOVC A, @A+PC和MOVC A, @A+DPTR
图1所示访问程序存储器的指令常用于查表操作,其中MOVC A, @A+PC是近程查表指令,MOVC A, @A+DPTR是远程查表指令。近程查表指令,表格离MOVC A, @A+PC语句下一条指令的距离不能太远(不能超过255字节)。近程查表子程序偏移量的计算比较复杂,一般都不使用,而改用远程查表指令。远程查表指令,表格离指令MOVC A, @A+DPTR的距离不受限制,可以放在64KB程序存储器的任意位置。
图2所示的求平方数近程查表子程序SQUARE1,执行MOVC A, @A+PC指令时,PC不是指向表首,而是指向下一条指令RET的首地址,RET指令机器码长度为1个字节,因此RET指令首地址距离TAB1表首地址为1,所以子程序SQUARE1要将偏移量加1。同理,如果在RET指令的上一行再加1条NOP指令,则要将偏移量加2。
图2所示的求平方数远程查表子程序SQUARE2,DPTR直接指向TAB2的表首,偏移量的计算比较简单,且表格离指令MOVC A, @A+DPTR的距离不受限制。

图2 查表指令
四、进栈指令PUSH direct和出栈指令POP direct
进栈指令PUSH direct和出栈指令POP direct后面的操作数,必须采用直接寻址。所以,PUSH A和POP A指令,在有的软件(如Keil uVision)中编译是通不过的,必须写成PUSH ACC和POP ACC。Wave(伟福)软件中,PUSH A和POP A指令编译可以通过。
关于出栈操作指令POP direct,很多教材采用图3所示的错误注释。假设执行POP SP指令以前,片内RAM区(30H)=58H,(SP)=30H,如果采用错误注释,执行完POP SP指令,(SP)=57H;如果采用正确注释,(SP)=58H。用Keil uVision软件仿真,结果应该是(SP)=58H。

图3 POP指令注释
五、绝对转移指令AJMP addr11的跳转范围
假设程序存储器容量有64KB,执行AJMP addr11指令时,整个程序存储器空间被分成32页,每页2KB。AJMP指令下一条语句的首地址(即PC+2)落在哪一页,就只能在该页的2KB范围内跳转;其中,PC15~PC11(00000B~11111B)称为页面地址,指令代码中低11位构成目标转移地址,称为页内地址。如果PC刚好在某页区域的边沿,哪怕离另一页咫尺之遥,也不能越雷池半步。例如,假设AJMP指令在第2页,其首地址是1FFEH;那么执行该指令时,(PC)=2000H,PC落在地3页,AJMP只能在第3页的2KB范围内跳转,即跳转范围是2000H~2FFFH;此时尽管它离1FFFH单元只有一步之遥,也不能跳转至该位置。
同理,ACALL addr11只能调用跟PC在同一2KB页内的子函数,与AJMP addr11跳转范围原理类似,不再赘述。

图4 AJMP指令跳转范围
六、LJMP addr16、AJMP addr11和SJMP rel的跳转范围
图5是无条件转移指令LJMP addr16、AJMP addr11和SJMP rel的跳转范围示意,它们的区别在于:LJMP跳转范围是64KB(整个程序存储器ROM),AJMP跳转范围是2KB,SJMP跳转范围是256B(8位补码表示的范围是-128~+127,故往前可跳127B,往后可跳128B)。
一般情况下,LJMP可以替代AJMP和SJMP,但AJMP不能替代所有SJMP。在搞不懂的情况下,干脆就使用LJMP。另外,即使错误使用了跳转指令,编译器也会提示出错:Target out of range.(目标超出了范围)。
为什么AJMP不能取代所有的SJMP呢?AJMP的跳转范围是2KB,虽然远大于SJMP的256B,但从图3分析可知,哪怕只有一步之遥,AJMP也不能跨页跳转,而此时用SJMP却不会有错误。所以AJMP能取代大部分SJMP,但不能取代所有SJMP。

图5 无跳转转移指令的跳转范围
七、如何使用CJNE指令比较两个数的大小?
汇编语言里面,没有直接比较两个数大小的指令。但可以通过将CJNE和JC(或JNC)指令配合,比较两个数的大小。因为执行CJNE指令时,前面的数≥后面的数,(C)=0;前面的数<后面的数,(C)=1。

例如:下面的子程序COMPARE,可以实现A>100时,将B赋值为1;A≤100时,将B赋值为2。

八、为什么中断返回指令RETI不能用RET替代?
RET指令是将堆栈栈顶内容(2字节,调用时保存的当前PC值)弹出给PC,实现返回。RETI指令除具有RET指令实现程序返回的功能外,还有对中断优先级状态触发器的清零作用。两者不能互换使用。
原因:CPU响应中断后,由硬件自动根据中断请求源的优先级高低,使相应的优先级状态触发器置1;此时,同级别或者低级别的中断将不能被响应。执行中断返回指令RETI时,将中断优先级状态触发器的清零;此时,同级别或者低级别的中断才能被响应。
下面的程序代码,如果将RETI用RET替代,中断第一次被响应后可以正常返回,但中断优先级状态触发器不会被清零,此时T0的后续中断将不能被响应。
程序实现的功能:单片机时钟频率fosc=12MHz,T0工作于方式1,产生50ms的定时中断。在中断里面计数20次,就是1s。每隔1s,将P1.0引脚的电平取反一次。

★温馨提示:本部分内容选自《单片机学习与实践教程》,朱向庆编著,北京邮电大学出版社,2018年出版,引用请注明出处。