How I've turned a Picoscope USB oscilloscope into an MCA

Begonnen von madexp, 17. Mai 2025, 12:42

⏪ vorheriges - nächstes ⏩

madexp




A few months ago I purchased a PicoScope 2204A USB oscilloscope purely for my teaching duties. I would plug it into my laptop, project live waveforms onto the classroom screen, and use it to illustrate analog signals in real time. One day I came across Dr. Max Fomitchev-Zamilov's MCA kits on eBay, which turn a PicoScope into a multi-channel analyzer via a .NET interface and the PicoSDK. I thought it would be a fun experiment to reproduce that functionality in Python.




In practice it proved fairly straightforward, but I discovered two important hardware limitations:

  • 8-bit ADC – with only 256 quantization levels, the spectral resolution is coarse.
  • Quirky hardware trigger – setting a threshold that works at one voltage range (e.g. 40 mV in ±500 mV) may fail at another (±1 V), because the internal DAC or comparator resolution isn't fine enough to generate the trigger voltage accurately. In such cases raising the threshold (e.g. to 80 mV) restores reliable triggering.

Below I sketch the core acquisition algorithm for pulse spectroscopy, and then describe the Python GUI implementation.

Acquisition Algorithm



  • Initialize
    Record the start time and zero the event counter.
  • Read User Settings
    • Full-scale input range (volts)
    • Lower-level discriminator (LLD) threshold (volts)
    • Polarity ("Positive" or "Negative" pulses)
    • Time-per-division (s/div)
    • Number of samples per block
  • Configure Scope
    Set the selected channel A range and coupling (AC for MCA), then apply the hardware trigger at the LLD threshold with the chosen edge.
  • Acquire Waveform
    Call run_block(), which:
    • Queries the timebase to get the sampling interval
    • Arms the scope and waits for the block to complete
    • Reads back a buffer of raw ADC counts
    • Converts counts to millivolts and then to volts
    • Returns time (t) and voltage (v) arrays
  • Detect Pulse Peak
    If polarity is positive, take peak = max(v); if negative, take peak = –min(v).
  • Compute Histogram Bin
    Normalize: raw_idx = (peak / full_scale_range) * 255
    Clamp: idx = min(max(int(raw_idx), 0), 255)
  • Update Statistics
    Increment histogram[idx], increment the total event count, then compute elapsed time since start and the event rate (events/sec).
  • Emit Updates
    • Send the raw waveform to the GUI for a "last-pulse" preview
    • Send the bin index to increment the bar or line histogram
    • Send the updated count and rate to the status display
  • Loop
    Continue steps 2–8 until the user stops the acquisition.

Software Structure

  • SimplePicoScope2000
    Wraps all PicoSDK calls: opening/closing the unit, setting channels and triggers, querying timebases, and retrieving blocks of data.
  • AcqThread
    A Qt thread that continuously acquires oscilloscope waveforms and emits them to the main GUI.
  • McaThread
    A Qt thread that runs the pulse-spectroscopy loop described above, building a 256-bin histogram and streaming updates back to the GUI.
  • MainWindow
    A PyQt5 application window with two tabs:
    • Oscilloscope: real-time trace display, noise measurement, and start/stop controls
    • MCA: spectrum acquisition controls, last-pulse preview, and dynamic histogram with rate and elapsed-time readouts

madexp

The source code of the script cannot be posted here.
I've published it on GitHub: https://github.com/l-papadopol/PyMCA

DL8BCN


madexp

Added baseline restoring plus pulse shape acquisition/recognition. Now it works quite well.
The pictures shows 50keV + 200keV + 300keV peaks from LYSO.
The peak on the righjt is 511keV peak wich is out of range ( I had to use different voltage range or reduce gain)
Sie dürfen in diesem Board keine Dateianhänge sehen.