r4633 - in developers/werner/ahrt/host/tmc: . demo lib
werner at docs.openmoko.org
werner at docs.openmoko.org
Mon Sep 8 07:11:03 CEST 2008
Author: werner
Date: 2008-09-08 07:11:00 +0200 (Mon, 08 Sep 2008)
New Revision: 4633
Added:
developers/werner/ahrt/host/tmc/demo/digitize.py
developers/werner/ahrt/host/tmc/demo/spi.py
developers/werner/ahrt/host/tmc/demo/wavemath.py
Modified:
developers/werner/ahrt/host/tmc/README
developers/werner/ahrt/host/tmc/demo/deglitch.py
developers/werner/ahrt/host/tmc/demo/fft.py
developers/werner/ahrt/host/tmc/demo/screen.py
developers/werner/ahrt/host/tmc/lib/instrument.py
developers/werner/ahrt/host/tmc/lib/scope.py
developers/werner/ahrt/host/tmc/lib/shape.py
developers/werner/ahrt/host/tmc/lib/trigger.py
developers/werner/ahrt/host/tmc/lib/wave.py
developers/werner/ahrt/host/tmc/setup.py
Log:
Highlights:
- a lot more wave operations: mathematics, deglitching, digitizing
- wavefirms can now be loaded from a file
- made a first pass at cleaning up triggers (finally !)
- some renaming, e.g., tmc.wave.analog_wave is now called just tmc.wave.analog
- waveforms are now downloaded from the scope, not the channel
- more examples in demo/. The bottom half of the SPI example has actually some
real-life significance.
Note that some of the changes are still unfinished/untested and that there
may be regressions.
- lib/wave.py (waves): new class for iteration and I/O of groups of waves
- lib/wave.py (analog_wave.append): ignore duplicate entries
- lib/wave.py (wave): added arithmetic operations between waves and numbers and
between waves (+, -, *, /, **, unary -, abs)
- lib/wave.py (wave.load): load waveform data from a file
- lib/wave.py (wave.dump), demo/deglitch.py: renamed to wave.dump to wave.save
- lib/wave.py (digital_wave.edges): moved to trigger.edge.apply
- lib/wave.py: renamed analog_wave to "analog" and digital_wave to "digital"
- lib/wave.py: finished the digitizing function, greatly simplifying it
- README: mention the libraries
- lib/instrument.py (settings): moved most of the shape_settings functionality
to its super class, generalized callbacks
- scope.py: moved trigger force, state, and sweep to the horizontal system
- scope.py (trigger): removed, and import tmc.trigger now
- scope.py (rigol_ds1000c.download_wave): simplified contorted logic to
determine channel number
- lib/scope.py: made "wave" a scope method instead of a channel method, so that
we can retrieve multiple channels in one operation
- lib/trigger.py: moved the trigger class from scope.py and did some cleanup.
The design still doesn't really make sense, but it'll have to do for now.
- demo/wavemath.py: wave mathematics example
- demo/spi.py: SPI decode example (to illustrate the use of triggers)
- demo/digitize.py: digitizing example
Modified: developers/werner/ahrt/host/tmc/README
===================================================================
--- developers/werner/ahrt/host/tmc/README 2008-09-07 08:31:42 UTC (rev 4632)
+++ developers/werner/ahrt/host/tmc/README 2008-09-08 05:11:00 UTC (rev 4633)
@@ -25,6 +25,24 @@
Concept and use
===============
+The tmc package contains a set of functions to communicate with test
+and measurement instruments. (Currently always using SCPI, but devices
+speaking different protocols could also be supported.)
+
+These basic functions are described below.
+
+On top of basic communication, tmc provides a rich set of abstractions
+and libraries with helper functions written in Python. The goal is to
+allow routine tasks to be expressed in a concise and understandable
+way, and to facilitate the construction of complex automated experiments.
+
+These libraries are not documented yet, but there is a number of
+annotated examples in the demo/ directory.
+
+
+Low-level interface
+===================
+
This suite supports instruments that speak SCPI or a dialect thereof
using one of the following transport protocols:
@@ -49,9 +67,9 @@
Communication to an instrument is established by creating an instance
of the tmc.Instr class:
- import tmc
+ import _tmc
- instr = tmc.Instr(transport, arg, ...)
+ instr = _tmc.Instr(transport, arg, ...)
"transport" is the transport to use, "tcp", "usb", or "serial".
@@ -117,7 +135,7 @@
Debug output can be enabled or disabled with
- tmc.debug(level)
+ _tmc.debug(level)
Level 0 turns debug output off.
Modified: developers/werner/ahrt/host/tmc/demo/deglitch.py
===================================================================
--- developers/werner/ahrt/host/tmc/demo/deglitch.py 2008-09-07 08:31:42 UTC (rev 4632)
+++ developers/werner/ahrt/host/tmc/demo/deglitch.py 2008-09-08 05:11:00 UTC (rev 4633)
@@ -4,7 +4,7 @@
#
from tmc.shape import arbitrary
-from tmc.wave import digital_wave
+from tmc.wave import digital
from math import floor
@@ -19,15 +19,13 @@
w = shape.wave(0, 100, 1e-3)
-# Crudely convert the analog waveform into a digital one.
+# Convert the analog waveform into a digital one.
-dw = digital_wave()
-for p in w:
- dw.append(p[0], p[1])
+dw = w.digitize(0.5)
# Dump the original waveform to a file
-dw.dump("before")
+dw.save("before")
# Remove all transients with a duration of less than 0.1 units.
#
@@ -38,7 +36,7 @@
# Dump the deglitched waveform to a file
-dw.dump("after")
+dw.save("after")
#
# Visualize the result with:
Added: developers/werner/ahrt/host/tmc/demo/digitize.py
===================================================================
--- developers/werner/ahrt/host/tmc/demo/digitize.py (rev 0)
+++ developers/werner/ahrt/host/tmc/demo/digitize.py 2008-09-08 05:11:00 UTC (rev 4633)
@@ -0,0 +1,39 @@
+#!/usr/bin/python
+#
+# digitize.py - Digitize an analog signal
+#
+
+from tmc.shape import sine
+
+
+# Generate a slow signal with amplitude 5 ("TTL") and high-frequency noise with
+# amplitude 1. We use a compact form to describe this operation. See
+# wavemath.py ot fft.py for details on the process.
+
+signal = sine(freq = 1, high = 5).wave(0, 3, 1e-3)
+noise = sine(freq = 30, peak = 1).wave(0, 3, 1e-3)
+
+# Combine signal and noise.
+
+input = signal+noise
+
+# We digitize the signal first without a hystersis, and then with the standard
+# TTL hysteresis.
+
+noisy = input.digitize(1.5)
+clean = input.digitize(0.8, 2.2)
+
+# Save the input and the results. For convenience, we shift the digital waves
+# vertically, so that everything can be plotted without additional adjustments.
+
+input.save("input")
+(noisy-2).save("noisy")
+(clean-4).save("clean")
+
+#
+# The results can be visualized with gnuplot:
+#
+# gnuplot> set style data lines
+# gnuplot> set yrange [-4.2:]
+# gnuplot> plot "input", "noisy", "clean", 0.8 lt 0, 1.5 lt 0, 2.2 lt 0
+#
Property changes on: developers/werner/ahrt/host/tmc/demo/digitize.py
___________________________________________________________________
Name: svn:executable
+ *
Modified: developers/werner/ahrt/host/tmc/demo/fft.py
===================================================================
--- developers/werner/ahrt/host/tmc/demo/fft.py 2008-09-07 08:31:42 UTC (rev 4632)
+++ developers/werner/ahrt/host/tmc/demo/fft.py 2008-09-08 05:11:00 UTC (rev 4633)
@@ -12,9 +12,11 @@
# Describe a 100Hz square wave with amplitude = 5
+
shape = tmc.shape.square(high = 5, freq = 100)
# Generate a waveform over t = [0, 1) with 10kSa/s
+
wave = shape.wave(0, 1, 1e-4)
@@ -23,17 +25,22 @@
#
# convert the waveform into a NumPy array
+
a = map(lambda p: p[1], wave)
# get the duration of the waveform
+
width = wave.end()-wave.start()
# get the number of samples
+
n = len(wave)
# do a discrete Fourier transform using the Hann(ing) window
+
fa = abs(fft(array(a)*hanning(n)))/n
# obtain the frequency components and put the positive half of the spectrum
# into a file with one (frequency, amplitude) tuple per line
+
savetxt("fft.out", transpose((fftfreq(n, d = width/n)[0:n/2], fa[0:n/2])))
Modified: developers/werner/ahrt/host/tmc/demo/screen.py
===================================================================
--- developers/werner/ahrt/host/tmc/demo/screen.py 2008-09-07 08:31:42 UTC (rev 4632)
+++ developers/werner/ahrt/host/tmc/demo/screen.py 2008-09-08 05:11:00 UTC (rev 4633)
@@ -11,13 +11,17 @@
# Open the Rigol DS1000
+
scope = tmc.scope.rigol_ds1000c()
# Open a pipe to a process running "convert" from ImageMagick
+
pipe = popen("convert - "+argv[1], "w")
# Retrieve the screendump as PPM file and send it to the converter
+
pipe.write(scope.screendump())
# Flush the pipe and terminate
+
pipe.close()
Added: developers/werner/ahrt/host/tmc/demo/spi.py
===================================================================
--- developers/werner/ahrt/host/tmc/demo/spi.py (rev 0)
+++ developers/werner/ahrt/host/tmc/demo/spi.py 2008-09-08 05:11:00 UTC (rev 4633)
@@ -0,0 +1,64 @@
+#!/usr/bin/python
+#
+# spi.py - Construct the waveforms of a string sent on SPI, then decode it
+#
+
+from tmc.wave import digital
+from tmc import trigger
+
+
+# Encoding: The message we're sending
+
+msg = "Hello"
+
+# We need two digial waves: one for the clock and one for the actual data
+
+mosi = digital()
+clk = digital()
+
+# We generate the SPI bit stream with MOSI changing at every rising edge of the
+# clock. "bit" is the bit position in the entire stream, "byte" is the value of
+# the byte at that position, and "bit_pos" is the bit position in the current
+# byte. SPI sends data MSB first, so the bit position is "reversed".
+
+for t in range(0, len(msg)*16):
+ bit = t >> 1
+ byte = ord(msg[bit >> 3])
+ bit_pos = 7-(bit & 7)
+ mosi.append(t, (byte >> bit_pos) & 1)
+ clk.append(t, (~t & 1))
+
+# Uncomment the lines below to obtain a trace of the waves we've generated.
+# Visualize them with:
+#
+# gnuplot> set yrange [-0.2:3.2]
+# gnuplot> plot "mosi" using 1:($2+2) with lines, "clk" with lines
+
+#mosi.save("mosi")
+#clk.save("clk")
+
+
+# Decoding: we extract data at the falling edges of the clock signal. First, we
+# define an edge trigger.
+
+trig = trigger.edge(level = 0.5, slope = trigger.slope.Falling)
+
+# We apply the trigger to the clock signal. The result is a list of times at
+# which the trigger fired.
+
+edges = trig.apply(clk)
+
+# We now extract the wave samples at the trigger times. Again, this is a list.
+
+data = mosi.get(edges)
+print "Bits =", len(data)
+
+# Finally, we iterate over the list and reconstruct the message. Note that
+# "print" puts a space beteen characters.
+
+for i in range(0, len(data), 8):
+ v = 0
+ for j in range(0, 8):
+ v |= data[i+j] << (7-j)
+ print "%c" % v,
+print
Property changes on: developers/werner/ahrt/host/tmc/demo/spi.py
___________________________________________________________________
Name: svn:executable
+ *
Added: developers/werner/ahrt/host/tmc/demo/wavemath.py
===================================================================
--- developers/werner/ahrt/host/tmc/demo/wavemath.py (rev 0)
+++ developers/werner/ahrt/host/tmc/demo/wavemath.py 2008-09-08 05:11:00 UTC (rev 4633)
@@ -0,0 +1,43 @@
+#!/usr/bin/python
+#
+# wavemath.py - Waveform mathematic
+#
+
+
+from tmc.shape import sine
+from tmc.wave import waves, digital
+
+
+# Generate a sine wave with high frequency (5Hz) and low amplitude,
+# with sample rate 1kHz.
+
+s = sine(freq = 5, peak = 0.2)
+ws = s.wave(0, 10, 1e-3)
+
+# Generate a digital wave with low frequency (0.5Hz) and large amplitude
+
+wd = digital()
+for t in range(0, 10):
+ wd.append(t, t & 1)
+
+# Add the two waves
+
+sum = ws+wd
+
+# Make a wave set with the sine wave shifted to +3, the digital wave reduced
+# in amplitude and shifted to +2, and the sum left in its original state. This
+# is similar to how multiple waves are arranged for viewing on an oscilloscope.
+
+set = waves(ws+3, wd/2+2, sum)
+
+# Write the waves to a file, each line containing the time and a sample of each
+# wave.
+
+set.save("out")
+
+#
+# Visualize the result with:
+#
+# gnuplot> set style data lines
+# gnuplot> plot "out" using 1:2, "out" using 1:3, "out" using 1:4
+#
Property changes on: developers/werner/ahrt/host/tmc/demo/wavemath.py
___________________________________________________________________
Name: svn:executable
+ *
Modified: developers/werner/ahrt/host/tmc/lib/instrument.py
===================================================================
--- developers/werner/ahrt/host/tmc/lib/instrument.py 2008-09-07 08:31:42 UTC (rev 4632)
+++ developers/werner/ahrt/host/tmc/lib/instrument.py 2008-09-08 05:11:00 UTC (rev 4633)
@@ -25,8 +25,25 @@
class settings(object):
- pass
+ def __init__(self, init, set = None, get = None):
+ self.value = init
+ self.__set = set
+ self.__get = get
+ def get(self):
+ if self.__get is None:
+ return self.value
+ else:
+ value = self.__get()
+ self.value = value
+ return value
+
+ def set(self, value):
+ if self.__set is not None:
+ self.__set(value)
+ self.value = value
+
+
class setting(settings):
def __init__(self, instr, path, in_convert = None, out_convert = None,
Modified: developers/werner/ahrt/host/tmc/lib/scope.py
===================================================================
--- developers/werner/ahrt/host/tmc/lib/scope.py 2008-09-07 08:31:42 UTC (rev 4632)
+++ developers/werner/ahrt/host/tmc/lib/scope.py 2008-09-08 05:11:00 UTC (rev 4633)
@@ -28,8 +28,9 @@
#
import time
-from tmc.instrument import setting, settable, instrument
-from tmc.wave import analog_wave, digital_wave
+from tmc.instrument import settings, setting, settable, instrument
+from tmc.wave import analog, digital
+from tmc.trigger import trigger
# horizontal system only accepts some frequencies
@@ -86,6 +87,7 @@
self.scope = scope
self.number = number
+ self.name = "CH"+str(number)
self.on = setting(scope, ":CHAN"+str(number)+":DISP",
lambda on: on == "ON",
@@ -119,6 +121,7 @@
# --- wave download -------------------------------------------------------
+ # obsolete !
def wave(self, start = None, end = None, step = None):
# @@@ move start and end adjustment here !
@@ -128,8 +131,35 @@
# === horizontal ==============================================================
+class sweep:
+ Auto, Normal, Single = range(3)
+
+
+class state:
+ Run, Stop, Triggered, Wait, Scan, Auto = range(6)
+
+
+class horizontal_trigger_setting(settings):
+
+ def __init__(self, scope, set = None, get = None):
+ self.scope = scope
+ self.trigger_set = set
+ self.trigger_get = get
+ settings.__init__(self, None, set = self.pass_set, get = self.pass_get)
+
+ def pass_set(self, value):
+ if self.scope.trigger is not None and self.trigger_set is not None:
+ self.trigger_set(self.scope.trigger, value)
+
+ def pass_get(self):
+ if self.scope.trigger is not None and self.trigger_get is not None:
+ return self.trigger_get(self.scope.trigger)
+
+
class horizontal(settable):
+ state_map = ( "RUN", "STOP", "T'D", "WAIT", "SCAN", "AUTO" )
+
def __init__(self, scope):
self.scope = scope
self.pos = setting(scope, ":TIM:OFFS",
@@ -138,83 +168,25 @@
self.scale = setting(scope, ":TIM:SCAL",
lambda x: float(x),
lambda x: "%.9f" % x)
+ self.sweep = horizontal_trigger_setting(scope,
+ trigger.set_sweep, trigger.get_sweep)
self.forget()
def forget(self):
self.pos = None
self.scale = None
+ self.sweep = None
-
-# === trigger =================================================================
-
-
-class trigger(settable):
-
- def __init__(self, scope):
- self.scope = scope
- #self.forget()
-
- def forget(self):
- self.mode.forget()
- self.source.forget()
- self.type.forget()
- self.level.forget()
- self.holdoff.forget()
-
- def mode(self, value):
- if value != None:
- if value != self.mode:
- self.scope.send(":TRIG"+str(self.number)+":SCAL "+str(value))
- self.scale = value
- else:
- pass
- return self.scale
-
- def source(self, value):
- if value != None:
- if value != self.scale:
- self.scope.send(":CHAN"+str(self.number)+":SCAL "+str(value))
- self.scale = value
- else:
- self.scale = self.scope.query(":CHAN"+str(self.number)+":SCAL?")
- return self.scale
-
- def sweep(self, value):
- if value != None:
- if value != self.sweep:
- self.scope.send(":CHAN"+self.mode+":SWE "+str(value))
- self.sweep = value
- else:
- self.sweep = self.scope.query(":CHAN"+self.mode+":SWE?")
- return self.sweep
-
- def level(self, value):
- if value != None:
- if value != self.level:
- self.scope.send(":TRIG:"+self.mode+":LEV "+str(value))
- self.scale = level
- else:
- self.level = self.scope.query(":TRIG:"+self.mode+":LEV?")
- return self.leve
-
- def holdoff(self, value):
- if value != None:
- if value != self.scale:
- self.scope.send(":CHAN"+str(self.number)+":SCAL "+str(value))
- self.scale = value
- else:
- self.scale = self.scope.query(":CHAN"+str(self.number)+":SCAL?")
- return self.scale
-
def force(self):
self.scope.send(":FORC")
def state(self):
- return self.scope.query(":TRIG:STAT?")
+ return self.state_map.index(self.scope.query(":TRIG:STAT?"))
-# ====
+# === Scope class =============================================================
+
class scope(instrument):
pass
@@ -234,7 +206,7 @@
#
def rigol_channel_data(s, t0, td, v0, vd):
- res = analog_wave()
+ res = analog()
i = 212
while i != 812:
div = (i-512)/50.0
@@ -249,7 +221,7 @@
def rigol_la_data(s, t0, td):
res = []
for b in range(0, 16):
- res.append(digital_wave())
+ res.append(digital())
i = 424
while i != 1624:
div = (i-1024)/100.0
@@ -331,16 +303,16 @@
scope.__init__(self, "usbtmc", "rigol", "timeout=2", "retry",
"vendor=0x0400", "product=0x05dc")
self.ch = []
+ self.d = map(lambda x: x, range(0, 16))
for n in range(1, self.channels+1):
self.ch.append(channel(self, n))
+ self.trigger = None
self.hor = horizontal(self)
- self.trigger = trigger(self)
def forget(self):
for ch in self.ch:
ch.forget()
self.hor.forget()
- self.trigger.forget()
def send(self, s):
scope.send(self, s)
@@ -360,12 +332,8 @@
pass
def download_wave(self, channel, start, end, step):
- c_num = None
- for n in range(0, len(self.ch)):
- if self.ch[n] == channel:
- c_num = n+1
return rigol_wave(self, start, end, step,
- ":WAV:DATA? CHAN"+str(c_num),
+ ":WAV:DATA? CHAN"+str(channel.number),
lambda a, b: a.extend(b),
rigol_channel_data, channel.pos, channel.scale)
@@ -382,3 +350,17 @@
def screendump(self):
self.send(":HARDCOPY")
return rigol_to_ppm(self.query(":LCD:DATA?"))
+
+ def wave(self, channels, start = None, end = None, step = None):
+ if not isinstance(channels, array):
+ return self.download([channels], start, end, step)[0]
+ la = None
+ res = []
+ for ch in channels:
+ if isinstance(ch, channel):
+ res.append(download_wave, start, end, step)
+ else:
+ if la is None:
+ la = self.download_la(start, end, step)
+ res.append(la[ch])
+ return res
Modified: developers/werner/ahrt/host/tmc/lib/shape.py
===================================================================
--- developers/werner/ahrt/host/tmc/lib/shape.py 2008-09-07 08:31:42 UTC (rev 4632)
+++ developers/werner/ahrt/host/tmc/lib/shape.py 2008-09-08 05:11:00 UTC (rev 4633)
@@ -24,74 +24,19 @@
from math import pi, sin, floor
from tmc.instrument import settable, settings
-from tmc.wave import analog_wave
+from tmc.wave import analog
-def set_low(shape, y):
- if shape.high < y:
- shape.high = y
-
-
-def set_high(shape, y):
- if shape.low > y:
- shape.low = y
-
-
-def set_offset(shape, y):
- peak = shape.peak
- shape.low = y-peak/2.0
- shape.high = y+peak/2.0
-
-
-def set_peak(shape, y):
- if y < 0:
- raise hell
- offset = shape.offset
- shape.low = offset-y/2.0
- shape.high = offset+y/2.0
-
-
-def set_freq(shape, f):
- if f < 0:
- raise hell
-
-
-def set_period(shape, t):
- if t < 0:
- raise hell
- shape.freq = 1.0/t
-
-
-def set_duty(shape, n):
- if n < 0 or n > 1:
- raise hell
-
-
-def set_width(shape, n):
- if n <= 0 or 1.0/n > shape.freq:
- raise hell
- shape.duty = shape.freq*n
-
-
class shape_setting(settings):
- def __init__(self, shape, init, set_fn = None, get_fn = None):
+ def __init__(self, shape, init, set = None, get = None):
+ settings.__init__(self, init, set, get)
self.shape = shape
self.value = init
- self.set_fn = set_fn
- self.get_fn = get_fn
- def get(self):
- if self.get_fn is None:
- return self.value
- else:
- return self.get_fn(self.shape)
-
def set(self, value):
prev = self.value
- if self.set_fn is not None:
- self.set_fn(self.shape, value)
- self.value = value
+ settings.set(self, value)
if self.shape.notify is not None:
self.shape.notify(shape, self, prev, self.value)
@@ -101,19 +46,53 @@
def __init__(self, notify = None, **list):
self.notify = None
- self.low = shape_setting(self, 0, set_low)
- self.high = shape_setting(self, 0, set_high)
- self.offset = shape_setting(self, None, set_offset,
- lambda shape: (shape.low+shape.high)/2.0)
- self.peak = shape_setting(self, None, set_peak,
- lambda shape: shape.high-shape.low)
- self.freq = shape_setting(self, 0, set_freq)
- self.period = shape_setting(self, None, set_period)
+ self.low = shape_setting(self, 0, self.__set_low)
+ self.high = shape_setting(self, 0, self.__set_high)
+ self.offset = shape_setting(self, None,
+ self.__set_offset, self.__get_offset)
+ self.peak = shape_setting(self, None, self.__set_peak, self.__get_peak)
+ self.freq = shape_setting(self, 0, self.__set_freq)
+ self.period = shape_setting(self, None, self.__set_period)
self.set(**list)
+ def __set_low(self, y):
+ if self.high < y:
+ self.high = y
+
+ def __set_high(self, y):
+ if self.low > y:
+ self.low = y
+
+ def __set_offset(self, y):
+ peak = self.peak
+ self.low = y-peak/2.0
+ self.high = y+peak/2.0
+
+ def __get_offset(self):
+ return (self.low+self.high)/2.0
+
+ def __set_peak(self, y):
+ if y < 0:
+ raise hell
+ offset = self.offset
+ self.low = offset-y/2.0
+ self.high = offset+y/2.0
+
+ def __get_peak(self):
+ return self.high-self.low
+
+ def __set_freq(self, f):
+ if f < 0:
+ raise hell
+
+ def __set_period(self, t):
+ if t <= 0:
+ raise hell
+ self.freq = 1.0/t
+
def wave(self, start, end, step):
- wave = analog_wave()
+ wave = analog()
t = start
while t <= end:
wave.append(t, self.get(t))
@@ -127,22 +106,36 @@
return sin(2*pi*t*self.freq)*self.peak/2.0+self.offset
-class square(shape):
+class __shape_with_duty(shape):
- def __init__(self, **list):
- self.duty = shape_setting(self, 0.5, set_duty)
+ def __init__(self, init, **list):
+ self.duty = shape_setting(self, init, self.__set_duty)
shape.__init__(self, **list)
+ def __set_duty(self, n):
+ if n < 0 or n > 1:
+ raise hell
+
+ def __set_width(self, n):
+ if n <= 0 or 1.0/n > self.freq:
+ raise hell
+ self.duty = self.freq*n
+
+
+class square(__shape_with_duty):
+
+ def __init__(self, **list):
+ __shape_with_duty(self, 0.5, **list)
+
def get(self, t):
p = t*self.freq
return (self.low, self.high)[p-floor(p) < self.duty]
-class ramp(shape):
+class ramp(__shape_with_duty):
def __init__(self, **list):
- self.duty = shape_setting(self, 1, set_duty)
- shape.__init__(self, **list)
+ __shape_with_duty(self, 1, **list)
def get(self, t):
p = t*self.freq
@@ -164,7 +157,6 @@
def __init__(self, **list):
self.fn = shape_setting(self, lambda t: 0, None)
-# self.arg = shape_setting(self, None, None)
shape.__init__(self, **list)
def get(self, t):
Modified: developers/werner/ahrt/host/tmc/lib/trigger.py
===================================================================
--- developers/werner/ahrt/host/tmc/lib/trigger.py 2008-09-07 08:31:42 UTC (rev 4632)
+++ developers/werner/ahrt/host/tmc/lib/trigger.py 2008-09-08 05:11:00 UTC (rev 4633)
@@ -12,63 +12,95 @@
#
#
-# @@@ old-style. needs rewriting.
+# @@@ The callback logic isn't quite right - we could have multiple users of
+# the same trigger settings, so just attaching it to a scope channel is wrong.
#
-class trigger(object):
- def __init__(self, scope):
- self.scope = scope
- self.forget()
+from tmc.instrument import settable, setting
- def forget(self):
- mode = None
- source = None
- type = None
- level = None
- holdoff = None
- def mode(self, value):
- if value != None:
- if value != self.mode:
- self.scope.send(":TRIG"+str(self.number)+":SCAL "+str(value))
- self.scale = value
- else:
- self.scale = self.scope.query(":CHAN"+str(self.number)+":SCAL?")
- return self.scale
+class coupling:
+ DC, AC, HF, LF = range(4)
- def source(self, value):
- if value != None:
- if value != self.scale:
- self.scope.send(":CHAN"+str(self.number)+":SCAL "+str(value))
- self.scale = value
- else:
- self.scale = self.scope.query(":CHAN"+str(self.number)+":SCAL?")
- return self.scale
- def sweep(self, value):
- if value != None:
- if value != self.sweep:
- self.scope.send(":CHAN"+self.mode+":SWE "+str(value))
- self.sweep = value
- else:
- self.sweep = self.scope.query(":CHAN"+self.mode+":SWE?")
- return self.sweep
+class trigger(settable):
+ coupling_map = [ "DC", "AC", "HF", "LF" ]
+ sweep_map = [ "AUTO", "NORMAL", "SINGLE" ]
- def level(self, value):
- if value != None:
- if value != self.level:
- self.scope.send(":TRIG:"+self.mode+":LEV "+str(value))
- self.scale = level
+ def __init__(self):
+ self.channel = None
+ self.scope = None
+
+ def source(self, channel, coupling = None):
+ if self.scope is not None:
+ self.scope.trigger = None
+ self.channel = channel
+ if channel is None:
+ self.scope = None
else:
- self.level = self.scope.query(":TRIG:"+self.mode+":LEV?")
- return self.leve
+ self.scope = channel.scope
+ self.scope.trigger = self
+ self.scope.send(":TRIG:MODE "+self.mode)
+ self.scope.send(":TRIG:"+self.mode+":SOUR "+channel.name)
+ if coupling is not None:
+ self.scope.send(
+ ":TRIG:"+self.mode+":COUP "+self.coupling_map[coupling])
+ self.scope.send(":TRIG:"+self.mode+":SWE "+scope.sweep)
+ self.update()
- def holdoff(self, value):
- if value != None:
- if value != self.scale:
- self.scope.send(":CHAN"+str(self.number)+":SCAL "+str(value))
- self.scale = value
- else:
- self.scale = self.scope.query(":CHAN"+str(self.number)+":SCAL?")
- return self.scale
+ def send(self, *args):
+ if self.scope is not None:
+ self.scope.send(*args)
+
+ def query(self, *args):
+ if self.scope is None:
+ raise hell
+ return self.scope.query(*args)
+
+ def set_sweep(self, value):
+ self.scope.send(":TRIG:"+self.mode+":SWE "+self.sweep_map[value])
+
+ def get_sweep(self):
+ return self.sweep_map.index(self.scope.query(":TRIG:"+self.mode+":SWE"))
+
+class slope:
+ Rising, Falling, Both = range(3)
+
+
+class edge(trigger):
+ mode = "EDGE"
+ slope_map = [ "POSITIVE", "NEGATIVE", "BOTH?" ] # @@@
+
+ def __init__(self, **list):
+ trigger.__init__(self)
+ self.level = setting(self, ":TRIG:"+self.mode+":LEV",
+ lambda x: float(x),
+ lambda x: "%.9f" % x)
+ self.slope = setting(self, ":TRIG:"+self.mode+":SLOP",
+ lambda x: self.slope_map.index(x),
+ lambda x: self.slope_map[x])
+ self.forget()
+ self.set(**list)
+
+ def forget(self):
+ self.level = None
+ self.slope = None
+
+ def update(self):
+ self.level = self.level
+ self.slope = self.slope
+
+ def apply(self, wave):
+ # @@@ only digital waves for now
+ # @@@ need to include holdoff time as well
+ if self.level <= 0 or self.level >= 1:
+ return []
+ if self.slope == slope.Both:
+ return wave.data[:]
+ res = []
+ i = int(wave.initial ^ self.slope == slope.Falling)+1
+ while i < len(wave.data):
+ res.append(wave.data[i])
+ i += 2
+ return res
Modified: developers/werner/ahrt/host/tmc/lib/wave.py
===================================================================
--- developers/werner/ahrt/host/tmc/lib/wave.py 2008-09-07 08:31:42 UTC (rev 4632)
+++ developers/werner/ahrt/host/tmc/lib/wave.py 2008-09-08 05:11:00 UTC (rev 4633)
@@ -35,14 +35,28 @@
# sample: (t, v)* x t* -> v*
# or (s0, t*) x t* -> v*
+# de-interpolation (analog): remove points that can be interpolated
+#
+# Known restrictions:
+# - waves.load has no way of knowing what kind of wave it is loading, so it
+# just makes everything an analog wave. Should perhaps add a comment on top
+# of the file with same hints. Or let provide a means for the user to tell
+# waves.load what the waves are like. An even more radical approach may be
+# to automatically convert waves consisting only of 0 and 1 and with only
+# vertical transitions to digital. CPU time is cheap :-)
+#
+
+import re
+
+
# === Waves ===================================================================
class wave(object):
- def dump(self, name, append = False):
+ def save(self, name, append = False):
f = open(name, ("w", "a")[append])
if append:
print >>f
@@ -50,6 +64,31 @@
print >>f, xy[0], xy[1]
f.close()
+ def load(self, name, step = None):
+ self.__init__()
+ empty = re.compile("^\s*(#.*)$")
+ if step is None:
+ numbers = re.compile("^\s*(\S+)\s*(\S+)\s*")
+ else:
+ numbers = re.compile("^\s*(\S+)?\s*(\S+)\s*")
+ t = 0
+ f = open(name, "r")
+ while True:
+ line = f.readline()
+ if line == "":
+ break
+ if empty.match(line):
+ continue
+ m = numbers.match(line)
+ if m is None:
+ raise hell
+ if step is None:
+ self.append(float(m.group(1)), float(m.group(2)))
+ else:
+ self.append(t, float(m.group(2)))
+ t += step
+ f.close()
+
def get(self, t):
if isinstance(t, list):
res = []
@@ -59,11 +98,188 @@
else:
return self.get_one(t)
+ def unary(self, op):
+ res = analog()
+ for p in self:
+ res.append(float(p[0]), float(op(p[1])))
+ return res
+ def binary(self, other, op):
+ res = analog()
+ if isinstance(self, wave):
+ if isinstance(other, wave):
+ for v in waves(self, other).iterate():
+ res.append(v[0], op(float(v[1]), float(v[2])))
+ else:
+ for p in self:
+ res.append(p[0], op(float(p[1]), float(other)))
+ else:
+ for p in other:
+ res.append(p[0], op(float(self), float(p[1])))
+ return res
+
+ def __add__(self, other):
+ return self.binary(other, float.__add__)
+
+ def __sub__(self, other):
+ return self.binary(other, float.__sub__)
+
+ def __mul__(self, other):
+ return self.binary(other, float.__mul__)
+
+ def __pow__(self, other):
+ return self.binary(other, float.__pow__)
+
+ def __div__(self, other):
+ return self.binary(other, float.__div__)
+
+ def __truediv__(self, other):
+ return self.binary(other, float.__truediv__)
+
+ def __radd__(self, other):
+ return self.binary(other, float.__radd__)
+
+ def __rsub__(self, other):
+ return self.binary(other, float.__rsub__)
+
+ def __rmul__(self, other):
+ return self.binary(other, float.__rmul__)
+
+ def __rpow__(self, other):
+ return self.binary(other, float.__rpow__)
+
+ def __rdiv__(self, other):
+ return self.binary(other, float.__rdiv__)
+
+ def __rtruediv__(self, other):
+ return self.binary(other, float.__rtruediv__)
+
+ def __neg__(self):
+ return self.unary(float.__neg__)
+
+ def __abs__(self):
+ return self.unary(float.__abs__)
+
+
+# === Wave groups =============================================================
+
+
+def next_or_none(iter):
+ try:
+ return iter.next()
+ except StopIteration:
+ return None
+
+
+# The iteration is a bit tricky, because waves may have different sample
+# intervals, so we need to interpolate, and they may have multiple samples with
+# the same time (in the case of digital waves), so we need to make sure we stop
+# at a given time until all samples have been extraced.
+
+class waves_iter:
+
+ def __init__(self, waves):
+ self.iter = []
+ self.cur = []
+ self.nxt = []
+ self.t = None
+ for w in waves:
+ i = w.__iter__()
+ v = next_or_none(i)
+ self.iter.append(i)
+ self.nxt.append(v)
+ self.cur.append(None)
+ if self.t is None:
+ if v is not None and (self.t is None or v[0] < self.t):
+ self.t = v[0]
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ if self.t is None:
+ raise StopIteration
+ next_t = None
+ res = [ self.t ]
+ for i in range(0, len(self.cur)):
+ if self.nxt[i] is not None and self.nxt[i][0] == self.t:
+ self.cur[i] = self.nxt[i]
+ self.nxt[i] = next_or_none(self.iter[i])
+ if self.nxt[i] is not None and \
+ (next_t is None or self.nxt[i][0] < next_t):
+ next_t = self.nxt[i][0]
+ if self.cur[i] is None:
+ if self.nxt[i] is None:
+ v = 0
+ else:
+ v = self.nxt[i][1]
+ else:
+ if self.nxt[i] is None or self.cur[i][0] == self.t:
+ v = self.cur[i][1]
+ else:
+ v = self.cur[i][1]+(self.nxt[i][1]-self.cur[i][1])* \
+ (self.t-self.cur[i][0])/ \
+ float(self.nxt[i][0]-self.cur[i][0])
+ res.append(v)
+ self.t = next_t
+ return res
+
+
+class waves(list):
+
+ def __init__(self, *args):
+ list.__init__(self)
+ self.extend(args)
+
+ def iterate(self):
+ return waves_iter(self)
+
+ def save(self, name, append = False):
+ f = open(name, ("w", "a")[append])
+ if append:
+ print >>f
+ for v in self.iterate():
+ for i in range(0, len(v)-1):
+ print >>f, v[i],
+ print >>f, v[-1]
+ f.close()
+
+ def load(self, name, step = None):
+ list.__init__(self)
+ empty = re.compile("^\s*(#.*)$")
+ space = re.compile("\s+")
+ first = True
+ skip = step is None
+ t = 0
+ f = open(name, "r")
+ while True:
+ line = f.readline()
+ if line == "":
+ break
+ if empty.match(line):
+ continue
+ m = space.split(line.lstrip().rstrip())
+ if first:
+ for i in range(0, len(m)-skip):
+ self.append(analog())
+ first = False
+ else:
+ if len(m)-skip != len(self):
+ raise hell
+ if step is None:
+ for i in range(0, len(m)-1):
+ self[i].append(float(m[0]), float(m[i+1]))
+ else:
+ for i in range(0, len(m)):
+ self[i].append(t, float(m[i]))
+ t += step
+ f.close()
+
+
# === Analog waves ============================================================
-class analog_wave_iter:
+class analog_iter:
def __init__(self, wave):
self.wave = wave
@@ -99,7 +315,7 @@
return low
-class analog_wave(wave):
+class analog(wave):
def __init__(self):
self.data = []
@@ -115,8 +331,11 @@
return self.data[-1][0]
def append(self, t, y):
- if len(self.data) and t <= self.data[-1][0]:
- raise hell
+ if len(self.data):
+ if t < self.data[-1][0]:
+ raise hell
+ if t == self.data[-1][0] and y == self.data[-1][1]:
+ return
self.data.append((t, y))
def extend(self, wave):
@@ -131,16 +350,32 @@
return self.data[binary(self.data, lambda x: x[0], t)]
def __iter__(self):
- return analog_wave_iter(self)
+ return analog_iter(self)
def __len__(self):
return len(self.data)
+ def digitize(self, low, high = None):
+ if high is None:
+ high = low
+ res = digital()
+ state = None
+ for p in self.data:
+ if p[1] >= high:
+ if state is None or not state:
+ res.append(p[0], 1)
+ state = True
+ elif p[1] <= low:
+ if state is None or state:
+ res.append(p[0], 0)
+ state = False
+ return res
+
# === Digital waves ===========================================================
-class digital_wave_iter:
+class digital_iter:
def __init__(self, wave):
self.wave = wave
@@ -162,7 +397,7 @@
return res
-class digital_wave(wave):
+class digital(wave):
def __init__(self):
self.initial = None
@@ -209,31 +444,8 @@
(binary(self.data, lambda x: x, t) & 1))
def __iter__(self):
- return digital_wave_iter(self)
+ return digital_iter(self)
- # @@@ experimental ! maybe use triggers instead ?
-
- def edges(self, rising, falling = None):
- if falling is None:
- falling = not rising
- if (not falling) and (not rising):
- return []
- res = []
- if falling and rising:
- i = 1
- while i != len(self.data):
- res.append(self.data[i])
- i += 1
- return res
- else:
- i = int(self.initial ^ (not rising))+1
- while i < len(self.data):
- res.append(self.data[i])
- i += 2
- return res
-
- # @@@ experimental !
-
def debounce(self, t):
i = 0
while i < len(self.data)-1:
@@ -245,33 +457,3 @@
def __len__(self):
return len(self.data)*2
-
-
-# === @@@ Work in progress ====================================================
-
-
-def digitize(wave, vl, vh):
- delta = []
- state = 0 # high: 2, rise: 1, unknown: 0, fall: -1, low: -2
- s0 = None
- t0 = None
- for p in wave:
- if p[1] >= vh:
- pass
- elif p1[x] <= vl:
- pass
- else:
- pass
- if state == 2: # high
- pass
- elif state == -2: # low
- pass
- elif state == 1: # rise
- pass
- elif state == -1: # fall
- pass
- else: # unknown
- pass
- if s0 == None:
- return None
- return (s0, delta)
Modified: developers/werner/ahrt/host/tmc/setup.py
===================================================================
--- developers/werner/ahrt/host/tmc/setup.py 2008-09-07 08:31:42 UTC (rev 4632)
+++ developers/werner/ahrt/host/tmc/setup.py 2008-09-08 05:11:00 UTC (rev 4633)
@@ -4,7 +4,7 @@
version = "0.0",
description = "Test and Measurement Control",
py_modules = [ "tmc.instrument",
- "tmc.wave", "tmc.shape",
+ "tmc.wave", "tmc.trigger", "tmc.shape",
"tmc.meter", "tmc.scope", "tmc.power", "tmc.function" ],
package_dir = { "tmc": "lib" },
ext_modules = [
More information about the commitlog
mailing list