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