An Arduino oscilloscope DIY project is one of the most educational builds you can undertake as an electronics enthusiast. While it won’t replace a professional bench oscilloscope, a carefully constructed Arduino-based signal analyzer is genuinely useful for debugging audio circuits, PWM signals, sensor outputs, and low-frequency waveforms — and it costs a fraction of even the cheapest dedicated scope. In this guide, we cover everything from ADC fundamentals to complete display implementations.
Table of Contents
- How an Arduino Oscilloscope Works
- Understanding ADC Limits and Sampling Rate
- Project 1: Basic Serial Plotter Oscilloscope
- Project 2: Standalone TFT Display Oscilloscope
- Implementing Triggering for Stable Waveforms
- Input Protection and Voltage Dividers
- Performance Tips and Limitations
- Frequently Asked Questions
How an Arduino Oscilloscope Works
A real oscilloscope continuously samples an input voltage, stores the samples in a buffer, and renders them as a time-domain waveform on screen. Your Arduino does exactly the same thing — just slower. The ATmega328P’s ADC converts an analog voltage (0–5V on most boards) to a 10-bit digital value (0–1023). By sampling at a known rate and plotting successive samples, you reconstruct the shape of the input waveform.
The key constraint is the Nyquist theorem: to accurately represent a signal, your sampling rate must be at least twice the signal’s highest frequency. The Arduino Uno’s ADC defaults to a 125kHz clock with a 13-cycle conversion time, giving about 9,600 samples per second (9.6kSPS). That means you can faithfully display signals up to roughly 4.8kHz — enough for audio fundamentals, PWM waveforms, and most sensor outputs, but nowhere near the megahertz range of real scopes.
With prescaler tuning (reducing the ADC clock divider from 128 to 16), you can push the ATmega328P to around 77kSPS at the cost of reduced accuracy (effectively 8-bit resolution). On faster platforms like the Arduino Nano RP2040 Connect, the RP2040’s ADC can reach 500kSPS, dramatically extending the useful frequency range.
Understanding ADC Limits and Sampling Rate
Before writing a single line of code, understand what your hardware can actually deliver:
ADC Prescaler Configuration
On AVR-based boards, the ADC clock is derived from the system clock (16MHz for Uno) divided by a prescaler. The default prescaler is 128, giving 16MHz/128 = 125kHz ADC clock. At 13 cycles per conversion: 125,000/13 ≈ 9,615 SPS.
// Fast ADC: prescaler 16 → ~76,923 SPS (reduced accuracy)
ADCSRA = (ADCSRA & ~0x07) | 0x04; // Set prescaler to 16
// Fastest: prescaler 4 → ~307,692 SPS (noisy, 6-bit effective)
ADCSRA = (ADCSRA & ~0x07) | 0x02; // Set prescaler to 4
Free-Running Mode
For maximum throughput, enable free-running mode where the ADC continuously converts without waiting for software to start each conversion:
ADMUX = (1<<REFS0) | 0; // AVCC reference, channel A0
ADCSRA |= (1<<ADATE); // Auto-trigger enable
ADSRB = 0; // Free-running mode
ADCSRA |= (1<<ADSC); // Start first conversion
In free-running mode, read ADCW (the combined ADC result register) directly for maximum speed. This is how high-performance Arduino oscilloscope libraries operate.
Project 1: Basic Serial Plotter Oscilloscope
The simplest Arduino oscilloscope uses the Arduino IDE’s built-in Serial Plotter (Tools → Serial Plotter). No additional hardware needed — just your Arduino and a signal source.
const int ADC_PIN = A0;
const int SAMPLE_RATE = 4000; // Hz
const unsigned long INTERVAL_US = 1000000UL / SAMPLE_RATE;
void setup() {
Serial.begin(115200);
// Optional: reduce ADC prescaler for faster sampling
ADCSRA = (ADCSRA & ~0x07) | 0x05; // Prescaler 32 ~ 19kSPS
}
void loop() {
static unsigned long lastSample = 0;
if (micros() - lastSample >= INTERVAL_US) {
lastSample += INTERVAL_US;
int value = analogRead(ADC_PIN);
// Map to voltage (0–5V)
float voltage = value * (5.0 / 1023.0);
Serial.println(voltage);
}
}
Open the Serial Plotter, set baud rate to 115200, and you’ll see a live waveform. This is excellent for visualizing audio from a microphone, PWM duty cycle changes, and temperature sensor outputs. The limitation is that the Serial Plotter’s X-axis is sample-count, not real time, so you need to calculate frequency from sample rate manually.
Project 2: Standalone TFT Display Oscilloscope
A standalone scope with a TFT display is far more satisfying and practical. You get a portable device without needing a computer. The 2.4″ TFT touch screen shield for Arduino Uno makes this straightforward:
Hardware Setup
- Arduino Uno or Mega
- 2.4″ TFT display shield (plugs directly into Uno)
- Input signal connected to A0 via a 1MΩ + 10kΩ voltage divider (see input protection section)
Core Display Logic
#include <Adafruit_GFX.h>
#include <Adafruit_TFTLCD.h>
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0_PIN // Careful: A0 used for input!
#define LCD_RESET A4
Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);
#define SCREEN_W 240
#define SCREEN_H 320
#define SCOPE_H 200
#define INPUT_PIN A5 // Shifted to A5 since A0 is LCD_RD
uint16_t buffer[SCREEN_W];
uint16_t prevBuffer[SCREEN_W];
void setup() {
tft.reset();
tft.begin(0x9341); // ILI9341 driver
tft.setRotation(1);
tft.fillScreen(0x0000); // Black background
drawGrid();
}
void loop() {
// Sample SCREEN_W points
for (int i = 0; i < SCREEN_W; i++) {
buffer[i] = map(analogRead(INPUT_PIN), 0, 1023, SCOPE_H - 1, 0);
}
// Erase old trace, draw new one
for (int i = 0; i < SCREEN_W - 1; i++) {
tft.drawLine(i, prevBuffer[i], i+1, prevBuffer[i+1], 0x0000);
tft.drawLine(i, buffer[i], i+1, buffer[i+1], 0x07E0); // Green
prevBuffer[i] = buffer[i];
}
prevBuffer[SCREEN_W-1] = buffer[SCREEN_W-1];
}
void drawGrid() {
// Draw graticule lines
for (int y = 0; y <= SCOPE_H; y += SCOPE_H/4)
tft.drawFastHLine(0, y, SCREEN_W, 0x2104); // Dark grey
for (int x = 0; x <= SCREEN_W; x += SCREEN_W/6)
tft.drawFastVLine(x, 0, SCOPE_H, 0x2104);
}
Implementing Triggering for Stable Waveforms
Without triggering, your waveform will scroll and appear unstable. A trigger waits for the signal to cross a threshold in a specific direction before starting a capture, ensuring successive captures start at the same phase and the waveform appears stationary.
bool waitForTrigger(int threshold, bool risingEdge, unsigned long timeoutUs) {
unsigned long start = micros();
int prev = analogRead(INPUT_PIN);
while (micros() - start < timeoutUs) {
int current = analogRead(INPUT_PIN);
if (risingEdge && prev < threshold && current >= threshold) return true;
if (!risingEdge && prev > threshold && current <= threshold) return true;
prev = current;
}
return false; // Timeout — draw anyway
}
// In loop():
if (!waitForTrigger(512, true, 50000)) {
// No trigger in 50ms — display "!TRIG" on screen
}
// Now capture your samples
Set the trigger level to mid-scale (512 for a 0–5V signal centred at 2.5V) for most AC waveforms. Add touch input to adjust the trigger level and choose rising or falling edge for full scope control.
Input Protection and Voltage Dividers
The Arduino ADC input is rated for 0–5V (or 0–3.3V on 3.3V boards). Exceeding this destroys the ADC — and possibly the entire microcontroller. Always protect the input:
Basic Voltage Divider for ±20V Range
Use a resistor divider with clamping diodes. For a ±15V signal range on a 5V Arduino:
- R1 = 100kΩ (input to junction)
- R2 = 10kΩ (junction to GND)
- This divides by 11, so ±15V input becomes ±1.36V at the ADC pin
- Add 1N4148 diodes from the junction to 5V and GND for clamping
- Add a 2.5V DC bias (via resistor divider from 5V) to centre the signal in the 0–5V ADC range
For high-voltage work (mains monitoring, automotive signals), use a dedicated front-end IC like the ACPL-C87A or a commercial probe attenuator, and never connect mains voltage directly to an Arduino under any circumstances.
Performance Tips and Limitations
- Use direct register access instead of
analogRead()—analogRead()takes ~104µs, direct ADC register reading takes ~13µs - Disable interrupts during sampling bursts to prevent timing jitter:
noInterrupts(); /* sample loop */ interrupts(); - Use DMA on RP2040 — the Nano RP2040 can use DMA to fill a sample buffer entirely in hardware, freeing the CPU for display rendering simultaneously
- Pre-allocate buffers — dynamic memory allocation during sampling causes jitter and potential heap fragmentation
- Acknowledge the bandwidth limit — publish the sample rate on-screen so users know what they’re looking at
Frequently Asked Questions
What is the maximum frequency an Arduino Uno oscilloscope can display?
At the default 9.6kSPS, the Nyquist limit is about 4.8kHz. With prescaler tuning to reach 77kSPS, you can display signals up to ~38kHz — though accuracy decreases. For anything above that, use the Nano RP2040 (500kSPS) or a dedicated scope.
Can an Arduino oscilloscope measure AC mains voltage?
Not directly and not safely. You would need a galvanically isolated front end. It is technically possible with an isolation transformer and a proper attenuator circuit, but the risk of electrocution from a mistake is severe. Use a proper isolated oscilloscope probe instead.
How do I increase the voltage range of my Arduino oscilloscope?
Use a resistor voltage divider with clamping diodes, as described in the input protection section. For signals above ±20V, use an op-amp attenuator circuit with a dedicated power supply. Always add clamp diodes to prevent over-voltage reaching the ADC pin.
What library should I use for the TFT display?
The Adafruit GFX library combined with the display-specific driver (MCUFRIEND_kbv for shields, Adafruit_ILI9341 for SPI modules) is the most well-documented choice. For maximum speed on AVR, consider the UTFT library which uses direct port manipulation.
Is there a ready-made Arduino oscilloscope I can buy?
Yes — the DSO138 and DSO150 are inexpensive dedicated scopes based on STM32 that offer 200kSPS and a proper analog front end. They cost more than a DIY Arduino project but significantly less than professional scopes and give much better performance.
Ready to build your own signal analyzer? Find all the Arduino boards, TFT displays, and components you need in our Arduino & Microcontrollers collection at Zbotic — with fast delivery across India.
Add comment