Arduino Uno Description

The Arduino Uno is a popular microcontroller development board based on the ATmega328P microcontroller. It offers various pins for digital I/O, analog inputs, and communication. Below is a detailed breakdown of the pins and their alternate functions:

Key Features
  • Microcontroller: ATmega328P
  • Operating Voltage: 5V
  • Digital I/O Pins: 14 (6 of which support PWM)
  • Analog Input Pins: 6
  • Clock Speed: 16 MHz
  • Flash Memory: 32 KB
  • SRAM: 2 KB
  • EEPROM: 1 KB
Arduino UNO pinout
Arduino Uno Peripherals

The Arduino Uno provides access to several key peripherals that allow for a wide range of applications.

1. Digital I/O

The Arduino Uno has 14 digital I/O pins, labeled D0 to D13. These pins can be used as either input or output pins, and support both HIGH (5V) and LOW (0V) states. Key features:

  • Pin D0 and D1 serve as UART RX and TX for serial communication.
  • PWM capability on pins D3, D5, D6, D9, D10, and D11 (used for pulse-width modulation).
2. Analog Inputs

The Arduino Uno has 6 analog input pins, labeled A0 to A5. These pins can read analog voltages in the range of 0 to 5V, and they are connected to a 10-bit ADC (Analog-to-Digital Converter), meaning they can provide a range of values from 0 to 1023.

  • Pins A4 and A5 can also function as I2C communication pins (SDA and SCL).
3. PWM (Pulse-Width Modulation)

The Arduino Uno supports PWM on 6 of its digital pins: D3, D5, D6, D9, D10, and D11. PWM allows for the generation of an analog-like signal using digital outputs, useful for applications like dimming LEDs or controlling the speed of motors.

PWM is a timer function.

4. UART (Serial Communication)

The Arduino Uno has one hardware UART interface available on pins D0 (RX) and D1 (TX). This is used for serial communication with other devices, such as a computer, using the built-in USB-to-serial converter. The Arduino IDE also uses this interface for uploading sketches.

5. SPI (Serial Peripheral Interface)

The SPI interface allows for high-speed synchronous communication with other SPI-enabled devices. It operates on pins:

  • Pin 10 (SS - Slave Select)
  • Pin 11 (MOSI - Master Out Slave In)
  • Pin 12 (MISO - Master In Slave Out)
  • Pin 13 (SCK - Serial Clock)

These pins allow communication with devices like sensors, displays, or SD cards.

6. I2C (Inter-Integrated Circuit)

The I2C protocol enables communication with multiple devices using just two wires:

  • Pin A4 (SDA - Data Line)
  • Pin A5 (SCL - Clock Line)

I2C is commonly used for communicating with peripherals like sensors, RTC (Real-Time Clock) modules, and LCD displays.

7. External Interrupts

The Arduino Uno supports two external interrupts, which allow the microcontroller to respond to events on specific pins:

  • Pin D2 (Interrupt 0)
  • Pin D3 (Interrupt 1)

These interrupts can be triggered by rising edges, falling edges, or changes in state.

8. Timer/Counter

The ATmega328P microcontroller features three hardware timers that can be used for timing and counting operations:

  • Timer0: 8-bit timer, used for PWM on pins D5 and D6.
  • Timer1: 16-bit timer, used for PWM on pins D9 and D10.
  • Timer2: 8-bit timer, used for PWM on pins D3 and D11.
9. AREF (Analog Reference)

The AREF pin is used to set the reference voltage for analog input. The default reference voltage is 5V, but the AREF pin allows the use of a lower reference voltage for more accurate readings from the analog pins.

10. Reset

The RESET pin can be used to restart the microcontroller. There is also a built-in reset button on the board.

11. Power Pins

The Arduino Uno provides the following power pins:

  • 3.3V: Provides 3.3V output (max 50mA).
  • 5V: Provides 5V output from the regulator.
  • GND: Ground pins.
  • VIN: Input voltage to the Arduino when using an external power source (7-12V).
Pin Description and Alternate Functions
Pin Pin Name Primary Function Alternate Functions
1Digital 0 (RX)GPIOUART RX
2Digital 1 (TX)GPIOUART TX
3Digital 2GPIOExternal Interrupt (INT0)
4Digital 3GPIOPWM, External Interrupt (INT1)
5Digital 4GPIONone
6Digital 5GPIOPWM
7Digital 6GPIOPWM
8Digital 7GPIONone
9Digital 8GPIONone
10Digital 9GPIOPWM
11Digital 10GPIOPWM, SPI SS
12Digital 11GPIOPWM, SPI MOSI
13Digital 12GPIOSPI MISO
14Digital 13 (LED)GPIOSPI SCK
15Analog 0Analog InputNone
16Analog 1Analog InputNone
17Analog 2Analog InputNone
18Analog 3Analog InputNone
19Analog 4Analog InputI2C SDA
20Analog 5Analog InputI2C SCL
21RESETResetNone
223.3VPowerNone
235VPowerNone
24GNDGroundNone
25VINPower InputNone
Alternative Arduino Boards
Board CPU Speed RAM Flash Memory Peripherals Operating Voltage Other Specifications
Arduino Uno 16 MHz 2 KB 32 KB 14 Digital I/O, 6 Analog I/O, UART, SPI, I2C 5V 8-bit AVR, beginner friendly
Arduino Mega 2560 16 MHz 8 KB 256 KB 54 Digital I/O, 16 Analog I/O, 4 UART, SPI, I2C 5V More I/O pins, suitable for large projects
Arduino Nano 16 MHz 2 KB 32 KB 14 Digital I/O, 8 Analog I/O, UART, SPI, I2C 5V Compact size for breadboard projects
Arduino Leonardo 16 MHz 2.5 KB 32 KB 20 Digital I/O, 12 Analog I/O, UART, SPI, I2C, USB HID 5V Can emulate USB devices (keyboard/mouse)
Arduino UNO ADC Overview

The Arduino UNO is equipped with an Analog-to-Digital Converter (ADC) that allows it to read analog signals and convert them into digital values. This functionality is crucial for interfacing with various sensors and other analog devices.

ADC Specifications
  • Resolution: 10-bit
  • Reference Voltage: Default is 5V (can be changed to 1.1V internal reference or an external reference voltage)
  • Channels: 6 (A0 to A5)
  • Sampling Rate: Approximately 10,000 samples per second
How It Works

The ADC converts an analog input voltage into a 10-bit digital number. The range of the digital output value is from 0 to 1023. The formula to convert the ADC value back to voltage is:

Voltage = (ADC_value / 1023.0) * Reference_Voltage

Where ADC_value is the value read from the ADC, and Reference_Voltage is the reference voltage (typically 5V or 3.3V).

More information on Successive Approximation Conversion

Using the ADC in Code

To read an analog input using the ADC, you use the analogRead() function in your Arduino sketch. Here’s a simple example:


int sensorPin = A0;  // Analog input pin
int sensorValue = 0; // Variable to store the value read

void setup() {
    Serial.begin(115200);
}

void loop() {
    sensorValue = analogRead(sensorPin);  // Read the analog value
    Serial.println(sensorValue);          // Print the value to the serial monitor
    delay(1000);                          // Delay for 1 second
}
        
Reference Voltage Options

The default reference voltage is 5V (or 3.3V for some boards). You can change the reference voltage by using the analogReference() function:


analogReference(EXTERNAL); // Use an external reference voltage
        

To use the internal 1.1V reference voltage, you can use:


analogReference(INTERNAL); // Use the internal 1.1V reference voltage
        
Additional Notes

Ensure that the voltage applied to the analog input pins does not exceed the reference voltage or the board's operating voltage, as this could damage the board.

How the ADC in Arduino Uno Works

The Arduino Uno contains a 10-bit ADC (Analog-to-Digital Converter) that is used to convert analog voltages from the analog pins (A0 to A5) into digital values. The following steps explain the internal workings of the ADC:

1. Voltage Range and Resolution

The default reference voltage for the Arduino Uno is 5V, meaning the ADC can convert any analog voltage between 0V and 5V into a corresponding digital value. Since the ADC is 10-bit, it produces a range of digital values from 0 to 1023. This gives the ADC a resolution of:


5V / 1024 = 4.88 mV
        

This means that each increment of 1 in the ADC's digital output represents a change of approximately 4.88 mV in the input voltage.

Arduino UNO I2C Interface Overview

The I2C (Inter-Integrated Circuit) interface is a popular communication protocol used for connecting multiple peripherals to a microcontroller. It simplifies wiring by using only two lines for communication: data and clock. The Arduino UNO supports I2C communication, making it easy to interface with various sensors and devices.

Key Characteristics
  • Number of Wires: 2 (SDA and SCL)
  • Bus Speed: Standard mode (100 kHz), Fast mode (400 kHz), and High-speed mode (3.4 MHz)
  • Addressing: 7-bit or 10-bit addresses (typically 7-bit)
  • Number of Devices: Up to 127 devices (depending on address and bus capacitance)
I2C Pinout on Arduino UNO
  • SDA (Serial Data Line): Analog pin A4
  • SCL (Serial Clock Line): Analog pin A5

SDA and SCL need pull-up resistors, normally in the range of 2.7k to 10k. They should be tied to 5V for 5V circuits, or 3.3V for 3.3V devices.

How It Works

The I2C bus uses two lines for communication:

  • SDA (Serial Data Line): This line carries data between the master and slave devices.
  • SCL (Serial Clock Line): This line carries the clock signal generated by the master to synchronize data transfer.

Communication follows a master-slave model, where the master initiates communication and controls the clock, while slave devices respond. Each device has a unique address.

More information I²C (Inter-Integrated Circuit)

Data Transmission

Data transmission occurs in the following sequence:

  • Start Condition: Master pulls SDA low while SCL is high.
  • Address Frame: Master sends target slave address + R/W bit.
  • Data Frames: 8-bit data bytes, each followed by an acknowledgment from receiver.
  • Stop Condition: Master pulls SDA high while SCL is high.
I2C Master Example

Arduino Uno as I2C master, sends a command to a slave and receives a response.

  • Initialization: The Wire.begin() function initializes the I2C library for the master. Serial.begin(9600) initializes serial communication for debugging.
  • Sending Data: The master starts communication with Wire.beginTransmission(SLAVE_ADDR), sends a command with Wire.write(), and ends the transmission with Wire.endTransmission().
  • Receiving Data: The master requests 1 byte of data with Wire.requestFrom(SLAVE_ADDR, 1) and reads it with Wire.read(). The received data is printed to Serial Monitor.

/*
 * © 2024 Copyright Peter I. Dunne, all rights reserved
 * Prepared for educational use
 * Released under the Mozilla Public License
 * I2C Master Example
 * Sends a command to an I2C slave and receives a response.
 */

#include <Wire.h>

#define SLAVE_ADDR 8 // Address of the I2C slave device

byte i=0;

void setup() {
    // Initialize I2C as master
    Wire.begin();
    Serial.begin(115200);
    Serial.println("Arduino i2c example by Peter Ivan Dunne, ©2024, all rights reserved");
    Serial.println("Released under the Mozilla Public License");
    Serial.println("https://jazenga.com/educational");
    Serial.println("Purpose: to demonstrate use of i2c interface in master mode");
}

void loop() {
    // Request data from the slave
    Wire.beginTransmission(SLAVE_ADDR); // Start communication with slave
    Wire.write(i); // Send a command to the slave
    Wire.endTransmission(); // End communication

    // Request 1 byte of data from the slave
    Wire.requestFrom(SLAVE_ADDR, 1);
    Serial.print("Transmitted ");
    Serial.println(i);

    // Check if data is available
    if (Wire.available()) {
        byte receivedData = Wire.read(); // Read the received byte
        Serial.print("Received data: ");
        Serial.println(receivedData); // Print data to Serial Monitor
    }
    i++;
    delay(500); // Delay for 1 second
}
        
I2C Slave Example

Arduino Uno as I2C slave, listens for commands from master and responds.

  • Initialization: Slave is initialized with Wire.begin(SLAVE_ADDR). Wire.onRequest(requestEvent) and Wire.onReceive(receiveEvent) register ISR for requests and received data.
  • Handling Requests: When master requests data, requestEvent() is called and sends data with Wire.write().
  • Receiving Data: When master sends data, receiveEvent() is called and reads data with Wire.read(). Data is printed to Serial Monitor.

/*
 * © 2024 Copyright Peter I. Dunne, all rights reserved
 * Prepared for educational use
 * Released under the Mozilla Public License
 * I2C Slave Example
 * Listens for commands from I2C master and responds with data.
 */

#include <Wire.h>

#define SLAVE_ADDR 8 // Address of the I2C slave device

void setup() {
    // Initialize I2C as slave
    Wire.begin(SLAVE_ADDR);
    Wire.onRequest(requestEvent); // Register request event
    Wire.onReceive(receiveEvent); // Register receive event
    Serial.begin(115200);
    Serial.println("Arduino i2c example by Peter Ivan Dunne, ©2024, all rights reserved");
    Serial.println("Released under the Mozilla Public License");
    Serial.println("https://jazenga.com/educational");
    Serial.println("Purpose: to demonstrate use of i2c interface in slave mode");
}
byte receivedData=0;
void loop() {
    // Main loop does nothing, communication is handled by interrupts
}

// Function called when master requests data
void requestEvent() {
    Wire.write(255-receivedData); // Send data to the master
}

// Function called when master sends data
void receiveEvent(int numBytes) {
    while (Wire.available()) {
        receivedData = Wire.read(); // Read data from master
        Serial.print("Received data: ");
        Serial.println(receivedData, HEX); // Print received data
    }
}
        
Arduino UNO SPI Interface Overview

The SPI (Serial Peripheral Interface) is a synchronous serial communication protocol used for short-distance communication, primarily in embedded systems. It allows for high-speed data transfer between the master device (Arduino UNO) and one or more peripheral devices.

Key Characteristics
  • Number of Wires: 4 (MOSI, MISO, SCK, SS)
  • Speed: Up to 8 MHz (depends on the device and clock speed)
  • Mode: Full-duplex communication
  • Addressing: No addressing; devices are selected using the Slave Select (SS) line
SPI Pinout on Arduino UNO
  • MOSI (Master Out Slave In): Pin 11
  • MISO (Master In Slave Out): Pin 12
  • SCK (Serial Clock): Pin 13
  • SS (Slave Select): Pin 10 (can be reconfigured to other pins if needed)
How It Works

The SPI protocol uses four lines to communicate between devices:

  • MOSI (Master Out Slave In): Line used by the master to send data to the slave.
  • MISO (Master In Slave Out): Line used by the slave to send data to the master.
  • SCK (Serial Clock): Clock signal generated by the master to synchronize data transfer.
  • SS (Slave Select): Line used by the master to select the active slave device.

The master controls the clock and initiates communication, while slave devices respond. Data is transferred in full-duplex mode, meaning that data can be sent and received simultaneously.

More information on Serial Peripheral Interface

Data Transmission

Data transmission in SPI occurs in the following sequence:

  • Slave Selection: The master selects a slave by pulling SS low.
  • Clock Signal: The master generates a clock signal on SCK to synchronize transfer.
  • Data Transfer: Data is transmitted in 8-bit bytes. Master sends on MOSI while reading from MISO.
  • Deselecting the Slave: Master pulls SS high after transfer.
SPI Master Example

Arduino Uno configured as SPI master, sends and receives data.

How It Works
  • Initialization: SPI.begin() initializes SPI library. SPI.setClockDivider(), SPI.setDataMode(), SPI.setBitOrder() configure clock, mode, and bit order.
  • SPI Communication: SS LOW starts communication. Data sent via SPI.transfer(), received simultaneously. SS HIGH ends transaction.
  • Data Output: Received data printed to Serial Monitor.

/*
 * © 2024 Copyright Peter I. Dunne, all rights reserved
 * Prepared for educational use
 * Released under the Mozilla Public License
 * SPI Master Example
 * Sends data to SPI Slave and receives a response.
 */

#include <SPI.h>

byte dataToSend=0;

void setup() {
    SPI.begin();
    SPI.setClockDivider(SPI_CLOCK_DIV8);
    SPI.setDataMode(SPI_MODE0);
    SPI.setBitOrder(MSBFIRST);

    Serial.begin(115200);
    Serial.println("Arduino SPI example by Peter Ivan Dunne, ©2024, all rights reserved");
    Serial.println("Released under the Mozilla Public License");
    Serial.println("https://jazenga.com/educational");
    Serial.println("Purpose: to demonstrate use of SPI interface in master mode");
}

void loop() {
    digitalWrite(SS, LOW);
    ++dataToSend;
    byte receivedData = SPI.transfer(dataToSend);
    digitalWrite(SS, HIGH);

    Serial.print("Sent data: ");
    Serial.println(dataToSend);
    Serial.print("Received data: ");
    Serial.println(receivedData);
    delay(500);
}
        
SPI Slave Example

Arduino Uno configured as SPI slave, receives and responds to master.

  • Initialization: Arduino set up as SPI slave using SPCR |= _BV(SPE). SPI interrupt optionally enabled.
  • Data Reception: Received data stored in receivedData. ISR reads SPDR register.
  • Data Response: Slave responds to master using SPDR. Data sent for each byte received from master.

/*
 * © 2024 Copyright Peter I. Dunne, all rights reserved
 * Prepared for educational use
 * Released under the Mozilla Public License
 * SPI Slave Example
 * Receives data from SPI Master and sends a response.
 */

#include <SPI.h>

volatile byte receivedData = 0;
volatile byte dataToSend = 0;

void setup() {
    pinMode(MISO, OUTPUT);
    SPCR |= _BV(SPE);
    pinMode(SS, INPUT);
    pinMode(MOSI, INPUT);
    pinMode(SCK, INPUT);

    Serial.begin(115200);
    Serial.println("Arduino SPI example by Peter Ivan Dunne, ©2024, all rights reserved");
    Serial.println("Released under the Mozilla Public License");
    Serial.println("https://jazenga.com/educational");
    Serial.println("Purpose: to demonstrate use of SPI interface in slave mode");
}

void loop() {
    if (SPSR & _BV(SPIF)) {
        receivedData = SPDR;
        dataToSend = 255 - receivedData;
        SPDR = dataToSend;
    }

    Serial.print("Received data: ");
    Serial.println(receivedData);
}
        
Timer Functions on Arduino Uno

The Arduino Uno, based on the ATmega328P microcontroller, has three hardware timers: Timer0, Timer1, and Timer2. These timers are essential for various time-based operations, such as generating precise delays, controlling Pulse Width Modulation (PWM) on pins, and managing interrupts. Each timer operates independently of the main CPU and has its own set of control registers.

Overview of Timers on Arduino Uno

  • Timer0 - 8-bit timer, used by default for the millis() and delay() functions.
  • Timer1 - 16-bit timer, useful for high-precision timing and PWM generation.
  • Timer2 - 8-bit timer, typically used for additional PWM channels and timing operations.

More information on MCU timers

Timer0

Timer0 is an 8-bit timer, which means it can count from 0 to 255 before it overflows. It is the default timer used by Arduino functions like millis(), micros(), and delay(). Modifying Timer0 settings can affect the functionality of these functions.

Registers:

  • TCCR0A - Control register A for Timer0, used to set the waveform generation mode and output compare modes.
  • TCCR0B - Control register B for Timer0, used to set the clock source and prescaler.
  • TCNT0 - Timer/Counter register, holds the current value of Timer0.
  • OCR0A/OCR0B - Output compare registers used for PWM generation on pins 5 and 6.
Timer1

Timer1 is a 16-bit timer, meaning it can count from 0 to 65535 before it overflows. Timer1 is often used for high-precision timing operations and PWM generation. It is also used for functions like Servo control due to its higher resolution.

Registers:

  • TCCR1A - Control register A for Timer1, used for configuring waveform generation and output compare modes.
  • TCCR1B - Control register B for Timer1, used to set the clock source and prescaler.
  • TCNT1 - Timer/Counter register, holds the current value of Timer1.
  • OCR1A/OCR1B - Output compare registers used for PWM generation on pins 9 and 10.
  • ICR1 - Input capture register, used for high-precision event capturing.
Timer2

Timer2 is an 8-bit timer like Timer0, but it is independent of the functions like millis() and micros(). It is used for additional PWM channels and timing operations. Timer2 controls PWM on pins 3 and 11.

Registers:

  • TCCR2A - Control register A for Timer2, used to set the waveform generation mode and output compare modes.
  • TCCR2B - Control register B for Timer2, used to set the clock source and prescaler.
  • TCNT2 - Timer/Counter register, holds the current value of Timer2.
  • OCR2A/OCR2B - Output compare registers used for PWM generation on pins 3 and 11.
Timer Modes

The timers on the Arduino can operate in different modes. These modes are controlled by the timer’s control registers and allow for various functionalities, including:

  • Normal Mode: The timer counts up to its maximum value and then overflows. An interrupt can be triggered on overflow.
  • CTC (Clear Timer on Compare Match) Mode: The timer counts until a compare value is reached, at which point it resets. Useful for generating precise time intervals.
  • Fast PWM Mode: The timer generates a fast PWM signal by rapidly toggling an output pin at a defined duty cycle.
  • Phase Correct PWM Mode: Generates a more precise PWM signal with less jitter than fast PWM, often used for motor control.
Prescaler

The prescaler determines the speed at which the timer counts by dividing the system clock. The ATmega328P has a 16 MHz clock, and the prescaler options are:

  • No prescaler (full 16 MHz)
  • Prescaler of 8, 64, 256, or 1024

For example, with a prescaler of 64, the timer will increment once every 64 clock cycles, slowing down the counting process.

Arduino Timer Examples

Timers in Arduino can be used for various tasks such as generating frequencies, creating PWM signals, measuring time, and more. This page provides examples of different timer functionalities.

1. Frequency Generation

This example generates a frequency output using Timer1 by toggling a pin at a specified frequency.


/*
 * © 2024 Copyright Peter I. Dunne, all rights reserved
 * Prepared for educational use
 * Released under the Mozilla Public License
 * Frequency Generation Example
 * Generates a 1 kHz square wave on pin 9 using Timer1.
 */

const int outputPin = 9; // Output pin

void setup() {
    pinMode(outputPin, OUTPUT); // Set output pin as an output

    // Configure Timer1 for 1 kHz frequency
    noInterrupts(); // Disable interrupts
    TCCR1A = 0; // Set Timer1 control register A to 0
    TCCR1B = 0; // Set Timer1 control register B to 0
    TCNT1  = 0; // Initialize counter value to 0

    // Set compare match register for 1 kHz frequency
    OCR1A = 999; // (16*10^6) / 1000 = 16000
                 // (16000/2/8=1000)
                 // 1000-1=999
    // Configure Timer1 for CTC mode and set prescaler to 8
    TCCR1B |= (1 << WGM12); // CTC mode
    TCCR1B |= (1 << CS11);  // Prescaler 8
    TIMSK1 |= (1 << OCIE1A); // Enable Timer1 compare interrupt

    interrupts(); // Enable interrupts
}

void loop() {
    // Main loop does nothing, ISR handles frequency generation
}

// Interrupt Service Routine for Timer1 compare match
ISR(TIMER1_COMPA_vect) {
    digitalWrite(outputPin, !digitalRead(outputPin)); // Toggle pin state
}     
        
2. PWM Generation

This example uses Timer0 to generate a PWM signal with a 50% duty cycle on pin 9.


/*
* © 2024 Copyright Peter I. Dunne, all rights reserved
 * Prepared for educational use
 * Released under the Mozilla Public License
 * PWM Generation Example
 * Generates a PWM signal with 50% duty cycle on pin 9 using Timer0.
 */

const int pwmPin = 9; // Pin for PWM output

void setup() {
    pinMode(pwmPin, OUTPUT); // Set PWM pin as output

    // Configure Timer0 for PWM mode
    analogWrite(pwmPin, 128); // 50% duty cycle (128 out of 255)
}

void loop() {
    // Main loop does nothing
}
        
3. One-Shot Timer

This example triggers an action once after a delay of 1 second using Timer1.


/*
* © 2024 Copyright Peter I. Dunne, all rights reserved
 * Prepared for educational use
 * Released under the Mozilla Public License
 * One-Shot Timer Example
 * Performs an action once after a 1-second delay using Timer1.
 */

const int ledPin = 13; // Pin connected to an LED
bool actionTriggered = false; // Flag to check if action is triggered

void setup() {
    pinMode(ledPin, OUTPUT); // Set LED pin as output

    // Configure Timer1 for 1-second delay
    noInterrupts(); // Disable interrupts
    TCCR1A = 0; // Set Timer1 control register A to 0
    TCCR1B = 0; // Set Timer1 control register B to 0
    TCNT1  = 0; // Initialize counter value to 0

    // Set compare match register for 1-second delay
    OCR1A = 15624; // (16*10^6) / (1024*1) - 1 = 15624

    // Configure Timer1 for CTC mode and set prescaler to 1024
    TCCR1B |= (1 << WGM12); // CTC mode
    TCCR1B |= (1 << CS12) | (1 << CS10); // Prescaler 1024
    TIMSK1 |= (1 << OCIE1A); // Enable Timer1 compare interrupt

    interrupts(); // Enable interrupts
}

void loop() {
    if (actionTriggered) {
        digitalWrite(ledPin, HIGH); // Turn LED on
        delay(1000); // Wait for 1 second
        digitalWrite(ledPin, LOW); // Turn LED off
        actionTriggered = false; // Reset flag
    }
}

// Interrupt Service Routine for Timer1 compare match
ISR(TIMER1_COMPA_vect) {
    actionTriggered = true; // Set flag to trigger action
}
        
4. Frequency Measurement

This example measures the frequency of an input signal on pin 2 using Timer1.


/*
* © 2024 Copyright Peter I. Dunne, all rights reserved
 * Prepared for educational use
 * Released under the Mozilla Public License
 * Frequency Measurement Example
 * Measures the frequency of an input signal on pin 2 using Timer1.
 */

const int inputPin = 2; // Pin connected to input signal
volatile unsigned long pulseCount = 0; // Count of pulses
unsigned long frequency = 0; // Frequency in Hz

void setup() {
    pinMode(inputPin, INPUT); // Set input pin as input

    // Configure Timer1 for frequency measurement
    noInterrupts(); // Disable interrupts
    TCCR1A = 0; // Set Timer1 control register A to 0
    TCCR1B = 0; // Set Timer1 control register B to 0
    TCNT1  = 0; // Initialize counter value to 0

    // Set compare match register for measurement period (1 second)
    OCR1A = 15624; // (16*10^6) / (1024*1) - 1 = 15624

    // Configure Timer1 for CTC mode and set prescaler to 1024
    TCCR1B |= (1 << WGM12); // CTC mode
    TCCR1B |= (1 << CS12) | (1 << CS10); // Prescaler 1024
    TIMSK1 |= (1 << OCIE1A); // Enable Timer1 compare interrupt

    // Attach external interrupt to input pin
    attachInterrupt(digitalPinToInterrupt(inputPin), countPulses, RISING);

    // Initialize Serial Monitor
    Serial.begin(115200);
    interrupts(); // Enable interrupts
}

void loop() {
    // Print frequency once per second
    delay(1000);
    frequency = pulseCount; // Frequency in Hz
    pulseCount = 0; // Reset pulse count
    Serial.print("Frequency: ");
    Serial.print(frequency);
    Serial.println(" Hz");
}

// Interrupt Service Routine for Timer1 compare match
ISR(TIMER1_COMPA_vect) {
    // Timer interrupt; no action needed
}

// External Interrupt Service Routine to count pulses
void countPulses() {
    pulseCount++; // Increment pulse count
}
        
5. Externally Triggered Timer Pulse

This example generates a pulse on pin 9 when an external trigger is detected on pin 2.


/*
* © 2024 Copyright Peter I. Dunne, all rights reserved
 * Prepared for educational use
 * Released under the Mozilla Public License
 * Externally Triggered Timer Pulse Example
 * Generates a pulse on pin 9 when an external trigger is detected on pin 2.
 */

const int triggerPin = 2; // Pin connected to external trigger
const int pulsePin = 9; // Pin for pulse output

void setup() {
    pinMode(triggerPin, INPUT); // Set trigger pin as input
    pinMode(pulsePin, OUTPUT); // Set pulse pin as output

    // Initialize Serial Monitor
    Serial.begin(115200);

    // Attach external interrupt to trigger pin
    attachInterrupt(digitalPinToInterrupt(triggerPin), triggerPulse, RISING);
}

void loop() {
    // Main loop does nothing
}

// External Interrupt Service Routine to trigger pulse
void triggerPulse() {
    digitalWrite(pulsePin, HIGH); // Set pulse pin high
    delay(10); // Pulse duration
    digitalWrite(pulsePin, LOW); // Set pulse pin low
    Serial.println("Pulse triggered");
}
        
6. Externally Triggered Time Measurement

This example measures the time interval between two external triggers on pin 2 and pin 3.


/*
* © 2024 Copyright Peter I. Dunne, all rights reserved
 * Prepared for educational use
 * Released under the Mozilla Public License
 * Externally Triggered Time Measurement Example
 * Measures the time interval between two external triggers on pins 2 and 3.
 */

const int triggerPin1 = 2; // Pin connected to first external trigger
const int triggerPin2 = 3; // Pin connected to second external trigger
volatile unsigned long startTime = 0; // Start time
volatile unsigned long endTime = 0; // End time
volatile bool firstTrigger = false; // Flag for first trigger

void setup() {
    pinMode(triggerPin1, INPUT); // Set first trigger pin as input
    pinMode(triggerPin2, INPUT); // Set second trigger pin as input

    // Initialize Serial Monitor
    Serial.begin(115200);

    // Attach external interrupts to trigger pins
    attachInterrupt(digitalPinToInterrupt(triggerPin1), recordStartTime, RISING);
    attachInterrupt(digitalPinToInterrupt(triggerPin2), recordEndTime, RISING);
}

void loop() {
    if (firstTrigger && (endTime > startTime)) {
        unsigned long interval = endTime - startTime; // Time interval
        Serial.print("Time Interval: ");
        Serial.print(interval);
        Serial.println(" microseconds");
        firstTrigger = false; // Reset flag
    }
}

// External Interrupt Service Routine to record start time
void recordStartTime() {
    startTime = micros(); // Record start time
    firstTrigger = true; // Set flag for first trigger
}

// External Interrupt Service Routine to record end time
void recordEndTime() {
    endTime = micros(); // Record end time
}
        
Arduino UNO PWM Example

This example demonstrates how to use PWM (Pulse Width Modulation) on the Arduino UNO. It includes code to set the PWM frequency to 25 kHz and provides details on how PWM works on the Arduino platform.

Code Example

/*
 * © 2024 Copyright Peter I. Dunne, all rights reserved
 * Prepared for educational use
 * Released under the Mozilla Public License
 * Arduino UNO PWM Example
 * Demonstrates how to use PWM and set a frequency of 25 kHz.
 */

const int pwmPin = 9; // PWM output pin

void setup() {
    pinMode(pwmPin, OUTPUT); // Set the PWM pin as an output

  // Stop Timer1
  TCCR1A = 0;  // Clear Timer/Counter Control Register A
  TCCR1B = 0;  // Clear Timer/Counter Control Register B

  // Set the PWM frequency to 25kHz
  // 16mhz/25000=640
  // Setting Prescaler to 1 and TOP to 640 gives us a 25kHz frequency
  TCCR1B |= _BV(WGM13) | _BV(WGM12);  // Set mode 14: Fast PWM with ICR1 as TOP
  TCCR1A |= _BV(WGM11);               // Set mode 14: Fast PWM
  TCCR1A |= _BV(COM1A1);              // Set non-inverting mode for pin 9

  ICR1 = 640;                         // Set TOP to 640 for 25kHz frequency
  OCR1A = 160;                        // Set PWM duty cycle to 25% duty cycle

  // Set prescaler to 1 (no prescaling)
  TCCR1B |= _BV(CS10);
}

void loop() {
    // Output a PWM signal with a 50% duty cycle
    analogWrite(pwmPin, 320); // 320 out of 640 corresponds to 50% duty cycle
    delay(1000); // Wait for 1 second

    // Change duty cycle
    analogWrite(pwmPin, 320); // 64 out of 640 corresponds to ~25% duty cycle
    delay(1000); // Wait for 1 second
}
        
How It Works
  • Setting PWM Frequency: The code calculates the appropriate prescaler for Timer1 to achieve a PWM frequency of 25 kHz.
  • Outputting PWM Signal: Uses the analogWrite() function to output a PWM signal with varying duty cycles on the specified pin.
Serial Communication - Arduino Uno

The Arduino Uno features one hardware serial port accessible via the Serial object. It communicates via USB to the PC using an onboard USB-to-Serial converter (ATmega16U2).

Simple Serial Text Communication

Send/Receive text using Serial Monitor:


void setup() {
  Serial.begin(115200);  // Initialize serial at 9600 baud
  while (!Serial);     // Wait for Serial (only needed on Leonardo, but safe)
  Serial.println("Hello from Arduino!");
}

void loop() {
  if (Serial.available()) {
    String input = Serial.readStringUntil('\n');
    Serial.print("You said: ");
    Serial.println(input);
  }
}
        
Structured Data Communication with CRC

This example uses a struct to send and receive binary data efficiently between devices (e.g., Arduino <-> PC or another Arduino).

The data packet includes:

  • bool status
  • int value
  • float temperature
  • time_t timestamp
  • char label[16]
  • uint8_t crc

#include <Arduino.h>

struct DataPacket {
  bool status;
  byte level;
  int16_t value16;
  int32_t value32;
  float temperature;
  time_t timestamp;
  char label[16];
  uint8_t crc;
};

DataPacket packet;

uint8_t calculateCRC(uint8_t *data, size_t length) {
  uint8_t crc = 0;
  for (size_t i = 0; i < length; i++) {
    crc ^= data[i];
  }
  return crc;
}

void setup() {
  Serial.begin(115200);
  while (!Serial);

  // Fill packet with test data
  packet.status = true;
  packet.level = 42;
  packet.value16 = -3276;
  packet.value32 = 12345678;
  packet.temperature = 25.7;
  packet.timestamp = now();
  strncpy(packet.label, "Node_A1", sizeof(packet.label));

  // Calculate CRC for all but last byte (crc itself)
  packet.crc = calculateCRC((uint8_t*)&packet, sizeof(packet) - 1);

  // Send the packet as binary
  Serial.write((uint8_t*)&packet, sizeof(packet));
}

void loop() {
  // Read structured data if available
  if (Serial.available() >= sizeof(DataPacket)) {
    DataPacket incoming;
    Serial.readBytes((char*)&incoming, sizeof(DataPacket));
    uint8_t calc = calculateCRC((uint8_t*)&incoming, sizeof(incoming) - 1);
    if (incoming.crc == calc) {
      Serial.print("Valid data: ");
      Serial.println(incoming.label);
    } else {
      Serial.println("CRC mismatch!");
    }
  }
}
        
Note
  • Ensure both devices use the same struct layout and byte ordering (no padding differences).
  • Use a reliable serial terminal or secondary microcontroller to test receive code.
  • time_t requires #include <TimeLib.h> if you want full RTC time support.
Arduino Interrupts Examples

Interrupts allow the Arduino to respond immediately to certain events by pausing the current code execution and executing a special function called an Interrupt Service Routine (ISR). This page provides examples of different types of interrupts on the Arduino Uno.

Interrupt Overview

Interrupts are signals that temporarily halt the main program to execute a special function, known as an ISR. The Arduino Uno supports various types of interrupts:

  • External Interrupts: Triggered by external signals on specific pins (e.g., pin 2 and pin 3).
  • Timer Interrupts: Triggered by internal timers to execute code at regular intervals.
  • Serial Interrupts: Triggered by incoming serial data.
  • SPI Interrupts: Triggered by SPI data transfers.
  • I2C Interrupts: Triggered by I2C data transfers.
  • ADC Interrupts: Triggered by Analog-to-Digital Converter (ADC) conversions.
  • Quadrature Encoding: Triggered by external signals to determine rotation of a shaft with quadrature encoder.
Caution!

All variables used in ISR must be defined with the Volatile keyword.

External Interrupt Example

This example demonstrates how to use an external interrupt to toggle an LED whenever a button connected to pin 2 is pressed.


/*
 * External Interrupt Example
 * Toggles an LED when a button connected to pin 2 is pressed.
 */

const int buttonPin = 2; // Pin connected to the button
const int ledPin = 13; // Pin connected to the LED

volatile bool ledState = LOW; // State of the LED (volatile as it is modified in ISR)

void setup() {
    pinMode(buttonPin, INPUT_PULLUP); // Set button pin as input with pull-up resistor
    pinMode(ledPin, OUTPUT); // Set LED pin as output

    // Attach interrupt to button pin
    attachInterrupt(digitalPinToInterrupt(buttonPin), toggleLED, FALLING);

    // Initialize Serial Monitor
    Serial.begin(115200);
}

void loop() {
    // Main loop does nothing, ISR handles LED toggling
}

// Interrupt Service Routine to toggle LED
void toggleLED() {
    ledState = !ledState; // Toggle LED state
    digitalWrite(ledPin, ledState); // Update LED
    Serial.println("Button pressed, LED toggled");
}
        
Timer Interrupt Example

This example demonstrates how to use Timer1 to generate an interrupt every 1 second.


/*
 * © 2024 Copyright Peter I. Dunne, all rights reserved
 * Prepared for educational use
 * Released under the Mozilla Public License
 * Timer interrupt example
 */

#include <avr/io.h>
#include <avr/interrupt.h>

void setup() {
  // Set pin 13 as output
  pinMode(13, OUTPUT);
  
  // Stop Timer1
  TCCR1A = 0; // Clear Timer/Counter Control Register A
  TCCR1B = 0; // Clear Timer/Counter Control Register B
  TCNT1 = 0;  // Initialize Timer1 counter to 0

  // Set CTC (Clear Timer on Compare Match) mode
  TCCR1B |= (1 << WGM12);

  // Set prescaler to 1024
  TCCR1B |= (1 << CS12) | (1 << CS10);

  // Set compare match register to trigger interrupt every 1 second
  // f_timer = 16 MHz / (1024 * (1 + 15624)) ≈ 1 Hz
  OCR1A = 15624;  // 16,000,000 / (1024 * 1Hz) - 1 = 15624

  // Enable Timer1 compare interrupt
  TIMSK1 |= (1 << OCIE1A);

  // Enable global interrupts
  sei();
}

void loop() {
  // Main loop does nothing, all work is done in ISR
}

// Timer1 interrupt service routine (ISR)
ISR(TIMER1_COMPA_vect) {
  // Toggle LED on pin 13
  digitalWrite(13, !digitalRead(13));
}
        
Serial Interrupt Example

This example demonstrates how to handle incoming serial data using interrupts. Note that serial interrupts are handled by the Arduino core library, and direct manipulation of serial interrupts is generally not required.


/*
 * Serial Interrupt Example
 * Responds to incoming serial data by echoing it back.
 */

const int ledPin = 13; // Pin connected to the LED

void setup() {
    pinMode(ledPin, OUTPUT); // Set LED pin as output

    // Initialize Serial Monitor
    Serial.begin(115200);
}

void loop() {
    // Check if data is available to read
    if (Serial.available() > 0) {
        char incomingByte = Serial.read(); // Read the incoming byte
        Serial.print("Received: ");
        Serial.println(incomingByte); // Echo received data back to Serial Monitor

        // Toggle LED as feedback
        digitalWrite(ledPin, HIGH);
        delay(100);
        digitalWrite(ledPin, LOW);
    }
}
        
SPI Interrupt Example

This example demonstrates handling SPI data interrupts. Note that the Arduino core library handles SPI interrupts internally.


/*
 * SPI Interrupt Example
 * Note: Direct SPI interrupt handling is generally managed by the Arduino core.
 * This example shows basic SPI communication.
 */

#include <SPI.h>

const int ledPin = 13; // Pin connected to the LED

void setup() {
    pinMode(ledPin, OUTPUT); // Set LED pin as output

    // Initialize SPI
    SPI.begin();
    SPI.setClockDivider(SPI_CLOCK_DIV8);

    // Initialize Serial Monitor
    Serial.begin(115200);
}

void loop() {
    // Basic SPI communication example
    digitalWrite(ledPin, HIGH);
    byte response = SPI.transfer(0x42); // Send a byte and receive a response
    Serial.print("SPI Response: ");
    Serial.println(response, HEX); // Print SPI response
    digitalWrite(ledPin, LOW);
    delay(1000); // Delay for 1 second
}
        
I2C Interrupt Example

This example demonstrates handling I2C data interrupts. The Arduino I2C library handles I2C interrupts internally.


/*
 * I2C Interrupt Example
 * Note: Direct I2C interrupt handling is managed by the Arduino core library.
 * This example demonstrates basic I2C communication.
 */

#include <Wire.h>

const int ledPin = 13; // Pin connected to the LED

void setup() {
    pinMode(ledPin, OUTPUT); // Set LED pin as output

    // Initialize I2C
    Wire.begin();
    Wire.beginTransmission(0x08); // Address of the I2C device
    Wire.write("Hello I2C");
    Wire.endTransmission();

    // Initialize Serial Monitor
    Serial.begin(115200);
}

void loop() {
    // Basic I2C communication example
    digitalWrite(ledPin, HIGH);
    Serial.println("I2C message sent.");
    digitalWrite(ledPin, LOW);
    delay(1000); // Delay for 1 second
}
        
ADC Interrupt Example

This example demonstrates handling ADC conversion completion using interrupts.



#include <avr/io.h>
#include <avr/interrupt.h>

volatile uint16_t adcValue = 0;  // Variable to store ADC result

void setup() {
  // Set up serial communication for debugging (optional)
  Serial.begin(115200);

  // Configure the ADC
  ADMUX = (1 << REFS0);  // Set reference voltage to AVcc (5V), input to ADC0 (A0)

  ADCSRA = (1 << ADEN);  // Enable the ADC
  ADCSRA |= (1 << ADIE); // Enable ADC interrupt
  ADCSRA |= (1 << ADPS2) | (1 << ADPS1); // Set ADC prescaler to 64 (16MHz/64 = 250kHz)

  // Start the first ADC conversion
  ADCSRA |= (1 << ADSC);

  // Enable global interrupts
  sei();
}

void loop() {
  // Main loop does nothing, all work is handled in the ISR
  // Optionally, print the ADC value for debugging
  Serial.println(adcValue);
  delay(500);  // Slow down the serial output for readability
}

// ADC Conversion Complete Interrupt Service Routine
ISR(ADC_vect) {
  // Read the 10-bit ADC result (ADCL must be read first)
  adcValue = ADC;

  // Start a new ADC conversion
  ADCSRA |= (1 << ADSC);
}
        
Quadrature Encoding on Arduino UNO

This example demonstrates how to use quadrature encoding to measure the position and direction of a rotary encoder on an Arduino UNO. There are two methods for handling the quadrature signals: one using interrupts and another using timer-based polling. Both methods are explained below.

Components Required
  • Arduino UNO
  • Quadrature Encoder (e.g., incremental rotary encoder)
  • Resistors (optional for pull-down)
What is Quadrature Encoding?

A quadrature encoder generates two square wave signals (A and B), which are 90 degrees out of phase. The phase relationship between the two signals allows the detection of both direction and speed of rotation. By counting the transitions of the A and B signals and checking their order, you can determine the direction and position of the encoder.

Interrupt-Driven Quadrature Encoding

In this method, we use the external interrupt pins on the Arduino UNO (pins 2 and 3) to capture changes in the quadrature signals. This allows the microcontroller to react quickly to signal changes without missing any steps, even at high speeds.

Code Example (Interrupt-Driven)


// Pin definitions
const int encoderPinA = 2;  // Pin for encoder A (interrupt pin)
const int encoderPinB = 3;  // Pin for encoder B

volatile long encoderPosition = 0;  // Variable to store the position of the encoder
volatile int lastState = 0;         // Store previous state of encoder pin A

void setup() {
  pinMode(encoderPinA, INPUT);
  pinMode(encoderPinB, INPUT);

  // Enable pull-up resistors
  digitalWrite(encoderPinA, HIGH);
  digitalWrite(encoderPinB, HIGH);

  // Attach interrupt to encoderPinA (Interrupt 0)
  attachInterrupt(digitalPinToInterrupt(encoderPinA), updateEncoder, CHANGE);

  // Start serial communication for debugging
  Serial.begin(115200);
}

void loop() {
  // Print the current position of the encoder
  Serial.print("Encoder Position: ");
  Serial.println(encoderPosition);
  delay(100);
}

// Interrupt Service Routine (ISR)
void updateEncoder() {
  int currentStateA = digitalRead(encoderPinA);  // Read current state of pin A
  int stateB = digitalRead(encoderPinB);         // Read current state of pin B

  // If the state of A has changed
  if (currentStateA != lastState) {
    // Check the direction based on state of B
    if (stateB != currentStateA) {
      encoderPosition++;
    } else {
      encoderPosition--;
    }
    lastState = currentStateA;  // Update the last state of A
  }
}

        
Explanation
  • Interrupts: We use attachInterrupt() to trigger an interrupt on both rising and falling edges of the A and B signals.
  • ISR: The updateEncoder() function is the Interrupt Service Routine (ISR) that runs every time there is a change in the state of the encoder signals. It reads both A and B pins and determines whether the encoder is rotating clockwise or counterclockwise.
  • Direction Detection: By checking the phase relationship between A and B, the code determines the direction of rotation. If A leads B, the encoder is moving forward, and if B leads A, it is moving backward.