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.

Status Register

Bit 7 6 5 4 3 2 1 0
Flag I T H S V N Z C

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 1

	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 0

	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 and clt instructions.

	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, sei() and 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.

Mnemonic Description
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.

Conclusion

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.

rjhcoding.com 2018