Update Time:2026-11-06

Building a Raspberry Pi Pico Oscilloscope with MicroPython

Build a Raspberry Pi Pico oscilloscope with MicroPython: generate waves, sample signals, and display waveforms on a web browser. Step-by-step guide.

Components & Parts

Building a Raspberry Pi Pico Oscilloscope with MicroPython

Raspberry Pi Pico Oscilloscope

You can build a working Raspberry Pi Pico oscilloscope with MicroPython, and this guide shows you how. The finished tool has three parts. A signal generator makes test waves. A sampler captures real voltages. A browser-based display draws the results.

You need only the Pico and a few common parts, like resistors, capacitors, and jumper wires. You do not need expensive lab equipment.

Basic electronics knowledge helps. Some Python experience helps too. You will wire a small circuit and run short scripts.

The payoff comes fast. You get a practical oscilloscope you built yourself, and you understand every stage of it.

Key Takeaways

  • Make a working oscilloscope using a Raspberry Pi Pico and a few low-cost parts.

  • Use MicroPython and Thonny to get your Pico set up fast and with little effort.

  • Make test waves using PWM and a filter to check your circuit.

  • Use the ADC and DMA to grab signals for fast, accurate sampling.

  • Show waveforms in a web browser by using the Web Serial API.

Pico Features and Hardware Setup

Why the RP2040/RP2350 Fits an Oscilloscope

The RP2040 and RP2350 chips give you a strong starting point for a DIY oscilloscope. A complete build can cost as little as about $4, which makes it a friendly entry into signal measurement. The chip pairs a fast processor with flexible peripherals, so you can sample, store, and stream data without extra hardware.

The built-in analog digital converter sets the core limits. According to Electrocredible, the RP2040 uses a 12-bit SAR converter with a maximum sampling rate of 500 kS/s. The converter runs from a 48 MHz clock and takes 96 cycles per sample, which works out to 2 µs per sample. Your digital readings therefore span 0 to 4095.

SpecificationValue
ADC resolution12-bit
ADC typeSAR
Maximum sampling rate500 kS/s
ADC clock48 MHz
Cycles per sample96
Maximum voltage3.3 V

An arxiv study notes that practical builds often reach 2.5 MS/s, yet performance stays clean only up to about 200 kHz. Plan your measurements around that range.

ADC Inputs and a Simple DAC

Pin GP26 is your main analog input. You create an ADC object on channel 26 and read it with read_u16(). The snippet below shows the basic pattern:

import machine
analog_value = machine.ADC(26)
reading = analog_value.read_u16()

For external power, the frontend circuit can draw from the VBUS, VSYS, or 3V3 rails. Choose the rail that matches your sensor's voltage range. Keep every input below 3.3 V to protect the chip.

The Pico lacks a true DAC, so you build one with PWM and a filter. This simple DAC drives your test waves. The same pins also support a logic analyzer role when you need to watch digital lines.

MicroPython Environment and Core Peripherals

Installing MicroPython and Tools

Thonny is the easiest way to get MicroPython working. KevsRobots explains the process in four steps. First, download Thonny from thonny.org and install it for your operating system. Windows users run the installer, macOS users drag the app to Applications, and Linux users install it through their package manager.

Next, hold the BOOTSEL button while you plug the Pico into USB. The board shows up as a removable drive. Open Thonny, go to Tools > Options, and pick the Interpreter tab. Choose MicroPython (Raspberry Pi Pico) and let Thonny install the firmware when it asks you. To check the setup, type a short print statement in the editor, save it to the Pico, and run it. The console should show your message.

GPIO, ADC, PWM, PIO, and DMA Basics

Your micropython code controls five peripheral families. GPIO handles digital pins. The adc reads voltages on channels like GP26. PWM makes filtered output for your wave generator. PIO state machines run tight timing loops in hardware, which frees the CPU. Direct memory access moves data without the processor's help.

The adc has real limits for scope work. CircuitSchools notes that each conversion takes 96 clock cycles at 48 MHz, so one sample lands every 2 microseconds, or 500 kS/s. An OSROTS RP2040 scope project pushes the chip to 2 MS/s on two channels by overclocking to 240 MHz. That project also scales 12-bit data down to 8-bit values for protocol compatibility. USB Full Speed bandwidth caps the link, so some samples drop at a 1 MS/s rate and above. Overclocking may affect absolute accuracy at the highest tiers.

Plan your sampling around these limits. Use PIO for precise timing, and let DMA fill a buffer with a fixed number of samples. This approach keeps your capture clean and your CPU free.

Building the Wave Function Generator

Building the Wave Function Generator

Your scope needs something to measure. This section builds a two-channel wave function generator that sits beside the two-channel scope, in the spirit of O-SCOPE-style RP2 projects. One board now makes a test signal and captures it. You can feed a filter, watch it distort the input, and compare both traces on the same screen.

Driving Output with PIO and DMA

The Pico's programmable input/output blocks handle the timing. You load a small program into the pio state machines, and they clock out samples without CPU help. DMA feeds the buffer. Hackaday reports that rgco's Pico build reached 125 megasamples per second this way, outputting a new sample every clock cycle. The same project's earlier Arduino Uno version managed 381 kilosamples per second at 42 instruction cycles per sample. The pio approach wins because hardware does the counting. That headroom leaves the CPU free for the sampler.

A CPU loop cannot match that. Python adds variable delay to every iteration, and your output frequency drifts with it. Hardware timing removes the jitter. The state machine shifts one value out per cycle, and a clock divider sets the pace. You pick the divider to match your target frequency.

You write the output loop once, then swap the sample table. Each channel gets its own pio program and its own DMA channel. A timer sets the update rate. Double buffering keeps the stream smooth: while DMA reads one table, you refill the other. Keep both tables in RAM so the transfer never stalls.

Generating Sine, Square, and Triangle Waves

A lookup table is the simplest route. You precompute one cycle of values, then let DMA replay it. The snippets below show the pattern for each shape.

import math
sine = [int(2047 + 2047 * math.sin(2 * math.pi * i / 64)) for i in range(64)]

A square wave needs only two levels. You build it by repeating a high value and a low value:

square = [4095] * 32 + [0] * 32

A triangle wave ramps up and back down:

triangle = [i * 128 for i in range(32)] + [(31 - i) * 128 for i in range(32)]

Table length sets your resolution. A longer table smooths the sine, and a shorter one saves memory. You change frequency by changing the replay rate, not the table. Amplitude scaling is a multiply on each entry. The 12-bit range runs from 0 to 4095, so the midpoint sits near 2047. Center your table there and the output swings symmetrically around it.

Hackster.io describes a different route. SellersCircuits drives an AD9833 direct digital synthesizer from an RP2040 over 3-wire SPI, and that chip produces sine, triangle, and square outputs. The project clocks it at 24 MHz for a theoretical 12 MHz ceiling. Steps in the output become visible at 1 MHz and above. Use the table method for low frequencies and the DDS chip when you need higher range.

Both channels share one sample clock, so the two waveforms stay aligned. That alignment matters when you compare a filter input against its output. The generator and the sampler share one board. Route a channel straight into GP26 for a loopback test, and that test proves both halves work before you connect a real circuit.

Building the Raspberry Pi Pico Oscilloscope Sampler

ADC Sampling with DMA and Triggering

The sampler turns voltages into numbers at a steady pace. The ADC controller can interleave several inputs, yet every channel shares one bandwidth pool. The arxiv study on O-SCOPE notes that all channels divide the maximum 500 kS/s sample rate between them. That project alternates between GPIO26 and GPIO27, so two channels still run at full speed.

Each conversion takes 96 cycles on both the RP2040 and RP2350. At the standard 48 MHz clock, one sample lands every 2 µs. You slow the pace with the clock divider, which yields integer rates such as 240000, 120000, and 60000 samples per second.

DMA does the heavy lifting. The ADC writes each result into its FIFO, and the FCS register enables that path. DMA then reads the FIFO through a memory-mapped access, paced by the ADC_DREQ signal. A full FIFO sets the sticky overflow flag, and further conversions are lost. Size your buffer with care.

The O-SCOPE DMAadc function takes three arguments: the channel, the number of samples, and the rate. It adds 256 extra samples for later channel alignment and triggering. You configure the DMA control word with 16-bit transfers, a fixed read address, and an incrementing write address. Then you set bit 3 of the ADC control register to start, and clear it to stop.

For a clean capture, drain the FIFO first, then enable free-running mode. A chained second DMA channel fills a backup buffer while your code processes the first. At 8 kHz with 1024-sample buffers, each buffer takes 128 ms to fill, and the siliconwit tutorial puts the flash write at roughly 6 ms. That margin keeps data sampling lossless.

Package Structure and Frontend Protection

The firmware package splits into small modules: an ADC and DMA driver, a trigger module, a PIO wave generator, and a serial protocol layer. The protocol sends a header, then the raw analog samples as binary frames. Your web display reads those frames and draws the waveforms.

Protect the inputs before anything else. The Pico datasheet, cited by PCBWay, warns that ADC pins carry an internal reverse diode to the 3V3 rail, so input voltage must stay below that rail plus about 300 mV. A Schottky diode clamps excess voltage, and a resistor network centers the input for AC signals. Circuit Digest describes a frontend with a ±330 mV mode using a gain-of-10 op-amp and a ±33 V mode using a divider.

Limit the clamp current. PCBWay suggests a 220R output resistor, which holds current near 10 mA into the op-amp sink. A 1N5817 diode beats a BAT46 here, because its forward drop stays under 300 mV at that current. Test the op-amp output before you connect the ADC pin, and confirm it stays below 3.6 V.

An on-device LCD is another option. An ST7789 driver over SPI at 24 MHz, paired with LVGL v8 and pure MicroPython drivers, draws traces without a browser. The Pimoroni forum shows a working 240x240 framebuffer setup. This path suits a standalone instrument, while the web display suits quick debugging.

Web Display and Testing

Streaming Waveforms with the Web Serial API

The web display is one option among several. You can stream over USB with the Web Serial API, run a smartphone-based scope, or add an ESP32 module for a wireless web display. Each path suits a different setup.

The Web Serial API gives you the simplest start. Plug in your flashed Pico, open the dashboard in Chrome or Edge, and click Connect in the Terminal section. Select the serial port when prompted, and live data streams in automatically. The SipandPuff-HID project shows this pattern: its dashboard is a browser-based UI with a real-time waveform, gauge, and configuration panel. Note that Firefox and Safari do not support Web Serial.

Your firmware sends text frames over CDC serial, as the same project demonstrates:

raw=8420156 delta=31644 event=1
raw=8388203 delta=-405 event=0

The raw field carries the ADC reading, delta shows the change from baseline, and event holds a classified ID. You can send binary frames the same way. The html page parses each frame, and an html canvas draws the trace. Keep the html and javascript code small: one html file holds the layout, the parser, and the drawing loop. A local web server serves that html file to the browser. The web server needs no internet access, so the html page loads from your laptop. If you prefer wireless, an ESP32 runs a web server that hosts the same html page. That web server forwards samples over WiFi, and the html canvas updates in the browser. A second web server on the Pico W can serve the html directly. Choose one web server and keep the html identical across both.

Validating Results with Example Signals

Test with a sine wave first. Feed the generator output into GP26 and check that the trace shows a smooth curve. Confirm the amplitude against your table values, then compare the measured frequency with the rate you set. Adjust the trigger level until the wave stands still on screen.

A square wave tests edges and the trigger. You should see flat tops and fast transitions. A triangle wave checks linearity across the full swing. Run all three at several rates and confirm the number of samples per cycle stays consistent. The logic analyzer mode uses the same capture path, so a clean analog trace suggests digital lines will read correctly too.

Scoppy and Picotronix are existing budget-friendly projects worth a look. Scoppy turns a phone into a scope, which fits portable work. Picotronix offers an extensible base for custom firmware. Both show where a home build fits beside ready-made tools.

You now have a complete raspberry pi pico oscilloscope. The build combines a MicroPython wave generator, DMA sampling, and a web display. Each part works on its own, and together they form a practical tool.

From here, you can make the resolution better, add trigger modes, or improve the frontend circuit. Small changes bring clear gains. The code package is ready for you to download and adapt. Try a new waveform or a tighter clamp, then share your build with the community. Projects like these pico-based oscilloscopes grow when people share their work. Send your results, and help the next builder capture a cleaner signal.

 

 

 

 


 

AiCHiPLiNK Logo

Written by Jack Elliott from AIChipLink.

 

AIChipLink, one of the fastest-growing global independent electronic   components distributors in the world, offers millions of products from thousands of manufacturers, and many of our in-stock parts is available to ship same day.

 

We mainly source and distribute integrated circuit (IC) products of brands such as Broadcom, Microchip, Texas Instruments, Infineon, NXP, Analog Devices, Qualcomm, Intel, etc., which are widely used in communication & network, telecom, industrial control, new energy and automotive electronics. 

 

Empowered by AI, Linked to the Future. Get started on AIChipLink and submit your RFQ online today! 

 

 

Frequently Asked Questions

What sampling rate can you expect from the ADC?

The RP2040 ADC tops out at 500 kS/s at its standard 48 MHz clock. Each conversion takes 96 cycles, so one sample lands every 2 µs. Practical builds stay clean up to about 200 kHz. Plan your measurements inside that range for trustworthy traces.

How do you protect the ADC input from overvoltage?

Keep every input below 3.3 V. A Schottky diode clamps excess voltage, and a 220R output resistor holds clamp current near 10 mA. A 1N5817 diode keeps its forward drop under 300 mV at that current. Confirm the op-amp output stays below 3.6 V before you connect the pin.

Can you use the Pico as a logic analyzer too?

Yes. The same capture path that samples analog voltages also watches digital lines. A clean analog trace suggests your digital reads will work correctly. You get a logic analyzer role from the same pins and the same DMA buffer, with no extra hardware.

What is the cheapest way to start this raspberry pi pico oscilloscope build?

A complete build can cost as little as about $4. You need the Pico, resistors, capacitors, and jumper wires. Thonny installs the MicroPython firmware in four steps. Start with a loopback test on GP26 to prove both halves work.

Which display option suits a standalone instrument?

An ST7789 driver over SPI at 24 MHz, paired with LVGL v8 and pure MicroPython drivers, draws traces without a browser. A 240x240 framebuffer setup works well. Choose this path for a standalone oscilloscope, and keep the web display for quick debugging.