The Status Register
We've see references to the Status Register several times so far in these tutorials - first when referring to carry bits for instructions like adc and rol, and then with the branch instruction brne. It's finally time to look into the details of this extremely important register.
The status register is an 8-bit register in the microcontroller's I/O memory space. It contains 8 flags that are updated based on the results of the previous instruction.
The representation of the 8 bits in the status register are:
- Bit 0: Carry Flag
- Bit 1: Zero Flag
- Bit 2: Negative Flag
- Bit 3: Two's Complement Overflow Flag
- Bit 4: Sign Bit
- Bit 5: Half Carry Flag
- Bit 6: Bit Copy Storage
- Bit 7: Global Interrupt Enable
The operation and use of each bit is explained below.
The Carry Flag (C)
The Carry Flag will be set if a carry occured in an arithmetic or logical operation. For example
ldi r16,0xFF ; load r16 with 0xFF ldi r17,0xFF ; load r17 with 0xFF add r16,r17 ; carry will be set
Because the sum of 0xFF and 0xFF is greater than an 8-bit register can hold, the MSB of the sum is shifted into the Carry Flag of the Status Register.
Alternatively, the following will not set the carry flag.
ldi r16,0x01 ; load r16 with 0x01 ldi r17,0x02 ; load r17 with 0x02 add r16,r17 ; carry will not be set
The same applies to shifts. For example, the following logical shift left will set the carry flag since the most significant bit of the operand is
ldi r16,0b10000000 ; load 0b10000000 into r16 lsl r16 ; carry will be set
But this will not since the most significant bit of the operand is
ldi r16,0b00000000 ; load 0b00000000 into r16 lsl r16 ; carry will not be set
The Zero Flag (Z)
The Zero Flag will be set if the previous operation resulted in a zero result. For example
ldi r16,0x03 ; load r16 with 0x01 dec r16 ; zero flag not set (r16 = 0x02) dec r16 ; zero flag not set (r16 = 0x01) dec r16 ; zero flag set (r16 = 0x00)
The Negative Flag (N)
The Negative Flag is set whenever bit 7 of the previous operation is set (i.e. the result is negative)
ldi r16,1 ; load r16 with 0x01 sbi r16,2 ; negative flag set (result = -1 or 255)
Note that there really are no data types in assembly - a register just has a sequence of bits that you may interpret as you please. The negative flag may be set on a number that you are not treating as signed.
There are no data types in assembly. The negative flag does not indicate that a value in a register is signed - only that it would be a negative number if you were to treat it as such.
The Two's Complement Overflow Flag (V)
The Carry Flag is great when we are working with 8-bit unsigned values which can go from 0 to 255, but what about signed values which overflow outside of -128 to 127?
In this case we have a Two's Complement Overflow Flag which will be set when the result of an operation is too large to fit in a 7-bit number.
ldi r16,-128 ; load r16 with -128 dec r16 ; two's complement overflow set ldi r17,127 ; load r17 with 127 inc r17 ; two's complement overflow set
Again note that we decide whether a value is signed or not, not the microcontroller. The Two's Complement Overflow Flag can be ignored if we don't need it.
The Sign Flag (S)
The Sign Flag is an extra bit of logic that can be used to determine if the result of a previous operation is positive or negative. It is just the exclusive OR of the Negative and Two's Complement Overflow flags. It will be set if one or the other is set, but not both.
Consider subtracting 10 from -120. It is obvious that the result it -130, but if we attempt this
ldi r16,0x88 ; load r16 with 0x88 (-120) ldi r17,0x0A ; load r17 with 0x0A (10) sub r16,r17 ; subtract r17 from r16 (result = 0x7E = 126)
The result is not -130 since that value is too large for an 8-bit signed number. After the sub instruction, the Negative Flag will not be set since bit 7 of the result is cleared. However, the Sign Flag will be set, correctly indicating the result should be negative.
Consider addition instead
ldi r16,0x78 ; load r16 with 0x78 (120) ldi r17,0x0A ; load r17 with 0x0A (10) add r16,r17 ; add r17 to r16 (result = 0x82 = 130)
Here the Negative Flag will be set even though the result is clearly positive. However, because a two's complement overflow occured, the Sign Flag will not be set, correctly indicating the result is positive.
Using the Sign flag can give you a better idea of whether a result is positive or negative. It is very useful when creating alternate branches to handle cases like were shown above.
The Half Carry Flag (H)
The Half Carry Flag is set when an overflow occurs between the lower and upper 4-bits of a register.
ldi r16,0x0F ; load r16 with 0x0F inc r16 ; increment r16 (half carry set)
The half carry is most useful when dealing with Binary Coded Digits, where two digits are stored in the upper and lower nibbles (4-bits) of a register.
Bit Copy Storage (T)
The Bit Copy Storage, a.k.a T Flag, is not actually set by any arithmetic or logical operations. Instead, it is yours to set and clear based on how you see fit, using the
set ; set T flag clt ; clear T flag
The T Flag is a great way to indicate success or failure after a custom subroutine.
Global Interrupt Enable (I)
If you have ever worked with interrupts in C, you should be familiar with the Global Interrupt Enable flag. By setting this flag, interrupts are enabled in the system. By clearing it, interrupts will not disrupt your program flow. In C, you used special functions to set and clear the Global Interrupt Flag,
cli(). In AVR Assembly, the same is accomplished with the instructions sei and cli.
sei ; enable interrupts cli ; disable interrupts
Setting and Clearing Flags Manually
Just like the sei, cli, set and clt instructions we've already seen, there are corresponding instructions f or each of the other flags, shown in the table below.
|clc||clear carry flag|
|clh||clear half carry flag|
|cli||clear global interrupt enable flag|
|cln||clear negative flag|
|cls||clear signed flag|
|clt||clear T flag|
|clv||clear overflow flag|
|clz||clear zero flag|
|sec||set carry flag|
|seh||set half carry flag|
|sei||set global interrupt enable flag|
|sen||set negative flag|
|ses||set signed flag|
|set||set T flag|
|sev||set overflow flag|
|sez||set zero flag|
The instructions for clearing and setting flags are easy to remember - they are just the letters cl or se followed by the flag you are operating on.
Hopefully now you understand the function of the status register and how it is affected by instructions. We've seen before how the Status Register is used with multi-precision arithmetic (e.g. adding two 16-bit numbers), but now we will see an even more useful function with Conditional Branching.