Overview

Before diving into microcontrollers, you will explore basic computer programming topics. This will provide the necessary context for writing Arduino code.

C++ Programming Language


Nearly four decades old, C++ is one of the most widely used general-purpose programming languages. It is highly versatile, utilized in operating systems, video games, internet browsers, embedded software, and more!

C++ is frequently used in embedded systems programming, especially because it easily interfaces with hardware. You can create and delete data in memory. You also have great awareness and control over where and how the data is stored. This is particularly useful when memory, storage, and processing power are limited. C++ programs can be lightweight and incredibly efficient.

Integrated Development Environment (IDE)

An integrated development environment (IDE) is a suite of applications used for programming. It includes a source-code editor, compiler, debugger, and build automation tools.

When you create a program, you will write and edit it in the source code editor. Then, you will employ the help of the compiler to convert the source code (C++ for example) to machine code, which is readable by the computer. You may also run a debugger to troubleshoot errors while the program is running. These are all a part of the IDE.

Popular IDEs include Microsoft Visual Studio, Eclipse, and IntelliJ IDEA. There are plenty more to choose from, some of which are customized to specific programming languages.

Compilers, Assemblers, and Linkers. Oh My!

You write code in a programming language like C++, but that is not what the computer executes as instructions. The computer sees only 1s and 0s, or binary machine code. So how do we bridge the communication gap between you and the computer? We use a program called a compiler to translate your C++ code into machine language.

We tend to use “compiler” as an umbrella term to describe what is actually several programs working together to create machine code. In reality, the compiler turns a high-level language like C++ into assembly code.

Assembly language is a low-level programming language based on a computer’s processor. Each assembly statement corresponds directly with a machine instruction or set of instructions. There was a time when most programmers wrote code in assembly language; this is no longer the case.

The assembler converts the assembly language into machine code. We’re not done, though! One last program called a linkertakes the machine code files and “links” them together into an executable file. You might recognize executables as files with the “.exe” extension on Windows. The executable is ultimately what the computer uses to execute your program.


Concepts

Built-in types


We can represent integers with int, truth values with bool, and other types exist too.

int a = 5;

bool k = true;

“a” is an object/variable of type integer. k is an object/variable of type boolean.

Variables


Variables point to values or point to other variables.

int a = 5;

int b = a;

variable a holds the value 5. Note that because we declared a as an int, 5 is an int. b points to a, therefore, b points to 5. We can modify the value of a variable.

a = 5 + 2;

“a” now gets 7. Note that we did not add an int in front of “a” this time, because we already declared “a”.

Functions


A function has a name and takes as inputs a number of values (could be zero). You define a function with the return type (output type), followed by the function name, followed by the parameters (inputs). After the header, you write the function logic. Ex:

void printName(string name){ #function header
	cout << “hi ” << name; #print syntax to output console
} 

You call the function like this:

printName(“bob”)
output: “hi bob”

Notice that there is no return statement and the return type is void. This is because the function does not output a value. It does print a statement, but that is internal to the function, which does not return anything. Below is an example of a function with a return statement.

int square(int val){
	return val * val;
}
						

The function takes as input an integer val, and returns (or outputs) the square of val.

Pass by Value vs Pass by Reference


The variable whose value you pass in as the function’s parameter may be modified or not, depending on how you write your function. This can be a difficult idea to understand, so let's see an example.

int squareModify(int & val){
	return val * val;
}
int square(int val){
	return val * val;
}
int main(){
     int k = 5;
     cout << k << endl; #k is 5
     square(k);
     cout << k << endl; #k is still: 5
     squareModify(k);
     cout << k << endl; #k is now: 5
}
					
					

What happened? Initially, k is 5. Then you call square, which takes in a copy of k. So when you do val * val you are not squaring k itself, but a copy of it. This is called pass by copy. That’s why k is still 5. But when you call squareModify, its input is a reference to k as denoted by int & val. So, val in squareModify points to k in main. When you square val, you modify k. That's why k becomes 25. This is called pass by reference.

Conditional Logic


Now, what if we wanted different outputs depending on our function's inputs? An if-else statement does this.

if (condition 1){ 
	Execute if true
}
else
Execute if condition 1 is false
				
You can also have an if-else if-else if-...-else statement to check for multiple conditions.

if (condition 1){ 
	//Execute if true
}
else if (condition 2) {
	//Execute if condition 2 is true and condition 1 is false
}
else 
	Execute if all is false
					

Here's an example used in a function:

bool isNegative(int k){
	if (k < 0){
		return true;
	}
	else{
		return false;
	}
} 

User-Defined Types



Remember how we talked about built-in types? You can define your own types. EX:

struct Person {
    string name;
    int age;

    // Method inside struct
    void introduce() {
        cout << "Hi, I'm " << name << " and I'm " << age << " years old." << endl;
    }
};

int main() {
    Person p1;
    p1.name = "Alice";
    p1.age = 25;

    p1.introduce();  // Call the method

    return 0;
}
						

Variable p1 is of type Person. We also call p1 a Person object. When we define our own type/object, we often assign data fields and member functions to that object. Here, Person has a name and an age. We can access and modify these data fields, which are formally called attributes. p1.name returns the name of p1. A function that you call on an object is called a method. p1.introduce() calls this function. Note that no parameters are needed. name and age are already part of p1 so they are implicit inputs to the function.

Basic Function Practice


Before we get started, you will need to choose an IDE. For this programming guide, any online C++ IDE is fine. You can use this: https://www.online-ide.com/online_c++_ide

You will write six functions. The requirements for each function are listed below, along with the solutions. Though do your best to come up with the solutions yourself first.

Copy the following code to serve as a template for the first five practice functions:


#include #include void echo() { // Write code here // Print something to the console } bool isEven(int num) { // Write code here return true; // Placeholder } unsigned pow(unsigned base, unsigned exp) { // Write code here return 0; // Placeholder } double avg(int arr[], int size) { // Write code here return 0; // Placeholder } void fib(unsignedint) { // Write code here return; // Placeholder } void swap(int& num1, int& num2) { // Write code here return; // Placeholder } int main() { // You can test your functions here return 0; }


1. Echo


Hint

These objects may be helpful: cout, cin


Echo Solution

#include 
#include 
using namespace std;

void echo() {
	string echo;
	getline(cin, string);
	cout << string;
}
						


2. Even or Odd?


Hint 1 When dividing an even number by 2, there is no remainder. However, dividing an odd number by 2 results in a remainder of 1

Hint 2 The modulus % operator returns the remainder of a division operation.

Even Odd Solution
bool isEven(int num) {
	if (num % 2 == 0) {
		return true;
	} 
      return false;
}
					


3. Number Power



Hint 1 Remember that raising the base to the power of an exponent is the same as multiplying the base by itself the number of times equal to the exponent.

Hint 2 Consider using a for loop.

Number Power Solution
unsigned pow2(unsigned base, unsigned exp) {
	unsigned num = base;
	for (int i = 1; i < exp; ++i) {
		num = num * num;
	}
	return num;
}
					

4. Take the Average



Hint 1 Recall the formula for calculating the mean of a number set.

Hint 2 Consider using a for loop.

Take the Average Solution
double avg(int arr[], int size) {
	double sum = 0;
	for (int i = 0; i < size; ++i) {
		sum = sum + arr[i];
	}
	double average = sum / size;
	return average;
}
					

5. Fibonacci Sequence



Hint 1 Recall the pattern of the Fibonacci Sequence.

Hint 2 Consider using a for loop.

Fibonacci Sequence Solution
void fib(unsigned n) {
    int a = 0;
    int b = 1;
    if (n == 0) {
        return;
    } else if (n == 1) {
        cout << a;
    } else if (n == 2) {
        cout << a << ", " << b;
    } else {
        cout << a << ", " << b << ", ";
        for (int i = 2; i < n; ++i) {
            int temp = b;
            b = a + b;
            a = temp;
            cout << b;
            if (i < n-1) {
                cout << ", ";
            }
            
        }
    }
}
					

6. Swap Integers


Hint 1 Use int assignment operator =

Swap Integers Solution
void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}