r4679 - in developers/werner/ahrt/host/tmc: demo lib
werner at docs.openmoko.org
werner at docs.openmoko.org
Mon Sep 29 02:33:57 CEST 2008
Author: werner
Date: 2008-09-29 02:33:55 +0200 (Mon, 29 Sep 2008)
New Revision: 4679
Added:
developers/werner/ahrt/host/tmc/demo/eye.py
developers/werner/ahrt/host/tmc/lib/phosphor.py
Modified:
developers/werner/ahrt/host/tmc/lib/trigger.py
developers/werner/ahrt/host/tmc/lib/wave.py
Log:
Highlights:
- new module phosphor.py to view how often patterns repeat
- example eye.py to plot an eye diagram with phosphor.py
Details:
- lib/wave.py (analog, digital): moved binary search by time from "get_one"
into new method "index"
- lib/trigger.py (edge.apply): returned a false trigger at the start of the
recording when using slope.Both
- lib/phosphor.py: "phosphor" screen that remembers how often a pixel was drawn
- demo/eye.py: example for drawing an eye diagram with phosphor.py
Added: developers/werner/ahrt/host/tmc/demo/eye.py
===================================================================
--- developers/werner/ahrt/host/tmc/demo/eye.py (rev 0)
+++ developers/werner/ahrt/host/tmc/demo/eye.py 2008-09-29 00:33:55 UTC (rev 4679)
@@ -0,0 +1,156 @@
+#!/usr/bin/python
+
+#
+# @@@ WORK IN PROGRESS.
+#
+# What's missing:
+#
+# - there's no way to display the color bar for reference
+# - there's no easy way to add axis and ticks
+# - the data processing to produce the eye diagram is complex enough that it
+# deserves to be packaged as one high-level operation in the library
+#
+
+import time
+from tmc.scope import *
+from tmc.trigger import *
+from tmc.phosphor import phosphor
+from math import ceil, floor
+
+#
+# This experiment captures a signal and draws an eye diagram. The eye diagram
+# allows examination of signal integrity and timing violations.
+#
+# Note that this is still a bit primitive. A proper eye diagram would also show
+# the "forbidden" areas.
+#
+# Our test setup is a function generator connected to the channel 2 ("B") of a
+# Rigol DS1000. The function generator emits a 1MHz square wave with slowly
+# (100ns) rising and falling edges.
+#
+# The parameters below:
+#
+# width: the nominal width of a pulse
+# overlap: the time we capture before and after each pulse
+# waveforms: how many waveforms we include in the output
+#
+
+width = 0.5e-6
+overlap = 0.2e-6
+waveforms = 1000
+
+# Open the scope
+
+s = rigol_ds1000c()
+s.debug = False
+
+# Set the horizontal system to have the trigger at the center and the scale
+# set to 10ns/div. We pick this scale because it gives us a 200MSa/s
+# resolution.
+
+s.hor.pos = 0
+s.hor.scale = 10e-9
+#s.hor.scale = 100e-6
+
+# We capture channel "B". Our input has a peak-to-peak voltage of 3.3V, so we
+# set the vertical system such that the screen center is at 1.5V and the scale
+# is 1V/div.
+
+ch = s.ch[1]
+ch.pos = 1.5
+ch.scale = 1
+
+# Define an edge trigger that triggers on the rising edge at 1.65V.
+
+t = edge(slope = slope.Rising, level = 1.65)
+
+# Apply the trigger to channel "B".
+
+t.source(ch)
+
+# Set the horizontal system to single sweep, stop the scope, then start it.
+
+s.hor.sweep = sweep.Single
+s.hor.stop()
+s.hor.run()
+
+# Wait until the scope has triggered and finished the acquisition.
+
+while s.hor.state() != state.Stop:
+ time.sleep(0.1)
+
+# With the above settings, the scope captures about 1.3ms left and right of the
+# trigger. We extract 1ms of the captured waveform.
+
+w = s.wave(ch, start = 0, end = width*waveforms)
+
+# Digitize the waveform and find the rising and falling edges. We construct the
+# trigger to extract the waveforms on the fly.
+
+d = w.digitize(0.8, 2.0)
+e = edge(slope = slope.Both, level = 0.5).apply(d)
+
+# Print what we got.
+
+print len(w), "samples,", len(e), "edges"
+
+# The "phosphor" display operates on a discrete pixel matrix. We therefore
+# obtain a list of the sample values, stripping the time.
+
+samples = map(lambda p: p[1], w)
+
+# Autoscale the y-axis: vcenter is the value at the center of the display. vres
+# is the peak to peak maximum. yres is the arbitrary y resolution we choose.
+# (260 is slightly larger than 256, the typical ADC resolution, so we should
+# get the full dynamic range of the ADC.)
+
+low = min(samples)
+high = max(samples)
+
+vcenter = (high+low)/2
+vres = high-low
+yres = 260
+
+# Autoscale the x-axis: tres is the time interval we want to see. 1/sr is the
+# sample rate. xres is the number of horizontal pixels we give to have each
+# sample its own column.
+
+tres = width+2*overlap
+sr = w.data[1][0]-w.data[0][0]
+xres = tres/sr
+
+# Create a phosphor "screen" with the desired resolution. We add some extra
+# pixels compensate for rounding errors.
+
+ph = phosphor(int(ceil(xres)), yres+2)
+
+# Calculate at which x-offset we put the trigger point.
+
+offset = int(overlap/sr)
+
+# Convert the samples to the y-coordinates on the "screen".
+
+values = map(lambda s: int(round((s-vcenter)/vres*yres+yres/2)), samples)
+
+# Convert all triggers from time to sample indices.
+
+starts = map(lambda t: w.index(t), e)
+
+# Draw the waveform in the window around each trigger index.
+
+for s in starts:
+ ph.draw_window(values, s, offset)
+
+# Generate a PNM image of the screen with the following characteristics:
+#
+# - each screen pixel is represented by a 2x1 rectangle in the image
+# - the frequency is normalized on a logarithmic scale
+# - the frequency of events is indicated with a color spectrum
+
+s = ph.pnm(2, 1, normalize = ph.logarithmic, colors = ph.color_spectrum)
+
+# Write the image to a file called "out.pnm".
+
+f = open("out.pnm", "w")
+print >>f, s
+f.close()
Property changes on: developers/werner/ahrt/host/tmc/demo/eye.py
___________________________________________________________________
Name: svn:executable
+ *
Added: developers/werner/ahrt/host/tmc/lib/phosphor.py
===================================================================
--- developers/werner/ahrt/host/tmc/lib/phosphor.py (rev 0)
+++ developers/werner/ahrt/host/tmc/lib/phosphor.py 2008-09-29 00:33:55 UTC (rev 4679)
@@ -0,0 +1,182 @@
+#
+# phosphor.py - "Phosphor" screen that remembers how often a pixel was drawn
+#
+# Copyright (C) 2008 by OpenMoko, Inc.
+# Written by Werner Almesberger <werner at openmoko.org>
+# All Rights Reserved
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+
+# @@@ EXPERIMENTAL !
+
+#
+# The screen width is in samples. Lines can only go from one sample to the
+# next, i.e., from x to x+1. Lines don't include their final point, the
+# assumption being that this point will be part of the next line drawn.
+#
+# There are several line algorithms:
+#
+# - "saturated" adds one to each pixel visited
+#
+# - "beam" mimicks the beam on an oscilloscope's CRT by decreasing the
+# intensity linearly with the length of the line, such that the sum of all
+# the pixels drawn for that line is one.
+#
+# "saturated" produces good-looking images, but makes areas with a high noise
+# look more intense than they are. "beam" is more accurate, but has the small
+# disadvantage that a lone outlier does not just use a single color.
+#
+# Colors are encoded as RGB tuples, where each component is in the range 0 to
+# 1. The color conversion function is given the value of the pixel, normalized
+# into the range 0 to 1.
+#
+# The following conversion functions are available:
+#
+# - "linear": this is just a linear greyscale from black to white
+#
+# - "default": similar to "linear", but the minimum value is 10% and a mild
+# fake gamma correction is applied, so that infrequent events are more
+# visible
+#
+# - "spectrum": uses a simple color spectrum going from blue to green to red
+#
+# Finally also the normalization is configurable:
+#
+# - linear: linear mapping of the range [0, max] to [0, 1]
+#
+# - logarithmic: logarithmic mapping of the range [ln(min), ln(max)] to [0, 1].
+# Black pixels are excluded from the minimum.
+#
+
+
+from math import log
+
+
+class norm_linear:
+
+ def __init__(self, min, max):
+ self.f = 1.0/max
+
+ def norm(self, v):
+ return self.f*v
+
+
+class norm_logarithmic:
+
+ def __init__(self, min, max):
+ self.offset = -log(min)
+ self.f = 1.0/(log(max)+self.offset)
+
+ def norm(self, v):
+ return self.f*(log(v)+self.offset)
+
+
+class phosphor(object):
+
+ linear = norm_linear
+ logarithmic = norm_logarithmic
+
+ def __init__(self, x, y):
+ self.m = []
+ for i in range(0, x):
+ self.m.append([0.0]*y)
+ self.x = x
+ self.y = y
+
+ def saturated_line(self, x, y0, y1):
+ if y0 == y1:
+ self.m[x][y0] += 1.0
+ elif y0 < y1:
+ for i in range(0, y1-y0):
+ self.m[x][y0+i] += 1.0
+ else:
+ for i in range(0, y0-y1):
+ self.m[x][y0-i] += 1.0
+
+ def beam_line(self, x, y0, y1):
+ if y0 == y1:
+ self.m[x][y0] += 1.0
+ elif y0 < y1:
+ f = 1.0/(y1-y0)
+ for i in range(0, y1-y0):
+ self.m[x][y0+i] += f
+ else:
+ f = 1.0/(y0-y1)
+ for i in range(0, y0-y1):
+ self.m[x][y0-i] += f
+
+ draw_line = beam_line
+
+ #
+ # "draw_window" is where really all the magic happens. It differs from a
+ # simple iteration over a subset of the samples by allowing the reference
+ # point of the waveform and the reference point on the screen to bet set
+ # independently.
+ #
+ # "samples" is the list of sample values.
+ # "start" is the reference point in the sample list.
+ # "offset" is the on-screen position of the reference point. If "offset" is
+ # positive, samples before "start" are drawn.
+ #
+
+ def draw_window(self, samples, start = 0, offset = 0):
+ for x in range(max(0, offset-start),
+ min(self.x, len(samples)-start+offset)-1):
+ pos = start+x-offset
+ self.draw_line(x, samples[pos], samples[pos+1])
+
+ def color_default(self, v):
+ return [0.1+0.9*v*(2.0-v)]*3
+
+ def color_linear(self, v):
+ return (v, v, v)
+
+ def color_spectrum(self, v):
+ return (
+ max(0.0, (4.0*v-4.0)*(1.0-v)+1.0),
+ max(0.0, 8.0*v*(1.0-v)-1.0),
+ max(0.0, 4.0*v*-v+1.0))
+ return (
+ min(1.0, max(0.0, (2.0*v-2.0)*(v+0.5)+1.0)),
+ max(0.0, 16.0*v*(1.0-v)-1.0),
+ min(1.0, 8.0*v*v))
+
+ def max(self):
+ return reduce(lambda a, b: max(a, max(b)), self.m, 0)
+
+ def min(self):
+ return reduce(lambda a, b: min(a,
+ reduce(lambda a, b: [a, min(a, b)][b != 0], b, 1)),
+ self.m, 1)
+
+ def pnm(self, px = 1, py = 1, colors = None, normalize = norm_linear):
+ if colors is None:
+ fn = self.color_default
+ elif isinstance(colors, list):
+ fn = lambda v: colors[min(len(colors)-1, int(v*len(colors)))]
+ else:
+ fn = colors
+
+ s = "P6 %d %d 255\n" % (self.x*px, self.y*py)
+
+ norm = normalize(self.min(), self.max())
+
+ for y in range(self.y-1, -1, -1):
+ row = ""
+ for x in range(0, self.x):
+ if self.m[x][y] == 0:
+ t = (0, 0, 0)
+ else:
+ t = fn(norm.norm(self.m[x][y]))
+ t = chr(min(int(t[0]*256), 255))+ \
+ chr(min(int(t[1]*256), 255))+ \
+ chr(min(int(t[2]*256), 255))
+ for i in range(0, px):
+ row += t
+ for i in range(0, py):
+ s += row
+ return s
Property changes on: developers/werner/ahrt/host/tmc/lib/phosphor.py
___________________________________________________________________
Name: svn:executable
+ *
Modified: developers/werner/ahrt/host/tmc/lib/trigger.py
===================================================================
--- developers/werner/ahrt/host/tmc/lib/trigger.py 2008-09-28 02:12:36 UTC (rev 4678)
+++ developers/werner/ahrt/host/tmc/lib/trigger.py 2008-09-29 00:33:55 UTC (rev 4679)
@@ -109,7 +109,7 @@
if self.level <= 0 or self.level >= 1:
return []
if self.slope == slope.Both:
- return wave.data[:]
+ return wave.data[1:]
res = []
i = int(wave.initial ^ self.slope == slope.Falling)+1
while i < len(wave.data):
Modified: developers/werner/ahrt/host/tmc/lib/wave.py
===================================================================
--- developers/werner/ahrt/host/tmc/lib/wave.py 2008-09-28 02:12:36 UTC (rev 4678)
+++ developers/werner/ahrt/host/tmc/lib/wave.py 2008-09-29 00:33:55 UTC (rev 4679)
@@ -344,11 +344,14 @@
raise hell
self.data.extend(wave.data)
- def get_one(self, t):
+ def index(self, t):
if len(self.data) == 0 or t < self.data[0][0] or t > self.data[-1][0]:
raise hell
- return self.data[binary(self.data, lambda x: x[0], t)]
+ return binary(self.data, lambda x: x[0], t)
+ def get_one(self, t):
+ return self.data[self.index(t)]
+
def __iter__(self):
return analog_iter(self)
@@ -434,11 +437,15 @@
self.data.extend(wave.data[1:])
self.t_end = wave.t_end
+ def index(self, t):
+ if len(self.data) == 0 or t < self.data[0] or t > self.t_end:
+ raise hell
+ return binary(self.data, lambda x: x, t)
+
def get_one(self, t):
if len(self.data) == 0 or t < self.data[0] or t > self.t_end:
raise hell
- return int(self.initial ^
- (binary(self.data, lambda x: x, t) & 1))
+ return int(self.initial ^ (self.index(t) & 1))
# experimental
More information about the commitlog
mailing list