Zbotic Logo Zbotic Logo
  • Home
  • Shop
  • Sale
  • 3D Print Service
  • PCB Service
  • B2B
  • Blogs
  • Contact Us
0 0

View Wishlist Add all to cart

0 0
0 Shopping Cart
Shopping cart (0)
Subtotal: ₹0.00

View cartCheckout

  • Shop
  • About Us
  • Contact Us
  • Reseller
  • Blogs
020 69134444
1800 209 0998
[email protected]
Help Desk
Facebook Twitter Instagram Linkedin YouTube
Zbotic Logo Zbotic Logo
0 0

View Wishlist Add all to cart

0 0
0 Shopping Cart
Shopping cart (0)
Subtotal: ₹0.00

View cartCheckout

All departments
  • 3D Print Service
  • 3D Printer
  • Batteries & Chargers
  • Development Boards
  • Drone Parts
  • EBike parts
  • Sensor Modules
  • Electronic Components
  • Electronic Modules
  • IoT and Wireless
  • Mechanical Parts and Workbench Tools
  • Motors & Drivers & Pumps & Actuators
  • DIY and Robot Kits
  • Show more
  • Home
  • Shop
  • Sale
  • 3D Print Service
  • PCB Service
  • B2B
  • Blogs
  • Contact Us
Return to previous page
Home Arduino & Microcontrollers

Arduino Timer Tutorial: millis(), micros() & Hardware Timers

Arduino Timer Tutorial: millis(), micros() & Hardware Timers

March 11, 2026 /Posted byJayesh Jain / 0

Timing is at the heart of almost every Arduino project. Blinking an LED, debouncing a button, reading sensors at fixed intervals, generating PWM signals — all of these depend on accurate time measurement and scheduling. Yet many beginners reach for delay() and quickly discover its painful limitation: the whole sketch freezes while it waits. In this comprehensive Arduino timer tutorial you’ll master everything from the simple millis() pattern to hardware timer interrupts that fire with microsecond precision.

Why delay() Is Almost Always Wrong

delay(n) blocks the CPU for n milliseconds. During that time, nothing else happens: no button reads, no sensor checks, no serial data received. For simple blink demos it’s fine. For any real project with multiple tasks, it’s a trap.

Consider a project that blinks an LED every 500 ms AND reads a button. With delay(500), the button is only checked twice per second — you’ll miss fast presses. The solution is non-blocking timing using millis().

millis() — Non-Blocking Millisecond Timing

millis() returns the number of milliseconds elapsed since the board was last reset or powered on. It’s driven by Timer0 hardware and updated in a background interrupt — no CPU blocking involved. The return type is unsigned long (32-bit), which means it overflows back to zero after approximately 49.7 days.

Classic Blink Without delay()

const int LED_PIN = 13;
const unsigned long BLINK_INTERVAL = 500;  // ms

unsigned long lastBlinkTime = 0;
bool ledState = false;

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

void loop() {
  unsigned long now = millis();

  if (now - lastBlinkTime >= BLINK_INTERVAL) {
    lastBlinkTime = now;
    ledState = !ledState;
    digitalWrite(LED_PIN, ledState);
  }

  // Other code runs here every loop iteration — not blocked!
}

The key expression is now - lastBlinkTime >= BLINK_INTERVAL. Using subtraction rather than direct comparison makes this overflow-safe — it continues to work correctly even when millis() wraps around at 49 days.

Recommended: Arduino Uno R3 Beginners Kit — includes LEDs, buttons, and sensors to build non-blocking multi-task timing projects right out of the box.

Running Multiple Timed Tasks

The real power of millis() is running several independent timers simultaneously:

unsigned long lastSensorTime = 0;
unsigned long lastHeartbeatTime = 0;
unsigned long lastDisplayTime = 0;

const unsigned long SENSOR_INTERVAL   = 200;   // 5 Hz
const unsigned long HEARTBEAT_INTERVAL = 1000;  // 1 Hz
const unsigned long DISPLAY_INTERVAL   = 100;   // 10 Hz

void loop() {
  unsigned long now = millis();

  if (now - lastSensorTime >= SENSOR_INTERVAL) {
    lastSensorTime = now;
    readSensors();
  }

  if (now - lastHeartbeatTime >= HEARTBEAT_INTERVAL) {
    lastHeartbeatTime = now;
    blinkHeartbeatLED();
  }

  if (now - lastDisplayTime >= DISPLAY_INTERVAL) {
    lastDisplayTime = now;
    updateDisplay();
  }

  checkButtons();  // Runs every loop — maximum responsiveness
}

This pattern scales to as many independent tasks as you need, with no impact on each other’s timing.

micros() — Microsecond Resolution

micros() works identically to millis() but returns elapsed time in microseconds, providing 4 µs resolution on a 16 MHz Arduino. It overflows after approximately 70 minutes.

// Measure pulse width manually
unsigned long pulseStart = micros();
while (digitalRead(SIGNAL_PIN) == HIGH);  // Wait for pulse end
unsigned long pulseWidth = micros() - pulseStart;
Serial.print("Pulse: ");
Serial.print(pulseWidth);
Serial.println(" µs");

micros() is ideal for:

  • Ultrasonic distance measurement (HC-SR04 pulse timing)
  • IR remote decoding
  • Bit-banging custom protocols
  • Benchmarking code execution time
Recommended: Arduino Frequency Counter Kit with 16×2 LCD Display — a ready-made project that demonstrates precise hardware timer-based frequency measurement; perfect for learning timer peripherals in action.

Hardware Timers: Timer0, Timer1 and Timer2

The ATmega328P (Arduino Uno/Nano) has three hardware timers, each with specific roles and capabilities:

Timer Bits PWM Pins Notes
Timer0 8-bit D5, D6 Used by millis(), micros(), delay() — modify with caution
Timer1 16-bit D9, D10 Best for precise timing; used by Servo library
Timer2 8-bit D3, D11 Used by tone() function

Timer Interrupts: Triggering Code at Exact Intervals

Hardware timer interrupts let you execute code at precise intervals completely independent of what the main loop is doing. Here’s how to configure Timer1 to fire every 1 second:

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

  // Configure Timer1 for 1-second interrupt
  cli();            // Disable interrupts during config

  TCCR1A = 0;       // Clear Timer1 control register A
  TCCR1B = 0;       // Clear Timer1 control register B
  TCNT1  = 0;       // Reset timer counter

  // Set compare match value for 1 Hz at 16 MHz with 1024 prescaler
  // Formula: OCR1A = (F_CPU / prescaler / freq) - 1
  // = (16,000,000 / 1024 / 1) - 1 = 15624
  OCR1A = 15624;

  TCCR1B |= (1 << WGM12);   // CTC mode (clear timer on compare match)
  TCCR1B |= (1 << CS12) | (1 << CS10);  // 1024 prescaler
  TIMSK1 |= (1 << OCIE1A);  // Enable Timer1 compare interrupt

  sei();  // Enable interrupts
}

ISR(TIMER1_COMPA_vect) {
  // This runs exactly every 1 second, regardless of loop() activity
  Serial.println("One second tick!");
}

void loop() {
  // Main loop code — runs independently
  delay(100);
}

Timer1 Frequency Formula

To calculate OCR1A for a desired interrupt frequency:

OCR1A = (F_CPU / prescaler / desired_frequency) - 1

Examples at 16 MHz:
  100 Hz,  prescaler 256: OCR1A = (16000000/256/100)  - 1 = 624
  10  Hz,  prescaler 256: OCR1A = (16000000/256/10)   - 1 = 6249
  1   Hz, prescaler 1024: OCR1A = (16000000/1024/1)   - 1 = 15624
  1  kHz,  prescaler   8: OCR1A = (16000000/8/1000)   - 1 = 1999
Recommended: Arduino Mega 2560 R3 Board — the Mega has six hardware timers (Timer0–5), giving you vastly more simultaneous precise timing channels for complex automation and motor control projects.

PWM Generation with Hardware Timers

Arduino’s analogWrite() function uses hardware timers to generate PWM signals automatically. The default PWM frequency varies by pin:

  • D5, D6 (Timer0): ~977 Hz
  • D9, D10 (Timer1): ~490 Hz
  • D3, D11 (Timer2): ~490 Hz

To change the PWM frequency, modify the timer prescaler bits. For example, to change Timer2’s frequency to approximately 31 kHz (useful for audio or motor drivers that need high-frequency PWM):

// Set Timer2 prescaler to 1 → ~31 kHz PWM on D3 and D11
TCCR2B = TCCR2B & B11111000 | B00000001;

// Set Timer2 prescaler to 64 → ~490 Hz (default)
TCCR2B = TCCR2B & B11111000 | B00000100;

// Set Timer2 prescaler to 1024 → ~30 Hz (very slow PWM)
TCCR2B = TCCR2B & B11111000 | B00000111;

Warning: changing Timer0’s prescaler also changes the rate at which millis() and micros() count — they will no longer return real time. Always prefer Timer1 or Timer2 for custom PWM frequencies.

Using the TimerOne and TimerThree Libraries

If direct register manipulation feels overwhelming, the popular TimerOne library wraps Timer1 with a clean API:

#include <TimerOne.h>

void timerCallback() {
  // Called every 500 ms
  digitalWrite(13, !digitalRead(13));
}

void setup() {
  pinMode(13, OUTPUT);
  Timer1.initialize(500000);  // Period in microseconds (500 ms)
  Timer1.attachInterrupt(timerCallback);
}

void loop() {
  // Free to do other things
}

Install via the Arduino Library Manager: search for “TimerOne”. The TimerThree library works identically for Timer3 on Mega-class boards.

Recommended: Multifunction Shield For Arduino Uno / Leonardo — includes a 4-digit 7-segment display, LEDs, and buzzer; perfect for building timer-controlled displays and sound alerts that test all timer concepts covered here.

Common Timer Pitfalls and How to Avoid Them

  • millis() overflow: Always use unsigned long for time variables and the subtraction pattern (now - lastTime). Never use == comparison or you’ll miss the exact tick.
  • ISR code size: Keep ISRs short and fast. No Serial.print(), no delay(), no blocking calls. Set a flag in the ISR and handle the work in loop().
  • Volatile variables: Variables shared between ISRs and main code must be declared volatile to prevent compiler optimisation from caching them in registers.
  • Servo + Timer1 conflict: The Servo library uses Timer1. You cannot use Timer1 for custom interrupts while Servo is active. Use Timer2 or the Mega’s Timer3/4/5 instead.
  • tone() + Timer2 conflict: The tone() function uses Timer2. Calling tone() will override any custom Timer2 configuration.

Frequently Asked Questions

What is the resolution of millis() on Arduino?

On a 16 MHz Arduino Uno, millis() has a resolution of approximately 1.024 ms (due to Timer0 prescaler settings). It does not tick exactly every 1.000 ms — there’s a small accumulated drift. For timing requirements tighter than ~1 ms, use micros() which has 4 µs resolution.

How do I measure elapsed time accurately in Arduino?

Use unsigned long start = millis(); before your operation, then unsigned long elapsed = millis() - start; after. The subtraction is overflow-safe for periods up to ~49 days. For sub-millisecond measurements use micros() with the same pattern, which overflows after ~70 minutes.

Can I use hardware timer interrupts alongside delay() and millis()?

Yes, with one constraint: avoid using Timer0 for custom interrupts (it drives millis() and delay()). Use Timer1 (16-bit, highest precision) or Timer2 (8-bit) for your interrupt. Both can coexist with the Timer0-based functions without any issue.

Why does my Arduino run slow when I use too many hardware timer interrupts?

If ISRs fire very frequently (e.g., every microsecond) and take more than a few CPU cycles each, the CPU spends most of its time in ISR context and the main loop barely gets to run. Reduce ISR frequency, minimise ISR code, or consider moving time-critical processing to the main loop using flags set by the ISR.

What’s the difference between CTC mode and Fast PWM mode?

CTC (Clear Timer on Compare Match) mode resets the counter when it matches OCR1A, creating precise periodic interrupts. Fast PWM mode continuously counts up and uses OCR values to toggle output pins, ideal for generating PWM waveforms. CTC is best for timer interrupts; Fast PWM is best for analog output and motor control.

Level Up Your Arduino Timing Skills

Mastering Arduino’s timer system — from the simple elegance of millis() to the precision of hardware timer interrupts — fundamentally changes what you can build. Multi-tasking sketches, microsecond-accurate measurements, custom PWM frequencies: all become possible once you move beyond delay().

Find all the Arduino hardware you need to put these techniques into practice at Zbotic.in — India’s Arduino specialists with fast nationwide delivery.

Tags: arduino interrupt, arduino micros, arduino millis, arduino non-blocking, arduino pwm, arduino timer, hardware timer arduino
Share Post
  • Facebook
  • Linkedin
  • Whatsapp
Arduino Nano Every: Better AVR...
blog arduino nano every better avr or overkill for beginners 594847
blog rc522 rfid library read write mifare tags tutorial 594852
RC522 RFID Library: Read Write...

Related posts

Svg%3E
Read more

Arduino Batch Programming: Flash Multiple Boards Quickly

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Based Radar System with Ultrasonic Sensor

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Automatic Plant Monitor: Sunlight, Moisture, Temperature

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Lie Detector: GSR Sensor Polygraph Project

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Metal Detector: Build a Treasure Finder

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading

Add comment Cancel reply

Your email address will not be published. Required fields are marked

Facebook Twitter Instagram Pinterest Linkedin Youtube

Get the latest deals and more.

Download on Google Play Download on the App Store

Call us: 020 69134444 / 1800 209 0998

Monday - Saturday 09:30 AM - 06:00 PM
For Technical Supports Email: [email protected]
For Sales / Enquiries Email: [email protected]

  • My Account

    • Cart

    • Wishlist

    • Checkout

    • My Orders

    • Track Order

    • My Account

  • Information

    • FAQs

    • Blogs

    • Career

    • About Us

    • Contact Us

    • Payment Options

  • Policies

    • Privacy Policy

    • Terms & Conditions

    • GST Input Tax Credit

    • Shipping Return Policy

    • E-Waste Collection Points

    • Our Sitemap

© Zbotic.in is registered trademark of Moxie Supply Pvt Ltd – All Rights Reserved
Login
Use Phone Number
Use Email Address
Not a member yet? Register Now
Reset Password
Use Phone Number
Use Email Address
Register
Already a member? Login Now