Chapter 2: The Basics of Arduino Programming
So far, we’ve studied what an Arduino micro-controller is, and how the native coding environment that is provided alongside works. Let’s now step up into learning about how actually to use all of this. This chapter will serve to create a basis for understanding and writing Arduino code, that we can interface into micro-controllers and create wonderful things as we so desire.
To be specific, this chapter will teach you about:
- Variables, constants, and their usage.
- The versatility of included math functions.
- Code comments.
- Coding in “Decisions” and reasoning behind it.
- Looping and reusing bits of code.
We’ve had some experience with Arduino IDE Client and its Web Editor and learned how to use setup() and loop(). This chapter, along with the next one, will deal with syntax, programming practices, and general how-to's related to Arduino’s programming language. We’ll start by introducing the most fundamental components of basic code syntax.
Curly Bracket
The pair of curly brackets are used to tell the IDE where a block of code will start and where it will end; the left ( { ) for the former and the right ( } ) for the latter. You might have seen examples of this in
prior sections, though we would like to mention these are used to make code blocks other than those of functions as well; this will be elaborated on further into this chapter.
Do note that full pairs of brackets must exist (an equal number of left and right brackets means that they’re “balanced”); otherwise, errors such as crypt compiler errors will occur, in which case counting these pairs is a good idea.
Semicolons
A semicolon is used to close off and end a statement of code (i.e., put at the end, like a period for normal sentences) to distinguish it from other lines. Not doing so will result in a fairly easy-to-debug error during compilation, as the IDE will mention where this has occurred.
Another use for semicolons is separating elements in for loops, which will also be discussed later.
Comments
Comments are bits of written text tagged with a syntax (in our case, a mix of forwarding slashes and asterisks) so that the compiler doesn’t read them and add them to the functionality of the program, allowing us to write whatever we want as comments to the actual code. There are two kinds of comments that the IDE has: Block
comments and Line
comments. Block comments are usually big blocks of text that describe the code around it, whereas line comments usually serve as a heading or a nametag for the code near it, telling the reader what it does.
A block comment uses ‘ /* ’ and ‘ */ ‘ to define where it starts and ends, respectively:
/* This is a block comment
This comment can span multiple lines
This type of comment is usually found outside function calls
*/
|
In contrast, a line comment begins with ‘ // ‘ and continues till the line ends. This can be put at the start of a line or somewhere in
between; both of these work:
// This is a single line comment
Serial.println("Hello World"); // comment after statement
|
Using comments to describe your work is a very good habit for readers to be able to discern what you want to do.
Variables
Variables are the meat and potatoes of coding. These are objects that store information for further application by the functionality of the code. Every variable has a unique name used to identify it and thus the information it stores. A good habit for naming variables is using Camel Case (i.e., writing the first word in lowercase, and then the first letter of the second word in uppercase, like a camel’s humps, e.g., oldGreyDog, warmApplePie)
Another good habit is to initialize the variable by assigning it a value, which helps in errors where the variable is called without a value. To do so, we tell it what data type it will be, give it a name, use an equal sign, and then give it an initial value (usually zero).
Datatype
What is ‘int’? It is a datatype
, a kind of badge that says what set of values the variable can hold. Let’s examine some more built-in datatypes commonly used in coding with the Arduino language.
Boolean
This datatype is used for basic logical decisions at least as old as Shakespeare: true or false. This datatype‘s size is one bit. These are the only values this set has.
This code declares the variable myBool as being a boolean, with an initial value ‘true.’ As you might expect, this datatype is extremely
common in Arduino, and all comparison operations produce (or ‘return’) a Boolean value.
Byte
A byte is eight bits, i.e., it is eight binary digits long, capable of referring to integer values from zero or 255. The keyword for this datatype is ‘byte.’
Integer
By far the most used data type, we use this to store integer values; the total number of unique values allowed is 2^16 or two bytes (sixteen binary digits long): from –32768 to 32767. We can also have it be unsigned, via adding the keyword ‘unsigned,’ to have this range go from 0 to 65,535. The keyword for this datatype is ‘int.’
int mySignedInt = 25;
unsigned int myUnsignedInt = 15;
|
Long
Similar to the previous datatypes, the long datatype can store four bytes (thirty-two binary digits) worth of integer numbers, from –2,147,483,648 to 2,147,483,647. ‘unsigned’ can be used as well for the same effect, going from 0 to 4,294,967,295. The keyword for this datatype is ‘long.’
long myLong = 123,456,789;
|
Do note that this consumes more memory (double that of integer), so take care to not use it unless it is necessary to store numbers that large.
Double and Float
These datatypes are ‘floating-point’ numbers, i.e., they allow us to go between integers and into decimal points. Both of these datatypes can store values ranging from -3.4028235x10^38 to
3.4028235x10^38.
Often (depending on what programming environment we’re using) these datatypes differ in the amount of decimal point accuracy, with float having an accuracy of six to seven decimal places as compared to double’s fifteen. However, for the Arduino platform, they both have the same accuracy of six to seven decimal places.
A few things to note: one, the precision inaccuracy may result in some weird values being returned to you, such as 6.0 divided by 3.0 giving a result of 1.9999999. two: floating-point/decimal computation is much more resource-intensive than the comparatively simpler integer math operations. For these reasons, do take to use these datatypes only when necessary.
The double datatype’s keyword is ‘double,’ while the keyword for the float datatype is ‘float.’
double myDouble = 1.25;
float myFloat = 1.5;
|
Character
Characters are generally referred to as the glyphs of alphabets and various other symbols, i.e., characters. However, on the back end, these characters are a numerical value that refers to the ASCII chart; we can store a character as either of these. The keyword for this datatype is ‘char’
char myChar = 'A';
char myChar = 65;
|
Both lines of the above code declare a character ‘myChar’ storing capital A. We can store a group of these characters using a method that will be discussed later, which will let us store words and sentences.
Arrays
An array is a storage structure that assigns an index value to some data and allows us to store multiple data entries into one structure,
with the caveat that it must be of one datatype that is declared alongside the array during initialization. A few ways to define arrays are as follows:
int myInts[10];
int myInts[] = {1, 2, 3, 4};
int myInts[8] = {2, 4, 6, 8, 10};
|
Notice that the name has a pair of square brackets, which is how we tell the environment that we’re creating an array.
- The first line defines an uninitialized array that can store ten values in a row but doesn’t store any at the moment. Be careful of declaring uninitialized arrays, as its interactions with memory spaces can be quite weird.
- The second line initializes an array with an undefined size but with a defined number of values. In this case, the array matches the size of the row of values that we defined.
- The third line initializes an array with both a defined size and values. In this case, the defined values are assigned to the first spaces in the array, but the last three are not. Care must be taken in this case for the same reason as the first declaration, which will be demonstrated shortly.
Let’s look at how actually to use arrays:
We access a value in an array by writing the array name, the square brackets, and the value’s index inside the bracket, such as follows:
int myInts[] = {1, 2, 3, 4};
int myInt = myInts[1]; //counting from 0, myInts contains the second value in the array, ‘2.’
|
The above code notes that for most programming languages, we count indexes starting from zero. The following should serve as further clarification:
int myInts[] = {1, 2, 3, 4};
int myInt0 = myInts[0]; // contains 1
int myInt1 = myInts[1]; // contains 2
int myInt2 = myInts[2]; // contains 3
int myInt3 = myInts[3]; // contains 4
|
Now let’s look at what happens when arrays aren’t initialized. Run the following in the Arduino IDE, and you’ll notice that the Serial Monitor displays a jumbled mess that is definitely not integers since they were never initialized.
int myInts[5];
Serial.println(myInts[0]);
Serial.println(myInts[1]);
Serial.println(myInts[2]);
Serial.println(myInts[3]);
Serial.println(myInts[4]);
|
Fortunately, providing values to uninitialized indexed array space is the same as providing a value to any variable, as follows:
int myInts[2];
myInts[0] = 0;
myInts[1] = 1;
|
Another possibility is multi-dimensional arrays, which are technically arrays stored within arrays. Two ways describing how we’d define one are as follows:
int myInts[3][4];
int myInts[][] = { {0,1,2,3}, {4,5,6,7}, {8,9,10,11} };
|
They’re accessed in the same way as you’d expect:
int myInts[3][4];
int myInts[][] = { {0,1,2,3}, {4,5,6,7}, {8,9,10,11} };
|
Now let’s use what we’ve learned about arrays and characters, and combine them into something that will let us write sentences:
Character Arrays
Character arrays are arrays that store characters. They’re initialized the same way as regular arrays:
char myStr[10];
char myStr[8] = {'A,’ 'r,’ 'd,’ 'u,’ 'i,’ 'n,’ 'o,’ '\0'};
|
Character arrays are often referred to as Strings
of characters. The previous code describes how we’d make a string that says ‘Arduino.’ The ‘\0’ character represents a null which terminates the string by being at the end of it. This is referred to as Null Termination
, and it helps the IDE and its functions determine where the string, stored in memory space, ends; otherwise, these functions would continue until some other null character is encountered and produce a bunch of garbage data meanwhile.
Simpler ways to declare strings are as follows:
char myStr[] = "Arduino";
char myStr[10] = "Arduino";
|
The first line makes an array that resizes to contain 9 characters - the word ‘Arduino’ and the null terminator, whereas the second line’s array leaves space for them to be put in their respective positions.
Note that a string object exists, which will be discussed outside of this chapter, but we’ll be using character arrays more often than not.
Let’s now look at something that allows us to lock a value in stone, providing some sort of bedrock for functions to refer to and work.
Constants
A constant remains constant, i.e., any value that it is initialized with does not change during runtime. There are two ways to declare constants; by either using the keyword ‘const’ for it or using ‘#define.’
The first method, using the keyword ‘const,’ changes the variable's behavior, similar to how ‘unsigned’ works. The ‘const’ keyword
serves to make the variable read-only and cause an error if there is code that tries to change the variable during compilation. An example is as follows:
We often use ‘const’ to declare constants, though we might prefer to use the other method if memory is a concern.
The second method, using ‘#define,’ allows the user to name the constant for use before the code is fully compiled, allowing you to reduce the amount of memory (by skipping allocation of the name) that is required. This trick proves useful in micro-controller models with smaller memory sizes, such as Arduino Nano.
An important thing to keep in mind while using the latter method to declare a constant is if the name we’re using for it is used elsewhere. For example, some other array or variable or even another constant, the original constant’s name will be replaced by its value instead. A good idea to counteract this is to have completely uppercase names for constants and regular Camel Case for other names.
‘#define’ also does not need to use a semicolon to terminate its line, as follows:
We’ve defined objects and initialized them with values. Let’s now look at how we can use these values and work with them, using basic arithmetic.
Arithmetic Functions
The four basic arithmetic processes, summation, subtraction, multiplication, and division, are allowed for by most if not all programming languages. Arduino provides operators - characters that when used in code refer to these arithmetic operations. These processes only work between operands of the same datatype; however, an int will not add to a float. We can create an exception to this rule using casting, which will be discussed in a short while. For now, let’s look at how to implement these basic functions:
z = x + y; // calculates the sum of x and y
z = x - y; // calculates the difference of x and y
z = x * y; // calculates the product of x and y
z = x / y; // calculates the quotient of x and y
|
Sometimes, we might only need the remainder of the division (in the case of integer operations), and we’ll use another tool for this, called the modulo operator ( ‘%’ ). An example of how this would work is as follows: when 5 is divided by 2, the result is 2.5. If we do not go into decimal places, this would instead return a remainder of 1. This value is the result of the modulo operation between these two numbers.
Another nifty syntax tool we can use is compound assignment operators, which combine the arithmetic operation with the assignment (or reassignment) operation of the variable. Here’s a list of these operators:
x++; // increments x by 1 and assigns the result to x
x--; // decrements x by 1 and assigns the result to x
x += y; //increments x by y and assigns the result to x
x -= y; //decrement x by y and assigns the result to x
x *= y; //multiplies x and y and assigns the result to x
x /= y; //divides x and y and assigns the result to x
|
Alongside both of these, full-fledged functions exist to help with performing common math operations and calculations. Here’s a list of those functions:
abs(x) // returns the absolute value of x
max(x,y) // returns the larger of the two values
min(x,y) //returns the smaller of the two values
pow(x,y) // returns the value of x raised to the power of y
sq(x) // returns the value of x squared
sqrt(x) // returns the square root of the value
|
Of particular note and usefulness, however, is the ability to compare values. Let’s continue and learn about operators that allow us to do so:
Comparison Operators
The Arduino language contains operator characters that allow us to make comparisons between two objects and return a Boolean value of either one or zero to confirm true or false, respectively. Here’s a list of those operators in use:
x == y // returns true if x is equal to y
x != y // returns true if x is not equal to y
x > y // returns true if x is greater than y
x < y // returns true if x is less than y
x >= y // returns true if x is greater or equal to y
x <= y // returns true if x is less than or equal to y
|
Let’s now look at logical operators, i.e., the operators for AND, OR, and NOT logical functions:
Logical Operators
‘AND,’ ‘OR,’ and ‘NOT’ are three operators that refer to their respective logical operations. ‘AND’ checks whether both values (or the Boolean results of some comparison statement) is true and returns true, ‘OR’ checks for either value of the previous case and returns true, and ‘NOT’ returns the opposite Boolean state of the value provided. Examples of these in action are provided below:
(x > 5 && x < 10) // true if x is greater than 5 and less than 10
(x > 5 || x < 1) // true if x is greater than 5 or less than 1
!(x == y) // returns true if x is not equal to y
|
Let’s now look at what we referred to previously as casting.
Casting
The cast operator changes the datatype of a variable to a different one, allowing it to be used where there’s a restriction of datatypes, like for arrays and arithmetic operations. Values converted via this operation are truncated to the relative decimal instead of being properly rounded up or down. For example, 8.7 being truncated to 8 instead of being rounded to 9. Thus, it is good to cast integers to float values instead of the other way around.
Casting is done by adding parentheses before the variable to be casted and writing the required datatype in it. An example:
int x = 5;
float y = 3.14;
float z = (float)x + y;
|
We need our program to make logical decisions as there’s not a lot one can do without some sort of logic behind it. Since we’ve learned how to compare values and draw some logical conclusion, let’s move on to using these tools and code in decision-making capabilities.
Decision Making
Decisions are often a matter of whether something will happen or not. Most programming languages, Arduino included, include an ‘if’ statement to that end. This function will check a statement inside a pair of parentheses, and if it is true(i.e., the Boolean statement returns a ‘TRUE’ Boolean value), it will run code placed inside curly brackets, as follows:
if (condition) {
// Code to execute
}
|
We can also use an ‘else’ statement when the conditional statement being checked returns false. The syntax is as follows:
if (condition) {
// Code to execute if condition is true
} else {
// Code to execute if condition is false
}
|
The else statement can also check a new statement and run if it returns a ‘TRUE’ Boolean value. Note that only the first block of code for which the respective statement returns true will execute, and the rest of the else statements will be ignored. The following code compares two variables, and the results of that comparison allows the ’if-else’ statements to function:
if (varA == varB) {
Serial.println("varA is equal to varB");
} else if (varA > varB) {
Serial.println("varA is greater than varB");
} else {
Serial.println("varB is greater than varA");
}
|
‘If-else' statements can get messy and expansive fairly fast, relative to how many statements we’d need to check. We use ‘switch/case' statements when we have multiple conditions (more than two or three).
‘switch/case’ statements take in a value inside parentheses (here ‘var’) and go down the list of cases, comparing the cases' value. If it finds a match, the statement inside the case runs. After its execution, we use ‘break’ to break the switch loop. If there is no match, a default case runs and then uses ‘break’ to end execution. The syntax for it is as follows:
switch (var) {
case match1:
// Code to execute if condition matches case
break;
case match2:
// Code to execute if condition matches case
break;
case match3:
// Code to execute if condition matches case
break;
default:
// Code to execute if condition matches case
}
|
We often need to have code repeating during runtime so that it functions continuously. We use loop structures for this. Let’s look at how to make loops now:
Looping
There are three kinds of loops: ‘for,’ ‘while’ and ‘do/while.’ These work in slightly different ways. Let’s start by looking at ‘for’ loops.
The ‘for’ loop
continuously repeats a block of code a specific number of times. This number is assigned to a variable that continuously changes per iteration of the loop which can also be used to access particular indexes of matrices.
There are three parts of a ‘for‘ loop’s statement: initialization, condition, and increment.
for (initialization; condition; change) { }
|
- The initialization creates a variable that the loop uses and increments (or decrements). We can initialize other variables here too, but we’d advise you to keep your code lean and only initialize variables that the loop uses.
- The condition checks the current state of the loop variable against some conditions. As long as it returns true, the code continues.
- The increment changes and updates the current state of the
loop variable.
An example of a ‘for’ loop in action is as follows. The statement is set up such that the loop increments a newly initialized variable i, causing the loop to re-execute ten times:
for (int i = 0; i < 10; i++) {
// Code to execute
}
|
The ‘while’ loop
checks whether a statement is true or not and continues running while
it returns a Boolean true. Care must be taken with this statement that the condition doesn’t infinitely return true, implying an infinitely repeating loop. The syntax for this loop is as follows:
while (condition) {
// code to execute
}
|
An example of this loop in action would be as follows. Good practices implore you to have the conditional be a comparison statement:
int x = 0;
while (x < 200) {
// code to execute
x++;
}
|
The ‘while’ loop checks the condition before execution. If we want it to be executed at least once, we’ll use a ‘do/while’ loop
. This means that in the loop the condition is checked after the block of code is run. The syntax to follow is like so:
do {
// code to execute
} while (condition);
|
Similar to the previous, it would be a good idea to use a comparison statement as a condition. An example of this loop is as follows:
int x = 0;
do {
// code to execute
x++;
} while (x < 200);
|
Functions
Functions are blocks of code that perform a specific task. They are given unique names to identify them and called when needed. Programming libraries often have hundreds if not thousands of pre-defined functions, and we can create our own if we need to, as follows:
type name (parameters) { }
|
The declaration of a function requires us to start with defining what datatype it will return. If it will not return a value, this function has a ‘void’ datatype. Moving on to naming it, use a name that describes what it does or when it activates, such as ‘ledRedOn’ or ‘onRedCall,’ following Camel Case.
Next, a pair of parentheses encase the parameters that we need the function to require and use, to control its functionality. These parameters are passed to and used by the function within its logic to return an answer respective to the parameters. Multiple parameters can be added by separating them using commas.
A pair of curly brackets come next, and within them, we write the code block for the function to define its behavior.
A few examples of function syntaxes are as follows:
void myFunction() {
// Function code
}
void myFunction(int param) {
// Function code
}
int myFunction() {
// Function code
}
int myFunction(int param) {
// Function Code
}
|
- The first function does not return anything, and therefore of a void datatype, nor does it require. We use this kind of function when we do not need it to return a value or pass a parameter to it; it is entirely self-sufficient.
- The second function takes in an integer datatype parameter for its code block to use.
- The third function can return an integer datatype value but does not require a parameter to be inserted.
- The fourth combines the second and third kinds of functions and their respective functionalities.
To return a value from a function, we use the keyword ‘return.’ Let’s look at a simple example of a function:
int myFunction() {
var x = 1;
var y = 2;
return x + y;
}
|
Any variables that are initialized in the function are only usable in that domain, and cannot be called by anything outside of it (this also means that variables with the same names can exist uniquely inside different functions, outside of each other’s reach). The following code block demonstrates this:
int g = 1;
void function myFunction1() {
int x1 = 2;
}
void function myFunction2() {
int x2 = 3;
}
|
The variable ‘g’ is accessible globally, i.e., by both functions, but ‘x1’ and ‘x2’ can only be accessed by their respective functions, not outside of these domains.