Almost every sketch uses mathematical operations to manipulate the value of variables. This chapter provides a brief overview of the most common mathematical operations. If you are already familiar with C or C++, you may be tempted to skip this chapter, but we suggest you review it because there are some idioms used by Arduino programmers that you may encounter even if you don’t use them yourself (such as the use of bitSet
to change the value of a bit). If you are new to C and C++, see one of the C reference books mentioned in the Preface.
Addition, subtraction, and multiplication for integers work much as you expect.
Integer division truncates the fractional remainder in the division example shown in this recipe’s Solution; myValue
will equal 1 after the division (see Recipe 2.3 if your application requires fractional results):
int
value
=
1
+
2
*
3
+
4
;
Compound statements, such as the preceding statement, may appear ambiguous, but the precedence (order) of every operator is well defined. Multiplication and division have a higher precedence than addition and subtraction, so the result will be 11. It’s advisable to use parentheses in your code to make the desired calculation precedence clear. int value = 1 + (2 * 3) + 4;
produces the same result but is easier to read.
Use parentheses if you need to alter the precedence, as in this example:
int
value
=
((
1
+
2
)
*
3
)
+
4
;
The result will be 13. The expression in the inner parentheses is calculated first, so 1 gets added to 2, this then gets multiplied by 3, and finally is added to 4, yielding 13.
You’ll need to make sure your result will not exceed the maximum size of the destination variable, because the Arduino IDE will not warn you about that, unless you enable warnings in File→Preferences. See Recipe 2.2. However, even if you use the correct type, you can still overflow the size of the destination variable. Consider this code:
// 60 seconds in a minute, 60 minutes in an hour, 24 hours in a day
long
seconds_per_day
=
60
*
60
*
24
;
In theory, that should be fine because the result is 86,400, which can fit in a long
data type. But the value that’s really stored in seconds_per_day
is 20,864. 86,400 is enough to overflow an integer twice (86,400 – 32,768 * 2 = 20,864). The overflow happens because the Arduino IDE’s C compiler sees an arithmetic expression composed of integers, and doesn’t know any better. You must tell the compiler that it should treat the whole expression like a long
by appending L
to the first value that is evaluated in the expression:
long
seconds_per_day
=
60L
*
60
*
24
;
If, for some reason, you are using parentheses, remember that innermost parentheses are evaluated first, so this will overflow:
long
seconds_per_day_plus_one
=
1L
+
60
*
(
60
*
24
);
However, this will run correctly:
long
seconds_per_day_plus_one
=
1
+
60
*
(
60L
*
24
);
Floating-point arithmetic is subject to all the imprecisions described in Recipe 2.3. For example, the following code, which divides 36.3 by 3 and prints the result to 10 decimal places, would display 12.0999994277
:
Serial
.
println
(
36.3
/
3
,
10
);
See Recipe 3.3 for a trick you can use to display an accurate calculation.
Use the following code:
int
myValue
=
0
;
myValue
=
myValue
+
1
;
// this adds one to the variable myValue
myValue
+=
1
;
// this does the same as the above
myValue
=
myValue
-
1
;
// this subtracts one from the variable myValue
myValue
-=
1
;
// this does the same as the above
myValue
=
myValue
+
5
;
// this adds five to the variable myValue
myValue
+=
5
;
// this does the same as the above
Increasing and decreasing the values of variables is one of the most common programming tasks, and Arduino has operators to make this easy. Increasing a value by one is called incrementing, and decreasing it by one is called decrementing. The longhand way to do this is as follows:
myValue
=
myValue
+
1
;
// this adds one to the variable myValue
But you can also combine the increment and decrement operators with the assign operator, like this:
myValue
+=
1
;
// this does the same as the above
If you are incrementing or decrementing a value by 1, you can use the abbreviated increment and decrement operators ++
or --
:
myValue
++
;
// this does the same as the above
When the increment or decrement operators appear after a variable, they are known as the post-increment or post-decrement operators because they perform their operation after the variable is evaluated. If they appear before the identifier (pre-increment or pre-decrement), they modify the value before the variable is evaluated:
int
myVal
=
1
;
Serial
.
println
(
myVal
++
);
// prints 1
Serial
.
println
(
myVal
);
// prints 2
Serial
.
println
(
++
myVal
);
// prints 3
Serial
.
println
(
myVal
);
// prints 3
Use the %
symbol (the modulus operator) to get the remainder:
int
myValue0
=
20
%
10
;
// get the modulus(remainder) of 20 divided by 10
int
myValue1
=
21
%
10
;
// get the modulus(remainder) of 21 divided by 10
myValue0
equals 0
(20 divided by 10 has a remainder of 0). myValue1
equals 1
(21 divided by 10 has a remainder of 1).
The modulus operator is surprisingly useful, particularly when you want to see if a value is a multiple of a number. For example, the code in this recipe’s Solution can be enhanced to detect when a value is a multiple of 10:
for
(
int
myValue
=
0
;
myValue
<=
100
;
myValue
+=
5
)
{
if
(
myValue
%
10
==
0
)
{
Serial
.
println
(
"The value is a multiple of 10"
);
}
}
The preceding code takes the modulus of the myValue
variable and compares the result to zero (see Recipe 2.17). If the result is zero, a message is printed saying the value is a multiple of 10.
Here is a similar example, but by using 2 with the modulus operator, the result can be used to check if a value is odd or even:
for
(
int
myValue
=
0
;
myValue
<=
10
;
myValue
++
)
{
if
(
myValue
%
2
==
0
)
{
Serial
.
println
(
"The value is even"
);
}
else
{
Serial
.
println
(
"The value is odd"
);
}
}
This example calculates the hour on a 24-hour clock for any given number of hours offset:
void
printOffsetHour
(
int
hourNow
,
int
offsetHours
)
{
Serial
.
println
((
hourNow
+
offsetHours
)
%
24
);
}
You can also use the modulus operator to help simulate floating-point operations. For example, consider the problem described in Recipe 3.1 where dividing 36.3 by 3 yields 12.0999994277
rather than the expected 12.1. You can multiply the two values by 10, then perform the division as an integer operation to get the integer part:
int
int_part
=
363
/
30
;
// result: 12
Next, you can calculate the remainder, multiply it by 100, then divide by the divisor to get the fractional part:
int
remainder
=
363
%
30
;
// result: 3
int
fractional_part
=
remainder
*
100
/
30
;
Finally, print the integer and fractional part separated by a period (full stop) to get 12.10
:
Serial
.
(
int_part
);
Serial
.
(
"."
);
Serial
.
println
(
fractional_part
);
You want to get the absolute value of a number.
abs(x)
computes the absolute value of x
. The following example takes the absolute value of the difference between readings on two analog input ports (see Chapter 5 for more on analogRead()
):
int
x
=
analogRead
(
A0
);
int
y
=
analogRead
(
A1
);
if
(
abs
(
x
-
y
)
>
10
)
{
Serial
.
println
(
"The analog values differ by more than 10"
);
}
abs(x-y);
returns the absolute value of the difference between x
and y
. It is used for integer (and long
integer) values. To return the absolute value of floating-point values, see Recipe 2.3.
myConstrainedValue
is set to a value that will always be greater than or equal to 100 and less than or equal to 200. If myValue
is less than 100, the result will be 100; if it is more than 200, it will be set to 200.
Table 3-1 shows some example output values using a min
of 100 and a max
of 200.
myValue (the input value) | constrain(myValue, 100, 200) |
---|---|
99 |
100 |
100 |
100 |
150 |
150 |
200 |
200 |
201 |
200 |
You want to find the minimum or maximum of two or more values.
min(x,y)
returns the smaller of two numbers. max(x,y)
returns the larger of two numbers:
int
myValue
=
analogRead
(
A0
);
int
myMinValue
=
min
(
myValue
,
200
);
// myMinValue will be the smaller of
// myVal or 200
int
myMaxValue
=
max
(
myValue
,
100
);
// myMaxValue will be the larger of
// myVal or 100
Table 3-2 shows some example output values using a min
of 200. The table shows that the output is the same as the input (myValue
) until the value becomes greater than 200.
myValue (the input value) | min(myValue, 200) |
---|---|
99 |
99 |
100 |
100 |
150 |
150 |
200 |
200 |
201 |
200 |
Table 3-3 shows the output using a max
of 100. The table shows that the output is the same as the input (myValue
) when the value is greater than or equal to 100.
myValue (the input value) | max(myValue, 100) |
---|---|
99 |
100 |
100 |
100 |
150 |
150 |
200 |
200 |
201 |
201 |
Use min
when you want to limit the upper bound. That may be counterintuitive, but by returning the smaller of the input value and the minimum value, the output from min
will never be higher than the minimum value (200 in the example).
Similarly, use max
to limit the lower bound. The output from max
will never be lower than the maximum value (100 in the example).
If you want to find the min
or max
value from more than two values, you can cascade the values as follows:
// myMinValue will be the smaller of the three analog readings:
int
myMinValue
=
min
(
analogRead
(
0
),
min
(
analogRead
(
1
),
analogRead
(
2
)));
In this example, the minimum value is found for analog ports 1 and 2, and then the minimum of that and port 0. This can be extended for as many items as you need, but take care to position the parentheses correctly. The following example gets the maximum of four values:
int
myMaxValue
=
max
(
analogRead
(
0
),
max
(
analogRead
(
1
),
max
(
analogRead
(
2
),
analogRead
(
3
))));
The pow
function can operate on integer or floating-point values and it returns the result as a floating-point value:
Serial
.
println
(
pow
(
3
,
2
));
// this prints 9.00
int
z
=
pow
(
3
,
2
);
Serial
.
println
(
z
);
// this prints 9
The first output is 9.00
and the second is 9
; they are not exactly the same because the first print
displays the output as a floating-point number and the second treats the value as an integer before printing, and therefore displays without the decimal point. If you use the pow
function, you may want to read Recipe 2.3 to understand the difference between these and integer values.
Here is an example of raising a number to a fractional power:
float
s
=
pow
(
2
,
1.0
/
12
);
// the twelfth root of two
The twelfth root of two is the same as 2 to the power of 0.083333. The resultant value, s
, is 1.05946 (this is the ratio of the frequency of two adjacent notes on a piano).
The sqrt
function returns a floating-point number (see the pow
function discussed in Recipe 3.7).
These functions are used for rounding floating-point numbers; use floor(x)
to get the largest integer that is not greater than x
. Use ceil
to get the smallest integer that is greater than x
.
Here is some example output using floor
:
Serial
.
println
(
floor
(
1
)
);
// this prints 1.00
Serial
.
println
(
floor
(
1.1
)
);
// this prints 1.00
Serial
.
println
(
floor
(
0
)
);
// this prints 0.00
Serial
.
println
(
floor
(
.1
)
);
// this prints 0.00
Serial
.
println
(
floor
(
-
1
)
);
// this prints -1.00
Serial
.
println
(
floor
(
-
1.1
)
);
// this prints -2.00
Here is some example output using ceil
:
Serial
.
println
(
ceil
(
1
)
);
// this prints 1.00
Serial
.
println
(
ceil
(
1.1
)
);
// this prints 2.00
Serial
.
println
(
ceil
(
0
)
);
// this prints 0.00
Serial
.
println
(
ceil
(
.1
)
);
// this prints 1.00
Serial
.
println
(
ceil
(
-
1
)
);
// this prints -1.00
Serial
.
println
(
ceil
(
-
1.1
)
);
// this prints -1.00
You can round to the nearest integer as follows:
int
result
=
round
(
1.1
);
You can truncate a floating-point number by casting (converting) to an int
, but this does not round correctly. Negative numbers such as –1.9 should round down to –2, but when cast to an int
they are rounded up to –1. The same problem exists with positive numbers: 1.9 should round up to 2 but will round down to 1. Use floor
, ceil
, and round
to get the correct results.
Angles are specified in radians and the result is a floating-point number (see Recipe 2.3). The following example illustrates the trig functions:
float
deg
=
30
;
// angle in degrees
float
rad
=
deg
*
PI
/
180
;
// convert to radians
Serial
.
println
(
rad
);
// print the radians
Serial
.
println
(
sin
(
rad
),
5
);
// print the sine
Serial
.
println
(
cos
(
rad
),
5
);
// print the cosine
This converts the angle into radians and prints the sine and cosine. Here is the output with annotation added:
0.52
30
degrees
is
0.5235988
radians
,
println
only
shows
two
decimal
places
0.50000
sine
of
30
degrees
is
.5000000
,
displayed
here
to
5
decimal
places
0.86603
cosine
is
.8660254
,
which
rounds
up
to
0.86603
at
5
decimal
places
Although the sketch calculates these values using the full precision of floating-point numbers, the Serial.print
and Serial.println
routines show the values of floating-point numbers to two decimal places by default, but you can specify a precision as the second argument (5 in the case of sine and cosine in this example) as discussed in Recipe 2.3.
The conversion from radians to degrees and back again is textbook trigonometry. PI
is the familiar constant for π (3.14159265...). PI
and 180
are both constants, and Arduino provides some precalculated constants you can use to perform degree/radian conversions:
rad
=
deg
*
DEG_TO_RAD
;
// a way to convert degrees to radians
deg
=
rad
*
RAD_TO_DEG
;
// a way to convert radians to degrees
Using deg * DEG_TO_RAD
looks more efficient than deg * PI / 180
, but it’s not, since the Arduino compiler is smart enough to recognize that PI / 180
is a constant (the value will never change), so it substitutes the result of dividing PI
by 180, which happens to be the same value as the constant DEG_TO_RAD
(0.017453292519...). Use whichever approach you prefer.
Use the random
function to return a random number. Calling random
with a single parameter sets the upper bound; the values returned will range from zero to one less than the upper bound:
int
minr
=
50
;
int
maxr
=
100
;
long
randnum
=
random
(
maxr
);
// random number between 0 and maxr -1
Calling random
with two parameters sets the lower and upper bounds; the values returned will range from the lower bound (inclusive) to one less than the upper bound:
long
randnum
=
random
(
minr
,
maxr
);
// random number between minr and maxr -1
Although there appears to be no obvious pattern to the numbers returned, the values are not truly random. Exactly the same sequence will repeat each time the sketch starts. In many applications, this does not matter. But if you need a different sequence each time your sketch starts, use the function randomSeed(seed)
with a different seed value each time (if you use the same seed value, you’ll get the same sequence). This function starts the random number generator at some arbitrary place based on the seed parameter you pass:
randomSeed
(
1234
);
// change the starting sequence of random numbers
Here is an example that uses the different forms of random number generation available on Arduino:
// Random
// demonstrates generating random numbers
int
randNumber
;
void
setup
()
{
Serial
.
begin
(
9600
);
while
(
!
Serial
);
// Print random numbers with no seed value
Serial
.
println
(
"Print 20 random numbers between 0 and 9"
);
for
(
int
i
=
0
;
i
<
20
;
i
++
)
{
randNumber
=
random
(
10
);
Serial
.
(
randNumber
);
Serial
.
(
" "
);
}
Serial
.
println
();
Serial
.
println
(
"Print 20 random numbers between 2 and 9"
);
for
(
int
i
=
0
;
i
<
20
;
i
++
)
{
randNumber
=
random
(
2
,
10
);
Serial
.
(
randNumber
);
Serial
.
(
" "
);
}
// Print random numbers with the same seed value each time
randomSeed
(
1234
);
Serial
.
println
();
Serial
.
println
(
"Print 20 random numbers between 0 and 9 after constant seed "
);
for
(
int
i
=
0
;
i
<
20
;
i
++
)
{
randNumber
=
random
(
10
);
Serial
.
(
randNumber
);
Serial
.
(
" "
);
}
// Print random numbers with a different seed value each time
randomSeed
(
analogRead
(
0
));
// read from an analog port with nothing connected
Serial
.
println
();
Serial
.
println
(
"Print 20 random numbers between 0 and 9 after floating seed "
);
for
(
int
i
=
0
;
i
<
20
;
i
++
)
{
randNumber
=
random
(
10
);
Serial
.
(
randNumber
);
Serial
.
(
" "
);
}
Serial
.
println
();
Serial
.
println
();
}
void
loop
()
{
}
Here is the output from this code as run on an Uno (you may get different results on different architectures):
20
random
numbers
between
0
and
9
7
9
3
8
0
2
4
8
3
9
0
5
2
2
7
3
7
9
0
2
20
random
numbers
between
2
and
9
9
3
7
7
2
7
5
8
2
9
3
4
2
5
4
3
5
7
5
7
20
random
numbers
between
0
and
9
after
constant
seed
8
2
8
7
1
8
0
3
6
5
9
0
3
4
3
1
2
3
9
4
20
random
numbers
between
0
and
9
after
floating
seed
0
9
7
4
4
7
7
4
4
9
1
6
0
2
3
1
5
9
1
1
If you press the reset button on your Arduino to restart the sketch, the first three lines of random numbers will be unchanged. (You may need to close and reopen the Serial Monitor after you press the reset button.) Only the last line changes each time the sketch starts, because it sets the seed to a different value by reading it from an unconnected analog input port as a seed to the randomSeed
function. If you are using analog port 0 for something else, change the argument for analogRead
to an unused analog port.
In general, the preceding example is the start and end of the options you have available for random number generation on an Arduino without external hardware. It may seem that an unconnected analog input port is a good, or at least acceptable, way to seed your random number generator. However, the analog-to-digital converter on most Arduino boards will return at most a 10-bit value, which can only hold 1,024 different values. This is far too small a range of values to seed a random number generator for strong random numbers. Additionally, a floating analog pin is not quite as random as you might think it would be. It is likely to exhibit somewhat consistent patterns, and can certainly be influenced by anyone who can get within proximity of your Arduino.
It would be difficult to generate truly random numbers on an Arduino, but like most computers, you can generate cryptographically strong pseudorandom numbers, or random numbers that are “random enough” to be suitable for use in cryptographic applications. Some Arduino boards, such as the Arduino WiFi Rev2, MKR Vidor 4000, and MKR WiFi 1000/1010 include the Atmel ECC508 or ECC608 crypto chip that has hardware support for cryptographic functions, including a strong random number generator. You can access it by installing the ArduinoECCX08 library using Arduino’s Library Manager (see Recipe 16.2 for instructions on installing libraries). For strong random number generation on any Arduino, check out Rhys Weatherley’s Crypto library, in particular the RNG class.
Arduino references for random
and random
Seed
Use the following functions:
bitSet(x, bitPosition)
Sets (writes a 1 to) the given bitPosition
of variable x
bitClear(x, bitPosition)
Clears (writes a 0 to) the given bitPosition
of variable x
bitRead(x, bitPosition)
Returns the value (as 0 or 1) of the bit at the given bit
Position
of variable x
bitWrite(x, bitPosition, value)
Sets the given value (as 0 or 1) of the bit at the given bitPosition
of variable x
bit(bitPosition)
Returns the value of the given bit position: bit(0)
is 1, bit(1)
is 2, bit(2)
is 4, and so on
In all these functions, bitPosition 0
is the least significant (rightmost) bit.
Here is a sketch that uses these functions to manipulate the bits of an 8-bit variable called flags
. It uses each of the eight bits as an independent flag that can be toggled on and off:
// bitFunctions
// demonstrates using the bit functions
byte
flags
=
0
;
// these examples set, clear, or read bits in a variable called flags
// bitSet example
void
setFlag
(
int
flagNumber
)
{
bitSet
(
flags
,
flagNumber
)
;
}
// bitClear example
void
clearFlag
(
int
flagNumber
)
{
bitClear
(
flags
,
flagNumber
)
;
}
// bitPosition example
int
getFlag
(
int
flagNumber
)
{
return
bitRead
(
flags
,
flagNumber
)
;
}
void
setup
(
)
{
Serial
.
begin
(
9600
)
;
}
void
loop
(
)
{
flags
=
0
;
// clear all flags
showFlags
(
)
;
setFlag
(
2
)
;
// set some flags
setFlag
(
5
)
;
showFlags
(
)
;
clearFlag
(
2
)
;
showFlags
(
)
;
delay
(
10000
)
;
// wait a very long time
}
// reports flags that are set
void
showFlags
(
)
{
for
(
int
flag
=
0
;
flag
<
8
;
flag
+
+
)
{
if
(
getFlag
(
flag
)
=
=
true
)
Serial
.
(
"
* bit set for flag
"
)
;
else
Serial
.
(
"
bit clear for flag
"
)
;
Serial
.
println
(
flag
)
;
}
Serial
.
println
(
)
;
}
This code will print the following every 10 seconds:
bit
clear
for
flag
0
bit
clear
for
flag
1
bit
clear
for
flag
2
bit
clear
for
flag
3
bit
clear
for
flag
4
bit
clear
for
flag
5
bit
clear
for
flag
6
bit
clear
for
flag
7
bit
clear
for
flag
0
bit
clear
for
flag
1
*
bit
set
for
flag
2
bit
clear
for
flag
3
bit
clear
for
flag
4
*
bit
set
for
flag
5
bit
clear
for
flag
6
bit
clear
for
flag
7
bit
clear
for
flag
0
bit
clear
for
flag
1
bit
clear
for
flag
2
bit
clear
for
flag
3
bit
clear
for
flag
4
*
bit
set
for
flag
5
bit
clear
for
flag
6
bit
clear
for
flag
7
Reading and setting bits is a common task, and many of the Arduino libraries use this functionality. One of the more common uses of bit operations is to efficiently store and retrieve binary values (on/off, true/false, 1/0, high/low, etc.).
The state of eight switches can be packed into a single 8-bit value instead of requiring eight bytes or integers. The example in this recipe’s Solution shows how eight values can be individually set or cleared in a single byte.
The term flag is a programming term for values that store the state of some aspect of a program. In this sketch, the flag bits are read using bitRead
, and they are set or cleared using bitSet
or bitClear
. These functions take two parameters: the first is the value to read or write (flags
in this example), and the second is the bit position indicating where the read or write should take place. Bit position 0 is the least significant (rightmost) bit; position 1 is the second position from the right, and so on. So:
bitRead
(
2
,
1
);
// returns 1 : 2 is binary 10 and bit in position 1 is 1
bitRead
(
4
,
1
);
// returns 0 : 4 is binary 100 and bit in position 1 is 0
There is also a function called bit
that returns the value of each bit position:
bit
(
0
)
is
equal
to
1
;
bit
(
1
)
is
equal
to
2
;
bit
(
2
)
is
equal
to
4
;
...
bit
(
7
)
is
equal
to
128
This fragment sets variable x
equal to 6. It shifts the bits left by one and prints the new value (12
). Then that value is shifted right two places (and in this example becomes equal to 3
):
int
x
=
6
;
x
=
x
<<
1
;
// 6 shifted left once is 12
Serial
.
println
(
x
);
x
=
x
>>
2
;
// 12 shifted right twice is 3
Serial
.
println
(
x
);
Here is how this works: 6 shifted left one place equals 12, because the decimal number 6 is 0110 in binary. When the digits are shifted left, the value becomes 1100 (decimal 12). Shifting 1100 right two places becomes 0011 (decimal 3). You may notice that shifting a number left by n places is the same as multiplying the value by 2 raised to the power of n. Shifting a number right by n places is the same as dividing the value by 2 raised to the power of n. In other words, the following pairs of expressions are the same:
x << 1
is the same as x * 2
.x << 2
is the same as x * 4
.x << 3
is the same as x * 8
.x >> 1
is the same as x / 2
.x >> 2
is the same as x / 4
.x >> 3
is the same as x / 8
.The Arduino controller chip can shift bits more efficiently than it can multiply and divide, and you may come across code that uses the bit shift to multiply and divide:
int
c
=
(
a
<<
1
)
+
(
b
>>
2
);
//add (a times 2) plus ( b divided by 4)
The expression (a << 1) + (b >> 2);
does not look much like (a * 2) + (b / 4);
, but both expressions do the same thing. Indeed, the Arduino compiler is smart enough to recognize that multiplying an integer by a constant that is a power of two is identical to a shift and will produce the same machine code as the version using shift. The source code using arithmetic operators is easier for humans to read, so it is preferred when the intent is to multiply and divide.
Arduino references for bit and byte functions: lowByte
, highByte
, bitRead
, bitWrite
, bitSet
, bitClear
, and bit
(see Recipe 3.12)
Use lowByte(i)
to get the least significant byte from an integer. Use highByte(i)
to get the most significant byte from an integer.
The following sketch converts an integer value into low and high bytes:
/*
* ByteOperators sketch
*/
int
intValue
=
258
;
// 258 in hexadecimal notation is 0x102
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
()
{
int
loWord
,
hiWord
;
byte
loByte
,
hiByte
;
hiByte
=
highByte
(
intValue
);
loByte
=
lowByte
(
intValue
);
Serial
.
println
(
intValue
,
DEC
);
Serial
.
println
(
intValue
,
HEX
);
Serial
.
println
(
loByte
,
DEC
);
Serial
.
println
(
hiByte
,
DEC
);
delay
(
10000
);
// wait a very long time
}
The example sketch prints intValue
followed by the low byte and high byte (with annotations added):
258
the
integer
value
to
be
converted
102
the
value
in
hexadecimal
notation
2
the
low
byte
1
the
high
byte
To extract the byte values from a long
, the 32-bit long value first gets broken into two 16-bit words that can then be converted into bytes as shown in the earlier code. At the time of this writing, the standard Arduino library did not have a function to perform this operation on a long
, but you can add the following lines to your sketch to provide this:
#define highWord(w) ((w) >> 16)
#define lowWord(w) ((w) & 0xffff)
These are macro expressions: highWord
performs a 16-bit shift operation to produce a 16-bit value, and lowWord
masks the lower 16 bits using the bitwise And operator (see Recipe 2.20).
The number of bits in an int
varies on different platforms. On Arduino it is 16 bits, but in other environments it is 32 bits. The term word as used here refers to a 16-bit value.
This code converts the 32-bit value 16909060 (hexadecimal 0x1020304) to its 16-bit constituent high and low values:
long
longValue
=
16909060
;
int
loWord
=
lowWord
(
longValue
);
int
hiWord
=
highWord
(
longValue
);
Serial
.
println
(
loWord
,
DEC
);
Serial
.
println
(
hiWord
,
DEC
);
This prints the following values:
772
772
is
0x0304
in
hexadecimal
258
258
is
0x0102
in
hexadecimal
Note that 772 in decimal is 0x0304 in hexadecimal, which is the low-order word (16 bits) of the longValue 0x102
0304
. You may recognize 258 from the first part of this recipe as the value produced by combining a high byte of 1 and a low byte of 2 (0x0102 in hexadecimal).
Arduino references for bit and byte functions: lowByte
, highByte
, bitRead
, bitWrite
, bitSet
, bitClear
, and bit
(see Recipe 3.12)
You want to create a 16-bit (int
) or 32-bit (long
) integer value from individual bytes; for example, when receiving integers as individual bytes over a serial communication link. This is the inverse operation of Recipe 3.14.
Use the word(h,l)
function to convert two bytes into a single Arduino integer. Here is the code from Recipe 3.14 expanded to convert the individual high and low bytes back into an integer:
/*
* Forming an int or long with byte operations sketch
*/
int
intValue
=
0x102
;
// 258
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
()
{
int
aWord
;
byte
loByte
,
hiByte
;
hiByte
=
highByte
(
intValue
);
loByte
=
lowByte
(
intValue
);
Serial
.
println
(
intValue
,
DEC
);
Serial
.
println
(
loByte
,
DEC
);
Serial
.
println
(
hiByte
,
DEC
);
aWord
=
word
(
hiByte
,
loByte
);
// convert the bytes back into a word
Serial
.
println
(
aWord
,
DEC
);
delay
(
10000
);
// wait a very long time
}
The word(high,low)
expression assembles a high and low byte into a 16-bit value. The code in this recipe’s Solution takes the low and high bytes formed as shown in Recipe 3.14 and assembles them back into a word. The output is the integer value, the low byte, the high byte, and the bytes converted back to an integer value:
258
2
1
258
Arduino does not have a function to convert a 32-bit long
value into two 16-bit words (at the time of this writing), but you can add your own makeLong()
capability by adding the following line to the top of your sketch:
#define makeLong(hi, low) ((hi) << 16 & (low))
This defines a command that will shift the high value 16 bits to the left and add it to the low value:
#define makeLong(hi, low) (((long) hi) << 16 | (low))
#define highWord(w) ((w) >> 16)
#define lowWord(w) ((w) & 0xffff)
// declare a value to test
long
longValue
=
0x1020304
;
// in decimal: 16909060
// in binary : 00000001 00000010 00000011 00000100
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
()
{
int
loWord
,
hiWord
;
Serial
.
println
(
longValue
,
DEC
);
// this prints 16909060
loWord
=
lowWord
(
longValue
);
// convert long to two words
hiWord
=
highWord
(
longValue
);
Serial
.
println
(
loWord
,
DEC
);
// print the value 772
Serial
.
println
(
hiWord
,
DEC
);
// print the value 258
longValue
=
makeLong
(
hiWord
,
loWord
);
// convert the words back to a long
Serial
.
println
(
longValue
,
DEC
);
// this again prints 16909060
delay
(
10000
);
// wait a very long time
}
The output is:
16909060
772
258
16909060
The term word refers to 16 bits when compiling for 8-bit boards such as the Uno; when compiling for 32-bit boards a word is 32 bits and a half word is 16 bits. Although the underlying architecture is different, the preceding code will return high and low 16-bit values on both 8-bit and 32-bit boards.
The Arduino references for bit and byte functions: lowByte
, highByte
, bitRead
, bitWrite
, bitSet
, bitClear
, and bit
(see Recipe 3.12)