AVR汇编(六):分支指令
分支指令用于改变程序的执行流,分为无条件分支和条件分支两类。
无条件分支指令#
JMP
#
JMP
指令用于无条件跳转,类似于C中的 goto
关键字, JMP
指令的跳转范围为[0, 4M-1]字。
RJMP
指令用于相对跳转,跳转范围为当前位置[-2K, 2K-1]字。
IJMP
指令用于间接跳转,跳转的目的地址存放在 Z
寄存器中(记住单位是字)。
例如:
JMP f2 ; 跳转到f2
f1:
RJMP f3 ; 跳转到f3
f2:
LDI ZL, lo8(f1)
LDI ZH, hi8(f1) ; Z = f1
CLC
ROR ZH
ROR ZL ; Z = Z >> 1
IJMP ; 跳转到f1
f3:
RJMP f2 ; 跳转到f2
注意:实测在GNU汇编下, IJMP
指令中不能直接把标签赋值给 Z
寄存器,因为标签表示的地址的单位是字节,而 Z
寄存器中存放的应该是字地址,所以要将标签右移一位传给 Z
寄存器。而 JMP
指令和 RJMP
指令则可以直接传标签。
CALL
/ RET
#
CALL
指令用于子程序调用,和 JMP
指令一样,也可以实现程序跳转,但是 CALL
指令在跳转之前会将下一条指令的地址(返回地址)压入栈中。 CALL
指令的跳转范围为[0, 64K-1]字。
RCALL
指令用于相对子程序调用,跳转范围为当前位置[-2K, 2K-1]字。
ICALL
指令用于间接子程序调用,子程序的地址存放在 Z
寄存器中(记住单位是字)。
RET
指令用于子程序返回,先将返回地址从栈中弹出,然后进行跳转。
RETI
指令用于中断子程序返回,和 RET
指令不同的是,它还会设置全局中断使能位 I
。
例如:
CALL f1 ; 调用f1子程序
RCALL f1 ; 调用f1子程序
LDI ZL, lo8(f1)
LDI ZH, hi8(f1) ; Z = f1
CLC
ROR ZH
ROR ZL ; Z = Z >> 1
ICALL ; 调用f1子程序
f1:
...
RET ; 子程序返回
XXX_IRQHandler:
...
RETI ; 中断子程序返回
条件分支指令#
CP
#
CP
指令用于比较,实际上就是只影响标志位而不保存结果的减法操作。后缀带 C
表示带进位比较,后缀带 I
表示与立即数比较,后缀带 SE
表示如果相等,则跳过下一条指令。
例如:
LDI R16, 0x01
LDI R17, 0x02
CP R16, R17
BRLT f1 ; 1 < 2,跳转到f1
RJMP f3 ; 不会执行
f1:
CPI R16, 0x01
BREQ f2 ; 1 == 1,跳转到f2
RJMP f3 ; 不会执行
f2:
LDI R17, 0x01
CPSE R16, R17 ; 1 == 1,跳过下一条指令
RJMP f3 ; 不会执行
SEC
CPC R16, R17
BRLT f3 ; 1 < 1 + C(1),跳转到f3
RJMP f1 ; 不会执行
f3:
RJMP f3
SBxx
#
形如 SBxx
的指令根据寄存器中的某一位来选择跳过执行下一条指令, SBRC
/ SBRS
指令根据的是通用寄存器中的位的清除/设置状态,而 SBIC
/ SBIS
指令根据的是I/O寄存器中的。
例如:
f1:
LDI R16, 0xAA
SBRS R16, 1 ; R16.1 == 1,跳过下一条指令
RJMP f1 ; 不会执行
SBIC PINB, 2 ; 如果PB2输入为低电平,跳过下一条指令
RJMP f1
BRxx
#
形如 BRxx
的指令用于根据条件改变程序执行流,支持的条件具体见下表:
BRxx
类的指令一般和 CP
或 SUB
指令配合使用。
例如:
f1:
LDI R16, 0X01
LDI R17, 0X02
CP R16, R17
BRLO f2 ; 0x01 < 0x02,跳转到f2
RJMP f1 ; 不会执行
f2:
CPI R16, 0x01
BRSH f3 ; 0x01 == 0x01,跳转到f3
RJMP f1 ; 不会执行
f3:
RJMP f3