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