Source code for lightlab.equipment.lab_instruments.Anritsu_MP1763B_PPG

from . import VISAInstrumentDriver
from lightlab.equipment.abstract_drivers import Configurable
from lightlab.laboratory.instruments import PulsePatternGenerator
from lightlab.util.data.one_dim import prbs_pattern
import warnings
import numpy as np
import matplotlib.pyplot as plt


[docs]class Anritsu_MP1763B_PPG(VISAInstrumentDriver, Configurable): ''' ANRITSU MP1761A PulsePatternGenerator The PPG MP1763B at Alex's bench, which also support MP1761A (by Hsuan-Tung 07/27/2017) Manual (MP1763C): http://www.otntech.com/modules/catalogue/download.php?id=52&mode=download&file_name=MP1763C.pdf Usage: :any:`/ipynbs/Hardware/PulsePatternGenerator.ipynb` ''' instrument_category = PulsePatternGenerator storedPattern = None def __init__(self, name='The PPG', address=None, **kwargs): VISAInstrumentDriver.__init__(self, name=name, address=address, **kwargs) Configurable.__init__(self, headerIsOptional=False, precedingColon=False)
[docs] def startup(self): self.on() self.setConfigParam('PTS', 1) # set to data pattern mode # set to negative because only data-bar works self.setConfigParam('LGC', 1)
[docs] def setPrbs(self, length): ''' Generates a PRBS ''' bits = np.random.randint(0, 2, length) self.setPattern(bits)
[docs] def setPattern(self, bitArray): ''' Data bitArray for the PPG to output. Args: bitArray (ndarray): array that is boolean or binary 1/0 ''' bitArray = np.array(bitArray, dtype=bool) bitArray = np.array(bitArray, dtype=int) # pad to multiple of 16 pad = np.zeros(-len(bitArray) % 16, dtype=int) bitArrayPadded = np.concatenate((bitArray, pad)) nBytes = int(np.ceil(len(bitArrayPadded) / 8)) byteMat = np.reshape(bitArrayPadded, (nBytes, 8)) pStr = '' intList = [None] * nBytes for i, bt in enumerate(byteMat): intVal = np.sum(bt * 2 ** np.arange(8)) pStr += chr(intVal) # Switch to other endian if i % 2 == 0: ind = i + 1 else: ind = i - 1 intList[ind] = intVal byteList = bytes(intList) self.setConfigParam('PTS', 1) # We only care to set data patterns # self.setConfigParam('DLN', 8*nBytes, forceHardware=True) self.setConfigParam('DLN', len(bitArray), forceHardware=True) preamble = 'WRT {}, 0'.format(nBytes) self.write(preamble) self.open() self.mbSession.write_raw(byteList) self.close() # self.write(pStr) self.storedPattern = bitArray
# print(bitArray)
[docs] def getPattern(self): ''' Inverts the setPattern method, so you can swap several patterns around on the fly. Does not communicate with the hardware as of now. ''' if self.storedPattern is None: raise NotImplementedError( 'Can not read pattern from PPG. Set it first using this instance.') return self.storedPattern
[docs] def on(self, turnOn=True): self.setConfigParam('OON', 1 if turnOn else 0)
[docs] def syncSource(self, src=None): ''' Output synchronizer is locked to pattern or not? Args: src (str): either 'fixed', 'variable' or 'clock64'. If None, leaves it Returns: (str): the set value as a string token ''' tokens = ['clock64', 'fixed', 'variable'] if src is not None: try: iTok = tokens.index(src) except ValueError: raise ValueError( src + ' is not a valid sync source: ' + str(tokens)) self.setConfigParam('SOP', iTok) return tokens[int(self.getConfigParam('SOP'))]
[docs] def amplAndOffs(self, amplOffs=None): ''' Amplitude and offset setting/getting Args: amplOffs (tuple(float)): new amplitude and offset in volts If either is None, returns but does not set Returns: (tuple(float)): amplitude and offset, read from hardware if specified as None ''' if amplOffs is None: amplOffs = (None, None) if np.isscalar(amplOffs): raise ValueError('amplOffs must be a tuple. ' + 'You can specify one element as None if you don\'t want to set it') amplitude, offset = amplOffs if amplitude is not None: self.setConfigParam('DAP', amplitude) if offset is not None: self.setConfigParam('DOS', offset) ampl = float(self.getConfigParam('DAP')) offs = float(self.getConfigParam('DOS')) return (ampl, offs)
# defining function bitsequence, that takes delays and transfer them into a sequence we want.
[docs] def bitseq(self, chpulses, clockfreq, ext=0, addplot=False, mult=1, res=5): ''' bitseq: Takes in dictionary 'chpulses', clock freq 'clockfreq', and opt. parameter 'ext.' Also includes plotting parameters (see below). chdelays: a dictionary in which keys are channel delays, and values contain a list of tuple pairs. Each pair contains pulse times (rising edges) and their duration (in ns). clockfreq: set the current clock frequency, in GHz ext: a continuous value from 0 to 1 which extends the pattern length, resulting in different synchronization between adjacent time windows. 0 -- will result in maximum similarity between time windows, plus or minus variabilities resulting from delay lines. This is ideal when only approximate timings are required, since channels IDs can be shuffled by time scrolling through the same PPG pattern. 1 -- will result in minimum similarity between adjacent time windows, at the cost of a larger total PPG pattern length. Anything beyond this value is not useful. Values between 0 and 1 will trade-off pattern length with window similarity. addplot: Adds a plot to visualize the output of the PPG along all channels. mult: graphing parameter - how many multiples of pattern length to display in time res: graphing parameter - how many sampling points per pattern bit Author: Mitchell A. Nahmias, Feb. 2018 ''' delays = sorted(chpulses.keys()) # timeWindow = min(np.diff(delays)) ChNum = len(chpulses) totalTime = np.mean(np.diff(delays)) * ChNum * (1 + ext) # bitLength = int(np.round(timeWindow*clockfreq)) totalBitLength = int(np.round(totalTime * clockfreq)) pattern = np.zeros(totalBitLength, dtype=int) warningFlag = False for delay in chpulses: for pulses in chpulses[delay]: pulsePos = pulses[0] pulseWidth = pulses[1] if not warningFlag and pulseWidth < 2 / clockfreq: warnings.warn( 'Pulse width(s) may be too short. Consider increasing the clock rate.') warningFlag = True # for X active channels, apply a [delay(X) - delay(X-i)] time offset # Note: Given N channels, the synchronized pulses appear at time window N. Therefore, the minimum # delay channel (D=0) outputs the (P=N) set of pulses, the second min. delay channel (D=1) # outputs the (P=N-1) set of pulses, etc. # As a result, each pulse receives an inverse channel delay: K - current_delay + pulsePosition, for some K. # We set K to max(delay) since that sets the last channel [max(delay)] at # the beginning of the pattern. pIndex = int(np.round((max(chpulses.keys()) - delay + pulsePos) * clockfreq)) # add pulse to pattern at correct delay pattern[pIndex:pIndex + int(np.round(pulseWidth * clockfreq))] = 1 # optional plotting function if addplot: T = np.linspace(0, mult * len(pattern) / clockfreq, mult * res * len(pattern)) # allows cyclical access to pattern array via any input index def circ_time(T, pattern): pattern_ind = (np.round(T * clockfreq) % len(pattern)).astype(int) return pattern[pattern_ind] plt.figure(figsize=(15, 5)) for d in delays: plt.plot(T, circ_time(T - d, pattern), label=str(int(d)) + ' ns') plt.xlabel('Time (ns)') plt.ylabel('a.u.') plt.title('Expected Output') plt.legend() return pattern
[docs] @classmethod def PRBS_pattern(cls, order, mark_ratio=0.5): # Documentation present in page 5-1 of the manual. if mark_ratio != 0.5: raise NotImplementedError("Only mark ratio of 1/2 is implemented so far.") # The seed corresponds to the first digits in the bit sequence # presented on the PPG (inverted order) if order == 7: polynomial = 0b10000011 # 1 + X^6 + X^7 seed = 0b1000000 elif order == 9: polynomial = 0b1000010001 # 1 + X^5 + X^9 seed = 0b111100000 elif order == 11: polynomial = 0b100000000101 # 1+X9+X11 seed = 0b11000000000 elif order == 15: polynomial = 0b1000000000000011 # 1+X14+X15 seed = 0b000000000000001 elif order == 20: polynomial = (1 << 20) + (1 << 20 - 3) + (1 << 20 - 20) # 1+X3+X20 seed = 0b00111000111000111000 elif order == 23: polynomial = (1 << 23) + (1 << 23 - 18) + (1 << 23 - 23) # 1+X18+X23 seed = 0b1111100 << 16 elif order == 31: polynomial = (1 << 31) + (1 << 31 - 28) + (1 << 31 - 31) # 1+X28+X31 seed = 0b1110000 << 24 else: raise NotImplementedError("PRBS{} not implemented.".format(order)) return prbs_pattern(polynomial, seed)