Guest User

chroma6310 ivi hack inplementation

a guest
Dec 28th, 2015
131
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 16.97 KB | None | 0 0
  1. class ChromaProgLoad(chroma6310.chroma6310, Instrument):
  2.     """Class for Chroma programmable DC electronic load"""
  3.  
  4.     def __init__(self, *args, **kwargs):
  5.         super(ChromaProgLoad, self).__init__(*args, **kwargs)
  6.         self.chan_dict = {}
  7.         #self._interface.writebybytes = True
  8.         #self._interface.message_delay = 0.05
  9.         self._interface.update_settings()
  10.  
  11.     """
  12.    Currently there is no IVI standard for programmable loads.
  13.    Rather than trying to invent one to follow the existing IVI standards
  14.    and then wrapping around it, for now just keep the chroma6310 class
  15.    a bare skeleton and do all the work in this class with the interface
  16.    we want to use.
  17.    """
  18.  
  19.     """
  20.    Modes of operation: Constant Current (CC), Constant Resistance (CR),
  21.    Constant Voltage (CV), Constant Power (CP).
  22.  
  23.        Low and high modes available.  The low range provides better
  24.        resolution at low current settings.
  25.  
  26.        Static and dynamic modes available.  Static checks the stability of
  27.        the output voltage from a power supply.  Dynamic mode takes two
  28.        load levels (CxDLT1, CxDLT2) and a slew rate (CxDL up, CxDL down) to
  29.        switch between the two, which would be useful for transient load
  30.        testing.
  31.    """
  32.  
  33.     def _chan_num(self, num_or_label):
  34.         num = None
  35.         if type(num_or_label) is str:
  36.             num = self.chan_dict[num_or_label]
  37.         elif type(num_or_label) is int:
  38.             num = num_or_label
  39.         else:
  40.             raise TypeError('Channel number or label expected')
  41.         return num
  42.  
  43.     def set_chan_label(self, chan_num, label):
  44.         self.chan_dict[label] = chan_num
  45.  
  46.     def switch_chan(self, num_or_label):
  47.         """Switch to specified channel @num_or_label."""
  48.         num = self._chan_num(num_or_label)
  49.         self._write('CHAN {num:d}'.format(num=num))
  50.  
  51.     def get_chan_num(self):
  52.         """Return current channel number."""
  53.         return int(self._ask('CHAN?'))
  54.  
  55.     def get_chan_label(self, num=None):
  56.         """Return channel label for given @num, or current channel label if
  57.        no @num specified."""
  58.         if num is None:
  59.             num = int(self._ask('CHAN?'))
  60.         for key in self.chan_dict:
  61.             if self.chan_dict[key] == num:
  62.                 return key
  63.         return str(num)
  64.  
  65.     def _measure_all(self, mtype='current'):
  66.         """Fetch voltages, currents, or power values measured on all channels
  67.        as a dictionary, where the key is the channel label and the value is
  68.        the measurement."""
  69.         m = mtype[0].upper()
  70.         res = self._ask('FETC:ALL{m:s}?'.format(m=m))
  71.         vals = res.split(',')
  72.         d = {}
  73.         num = 1
  74.         for v in vals:
  75.             label = self.get_chan_label(num)
  76.             d[label] = float(v)
  77.             num += 1
  78.         return d
  79.  
  80.     def get_load_voltage(self, chan=None):
  81.         """Measure voltage on @chan (number or label), or current channel if
  82.        @chan is None."""
  83.         if not chan is None:
  84.             self.switch_chan(chan)
  85.         return float(self._ask('FETC:VOLT?'))
  86.  
  87.     def get_load_current(self, chan=None):
  88.         """Measure current on @chan_num (1-based), or current channel if
  89.        @chan_num is None."""
  90.         if not chan is None:
  91.             self.switch_chan(chan)
  92.         return float(self._ask('FETC:CURR?'))
  93.  
  94.     def get_max_current(self, chan=None):
  95.         """Ask instrument for the maximum current allowed on @chan_num
  96.        (1-based), or current channel if @chan_num is None."""
  97.         if not chan is None:
  98.             self.switch_chan(chan)
  99.         return float(self._ask('CURR:STAT:L1? MAX'))
  100.  
  101.     def get_load_power(self, chan=None):
  102.         """Measure power on @chan_num (1-based), or current channel if
  103.        @chan_num is None."""
  104.         if not chan is None:
  105.             self.switch_chan(chan)
  106.         # Older Chroma 6314 (not 6314A) doesn't seem to support FETC:POW?
  107.         # Simulate it by doing a voltage and current measurement
  108.         # and multiplying the results
  109.         if self.get_model() == '6314':
  110.             v = float(self._ask('FETC:VOLT?'))
  111.             i = float(self._ask('FETC:CURR?'))
  112.             return v*i
  113.         return float(self._ask('FETC:POW?'))
  114.  
  115.     def get_all_voltages(self):
  116.         """Fetch voltages measured on all channels as a dictionary, where
  117.        the key is the channel label and the value is the voltage measured."""
  118.         return self._measure_all('voltage')
  119.  
  120.     def get_all_currents(self):
  121.        """Fetch currents measured on all channels as a dictionary, where
  122.       the key is the channel label and the value is the current measured."""
  123.        return self._measure_all('current')
  124.  
  125.     def get_all_powers(self):
  126.        """Fetch powers measured on all channels as a dictionary, where
  127.       the key is the channel label and the value is the watts measured."""
  128.        if self.get_model() == '6314':
  129.            all_v = self.get_all_voltages()
  130.            all_i = self.get_all_currents()
  131.            return {k: all_v[k] * all_i[k] for k in all_v}
  132.        return self._measure_all('power')
  133.  
  134.     def short_enable(self, en, chan=None):
  135.         """Activate or deactivate short-circuit simulation for @chan"""
  136.         if not chan is None:
  137.             self.switch_chan(chan)
  138.        
  139.         if en:
  140.             state_s = 'ON'
  141.             # Use CRL mode to have 0.01 ohm short
  142.             self._write('MODE CRL')
  143.         else:
  144.             state_s = 'OFF'
  145.  
  146.         self._write('LOAD:SHOR {state:s}'.format(state=state_s))
  147.  
  148.     def activate_chan(self, chan=None):
  149.         """Turn on electronic load on given @chan."""
  150.         if not chan is None:
  151.             self.switch_chan(chan)
  152.         self._write('LOAD ON')
  153.  
  154.     def deactivate_chan(self, chan=None):
  155.         """Turn off electronic load on given @chan."""
  156.         if not chan is None:
  157.             self.switch_chan(chan)
  158.         self._write('LOAD OFF')
  159.  
  160.     def chan_is_active(self, chan=None):
  161.         """Return True if given @chan is active / has a load enabled."""
  162.         if not chan is None:
  163.             self.switch_chan(chan)
  164.         res = int(self._ask('LOAD?'))
  165.         return res == 1
  166.  
  167.     def deactivate_all(self):
  168.         """Sets all electronic loads to OFF."""
  169.         self._write('ABOR')
  170.  
  171.     def _set_param(self, cmd, value, delay=0.0, rnd=None, trys=1, max_dev=None):
  172.         loopdelay = delay
  173.         timeout = trys # default - only try once, no redo's
  174.         while (timeout > 0):
  175.             if (rnd == None):
  176.                 self._write(cmd + " " + value)
  177.                 time.sleep(loopdelay)
  178.                 readback = origRead = self._ask(cmd + "?")[0:len(value)]
  179.             else:
  180.                 value = round(float(value),rnd)
  181.                 self._write(cmd + " %e" % value)
  182.                 time.sleep(loopdelay)
  183.                 readback = origRead = self._ask(cmd + "?")
  184.                 readback = round(float(readback),rnd)
  185.             if(readback == value):
  186.                 break
  187.             elif ((abs(readback - value) / value) > max_dev) and max_dev:
  188.                 break #if within allowed deviation, continue.
  189.  
  190.             loopdelay += 0.01 #delay gets steadily longer with each failed command.
  191.             timeout -= 1
  192.  
  193.         if (timeout <= 0):
  194.             raise AssertionError("Timeout during setting of '%s'. expecting '%s', got '%s'" % (cmd, value, read))
  195.         return origRead
  196.  
  197.     def set_static_current(self, setpoint, chan=None, low_range=False,
  198.                        slew_rise=0.0032, slew_fall=0.0032, approx_ok=False):
  199.         """Set static current level for constant current mode.
  200.        Optionally, set a slew rate in Amps/uS."""
  201.         raise NotImplementedError('cc mode is just a modified copy of cr mode')
  202.         if not chan is None:
  203.             self.switch_chan(chan)
  204.  
  205.         if low_range:
  206.             self._write('MODE CCL')
  207.         else:
  208.             self._write('MODE CCH')
  209.  
  210.  
  211.  
  212.         self._set_param('CURR:STAT:RISE', slew_rise, rnd=6)
  213.         self._set_param('CURR:STAT:RISE', slew_rise, rnd=6)
  214.         #self._write('CURR:STAT:RISE {v:.6f}'.format(v=slew_rise))
  215.         #self._write('CURR:STAT:FALL {v:.6f}'.format(v=slew_fall))
  216.  
  217.         self._set_param('CURR:STAT:L1', setpoint, rnd=6)
  218.         #self._write('CURR:STAT:L1 {v:.6f}'.format(v=setpoint))
  219.         #self._write('CURR:STAT:L2 {v:.6f}'.format(v=setpoint))
  220.  
  221.         # readback = float(self._ask('CURR:STAT:L1?'))
  222.         # max_dev = 2.0 if approx_ok else 0.015
  223.         # if (abs(readback - setpoint) / setpoint) > max_dev:
  224.         #     raise AssertionError('Failed to set current.  Expected ' \
  225.         #         '{exp:.3f}, got {rb:.3f}'.format(exp=setpoint, rb=readback))
  226.  
  227.  
  228.  
  229.     def set_dynamic_current(self, load1, load2, load1dur, load2dur,
  230.                             slew_rise=0.0032, slew_fall=0.0032,
  231.                             chan=None, low_range=False, approx_ok=False):
  232.         """
  233.        Set dynamic current level for constant current mode.
  234.        Optionally, set a slew rate in Amps/uS.
  235.  
  236.        dynamic current mode switched between load one and load two
  237.        once the duration for load one or two elapses.
  238.        current slew rate between the two loads is controlled by
  239.        slew rise and slew fall
  240.  
  241.        variable:       unit:
  242.        ----------------------
  243.        load1       -   amps
  244.        load2       -   amps
  245.        load1dur    -   uS
  246.        load2dur    -   uS
  247.        slew_rise   -   Amps/uS
  248.        slew_fall   -   Amps/uS
  249.        """
  250.         raise NotImplementedError('cc mode is just a modified copy of cr mode')
  251.         if not chan is None:
  252.             self.switch_chan(chan)
  253.  
  254.         if low_range:
  255.             self._write('MODE CCDL')
  256.         else:
  257.             self._write('MODE CCDH')
  258.  
  259.  
  260.         self._write('CURR:L1 {v:.6f}'.format(v=setpoint))
  261.         # readback = float(self._ask('RES:L1?'))
  262.         # max_dev = 2.0 if approx_ok else 0.015
  263.         # if (abs(readback - setpoint) / setpoint) > max_dev:
  264.         #     raise AssertionError('Failed to set current.  Expected ' \
  265.         #         '{exp:.3f}, got {rb:.3f}'.format(exp=setpoint, rb=readback))
  266.  
  267.     def set_resistance(self, setpoint, chan=None, low_range=False,
  268.                        slew_rise=0.0032, slew_fall=0.0032, approx_ok=False):
  269.         """Set static resistance level for constant resistance mode.
  270.        Optionally, set a slew rate in Amps/uS."""
  271.         #todo - actually implement slew rate. slew rate not implemented.
  272.         if not chan is None:
  273.             self.switch_chan(chan)
  274.  
  275.         if low_range:
  276.             self._write('MODE CRL')
  277.         else:
  278.             self._write('MODE CRH')
  279.  
  280.         self._write('RES:L1 {v:.6f}'.format(v=setpoint))
  281.         readback = float(self._ask('RES:L1?'))
  282.         max_dev = 2.0 if approx_ok else 0.015
  283.         if (abs(readback - setpoint) / setpoint) > max_dev:
  284.             raise AssertionError('Failed to set resistance.  Expected ' \
  285.                 '{exp:.3f}, got {rb:.3f}'.format(exp=setpoint, rb=readback))
  286.  
  287.     def get_resistance(self, chan=None):
  288.         """Return static resistance level for channel."""
  289.         if not chan is None:
  290.             self.switch_chan(chan)
  291.  
  292.         return float(self._ask('RES:L1?'))
  293.  
  294.     def set_voltage(self, setpoint, current_limit, chan=None,
  295.                     response_speed='fast'):
  296.         """Set constant voltage mode level."""
  297.         if not chan is None:
  298.             self.switch_chan(chan)
  299.  
  300.         self._write('MODE CV')
  301.  
  302.         self._write('VOLT:L1 {v:.6f}V'.format(v=setpoint))
  303.         readback = float(self._ask('VOLT:L1?'))
  304.         if (readback - setpoint) > 1.0:
  305.             raise AssertionError('Failed to set voltage.  Expected ' \
  306.                 '{exp:.3f}, got {rb:.3f}'.format(exp=setpoint, rb=readback))
  307.        
  308.         self._write('VOLT:MODE {resp:s}'.format(resp=response_speed))
  309.  
  310.         if type(current_limit) is str:
  311.             self._write('VOLT:CURR {lim:s}'.format(lim=current_limit))
  312.         else:
  313.             self._write('VOLT:CURR {lim:.6f}'.format(lim=current_limit))
  314.  
  315.     def set_power(self, setpoint, chan=None, low_range=False,
  316.                        slew_rise=0.0032, slew_fall=0.0032, approx_ok=False):
  317.         """Set static power level for constant power mode.
  318.        Optionally, set a slew rate in Amps/uS."""
  319.         raise NotImplementedError('cp mode is just a modified copy of cr mode')
  320.         if not chan is None:
  321.             self.switch_chan(chan)
  322.  
  323.         if low_range:
  324.             self._write('MODE CPL')
  325.         else:
  326.             self._write('MODE CPH')
  327.  
  328.         self._write('RES:L1 {v:.6f}'.format(v=setpoint))
  329.         # readback = float(self._ask('RES:L1?'))
  330.         # max_dev = 2.0 if approx_ok else 0.015
  331.         # if (abs(readback - setpoint) / setpoint) > max_dev:
  332.         #     raise AssertionError('Failed to set current.  Expected ' \
  333.         #         '{exp:.3f}, got {rb:.3f}'.format(exp=setpoint, rb=readback))
  334.  
  335.     def set_defaults(self):
  336.         """Set instrument to safe defaults."""
  337.         self.deactivate_all()
  338.         self.utility.reset()
  339.  
  340.     def check_errors(self):
  341.         """Make sure no errors are present.  This is ONLY for the current
  342.        channel.  Return a tuple of format (error code, error message).
  343.        error code will be 0 if no error exists."""
  344.         code = int(self._ask('STAT:CHAN:COND?').strip('"'))
  345.         errors = []
  346.         # status[4:0] = {OT, RV, OP, OV, OC}
  347.         if code & 0x01:
  348.             errors.append('Over current')
  349.         if code & 0x02:
  350.             errors.append('Over voltage')
  351.         if code & 0x04:
  352.             errors.append('Over power')
  353.         if code & 0x08:
  354.             errors.append('Reverse voltage on input')
  355.         if code & 0x10:
  356.             errors.append('Over temperature')
  357.         error_str = ', '.join(errors)
  358.  
  359.         try:
  360.             chan = self.get_chan_label()
  361.         except:
  362.             chan = 'Unknown'
  363.  
  364.         msg = 'Channel {chan:s} status 0x{stat:02x}: {msg:s}'. \
  365.               format(chan=chan, stat=code, msg=error_str)
  366.  
  367.         return (code, msg)
  368.  
  369.     def spec_test_start(self, chan=None, mode='voltage',
  370.                   center=0.0, high=0.0, low=0.0):
  371.         """Execute specification test on @chan_num"""
  372.         if not chan is None:
  373.             self.switch_chan(chan)
  374.  
  375.         self._write('SPEC:UNIT VALUE')
  376.         m = mode[0:4].upper()
  377.         # set test limits
  378.         self._write('SPEC:{m:s}:C {v:.6f}'.format(m=m, v=center))
  379.         self._write('SPEC:{m:s}:H {v:.6f}'.format(m=m, v=high))
  380.         self._write('SPEC:{m:s}:L {v:.6f}'.format(m=m, v=low))
  381.         # start test
  382.         self._write('SPEC:TEST ON')
  383.  
  384.     def spec_test_status_all(self):
  385.         """Return GO-NG (True/False) global status for all channels.
  386.        This should return False (NG) if ANY channel's spec test has
  387.        failed."""
  388.         return (self._ask('SPEC?') == '1')
  389.  
  390.     def spec_test_status(self, chan=None, mode='voltage'):
  391.         """Return GO-NG (True/False) status for channel's spec test."""
  392.         if not chan is None:
  393.             self.switch_chan(chan)
  394.         m = mode[0:4].upper()
  395.         res = self._ask('SPEC:{m:s}?'.format(m=m))
  396.         return res == '1'
  397.  
  398.     def spec_test_stop(self, chan=None, mode='voltage'):
  399.         """Stop channel specification test and return True on PASS (GO)
  400.        and False for FAIL (NG)."""
  401.         if not chan is None:
  402.             self.switch_chan(chan)
  403.         # result is only valid BEFORE stopping test
  404.         res = self.spec_test_status()
  405.         self._write('SPEC:TEST OFF')
  406.         return res
  407.  
  408.     def ocp_test(self):
  409.         """In OCP/OPP mode, the load provides a ramped up current or power to
  410.        test if the UUT voltage reaches a trigger voltage level and the
  411.        OCP or OPP protection is operating normally."""
  412.         raise NotImplementedError() #todo - finish implementing this
  413.         # Start current
  414.         self._write('OCP:ISTA 10.25')
  415.         # End current
  416.         self._write('OCP:IEND 20')
  417.         # Step count
  418.         self._write('OCP:STEP 100')
  419.         # Dwell time (ms)
  420.         self._write('OCP:DWELI 100')
  421.         # Trigger voltage (V)
  422.         self._write('OCP:TRIG:VOLT 4.5')
  423.         # Low level current of specification (A)
  424.         self._write('OCP:SPEC:L 11')
  425.         # High level current of specification (A)
  426.         self._write('OCP:SPEC:H 15')
  427.         # Execute the OCP test
  428.         self._write('OCP ON')
  429.         # Read result of OCP test function:
  430.         #   -1: Test stopped
  431.         #   -2: Test Ready to execute, waiting?
  432.         #   -3: Test finished executing
  433.         # <result>,<ocp current>
  434.         # where <result> = 0 for PASS, 1 for FAIL
  435.         self._write('OCP:RES?')
  436.        
  437.     def grab(self):
  438.         """Place instrument into remote control mode."""
  439.         # Note: Making this actually turn it OFF because it was
  440.         # causing broken behavior on some devices.  As far as I can
  441.         # tell there is no harm in doing this other than lack of
  442.         # front panel lockout during the test.
  443.         self._write('CONF:REM OFF')
Add Comment
Please, Sign In to add comment