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 non-programmers 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.
You want to understand the fundamental structure of an Arduino program. We’ll show this structure in the following sketch, which programs an Arduino to continually flash an LED light.
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 http://www.arduino.cc/en/Hacking/BuildProcess provide more on the build process.
Arduino has different types of variables to efficiently represent values. You want to know how to select and use these Arduino data types.
Although the int
(short for integer) data type is the most common choice for the numeric values encountered in Arduino applications, you can use Table 2-1 to determine the data type that fits the range of values your application expects.
Numeric types | Bytes | Range | Use |
---|---|---|---|
|
2 |
–32768 to 32767 |
Represents positive and negative integer values. |
|
2 |
0 to 65535 |
Represents only positive values; otherwise, similar to |
|
4 |
–2147483648 to 2147483647 |
Represents a very large range of positive and negative values. |
|
4 |
4294967295 |
Represents a very large range of positive values. |
|
4 |
3.4028235E+38 to –3.4028235E+38 |
Represents numbers with fractions; use to approximate real-world measurements. |
|
4 |
Same as |
In Arduino, |
|
1 |
|
Represents true and false values. |
|
1 |
–128 to 127 |
Represents a single character. Can also represent a signed numeric value between –128 and 127. |
|
1 |
0 to 255 |
Similar to |
Other types | Use |
---|---|
|
Represents a sequence of characters typically used to contain text. |
|
Used only in function declarations where no value is returned. |
Numeric types | Bytes | Range | Use |
---|---|---|---|
|
2 |
–32768 to 32767 |
Same as int on 8-bit boards. |
|
2 |
0 to 65535 |
Same as unsigned int on 8-bit boards. |
|
4 |
–2147483648 to 2147483647 |
Represents positive and negative integer values. |
|
4 |
0 to 4294967295 |
Represents only positive values; otherwise, similar to |
|
4 |
–2147483648 to 2147483647 |
Same as int. |
|
4 |
4294967295 |
Same as unsigned int. |
|
4 |
±3.4028235E+38 |
Represents numbers with fractions; use to approximate real-world measurements. |
|
8 |
±1.7976931348623158E+308 |
32-bit boards have much greater range and preceision than 8-bit boards. |
|
1 |
|
Represents true and false values. |
|
1 |
–128 to 127 |
Represents a single character. Can also represent a signed value between –128 and 127. |
|
1 |
0 to 255 |
Similar to |
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 specifically 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 32767, it will roll over to -32768. 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 (65535).
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 ints 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 at https://www.arduino.cc/reference provides details on data types.
Floating-point numbers are used for values expressed with decimal points (this is the way to represent fractional values). You want to calculate and compare these values in your sketch.
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.print("The value "); Serial.print(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 then 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.
Although float
and double
are exactly the same on Arduino Uno, double
s do have a higher precision on 32-bit boards and many other platforms. If you are importing code that uses float
and double
from another platform, check that there is sufficient precision for your application.
The Arduino reference for float
: http://www.arduino.cc/en/Reference/Float.
You want to create and use a group of values (called arrays). The arrays may be a simple list or they could have two or more dimensions. You want to know how to determine the size of the array and how to access the elements in the array.
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 5 for more on using switches see Chapter 7 for 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 pulldown 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 pullup 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 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 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 intPIN_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.
You want to manipulate text. You need to copy it, add bits together, and determine the number of characters.
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.print("text1 is "); Serial.print(text1.length()); Serial.println(" characters long."); Serial.print("text2 is "); Serial.print(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();
|
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
datatype 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
datatype 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 through 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 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 equivalent code using a C character array and atoi
to convert to an int
. The Arduino String
version also requires a little 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 (https://github.com/arduino/Arduino/issues) for “String.”
The Arduino distribution provides String
example sketches (File→Examples→Strings).
The String
reference page can be found at https://www.arduino.cc/reference/en/language/variables/data-types/stringobject/.
Tutorials for the String library are available at http://arduino.cc/en/Tutorial/HomePage
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 init(ialize) the string to "Arduino" char stringC[16] = "Arduino"; // as above, but string has room to grow char stringD[ ] = "Arduino"; // the compiler inits the 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
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" }
Text is represented in the Arduino environment using an array of characters called strings. A string consists of a number of characters followed by a null (the value 0). The null is not displayed, but it is needed to indicate the end of the string to the software.
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 http://www.cplusplus.com/reference/clibrary/cstring/ and http://www.cppreference.com/wiki/string/c/start.
You have a string that contains two or more pieces of data separated by commas (or any other separator). You want to split the string so that you can use each individual part.
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]; // a static buffer for computation and 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; // loop 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 https://www.arduino.cc/en/Reference/StyleGuide.
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 http://www.nongnu.org/avr-libc/user-manual/group__avr__string.html for more on C string functions such as strtok_r
and strcmp
.
See http://man7.org/linux/man-pages/man3/strtok.3.html for an online reference to the C/C++ functions strtok_r
and strcmp
.
You need to convert a number to a string, perhaps to show the number on an LCD or other display.
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 in to 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
or 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.print( value); Serial.print(" has "); Serial.print(strlen(buffer)); Serial.println(" digits"); value = 123456789; ltoa(value, buffer, 10); Serial.print( value); Serial.print(" has "); Serial.print(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.
You need to convert a string to a number. Perhaps you have received a value as a string over a communication link and you need to use this as an integer or floating-point value.
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 Ener or Return. For this to work, though, you’ll need to enable the newline option in the Serial Monitor or type some non-digit 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 non digit 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 5):
void loop() { if( Serial.available()) { int newValue = Serial.parseInt(); if (newValue != 0) { blinkDelay = newValue; Serial.print("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: http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html.
There are many online C/C++ reference pages covering these low-level functions, such as http://www.cplusplus.com/reference/clibrary/cstdlib/atoi/ or http://www.cppreference.com/wiki/string/c/atoi.
See Recipe 4.3 and Recipe 4.5 for more about using parseInt
with Serial.
You want to know how to add functions to a sketch, and understand how to plan the overall structure of a sketch.
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
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 250ms on and 250ms off Serial.print("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.
The Arduino function reference page: http://www.arduino.cc/en/Reference/FunctionDeclaration
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.print("The value of x and y before swapping are: "); Serial.print(x); Serial.print(","); Serial.println(y); swap(); Serial.print("The value of x and y after swapping are: "); Serial.print(x); Serial.print(","); 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.print("The value of x and y before swapping are: "); Serial.print(x); Serial.print(","); Serial.println(y); swapRef(x,y); Serial.print("The value of x and y after swapping are: "); Serial.print(x); Serial.print(","); 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.print("The value of x and y before swapping are: "); Serial.print(mypair.a); Serial.print(","); Serial.println(mypair.b); mypair = swap(mypair); Serial.print("The value of x and y after swapping are: "); Serial.print(mypair.a); Serial.print(","); 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 structs as similar to classes, but structs are nothing more than the data they contain.
You want to execute a block of code only if a particular condition is true. For example, you may want to light an LED if a switch is pressed or if an analog value is greater than some threshold.
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.
You want to repeat a block of statements while an expression is true.
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.print("."); } 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.print("."); // this is not executed until 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.print("."); } while (analogRead(sensorPin) > 100);
The preceding code will flash the LED at least once and will keep flashing 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.print("."); if(Serial.available()) { while(Serial.available()) { // consume any pending serial input Serial.read(); } break; // any serial input breaks out of the while loop } }
You want to repeat one or more statements a certain number of times. The for
loop is similar to the while
loop, but you have more control over the starting and ending conditions.
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 } }
Arduino reference for the for
statement: https://www.arduino.cc/reference/en/language/structure/control-structure/for/
You want to terminate a loop early based on some condition you are testing.
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.
Arduino reference for the break
statement: https://www.arduino.cc/reference/en/language/structure/control-structure/break/
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 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 ' ': // newline, safe to ignore break; case ' ': // carriage return, safe to ignore break; default: Serial.print(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
datatype 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.
Arduino reference for the switch
and case
statements: https://www.arduino.cc/reference/en/language/structure/control-structure/switchcase/
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.print("i = "); Serial.print(i); Serial.print(" 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 he 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"); }
Arduino reference for conditional and comparison operators: https://www.arduino.cc/reference/en/language/structure/control-structure/if/
Arduino ASCII chart reference: https://www.arduino.cc/en/Reference/ASCIIchart
You want to see if two character strings are identical.
There is a function to compare strings, called strcmp
(short for string compare). Here is a fragment showing its use:
char string1[ ] = "left"; char string2[ ] = "right"; if(strcmp(string1, string2) == 0) { Serial.println("strings are equal"); }
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"); }
A tutorial on Arduino String
comparison is at http://arduino.cc/en/Tutorial/StringComparisonOperators.
More information on strcmp
is available at http://www.cplusplus.com/reference/clibrary/cstring/strcmp/.
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 pins 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
You want to set or clear certain bits in a value.
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.print("3 & 1 equals "); // bitwise And 3 and 1 Serial.print(3 & 1); // print the result Serial.print(" decimal, or in binary: "); Serial.println(3 & 1 , BIN); // print the binary representation of the result Serial.print("3 | 1 equals "); // bitwise Or 3 and 1 Serial.print(3 | 1 ); Serial.print(" decimal, or in binary: "); Serial.println(3 | 1 , BIN); // print the binary representation of the result Serial.print("3 ^ 1 equals "); // bitwise exclusive or 3 and 1 Serial.print(3 ^ 1); Serial.print(" 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.print("~byteVal (1) equals "); // bitwise negate an 8-bit value Serial.println(byteVal, BIN); // print the binary representation of the result Serial.print("~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 zeroes because it makes it easier to evaluate the effect of bitwise operations at a glance, but the leading zeroes 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.
Arduino reference for the bitwise And, Or, and Exclusive Or operators: http://www.arduino.cc/en/Reference/Bitwise
You want to understand and use compound operators. It is not uncommon to see published code that uses expressions that do more than one thing in a single statement. You want to understand a += b
, a >>= b
, and a &= b
.
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 http://www.arduino.cc/en/Reference/HomePage for an index to the reference pages for compound operators.
3.129.247.196