TinkerBench is reader-supported. When you buy through links on our site, we may earn an affiliate commission.
Advanced Arduino Techniques: Beyond the Basics
Phone cases · 15% off first order
Phone Case GiftThey pick the model · 2 minutes Code FIRST15GIFTYou’ve mastered blinking LEDs and reading sensor values with your Arduino. Now it’s time to unlock the true potential of this versatile microcontroller. This guide will delve into three key advanced techniques: interrupts, timers, and EEPROM, offering practical examples and explanations to elevate your Arduino projects from simple to sophisticated. We’ll be using the ubiquitous Arduino Uno Rev3 (A000066)Check on Amazon → as our example platform.
Who is this guide for?
This guide is for intermediate Arduino users who have a solid grasp of basic programming concepts and Arduino syntax. If you’re comfortable with digital and analog I/O, variables, loops, and conditional statements, you’re ready to move on to these more advanced topics. If you’re new to Arduino, we suggest starting with one of our beginner guides first, like our Arduino starter kit recommendationCheck on Amazon →.
Harnessing the Power of Interrupts
Interrupts allow your Arduino to respond to external events in real-time, without constantly polling for changes. This is crucial for applications where timing is critical, such as button presses, encoder readings, or receiving data from a serial port.
Understanding Interrupts
An interrupt is a signal that tells the microcontroller to immediately suspend its current task and execute a special function called an Interrupt Service Routine (ISR). After the ISR is complete, the microcontroller resumes where it left off.
The Arduino Uno has a limited number of external interrupt pins:
- Pin 2: Interrupt 0
- Pin 3: Interrupt 1
These pins can be configured to trigger interrupts on different events:
- LOW: The interrupt is triggered when the pin is LOW.
- CHANGE: The interrupt is triggered when the pin changes value.
- RISING: The interrupt is triggered when the pin goes from LOW to HIGH.
- FALLING: The interrupt is triggered when the pin goes from HIGH to LOW.
Example: Button Debouncing with Interrupts
Button bouncing occurs when a button press generates multiple rapid on/off signals due to mechanical imperfections. Interrupts provide a clean way to debounce a button.
volatile byte buttonState = HIGH; // volatile because it's modified in the ISR
void setup() {
pinMode(2, INPUT_PULLUP); // Use internal pull-up resistor
attachInterrupt(digitalPinToInterrupt(2), buttonPress, FALLING); // Interrupt on falling edge
Serial.begin(9600);
}
void loop() {
if (buttonState == LOW) {
Serial.println("Button Pressed!");
buttonState = HIGH; // Reset the state
delay(200); // Debounce delay
}
}
void buttonPress() {
buttonState = LOW; // Set the state to LOW
}
Explanation:
volatile byte buttonState = HIGH;: Declares a volatile variable to store the button state.volatileis crucial because the variable is modified within the ISR.pinMode(2, INPUT_PULLUP);: Configures pin 2 as an input with the internal pull-up resistor enabled.attachInterrupt(digitalPinToInterrupt(2), buttonPress, FALLING);: Attaches an interrupt to pin 2 that triggers thebuttonPressfunction on the falling edge.digitalPinToInterrupt(pin)converts the digital pin number to the corresponding interrupt number.buttonPress(): This is the Interrupt Service Routine (ISR). It setsbuttonStatetoLOWwhen the button is pressed.- The
loop()function checks thebuttonState. If it’sLOW, it prints “Button Pressed!”, resets the state toHIGH, and introduces a 200ms delay for debouncing.
Important Notes about ISRs:
- Keep ISRs short and fast. Avoid delays or long computations within an ISR.
- Declare variables shared between the ISR and the main program as
volatile. - Disable interrupts during critical sections of code using
noInterrupts()and re-enable them withinterrupts().
Mastering Timers for Precise Timing
Phone cases · 15% off first order
Titan CasePrecision fit · 2,000+ designs Code FIRST15TITTimers are essential for tasks that require precise timing, such as PWM control, generating waveforms, or measuring intervals. The Arduino Uno has three timers: Timer0, Timer1, and Timer2. Each timer has different capabilities and configurations.
Understanding Timers
Timers are hardware counters that increment at a specific rate. When the counter reaches a predefined value, an interrupt can be triggered. This allows you to execute code at precise intervals.
Example: Generating a PWM Signal with Timer1
This example demonstrates how to use Timer1 to generate a PWM signal on pin 9.
void setup() {
pinMode(9, OUTPUT); // PWM output pin
TCCR1A = _BV(COM1A1) | _BV(WGM11); // Fast PWM mode, non-inverting output on OC1A
TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // Fast PWM mode, no prescaling
ICR1 = 399; // Set PWM frequency to approximately 10 kHz (16 MHz / (399 + 1))
OCR1A = 200; // Set duty cycle to approximately 50% (200 / 399)
}
void loop() {
// You can adjust the duty cycle in the loop if needed
// For example:
// OCR1A = analogRead(A0) / 5; // Map analog input to duty cycle
}
Explanation:
pinMode(9, OUTPUT);: Configures pin 9 as an output pin.TCCR1AandTCCR1B: These registers configure Timer1’s control registers.COM1A1: Enables non-inverting PWM output on OC1A (pin 9).WGM11,WGM12,WGM13: Sets Timer1 to Fast PWM mode with ICR1 as the TOP value.CS10: Sets the prescaler to no prescaling (clock frequency = 16 MHz).
ICR1 = 399;: Sets the Input Capture Register 1 (ICR1) to 399. This determines the PWM frequency. The formula is:Frequency = Clock Frequency / (ICR1 + 1). In this case, 16 MHz / (399 + 1) ≈ 40 kHz.OCR1A = 200;: Sets the Output Compare Register 1A (OCR1A) to 200. This determines the duty cycle. The formula is:Duty Cycle = OCR1A / ICR1. In this case, 200 / 399 ≈ 50%.
Important Notes about Timers:
- Directly manipulating timer registers can be complex. Use libraries like
<TimerOne.h>for easier timer management in some cases. However, understanding the underlying registers provides more control. - Timer0 is used by the
millis()anddelay()functions. Modifying Timer0 can affect these functions. - Each timer has different modes of operation. Refer to the ATmega328P datasheet for detailed information.
Utilizing EEPROM for Persistent Data Storage
EEPROM (Electrically Erasable Programmable Read-Only Memory) allows you to store data that persists even when the Arduino is powered off. This is useful for storing configuration settings, calibration data, or other important information that needs to be retained between sessions.
Understanding EEPROM
The Arduino Uno has 1KB (1024 bytes) of EEPROM. You can read from and write to EEPROM using the <EEPROM.h> library.
Example: Storing and Retrieving a Calibration Value
This example demonstrates how to store a calibration value in EEPROM and retrieve it on startup.
#include <EEPROM.h>
const int calibrationAddress = 0; // EEPROM address to store the calibration value
float calibrationValue = 1.0; // Default calibration value
void setup() {
Serial.begin(9600);
delay(1000);
// Read the calibration value from EEPROM
EEPROM.get(calibrationAddress, calibrationValue);
Serial.print("Calibration Value: ");
Serial.println(calibrationValue);
// Check if the EEPROM value is valid (e.g., not all 0xFF)
if (calibrationValue > 1000 || calibrationValue < -1000) {
Serial.println("Invalid calibration value in EEPROM. Using default.");
calibrationValue = 1.0;
}
// Simulate a calibration process (replace with your actual calibration)
calibrationValue = 1.23;
// Store the new calibration value in EEPROM
EEPROM.put(calibrationAddress, calibrationValue);
Serial.println("New Calibration Value stored in EEPROM.");
}
void loop() {
// Use the calibration value in your application
// For example:
// float sensorValue = analogRead(A0) * calibrationValue;
delay(1000);
}
Explanation:
#include <EEPROM.h>: Includes the EEPROM library.const int calibrationAddress = 0;: Defines the EEPROM address where the calibration value will be stored.float calibrationValue = 1.0;: Defines a variable to store the calibration value with a default value.EEPROM.get(calibrationAddress, calibrationValue);: Reads the calibration value from EEPROM at the specified address and stores it in thecalibrationValuevariable.- The code checks if the read value is within a reasonable range. This helps prevent using corrupt data if the EEPROM is uninitialized.
EEPROM.put(calibrationAddress, calibrationValue);: Writes the new calibration value to EEPROM.
Important Notes about EEPROM:
- EEPROM has a limited number of write cycles (typically 100,000). Avoid writing to EEPROM frequently.
- Use
EEPROM.update()instead ofEEPROM.write()to avoid unnecessary writes.EEPROM.update()only writes to the EEPROM if the value has changed. - Be careful not to overwrite critical system data stored in EEPROM.
- EEPROM is byte-oriented. You need to handle data types larger than a byte (e.g.,
int,float) using functions likeEEPROM.get()andEEPROM.put().
By mastering interrupts, timers, and EEPROM, you’ll significantly expand your Arduino programming capabilities and be able to create more complex and responsive projects. Remember to consult the ATmega328P datasheet and Arduino reference documentation for more in-depth information.






