Conditional Branching
The ability to control program flow is one of the most fundamental and important aspects of assembly language programming. Without it, microcontrollers would just sequentially execute the instructions in their memory until they ran out. Writing a non-trivial program this way would be extremely difficult if not impossible.
Controlling program flow means we instruct the microcontroller to jump from one address in code to another - referred to as branching. This can be done with three distinct methods:
- Conditional Branching
- Jumps
- Subroutine Calling
Conditional Branching allows you to test a condition (e.g. whether a bit is set or a register equals a particular value) and branch to a new location if the condition is true. If it is not, the microcontroller will continue to the next instruction.
Jumping is a form of Unconditional Braching. It redirects program flow regardless of conditions. Jumping allows you to simply jump to a new address in program memory.
Subroutine Calling is another form of Unconditional Braching. It works similarly to Jumping in that it causes the microcontroller to jump to a new address in program memory. However, the microcontroller will remember the address the subroutine call was made from, and upon reaching the end of the subroutine, return to the instruction immediately following the subroutine call.
In this tutorial, we will look specifically at Conditional Branching, so named as it creates multiple paths through a program.
Setting Flags in the Status Register
Most of the Conditional Branching instructions operate based on flags in the Status Register. As we saw previously, flags are automatically set in the Status Register after performing any operation. Thus, Conditional Branching instructions can often be placed organically in a program without other supporting instructions.
However, sometimes a condition needs to be tested on a register that was not affected by the previous operation. In this case, we have a few instructions at our disposal, shown below.
Mnemonic | Description |
---|---|
cp | compare |
cpc | compare with carry |
cpi | compare with immediate |
tst | test for zero or minus |
The cp instruction allows us to compare two registers.
ldi r16,0x01 ; load r16 with 0x01
ldi r17,0x02 ; load r17 with 0x02
cp r16,r17 ; compare r16 and r17
What this instruction really does is subtract the second register from the first to set flags in the Status Register. However, cp leaves the registers themselves unmodified. Using the flags in the Status Register we can determine which register is larger or if they have the same value.
In the example above, the Carry Flag will be set in the Status Register after the compare, since we are essentially computing 0x01 - 0x02. Because of this we can conclude that the second operand is larger than the first.
Alternatively, in the following we check if two registers are equal in value.
ldi r16,0x01 ; load r16 with 0x01
ldi r17,0x01 ; load r17 with 0x01
cp r16,r17 ; compare r16 and r17
After the compare, the Zero Flag will be set since 0x01 - 0x01 = 0x00. Thus, we can conclude the two operands are equal.
If more than one byte must be compared, the cpc instruction can be used which will include a carry bit in the comparison. For example, to compare the 16-bit numbers 0xAA01 and 0xAA02
ldi r16,0x01 ; load r16 with 0x01
ldi r17,0xAA ; load r17 with 0xAA
ldi r18,0x02 ; load r18 with 0x02
ldi r19,0xAA ; load r19 with 0xAA
cp r16,r18 ; compare r16 and r18
cpc r17,r19 ; compare r19 and r17 with carry
After the first compare, the Carry Flag will be set, indicating that the first number is less than the second.
In the second compare, even though the high bytes of both numbers are equal, the Carry Flag will still be set, correctly indicating 0xAA02 is greater than 0xAA01. If the cp instruction had been used instead, the Status Register would incorrectly indicate the two numbers are equal.
For registers 16 through 31, we have an immediate comparison, cpi.
ldi r16,0x05 ; load r16 with 0x05
cpi r16,0x05 ; compare r16 with 0x05
In the above example the Zero Flag will be set, indicating r16 is equal to the constant we are comparing it against.
Lastly, the tst instruction can be used on a single register to check if it is negative or zero. tst will set both the Sign and Negative Flag if bit 7 of the register is set, or set the Zero Flag if the register is zero. tst will always clear the Two's Complement Overflow Flag.
ldi r16,0x80 ; load r16 with 0x80
tst r16 ; test r16
In the above, the Sign and Negative Flag will be set since bit 7 of 0x80 is 1.
Conditional Branching
Once flags are set in the Status Register, there are a variety of Conditional Branching instructions that can control program flow.
Mnemonic | Description |
---|---|
brbs | branch if status flag set |
brbc | branch if status flag cleared |
breq | branch if equal |
brne | branch if not equal |
brcs | branch if carry set |
brcc | branch if carry cleared |
brsh | branch if same or higher |
brlo | branch if lower |
brmi | branch if minus |
brpl | branch if plus |
brge | branch if greater or equal, signed |
brlt | branch if less than, signed |
brhs | branch if half carry set |
brhc | branch if half carry cleared |
brts | branch if t flag set |
brts | branch if t flag cleared |
brvs | branch if overflow flag set |
brvc | branch if overflow flag cleared |
brie | branch if interrupt enabled |
brid | branch if interrupt disabled |
The syntax for a branch instruction is simply
branch label
If the condition specified by the branch instruction is satisfied, it will jump to the address marked by the label. Otherwise, it will continue to the next instruction.
For example, the following code will compare r16 and r17 and branch to if they are equal.
cp r16,r17 ; compare r16 and r17
...
breq lbl ; branch if r16 == r17
...
lbl:
Some of the Conditional Branching instructions may seem redundant, but that is only because there is an unsigned and signed version. For example, brsh - branch if same or higher, is used with unsigned values
ldi r16,0x9C ; load r16 with 0x9C (156 unsigned)
ldi r17,0x0F ; load r17 with 0x0F (15 unsigned)
cp r16,r17 ; compare r16 and r17
...
brsh lbl ; r16 is >= r17, code will branch
...
lbl:
The instruction brge - branch if greater or equal, performs the same type of comparison but works with signed values.
ldi r16,0x9C ; load r16 with 0x9C (-100 signed)
ldi r17,0x0F ; load r17 with 0x0F (15 signed)
cp r16,r17 ; compare r16 and r17
...
brsh lbl ; r16 is not >= r17, code will not branch
...
lbl:
You may notice there are instructions for checking if a register is greater than or equal to another register, and corresponding instructions to check if a register is less than another register. However, there are no less than or equal comparisons. This is because they are not needed. Less than or equal can be checked by simply swapping the order of the operands with a greater than or equal instruction. For example
ldi r16,0x9C ; load r16 with 0x9C (156 unsigned)
ldi r17,0x0F ; load r17 with 0x0F (15 unsigned)
cp r17,r16 ; compare r17 and r16
...
brsh lbl ; r16 is not <= r17, code will not branch
...
lbl:
I won't belabor the usage of the above instructions as their descriptions make it fairly obvious. Instead, we will look at how to create familiar structures from high level programming languages using Conditional Branches.
While Loop
A While Loop in assembly can be constructed as follows
loop: cpi r16,100 ; compare r16 and 100
...
brsh next ; branch if r16 >= 100
rjmp loop ; repeat loop
next:
In the above, the loop will check if r16 is less than 100 at the beginning of each iteration. If it is not, it will execute whatever code is in the loop, then jump back to the beginning and compare again. If r16 is greater than or equal to 100, the loop will break and the program will jump to the label.
This is equivalent to the following C code, assuming r16 is stored in the variable x.
while(x < 100)
{
...
}
Do While Loop
Alternatively, a Do While Loop can be constructed in assembly as
loop: ...
cpi r16,0 ; check if r16 is zero
brne loop ; repeat loop if r16 is not 0
Note that in a Do While Loop we do not need an rjmp instruction. The Conditional Branch is used to repeat the loop, and the program simply continues to the next instruction when the condition is not met.
This is equivalent to the following C code, assuming r16 is stored in the variable x.
do
{
...
}
while(x != 0)
For Loop
An assembly For Loop is shown below
clr r16 ; clear r16
loop: cpi r16,11 ; compare r16 to 11
breq next ; break loop if equal
...
inc r16 ; increment r16
rjmp loop ; repeat loop
next:
In this example, our For Loop index is stored in r16 and initialized to 0. r16 is compared to a count of 11 and will break out of the loop when it is equal. Note that this is our way of checking if r16 is less than or equal to 10.
This is equivalent to the following C code, assuming r16 is stored in the variable x.
for(i = 0; i <= 10; i++)
{
...
}
"Skip" Branching
In addition to the Conditional Branching instructions above which will branch to a label when a condition is true, there are also a few instructions that will simply "skip" an instruction if a condition is set. They are shown in the table below.
Mnemonic | Description |
---|---|
sbrc | skip if bit in register cleared |
sbrs | skip if bit in register set |
sbic | skip if bit in i/o register cleared |
sbis | skip if bit in i/o register set |
The sbrc and sbrs allow you to "skip" the following instruction if a bit in a general purpose working register is set or cleared. For example, the following will create a loop that will run until bit 3 of r0 is set.
loop: ... ; loop code
sbrs r0,3 ; break loop if bit 3 of r0 is 0
rjmp loop ; repeat loop
Additionally, sbic and sbis can be used in the same way for I/O Regisers. For example, the following will create a loop that will run until PINB3 is cleared.
loop: ... ; loop code
sbic PINB,3 ; break loop when PINB3 is 0
rjmp loop ; repeat loop
Conclusion
There you have it, Conditional Branching in AVR Assembly. Understanding the above is a significant step forward in writing useful assembly language programs.
إرسال تعليق