Skip to main content

The Ultimate Arduino Tutorial

Chriscamaro loves playing sports, modifying cars, and playing video games.

The flagship UNO.

The flagship UNO.

What Is Arduino?

Arduino is the brand name of a family of simple micro-controller boards, which are like cut-down computer processors, that are meant for rapid execution of very specific tasks related to electo-mechanical and mechatronics systems. Basically, you use these things to interface with sensors, motors, lights, switches, and other digital electronics to make decisions based on inputs regarding how to produce outputs (for example, if the ambient temperature is greater than 30°C, turn on a cooling fan).

Many third parties have made electrically identical clones that are extremely cheap, making micro-controller-based projects extremely economical and popular with people of any economic demographic.

What Options Are There?

To access the micro-controller's features, the chip itself is soldered onto boards which expose all of the chip's pins. The Arduino family of boards vary in form factor (size/shape) as well as feature-set, in case you want to scale your project up or down in complexity. Because there are so many options, I will be focusing on the most popular chip used in the UNO and NANO, which is the ATMEGA328P. That's just the name of the "CPU," so to speak, but it's common to the UNO and NANO. Thus the features are the same and so are the software commands need to implement the many features.

I really like the NANO because it's super compact and breadboard friendly, so whenever I reference the board itself, I will reference the NANO. However, keep in mind the pin layout is almost identical on the UNO, so you can use this guide for both (and even on some other similar boards like the Leonardo, with some exceptions).

Why I Like Arduino

As a university student we used older PIC micro-controllers to do some pretty cool things, and since then I always knew I wanted to get into micro-controllers and all sorts of exciting projects that were possible with them. Problem was, I didn't really know which chip to buy, how to communicate with the hardware, whether I needed to learn some exotic assembly language, etc.

When I stumbled on Arduino, it was like the holy grail. Not only did it have all the hardware features I could ever want (and some I didn't even know I wanted), but it used a compiler based on C, which I already knew and which is very commonly taught. It also requires nothing more than a USB cable to upload code to the chip, so no fancy hardware is necessary. Aside from the board, all the software is free and open source and with ample example programs provided to you by default. The learning curve is quite low—so as I said, this was like magic. Just buy a board and jump right into things!

This the 328 microcontroller

This the 328 microcontroller

ATMEGA328P Specs and Features

As I said, this chip, which I will refer to simply as the 328, is the heart of the most popular Arduino board. Everything else you'll see on the board is just supporting circuitry or headers coming from the chip's many pins so you can easily connect to them with wires. The 328 is where all of the work is done. We'll begin by simply listing the specs and features so you can see, at a glance, what the chip is capable of.

The Specs

You may be inclined to be overly-critical of some of the more familiar numbers below but believe me: Once you understand at a deeper level why these specs are what they are, you'll have an appreciation for why we all don't just use super-fast computer-like boards, such as the Raspberry Pi, a popular competing product. So without further ado, the specs:

  • 16MHz Clock Speed (16 MIPS throughput)
  • 8 Bit Processing
  • 5V Supply (3.3V Minimum)
  • 40mA Maximum Current Draw
  • 32KB Program Space (for your code)
  • 1KB EEPROM (for saving stuff at runtime, permanently)
  • 2KB RAM (for all your runtime variables and calculations)
  • 0.1 uA - 19mA Current Consumption Range

You'll note that these values are rather unimpressive on the scale you're familiar with. If you own a PC, you're used to GigaHertz and GigaBytes and 64 bits. Try to suppress the urge to be overly critical of these "pathetic" figures. Don't forget that your car and the space shuttle run on similarly "pathetic" computers. Why? Because sometimes you can do a job better and faster with less if the hardware is specifically designed for the task at hand.

Remember the Super Nintendo Entertainment System (SNES)? The chip that ran that game console was far less powerful than the 486 computer that ran Windows 3.1. However, if you try to emulate a Super Nintendo on a 486 computer, the 486 can't cope. It's unplayable. This is why computers are good for everything and anything because they are un-specialized but a micro-controller is good for only 1 thing: Controlling electro-mechanical systems and it does this far more efficiently than a computer with superior speed and memory. When you consider the fact that an Arduino board is the size of your thumb, can run off a watch battery and requires no operating system, you start to realize how much you can actually do with 16MHz and 2KB of RAM.

Also noteworthy is something akin to a hard drive on the 328. The 32KB of program space can only be used to upload stuff you already know about: Code, reference tables, etc. but if you want to save data you don't know about... data that is generated at run-time, like sensor readings you later plan to graph in Excel, you can save that data on 1KB of EEPROM like a mini hard drive and read it later. How cool is that?

Scroll to Continue

If not for this tiny block of storage, you'd have no way of getting any feedback from your device without buying some sort of add-on (like a display screen)... unless you're keen on using the single LED on the board to blink morse code at you... and that can be kinda fun :)

The Features

Now, in addition to the 328's basic specs, here is a list of all the amazing features it has:

  • 3 Timers (2 x 8 bit and 1 x 16 bit)
  • 8 ADC Channels with 10 bit Resolution per Channel
  • 6 PWM Channels on Select Digital Pins
  • Analog Out via PWM
  • Internal Temperature Sensor (a 9th channel on the ADC multiplexer)
  • USART
  • SPI
  • I2C
  • Watchdog Timer
  • 2 Hardware Interrupts
  • 6 Power Saving Modes
  • 22 Digital I/O Pins
  • Analog Comparator

Please note that this is not an exhaustive list. The 328 has a few more tricks up its sleeve BUT these ones are ready to use, "out of the box" as it were, with little to no setup. The others are a bit more obscure and are beyond the scope of this article.

Explaining the 328's Features and Benefits

Now that you've seen a bunch of cryptic specs and features, let's go through what it all means and how to use it. For each feature I'm going to answer 3 questions:

  • What is it?
  • What's a common example?
  • How do I use it?

That way it won't just be technobabble. You'll actually finish reading this tutorial and be able to use the info in a real project the same day! As you read through these examples you will understand the procedure and the meaning behind the features being explained but keep in mind that you will need to do some work to adapt this to your own project.

Often we will be modifying registers and it's too messy to explain all that in depth here for every possible scenario so what you'll have to do is use the example as a guide to know where to look in the 328's data sheet. In there you'll find maps of all the registers that are modified in the examples below. You simply need to find the tables for each relevant register and change the bits according to the modes and values YOU want. These examples are therefore meant to get you started in the right direction, as you'll already know which registers to look for and which tables to check.

1KB EEPROM

The EEPROM is a bit of an oddball because it's not really a feature but it's not obligatory like the RAM or the CPU either. It just sits there and you can use it or not use it. EEPROM stands for Electrically Erasable Programmable Read Only Memory. Basically it's non-volatile memory (it doesn't forget what you put in it when the power goes off, like RAM does), so you can store things in there and they will persist after the Arduino is powered off. This makes it the equivalent of a mini hard drive! Sure, 1KB isn't much of a hard drive but for Arduino projects, it's often more than enough for what you need.

For example, if your program is designed to detect the acceleration due to a fall, you might employ an accelerometer and try to analyze the signature of a fall by looking at the x, y and z acceleration values. Since a fall is a very transient, brief event, and since the sensor itself is falling, chances are you won't be able to collect any useful data from visual inspection in real-time. What you need to do is record the sensor data somewhere. Again, because your sensor is in a free fall, you likely won't have the sensor or Arduino board connected to anything to transmit the data via serial, SPI, I2C or the like. Really the best option is to keep everything completely portable and isolated and store the data locally, in the EEPROM. Simply sample the data at a certain frequency and write it to the EEPROM memory. Later on you can dump all the data off the EEPROM onto your computer via serial terminal and put that in excel to graph and look at it! EEPROM is really cool.

The code below reads a sensor, stores each value in the next available byte of EEPROM and then when it's full, the program reads all the values in order and prints them to the serial terminal. Be aware that each BYTE of EEPROM only holds a number up to 256 (a byte is an 8-bit number) so you either need to store numbers lower than that or use more than 1 byte of EEPROM to store a bigger number (or other data type). The EEPROM write function works by first specifying the byte number you are writing to in memory and then the value being written. Make sure you know how many bytes of memory your Arduino has (the latest Uno/Nano has 1024) and which byte you want to write to. Also here's another trick you'll need... Start your program with a delay of several seconds. Why? Because after you store the data, the next thing you want to do is read it but you have to load the "reading sketch" first and as soon as you plug in your USB cable to do that, the old program will start overwriting your data from the last session. If you put in a delay to start, you have a few seconds to hit the upload button before the old program can ruin your data.

#include <EEPROM.h>

int addr = 0;

void setup() {

  Serial.begin(9600);

}

void loop() {


  while(addr < 1024) {

    int val = analogRead(0);
    EEPROM.write(addr, val);
    addr++;
    delay(100);

  }

  addr = 0;
  while(addr < 1024) {

    Serial.println(EEPROM.read(addr));
    addr++;
    delay(100);

  }

}
the-ultimate-arduino-tutorial

3 Timers

What is it?

A timer is a device that uses the system clock as a means of counting with each clock tick. As the microcontroller does its business, the clocks act independently and will count up to whatever number you want, at the same speed as the rest of the chip is performing its calculations. Therefore each of the 3 timers can count independently, though their counting speeds are all based on the system clock speed so if the clock speed is 16MHz (which it is), the clock ticks 16 million times per second and each timer can potentially count 16 million numbers per second, if you want them to but since they are independent, they can do this whether the CPU is busy or not, and they can start/stop whenever you want them to.

Timers work on the simple premise that all they do is count but you can use this feature to do a ton of different things. By picking the number they count to and telling them what to do when they reach that number (stop, count the other way, interrupt the program, etc.) you can do a wide array of tasks, from telling time, to driving a switched power supply. Of the 3 timers available on the Arduino chip, 2 are 8 bit, meaning they can only count to 256 and the 3rd one is 16 bit, meaning it can count to about 65 thousand.

What's a common example?

Say you want to program a machine to water your flowers once a week but in the meantime you want to collect data on soil moisture levels and ambient light levels. You need a timer to keep track of the watering schedule without interrupting the main program with a huge 1 week delay.

How do I use it?

This program uses Timer1 (16 bit) to count 1 second intervals and flash the LED on and off at that interval. In order to do that, the counter has to count to a certain number that takes 1 second to do. Because the clock runs at 16MHz and the timer can only count to 65000 or so (that's the size of a 16 bit number), it would normally count all the way to the end well before 1 second of CPU time. To fix this, we use a prescaler factor to drop the 16 MHz down to something lower, so that the counter ticks slower than the system clock. Then we can actually count 1 second.

There's really nothing to do in the loop function. Simply by setting up the timer, it begins running. The first thing to do is configure the 2 timer registers TCCR1A and B. First we set both to 0 so we can only enable the pieces we want. Then we set OCR1A, which is what determines the number we want to count to. If we prescale the clock by 1024, we get 16000000 / 1024 = 15625. Since this is the Timer's clock speed, 1 second contains that many ticks and since we want 1 second anyway, we want to keep this number as our upper limit to count to. Had we wanted 2 seconds, we'd simply double this number. Since we start counting from 0, the actual limit in this case is 15624. This goes into OCR1A.

Now we set the timer "mode" by WGM12. That's because the timer can do a lot of things but all we want it to do is count to a maximum number and trigger an interrupt. This is called CTC mode and is selected through WGM12. Then CS10 and CS12 set the prescaler to 1024 as discussed above. TIMSK1 is the register we need to configure so that interrupts will be enabled and with that, we're done. The timer will automatically start counting and once every second it will reach the target value in OCR1A, interrupt and "toggle" the LED pin.

void setup() {
  
    pinMode(13, OUTPUT);
 
    cli();          
    TCCR1A = 0;     
    TCCR1B = 0;     
 
    OCR1A = 15624;
    TCCR1B |= (1 << WGM12);

    TCCR1B |= (1 << CS10);
    TCCR1B |= (1 << CS12);

    TIMSK1 |= (1 << OCIE1A);
    sei(); 
}

void loop() {


  
}

ISR(TIMER1_COMPA_vect) {
  
    digitalWrite(13, !digitalRead(13));
    
}
ADC channels

ADC channels

8 ADC Channels With 10-Bit Resolution

What is it?

An ADC is an Analog to Digital Converter, which basically means it takes in variable information in the form of a voltage, which could be anything from 0 to 5 volts, and converts it into a digital 10 bit number from 0 to 1023. The size of the number then represents the size of the signal that was read, where 1023 = 5V and 0 = 0V. This is extremely useful for reading sensors of all kinds. There are a ton of sensors that convey what they are sensing by reporting back a voltage from 0-5V. By being able to read this voltage and turn it into something the micro-controller can understand and manipulate mathematically, like an integer, you can manipulate what your program does based on input from the environment.

The ADC multiplexer also has a built-in band-gap reference voltage of 1.1V. All this means is that you have a built-in option to change your measurement range to 1.1V at any time to get better resolution for small measurements that you know won't go over 1.1V. Just keep in mind that after you switch to this mode, you have to wait a bit for the voltage reference to drop from its default of 5V or your readings will be wrong. A simple delay can accomplish this.

What's a common example?

Suppose you want to measure the rate at which you're getting fatter. You could put on a belt containing strain gauges (sensors that detect change in length) and connect the sensor to an amplifier, which then connects to one of your ADC pins. With every inch of lard you add to your waist, the voltage from the strain gauge will increase and become amplified. The ADC pin will then detect the increasing voltage and convert it to an increasing integer. Make sure your code can handle overflowing the 10 bit range of the ADC!

How do I use it?

This is really easy. Analog pins are always inputs and don't need to be configured in setup. However unlike digital pins that are identified by only their number, analog pins need to be called out by their full name written on the board (such as A3, not just 3). Any time you want to measure the voltage on an analog pin, simply use the code below and then take the integer stored in "value", divide by 255 and multiply by 5. This will give you the analog voltage on that pin at the time it was measured. In the example below the A0 pin is being measured. Just for the heck of it, let's calculate the voltage and print it to the serial terminal too.

int value;
double voltage;

void setup() {

  Serial.begin(9600);

}

void loop() {

  value = analogRead(A0);
  voltage = value/255*5;
  Serial.println(voltage);

}

6 PWM Channels

What is it?

PWM stands for Pulse Width Modulation and is basically an alternating square wave of a fixed frequency. The square wave has a high level and a low level. For any given cycle (1 wave in duration), the amount of time the wave is high vs the amount of time the wave is low, determines the "duty" or the ratio of high time to low time. This is useful because it provides a means of timing events that need to switch between different states. A power supply that takes a high voltage and produces many lower voltages from it, works on such a principle. The PWM duty tells the power supply how much to drop the voltage because the duty represents a ratio, so a 50% duty is the same as saying, I want 50% of the maximum voltage.

For everything you ever wanted to know about PWM, check the video near the top of the page.

What's a common example?

You want to learn how to use an oscilloscope so you use your Arduino nano to provide a test signal to measure. You configure a PWM capable digital pin as output and issue the analogWrite() command with different duty values, confirming the waveform changes shape as expected on the scope screen.

How do I use it?

Extremely simple. After picking a digital pin that supports PWM (D3, D5, D6, D9, D10, D11), just use the following code to set the pin (D9 in this case) as an output and then produce the PWM. The "duty" parameter dictates the "on time" as a ratio to the total pulse interval. Thus a 25% duty cycle would be 63/255 so "duty" would be 63. See below:

int duty = 0;

void setup() {

  pinMode(9, OUTPUT);

}

void loop() {

  duty = 63;
  analogWrite(9, duty);

}
the-ultimate-arduino-tutorial

Analog Out

What is it?

Analog Out is the ability for a digital source like an Arduino micro-controller to produce an analog signal to drive something external, like headphones, which produce sound from analog voltage signals. This is extremely valuable because lots of electro-mechanical devices actually require analog signals to work properly. Only digital electronics can and prefer to work using discrete voltage levels like 0 or 5V. Everything else from motors to speakers and radio transmitters require an arbitrary voltage pattern to work as intended. Therefore there must be special circuitry which can create these analog signals from digital information in your program.

What's a common example?

Suppose you want to create a power inverter. For that you need to generate a sine wave, which is an analog voltage with a 170V peak and frequency of 60 Hz. You need to produce this wave with digital logic so a DAC (Digital to Analog Converter) is required to output the analog voltage that makes up the sine wave.

How do I use it?

Well first of all, there is no "analog out" on an Arduino board but I'm not trying to trick you or anything. The hardware is almost all there. You just have to add a simple low-pass filter to one of the digital pins, drive it with PWM and boom - analog out capabilities. So to do this, first pick a digital pin, say D9, and connect a series resistor and a parallel capacitor to the pin. There are many tools you can google which will help you choose the values but for the sake of argument let's say 10000 ohms and 10 uF.

Once you've connected these 2 external elements, try the code below and then just vary the value of "duty" to increase or decrease the output voltage from 0 to 5V (the example below gives 2.5V). The voltage you get will be: 5 x (duty / 255) so change the value of duty accordingly in your code.

int duty = 0;

void setup() {

  pinMode(9, OUTPUT);

}

void loop() {

  duty = 127;
  analogWrite(9, duty);

}
the-ultimate-arduino-tutorial

Internal Temperature Sensor

What is it?

The Arduino chip has a built-in temperature sensor, which measure the temperature inside the chip. This is useful when you want to add a layer of protection to your board, to save it from damage due to drawing too much power from it or from operating it at high power in a relatively hot environment. You can also use it to get an approximate guess of the ambient temperature if you put the Arduino to sleep for several minutes and then measure the temperature immediately when you wake it up.

What's a common example?

You are using an Arduino board in your car to measure various under-hood sensors, which you drive in Arizona in the summer. Temperatures inside the car can melt your face off in a hurry if you leave the car derelict in the open sun. The internal temp sensor can act as an alarm to notify the driver of unsafe operating temperatures or can throw the chip into one of its sleep modes to prevent additional heat generation from damaging the micro-controller.

How do I use it?

This block of code will check and print the chip temperature to the serial monitor. To make this useful for ambient temperature checks, you would have to execute this immediately after your board had been sitting unpowered for several minutes in the environment of interest. As soon as the board turns on, the 328 will heat up and throw off your ambient temperature value so check this right away.

void setup() {
  
  Serial.begin(9600);

}

void loop() {

  unsigned int wADC;
  double t;

  ADMUX = B11001000;
  ADCSRA |= _BV(ADEN);  

  delay(50);          

  ADCSRA |= _BV(ADSC);
  while (bit_is_set(ADCSRA,ADSC));
  wADC = ADCW;
  t = (wADC - 324.31 ) / 1.22;

  Serial.println(t);
  delay(1000);
}

The first several lines of the loop function configure the ADC multiplexer to read from channel 8, which is where the temperature sensor is. It also drops the reference voltage from 5V to 1.1V to provide better resolution. Then the temperature 't' is calculated by adjusting the raw measurement wADC by an offset (324.31) and a gain (1.22). These are default values and might be wrong for your board so use a thermometer to check against and adjust the offset until the program output matches the thermometer. Then if the value is still off at a completely different temperature, you'll have to adjust the gain as well.

Play with these values until the sensor reads within +/- 2 degrees C at all temperatures. Also, if you want to make the code more compact and don't need to print the temperature to the serial monitor, just cut out everything in the setup block and cut out the Serial.println(t) from the loop block.

An Arduino car!

An Arduino car!

USART

What is it?

USART is basically serial communication... the sort that is required for USB devices to talk to the computer. When you upload a program to your Arduino board it uses the USART to convey the data from the computer to the chip and vice versa when the chip wants to report data from the code you are running, provided you set up your code to do so. Think of it as a serial communication interface.

What's a common example?

You've just purchased a magnetometer chip, which gives you a heading in degrees based on the magnetic poles of the Earth. You want to get live readings from the sensor to ensure that it is working correctly and that there is no interference throwing it off. Therefore you issue print commands in your code to display the magnetometer readout twice per second on the serial terminal which is integrated into the Arduino compiler. This is in lieu of obtaining and setting up a portable display screen or similar output device.

How do I use it?

The Arduino language allows you to set up serial communication very easily to send data to your PC. You can also read user input from the serial terminal. And there's no setup at all when you simply want to use it to upload code to the chip since that is all handled by the Arduino software when you hit the upload button. The following code is the most basic starting point for ANY project, as it provides you with the most rudimentary feedback from your code, without needing to hook up graphical displays or other output devices. It also has basic user input capabilities, as it requires you to type the letter X to the Arduino board in order to get an analog measurement value from it. In this way it satisfies the most universal need for input and output capabilities by virtue of the USART.

Take note that once you upload this code, you need to click on the serial monitor in your compiler, select the baud rate that matches your code (9600 in this case) and then close and open the monitor again. Then you will need to type X in the bar at the top and hit enter to see if the output works. If the baud rates don't match you will see garbage come out.

void setup() {

  Serial.begin(9600);
  
}

void loop() {

  byte input = Serial.read();
  if(input == 'X') {
    int sensorValue = analogRead(A0);
    Serial.println(sensorValue);
  }
  
}

SPI

What is it?

It's a communications protocol that allows you to control 1 or more peripheral devices using your Arduino. Many sensors and displays require this protocol to transfer data back and forth with the Arduino. It is simple in execution and fast but eats up a lot of separate wires on your 328.

What's a common example?

You want to create a user interface screen for a multi-purpose battery charger that allows you to enter all your battery parameters and select your charge or discharge mode. You want to display real-time sensor data on another screen at the same time. You could use the SPI protocol to access both displays according to their chip select pins so that each can display different info at essentially the same time and sharing most of the same connection points.

How do I use it?

At a minimum you need to connect matching wires between your Arduino and peripheral device for: VCC, GND, MISO, MOSI, SS (or CS), SCK. If you're only driving 1 device sometimes you can skip the SS/CS pin and if the device is only capable of slave operation, sometimes you can skip the MISO pin too. These pins can be arbitrarily assigned to different I/O pins on your Arduino board via your sketch, which will run SPI in software mode, or you can use the predefined hardware pins on the ICSP header, which will run SPI in hardware mode (faster). The ICSP pinout will tell you which pin does what.

With regards to your sketch, you'll need to include the SPI.h library and whatever device-specific library is required to interface with it. Then you'll need to issue some sort of initialization command, which varies across devices but usually defines the aforementioned pin locations if running in software. Below is a trimmed down sample sketch to drive an SPI OLED display. This program is going to appear later in the I2C section so have a close look at how they differ.

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// If using software SPI (the default case):
#define OLED_MOSI   9
#define OLED_CLK   10
#define OLED_DC    11
#define OLED_CS    12
#define OLED_RESET 13
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

#define LOGO16_GLCD_HEIGHT 16 
#define LOGO16_GLCD_WIDTH  16 

void setup()   {                

  display.begin(SSD1306_SWITCHCAPVCC);

  display.display();
  delay(2000);
  display.clearDisplay();

  display.drawPixel(10, 10, WHITE);
 
  display.display();
  delay(2000);
  display.clearDisplay();

}


void loop() {
  
}

I2C (I "Squared" C)

What is it?

Another communications protocol similar to SPI. The difference is, fewer wires are needed and you can connect far more devices by sharing the same 2 wires, whereas SPI requires more wires for additional devices. The tradeoff is that I2C is slower than SPI. Some devices come in both SPI and I2C flavours such as OLED displays.

What's a common example?

You purchase an IMU (Inertial Measurement Unit) sensor, which is a single board solution containing 3 different sensor chips: 1 magnetometer, 1 gyroscope and 1 accelerator. While this may appear to be 1 sensor from the outside, it is actually 3 as far as your code is concerned. You would use the I2C protocol to communicate with the specific hardware functions of each chip because I2C allows you to gang multiple devices on the same data and clock lines, using software addressing to distinguish and enable 1 device at a time on the same shared pipeline.

How do I use it?

Whatever device you are running from your 328 will have its pinout labelled (I would hope). You only need to find 4 pins: VCC, GND, SDA, SCL. Sometimes VCC will say 5V or 3.3V, but whatever voltage it takes, there's a pin on your Arduino to supply it so just use the appropriate power pin. GND will always be the same and you need that, SDA is analog pin A4 and SCL is analog pin A5. Just match up all 4 pins on both your Arduino board and your device.

Now as for the code... it completely depends on what sort of device you are running so there's no universal answer. The only universal part of it is that you'll need the Wire.h library and you'll need to issue some sort of initialization command, whether it be native to the device's library, or a simple wire.begin() or whatever.

I can't predict what form your code will take because it varies with the device but if you look at the below example, it's a stripped down program that drives an OLED display. Pay particular attention to the fact that it calls the wire library, it identifies the device by its HEX address (0x3D) and prints a single pixel to the screen using commands that are native to that OLED device. I2C may not have universal execution but it's not too hard to figure it out if you just read up on the device you're trying to run.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#define LOGO16_GLCD_HEIGHT 16 
#define LOGO16_GLCD_WIDTH  16 

void setup()   {                

  display.begin(SSD1306_SWITCHCAPVCC, 0x3D);  
  display.display();
  delay(2000);
  display.clearDisplay();

  display.drawPixel(10, 10, WHITE);

  display.display();
  delay(2000);
  display.clearDisplay();

}


void loop() {
  
}
the-ultimate-arduino-tutorial

Watchdog Timer

What is it?

It's a special timer that monitors the CPU and looks for regular status updates to make sure everything is ok. If the code malfunctions and hangs the CPU or if there's a hardware failure causing a similar fault in execution, the watchdog timer will time-out and is capable of resetting the Arduino board. Because the watchdog runs independently of the CPU, it has its own separate oscillator and can also wake up the CPU when it's sleeping.

Watchdog timers are very useful when you are running code for long periods of time with sleep intervals, or when you expect uncertain program behaviour may occur and don't want the unit to hang forever. When your code doesn't hang, it needs to "kick" the dog in order to prevent it from resetting your board, which basically amounts to sending an "OK" signal to the dog so it will not time out.

What's a common example?

You are attempting to use 1 Arduino board to communicate with another. The second board is connected to various sensors in various states of readiness. At program initialization, you want to check the second board and all of its sensors to ensure they are ready to transmit data before you continue with your code. Each check could potentially hang your program, if the sensor is not properly powered on and initialized.

You want to ensure that your main program doesn't hang forever on a non-responsive sensor but instead resets itself after a certain amount of time. You program the watchdog timer therefore to wait only for so long for all the sensors to report ready. If any one of them hangs while being polled, the watchdog will time out and reset the main program so that it goes through its checks again. If the checks are all successful, the watchdog will be "kicked" so as to prevent a program reset and the main program can proceed as planned.

How do I use it?

The code below is a basic example of how to use the watchdog timer to interrupt and then reset the 328 due to an infinite loop (which can essentially hang the program forever).

It works by first resetting and then configuring the watchdog timer. The 2 registers being assigned binary values both set the WDT register bits so that the watchdog will time out in 8 seconds, enable interrupts and enable reset. In the loop function, we then enter a loop which simulates "correct" program execution, where the dog is kicked after each loop via wdt_reset(), indicating the program is working correctly. This lasts for about 5 seconds. After that, the program enters a second loop, this time with no exit condition. This simulates faulty execution resulting in an infinite loop.

After 8 seconds of this loop, the dog doesn't get kicked so it automatically interrupts the infinite loop, which is noticeable by the LED being set to LOW. Finally the dog resets the 328 and the code starts all over again. This is just a prototype for your own personal application but it demonstrates both correct and erroneous program execution, and how the watchdog timer works when configured correctly. You can build your code around this example, simply changing the WDTCSR register bits according to the 328 datasheet, to get your desired watchdog time out and time out behaviour.

Note: Some Nano boards (the clones in particular) don't work with the WDT. What happens is, the program will execute fine, the WDT will interrupt and reset the board fine when it hangs but then the board will not restart. This is because of a flaw in the boot loader that was never fixed. The solution to this problem is to change the boot loader from the Nano version to the Uno version, essentially making the Nano think it's an Uno.

Since they are so similar this is not an issue in 99.9% of cases but in order to make the change you need to have 2 boards, an Uno acting as programmer and the board to be programmed. You also need to connect the ICSP header wires between the 2 boards and upload a special sketch that will burn the new boot loader.

#include <avr/wdt.h>

void setup() {

  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH); 

  cli();
  wdt_reset();

  WDTCSR |= B00011000;
  WDTCSR = B01101001;

  sei();

}

void loop() {

  int i = 0;

  while(i < 5) {

    delay(1000);
    wdt_reset();
    i++;

  }

  while(1)
    delay(50);

}

ISR(WDT_vect) {

  digitalWrite(13, LOW);

}

2 Hardware Interrupts

What is it?

An interrupt is a feature that is capable of stopping code execution, no matter where it happens to be, so that something else can be done in its place. This is most often needed when you are waiting for something to occur but you don't know when it is going to happen and you can't afford to wait to check it in a looping structure. If you are monitoring a sensor that is usually not active and you want to take immediate action when its state changes, you need an interrupt so that when the sensor gives the right signal, you stop what you're doing and execute a little piece of code to deal with the sensor reading and then resume where you left off.

Any pin can be configured as an interrupt but there are 2 special hardware interrupts assigned to specific pins. These are much faster because they are hardwired into the chip and don't rely on software to emulate the interrupt feature.

What's a common example?

You want to measure the RPM of a motor based on an optical encoder attached to the shaft. The interval of the pulses from the encoder varies with motor speed so you cannot simply check for the pulses at regular intervals or you may miss them. Therefore you get the pulses to tell the program when they arrive by connecting the encoder line to one of the hardware interrupt pins. When the pin detects a pulse, it prompts the program to halt, check the time and do a quick calculation to measure the duration since the last pulse. The inverse of this duration is the speed of the motor.

How do I use it?

There is an Arduino function called attachInterrupt(pin, ISR, mode) which will first designate a specific pin as an interrupt pin, which routine to run when the interrupt occurs and how the interrupt will be triggered (for example when the pin changes state from HIGH to LOW). The routine you run is called an ISR. You can give it a name of your choosing and simply reference it in the attachInterrupt line.

The contents of the ISR should be EXTREMELY SHORT. This is because interrupting your code stops everything until the ISR is done. If the main code was doing something time sensitive, you want to resume that code ASAP, so often ISRs simply change a boolean flag and let the main code handle it in the next loop by checking the flag and doing something. In the example below, a flag is toggled, which prompts the main program to turn on the nano's LED.

Another thing is that each Arduino board has different hardware interrupt pins and IDs. On the nano pins 2 and 3 are INT.0 and INT.1 respectively. Make sure you reference the latter in your attachInterrupt function (0 in the example). However when you setup the pin as an input, you need to use the normal pin ID (which is 2). Just be careful of that. Also note that we use a special input mode in the pin setup called INPUT_PULLUP. This is like INPUT except that it forces the pin to stick at 5V unless a real signal drives it to some other level.

This is important when you're not sure of the quality of the input on a given pin. Sometimes if a signal is absent or weak, the pin can float around due to static or electric fields in the air and it can display any value randomly. This could cause all sorts of false interrupts and other wonky behaviour so particularly with interrupts set up to detect pin level changes, you want to use the INPUT_PULLUP mode.

boolean turnon = false;

void setup() {

  pinMode(2, INPUT_PULLUP);
  attachInterrupt(0, interruptRoutine, CHANGE);

}

void loop() {

  if(turnon == true) {
    pinMode(13, OUTPUT);
    digitalWrite(13, HIGH);
  }

}

void interruptRoutine() {
  turnon = !turnon;
}
the-ultimate-arduino-tutorial