mirror of
https://github.com/jonathanhogg/scopething
synced 2025-07-13 18:52:10 +01:00
Compare commits
2 Commits
b62bffe631
...
742109336b
Author | SHA1 | Date | |
---|---|---|---|
742109336b | |||
42bb240cc4 |
55
README.md
55
README.md
@ -8,3 +8,58 @@
|
|||||||
- Also requires **NumPy** and **SciPy** if you want to do analog range calibration
|
- Also requires **NumPy** and **SciPy** if you want to do analog range calibration
|
||||||
- Having **Pandas** is useful for wrapping capture results for further processing
|
- Having **Pandas** is useful for wrapping capture results for further processing
|
||||||
|
|
||||||
|
## Longer Notes
|
||||||
|
|
||||||
|
### Why have I written this?
|
||||||
|
|
||||||
|
BitScope helpfully provide applications and libraries for talking to their USB
|
||||||
|
capture devices, so one can just use those. I wrote this code because I want
|
||||||
|
to be able to grab the raw data and further process it in various ways. I'm
|
||||||
|
accustomed to working at a Python prompt with various toolkits like SciPy and
|
||||||
|
Matplotlib, so I wanted a simple way to just grab a trace as an array.
|
||||||
|
|
||||||
|
The BitScope library is pretty simple to use, but also requires you to
|
||||||
|
understand a fair amount about how the scope works and make a bunch of decisions
|
||||||
|
about what capture mode and rate to use. I want to just specify a time period,
|
||||||
|
voltage range and a rough number of samples and have all of that worked out for
|
||||||
|
me, same way as I'd use an actual oscilloscope: twiddle the knobs and look at
|
||||||
|
the trace.
|
||||||
|
|
||||||
|
Of course, I could have wrapped the BitScope library in something that would do
|
||||||
|
this, but after reading a bit about how the scope works I was fascinated with
|
||||||
|
understanding it further and so I decided to go back to first principles and
|
||||||
|
start with just talking to it with a serial library. This code thus serves as
|
||||||
|
(sort of) documentation for the VM registers, capture modes and how to use them.
|
||||||
|
It also has the advantage of being pure Python.
|
||||||
|
|
||||||
|
The code prefers the highest capture resolution possible and will do the mapping
|
||||||
|
from high/low/trigger voltages to the mysterious magic numbers that the device
|
||||||
|
needs. It can also do logic and mixed-signal capture.
|
||||||
|
|
||||||
|
In addition to capturing, the code can also generate waveforms at arbitrary
|
||||||
|
frequencies – something that is tricky to do as the device operates at specific
|
||||||
|
frequencies and so one has to massage the width of the waveform buffer to get a
|
||||||
|
frequency outside of these. It can also control the clock generator.
|
||||||
|
|
||||||
|
I've gone for an underlying async design as it makes it easy to integrate the
|
||||||
|
code into UI programs or network servers – both of which interest me as the end
|
||||||
|
purpose for this code. However, for shell use there are synchronous wrapper
|
||||||
|
functions. Of particular note is that the synchronous wrapper understands
|
||||||
|
keyboard interrupt and will cancel a capture returning the trace around the
|
||||||
|
cancel point. This is useful if your trigger doesn't fire and you want to
|
||||||
|
understand why.
|
||||||
|
|
||||||
|
### Where's the documentation, mate?
|
||||||
|
|
||||||
|
Yeah, yeah. I know.
|
||||||
|
|
||||||
|
### Also, I see no unit tests...
|
||||||
|
|
||||||
|
It's pretty hard to do unit tests for a physical device. That's my excuse and
|
||||||
|
I'm sticking to it.
|
||||||
|
|
||||||
|
### Long lines and ignoring E221, eh?
|
||||||
|
|
||||||
|
"A foolish consistency is the hobgoblin of little minds"
|
||||||
|
|
||||||
|
Also, I haven't used an 80 character wide terminal in this century.
|
||||||
|
19
test.py
19
test.py
@ -1,26 +1,12 @@
|
|||||||
|
|
||||||
import numpy as np
|
|
||||||
from pylab import figure, plot, show
|
from pylab import figure, plot, show
|
||||||
|
|
||||||
from analysis import annotate_series
|
from analysis import annotate_series
|
||||||
from scope import await_, capture, main
|
from scope import await_, capture, main
|
||||||
from utils import DotDict
|
|
||||||
|
|
||||||
|
|
||||||
await_(main())
|
await_(main())
|
||||||
|
series = capture(['A'], period=20e-3, nsamples=2000).A
|
||||||
# o = 400
|
|
||||||
# m = 5
|
|
||||||
# n = o * m
|
|
||||||
# samples = square_wave(o)
|
|
||||||
# samples = np.hstack([samples] * m) * 2
|
|
||||||
# samples = np.hstack([samples[100:], samples[:100]])
|
|
||||||
# samples += np.random.normal(size=n) * 0.1
|
|
||||||
# samples += np.linspace(4.5, 5.5, n)
|
|
||||||
# series = DotDict(samples=samples, sample_rate=1000000)
|
|
||||||
|
|
||||||
data = capture(['A'], period=20e-3, nsamples=2000)
|
|
||||||
series = data.A
|
|
||||||
|
|
||||||
figure(1)
|
figure(1)
|
||||||
plot(series.timestamps, series.samples)
|
plot(series.timestamps, series.samples)
|
||||||
@ -35,6 +21,7 @@ if annotate_series(series):
|
|||||||
print(f"Found {waveform.frequency:.0f}Hz {waveform.shape} wave, "
|
print(f"Found {waveform.frequency:.0f}Hz {waveform.shape} wave, "
|
||||||
f"with amplitude ±{waveform.amplitude:.2f}V and offset {waveform.offset:.2f}V")
|
f"with amplitude ±{waveform.amplitude:.2f}V and offset {waveform.offset:.2f}V")
|
||||||
|
|
||||||
plot(waveform.timestamps + waveform.capture_start - series.capture_start, waveform.samples * waveform.amplitude + waveform.offset)
|
plot(waveform.timestamps + waveform.capture_start - series.capture_start,
|
||||||
|
waveform.samples * waveform.amplitude + waveform.offset)
|
||||||
|
|
||||||
show()
|
show()
|
||||||
|
Reference in New Issue
Block a user