def add_AO_funcgen_channel(self, ao, name=None, func=None, fsamp=None, amp=None, offset='0V'): """ Adds an analog output funcgen channel (or channels) to the task """ ao_name = "{}/ao{}".format(self.dev.name, ao).encode('ascii') ch_name = 'ao{}'.format(ao) if name is None else name ch_name = ch_name.encode('ascii') self.AOs.append(ch_name) if fsamp is None or amp is None: raise Exception("Must include fsamp, and amp") fsamp_mag = float(Q_(fsamp).to('Hz').magnitude) amp_mag = float(Q_(amp).to('V').magnitude) off_mag = float(Q_(offset).to('V').magnitude) func_map = { 'sin': mx.DAQmx_Val_Sine, 'tri': mx.DAQmx_Val_Triangle, 'squ': mx.DAQmx_Val_Square, 'saw': mx.DAQmx_Val_Sawtooth } func = func_map[func] self.t.CreateAOFuncGenChan(ao_name, ch_name, func, fsamp_mag, amp_mag, off_mag)
def read_AI_scalar(self, timeout=-1.0): if timeout != -1.0: timeout = float(Q_(timeout).to('s').magnitude) value = c_double() self.t.ReadAnalogScalarF64(timeout, byref(value), None) return Q_(value.value, 'V')
def read_AI_channels(self, samples=-1, timeout=-1.0): """ Returns a dict containing the AI buffers. """ samples = int(samples) if timeout != -1.0: timeout = float(Q_(timeout).to('s').magnitude) if samples == -1: buf_size = self.get_buf_size() * len(self.AIs) else: buf_size = samples * len(self.AIs) data = np.zeros(buf_size, dtype=np.float64) num_samples_read = c_int32() self.t.ReadAnalogF64(samples, timeout, mx.DAQmx_Val_GroupByChannel, data, len(data), byref(num_samples_read), None) num_samples_read = num_samples_read.value res = {} for i, ch_name in enumerate(self.AIs): start = i * num_samples_read stop = (i + 1) * num_samples_read res[ch_name] = Q_(data[start:stop], 'V') res['t'] = Q_( np.linspace(0, num_samples_read / self.fsamp, num_samples_read, endpoint=False), 's') return res
def take_measure(COUNTER, POWERMETER, Vthresh, integration_time): ''' Collect counts during integration_time and measures power Input Parameters: COUNTER: frequency counter object POWERMETER: powermeter object Vthres: threshold voltage for frequency counter Returns: (cps, int_power) cps: counts per second power: average optical power during measurement returned as pint.Measurement object ''' with visa_timeout_context(COUNTER._rsrc, 60000): # timeout of 60,000 msec COUNTER.Vthreshold = Q_(Vthresh, 'V') COUNTER.write('INIT') # Initiate couting COUNTER.write('*WAI') if POWERMETER is not None: power = POWERMETER.measure(n_samples=int( integration_time / 0.003)) # each sample about 3ms else: power = Q_(0.0, 'W').plus_minus(Q_(0.0, 'W')) num_counts = float(COUNTER.query('FETC?')) cps = num_counts / integration_time return (cps, power)
def _handle_minmax_AO(self, min_val, max_val): if min_val is None or max_val is None: min_mag, max_mag = self.dev.get_AO_max_range() else: min_mag = Q_(min_val).to('V').magnitude max_mag = Q_(max_val).to('V').magnitude return min_mag, max_mag
def write_sequence(self, data, duration=None, reps=None, fsamp=None, freq=None, onboard=True): """Write an array of samples to the digital output channel Outputs a buffered digital waveform, writing each value in sequence at the rate determined by `duration` and `fsamp` or `freq`. You must specify either `fsamp` or `freq`. This function blocks until the output sequence has completed. Parameters ---------- data : array or list of ints or bools The sequence of samples to output. For a single-line DO channel, samples can be bools. duration : Quantity, optional How long the entirety of the output lasts, specified as a second-compatible Quantity. If `duration` is longer than a single period of data, the waveform will repeat. Use either this or `reps`, not both. If neither is given, waveform is output once. reps : int or float, optional How many times the waveform is repeated. Use either this or `duration`, not both. If neither is given, waveform is output once. fsamp: Quantity, optional This is the sample frequency, specified as a Hz-compatible Quantity. Use either this or `freq`, not both. freq : Quantity, optional This is the frequency of the *overall waveform*, specified as a Hz-compatible Quantity. Use either this or `fsamp`, not both. onboard : bool, optional Use only onboard memory. Defaults to True. If False, all data will be continually buffered from the PC memory, even if it is only repeating a small number of samples many times. """ if (fsamp is None) == (freq is None): raise Exception("Need one and only one of 'fsamp' or 'freq'") if fsamp is None: fsamp = Q_(freq) * len(data) else: fsamp = Q_(fsamp) if (duration is not None) and (reps is not None): raise Exception( "Can use at most one of `duration` or `reps`, not both") if duration is None: duration = (reps or 1) * len(data) / fsamp fsamp, n_samples = _handle_timing_params(duration, fsamp, len(data)) with self.dev.create_task() as t: t.add_DO_channel(self._get_name()) t.set_DO_only_onboard_mem(self._get_name(), onboard) t.config_timing(fsamp, n_samples, clock='') t.write_DO_channels({self.name: data}, [self]) t.t.WaitUntilTaskDone(-1)
def set_low(self, low, channel=1): """ Set the low voltage level of the current waveform. This changes the low level while keeping the high level fixed. Parameters ---------- low : pint.Quantity The new low level in volt-compatible units """ low = Q_(low) mag = low.to('V').magnitude self.inst.write('source{}:voltage:low {}V'.format(channel, mag))
def set_offset(self, offset, channel=1): """ Set the voltage offset of the current waveform. This changes the offset while keeping the amplitude fixed. Parameters ---------- offset : pint.Quantity The new voltage offset in volt-compatible units """ offset = Q_(offset) mag = offset.to('V').magnitude self.inst.write('source{}:voltage:offset {}V'.format(channel, mag))
def bring_down_from_breakdown(SOURCEMETER, Vbd): Vstep = Q_(5.0, 'V') Vinit = Vbd - Vstep while (Vinit > Q_(0, 'V')): # SOURCEMETER.write(':SOUR1:VOLT {}'.format(Vinit)) # SOURCEMETER.write(':OUTP ON') SOURCEMETER.set_voltage(Vinit) Vinit = Vinit - Vstep time.sleep(0.5) SOURCEMETER.set_voltage(Q_(0, 'V')) print('Sourcemeter at 0V')
def _handle_timing_params(duration, fsamp, n_samples): if duration: duration = Q_(duration).to('s') if fsamp: fsamp = Q_(fsamp).to('Hz') n_samples = int((duration*fsamp).to('')) # Exclude endpoint else: n_samples = int(n_samples or 1000.) fsamp = n_samples / duration elif fsamp: fsamp = Q_(fsamp).to('Hz') n_samples = int(n_samples or 1000.) return fsamp, n_samples
def set_high(self, high, channel=1): """ Set the high voltage level of the current waveform. This changes the high level while keeping the low level fixed. Parameters ---------- high : pint.Quantity The new high level in volt-compatible units """ high = Q_(high) mag = high.to('V').magnitude self.inst.write('source{}:voltage:high {}V'.format(channel, mag))
def output_pulses(self, freq, duration=None, reps=None, idle_high=False, delay=None, duty_cycle=0.5): """Generate digital pulses using the counter. Outputs digital pulses with a given frequency and duty cycle. This function blocks until the output sequence has completed. Parameters ---------- freq : Quantity This is the frequency of the pulses, specified as a Hz-compatible Quantity. duration : Quantity, optional How long the entirety of the output lasts, specified as a second-compatible Quantity. Use either this or `reps`, not both. If neither is given, only one pulse is generated. reps : int, optional How many pulses to generate. Use either this or `duration`, not both. If neither is given, only one pulse is generated. idle_high : bool, optional Whether the resting state is considered high or low. Idles low by default. delay : Quantity, optional How long to wait before generating the first pulse, specified as a second-compatible Quantity. Defaults to zero. duty_cycle : float, optional The width of the pulse divided by the pulse period. The default is a 50% duty cycle. """ idle_state = mx.High if idle_high else mx.Low delay = 0 if delay is None else Q_(delay).to('s').magnitude freq = Q_(freq).to('Hz').magnitude if (duration is not None) and (reps is not None): raise Exception( "Can use at most one of `duration` or `reps`, not both") if reps is None: if duration is None: reps = 1 else: reps = int(Q_(duration).to('s').magnitude * freq) with self.dev.create_task() as t: t.t.CreateCOPulseChanFreq(self.fullname, None, mx.Hz, idle_state, delay, freq, duty_cycle) t.t.CfgImplicitTiming(mx.FiniteSamps, reps) t.t.StartTask() t.t.WaitUntilTaskDone(-1)
def set_phase(self, phase, channel=1): """ Set the phase offset of the current waveform. Parameters ---------- phase : pint.Quantity or number The new low level in radian-compatible units. Unitless numbers are treated as radians. """ phase = Q_(phase) # This also accepts dimensionless numbers as rads if phase < -u.pi or phase > +u.pi: raise Exception("Phase out of range. Must be between -pi and +pi") mag = phase.to('rad').magnitude self.inst.write('source{}:phase {}rad'.format(channel, mag))
def bring_to_breakdown(SOURCEMETER, Vbd): Vinit = Q_(0, 'V') Vstep = Q_(5.0, 'V') while (Vinit < Vbd): # SOURCEMETER.write(':SOUR1:VOLT {}'.format(Vinit)) # SOURCEMETER.write(':OUTP ON') SOURCEMETER.set_voltage(Vinit) Vinit = Vinit + Vstep time.sleep(0.5) SOURCEMETER.set_voltage(Vbd) time.sleep(5.0) print('Sourcemeter at breakdown voltage {}'.format(Vbd))
def sweep_end_frequency(self, val=None): """ Sets the end frequency of a sweep. Sets the ending frequency for subsequent sweeps. If no value is specified, the current ending frequency setting is returned. Parameters ---------- val : float, optional The ending value of the frequency sweep in THz. Step: 0.00001 (THz) Returns ------- Quantity The current sweep end frequency in THz, and its units. >>> laser.sweep_end_frequency() <Quantity(183.92175, 'terahertz')> >>> laser.sweep_end_frequency(185.5447) <Quantity(185.5447, 'terahertz')> """ return Q_(float(self._set_var("FF", 5, val)), 'THz')
def sweep_end_wavelength(self, val=None): """ Sets the end wavelength of a sweep. Sets the ending wavelength for subsequent sweeps. If no value is specified, the current ending wavelength setting is returned. Parameters ---------- val : float, optional The ending value of the wavelength sweep in nanometers. Step: 0.0001 (nm) Returns ------- Quantity The current sweep end wavelength in nm, and its units. >>> laser.sweep_end_wavelength() <Quantity(1630.0, 'nanometer')> >>> laser.sweep_end_wavelength(1618) <Quantity(1618.0, 'nanometer')> """ return Q_(float(self._set_var("SE", 4, val)), 'nm')
def test(): ps = MyPowerSupply() ps.voltage = '12V' assert ps.voltage == Q_(12, 'volts') with pytest.raises(DimensionalityError): ps.voltage = '200 mA'
def sweep_start_frequency(self, val=None): """ Sets the start frequency of a sweep. Sets the starting frequency for subsequent sweeps. If no value is specified, the current starting frequency setting is returned. Parameters ---------- val : float, optional The starting value of the frequency sweep in terahertz. Step: 0.00001 (THz) Returns ------- Quantity The current sweep start frequency in THz, and its units. >>> laser.sweep_start_frequency() <Quantity(199.86164, 'terahertz')> >>> laser.sweep_start_frequency(196) <Quantity(195.99999, 'terahertz')> """ return Q_(float(self._set_var("FS", 5, val)), 'THz')
def wavelength(self, val=None): """ Sets the output wavelength. If a value is not specified, returns the currently set wavelength. Parameters ---------- val : float, optional The wavelength the laser will be set to in nanometers. Step: 0.0001 (nm) Returns ------- Quantity The currently set wavelength in nanometers, and its units. You can get the current wavelength by calling without arguments. >>> laser.wavelength() <Quantity(1630.0, 'nanometer')> The following sets the output wavelength to 1560.123 nanometers. >>> laser.wavelength(1560.123) <Quantity(1630.0, 'nanometer')> # Bug returns the last set wavelength """ return Q_(float(self._set_var("WA", 4, val)), 'nm')
def sweep_step_frequency(self, val=None): """ Set the size of each step in the stepwise sweep when constant frequency intervals are enabled. If a new value is not provided, the current one will be returned. Units: THz Parameters ---------- val : float, optional The step size of each step in the stepwise sweep in terahertz. Range: 0.00002 - 19.76219 (THz) Step: 0.00001 (THz) Returns ------- Quantity The set step size in THz, and its units. >>> laser.sweep_step_frequency() <Quantity(0.1, 'terahertz')> >>> laser.sweep_step_frequency(0.24) <Quantity(0.24, 'terahertz')> """ return Q_(float(self._set_var("WF", 5, val)), 'THz')
def read_measurement_stats(self, num): """ Read the value and statistics of a measurement. Parameters ---------- num : int Number of the measurement to read from, from 1-4 Returns ------- stats : dict Dictionary of measurement statistics. Includes value, mean, stddev, minimum, maximum, and nsamps. """ prefix = 'measurement:meas{}'.format(num) if not self.are_measurement_stats_on(): raise Exception("Measurement statistics are turned off, " "please turn them on.") # Potential issue: If we query for all of these values in one command, # are they guaranteed to be taken from the same statistical set? # Perhaps we should stop_acquire(), then run_acquire()... keys = ['value', 'mean', 'stddev', 'minimum', 'maximum'] res = self.inst.query( prefix + ':value?;mean?;stddev?;minimum?;maximum?;units?').split(';') units = res.pop(-1).strip('"') stats = {k: Q_(rval + units) for k, rval in zip(keys, res)} num_samples = int(self.inst.query('measurement:statistics:weighting?')) stats['nsamps'] = num_samples return stats
def write_AO_channels(self, data, timeout=-1.0, autostart=True): if timeout != -1.0: timeout = float(Q_(timeout).to('s').magnitude) arr = np.concatenate([data[ao].to('V').magnitude for ao in self.AOs]).astype(np.float64) samples = data.values()[0].magnitude.size samples_written = c_int32() self.t.WriteAnalogF64(samples, autostart, timeout, mx.DAQmx_Val_GroupByChannel, arr, byref(samples_written), None)
def _write_AO_channels(self, data): task = self._mxtasks['AO'] ao_names = [name for (name, ch) in self.channels.items() if ch.type == 'AO'] arr = np.concatenate([Q_(data[ao]).to('V').magnitude for ao in ao_names]) arr = arr.astype(np.float64) samples = data.values()[0].magnitude.size samples_written = c_int32() task.WriteAnalogF64(samples, False, -1.0, mx.DAQmx_Val_GroupByChannel, arr, byref(samples_written), None)
def config_analog_trigger(self, source, trig_level, rising=True, pretrig_samples=2): source_name = self._handle_ai(source) level_mag = float(Q_(trig_level).to('V').magnitude) slope = mx.DAQmx_Val_RisingSlope if rising else mx.DAQmx_Val_FallingSlope self.t.CfgAnlgEdgeStartTrig(source_name, slope, level_mag, pretrig_samples)
def write_DO_channels(self, data, channels, timeout=-1.0, autostart=True): if timeout != -1.0: timeout = float(Q_(timeout).to('s').magnitude) arr = self._make_DO_array(data, channels) n_samples = arr.size n_samples_written = c_int32() self.t.WriteDigitalU32(n_samples, autostart, timeout, mx.DAQmx_Val_GroupByChannel, arr, byref(n_samples_written), None)
def _read_AI_channels(self): """ Returns a dict containing the AI buffers. """ task = self._mxtasks['AI'] bufsize_per_chan = c_uint32() task.GetBufInputBufSize(byref(bufsize_per_chan)) buf_size = bufsize_per_chan.value * len(self.AIs) data = np.zeros(buf_size, dtype=np.float64) num_samples_read = c_int32() task.ReadAnalogF64(-1, -1.0, mx.DAQmx_Val_GroupByChannel, data, len(data), byref(num_samples_read), None) num_samples_read = num_samples_read.value res = {} for i, ch in enumerate(self.AIs): start = i*num_samples_read stop = (i+1)*num_samples_read res[ch.name] = Q_(data[start:stop], 'V') res['t'] = Q_(np.linspace(0, float(num_samples_read-1)/self.fsamp, num_samples_read), 's') return res
def set_sweep_center(self, center, channel=1): """ Set the sweep frequency center. This sets the sweep center frequency while keeping the sweep frequency span fixed. The start and stop frequencies will be changed. Parameters ---------- center : pint.Quantity The center frequency of the sweep in Hz-compatible units """ val = Q_(center).to('Hz').magnitude self.inst.write('source{}:freq:center {}Hz'.format(channel, val))
def set_sweep_hold_time(self, time, channel=1): """ Set the hold time of the sweep. The hold time is the amount of time that the frequency is held constant after reaching the stop frequency. Parameters ---------- time : pint.Quantity The hold time in second-compatible units """ val = Q_(time).to('s').magnitude self.inst.write('source{}:sweep:htime {}s'.format(channel, val))
def set_am_depth(self, depth, channel=1): """ Set depth of amplitude modulation. Parameters ---------- depth : number Depth of modulation in percent. Must be between 0.0% and 120.0%. Has resolution of 0.1%. """ val = Q_(depth).magnitude if not (0.0 <= val <= 120.0): raise Exception("Depth must be between 0.0 and 120.0") self.inst.write('source{}:am:depth {:.1f}pct'.format(channel, val))
def tune_SHG(lm, wait=False, n_temp_samples=10, temp_timeout=30 * u.minute, temp_max_err=Q_(0.05, u.degK)): shg_data = load_newest_SHG_calibration() poling_region = get_poling_region(lm) if poling_region: T_set = Q_( np.asscalar(shg_data[poling_region]['phase_match_model'](lm)), u.degC) print('tuning SHG:') print('\tpoling region: {}, LM={:2.4f}um'.format( current_poling_region, LM_cov[current_poling_region].magnitude)) print('\tset temperature: {:4.1f}C'.format(T_set.magnitude)) if wait: oc.set_temp_and_wait(T_set, max_err=temp_max_err, n_samples=n_temp_samples, timeout=temp_timeout) else: oc.set_set_temp(T_set)
def _set_amplitude(self, val, units, channel): val = Q_(val) mag = val.to('V').magnitude self.inst.write('source{}:voltage {}{}'.format(channel, mag, units))