</>CoderDIY
← Back to list
Article

Build a DIY Mini Oscilloscope with Arduino

Author: CoderDIY··Đọc bằng tiếng Việt
📈

A real oscilloscope costs anywhere from a couple hundred to several thousand dollars, but most basic analog debugging — checking a PWM waveform, seeing whether a sensor signal is noisy, measuring the frequency of a slow signal — can be done entirely with an Arduino and a plotting script on your computer. This isn’t a professional-grade scope (Arduino’s sample rate tops out at a few tens of kHz), but it’s a genuinely useful learning and debugging tool, and nearly free if you already own an Arduino.

How it works

  1. The Arduino continuously reads analog voltage through its 10-bit ADC (values 0-1023, mapping to 0-5V) at the highest sample rate it can manage.
  2. Each reading is immediately sent over the Serial (USB) connection to the computer, packed as binary to maximize transfer speed.
  3. A Python script on the computer continuously reads from the serial port, decodes the values, and plots voltage over time using matplotlib, updating in real time like an actual oscilloscope display.
  4. Because Serial throughput is limited (~115200 baud), the achievable sample rate is roughly 5,000-10,000 samples/second — enough to observe low-frequency signals (PWM in the hundreds of Hz to a few kHz, sensor signals, low-frequency audio) but not fast enough to capture MHz-range signals like a dedicated scope.
<rect x="10" y="20" width="140" height="60" rx="10" class="box" />
<text x="80" y="45" text-anchor="middle" class="label">Read ADC (A0)</text>
<text x="80" y="62" text-anchor="middle" class="label">continuously</text>

<rect x="170" y="20" width="140" height="60" rx="10" class="box" />
<text x="240" y="45" text-anchor="middle" class="label">Send over Serial</text>
<text x="240" y="62" text-anchor="middle" class="label">(binary, 115200 baud)</text>

<rect x="330" y="20" width="140" height="60" rx="10" class="box" />
<text x="400" y="45" text-anchor="middle" class="label">Python reads</text>
<text x="400" y="62" text-anchor="middle" class="label">pyserial</text>

<rect x="490" y="20" width="140" height="60" rx="10" class="box" />
<text x="560" y="45" text-anchor="middle" class="label">Plot graph</text>
<text x="560" y="62" text-anchor="middle" class="label">matplotlib (live)</text>

<line x1="150" y1="50" x2="170" y2="50" class="arrow" />
<line x1="310" y1="50" x2="330" y2="50" class="arrow" />
<line x1="470" y1="50" x2="490" y2="50" class="arrow" />

<text x="320" y="130" text-anchor="middle" class="sublabel">Actual sample rate: ~5,000-10,000 samples/sec, limited by Serial bandwidth</text>
Voltage data flows from the Arduino's ADC, over Serial, to a Python script that plots it in real time.

Parts list

  • Arduino Uno or Nano (boards with faster ADC handling, like SAMD/ARM-based ones, will sample faster)
  • USB cable connecting the Arduino to your computer
  • A computer with Python 3, pyserial, and matplotlib installed
  • Test leads or a probe wire to connect the signal you want to observe to pin A0
  • (Recommended) A voltage divider or zener protection circuit if the measured signal could exceed 5V, to avoid damaging the ADC pin
<rect x="30" y="50" width="170" height="120" rx="10" class="box" />
<text x="115" y="115" text-anchor="middle" class="label">Arduino (A0)</text>

<rect x="440" y="50" width="170" height="120" rx="10" class="box" />
<text x="525" y="105" text-anchor="middle" class="label">Signal probe point</text>
<text x="525" y="125" text-anchor="middle" class="label">(0-5V)</text>

<line x1="200" y1="90" x2="440" y2="90" class="wire" />
<text x="320" y="82" text-anchor="middle" class="sublabel">Through a voltage divider if &gt;5V</text>
<circle cx="200" cy="90" r="4" class="pin" />
<circle cx="440" cy="90" r="4" class="pin" />

<line x1="200" y1="140" x2="440" y2="140" class="wire" />
<text x="320" y="132" text-anchor="middle" class="sublabel">Shared GND</text>
<circle cx="200" cy="140" r="4" class="pin" />
<circle cx="440" cy="140" r="4" class="pin" />
The signal under test connects to A0; always share GND with the Arduino so the measurement has a correct reference point.

Sample code

Arduino firmware, sampling continuously and sending binary data over Serial (faster than sending text):

const int SIGNAL_PIN = A0;

void setup() {
  Serial.begin(115200);
  // Speed up the ADC by lowering the prescaler (default 128 -> 16, faster but slightly less accurate)
  ADCSRA = (ADCSRA & 0xF8) | 0x04;
}

void loop() {
  int value = analogRead(SIGNAL_PIN); // 0-1023
  byte low = value & 0xFF;
  byte high = (value >> 8) & 0xFF;
  Serial.write(high);
  Serial.write(low);
}

Python script that reads the data and plots it live:

import serial
import matplotlib.pyplot as plt
from collections import deque

PORT = "COM3"       # change to your port, e.g. /dev/ttyUSB0 on Linux
BAUD = 115200
WINDOW = 500         # number of samples shown on the plot

ser = serial.Serial(PORT, BAUD, timeout=1)
data = deque([0] * WINDOW, maxlen=WINDOW)

plt.ion()
fig, ax = plt.subplots()
line, = ax.plot(data)
ax.set_ylim(0, 1023)
ax.set_ylabel("ADC value (0-1023)")
ax.set_xlabel("Sample")

while True:
    raw = ser.read(2)
    if len(raw) < 2:
        continue
    value = (raw[0] << 8) | raw[1]
    data.append(value)
    line.set_ydata(data)
    fig.canvas.draw()
    fig.canvas.flush_events()

Common pitfalls

  • Measuring a signal above 0-5V (or 0-3.3V on a 3.3V board) can damage the ADC pin — always use a voltage divider or attenuator to bring the signal into the Arduino’s accepted range before measuring.
  • Sending data as text (Serial.println) instead of binary drastically lowers the achievable sample rate since each number takes several ASCII bytes — use Serial.write() with binary data whenever you need speed.
  • No shared GND between the probe point and the Arduino produces skewed or completely noisy readings — always connect GND before connecting the signal wire.
  • Expecting to capture high-frequency signals (MHz range) — the Arduino’s ADC tops out at a few tens of kHz, which isn’t fast enough to view high-speed digital signals like I2C or SPI.

Extensions

Add a trigger feature (only start plotting once the signal crosses a threshold, like a real oscilloscope) for a more stable view of repeating signals, switch to an ESP32 with a faster ADC and stream data over WiFi instead of Serial for untethered use, or pair it with the self-hosted sensor dashboard with Grafana project to store and replay waveforms over time.

Tags:#arduino#electronics#diy-tools

Comments