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:
The Arduino Uno provides access to several key peripherals that allow for a wide range of applications.
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:
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.
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.
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.
The SPI interface allows for high-speed synchronous communication with other SPI-enabled devices. It operates on pins:
These pins allow communication with devices like sensors, displays, or SD cards.
The I2C protocol enables communication with multiple devices using just two wires:
I2C is commonly used for communicating with peripherals like sensors, RTC (Real-Time Clock) modules, and LCD displays.
The Arduino Uno supports two external interrupts, which allow the microcontroller to respond to events on specific pins:
These interrupts can be triggered by rising edges, falling edges, or changes in state.
The ATmega328P microcontroller features three hardware timers that can be used for timing and counting operations:
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.
The RESET pin can be used to restart the microcontroller. There is also a built-in reset button on the board.
The Arduino Uno provides the following power pins:
| Pin | Pin Name | Primary Function | Alternate Functions |
|---|---|---|---|
| 1 | Digital 0 (RX) | GPIO | UART RX |
| 2 | Digital 1 (TX) | GPIO | UART TX |
| 3 | Digital 2 | GPIO | External Interrupt (INT0) |
| 4 | Digital 3 | GPIO | PWM, External Interrupt (INT1) |
| 5 | Digital 4 | GPIO | None |
| 6 | Digital 5 | GPIO | PWM |
| 7 | Digital 6 | GPIO | PWM |
| 8 | Digital 7 | GPIO | None |
| 9 | Digital 8 | GPIO | None |
| 10 | Digital 9 | GPIO | PWM |
| 11 | Digital 10 | GPIO | PWM, SPI SS |
| 12 | Digital 11 | GPIO | PWM, SPI MOSI |
| 13 | Digital 12 | GPIO | SPI MISO |
| 14 | Digital 13 (LED) | GPIO | SPI SCK |
| 15 | Analog 0 | Analog Input | None |
| 16 | Analog 1 | Analog Input | None |
| 17 | Analog 2 | Analog Input | None |
| 18 | Analog 3 | Analog Input | None |
| 19 | Analog 4 | Analog Input | I2C SDA |
| 20 | Analog 5 | Analog Input | I2C SCL |
| 21 | RESET | Reset | None |
| 22 | 3.3V | Power | None |
| 23 | 5V | Power | None |
| 24 | GND | Ground | None |
| 25 | VIN | Power Input | None |
| 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) |
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.
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).
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
}
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
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.
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:
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.
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.
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.
The I2C bus uses two lines for communication:
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.
Data transmission occurs in the following sequence:
Arduino Uno as I2C master, sends a command to a slave and receives a response.
Wire.begin() function initializes the I2C library for the master. Serial.begin(9600) initializes serial communication for debugging.Wire.beginTransmission(SLAVE_ADDR), sends a command with Wire.write(), and ends the transmission with Wire.endTransmission().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
}
Arduino Uno as I2C slave, listens for commands from master and responds.
Wire.begin(SLAVE_ADDR). Wire.onRequest(requestEvent) and Wire.onReceive(receiveEvent) register ISR for requests and received data.requestEvent() is called and sends data with Wire.write().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
}
}
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.
The SPI protocol uses four lines to communicate between devices:
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.
Data transmission in SPI occurs in the following sequence:
Arduino Uno configured as SPI master, sends and receives data.
SPI.begin() initializes SPI library. SPI.setClockDivider(), SPI.setDataMode(), SPI.setBitOrder() configure clock, mode, and bit order.SPI.transfer(), received simultaneously. SS HIGH ends transaction.
/*
* © 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);
}
Arduino Uno configured as SPI slave, receives and responds to master.
SPCR |= _BV(SPE). SPI interrupt optionally enabled.receivedData. ISR reads SPDR register.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);
}
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.
millis() and delay() functions.
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 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 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.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:
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:
For example, with a prescaler of 64, the timer will increment once every 64 clock cycles, slowing down the counting process.
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.
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
}
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
}
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
}
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
}
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");
}
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
}
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.
/*
* © 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
}
analogWrite() function to output a PWM signal with varying duty cycles on the specified pin.
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).
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);
}
}
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 statusint valuefloat temperaturetime_t timestampchar 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!");
}
}
}
time_t requires #include <TimeLib.h> if you want full RTC time support.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.
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:
All variables used in ISR must be defined with the Volatile keyword.
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");
}
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));
}
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);
}
}
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
}
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
}
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);
}
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.
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.
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.
// 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
}
}
attachInterrupt() to trigger an interrupt on both rising and falling edges of the A and B signals.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.