Though much of an Arduino project will involve integrating the Arduino board with supporting hardware, you need to be able to tell the board what to do with the rest of your project. This chapter introduces core elements of Arduino programming, shows nonprogrammers how to use common language constructs, and provides an overview of the language syntax for readers who are not familiar with C or C++, the language that Arduino uses.
Since making the examples interesting requires making Arduino do something, the recipes use physical capabilities of the board that are explained in detail in later chapters. If any of the code in this chapter is not clear, feel free to jump forward, particularly to Chapter 4 for more on serial output and Chapter 5 for more on using digital and analog pins. You don’t need to understand all the code in the examples, though, to see how to perform the specific capabilities that are the focus of the recipes. Here are some of the more common functions used in the examples that are covered in the next few chapters:
Serial.println(value);
Prints the value to the Arduino IDE’s Serial Monitor so you can view Arduino’s output on your computer; see Recipe 4.1.
pinMode(pin, mode);
Configures a digital pin to read (input) or write (output) a digital value; see the introduction to Chapter 5.
digitalRead(pin);
Reads a digital value (HIGH
or LOW
) on a pin set for input; see Recipe 5.1.
digitalWrite(pin, value);
Writes the digital value (HIGH
or LOW
) to a pin set for output; see Recipe 5.1.
Programs for Arduino are usually referred to as sketches; the first users were artists and designers, and sketch highlights the quick-and-easy way to have an idea realized. The terms sketch and program are interchangeable. Sketches contain code—the instructions the board will carry out. Code that needs to run only once (such as to set up the board for your application) must be placed in the setup
function. Code to be run continuously after the initial setup has finished goes into the loop
function. Here is a typical sketch:
// The setup() method runs once, when the sketch starts
void
setup
()
{
pinMode
(
LED_BUILTIN
,
OUTPUT
);
// initialize the onboard LED as an output
}
// the loop() method runs over and over again,
void
loop
()
{
digitalWrite
(
LED_BUILTIN
,
HIGH
);
// turn the LED on
delay
(
1000
);
// wait a second
digitalWrite
(
LED_BUILTIN
,
LOW
);
// turn the LED off
delay
(
1000
);
// wait a second
}
When the Arduino IDE finishes uploading the code, and every time you power on the board after you’ve uploaded this code, it starts at the top of the sketch and carries out the instructions sequentially. It runs the code in setup
once and then goes through the code in loop
. When it gets to the end of loop
(marked by the closing bracket, }
) it calls the loop
function again, and does so over and over again until you disconnect power or reset the board.
This example continuously flashes an LED by writing HIGH
and LOW
outputs to a pin. See Chapter 5 to learn more about using Arduino pins. When the sketch begins, the code in setup
sets the pin mode (so it’s capable of lighting an LED). After the code in setup
is completed, the code in loop
is repeatedly called (to flash the LED) for as long as the Arduino board is powered on.
You don’t need to know this to write Arduino sketches, but experienced C/C++ programmers may wonder where the expected main()
entry point function has gone. It’s there, but it’s hidden under the covers by the Arduino build environment. The build process creates an intermediate file that includes the sketch code and the following additional statements. Here’s what the main
function looks like for 8-bit boards (32-bit boards are similar):
int
main
(
void
)
{
init
();
initVariant
();
#if defined(USBCON)
USBDevice
.
attach
();
#endif
setup
();
for
(;;)
{
loop
();
if
(
serialEventRun
)
serialEventRun
();
}
return
0
;
}
The first thing that happens is a call to an init()
function that initializes the Arduino hardware. After that, initVariant()
gets called. This is a rarely used hook to give makers of Arduino-compatible boards a way to invoke their own custom initialization routines. If the microcontroller on the board has dedicated USB hardware, main
will prepare (attach) it for use.
Next, your sketch’s setup()
function is called. Finally, your loop()
function is called over and over. Because the for
loop never terminates, the return
statement is never executed.
Right after each call to loop
, the main
function will call serialEventRun
if it’s supported on your board (it’s not available on boards that are based on the ATmega32U4 such as the Leonardo). This allows you to add a special function called serialEvent
in your sketch that will be called whenever data is available on the serial port (see Recipe 4.3).
Recipe 1.4 explains how to upload a sketch to the Arduino board.
Chapter 17 and the Arduino CLI sketch build process page provide more on the build process.
Although the int
(short for integer) data type is the most common choice for the numeric values encountered in Arduino applications, you can use Tables 2-1 and
2-2 to determine the data type that fits the range of values your application expects. Table 2-1 shows data types for 8-bit boards, and Table 2-2 shows data types for 32-bit boards.
Other types | Use |
---|---|
Represents a sequence of characters typically used to contain text. |
|
Used only in function declarations where no value is returned. |
Other types | Use |
---|---|
|
Represents a sequence of characters typically used to contain text. |
Used only in function declarations where no value is returned. |
Except in situations where maximum performance or memory efficiency is required, variables declared using int
will be suitable for numeric values if the values do not exceed the range (shown in Table 2-1) and if you don’t need to work with fractional values. Most of the official Arduino example code declares numeric variables as int
. But sometimes you do need to choose a type that specifically suits your application. This is especially important if you are calling library functions that return values other than int
. Take, for example, the millis
function shown in Recipe 2.10 and other recipes. It returns an unsigned long
value. If you use an int
on an 8-bit board to store the results of that function, you won’t get a warning, but you will get the wrong results because an int
is not large enough to hold the maximum value of a long
. Instead, after you reach 32,767, it will roll over to -32,768. If you were to try to stuff a long
into an unsigned int
, you’ll roll over to zero after you pass the maximum value for an unsigned int
(65,535).
Sometimes you need negative numbers and sometimes you don’t, so numeric types come in two varieties: signed
and unsigned
. unsigned
values are always positive. Variables without the keyword unsigned
in front are signed so that they can represent negative and positive values. One reason to use unsigned
values is when the range of signed
values will not fit the range of the variable (an unsigned variable has twice the capacity of a signed variable). Another reason programmers choose to use unsigned
types is to clearly indicate to people reading the code that the value expected will never be a negative number.
On a 32-bit board an int
requires twice as many bytes as on an 8-bit board, however, memory is ample on 32-bit boards, so most code for 8-bit will run on 32-bit boards. A rare exception is code that assumes that int
s will always be represented in memory using 2 bytes, something well-written code and libraries should not do.
bool
(boolean) types have two possible values: true
or false
. They are commonly used to store values that represent a yes/no condition. You may also see bool
types used in place of the built-in constants HIGH
and LOW
, which are used to modify (with digitalWrite()
) or determine (with digitalRead()
) the state of a digital I/O pin. For example, the statement digitalWrite(LED_BUILTIN, HIGH);
will transmit power to the pin that the built-in LED is connected to. Using LOW
instead of HIGH
will turn off the power. You can use true
or false
in place of HIGH
or LOW
, and you are likely to find examples of this in code you find online. You will also see examples where 1 and 0 are used (1 is equivalent to true and 0 is equivalent to false). However, it is a bad habit to make assumptions about the underlying value of a constant, so you should always use the constants HIGH
and LOW
. It is extremely unlikely that you would ever come across an Arduino variant where HIGH
was equal to false
. But there are many other constants you will come across, and most of them do not have such an explicit and obvious relationship to their underlying values.
The Arduino reference provides details on data types.
The following code shows how to declare floating-point variables, illustrates problems you can encounter when comparing floating-point values, and demonstrates how to overcome them:
/*
* Floating-point example
* This sketch initialized a float value to 1.1
* It repeatedly reduces the value by 0.1 until the value is 0
*/
float
value
=
1.1
;
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
()
{
value
=
value
-
0.1
;
// reduce value by 0.1 each time through the loop
if
(
value
==
0
)
{
Serial
.
println
(
"The value is exactly zero"
);
}
else
if
(
almostEqual
(
value
,
0
))
{
Serial
.
(
"The value "
);
Serial
.
(
value
,
7
);
// print to 7 decimal places
Serial
.
println
(
" is almost equal to zero, restarting countdown"
);
value
=
1.1
;
}
else
{
Serial
.
println
(
value
);
}
delay
(
250
);
}
// returns true if the difference between a and b is small
bool
almostEqual
(
float
a
,
float
b
)
{
const
float
DELTA
=
.00001
;
// max difference to be almost equal
if
(
a
==
0
)
return
fabs
(
b
)
<=
DELTA
;
if
(
b
==
0
)
return
fabs
(
a
)
<=
DELTA
;
return
fabs
((
a
-
b
)
/
max
(
fabs
(
a
),
fabs
(
b
)))
<=
DELTA
;
}
Floating-point math is not exact, and values returned can have a small approximation error. The error occurs because floating-point values cover a huge range, so the internal representation of the value can only hold an approximation. Because of this, you need to test if the values are within a range of tolerance rather than exactly equal.
The Serial Monitor output from this sketch is as follows:
1.00
0.90
0.80
0.70
0.60
0.50
0.40
0.30
0.20
0.10
The
value
-
0.0000001
is
almost
equal
to
zero
,
restarting
countdown
1.00
0.90
The output starts over from the beginning (1.00) and continues the countdown.
You may expect the code to print "The value is exactly zero"
after value
is 0.1
and then 0.1 is subtracted from it. But value
never equals exactly zero; it gets very close, but that is not good enough to pass the test: if (value == 0)
. This is because the only memory-efficient way that floating-point numbers can contain the huge range in values they can represent is by storing an approximation of the number.
The solution to this is to check if a variable is close to the desired value, as shown in this recipe’s Solution.
The almostEqual
function tests if the variable value
is within a margin of the desired target and returns true
if so. The acceptable range is set with the constant DELTA
; you can change this to smaller or larger values as required. The function named fabs
(short for floating-point absolute value) returns the absolute value of a floating-point variable, and this is used to test the difference between the given parameters.
Before the almostEqual
function compares the difference between a
and b
to DELTA
, it scales that difference by the maximum value of either a
or b
. This is necessary to account for the fact that the precision of floating-point values varies by their magnitude. In fact, because this code compares a value to 0, this expression is not necessary because the logic in the preceding two lines takes over when either a
or b
is 0. Table 2-3 shows what would happen at different orders of magnitude for pairs of Start and Comparison values. Equal At shows the value reached by the starting value when they are considered equal. Unscaled Difference shows the difference between a
and b
when almostEqual
determines they are almost equal. Scaled Difference shows the difference that the final line in almostEqual
uses to make that determination. As you can see, by the time you get up to 100, the unscaled value exceeds the DELTA
of 0.00001.
Start | Comparison | Equal at | Unscaled difference | Scaled difference |
---|---|---|---|---|
11.1 | 10 | 9.9999962 | 0.0000038 | 0.0000004 |
101.1 | 100 | 100.0000153 | 0.0000153 | 0.0000002 |
1001.1 | 1000 | 1000.0002441 | 0.0002441 | 0.0000002 |
Floating point approximates numbers because it only uses 32 bits to hold all values within a huge range. Eight bits are used for the decimal multiplier (the exponent), and that leaves 24 bits for the sign and value—only enough for seven significant decimal digits.
This sketch creates two arrays—an array of integers for pins connected to switches and an array of pins connected to LEDs, as shown in Figure 2-1:
/* array sketch an array of switches controls an array of LEDs see
Chapter 5for more on using switches see
Chapter 7for information on LEDs */
int
inputPins
[
]
=
{
2
,
3
,
4
,
5
}
;
// create an array of pins for switch inputs
int
ledPins
[
]
=
{
10
,
11
,
12
,
13
}
;
// create array of output pins for LEDs
void
setup
(
)
{
for
(
int
index
=
0
;
index
<
4
;
index
+
+
)
{
pinMode
(
ledPins
[
index
]
,
OUTPUT
)
;
// declare LED as output
pinMode
(
inputPins
[
index
]
,
INPUT_PULLUP
)
;
// declare as input
}
}
void
loop
(
)
{
for
(
int
index
=
0
;
index
<
4
;
index
+
+
)
{
int
val
=
digitalRead
(
inputPins
[
index
]
)
;
// read input value
if
(
val
=
=
LOW
)
// check if the switch is pressed
{
digitalWrite
(
ledPins
[
index
]
,
HIGH
)
;
// LED on if switch is pressed
}
else
{
digitalWrite
(
ledPins
[
index
]
,
LOW
)
;
// turn LED off
}
}
}
If you’re familiar with Arduino’s INPUT
mode, you may be used to wiring the button up with a pull-down resistor that connects the input pin to ground. But with the INPUT_PULLUP
mode, you don’t need this resistor in your circuit, because this mode enables Arduino’s internal pull-up resistors. The difference with the INPUT_PULLUP
mode is that when the button is pressed, digitalRead
returns LOW
rather than HIGH
.
Arrays are collections of consecutive variables of the same type. Each variable in the collection is called an element. The number of elements is called the size of the array.
The Solution demonstrates a common use of arrays in Arduino code: storing a collection of pins. Here the pins connect to switches and LEDs (a topic covered in more detail in Chapter 5). The important parts of this example are the declaration of the array and access to the array elements.
The following line of code declares (creates) an array of integers with four elements and initializes each element. The first element is set equal to 2, the second to 3, and so on:
int
inputPins
[]
=
{
2
,
3
,
4
,
5
};
If you don’t initialize values when you declare an array (for example, when the values will only be available when the sketch is running), you must set each element individually. You can declare the array as follows:
int
inputPins
[
4
];
If you declare the array outside of a function, this declares an array of four elements with the initial value of each element set to zero. (If you declare it inside of a function, such as with setup()
or loop()
, the elements will be set to seemingly random values.) The number within the square brackets ([]
) is the size, and this sets the number of elements. This array has a size of four and can hold, at most, four integer values. The size can be omitted if the array declaration contains initializers (as shown in the first example) because the compiler figures out how big to make the array by counting the number of initializers.
Because Arduino’s programming environment accepts C or C++ syntax, it is governed by the conventions of those languages. In C and C++, arrays that are declared globally (outside of a function) but are not initialized will have their elements initialized to 0. If they are declared within a function and not initialized, their elements will be undefined, and will probably contain whatever happens to be sitting inside the memory that the array element points to. Uninitialized variables (such as int i;
) may often be set to zero, but there is no guarantee of this, so it’s always important to initialize variables before you try to use their values.
The first element of the array is arrayname[0]
:
int
firstElement
=
inputPins
[
0
];
// this is the first element
inputPins
[
0
]
=
2
;
// set the value of the first element to 2
The last element is one less than the size, so for a four-element array, the last element is element 3:
int
lastElement
=
inputPins
[
3
];
// this is the last element
It may seem odd that an array with a size of four has the last element accessed using array[3]
, but because the first element is array[0]
, the four elements are:
inputPins
[
0
],
inputPins
[
1
],
inputPins
[
2
],
inputPins
[
3
]
In the previous sketch, the four elements are accessed using a for
loop:
for
(
int
index
=
0
;
index
<
4
;
index
++
)
{
pinMode
(
ledPins
[
index
],
OUTPUT
);
// declare LED as output
pinMode
(
inputPins
[
index
],
INPUT_PULLUP
);
// declare as input
}
This loop will step through the variable index
with values starting at 0 and ending at 3. It is a common mistake to accidentally access an element that is beyond the actual size of the array. This is a bug that can have many different symptoms, and care must be taken to avoid it. One way to keep your loops under control is to set the size of an array by using a constant as follows:
const int PIN_COUNT = 4; // define a constant for the number of elements
int inputPins[PIN_COUNT] = {2,3,4,5};
int ledPins[PIN_COUNT] = {10, 11, 12, 13};
/* ... */
for(int index = 0; index < PIN_COUNT
; index++)
{
pinMode(ledPins[index], OUTPUT);
pinMode(inputPins[index], INPUT_PULLUP);
}
The compiler will not report an error if you accidentally try to store or read beyond the size of the array, but it’s likely that your sketch will mysteriously crash if you do. You must be careful that you only access elements that are within the bounds you have set. Using a constant to set the size of an array and in code referring to its elements helps your code stay within the bounds of the array.
Another use of arrays is to hold a string of text characters. In Arduino code, these are called character strings (strings for short). A character string consists of one or more characters, followed by the null character (the value 0) to indicate the end of the string.
The null at the end of a character string is not the same as the character 0. The null has an ASCII value of 0, whereas 0 has an ASCII value of 48.
The previous recipe mentioned how arrays of characters can be used to store text: these character arrays are usually called strings. Arduino has a String
object that adds rich functionality for storing and manipulating text. Note that the “S” in the String
object’s name is uppercase.
The word String with an uppercase S refers to the Arduino text capability provided by the Arduino String library. The word string with a lowercase s refers to the group of characters rather than the Arduino String
functionality.
This recipe demonstrates how to use Arduino String
s.
Load the following sketch onto your board, and open the Serial Monitor to view the results:
/*
Basic_Strings sketch
*/
String
text1
=
"This text"
;
String
text2
=
" has more characters"
;
String
text3
;
// to be assigned within the sketch
void
setup
()
{
Serial
.
begin
(
9600
);
while
(
!
Serial
);
// Wait for serial port (Leonardo, 32-bit boards)
Serial
.
(
"text1 is "
);
Serial
.
(
text1
.
length
());
Serial
.
println
(
" characters long."
);
Serial
.
(
"text2 is "
);
Serial
.
(
text2
.
length
());
Serial
.
println
(
" characters long."
);
text1
.
concat
(
text2
);
Serial
.
println
(
"text1 now contains: "
);
Serial
.
println
(
text1
);
}
void
loop
()
{
}
This sketch creates three variables of type String
, called text1
, text2
, and text3
. Variables of type String
have built-in capabilities for manipulating text. The statement text1.length()
returns (provides the value of) the length (number of characters) in the string text1
.
text1.concat(text2)
combines the contents of strings; in this case, it appends the contents of text2
to the end of text1
(concat
is short for concatenate).
The Serial Monitor will display the following:
text1
is
9
characters
long
.
text2
is
20
characters
long
.
text1
now
contains
:
This
text
has
more
characters
Another way to combine strings is to use the string addition operator. Add these two lines to the end of the setup
code:
text3
=
text1
+
" and more"
;
Serial
.
println
(
text3
);
The new code will result in the Serial Monitor adding the following line to the end of the display:
This
text
has
more
characters
and
more
You can use the indexOf
and lastIndexOf
functions to find an instance of a particular character in a string. Like arrays, Arduino strings are indexed beginning with 0.
You will come across Arduino sketches that use arrays of characters or pointers to a sequence of characters rather than the String
type. See Recipe 2.6 for more on using arrays of characters without the help of the Arduino String
functionality. See Recipe 17.4 for instructions on storing string literals in flash memory rather than Arduino’s main working RAM memory.
If you see a line such as the following:
char
oldString
[]
=
"this is a character array"
;
the code is using C-style character arrays (see Recipe 2.6). If the declaration looks like this:
String
newString
=
"this is a string object"
;
the code uses Arduino String
s. To convert a C-style character array to an Arduino String
, just assign the contents of the array to the String
object:
char
oldString
[]
=
"I want this character array in a String object"
;
String
newString
=
oldString
;
To use any of the functions listed in Table 2-4, you need to invoke them upon an existing string object, as in this example:
int
len
=
myString
.
length
();
Function | What it does |
---|---|
Returns the nth character of the String |
|
Compares the String to the given String S2 |
|
Returns a new String that is the combination of the String and S2 |
|
Returns true if the String ends with the characters of S2 |
|
Returns true if the String is an exact match for S2 (case-sensitive) |
|
Same as equals but is not case-sensitive |
|
Copies len (gth) characters into the supplied byte buffer |
|
Returns the index of the supplied String (or character) or –1 if not found |
|
Same as indexOf but starts from the end of the String |
|
Returns the number of characters in the String |
|
Removes the character in the String at the given index |
|
|
Removes the specified number of characters from the String starting at the given index |
Replaces all instances of String (or character) A with B |
|
Sets aside (allocates) the specified number of bytes to make subsequent String operations more efficient |
|
Stores the character c in the String at the given index |
|
Returns true if the String starts with the characters of S2 |
|
Returns a String with the characters starting from index to the end of the String |
|
|
Same as above, but the substring ends at the character location before the to position |
Copies up to len characters of the String to the supplied buffer |
|
Returns the floating-point value of the numeric digits in the String |
|
Returns the integer value of the numeric digits in the String |
|
Returns a String with all characters converted to lowercase |
|
Returns a String with all characters converted to uppercase |
|
Returns a String with all leading and trailing whitespace removed |
See the Arduino reference pages for more about the usage and variants for these functions.
Arduino’s built-in String
data type is easier to use than C character arrays, but this is achieved through complex code in the String library, which makes more demands on your Arduino, and is, by nature, more prone to problems.
The String
data type is so flexible because it makes use of dynamic memory allocation. That is, when you create or modify a String
, Arduino requests a new region of memory from the C library, and when you’re done using a String
, Arduino needs to release that memory. This usually works smoothly, but 8-bit Arduino boards have so little working RAM (2K on the Arduino Uno) that even small memory leaks can have a big impact on your sketch. A memory leak occurs when, through a bug in a library or incorrect usage of it, memory that you allocate is not released. When this happens, the memory available to Arduino will slowly decrease (until you reboot the Arduino). A related issue is memory fragmentation: as you repeatedly allocate and release memory, Arduino will have successively smaller contiguous blocks of free memory, which could cause a String
allocation to fail even if there’s otherwise sufficient RAM.
Even if there are no memory leaks, it’s complicated to write code to check if a String
request failed due to insufficient memory (the String
functions mimic those in Processing, but unlike that platform, Arduino does not have runtime error exception handling). Running out of dynamic memory is a bug that can be very difficult to track down because the sketch can run without problems for days or weeks before it starts misbehaving due to insufficient memory.
If you use C character arrays, you are in control of memory usage: you’re allocating a fixed (static) amount of memory at compile time so you don’t get memory leaks. Your Arduino sketch will have the same amount of memory available to it all the time it’s running. And if you do try to allocate more memory than is available, finding the cause is easier because there are tools that tell you how much static memory you have allocated (see the reference to avr-objdump
in Recipe 17.1).
However, with C character arrays, it’s easier for you to have another problem: C will not prevent you from modifying memory beyond the bounds of the array. So if you allocate an array as myString[4]
, and assign myString[4] = 'A'
(remember, myString[3]
is the end of the array), nothing will stop you from doing this. But who knows what piece of memory myString[4]
refers to? And who knows whether assigning 'A'
to that memory location will cause you a problem? Most likely, it will cause your sketch to misbehave.
So, Arduino’s built-in String library, by virtue of using dynamic memory, runs the risk of eating up your available memory. C’s character arrays require care on your part to ensure that you do not exceed the bounds of the arrays you use. So use Arduino’s built-in String library if you need rich-text handling capability and you won’t be creating and modifying String
s over and over again. If you need to create and modify them in a loop that is constantly repeating, you’re better off allocating a large C character array and writing your code carefully so you don’t write past the bounds of that array.
Another instance where you may prefer C character arrays over Arduino String
s is in large sketches that need most of the available RAM or flash. The Arduino StringToInt
example code uses almost 2 KB more flash than the code using a C character array and atoi
to convert to an int
. The Arduino String
version also needs a bit more RAM to store allocation information in addition to the actual string.
If you do suspect that the String library, or any other library that makes use of dynamically allocated memory, might be leaking memory, you can determine how much memory is free at any given time; see Recipe 17.2. Check the amount of RAM when your sketch starts, and monitor it to see whether it’s decreasing over time. If you suspect a problem with the String library, search the list of open bugs for “String.”
You want to understand how to use raw character strings: you want to know how to create a string, find its length, and compare, copy, or append strings. The core C language does not support the Arduino-style String
capability, so you want to understand code from other platforms written to operate with primitive character arrays.
Arrays of characters are sometimes called character strings (or simply strings for short). Recipe 2.4 describes Arduino arrays in general. This recipe describes functions that operate on character strings. If you have done any C or C++ programming, you may be used to adding #include <string.h>
to your code in order to get access to these functions. The Arduino IDE does this for you under the hood, so you don’t need the #include
.
You declare strings like this:
char
stringA
[
8
];
// declare a string of up to 7 chars plus terminating null
char
stringB
[
8
]
=
"Arduino"
;
// as above and initialize the string to "Arduino"
char
stringC
[
16
]
=
"Arduino"
;
// as above, but string has room to grow
char
stringD
[
]
=
"Arduino"
;
// the compiler inits string and calculates size
Use strlen
(short for string length) to determine the number of characters before the terminating null:
int
length
=
strlen
(
string
);
// return the number of characters in the string
length
will be 0 for stringA
and 7 for the other strings shown in the preceding code. The null that indicates the end of the string is not counted by strlen
.
Use strcpy
(short for string copy) to copy one string to another:
strcpy
(
destination
,
source
);
// copy string source to destination
Use strncpy
(like strcpy
, but with a limit) to limit the number of characters to copy (useful to prevent writing more characters than the destination string can hold). You can see this used in Recipe 2.7:
// copy up to 6 characters from source to destination
strncpy
(
destination
,
source
,
6
);
Use strcat
(short for string concatenate) to append one string to the end of another:
// append source string to the end of the destination string
strcat
(
destination
,
source
);
Always make sure there is enough room in the destination when copying or concatenating strings. Don’t forget to allow room for the terminating null.
Use strcmp
(short for string compare) to compare two strings. You can see this used in Recipe 2.7:
if
(
strcmp
(
str
,
"Arduino"
)
==
0
)
{
// do something if the variable str is equal to "Arduino"
}
The str*
functions described in this recipe are part of C’s string.h
library. See one of the many online C/C++ reference pages, such as cplusplus.com and the C++ Referencepage.
This sketch prints the text found between each comma:
/*
* SplitSplit sketch
* split a comma-separated string
*/
String
text
=
"Peter,Paul,Mary"
;
// an example string
String
message
=
text
;
// holds text not yet split
int
commaPosition
;
// the position of the next comma in the string
void
setup
()
{
Serial
.
begin
(
9600
);
while
(
!
Serial
);
// Wait for serial port (Leonardo, 32-bit boards)
Serial
.
println
(
message
);
// show the source string
do
{
commaPosition
=
message
.
indexOf
(
','
);
if
(
commaPosition
!=
-
1
)
{
Serial
.
println
(
message
.
substring
(
0
,
commaPosition
));
message
=
message
.
substring
(
commaPosition
+
1
,
message
.
length
());
}
else
{
// here after the last comma is found
if
(
message
.
length
()
>
0
)
Serial
.
println
(
message
);
// if there is text after the last comma,
// print it
}
}
while
(
commaPosition
>=
0
);
}
void
loop
()
{
}
The Serial Monitor will display the following:
Peter
,
Paul
,
Mary
Peter
Paul
Mary
This sketch uses String
functions to extract text from between commas. The following code:
commaPosition
=
message
.
indexOf
(
','
);
sets the variable commaPosition
to the position of the first comma in the String
named message
(it will be set to –1 if no comma is found). If there is a comma, the substring
function is used to print the text from the beginning of the string up to, but excluding, the comma. The text that was printed, and its trailing comma, are removed from message
in this line:
message
=
message
.
substring
(
commaPosition
+
1
,
message
.
length
());
substring
returns a string starting from commaPosition+1
(the position just after the first comma) up to the length of the message. This results in that message containing only the text following the first comma. This is repeated until no more commas are found (commaPosition
will be equal to –1).
If you are an experienced programmer, you can also use the low-level functions that are part of the standard C library. The following sketch has similar functionality to the preceding one using Arduino strings:
/*
* strtok sketch
* split a comma-separated string
*/
const
int
MAX_STRING_LEN
=
20
;
// set this to the largest string
// you will process
char
stringList
[]
=
"Peter,Paul,Mary"
;
// an example string
char
stringBuffer
[
MAX_STRING_LEN
+
1
];
// static buffer for computation/output
void
setup
()
{
Serial
.
begin
(
9600
);
while
(
!
Serial
);
// Wait for serial port (Leonardo, 32-bit boards)
char
*
str
;
char
*
p
;
strncpy
(
stringBuffer
,
stringList
,
MAX_STRING_LEN
);
// copy source string
Serial
.
println
(
stringBuffer
);
// show the source string
for
(
str
=
strtok_r
(
stringBuffer
,
","
,
&
p
);
// split using comma
str
;
// while str is not null
str
=
strtok_r
(
NULL
,
","
,
&
p
)
// get subsequent tokens
)
{
Serial
.
println
(
str
);
}
}
void
loop
()
{
}
Although you can use pointers with Arduino, it’s generally discouraged in sketches because it makes it harder for beginners to understand your code. In practice, you will rarely see pointers or any advanced C functionality in example sketches. For more information on recommended Arduino coding style, see the Arduino Style Guide.
The core functionality comes from the function named strtok_r
(the name of the version of strtok
that comes with the Arduino compiler). The first time you call strtok_r
, you pass it the string you want to tokenize (separate into individual values). But strtok_r
overwrites the characters in this string each time it finds a new token, so it’s best to pass a copy of the string as shown in this example. Each call that follows uses a NULL
to tell the function that it should move on to the next token. In this example, each token is printed to the serial port. *p
is a pointer that strtok_r
uses to keep track of the string it’s working on. You declare it as *p
but you pass it into the strtok_r
function as &p
.
If your tokens consist only of numbers, see Recipe 4.5. This shows how to extract numeric values separated by commas in a stream of serial characters.
See AVR Libc home page for more on C string functions such as strtok_r
and strcmp
.
See Man7.org for an online reference to the C/C++ functions strtok_r
and strcmp
.
The String
variable will convert numbers to strings of characters. You can use literal values, or the contents of a variable. For example, the following code will work:
String
myNumber
=
String
(
1234
);
As will this:
int
value
=
127
;
String
myReadout
=
"The reading was "
;
myReadout
.
concat
(
value
);
Or this:
int
value
=
127
;
String
myReadout
=
"The reading was "
;
myReadout
+=
value
;
If you are converting a number to display as text on an LCD or serial device, the simplest solution is to use the conversion capability built into the LCD and Serial libraries (see Recipe 4.2). But perhaps you are using a device that does not have this built-in support (see Chapter 13) or you want to manipulate the number as a string in your sketch.
The Arduino String
class automatically converts numerical values when they are assigned to a String
variable. You can combine (concatenate) numeric values at the end of a string using the concat
function or the string +
operator.
The +
operator is used with number types as well as strings, but it behaves differently with each.
The following code results in number
having a value of 13
:
int
number
=
12
;
number
+=
1
;
With a String
, as shown here:
String
textNumber
=
"12"
;
textNumber
+=
1
;
textNumber
is the text string "121"
.
Prior to the introduction of the String
class, it was common to find Arduino code using the itoa
or ltoa
function. The names come from “integer to ASCII” (itoa
) and “long to ASCII” (ltoa
). The String
version described earlier is easier to use, but the following can be used if you prefer working with C character arrays as described in Recipe 2.6.
itoa
and ltoa
take three parameters: the value to convert, the buffer that will hold the output string, and the number base (10 for a decimal number, 16 for hex, and 2 for binary).
The following sketch illustrates how to convert numeric values using ltoa
:
/*
* NumberToString
* Creates a string from a given number
*/
char
buffer
[
12
];
// long data type has 11 characters (including the
// minus sign) and a terminating null
void
setup
()
{
Serial
.
begin
(
9600
);
while
(
!
Serial
);
long
value
=
12345
;
ltoa
(
value
,
buffer
,
10
);
Serial
.
(
value
);
Serial
.
(
" has "
);
Serial
.
(
strlen
(
buffer
));
Serial
.
println
(
" digits"
);
value
=
123456789
;
ltoa
(
value
,
buffer
,
10
);
Serial
.
(
value
);
Serial
.
(
" has "
);
Serial
.
(
strlen
(
buffer
));
Serial
.
println
(
" digits"
);
}
void
loop
()
{
}
Your buffer must be large enough to hold the maximum number of characters in the string. For 16-bit base 10 (decimal) integers, that is seven characters (five digits, a possible minus sign, and a terminating 0 that always signifies the end of a string); 32-bit long integers need 12-character buffers (10 digits, the minus sign, and the terminating 0). No warning is given if you exceed the buffer size; this is a bug that can cause all kinds of strange symptoms, because the overflow will corrupt some other part of memory that may be used by your program. The easiest way to handle this is to always use a 12-character buffer and always use ltoa
because this will work on both 16-bit and 32-bit values.
There are a number of ways to solve this. If the string is received as serial stream data, it can be converted using the parseInt
function. See the Discussion section of this recipe or Recipe 4.3 for examples of how to do this using the serial port.
Another approach to converting text strings representing numbers is to use the C language conversion function called atoi
(for int
variables) or atol
(for long
variables).
This sketch terminates the incoming digits on any character that is not a digit (or if the buffer is full). After you upload the sketch, open the Serial Monitor and type some numeric characters, then press Enter or Return. For this to work, though, you’ll need to enable the newline option in the Serial Monitor or type some nondigit characters before you press Enter or Return:
/*
* StringToNumber
* Creates a number from a string
*/
int
blinkDelay
;
// blink rate determined by this variable
char
strValue
[
6
];
// must be big enough to hold all the digits and the
// 0 that terminates the string
int
index
=
0
;
// the index into the array storing the received digits
void
setup
()
{
Serial
.
begin
(
9600
);
pinMode
(
LED_BUILTIN
,
OUTPUT
);
// enable LED pin as output
}
void
loop
()
{
if
(
Serial
.
available
())
{
char
ch
=
Serial
.
read
();
if
(
index
<
5
&&
isDigit
(
ch
)
){
strValue
[
index
++
]
=
ch
;
// add the ASCII character to the string;
}
else
{
// here when buffer full or on the first nondigit
strValue
[
index
]
=
0
;
// terminate the string with a 0
blinkDelay
=
atoi
(
strValue
);
// use atoi to convert the string to an int
index
=
0
;
}
}
blink
();
}
void
blink
()
{
digitalWrite
(
LED_BUILTIN
,
HIGH
);
delay
(
blinkDelay
/
2
);
// wait for half the blink period
digitalWrite
(
LED_BUILTIN
,
LOW
);
delay
(
blinkDelay
/
2
);
// wait for the other half
}
The obscurely named atoi
(for ASCII to int
) and atol
(for ASCII to long
) functions convert a string into integers or long integers. To use them, you have to receive and store the entire string in a character array before you can call the conversion function. The code creates a character array named strValue
that can hold up to five digits (it’s declared as char strValue[6]
because there must be room for the terminating null). It fills this array with digits from Serial.read
until it gets the first character that is not a valid digit. The array is terminated with a null and the atoi
function is called to convert the character array into the variable blinkDelay
.
A function called blink
is called that uses the value stored in blinkDelay
.
As mentioned in the warning in Recipe 2.4, you must be careful not to exceed the bounds of the array. If you are not sure how to do that, see the Discussion section of that recipe.
Arduino also offers the parseInt
function that can be used to get integer values from Serial
and Ethernet
(or any object that derives from the Stream
class). The following fragment will convert sequences of numeric digits into numbers. It is similar to the solution but does not need a buffer (and does not limit the number of digits to five):
void
loop
()
{
if
(
Serial
.
available
())
{
int
newValue
=
Serial
.
parseInt
();
if
(
newValue
!=
0
)
{
blinkDelay
=
newValue
;
Serial
.
(
"New delay: "
);
Serial
.
println
(
blinkDelay
);
}
}
blink
();
}
Stream-parsing methods such as parseInt
use a timeout to return control to your sketch if data does not arrive within the desired interval. The default timeout is one second but this can be changed by calling the setTimeout
method:
Serial.setTimeout(1000 * 60); // wait up to one minute
parseInt
(and all other stream methods) will return whatever value was obtained prior to the timeout if no delimiter was received. The return value will consist of whatever values were collected; if no digits were received, the return value will be zero.
Documentation for atoi
can be found at the AVR Libc site.
There are many online C/C++ reference pages covering these low-level functions, such as cplusplus or CPP Reference.
See Recipes 4.3 and 4.5 for more about using parseInt
with Serial
.
Functions are used to organize the actions performed by your sketch into functional blocks. Functions package functionality into well-defined inputs (information given to a function) and outputs (information provided by a function) that make it easier to structure, maintain, and reuse your code. You are already familiar with the two functions that are in every Arduino sketch: setup
and loop
. You create a function by declaring its return type (the information it provides), its name, and any optional parameters (values) that the function will receive when it is called.
The terms functions and methods are used to refer to well-defined blocks of code that can be called as a single entity by other parts of a program. The C language refers to these as functions. Object-oriented languages such as C++ that expose functionality through classes tend to use the term method. Arduino uses a mix of styles (the example sketches tend to use C-like style; libraries tend to be written to expose C++ class methods). In this book, the term function is usually used unless the code is exposed through a class. Don’t worry; if that distinction is not clear to you, treat both terms as the same.
Here is a simple function that just blinks an LED. It has no parameters and doesn’t return anything (the void
preceding the function indicates that nothing will be returned):
// blink an LED once
void
blink1
()
{
digitalWrite
(
LED_BUILTIN
,
HIGH
);
// turn the LED on
delay
(
500
);
// wait 500 milliseconds
digitalWrite
(
LED_BUILTIN
,
LOW
);
// turn the LED off
delay
(
500
);
// wait 500 milliseconds
}
The following version has a parameter (the integer named count
) that determines how many times the LED will flash:
// blink an LED the number of times given in the count parameter
void
blink2
(
int
count
)
{
while
(
count
>
0
)
// repeat until count is no longer greater than zero
{
digitalWrite
(
LED_BUILTIN
,
HIGH
);
// turn the LED on
delay
(
500
);
// wait 500 milliseconds
digitalWrite
(
LED_BUILTIN
,
LOW
);
// turn the LED off
delay
(
500
);
// wait 500 milliseconds
count
=
count
-
1
;
// decrement count
}
}
Experienced programmers will note that both functions could be named blink
because the compiler will differentiate them by the type of values used for the parameter. This behavior is called function overloading. The Arduino print function discussed in Recipe 4.2 is a common example. Another example of overloading is in the discussion of Recipe 4.6.
That version checks to see if the value of count
is 0
. If not, it blinks the LED and then reduces the value of count
by one. This will be repeated until count
is no longer greater than 0
.
A parameter is sometimes referred to as an argument in some documentation. For practical purposes, you can treat these terms as meaning the same thing.
Here is an example sketch with a function that takes a parameter and returns a value. The parameter determines the length of the LED on and off times (in milliseconds). The function continues to flash the LED until a button is pressed, and the number of times the LED flashed is returned from the function. This sketch uses the same wiring as the pull-up sketch from Recipe 5.2:
/*
blink3 sketch
Demonstrates calling a function with a parameter and returning a value.
The LED flashes when the program starts and stops when a switch connected
to digital pin 2 is pressed.
The program prints the number of times that the LED flashes.
*/
const
int
inputPin
=
2
;
// input pin for the switch
void
setup
()
{
pinMode
(
LED_BUILTIN
,
OUTPUT
);
pinMode
(
inputPin
,
INPUT
);
digitalWrite
(
inputPin
,
HIGH
);
// use internal pull-up resistor (Recipe 5.2)
Serial
.
begin
(
9600
);
}
void
loop
(){
Serial
.
println
(
"Press and hold the switch to stop blinking"
);
int
count
=
blink3
(
250
);
// blink the LED 250 ms on and 250 ms off
Serial
.
(
"The number of times the switch blinked was "
);
Serial
.
println
(
count
);
while
(
digitalRead
(
inputPin
)
==
LOW
)
{
// do nothing until they let go of the button
}
}
// blink an LED using the given delay period
// return the number of times the LED flashed
int
blink3
(
int
period
)
{
int
blinkCount
=
0
;
while
(
digitalRead
(
inputPin
)
==
HIGH
)
// repeat until switch is pressed
// (it will go low when pressed)
{
digitalWrite
(
LED_BUILTIN
,
HIGH
);
delay
(
period
);
digitalWrite
(
LED_BUILTIN
,
LOW
);
delay
(
period
);
blinkCount
=
blinkCount
+
1
;
// increment the count
}
// here when inputPin is no longer HIGH (means the switch is pressed)
return
blinkCount
;
// this value will be returned
}
A function declaration is a prototype—a specification of the name, the types of values that may be passed to the function, and the function’s return type. The Arduino build process creates the declarations for you under the covers, so you do not need to follow the standard C requirement of declaring the function separately.
The code in this recipe’s Solution illustrates the three forms of function call that you will come across. blink1
has no parameter and no return value. Its form is:
void
blink1
()
{
// implementation code goes here...
}
blink2
takes a single parameter but does not return a value:
void
blink2
(
int
count
)
{
// implementation code goes here...
}
blink3
has a single parameter and returns a value:
int
blink3
(
int
period
)
{
int
result
=
0
;
// implementation code goes here...
return
result
;
// this value will be returned
}
The data type that precedes the function name indicates the return type (or no return type if void
). When declaring the function (writing out the code that defines the function and its action), you do not put a semicolon following the parenthesis at the end. When you use (call) the function, you do need a semicolon at the end of the line that calls the function.
Most of the functions you come across will be some variation on these forms.
The data type identifier in front of the declaration tells the compiler (and reminds the programmer) what data type the function returns. In the case of blink1
and blink2
, void
indicates that it returns no value. In the case of blink3
, int
indicates that it returns an integer. When creating functions, choose the return type appropriate to the action the function performs.
It is recommended that you give your functions meaningful names, and it is a common practice to combine words by capitalizing the first letter of each word, except for the first word. Use whatever style you prefer, but it helps others who read your code if you keep your naming style consistent.
The blink2
function has a parameter called count
(when the function is called, count
is given the value that is passed to the function). The blink3
function is different in that it is given a parameter called period
.
The body of the function (the code within the curly brackets) performs the action you want—for blink1
, it blinks the LED on and then off. For blink2
, it iterates through a while
loop the number of times specified by count
, blinking the LED each time through. For blink3
, it flashes the LED until you press a button, and then it returns a value to the calling function: the number of times that the LED blinked before you pressed the button.
You want to return two or more values from a function. Recipe 2.10 provided examples for the most common form of a function, one that returns just one value or none at all. But sometimes you need to modify or return more than one value.
There are various ways to solve this. The easiest to understand is to have the function change some global variables and not actually return anything from the function:
/*
swap sketch
demonstrates changing two values using global variables
*/
int
x
;
// x and y are global variables
int
y
;
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
(){
x
=
random
(
10
);
// pick some random numbers
y
=
random
(
10
);
Serial
.
(
"The value of x and y before swapping are: "
);
Serial
.
(
x
);
Serial
.
(
","
);
Serial
.
println
(
y
);
swap
();
Serial
.
(
"The value of x and y after swapping are: "
);
Serial
.
(
x
);
Serial
.
(
","
);
Serial
.
println
(
y
);
Serial
.
println
();
delay
(
1000
);
}
// swap the two global values
void
swap
()
{
int
temp
;
temp
=
x
;
x
=
y
;
y
=
temp
;
}
The swap
function changes two values by using global variables. Global variables are easy to understand (global variables are values that are accessible everywhere and anything can change them), but they are avoided by experienced programmers because it’s easy to inadvertently modify the value of a variable or to have a function stop working because you changed the name or type of a global variable elsewhere in the sketch.
A safer and more elegant solution is to pass references to the values you want to change and let the function use the references to modify the values. This is done as follows:
/*
functionReferences sketch
demonstrates returning more than one value by passing references
*/
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
(){
int
x
=
random
(
10
);
// pick some random numbers
int
y
=
random
(
10
);
Serial
.
(
"The value of x and y before swapping are: "
);
Serial
.
(
x
);
Serial
.
(
","
);
Serial
.
println
(
y
);
swapRef
(
x
,
y
);
Serial
.
(
"The value of x and y after swapping are: "
);
Serial
.
(
x
);
Serial
.
(
","
);
Serial
.
println
(
y
);
Serial
.
println
();
delay
(
1000
);
}
// swap the two given values
void
swapRef
(
int
&
value1
,
int
&
value2
)
{
int
temp
;
temp
=
value1
;
value1
=
value2
;
value2
=
temp
;
}
Finally, another option is to use a C structure, which can contain multiple fields, allowing you to pass and return all kinds of data:
/*
struct sketch
demonstrates returning more than one value by using a struct
*/
struct
Pair
{
int
a
,
b
;
};
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
()
{
int
x
=
random
(
10
);
// pick some random numbers
int
y
=
random
(
10
);
struct
Pair
mypair
=
{
random
(
10
),
random
(
10
)};
Serial
.
(
"The value of x and y before swapping are: "
);
Serial
.
(
mypair
.
a
);
Serial
.
(
","
);
Serial
.
println
(
mypair
.
b
);
mypair
=
swap
(
mypair
);
Serial
.
(
"The value of x and y after swapping are: "
);
Serial
.
(
mypair
.
a
);
Serial
.
(
","
);
Serial
.
println
(
mypair
.
b
);
Serial
.
println
();
delay
(
1000
);
}
// swap the two given values
Pair
swap
(
Pair
pair
)
{
int
temp
;
temp
=
pair
.
a
;
pair
.
a
=
pair
.
b
;
pair
.
b
=
temp
;
return
pair
;
}
The swapRef
function is similar to the functions with parameters described in Recipe 2.10, but the ampersand (&
) symbol indicates that the parameters are references. This means changes in values within the function will also change the value of the variable that is given when the function is called. You can see how this works by first running the code in this recipe’s Solution and verifying that the parameters are swapped. Then modify the code by removing the two ampersands in the function definition.
The changed line should look like this:
void
swapRef
(
int
value1
,
int
value2
)
Running the code shows that the values are not swapped—changes made within the function are local to the function and are lost when the function returns.
The swapPair
function uses a C language feature called a struct
(or structure). A structure contains any number of primitive types or pointers. The amount of memory reserved for a struct
is equivalent to the size of its elements (on an 8-bit Arduino, a Pair
would take up four bytes, eight on a 32-bit board). If you are familiar with object-oriented programming, it may be tempting to think of struct
s as similar to classes, but struct
s are nothing more than the data they contain.
The following code uses the wiring shown in Recipe 5.2:
/*
* Pushbutton sketch
* a switch connected to digital pin 2 lights the built-in LED
*/
const
int
inputPin
=
2
;
// choose the input pin (for a pushbutton)
void
setup
()
{
pinMode
(
LED_BUILTIN
,
OUTPUT
);
// declare LED pin as output
pinMode
(
inputPin
,
INPUT_PULLUP
);
// declare pushbutton pin as input
}
void
loop
()
{
int
val
=
digitalRead
(
inputPin
);
// read input value
if
(
val
==
LOW
)
// Input is LOW when the button is pressed
{
digitalWrite
(
LED_BUILTIN
,
HIGH
);
// turn LED on if switch is pressed
}
}
The if
statement is used to test the value of digitalRead
. An if
statement must have a test within the parentheses that can only be true or false. In the example in this recipe’s Solution, it’s val == LOW
, and the code block following the if
statement is only executed if the expression is true. A code block consists of all code within the curly brackets (or if you don’t use curly brackets, the block is just the next executable statement terminated by a semicolon).
If you want to do one thing if a statement is true and another if it is false, use the if...else
statement:
/*
* Pushbutton sketch
* a switch connected to pin 2 lights the built-in LED
*/
const
int
inputPin
=
2
;
// choose the input pin (for a pushbutton)
void
setup
()
{
pinMode
(
LED_BUILTIN
,
OUTPUT
);
// declare LED pin as output
pinMode
(
inputPin
,
INPUT_PULLUP
);
// declare pushbutton pin as input
}
void
loop
()
{
int
val
=
digitalRead
(
inputPin
);
// read input value
if
(
val
==
LOW
)
// Input is LOW when the button is pressed
{
// do this if val is LOW
digitalWrite
(
LED_BUILTIN
,
HIGH
);
// turn LED on if switch is pressed
}
else
{
// else do this if val is not LOW
digitalWrite
(
LED_BUILTIN
,
LOW
);
// turn LED off
}
}
See the discussion on Boolean types in Recipe 2.2.
A while
loop repeats one or more instructions while an expression is true:
/*
* Repeat
* blinks while a condition is true
*/
const
int
sensorPin
=
A0
;
// analog input 0
void
setup
()
{
Serial
.
begin
(
9600
);
pinMode
(
LED_BUILTIN
,
OUTPUT
);
// enable LED pin as output
}
void
loop
()
{
while
(
analogRead
(
sensorPin
)
>
100
)
{
blink
();
// call a function to turn an LED on and off
Serial
.
(
"."
);
}
Serial
.
println
(
analogRead
(
sensorPin
));
// this is not executed until after
// the while loop finishes!!!
}
void
blink
()
{
digitalWrite
(
LED_BUILTIN
,
HIGH
);
delay
(
100
);
digitalWrite
(
LED_BUILTIN
,
LOW
);
delay
(
100
);
}
This code will execute the statements in the block within the curly brackets ({}
) while the value from analogRead
is greater than 100. This could be used to flash an LED as an alarm while some value exceeded a threshold. The LED is off when the sensor value is 100 or less; it flashes continuously when the value is greater than 100.
Curly brackets define the extent of the code block to be executed in a loop. If curly brackets are not used, only the first line of code will be repeated in the loop, so you should use this style sparingly (or not at all):
while
(
analogRead
(
sensorPin
)
>
100
)
blink
();
// line immediately following the loop expression is executed
Serial
.
(
"."
);
// this is executed only after the while loop finishes!
The do...while
loop is similar to the while
loop, but the instructions in the code block are executed before the condition is checked. Use this form when you must have the code executed at least once, even if the expression is false:
do
{
blink
();
// call a function to turn an LED on and off
Serial
.
(
"."
);
}
while
(
analogRead
(
sensorPin
)
>
100
);
The preceding code will flash the LED at least once and will keep flashing it as long as the value read from a sensor is greater than 100. If the value is not greater than 100, the LED will only flash once. This code could be used in a battery-charging circuit, if it were called once every 10 seconds or so: a single flash shows that the circuit is active, whereas continuous flashing indicates the battery is charged.
Only the code within a while
or do
loop will run until the conditions permit exit. If your sketch needs to break out of a loop in response to some other condition such as a timeout, sensor state, or other input, you can use break
:
while
(
analogRead
(
sensorPin
)
>
100
)
{
blink
();
Serial
.
(
"."
);
if
(
Serial
.
available
())
{
while
(
Serial
.
available
())
{
// consume any pending serial input
Serial
.
read
();
}
break
;
// any serial input breaks out of while loop
}
}
This sketch counts from zero to three by printing the value of the variable i
in a for
loop:
/*
ForLoop sketch
demonstrates for loop
*/
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
(){
Serial
.
println
(
"for(int i=0; i < 4; i++)"
);
for
(
int
i
=
0
;
i
<
4
;
i
++
)
{
Serial
.
println
(
i
);
}
delay
(
1000
);
}
The Serial Monitor output from this is as follows (it will be displayed over and over):
for
(
int
i
=
0
;
i
<
4
;
i
++
)
0
1
2
3
A for
loop consists of three parts: initialization, conditional test, and iteration (a statement that is executed at the end of every pass through the loop). Each part is separated by a semicolon. In the code in this recipe’s Solution, int i=0;
initializes the variable i
to 0
; i < 4;
tests the variable to see if it’s less than 4; and i++
increments i
.
A for
loop can use an existing variable, or it can create a variable for exclusive use inside the loop. This version uses the value of the variable j
created earlier in the sketch:
int
j
;
Serial
.
println
(
"for(j=0; j < 4; j++)"
);
for
(
j
=
0
;
j
<
4
;
j
++
)
{
Serial
.
println
(
j
);
}
This is almost the same as the earlier example, but it does not have the int
keyword in the initialization part because the variable j
was already defined. The output of this version is similar to the output of the earlier version:
for
(
j
=
0
;
j
<
4
;
j
++
)
0
1
2
3
You control when the loop stops in the conditional test. The previous example tests whether the loop variable is less than 4 and will terminate when the condition is no longer true.
If your loop variable starts at 0 and you want it to repeat four times, your conditional statement should test for a value less than 4. The loop repeats while the condition is true, and there are four values that are less than 4 with a loop starting at 0.
The following code tests if the value of the loop variable is less than or equal to 4. It will print the digits from 0 to 4:
Serial
.
println
(
"for(int i=0; i <= 4; i++)"
);
for
(
int
i
=
0
;
i
<=
4
;
i
++
)
{
Serial
.
println
(
i
);
}
The third part of a for
loop is the iterator statement that gets executed at the end of each pass through the loop. This can be any valid C/C++ statement. The following increases the value of i
by two on each pass:
Serial
.
println
(
"
for(int i=0; i < 4; i+= 2)
"
)
;
for
(
int
i
=
0
;
i
<
4
;
i
+
=
2
)
{
Serial
.
println
(
i
)
;
}
That expression only prints the values 0 and 2.
The iterator expression can be used to cause the loop to count from high to low, in this case from 3 to 0:
Serial
.
println
(
"for(int i=3; i >= 0 ; i--)"
);
for
(
int
i
=
3
;
i
>=
0
;
i
--
)
{
Serial
.
println
(
i
);
}
Like the other parts of a for
loop, the iterator expression can be left blank (you must always have the two semicolons separating the three parts even if they are blank).
This version only increments i
when an input pin is high. The for
loop does not change the value of i
; it is only changed by the if
statement after Serial.print
—you’ll need to define inPin
and set it to INPUT
with pinMode()
:
pinMode
(
2
,
INPUT_PULLUP
);
// this goes in setup()
/* ... */
Serial
.
println
(
"for(int i=0; i < 4; )"
);
for
(
int
i
=
0
;
i
<
4
;
)
{
Serial
.
println
(
i
);
if
(
digitalRead
(
2
)
==
LOW
)
{
i
++
;
// only increment the value if the button is pressed
}
}
Use the break
statement as shown in the following sketch:
/*
* break sketch
* Demonstrates the use of the break statement
*/
const
int
switchPin
=
2
;
// digital input 2
void
setup
()
{
Serial
.
begin
(
9600
);
pinMode
(
LED_BUILTIN
,
OUTPUT
);
// enable LED pin as output
pinMode
(
switchPin
,
INPUT_PULLUP
);
// enable button pin as input
}
void
loop
()
{
while
(
true
)
// endless loop
{
if
(
digitalRead
(
switchPin
)
==
LOW
)
{
break
;
// exit the loop if the switch is pressed
}
blink
();
// call a function to turn an LED on and off
}
}
void
blink
()
{
digitalWrite
(
LED_BUILTIN
,
HIGH
);
delay
(
100
);
digitalWrite
(
LED_BUILTIN
,
LOW
);
delay
(
100
);
}
This code is similar to the one using while
loops, but it uses the break
statement to exit the loop if a digital pin goes high. For example, if a switch is connected on the pin as shown in Recipe 5.2, the loop will exit and the LED will stop flashing even though the condition in the while
loop is true.
The Arduino reference for the break
statement
An interrupt is a facility built into the microcontroller hardware that allows you to take action more or less immediately when a pin state changes. See Recipe 18.2 for more details.
You need to do different things depending on some value. You could use multiple if
and else if
statements, but the code soon gets complex and difficult to understand or modify. Additionally, you may want to test for a range of values.
The switch
statement provides for the selection of a number of alternatives. It is functionally similar to multiple if
/else if
statements but is more concise:
/*
* SwitchCase sketch
* example showing switch statement by switching on chars from the serial port
*
* sending the character 1 blinks the LED once, sending 2 blinks twice
* sending + turns the LED on, sending - turns it off
* any other character prints a message to the Serial Monitor
*/
void
setup
()
{
Serial
.
begin
(
9600
);
// Initialize serial port to send and
// receive at 9600 baud
pinMode
(
LED_BUILTIN
,
OUTPUT
);
}
void
loop
()
{
if
(
Serial
.
available
())
// Check to see if at least one
// character is available
{
char
ch
=
Serial
.
read
();
switch
(
ch
)
{
case
'1'
:
blink
();
break
;
case
'2'
:
blink
();
blink
();
break
;
case
'+'
:
digitalWrite
(
LED_BUILTIN
,
HIGH
);
break
;
case
'-'
:
digitalWrite
(
LED_BUILTIN
,
LOW
);
break
;
case
'\n'
:
// newline, safe to ignore
break
;
case
'\r'
:
// carriage return, safe to ignore
break
;
default
:
Serial
.
(
ch
);
Serial
.
println
(
" was received but not expected"
);
break
;
}
}
}
void
blink
()
{
digitalWrite
(
LED_BUILTIN
,
HIGH
);
delay
(
500
);
digitalWrite
(
LED_BUILTIN
,
LOW
);
delay
(
500
);
}
The switch
statement evaluates the variable ch
received from the serial port and branches to the label that matches its value. The labels must be numeric constants. Because the char
data type is represented as a numeric value (see Recipe 2.2), it is permitted. However, you can’t use strings in a case
statement, and no two labels can have the same value. If you don’t have a break
statement following each expression, the execution will fall through into the statement:
case
'1'
:
blink
();
// no break statement before the next label
case
'2'
:
blink
();
// case '1' will continue here
blink
();
break
;
// break statement will exit the switch expression
If the break
statement at the end of case '1':
was removed (as shown in the preceding code), when ch
is equal to the character 1
the blink
function will be called three times. Accidentally forgetting the break
is a common mistake. Intentionally leaving out the break
is sometimes handy; it can be confusing to others reading your code, so it’s a good practice to clearly indicate your intentions with comments in the code.
If your switch
statement is misbehaving, check to ensure that you have not forgotten the break
statements.
The default:
label is used to catch values that don’t match any of the case
labels. If there is no default
label, the switch
expression will not do anything if there is no match.
You want to determine the relationship between values.
Compare integer values using the relational operators shown in Table 2-5.
Operator | Test for | Example |
---|---|---|
Equal to |
|
|
Not equal to |
|
|
Greater than |
|
|
Less than |
|
|
Greater than or equal to |
|
|
Less than or equal to |
|
The following sketch demonstrates the results of using the comparison operators:
/*
* RelationalExpressions sketch
* demonstrates comparing values
*/
int
i
=
1
;
// some values to start with
int
j
=
2
;
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
(){
Serial
.
(
"i = "
);
Serial
.
(
i
);
Serial
.
(
" and j = "
);
Serial
.
println
(
j
);
if
(
i
<
j
)
Serial
.
println
(
" i is less than j"
);
if
(
i
<=
j
)
Serial
.
println
(
" i is less than or equal to j"
);
if
(
i
!=
j
)
Serial
.
println
(
" i is not equal to j"
);
if
(
i
==
j
)
Serial
.
println
(
" i is equal to j"
);
if
(
i
>=
j
)
Serial
.
println
(
" i is greater than or equal to j"
);
if
(
i
>
j
)
Serial
.
println
(
" i is greater than j"
);
Serial
.
println
();
i
=
i
+
1
;
if
(
i
>
j
+
1
)
{
delay
(
10000
);
// long delay after i is no longer close to j
}
else
{
delay
(
1000
);
// short delay
}
}
Here is the output:
i
=
1
and
j
=
2
i
is
less
than
j
i
is
less
than
or
equal
to
j
i
is
not
equal
to
j
i
=
2
and
j
=
2
i
is
less
than
or
equal
to
j
i
is
equal
to
j
i
is
greater
than
or
equal
to
j
i
=
3
and
j
=
2
i
is
not
equal
to
j
i
is
greater
than
or
equal
to
j
i
is
greater
than
j
Note that the equality operator is the double equals sign, ==
. One of the most common programming mistakes is to confuse this with the assignment operator, which uses a single equals sign.
The following expression will compare the value of i
to 3. The programmer intended this:
if
(
i
==
3
)
// test if i equals 3
But suppose they put this in the sketch:
if
(
i
=
3
)
// single equals sign used by mistake!!!!
This will always return true
, because i
will be set to 3
, so they will be equal when compared.
You can also perform these sorts of comparisons on character values because they are represented as numeric values (see Recipe 2.2). This will evaluate to true:
if
(
'b'
>
'a'
)
As will this, because the numeric value of 'a'
is 97 in the ASCII character set that Arduino uses:
if
(
'a'
==
97
)
However, strings cannot be directly compared to numeric values:
String
word1
=
String
(
"Hello"
);
char
word2
[]
=
"World"
;
if
(
word1
>
'G'
)
// This will not compile
{
Serial
.
println
(
"word1 > G"
);
}
if
(
word2
>=
'W'
)
// This also will not compile
{
Serial
.
println
(
"word2 >= W"
);
}
But you can always compare a number or char against a single character from a string:
if
(
word1
.
charAt
(
0
)
>
'G'
)
{
Serial
.
println
(
"word1[0] > G"
);
}
if
(
word2
[
0
]
>=
'W'
)
{
Serial
.
println
(
"word2[0] >= W"
);
}
strcmp
returns the value 0
if the strings are equal and a value greater than zero if the first character that does not match has a greater value in the first string than in the second. It returns a value less than zero if the first nonmatching character in the first string is less than in the second. Usually you only want to know if they are equal, and although the test for zero may seem unintuitive at first, you’ll soon get used to it.
Bear in mind that strings of unequal length will not be evaluated as equal even if the shorter string is contained in the longer one. So:
if
(
strcmp
(
"left"
,
"leftcenter"
)
==
0
)
// this will evaluate to false
You can compare strings up to a given number of characters by using the strncmp
function. You give strncmp
the maximum number of characters to compare and it will stop comparing after that many characters:
if
(
strncmp
(
"left"
,
"leftcenter"
,
4
)
==
0
)
// this will evaluate to true
Unlike character strings, Arduino String
s can be directly compared as follows:
String
stringOne
=
String
(
"this"
);
if
(
stringOne
==
"this"
)
{
Serial
.
println
(
"this will be true"
);
}
if
(
stringOne
==
"that"
)
{
Serial
.
println
(
"this will be false"
);
}
For more see the tutorial on Arduino string comparison.
More information on strcmp
is available at cplusplus.
See Recipe 2.5 for an introduction to the Arduino String
.
You want to evaluate the logical relationship between two or more expressions. For example, you want to take a different action depending on the conditions of an if
statement.
Use the logical operators as outlined in Table 2-6.
Symbol | Function | Comments |
---|---|---|
Logical And |
Evaluates as |
|
Logical Or |
Evaluates as |
|
Not |
Evaluates as |
Logical operators return true
or false
values based on the logical relationship. The examples that follow assume you have sensors wired to digital pins 2 and 3 as discussed in Chapter 5.
The logical And operator &&
will return true
if both its two operands are true, and false
otherwise:
if
(
digitalRead
(
2
)
&&
digitalRead
(
3
)
)
blink
();
// blink if both pins are HIGH
The logical Or operator ||
will return true
if either of its two operands are true, and false
if both operands are false:
if
(
digitalRead
(
2
)
||
digitalRead
(
3
)
)
blink
();
// blink if either pin is HIGH
The Not operator !
has only one operand, whose value is inverted—it results in false
if its operand is true and true
if its operand is false:
if
(
!
digitalRead
(
2
)
)
blink
();
// blink if the pin is not HIGH
Use the bit operators as outlined in Table 2-7. The 0b
prefix indicates binary representation of numbers and is used to disambiguate between the decimal and binary numbers in the table.
Symbol | Function | Result | Example |
---|---|---|---|
Bitwise And |
Sets bits in each place to 1 if both bits are 1; otherwise, bits are set to 0. |
( |
|
Bitwise Or |
Sets bits in each place to 1 if either bit is 1. |
( |
|
Bitwise Exclusive Or |
Sets bits in each place to 1 only if one of the two bits is 1. |
( |
|
Bitwise Negation |
Inverts the value of each bit. The result depends on the number of bits in the data type. |
( |
Here is a sketch that demonstrates the example values shown in Table 2-7:
/* * bits sketch * demonstrates bitwise operators */
void
setup
(
)
{
Serial
.
begin
(
9600
)
;
}
void
loop
(
)
{
Serial
.
(
"
3 & 1 equals
"
)
;
// bitwise And 3 and 1
Serial
.
(
3
&
1
)
;
// print the result
Serial
.
(
"
decimal, or in binary:
"
)
;
Serial
.
println
(
3
&
1
,
BIN
)
;
// print the binary representation of the result
Serial
.
(
"
3 | 1 equals
"
)
;
// bitwise Or 3 and 1
Serial
.
(
3
|
1
)
;
Serial
.
(
"
decimal, or in binary:
"
)
;
Serial
.
println
(
3
|
1
,
BIN
)
;
// print the binary representation of the result
Serial
.
(
"
3 ^ 1 equals
"
)
;
// bitwise exclusive or 3 and 1
Serial
.
(
3
^
1
)
;
Serial
.
(
"
decimal, or in binary:
"
)
;
Serial
.
println
(
3
^
1
,
BIN
)
;
// print the binary representation of the result
byte
byteVal
=
1
;
int
intVal
=
1
;
byteVal
=
~
byteVal
;
// do the bitwise negate
intVal
=
~
intVal
;
Serial
.
(
"
~byteVal (1) equals
"
)
;
// bitwise negate an 8-bit value
Serial
.
println
(
byteVal
,
BIN
)
;
// print the binary representation of the result
Serial
.
(
"
~intVal (1) equals
"
)
;
// bitwise negate a 16-bit value
Serial
.
println
(
intVal
,
BIN
)
;
// print the binary representation of the result
delay
(
10000
)
;
}
This is what is displayed on the Serial Monitor:
3
&
1
equals
1
decimal
,
or
in
binary
:
1
3
|
1
equals
3
decimal
,
or
in
binary
:
11
3
^
1
equals
2
decimal
,
or
in
binary
:
10
~
byteVal
(
1
)
equals
11111110
~
intVal
(
1
)
equals
11111111111111111111111111111110
Bitwise operators are used to set or test bits. When you “And” or “Or” two values, the operator works on each individual bit. It is easier to see how this works by looking at the binary representation of the values.
The decimal integer 3 is binary 11, and the decimal integer 1 is binary 1. Bitwise And operates on each bit. The rightmost bits are both 1, so the result of And-ing these is 1. Moving to the left, the next bits are 1 and 0; And-ing these results in 0. All the remaining bits are 0, so the bitwise result of these will be 0. In other words, for each bit position where there is a 1 in both places, the result will have a 1; otherwise, it will have a 0. So, 11 & 01
equals 1. Binary numbers are often written with leading zeros because it makes it easier to evaluate the effect of bitwise operations at a glance, but the leading zeros are not significant.
Tables 2-8, 2-9, and 2-10 should help to clarify the bitwise And, Or, and Exclusive Or values.
Bit 1 | Bit 2 | Bit 1 and Bit 2 |
---|---|---|
0 |
0 |
0 |
0 |
1 |
0 |
1 |
0 |
0 |
1 |
1 |
1 |
Bit 1 | Bit 2 | Bit 1 or Bit 2 |
---|---|---|
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
1 |
1 |
1 |
Bit 1 | Bit 2 | Bit 1 ^ Bit 2 |
---|---|---|
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
1 |
1 |
0 |
All the bitwise expressions operate on two values, except for the negation operator. This simply flips each bit, so 0 becomes 1 and 1 becomes 0. In the example, the byte
(8-bit) value 00000001 becomes 11111110. The int
value has 16 bits, so when each is flipped, the result is 15 ones followed by a single zero.
Table 2-11 shows the compound assignment operators and their equivalent full expression.
Operator | Example | Equivalent expression |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
These compound statements are no more efficient at runtime than the equivalent full expression, and if you are new to programming, using the full expression is clearer. Experienced coders often use the shorter form, so it is helpful to be able to recognize the expressions when you run across them.
See the Arduino Reference home page for an index to the reference pages for compound operators.