I want to understand how does the microcontroller determine that the conditions provided to it in the input are true or false. eg:
if (someVariable > 50)
{
// do something here
}
Then how does Arduino determine if the value that I put in `somevariable' is larger than 50 (as apposed to less than or equal to 50)?
2 Answers 2
In general terms comparisons are usually performed, at the low level of assembly language, using subtraction.
In assembly language when you perform a subtraction there are flags in the status register that may or may not get set depending on what has happened. These include the Zero flag and the Carry flag.
If you are comparing A to B then you subtract B from A and see what happened by examining the flags.
For instance, if your variable contains 30 and you compare it to 50 you subtract 50 from 30. Because the result "passes through" zero the Carry flag is set. You can then do a "Jump if Carry Set" call.
If you should have 50 in your variable and you compare it to 50 the subtraction ends up with the Zero flag set since the result is exactly zero. It is then possible to do a "Jump if Zero Set" call.
If you have 60 in your variable and compare it to 50, the subtraction results in 10, which doesn't set either flag. That is actually slightly harder to work out (depending on architecture) since you are then wanting to compare two flags (Carry and Zero) and see if neither of them are set. So it is common for the compiler to optimize it so that the condition is reversed (a > b
is the same as !(a <= b)
for instance).
Of course, some architectures have actual comparison instructions (often CMP
) which compares two registers and sets or clears the different flags for you. For instance, the ATMega328P has the instructions:
CPSE
- Compare, Skip if EqualCP
- CompareCPC
- Compare with CarryCPI
- Compare Register with Immediate
CPI
would be the most logical choice in the example you give - compare the contents of a register (variable) with an "immediate" value (an "immediate" value is a constant value):
CPI $r3, 50
According to the instruction set manual the comparison is, surprise surprise, performed as a subtraction, and what happened during the subtraction sets the different flags. To make life simpler the instruction set has a number of branch instructions which look at the different flag combinations and jump to another address if they match:
- BRLT - Branch if the result of the previous comparison was A < B
- BRLE - Branch if the result of the previous comparison was A <= B
- BRGT - Branch if the result of the previous comparison was A> B
- BRGE - Branch if the result of the previous comparison was A>= B
- BREQ - Branch if the result of the previous comparison was A == B
- BRNE - Branch if the result of the previous comparison was A != B
There are more as well for other special comparisons. You can read up all about them in the AVR instruction set manual.
Just to complement Majenko's answer with an example, I tried the following:
volatile int someVariable;
void do_something_here();
void foo()
{
if (someVariable > 50)
{
do_something_here();
}
}
and the compiler generated the following assembly (comments mine):
foo:
lds r24, someVariable ; load variable into registers, LSB
lds r25, someVariable + 1 ; and MSB
sbiw r24, 51 ; subtract 51
brlt .L1 ; if (result < 0) goto .L1
jmp do_something_here ; do_something_here()
.L1:
ret ; return
The sbiw
instruction means "subtract immediate from word". It
subtracts a 6-bit immediate value from a 16-bit value contained in a
register pair (r25:r24 in this case). It is one of the very few 16-bit
instructions of this mostly 8-bit CPU.
As you see, the compiler did some optimization and changed the code to something looking more like:
if (!(someVariable - 51 < 0))
{
do_something_here();
}
It is also worth noting that the brlt
instruction (branch if lower
than) tests the sign flag of the status register, which is the exclusive
OR of the negative flag (result < 0) and the overflow flag. This way the
result of the comparison is correct even if the subtraction overflows.
Note also that this is only an example. The generated assembly would be
different if you use another type than int
(a wider or smaller or
unsigned integer), or an explicit constant greater than 62.
(someVariable > 50)
? Or something else?