Wed, 19 Dec 2007 00:19:25 +0100
Several improvements (code refactoring) + add stuff to decode binary coordinate data block ("DCBN" command)
HP3562A/__init__.py | file | annotate | diff | comparison | revisions | |
HP3562A/coord_decoder.py | file | annotate | diff | comparison | revisions | |
HP3562A/enum_types.py | file | annotate | diff | comparison | revisions | |
HP3562A/trace_decoder.py | file | annotate | diff | comparison | revisions | |
bin/read_coord | file | annotate | diff | comparison | revisions | |
examples/coord.bin | file | annotate | diff | comparison | revisions | |
examples/state.bin | file | annotate | diff | comparison | revisions | |
examples/trace.bin | file | annotate | diff | comparison | revisions |
--- a/HP3562A/__init__.py Tue Dec 18 00:38:33 2007 +0100 +++ b/HP3562A/__init__.py Wed Dec 19 00:19:25 2007 +0100 @@ -15,27 +15,46 @@ import struct import re import gpib +import numpy ######################################## # HP3562A internal binary types decoders def decode_float(s): - assert len(s) in [4,8] - # exponential term - e = ord(s[-1]) - if e & 0x80: - e = e - 256 + if len(s) == 4: + i1, i2, e = struct.unpack('>hbb', s) + i2 = i2 * 2**(-23) + return (i1/32768. + i2)*2**e + else: + i1, i2, i3, i4, e = struct.unpack('>hhhbb', s) + if i2 < 0: + i2 = (i2+32768.)*2**(-15) + else: + i2 = i2*2**(-15) + if i3 < 0: + i3 = (i3+32768.)*2**(-30) + else: + i3 = i3*2**(-15) + i4 = i4 * 2**(-38) + return (i1+i2+i3+i4)*2**(e-15) + +# def decode_float(s): +# assert len(s) in [4,8] +# # exponential term +# e = ord(s[-1]) +# if e & 0x80: +# e = e - 256 - # mantissa - m = [ord(x) for x in s[:-1]] - M = 0. - for i in range(len(s)-1): - #M += m[i]<<(i*8) - M += float(m[i])/2**((i+1)*8) - # XXX how do we deal negative numbers? - #if m[0] & 0x80: - # M = M - 2^(len(s)) - return M * 2**(e+1) +# # mantissa +# m = [ord(x) for x in s[:-1]] +# M = 0. +# for i in range(len(s)-1): +# #M += m[i]<<(i*8) +# M += float(m[i])/2**((i+1)*8) +# # XXX how do we deal negative numbers? +# #if m[0] & 0x80: +# # M = M - 2^(len(s)) +# return M * 2**(e+1) def decode_string(s): nb = ord(s[0]) @@ -59,6 +78,8 @@ for row in head_struct: key = row[0] typ = row[1] + if typ is None: + continue val = header.get(key, "N/A") if isinstance(val, basestring): val = repr(val) @@ -83,23 +104,31 @@ fmt = "%%-%ds %%-%ds"%(maxk, maxv) w = maxk+maxv+4 ncols = columns/w - nrows = len(todisp)/ncols + if ncols: + nrows = len(todisp)/ncols + else: + nrows = len(todisp) res = "" for i in range(nrows): res += "| ".join([fmt%todisp[j*nrows+i] for j in range(ncols)]) + "\n" return res -def decode_header(data, header_struct): +def decode_header(data, header_struct, idx=0): d = data - typ = d[:2] - assert typ == "#A" - - totlen = struct.unpack('>h', d[2:4])[0] - idx = 4 + if d[idx:].startswith('#'): + # we have a preliminary header here... + typ = d[idx:idx+2] + assert typ == "#A" + idx += 2 + totlen = struct.unpack('>h', d[idx:idx+2])[0] + idx += 2 tt=0 header = {} for i, (nam, dtype, fmt, nbytes) in enumerate(header_struct): - if dtype == str: + if dtype is None: + idx += nbytes + continue + elif dtype == str: val = decode_string(d[idx:]) else: if fmt: @@ -114,6 +143,14 @@ idx += nbytes return header, idx +def read_trace(data, idx, nelts): + assert len(data[idx:]) >= (nelts*4), "data[idx:] is too small (%s for %s)"%(len(data[idx:]), (nelts*4)) + resu = [] + for i in range(nelts): + resu.append(decode_float(data[idx: idx+4])) + idx += 4 + return numpy.array(resu, dtype=float) + ##################### # HP3562A constants
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/HP3562A/coord_decoder.py Wed Dec 19 00:19:25 2007 +0100 @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +""" +state_decoder +============= + +Module for decoding the internal state of the HP3562A DSA, using the +GPIB command "DSBN" (Dump State BiNary). + +This file can be exectued as a python script. Use '-h' for more informations. +""" + +from HP3562A import format_header, decode_float, decode_string, decode_header, read_trace +from HP3562A.trace_decoder import decode_trace, HEADER as TRACE_HEADER +from HP3562A.enum_types import * + +HEADER = [("Y coordinates", EYCOORD, 'h', 2), + ("# of disp elements", int, "h", 2), + ("First element", int, "h", 2), + ("Total elements", int, 'h', 2), + ("Display sampling", EDISPSMP, 'h', 2), + ("Scaling", ESCAL, 'h', 2), + ("Data pointer", long, 'l', 4), + ("In data", long, 'l', 4), + ("Log/linear x-axis", bool, 'h', 2), + ("Sampled display data", bool, 'h', 2), + ("Plot/Graph mode", bool, 'h', 2), + ("Phase wrap", bool, 'h', 2), + ("Not used", None, None, 36), + ("X scale factor", decode_float, None, 4), + ("Grid min Y scale", decode_float, None, 4), + ("Grid max Y scale", decode_float, None, 4), + ("/div", decode_float, None, 4), + ("Min value of data", decode_float, None, 4), + ("Max value of data", decode_float, None, 4), + ("Y cumulative min", decode_float, None, 4), + ("Y cumulative max", decode_float, None, 4), + ("Y scale factor", decode_float, None, 4), + ("Not used", None, None, 16), + ("Stop value", decode_float, None, 8), + ("Left grid", decode_float, None, 8), + ("Right grid", decode_float, None, 8), + ("Left data", decode_float, None, 8), + ("Right data", decode_float, None, 8), + ] + +def decode_coord(data): + """ + Decode the data (as generated by the HP3562A DSA in response to a + "DSBN" command), and returns a dict (header) + + header is the dictionnary of the header of the dumped data block, + """ + header, idx = decode_header(data, HEADER) + trace_header, idx = decode_header(data, TRACE_HEADER, idx) + trace = read_trace(data, idx, trace_header["Number of elements"]) + return header, trace_header, trace + + +def main(): + import sys + import optparse + import numpy + + opt = optparse.OptionParser("A simple tool for displaying dumped coord data block") + opt.add_option('-f', '--filename', default=None, + dest='filename', + help='Output filename. If not set, read from stdin') + opt.add_option('-m', '--mode', default='binary', + dest='mode', + help='Dumping mode (may be "binary" [default], "ascii" or "ansi")', + ) + opt.add_option('-d', '--display-header', default=False, + action="store_true", + dest="displayheader", + help="Display the trace header") + opt.add_option('-P', '--noplot-trace', default=True, + action="store_false", + dest="plot", + help="Do not display the plot of the trace") + opt.add_option('-x', '--xmode', default='lin', + dest='xmode', + help='X coordinate mode (may be "lin" [default] or "log")') + opt.add_option('-y', '--ymode', default='lin', + dest='ymode', + help='Y coordinate mode (may be "lin" [default], "log" or "db")') + + options, argv = opt.parse_args(sys.argv) + + + if options.filename is None: + print "Can't deal stdin for now..." + sys.exit(1) + try: + coord_header, header, data = decode_coord(open(options.filename, 'rb').read()) + except Exception, e: + print "ERROR: can't read %s an interpret it as a HP3562 trace"%options.filename + print e + sys.exit(1) + + if options.displayheader: + print format_header(coord_header, HEADER, 100) + print format_header(header, TRACE_HEADER, 100) + if options.plot: + f0 = header['Start freq value'] + dx = header['Delta X-axis'] + n = header['Number of elements'] + x = numpy.linspace(f0, f0+dx*n, len(data)) + y = data.copy() + + ym = coord_header['Min value of data'] + yM = coord_header['Max value of data'] + ys = coord_header['Y scale factor'] + + y[y<ym] = ym + y *= ys + + import pylab + pylab.ylabel(header['Amplitude units']) + pylab.grid() + pylab.plot(x, y) + pylab.xlabel('frequency (%s)'%header["X axis units"]) + pylab.ylabel("%s (%s)"%(coord_header['Y coordinates'], header['Amplitude units']) ) + pylab.show() + +if __name__ == "__main__": + main()
--- a/HP3562A/enum_types.py Tue Dec 18 00:38:33 2007 +0100 +++ b/HP3562A/enum_types.py Wed Dec 19 00:19:25 2007 +0100 @@ -70,52 +70,52 @@ 2: "Volt (indicates peak only)", } -EAMP = {0: "Volts", - 1: "Volts squared", - 2: "PSD (V²/Hz)", - 3: "ESD (V²s/Hz)", - 4: "PSD¹² (V/Hz¹²)", - 5: "No unit", - 6: "Unit volts", - 7: "Unit volts²", +EAMP = {0: u"Volts", + 1: u"Volts²", + 2: u"PSD (V²/Hz)", + 3: u"ESD (V²s/Hz)", + 4: u"PSD¹² (V/Hz¹²)", + 5: u"No unit", + 6: u"Unit volts", + 7: u"Unit volts²", } -EXAXIS= {0: "No units", - 1: "Hertz", - 2: "RPM", - 3: "Orders", - 4: "Seconds", - 5: "Revs", - 6: "Degrees", - 7: "dB", - 8: "dBV", - 9: "Volts", - 10: "V Hz¹²", - 11: "Hz/s", - 12: "V/EU", - 13: "Vrms", - 14: "V²/Hz", - 15: "%", - 16: "Points", - 17: "Records", - 18: "Ohms", - 19: "Hertz/octave", - 20: "Pulse/Rev", - 21: "Decades", - 22: "Minutes", - 23: "V²s/Hz", - 24: "Octave", - 25: "Seconds/Decade", - 26: "Seconds/Octave", - 27: "Hz/Point", - 28: "Points/Sweep", - 29: "Points/Decade", - 30: "Points/Octave", - 31: "V/Vrms", - 32: "V²", - 33: "EU referenced to chan 1", - 34: "EU referenced to chan 2", - 35: "EU value", +EXAXIS= {0: u"No units", + 1: u"Hertz", + 2: u"RPM", + 3: u"Orders", + 4: u"Seconds", + 5: u"Revs", + 6: u"Degrees", + 7: u"dB", + 8: u"dBV", + 9: u"Volts", + 10: u"V\u221AHz", + 11: u"Hz/s", + 12: u"V/EU", + 13: u"Vrms", + 14: u"V²/Hz", + 15: u"%", + 16: u"Points", + 17: u"Records", + 18: u"Ohms", + 19: u"Hertz/octave", + 20: u"Pulse/Rev", + 21: u"Decades", + 22: u"Minutes", + 23: u"V²s/Hz", + 24: u"Octave", + 25: u"Seconds/Decade", + 26: u"Seconds/Octave", + 27: u"Hz/Point", + 28: u"Points/Sweep", + 29: u"Points/Decade", + 30: u"Points/Octave", + 31: u"V/Vrms", + 32: u"V²", + 33: u"EU referenced to chan 1", + 34: u"EU referenced to chan 2", + 35: u"EU value", } EMEAS = {0: "Linear resolution", @@ -246,3 +246,25 @@ ETRGLVLUNT = EXAXIS ECPTLGHUNT = EXAXIS + +EYCOORD = { 1: "Real", + 2: "Imaginary", + 3: "Linear magnitude", + 4: "Log magnitude", + 5: "dB", + 6: "Nyquist", + 7: "Not used", + 8: "Phase", + 9: "Nichols", + 10: "dBm", + } + +EDISPSMP = { 0: "Not sampled", # #displayed elements = total elements + 1: "Half sampled", # #displayed elements = total elements/2 + 2: "Sampled", # #displayed elements < total elements + } +ESCAL = { 0: "X and Y auto scale", + 1: "X fixed, Y auto scale", + 2: "X auto scale, Y fixed", + 3: "X and Y fixed", + }
--- a/HP3562A/trace_decoder.py Tue Dec 18 00:38:33 2007 +0100 +++ b/HP3562A/trace_decoder.py Wed Dec 19 00:19:25 2007 +0100 @@ -10,7 +10,7 @@ """ import struct import numpy -from HP3562A import format_header, decode_float, decode_string, decode_header +from HP3562A import format_header, decode_float, decode_string, decode_header, read_trace from HP3562A.enum_types import * @@ -61,7 +61,7 @@ ('Start data value', decode_float, None, 8), ] -def decode_trace(data): +def decode_trace(data, idx=0): """ Decode the data (as generated by the HP3562A DSA in response to a "DDBN" command), and returns a couple (header, values). @@ -70,14 +70,8 @@ value is a numpy array holding the trace (vector of float or complex values). """ - header, idx = decode_header(data, HEADER) - resu = [] - for i in range(header["Number of elements"]): - resu.append(decode_float(data[idx: idx+4])) - idx += 4 - return header, numpy.array(resu, dtype=float) - - + header, idx = decode_header(data, HEADER, idx) + return header, read_trace(data, idx, header["Number of elements"]) def main(): import sys