Introduction: Arduino 101

To get started with Arduino, you'll need some basics. In this tutorial, we'll go beyond a little blinky light and teach you about:

  • Variables
  • Input and Output pins
  • Sensors and reading data
    • Mapping results
  • Functions

In another tutorial, I'll cover classes and why we might use them.

But, we might still make a little light blink... Just because... Hello, World! Ok. Bad joke.

This will be fairly simple, but it will progressively get more difficult. I suggest breaking it in to parts and just going through one section at a time.

To do this, you'll need a Tinkercad account (open www.tinkercad.com) and use Google to sign up (makes things easier) or just sign up with your email. Let's get started!

I'll embed the circuit design here for reference. It is basic, but it isn't meant to be about electronics design but about interacting with various components using code features in Arduino.

Step 1: Environment Setup

Let's setup the environment. Throughout this tutorial, we wont need much other than an Arduino. Check out the pictures above, but you'll need to:

  1. Use the drop-down menu to change to All components.
  2. Type Arduino in the search and put an Arduino board by click-dragging on to the canvas
  3. Open the Code menu and change to Text (you can clear the warning that appears)
  4. Clear all the code, leaving the void setup() and void loop() functions in place (see the image for what the code panel should look like)

Step 2: Arduino Basics

Let's talk conventions.

A convention is something all programmers do to keep their code clean and organised. You can adopt a certain convention that is published, or you can follow a general guide and define your own. If you're starting out, it is best to have good code from the outset. If you've been programming a bit and have poor code management, then now is the time!

I suggest reading this: http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#main. These C++ core guidelines will help you understand more.

These are my (very basic) conventions.

  • Camel case for writing variable names - First word lower case, remainder with a first letter capital, because... camels.
int someVariableName;
// I find this better than:
int some_variable_name;
  • Minimise spaces between symbols but maintain legibility
int someVariable = 9;
// Rather than
int someVariable=9

In my opinion, the second is much less readable.

  • Declare one, maybe two variables maximum on one line. Never two separate instructions on a line
int someVar, anotherVar;

// Gross. Debugging, please?!
int someVar; someVar = someVar - 10;

// Much clearer
int someVar;
someVar = someVar - 10;
  • Use constants where possible/beneficial
  • Code to describe functions and where necessary, but clearer code is better
  • Functions with capitals for first letter of all words
void MyGreatFunction();
// It's clear this is different to a variable in a call, now

int someVar = MyGreatFunction();
  • Clear indenting at all times using 4 spaces or 1 tab.
void MyGreatFunction() {
	int someVar = 10;
}

// Rather than this...

void MyGreatFunction()
{
int someVar = 10;
}
// It's more compact, too!

This is be no means exhaustive, but it gives most of the basics for what we need. Stick to your own styles and you'll be able to create great code!

Step 3: Characteristics & Features

If you haven't seen an Arduino before, or aren't familiar with it, let's look at the characteristics of the board and features of the environment.

First, the Arduino is a prototyping board with a microprocessor attached that is capable of controlling inputs, outputs and talking to other devices. It uses C++ as a core programming language. Take a look at the image. There are:

  • Digital pins for logic input and output. This can be a simple True / False, or it can be used as an analog pin with a little code. You can also attach lights and other devices that can be turned on and off with 5v power. We'll use these for a number of things. You can extend these with other devices, too.
  • Analog pins are read/write pins too, but they are not digital and don't provide a logic output or input. These are good for resistive devices like temperature or light sensors.
  • Power and ground - these are provided so you can route power to a device without using up a digital pin set to high. We often use a breakout board to extend these pins as they are very useful and easily extended

That's the board. For the code, we have two critical functions. Let's look at these.

void setup() {
}

This function runs once every time the Arduino loads, like when you plug it in to a USB outlet or you connect a battery. It only runs once and cannot be called (without potential problems).

void loop() {
}

The loop runs endlessly after the setup function completes, and it will not halt unless the board is unplugged, you accidentally crash the loop or exit it prematurely.

Whenever you start your code, you should delete everything in the Arduino IDE (an Integrated Development Environment) or in Tinkercad. Often, these programs put stuff in there to show you where to start, but it can get in the way, so clear it and that way you won't run in to issues that aren't yours.

Step 4: Functions

A function is a piece of code that runs when we call it. They usually provide a return value, but in some cases, no value. We know this because the function starts with void. This means that compiler won't expect a return value at the end of the function. If I was to write a function like this -

bool myFunction(){
	// do something
	return someBooleanValue;
} 

Then the function would be required to return a value of either true or false. Normally, in the loop() function, we do all the work we need to do. We can extract parts of this work into other functions, or we can create our own classes that help us attach objects we can interact with. We'll do more in another tutorial, though!

Step 5: Variables

The images above capture declaration of variables on the global scope. Wait, what?!

The global scope is like the root of the program. It's a space where all functions can access your variables. This is good and bad. Sometimes, it can cause conflicts and sometimes, it can help functions communicate easily. Generally, you would declare variables here that need to be declared once and used many times. Try and only access/change/update them in one location, though. The idea is that if multiple functions are updating the same variable, then you might have an unexpected result or code that is really hard to follow, but we can get to that when we discuss functions. Let's look at the code.

int onBoardLed = 13;

In this first line of code, we are doing three things. First, we name a variable onBoardLed. A variable is a bucket, and we can put stuff into that bucket as long as it is the same type. Array's are a little different, but that's for later. The second thing we are doing is using an assignment symbol. The equals sign indicates the variable will hold that value. In this case, we are assigning an integer - a whole number. Hence, at the start of the line we typed int. That makes this type an integer type variable. We can also use:

  • float (decimal number, 7 digits of precision)
  • double (decimal number, 15 digits of precision)
  • bool (true/false, 0/1)
  • char

These are the main ones we will use in Arduino. For the time being, it's unlikely we will even use an Array, but we'll still cover them later.

When assigning a variable, remember that the value of a variable can be changed. So the following is valid:

int onBoardLed = 13;
onBoardLed = 12;

This might have unexpected results. Let's look at a better example.

const int onBoardLed = 13;
int someInteger = 0;

void SomeFunction() {
	onBoardLed = 9;
	someInteger = 9;
}

In this example, I assigned the onBoardLed to a const int - a constant integer. This is no longer a variable but a constant, which means it cannot change. The first line inside SomeFunction would fail, because you're trying to assign a value to a constant. Try it in Tinkercad! Type the code in and click Start Simulation. You'll see the compiler will throw an error.

Delete the code from Tinkercad you typed in and go back to the two basic functions in the image above. You can re-type the constant for onBoardLed. You should have this:

const int onBoardLed = 13;

void setup() {
}

void loop() {
}

The other place we can assign variables in within the local scope, which is within a function. If you do that, the variable is only available within that function and not by another. Let's see an example.

// A global variable
int myVar = 10;

void SomeFunction() {
	// A local variable
	int fooVariable = 15;
}

void anotherFunction() {
	// If I tried to reference fooVariable here, the code would fail
	fooVariable = 5;

	// But I can reference myVar
	myVar = 5;
}

You'll notice the errors created (see fourth image for the error).

The general principle is:

  • If you want to declare variables, do so globally if you know you'll need to access them in a number of functions. For Arduino, we'll declare variables for pins and some other reference points we don't want constantly updated or reset. In the next step, we'll do a practical example of this.
  • Declare variables you're happy to destroy locally - sometimes we don't need to keep track of every variable, so we can 'lose' them each time the program loops.

So, that's variables. Let's do a practical example on the Arduino.

Step 6: Practical Example With Variables

Ok, let's look at a practical example. First, the code.

You started out with the base code:

void setup() {
}

void loop() {
}

Let's do a blinky light, because we have to start somewhere...

First, assign a variable with the value of the on board LED, which is on pin 13.

const int onBoardLed = 13;

That should go above the setup() function. That puts it on the global scope. Note, I made it constant so I can't reassign it somewhere later by accident.

Now, let's setup the pin.

const int onBoardLed = 13;
void setup() {
	pinMode(onBoardLed, OUTPUT);
}

Inside the setup() function, I make a call to a function called pinMode(). This function takes two arguments minimum, the variable pin value (which we assigned to onBoardLed) and the mode of the pin, which can be either INPUT or OUTPUT. This must be in capitals because it is a constant value in the Arduino library and is defined as such. We are going to use it as an OUTPUT pin.

Next, type the code that will make the light blink, once per second.

const int onBoardLed = 13;

void setup() {
	pinMode(onBoardLed, OUTPUT);
}

void loop() {
	// Turn the light on and wait one second
	digitalWrite(onBoardLed, HIGH);
	delay(1000);

	// Turn the light off and wait one second
	digitalWrite(onBoardLed, LOW);
	delay(1000);
}

If you write this code into the Tinkercad Code editor, you should have something similar to the image above.

Let's go through the code.

You know how to do variables and constants, so I won't rehash that. The loop() function contains two new functions.

digitalWrite(onBoardLed, HIGH);
// And
delay(1000);

The first is a function, digitalWrite() that takes two arguments. This is where we use digital pins. The first argument is a value that refers to a pin on the board, usually a digital pin. Then, it takes either HIGH or LOW to set the status of the pin, which is essentially ON or OFF. This is the logic of the pin. For this scenario, this is how we use the pin.

The second is the delay() function, which literally pauses the loop for the number of milliseconds defined as the single argument the function expects. You can set this to any number of milliseconds. Go ahead and try it! What happens to the LED on the board?

If everything worked as planned, you'll have a faint but blinking light on the board. Well done! On to the next concept...!

Step 7: Input and Output Pins

Let's talk about input and output pins and complete three examples or reading and writing analog and writing digital pins. You wrote to a digital pin in the last step, so we won't rehash that one. Otherwise, we'll break this in to two steps, one for analog and one for digital. Let's get started!

For this step, go to Tinkercad and use the same circuit we have been working on. You'll need to access the components panel (on the right, where we added the Arduino from) and add three items:

  • Potentiometer
  • Slideswitch
  • LED (any colour is fine - you can change it when clicked)
  • Two resistors, set to 150Ω (read below on how to do this)
  • Two resistors, set to 10KΩ

In the second image you'll see I've clicked the resistor and changed its value to 150 Ohms. I won't go too much into how we determine resistance because that's another conversation all together. Essentially, you need to solve for the total supply voltage less the forward voltage of the LED (it's voltage requirement), divided by the forward current (milliamp requirements) of the LED. It looks like this (where I is impedance / current):

R = (Vs - Vf) / If

So in our case it is:

R = (5 - 2.2) / 20

R = 0.14 KΩ or 140Ω

Tinkercad errs on the side of caution and alerts you at 140Ω that your LED will burn out quickly. I'm not 100% sure what their settings are for forward voltage so I dial it up to 150Ω to get rid of the warning. Either way, it'll work (it's just a sim, after all).

In real circuits, I usually use a little over anyway just to protect the circuit, so take from that what you will.

Step 8: Digital Read

Digital read is diverse and the way you interect with it depends entirely on the device you've plugged in. We're going to use a slideswitch and read the value of the switch on two pins, using digitalRead(). We'll then use our previous experience with digitalWrite() to turn on one of two LEDs. Let's wire it up!

Check out the image above. Search for and add a Breadboard Small from the Components panel. Place it over the Arduino. This won't connect it but it makes it easier for prototyping. We need to connect wires between the Arduino and the components. Let's start with the slide switch.

Place the slide switch into the breadboard. Take a look at images 2 and 3. Remember, the dots on a breadboard are connected vertically, but the channel in the middle disconnects the top block from the bottom. We'll use this gap later in the tutorial. The dots are not connected horizontally, so you need to make sure you connect all devices across the columns. Place the slideswitch in the same orientation as per image 3.

Wiring up

To wire up, connect the left and right columns to digital pins 2 and 3. Then connect the middle wire to ground (GND) on the board. Colour the wires green (or whatever you like other than red or black) for the connections to digital and black to GND. Do this by clicking the wire, then selecting the colour.

Add the 10KΩ resistors from the same column of the outer pins to a spot where they can be easily routed to GND. This makes sure they go to ground when the switch is turned off.

Great! Let's do the code.

Step 9: Coding

Click the Code button on the interface and change "Blocks" to "Text". We love blocks, but we need to learn text code, too. Remove all the code so it just reads:

void setup(){
}

void loop() {
}

Ok, to read the digital pin, we need to run a couple of functions inside the loop. Let's write the code then go through it.

// Assign constants
const int switchPosition1 = 2;
const int switchPosition2 = 3;

void setup() {
	// Assign the pin types (INPUT or OUTPUT)
	pinMode(switchPosition1, INPUT);
	pinMode(switchPosition2, INPUT);
}

void loop() {
	int positionOneActive = digitalRead(switchPosition1);
	int positionTwoActive = digitalRead(switchPosition2);

	// Respond to active position
	if (positionOneActive == HIGH){
		// do something
	}
	if (positionTwoActive == HIGH){
		// do something
	}
}

Ok.. That's quite a bit.

First, we assigned the two switch positions. This is so we can read the pin values.

Then, in the setup function we set the pin types. We have done this before, except last time we used an OUTPUT type.

Now, in the loop we introduced a new function and an if() statement. This is so we can make decisions based on input. Let's look at it.

if (positionOneActive == HIGH)

First, we state the if, then inside the round braces, we use a comparative. We can compare in many ways, such as:

  • == or equal to
  • <= or less than or equal
  • >= or greater than or equal
  • etc...

There are a few comparatives and it is best to find the one you need for your use case.

So, in this case we are checking if the positionOneActive is HIGH, in which case we can "do something". In the next step, we're going to turn on a light as a result of this work.

Step 10: Do Something...

First, let's wire up the lights.

We discussed earlier that columns are connected and rows are not. This means we can easily connect wires to pins on the board. The best place to start is by placing the LEDs and the resistors on the board. See the image for placement.

Roll over the bottom of each LED. You'll notice one is the Cathode and the other is the Anode. The Cathode is the negative, whilst the Anode is the Positive. This is only relevant at the moment because we need to connect the Anode to the digitalPin, and the Cathode to the GND.

  • Place a wire from the Cathode to the GND
  • Place another from the top of the column to digital pins 4 and 5. Make sure they contact the breadboard in the same column as the pin for the Anode.
  • Bridge the gap with a resistor.

Ok, this is where we ended up. So let's replace the comments below (the ones that read // do something) and add two more pin positions

// Assign constants
const int switchPosition1 = 2;
const int switchPosition2 = 3;
const int led1 = 4;
const int led2 = 5;

void setup() { 
	// Assign the pin types (INPUT or OUTPUT)
	pinMode(switchPosition1, INPUT);
	pinMode(switchPosition2, INPUT);
	pinMode(led1, OUTPUT);
	pinMode(led2, OUTPUT);
}

void loop() {
	int positionOneActive = digitalRead(switchPosition1);
	int positionTwoActive = digitalRead(switchPosition2);

	// Respond to active position
	if (positionOneActive == HIGH){
		digitalWrite(led1, HIGH);
	} 
	if (positionOneActive == LOW){
		digitalWrite(led1, LOW);
	} 
	if (positionTwoActive == HIGH){
		digitalWrite(led2, HIGH);
	}
	if (positionTwoActive == LOW){
		digitalWrite(led2, LOW);
	}
}

Ok, so a few things.

First, we added the new led1 and led2 pins to the list of constants. Then, I added the two pinMode() function calls to assign led1 and led2 as OUTPUT types.

The loop() is where the real meat is. For the IF statements, I need any case that is true or false to fire every time the conditions are checked.** I added these in for all conditions.

Then, I used the digitalWrite() function to write to the LEDs. Check it out in Tinkercad. Does your circuit work when you toggle the switch?

** There are more efficient ways to write this, like with else or some other shorter notation. I did this for clarity :)

Step 11: Mapping Results (of Input)

Ok, this is the last step!

Mapping integers is a powerful way to manage some devices. Let's say you want to move a servo motor with a potentiometer. One way to do that is add a little code that reads the value of the potentiometer. What we do is convert the reading from the usual value on the analog pin of between 0 and 1023 to something that is more usable. Let's just do the mapping - motors are another day!

For this, we'll read the value and print to the serial. Rather than rewrite the entire codebase from previous steps, I'll start with a blank setup() and loop(). You can add the code to your existing code in Tinkercad and it will work just fine.

// Assign a constant with the pin value
const int potPosition = A0; void setup() { pinMode(potPosition, INPUT); Serial.begin(9600); } void loop() { //Read the value and print to Serial int currentPotValue = analogRead(potPosition); int mappedPotValue = map(currentPotValue, 0, 1023, 0, 50); Serial.print("Pot Value (original): "); Serial.println(currentPotValue); Serial.print("Pot Value (new): "); Serial.println(mappedPotValue); delay(500); }

So the Map() function just reshuffles the numbers. It takes 5 arguments. The first is the variable we want to map. The second and third are the original range. The fourth and fifth are the new. Arduino manages all the overhead for us and does the conversion.

I added a delay to make it a little easier to read.

Great!

Step 12: Final Notes

This get's you through some serious work with the Arduino. It's a great way to get your head around the basics and understand just what you can do. There is so much more, too. In another tutorial, we'll cover using basic motors like servos. We'll also look at functions and classes. There have been a few requests for SD card data, too, so I'll cover that.

If you'd like to see the circuit on Tinkercad you can do that, and I have embedded it above.

If you have questions, please leave a comment and I'll endeavour to get back to you quickly. Also if you see errors, message me and I'll fix the error and provide due credit. Thank you!

Epilog X Contest

Participated in the
Epilog X Contest