Basic Programming for the Arduino - uraich/IoT4AQ GitHub Wiki
As we have seen in the chapter on the CPU cards, we must write our programs in the C/C++ programming language, supported by Arduino SDK. When programming for the ESP32 DevKit we must select the corresponding ESP32 board under Tools -> Board and we must make sure that the correct serial port is selected for code uploading under Tools->Port. When we start writing a new program File->New Sketch (and Arduino program is called a sketch) this is what we will see:
The structure of a micro-controller program is always the same. There is a block of code which initializes the hardware and maybe some variables. This code, the function void setup(), is executed only once. Then there is the void loop() function, which is executed repeatedly. All instructions in this function (for the moment there are none) are sequentially executed and when we get to the end of the code, we jumps back to its beginning and execute the code again. This is done forever (or until we switch off the machine or load another program). We see more about how functions work, later in this chapter.
Variables can be seen as locations in memory into which we can store values. These locations are named. Let's say we want to write a program that adds 2 integer numbers, then these numbers will be stored in variables that must be declared before being used.
Variable have a type and a name and can be assigned a value. Here is a list of some C++ types:
- int: integer value
- float: floating point (real) value. Here the floating point value is coded in 32 bits
- double: double precision floating point values coded in 64 bits.
- bool: boolean values that can only take the values true or false. In fact boolean variables can have the value zero, in which case they represent false or any non-zero value, which is interpreted as true.
- char: characters. These are letters coded in ASCII code. There are quite a few more types, and in fact you can define your own types, but this we will see later.
Once the variable is declared, we can assign it a value:
int a,b,c; // The double / designates a comment. A code line is always terminated be a ";"
a = 5;
b = 7;
c = a+b; // Calculates the sum of a and b and assigns the variable c with the result.
// Of course you also have the operators "-", "*", "/" for subtraction, multiplication and division.
Now that we know how to perform some simple arithmetic, how can we get at the results? The Arduino has functions for a Serial device onto which we can print the result. Before using it we must define the speed of data transmission, after that we can print:
void setup() {
Serial.begin(115200); // open the serial port with a speed of 115200 baud (bits per second)
while (!Serial) // as long as the Serial port is not ready, wait for 10 ms (we will see loops in a minute)
delay(10);
}
This code, as it is only executed once, goes into the setup() function.
Then we can put our calculations into the loop function:
void loop() {
int a,b,c; // declare the variables a,b,c as integers
a = 7;
b = 5;
c = a + b;
Serial.print("The result of the addition: "); // This is a text string. With print, the next character is printed on the same line
Serial.print(a); // Prints the contents (value) of variable a
Serial.print(" + "); // Again a text string
Serial.print(b); // the value of b
Serial.print(" = "); // again text
Serial.print(c); // and finally the result of the addition which was saved in variable c
delay(10000); // wait for 10s (10000 ms) before we do the calculation again
// remember that the code in loop() is repeated
}
Once you finished editing the program it must be compiled into binary code for the ESP32 processor. Before doing this, the compiler checks that your source code is syntactically correct and it will print error messages if not, abandoning generation of the binary code. Did you mistype a variable name? Did you forget a semi-colon at the end of the statement?
The compiler is started with the top left button on the Arduino SDK (the one with the tick mark). The button to the right, the one with the right arrow, will compile, upload and start the program, resetting the ESP32. To see the serial output push the button to the far right, the one with the circle.
The first few lines on the serial monitor are boot messages from the ESP32. After that you see the output of the program. Even though statements like c = a + b; look like a mathematical expression, they are not. You can see that easily when looking at the perfectly valid statement
a = a + 5;
which makes no sense when interpreted as a mathematical equation. In C++ the statement means:
-
get the value of a (which in our case is 7)
-
add 5 to it
-
save the result in the variable a Therefore the value of variable a will be 12 after execution of the statement. Adding a constant value to a variable is very common and C++ provides an abbreviation:
a += 5;
The same is true for subtraction, multiplication ...
Even more common is incrementing or decrementing a value (by 1) which can be written
a++;
or
a--;
when decrementing the value.
In our first code we have already seen a conditional statement, which was not eexplained:
if (!Serial)
delay(10);
Conditional statements have the form
if (condition) // a single statement is executed when the condition evaluates to true
statement;
or
if (condition) { // the true condition results in the execution of a block of statements
statement 1;
statement 2;
...
}
More complex conditional statements can be implemented with:
if (condition) {
statements ...
}
else if (condition) {
statements ...
}
else {
statements ...
}
In the case of
if (!Serial)
delay(10);
Serial is a boolean variable which turns true when the serial port gets ready. The "!" mark designates inversion. Therefore the statement says: if the serial port is not ready, wait for 10 ms. It may be ready by then. Conditions often imply comparison and there is a series of comparison operators: "<", ">", "==", "!=" for smaller, bigger, equal and not equal.
Normally program statements are executed sequentially but often a series of statements must be executed many times. This is accomplished by loops. Since loops are a very important concept in programming several types of loops are available in C++. We look only at the most common ones.
The while loop is executed as long as a condition is fulfilled. Its layout is
while (condition) {
statements ...
}
Here is a simple example calculating the factorial of 10 using a while loop:
count = 1;
factorial = 1;
while (count < 11) { // it is executed up to 10
factorial *= count; // this is the same as factorial = factorial*count
count++;
}
Serial.print("10! = ");
Serial.print(factorial);
You can of course easily create an endless loop when setting the condition to true:
while (true) {
statements...
}
You can break out of the while loop when a certain condition is true within the while loop. We could have written the above example also like this (of course factorial and count must be declared to be integer variables before):
count = 1;
while (true) {
factorial *= count; // this is the same as factorial = factorial*count
count++;
if (count > 10)
break;
}
Serial.print("10! = ");
Serial.print(factorial);
It is very common that a loop must be executed a fixed number of times (see the example above, where we execute the loop 10 times). In this case we can use a for loop, which has the following structure: for (i=0; i<10;i++) { statements ... } We could have written the factorial code like this:
factorial = 1;
for (i=1; i<11;i++) // again i must be declared to be of type integer
factorial *= i;
We have already seen two functions: void setup() and void loop(). Now we will discuss how you can write your own functions. Just like variables, functions have a type and must be declared before you can use them. Generally, a function has a number of input parameters and an output value, which can be of any type. The functions we have seen so far are declared void, which means that they do not return any value and the bracket () is empty, which means that they do not use any parameter. Now let's look at a simple example in which we want to calculate a straight line. The mathematical formula is:
y = mx + b
where m is the slope and b is the intercept of the y-axis. In order to calculate such a line we need a function that takes m, b and x as parameters and that returns y:
float line(float m,float b,float x) {
return (m*x + b);
}
Now we can calculate and print the value of y for 50 different values of x:
void loop() {
float slope = 2.0; // declares and initializes the variable
float y_intersect = 1.0;
float x;
for (int i=0; i<50; i++) {
x = 0.1 * i; // x takes values from 0 .. 10
Serial.print("y = ");
Serial.println(line(slope, y_intersect, x);
delay(10); //
}
delay(10000); // wait 10s before doing it again
}
Nicer than just seeing the number fly by on the serial monitor, is plotting them. You can do that through the second top right button on the Arduino SDK, the one that is shown with the symbol of an electronic signal.
If you have a number of values of the same type, then you can assembles these into arrays.
int my_array[10]; // defines an array of 10 integer values.
After declaration you can access the individual elements through an index. In the above example the index will run from zero to 9 (in contrast to our usual counting, which start by 1, in most programming languages we start counting from zero) Now we could fill our array with:
for (i=0;i<10;i++)
my_array[i] = 2*i + 3;
Of course we can also recuperate the values from the array with
a = my_array[7]; // getting the 8th value from the array (remember, we start counting from zero!)
In C, strings are implemented as character arrays. Characters are coded in bytes (8 bit values) and a C string is an array of characters terminated by a zero byte. There is a series of functions in the C library that allow to
- determine the length of a string (excluding the terminating zero: strlen(charString)
- copy a string: strcpy(destination, source)
- copy a string but not more than n characters: strncpy(destination,source,n)
- compare strings: strcmp(str1,str2)
Pointers are a concept that is usually very difficult to grasp by programming beginners. A pointer is indicated by a '*' before the name of the variable. In this case the variable does not contain the value but it contains the address (position in memory) where to find the value. When you want to access the variable itself then you use the de-referencing operator '*'. Example:
int a = 5; declare an integer variable and initialize it with the value 5
int *aPtr; declare a pointer variable, pointing to an integer
aPtr = &a; initialize the pointer variable with the address of a in memory
int b = *aPtr; pick up the value to which aPtr points and copy it into the variable b. b will now contain 5
Example with character strings:
char hello[] = "Hello World";
char *helloPtr; // declaration of the pointer variable (it is not initialized yet)
char c;
helloPtr = hello; // helloPtr now contains the memory address of the first character of the hello[] string
c = *helloPtr; // get the character that is pointed to by helloPtr and put it into the variable c
// c will now have the value 'H'
Serial.print(c);
// if you now increment the pointer, it will point to the next element in the character array, which is 'e'
helloPtr++; // The pointer is incremented, the string that it points to is not modified
*helloPtr = 'a'; // We modify the memory location where helloPtr points to. This means the 'e' is replaced by 'a'
We can also easily use the pointer to traverse all the character array, print all the characters and stop, when the terminating zero is found:
char *hello = "Hello World!"; // This time we define a pointer to a constant character array
// We could also define it as we did before
char *helloPtr = hello; // hello and helloPtr now point to the same memory location, they are equal
while (*helloPtr != 0)
Serial.print(*helloPtr++); // print the character that helloPtr points to and increment the pointer afterwards
// it now points to the next character
// the while loop stops when the terminating zero is seen
You can do the same thing with other data types like integers of floats. In this case the pointer is always incremented to point to the next element. While for characters we increment by one memory location, in case of integers, which use 4 bytes instead on 1 used by characters, we increment by 4.
Question: What will be the result of the following code?
char h1[] = "Hello World!";
char h2[] = "Hello World!";
if (h1 == h2)
Serial.println("The strings are equal");
else
Serial.println("The strings are not equal);