Low-cost STM32 oscilloscope that streams ADC samples to a Python GUI for live plotting, frequency readout, and FFT.
STM32 ADC DMA UART Python PyQtGraph
Highlights
DMA moves ADC samples into ping-pong buffers: the half- and full-complete interrupts flag each half, and the main loop sends it over UART while the other half fills, keeping the ISRs short.
Sized the buffers and interrupt period from throughput math (100 samples every ~10 ms at 921,600 baud), then halved transfer time to ~2 ms by sending 16-bit half-words instead of 32-bit values.
Python GUI (PyQtGraph + pyserial) reads and processes serial data on a background thread while the main thread redraws every 20 ms, with frequency readout, FFT, and waveform export.
Specs
Board
STM32L476RG (NUCLEO-L476RG), Arm Cortex-M4 at 80 MHz
ADC
12-bit, one ADC per channel, continuous conversion into DMA
Sampling rate
~105 kS/s single channel (2 MHz ADC clock, 6.5-cycle sample + 12.5-cycle conversion) · ~5.2 kS/s per channel in two-channel mode
Buffering
Circular DMA ping-pong buffers with half- and full-complete interrupts: 1,024 samples (half fills every ~4.8 ms) single channel · 100 samples per ADC (~10 ms per half) two-channel
Link to PC
UART at 921,600 baud with samples packed as 16-bit half-words (~2 ms per 100-sample transfer); USB CDC also tested for more throughput
Display software
Python, PyQtGraph, and pyserial; background reader thread; plots refresh every 20 ms with frequency readout, FFT, and waveform export; up to 4 COM ports
Test signal
~160 Hz sine wave from an ESP32, compared against a bench oscilloscope
Gallery
Select any image or video to view it full size.
Fig. 1 Single-channel demo: a ~160 Hz sine wave from an ESP32, plotted live by the Python GUI (left) next to a bench oscilloscope (right).