def transition_to_buffered(self, device_name, h5file, initial_values, fresh): self.h5file = h5file # We'll need this in transition_to_manual self.device_name = device_name with h5py.File(h5file, 'r') as hdf5_file: print("\nUsing "+h5file) self.atsparam = atsparam = labscript_utils.properties.get( hdf5_file, device_name, 'device_properties') #print("atsparam: " + repr(self.atsparam)) clock_source_id = atsparam['clock_source_id'] requested_acquisition_rate = atsparam['requested_acquisition_rate'] clock_edge_id = atsparam['clock_edge_id'] if clock_source_id == ats.INTERNAL_CLOCK: # Actually we should find smallest internal clock faster than the one asked for. Next time. actual_acquisition_rate = find_nearest_internal_clock( atsSampleRates.keys(), requested_acquisition_rate) # This is an ID not a sample per sec. It takes both. atsSamplesPerSec_or_id = atsSampleRates[actual_acquisition_rate] decimation = 0 # Must be zero for internal clocking clock_edge_id = ats.CLOCK_EDGE_RISING print('Internal clocking at {:.0f} samples per second ({:.1f} MS/s), from internal reference.'. format(actual_acquisition_rate, actual_acquisition_rate/1e6)) elif clock_source_id == ats.EXTERNAL_CLOCK_10MHz_REF: atsSamplesPerSec_or_id, divisor = ats9462_clock( requested_acquisition_rate) actual_acquisition_rate = atsSamplesPerSec_or_id // divisor decimation = divisor - 1 clock_edge_id = ats.CLOCK_EDGE_RISING print('Internally clock at {:.0f} samples per second ({:.1f} MS/s), from external 10MHz reference ({:d}MHz PLL divided by {:d}).'. format(actual_acquisition_rate, actual_acquisition_rate/1e6, atsSamplesPerSec_or_id//1000000, divisor)) elif clock_source_id == ats.FAST_EXTERNAL_CLOCK: raise LabscriptError( "Requested capture clock type FAST_EXTERNAL_CLOCK is not implemented") elif clock_source_id == ats.MEDIUM_EXTERNAL_CLOCK: raise LabscriptError( "Requested capture clock type MEDIUM_EXTERNAL_CLOCK is not implemented") elif clock_source_id == ats.SLOW_EXTERNAL_CLOCK: raise LabscriptError( "Requested capture clock type SLOW_EXTERNAL_CLOCK is not implemented") elif clock_source_id == ats.EXTERNAL_CLOCK_AC: raise LabscriptError( "Requested capture clock type EXTERNAL_CLOCK_AC is not implemented") elif clock_source_id == ats.EXTERNAL_CLOCK_DC: raise LabscriptError( "Requested capture clock type EXTERNAL_CLOCK_DC is not implemented") else: raise LabscriptError("Requested capture clock type with code {:d} is not recognised".format( atsparam['clock_source_id'])) # The clock_edge_id parameter is not needed for INTERNAL_CLOCK and EXTERNAL_CLOCK_10MHz_REF modes but is here for future extension try: self.board.setCaptureClock( atsparam['clock_source_id'], atsSamplesPerSec_or_id, clock_edge_id, decimation) except ats.AlazarException as e: errstring, funcname, arguments, retCode, retText = e.args if retText == 'ApiPllNotLocked': print("Error: PLL not locked! ") try: print("Error: For this {:s} board, the ext reference should be {:s}".format(self.board_name, atsExternalClockAdvice[self.board_name])) except KeyError: print("Error: I don't have any advice for you on clocking the {:s} board".format( self.board_name)) raise ats.AlazarException(e) # Store the actual acquisition rate back as an attribute. # Again, this should be done as an ACQUISITIONS table entry, but not today with h5py.File(h5file, 'r+') as hdf5_file: hdf5_file['devices'][device_name].attrs.create( 'acquisition_rate', actual_acquisition_rate, dtype='int32') # ETR_5V means +/-5V, and is 8bit # So code 150 means (150-128)/128 * 5V = 860mV. self.board.setExternalTrigger( atsparam['exttrig_coupling_id'], atsparam['exttrig_range_id']) print("Trigger coupling_id: {:d}, range_id: {:d}.".format( atsparam['exttrig_coupling_id'], atsparam['exttrig_range_id'])) self.board.setTriggerOperation(atsparam['trig_operation'], atsparam['trig_engine_id1'], atsparam['trig_source_id1'], atsparam[ 'trig_slope_id1'], atsparam['trig_level_id1'], atsparam['trig_engine_id2'], atsparam['trig_source_id2'], atsparam['trig_slope_id2'], atsparam['trig_level_id2']) print("Trigger operation set to operation: {:d}".format( atsparam['trig_operation'])) print("Trigger engine 1 set to {:d}, source: {:d}, slope: {:d}, level: {:d}.".format( atsparam['trig_engine_id1'], atsparam['trig_source_id1'], atsparam['trig_slope_id1'], atsparam['trig_level_id1'])) print("Trigger engine 2 set to {:d}, source: {:d}, slope: {:d}, level: {:d}.".format( atsparam['trig_engine_id2'], atsparam['trig_source_id2'], atsparam['trig_slope_id2'], atsparam['trig_level_id2'])) # We will deal with trigger delays in labscript! triggerDelay_sec = 0 triggerDelay_samples = int( triggerDelay_sec * actual_acquisition_rate + 0.5) self.board.setTriggerDelay(0) # NOTE: The board will wait for a for this amount of time for a trigger event. If a trigger event does not arrive, then the # board will automatically trigger. Set the trigger timeout value to 0 to force the board to wait forever for a trigger event. # LDT: We'll leave this set to zero for now. We timeout on the readout, not on the trigger. # But we should probably check if we ever got a trigger! self.board.setTriggerTimeOut(0) #print("Trigger timeout set to infinity") # Configure AUX I/O connector. # By default this emits the sample clock; not sure if this is before or after decimation # Second param is a dummy value when AUX_OUT_TRIGGER self.board.configureAuxIO(ats.AUX_OUT_TRIGGER, 0) #print("Aux output set to sample clock.") try: chA_range_id = atsRanges[atsparam['chA_input_range']] except KeyError: print("Voltage setting {:d}mV for Channel A is not recognised in atsapi. Make sure you use millivolts.".format( atsparam['chA_input_range'])) self.board.inputControl( ats.CHANNEL_A, atsparam['chA_coupling_id'], chA_range_id, atsparam['chA_impedance_id']) self.board.setBWLimit(ats.CHANNEL_A, atsparam['chA_bw_limit']) print("Channel A input full scale: {:d}, coupling: {:d}, impedance: {:d}, bandwidth limit: {:d}.".format( atsparam['chA_input_range'], atsparam['chA_coupling_id'], atsparam['chA_impedance_id'], atsparam['chA_bw_limit'])) try: chB_range_id = atsRanges[atsparam['chB_input_range']] except KeyError: print("Voltage setting {:d}mV for Channel B is not recognised in atsapi. Make sure you use millivolts.".format( atsparam['chB_input_range'])) self.board.inputControl( ats.CHANNEL_B, atsparam['chB_coupling_id'], chB_range_id, atsparam['chB_impedance_id']) self.board.setBWLimit(ats.CHANNEL_B, atsparam['chB_bw_limit']) print("Channel B input full scale: {:d}, coupling: {:d}, impedance: {:d}, bandwidth limit: {:d}.".format( atsparam['chB_input_range'], atsparam['chB_coupling_id'], atsparam['chB_impedance_id'], atsparam['chB_bw_limit'])) # ====== Acquisition code starts here ===== # This is a magic number and should at the very least move up self.samplesPerBuffer = 204800 self.oneM = 2**20 # This should be determined by experiment run time. self.timeout = 60000 # Check which channels we are acquiring #channels = ats.CHANNEL_A | ats.CHANNEL_B self.channels = atsparam['channels'] if not (self.channels & ats.CHANNEL_A or self.channels & ats.CHANNEL_B): raise LabscriptError( "You must select either Channel-A or Channel-B, or both. Zero or >2 channels not supported.") self.channelCount = 0 for c in ats.channels: self.channelCount += (c & self.channels == c) # Compute the number of bytes per record and per buffer memorySize_samples, self.bitsPerSample = self.board.getChannelInfo() self.bytesPerDatum = (self.bitsPerSample + 7) // 8 # One 'sample' is one datum from each channel print("bytesPerDatum = {:d}. channelcount = {:d}.".format( self.bytesPerDatum, self.channelCount)) self.bytesPerBuffer = self.bytesPerDatum * \ self.channelCount * self.samplesPerBuffer # Calculate the number of buffers in the acquisition self.samplesPerAcquisition = int( actual_acquisition_rate * atsparam['acquisition_duration'] + 0.5) memoryPerAcquisition = self.bytesPerDatum * \ self.samplesPerAcquisition * self.channelCount self.buffersPerAcquisition = ((self.samplesPerAcquisition + self.samplesPerBuffer - 1) // self.samplesPerBuffer) print('Acquiring for {:5.3f}s generates {:5.3f} MS ({:5.3f} MB total)'.format( atsparam['acquisition_duration'], self.samplesPerAcquisition/1e6, memoryPerAcquisition/self.oneM)) print('Buffers are {:5.3f} MS and {:d} bytes. Allocating {:d} buffers... '.format( self.samplesPerBuffer/1e6, self.bytesPerBuffer, self.buffersPerAcquisition), end='') self.board.setRecordSize(0, self.samplesPerBuffer) # Allocate buffers # We know that disk can't keep up, so we preallocate all buffers sample_type = ctypes.c_uint16 # It's 16bit, let's not stuff around self.buffers = [] for i in range(self.buffersPerAcquisition): self.buffers.append(ats.DMABuffer( sample_type, self.bytesPerBuffer)) #print('{:d} '.format(i),end="") print('done.') # This works but ADMA_ALLOC_BUFFERS is questionable because we have allocated the buffers (well atsapi.py buffer class has) acqflags = ats.ADMA_TRIGGERED_STREAMING | ats.ADMA_ALLOC_BUFFERS | ats.ADMA_FIFO_ONLY_STREAMING #print("Acqflags in decimal: {:d}".format(acqflags)) # This does not actually start the capture, it just sets it up self.board.beforeAsyncRead(self.channels, 0, # Trig offset, must be 0 self.samplesPerBuffer, 1, # Must be 1 0x7FFFFFFF, # Ignored acqflags) self.acquisition_queue.put('start') return {} # ? Check this
def generate_code(self, hdf5_file): '''Automatically called by compiler to write acquisition instructions to h5 file. Configures counters, analog and digital acquisitions.''' Device.generate_code(self, hdf5_file) trans = {'pulse': 'PUL', 'edge': 'EDG', 'pos': 'P', 'neg': 'N'} acqs = {'ANALOG': [], 'POD1': [], 'POD2': []} for channel in self.child_devices: if channel.acquisitions: # make sure channel is allowed if channel.connection in self.allowed_analog_chan: acqs['ANALOG'].append( (channel.connection, channel.acquisitions[0]['label'])) elif channel.connection in self.allowed_pod1_chan: acqs['POD1'].append( (channel.connection, channel.acquisitions[0]['label'])) elif channel.connection in self.allowed_pod2_chan: acqs['POD2'].append( (channel.connection, channel.acquisitions[0]['label'])) else: raise LabscriptError( '{0:s} is not a valid channel.'.format( channel.connection)) acquisition_table_dtypes = np.dtype({ 'names': ['connection', 'label'], 'formats': ['a256', 'a256'] }) grp = self.init_device_group(hdf5_file) # write tables if non-empty to h5_file for acq_group, acq_chan in acqs.items(): if len(acq_chan): table = np.empty(len(acq_chan), dtype=acquisition_table_dtypes) for i, acq in enumerate(acq_chan): table[i] = acq grp.create_dataset(acq_group + '_ACQUISITIONS', compression=config.compression, data=table) grp[acq_group + '_ACQUISITIONS'].attrs['trigger_time'] = self.trigger_time # now do the counters counts = [] for channel in self.child_devices: if hasattr(channel, 'counts'): for counter in channel.counts: counts.append((channel.connection, trans[counter['type']], trans[counter['polarity']])) counts_table_dtypes = np.dtype({ 'names': ['connection', 'type', 'polarity'], 'formats': ['a256', 'a256', 'a256'] }) counts_table = np.empty(len(counts), dtype=counts_table_dtypes) for i, count in enumerate(counts): counts_table[i] = count if len(counts_table): grp.create_dataset('COUNTERS', compression=config.compression, data=counts_table) grp['COUNTERS'].attrs['trigger_time'] = self.trigger_time
def convert_to_pb_inst(self, dig_outputs, dds_outputs, freqs, amps, phases): pb_inst = [] # An array for storing the line numbers of the instructions at # which the slow clock ticks: slow_clock_indices = [] # index to keep track of where in output.raw_output the # pulseblaster flags are coming from i = 0 # index to record what line number of the pulseblaster hardware # instructions we're up to: j = 0 # We've delegated the initial two instructions off to BLACS, which # can ensure continuity with the state of the front panel. Thus # these two instructions don't actually do anything: flags = [0]*self.n_flags freqregs = [0]*2 ampregs = [0]*2 phaseregs = [0]*2 dds_enables = [0]*2 if self.fast_clock_flag is not None: for fast_flag in self.fast_clock_flag: flags[fast_flag] = 0 if self.slow_clock_flag is not None: for slow_flag in self.slow_clock_flag: flags[slow_flag] = 0 pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'flags': ''.join([str(flag) for flag in flags]), 'instruction': 'STOP', 'data': 0, 'delay': 10.0/self.clock_limit*1e9}) pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'flags': ''.join([str(flag) for flag in flags]), 'instruction': 'STOP', 'data': 0, 'delay': 10.0/self.clock_limit*1e9}) j += 2 flagstring = '0'*self.n_flags # So that this variable is still defined if the for loop has no iterations for k, instruction in enumerate(self.clock): if instruction == 'WAIT': # This is a wait instruction. Repeat the last instruction but with a 100ns delay and a WAIT op code: wait_instruction = pb_inst[-1].copy() wait_instruction['delay'] = 100 wait_instruction['instruction'] = 'WAIT' wait_instruction['data'] = 0 pb_inst.append(wait_instruction) j += 1 continue flags = [0]*self.n_flags # The registers below are ones, not zeros, so that we don't # use the BLACS-inserted initial instructions. Instead # unused DDSs have a 'zero' in register one for freq, amp # and phase. freqregs = [1]*2 ampregs = [1]*2 phaseregs = [1]*2 dds_enables = [0]*2 for output in dig_outputs: flagindex = int(output.connection.split()[1]) flags[flagindex] = int(output.raw_output[i]) for output in dds_outputs: ddsnumber = int(output.connection.split()[1]) freqregs[ddsnumber] = freqs[ddsnumber][output.frequency.raw_output[i]] ampregs[ddsnumber] = amps[ddsnumber][output.amplitude.raw_output[i]] phaseregs[ddsnumber] = phases[ddsnumber][output.phase.raw_output[i]] dds_enables[ddsnumber] = output.gate.raw_output[i] if self.fast_clock_flag is not None: for fast_flag in self.fast_clock_flag: if (type(instruction['fast_clock']) == list and 'flag %d'%fast_flag in instruction['fast_clock']) or instruction['fast_clock'] == 'all': flags[fast_flag] = 1 else: flags[fast_flag] = 1 if instruction['slow_clock_tick'] else 0 if self.slow_clock_flag is not None: for slow_flag in self.slow_clock_flag: flags[slow_flag] = 1 if instruction['slow_clock_tick'] else 0 if instruction['slow_clock_tick']: slow_clock_indices.append(j) flagstring = ''.join([str(flag) for flag in flags]) if instruction['reps'] > 1048576: raise LabscriptError('Pulseblaster cannot support more than 1048576 loop iterations. ' + str(instruction['reps']) +' were requested at t = ' + str(instruction['start']) + '. '+ 'This can be fixed easily enough by using nested loops. If it is needed, ' + 'please file a feature request at' + 'http://redmine.physics.monash.edu.au/projects/labscript.') # Instruction delays > 55 secs will require a LONG_DELAY # to be inserted. How many times does the delay of the # loop/endloop instructions go into 55 secs? if self.has_clocks: quotient, remainder = divmod(instruction['step']/2.0,55.0) else: quotient, remainder = divmod(instruction['step'],55.0) if quotient and remainder < 100e-9: # The remainder will be used for the total duration of the LOOP and END_LOOP instructions. # It must not be too short for this, if it is, take one LONG_DELAY iteration and give # its duration to the loop instructions: quotient, remainder = quotient - 1, remainder + 55.0 if self.has_clocks: # The loop and endloop instructions will only use the remainder: pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'flags': flagstring, 'instruction': 'LOOP', 'data': instruction['reps'], 'delay': remainder*1e9}) if self.fast_clock_flag is not None: for fast_flag in self.fast_clock_flag: flags[fast_flag] = 0 if self.slow_clock_flag is not None: for slow_flag in self.slow_clock_flag: flags[slow_flag] = 0 flagstring = ''.join([str(flag) for flag in flags]) # If there was a nonzero quotient, let's wait twice that # many multiples of 55 seconds (one multiple of 55 seconds # for each of the other two loop and endloop instructions): if quotient: pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'flags': flagstring, 'instruction': 'LONG_DELAY', 'data': int(2*quotient), 'delay': 55*1e9}) pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'flags': flagstring, 'instruction': 'END_LOOP', 'data': j, 'delay': remainder*1e9}) # Two instructions were used in the case of there being no LONG_DELAY, # otherwise three. This increment is done here so that the j referred # to in the previous line still refers to the LOOP instruction. j += 3 if quotient else 2 else: # The loop and endloop instructions will only use the remainder: pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'flags': flagstring, 'instruction': 'CONTINUE', 'data': 0, 'delay': remainder*1e9}) # If there was a nonzero quotient, let's wait that many multiples of 55 seconds: if quotient: pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'flags': flagstring, 'instruction': 'LONG_DELAY', 'data': int(quotient), 'delay': 55*1e9}) j += 2 if quotient else 1 try: if self.clock[k+1] == 'WAIT' or self.clock[k+1]['slow_clock_tick']: i += 1 except IndexError: pass # This is how we stop the pulse program. We branch from the last # instruction to the zeroth, which BLACS has programmed in with # the same values and a WAIT instruction. The PulseBlaster then # waits on instuction zero, which is a state ready for either # further static updates or buffered mode. pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'flags': flagstring, 'instruction': 'BRANCH', 'data': 0, 'delay': 10.0/self.clock_limit*1e9}) return pb_inst, slow_clock_indices
def setphase(self, value, units=None): raise LabscriptError('QuickSyn does not support phase control')
def generate_code(self, hdf5_file): DDSs = {} for output in self.child_devices: # Check that the instructions will fit into RAM: if isinstance(output, DDS) and len( output.frequency.raw_output ) > 16384 - 2: # -2 to include space for dummy instructions raise LabscriptError( '%s can only support 16383 instructions. ' % self.name + 'Please decrease the sample rates of devices on the same clock, ' + 'or connect %s to a different pseudoclock.' % self.name) try: prefix, channel = output.connection.split() channel = int(channel) except: raise LabscriptError( '%s %s has invalid connection string: \'%s\'. ' % (output.description, output.name, str(output.connection)) + 'Format must be \'channel n\' with n from 0 to 4.') DDSs[channel] = output for connection in DDSs: if connection in range(4): # Dynamic DDS dds = DDSs[connection] dds.frequency.raw_output, dds.frequency.scale_factor = self.quantise_freq( dds.frequency.raw_output, dds) dds.phase.raw_output, dds.phase.scale_factor = self.quantise_phase( dds.phase.raw_output, dds) dds.amplitude.raw_output, dds.amplitude.scale_factor = self.quantise_amp( dds.amplitude.raw_output, dds) # elif connection in range(2,4): # # StaticDDS: # dds = DDSs[connection] # dds.frequency.raw_output, dds.frequency.scale_factor = self.quantise_freq(dds.frequency.static_value, dds) # dds.phase.raw_output, dds.phase.scale_factor = self.quantise_phase(dds.phase.static_value, dds) # dds.amplitude.raw_output, dds.amplitude.scale_factor = self.quantise_amp(dds.amplitude.static_value, dds) else: raise LabscriptError( '%s %s has invalid connection string: \'%s\'. ' % (dds.description, dds.name, str(dds.connection)) + 'Format must be \'channel n\' with n from 0 to 4.') dtypes = [('freq%d'%i,np.uint32) for i in range(2)] + \ [('phase%d'%i,np.uint16) for i in range(2)] + \ [('amp%d'%i,np.uint16) for i in range(2)] static_dtypes = [('freq%d'%i,np.uint32) for i in range(2,4)] + \ [('phase%d'%i,np.uint16) for i in range(2,4)] + \ [('amp%d'%i,np.uint16) for i in range(2,4)] clockline = self.parent_clock_line pseudoclock = clockline.parent_device times = pseudoclock.times[clockline] out_table = np.zeros(len(times), dtype=dtypes) out_table['freq0'].fill(1) out_table['freq1'].fill(1) static_table = np.zeros(1, dtype=static_dtypes) static_table['freq2'].fill(1) static_table['freq3'].fill(1) for connection in range(2): if not connection in DDSs: continue dds = DDSs[connection] # The last two instructions are left blank, for BLACS # to fill in at program time. out_table['freq%d' % connection][:] = dds.frequency.raw_output out_table['amp%d' % connection][:] = dds.amplitude.raw_output out_table['phase%d' % connection][:] = dds.phase.raw_output for connection in range(2, 4): if not connection in DDSs: continue dds = DDSs[connection] static_table['freq%d' % connection] = dds.frequency.raw_output[0] static_table['amp%d' % connection] = dds.amplitude.raw_output[0] static_table['phase%d' % connection] = dds.phase.raw_output[0] if self.update_mode == 'asynchronous' or self.synchronous_first_line_repeat: # Duplicate the first line of the table. Otherwise, we are one step # ahead in the table from the start of a run. In asynchronous # updating mode, this is necessary since the first line of the # table is already being output before the first trigger from # the master clock. When using a simple delay line for synchronous # output, this also seems to be required, in which case # synchronous_first_line_repeat should be set to True. # However, when a tristate driver is used as described at # http://labscriptsuite.org/blog/implementation-of-the-novatech-dds9m/ # then is is not neccesary to duplicate the first line. Use of a # tristate driver in this way is the correct way to use # the novatech DDS, as per its instruction manual, and so is likely # to be the most reliable. However, through trial and error we've # determined that duplicating the first line like this gives correct # output in asynchronous mode and in synchronous mode when using a # simple delay line, at least for the specific device we tested. # Your milage may vary. out_table = np.concatenate([out_table[0:1], out_table]) grp = self.init_device_group(hdf5_file) grp.create_dataset('TABLE_DATA', compression=config.compression, data=out_table) grp.create_dataset('STATIC_DATA', compression=config.compression, data=static_table) self.set_property('frequency_scale_factor', 10, location='device_properties') self.set_property('amplitude_scale_factor', 1023, location='device_properties') self.set_property('phase_scale_factor', 45.511111111111113, location='device_properties')
def _generate_code(self, hdf5_file): IntermediateDevice.generate_code(self, hdf5_file) analogs = {} digitals = {} inputs = {} for device in self.child_devices: if isinstance(device, AnalogOut): analogs[device.connection] = device elif isinstance(device, DigitalOut): digitals[device.connection] = device elif isinstance(device, AnalogIn): inputs[device.connection] = device else: raise Exception('Got unexpected device.') clockline = self.parent_device pseudoclock = clockline.parent_device times = pseudoclock.times[clockline] analog_connections = analogs.keys() analog_connections.sort() analog_out_attrs = [] #KLUGGEEEE #get lenth of one output connection output_length = len(analogs[analog_connections[0]].raw_output) analog_out_table = np.empty((output_length, len(analogs)), dtype=np.float32) #analog_out_table = np.empty((len(times),len(analogs)), dtype=np.float32) for i, connection in enumerate(analog_connections): output = analogs[connection] if any(output.raw_output > 10) or any(output.raw_output < -10): # Bounds checking: raise LabscriptError( '%s %s ' % (output.description, output.name) + 'can only have values between -10 and 10 Volts, ' + 'the limit imposed by %s.' % self.name) analog_out_table[:, i] = output.raw_output analog_out_attrs.append(self.MAX_name + '/' + connection) """ if self.name == 'ni_card_B': #kluge to double every cell new_out_table = np.empty((output_length*2, len(analogs)), dtype=np.float32) for i, row in enumerate(analog_out_table): new_out_table[2*i] = row new_out_table[2*i+1] = row analog_out_table = new_out_table """ #now we know that the last column should be randomized for one of the #CHOOSE CHANNEL HERE if self.name == 'ni_card_B': random_column = 0 #randint(0,6) else: # CHANGE THIS FOR CARD A random_column = 1 #analog_out_table[:,-1] = analog_out_table[:,random_column] input_connections = inputs.keys() input_connections.sort() input_attrs = [] acquisitions = [] for connection in input_connections: input_attrs.append(self.MAX_name + '/' + connection) for acq in inputs[connection].acquisitions: acquisitions.append( (connection, acq['label'], acq['start_time'], acq['end_time'], acq['wait_label'], acq['scale_factor'], acq['units'])) # The 'a256' dtype below limits the string fields to 256 # characters. Can't imagine this would be an issue, but to not # specify the string length (using dtype=str) causes the strings # to all come out empty. acquisitions_table_dtypes = [('connection', 'a256'), ('label', 'a256'), ('start', float), ('stop', float), ('wait label', 'a256'), ('scale factor', float), ('units', 'a256')] acquisition_table = np.empty(len(acquisitions), dtype=acquisitions_table_dtypes) for i, acq in enumerate(acquisitions): acquisition_table[i] = acq digital_out_table = [] if digitals: digital_out_table = self.convert_bools_to_bytes(digitals.values()) grp = self.init_device_group(hdf5_file) if all(analog_out_table.shape): # Both dimensions must be nonzero grp.create_dataset('ANALOG_OUTS', compression=config.compression, data=analog_out_table) self.set_property('analog_out_channels', ', '.join(analog_out_attrs), location='device_properties') if len(digital_out_table): # Table must be non empty grp.create_dataset('DIGITAL_OUTS', compression=config.compression, data=digital_out_table) self.set_property('digital_lines', '/'.join((self.MAX_name, 'port0', 'line0:%d' % (self.n_digitals - 1))), location='device_properties') if len(acquisition_table): # Table must be non empty grp.create_dataset('ACQUISITIONS', compression=config.compression, data=acquisition_table) self.set_property('analog_in_channels', ', '.join(input_attrs), location='device_properties') # TODO: move this to decorator (requires ability to set positional args with @set_passed_properties) self.set_property('clock_terminal', self.clock_terminal, location='connection_table_properties')
def generate_code(self, hdf5_file): DDSs = {} for output in self.child_devices: # Check that the instructions will fit into RAM: if isinstance(output, DDS) and len( output.frequency.raw_output ) > 16384 - 2: # -2 to include space for dummy instructions raise LabscriptError( '%s can only support 16383 instructions. ' % self.name + 'Please decrease the sample rates of devices on the same clock, ' + 'or connect %s to a different pseudoclock.' % self.name) try: prefix, channel = output.connection.split() channel = int(channel) except: raise LabscriptError( '%s %s has invalid connection string: \'%s\'. ' % (output.description, output.name, str(output.connection)) + 'Format must be \'channel n\' with n from 0 to 4.') DDSs[channel] = output for connection in DDSs: if connection in range(4): # Dynamic DDS dds = DDSs[connection] dds.frequency.raw_output, dds.frequency.scale_factor = self.quantise_freq( dds.frequency.raw_output, dds) dds.phase.raw_output, dds.phase.scale_factor = self.quantise_phase( dds.phase.raw_output, dds) dds.amplitude.raw_output, dds.amplitude.scale_factor = self.quantise_amp( dds.amplitude.raw_output, dds) # elif connection in range(2,4): # # StaticDDS: # dds = DDSs[connection] # dds.frequency.raw_output, dds.frequency.scale_factor = self.quantise_freq(dds.frequency.static_value, dds) # dds.phase.raw_output, dds.phase.scale_factor = self.quantise_phase(dds.phase.static_value, dds) # dds.amplitude.raw_output, dds.amplitude.scale_factor = self.quantise_amp(dds.amplitude.static_value, dds) else: raise LabscriptError( '%s %s has invalid connection string: \'%s\'. ' % (dds.description, dds.name, str(dds.connection)) + 'Format must be \'channel n\' with n from 0 to 4.') dtypes = [('freq%d'%i,np.uint32) for i in range(2)] + \ [('phase%d'%i,np.uint16) for i in range(2)] + \ [('amp%d'%i,np.uint16) for i in range(2)] static_dtypes = [('freq%d'%i,np.uint32) for i in range(2,4)] + \ [('phase%d'%i,np.uint16) for i in range(2,4)] + \ [('amp%d'%i,np.uint16) for i in range(2,4)] clockline = self.parent_clock_line pseudoclock = clockline.parent_device times = pseudoclock.times[clockline] out_table = np.zeros(len(times), dtype=dtypes) out_table['freq0'].fill(1) out_table['freq1'].fill(1) static_table = np.zeros(1, dtype=static_dtypes) static_table['freq2'].fill(1) static_table['freq3'].fill(1) for connection in range(2): if not connection in DDSs: continue dds = DDSs[connection] # The last two instructions are left blank, for BLACS # to fill in at program time. out_table['freq%d' % connection][:] = dds.frequency.raw_output out_table['amp%d' % connection][:] = dds.amplitude.raw_output out_table['phase%d' % connection][:] = dds.phase.raw_output for connection in range(2, 4): if not connection in DDSs: continue dds = DDSs[connection] static_table['freq%d' % connection] = dds.frequency.raw_output[0] static_table['amp%d' % connection] = dds.amplitude.raw_output[0] static_table['phase%d' % connection] = dds.phase.raw_output[0] if self.update_mode == 'asynchronous': # Duplicate the first line. Otherwise, we are one step ahead in the table # from the start of a run. This problem is not completely understood, but this # fixes it: out_table = np.concatenate([out_table[0:1], out_table]) grp = self.init_device_group(hdf5_file) grp.create_dataset('TABLE_DATA', compression=config.compression, data=out_table) grp.create_dataset('STATIC_DATA', compression=config.compression, data=static_table) self.set_property('frequency_scale_factor', 10, location='device_properties') self.set_property('amplitude_scale_factor', 1023, location='device_properties') self.set_property('phase_scale_factor', 45.511111111111113, location='device_properties')
def generate_code(self, hdf5_file): IntermediateDevice.generate_code(self, hdf5_file) DDSProfs = {} if len(self.child_devices) > 1: raise LabscriptError( "Too many child_devices. This device expects exactly 1 AD_DDS child output." ) output = self.child_devices[0] if isinstance(output, AD_DDS): prefix = output.connection[0] channel = int(output.connection[1]) else: raise Exception('Got unexpected device.') numDDSProfs = len(output.profiles) grp = hdf5_file.create_group('/devices/' + self.name) if numDDSProfs: profile_dtypes = [('freq%d'%i,np.float) for i in range(numDDSProfs)] + \ [('phase%d'%i,np.float) for i in range(numDDSProfs)] + \ [('amp%d'%i,np.float) for i in range(numDDSProfs)] profile_table = np.zeros(1, dtype=profile_dtypes) for profile in output.profiles: profile_table['freq' + profile[-1:]] = output.profiles[profile]['freq'] profile_table['amp' + profile[-1:]] = output.profiles[profile]['amp'] profile_table['phase' + profile[-1:]] = output.profiles[profile]['phase'] grp.create_dataset('PROFILE_DATA', compression=config.compression, data=profile_table) if output.sweep: sweep_dtypes = [('sweep_type', np.int), ('sweep_low', np.float), ('sweep_high', np.float), ('sweep_risetime', np.float), ('sweep_falltime', np.float), ('sweep_dt', np.float)] sweep_table = np.empty(1, dtype=sweep_dtypes) sweep_types = {'freq': 0, 'phase': 1, 'amp': 2} sweep_table['sweep_type'] = sweep_types[ output.sweep_params['type']] sweep_table['sweep_low'] = output.sweep_params['low'] sweep_table['sweep_high'] = output.sweep_params['high'] sweep_table['sweep_risetime'] = output.sweep_params['risetime'] sweep_table['sweep_falltime'] = output.sweep_params['falltime'] sweep_table['sweep_dt'] = output.sweep_params['sweep_dt'] grp.create_dataset('SWEEP_DATA', compression=config.compression, data=sweep_table)
def set_cam_param(self, param, value): if self.other_params.has_key(param): self.other_params[param] = value else: raise LabscriptError( 'Camera parameter %s does not exist in dictionary' % param)
def __init__(self, name, DoSomething = False, **kwargs): if DoSomething is not False: raise LabscriptError('test_device does nothing, but kwarg DoSomething was not passed False') Device.__init(self, name, None, None, **kwargs)
def generate_code(self, hdf5_file): PseudoclockDevice.generate_code(self, hdf5_file) group = hdf5_file['devices'].create_group(self.name) # compress clock instructions with the same period: This will # halve the number of instructions roughly, since the PineBlaster # does not have a 'slow clock': reduced_instructions = [] current_wait_index = 0 wait_table = sorted(compiler.wait_table) if not self.is_master_pseudoclock: reduced_instructions.append({ 'on': 0, 'off': ((self.trigger_edge_type == 'rising') << 1) + 1, 'reps': 0 }) for instruction in self.pseudoclock.clock: if instruction == 'WAIT': # The following period and reps indicates a wait instruction wait_timeout = compiler.wait_table[ wait_table[current_wait_index]][1] current_wait_index += 1 # The actual wait instruction. # on_counts correspond to teh number of reference clock cycles # to wait for external trigger before auto-resuming. # It overcounts by 1 here because the logic on the FPGA is different for the first reference clock cycle # (you cannot resume until the after second reference clock cycle), so we subtract 1 off the on counts # so that it times-out after the correct number of samples reduced_instructions.append({ 'on': round(wait_timeout / self.clock_resolution) - 1, 'off': ((self.trigger_edge_type == 'rising') << 1) + 1, 'reps': 0 }) continue reps = instruction['reps'] # period is in quantised units: periods = int(round(instruction['step'] / self.clock_resolution)) # Get the "high" half of the clock period on_period = int(periods / 2) # Use the remainder to calculate the "off period" (allows slightly assymetric clock signals so to minimise timing errors) off_period = periods - on_period if reduced_instructions and reduced_instructions[-1][ 'on'] == on_period and reduced_instructions[-1][ 'off'] == off_period: reduced_instructions[-1]['reps'] += reps else: reduced_instructions.append({ 'on': on_period, 'off': off_period, 'reps': reps }) if len(reduced_instructions) > self.max_instructions: raise LabscriptError( "%s %s has too many instructions. It has %d and can only support %d" % (self.description, self.name, len(reduced_instructions), self.max_instructions)) # Store these instructions to the h5 file: dtypes = [('on_period', np.int64), ('off_period', np.int64), ('reps', np.int64)] pulse_program = np.zeros(len(reduced_instructions), dtype=dtypes) for i, instruction in enumerate(reduced_instructions): pulse_program[i]['on_period'] = instruction['on'] pulse_program[i]['off_period'] = instruction['off'] pulse_program[i]['reps'] = instruction['reps'] group.create_dataset('PULSE_PROGRAM', compression=config.compression, data=pulse_program) self.set_property('is_master_pseudoclock', self.is_master_pseudoclock, location='device_properties') self.set_property('stop_time', self.stop_time, location='device_properties')
def __init__(self, name, parent_device=None, clock_terminal=None, MAX_name=None, static_AO=None, static_DO=None, clock_mirror_terminal=None, acquisition_rate=None, AI_range=None, AI_range_Diff=None, AI_start_delay=0, AI_start_delay_ticks=None, AI_term='RSE', AI_term_cfg=None, AO_range=None, max_AI_multi_chan_rate=None, max_AI_single_chan_rate=None, max_AO_sample_rate=None, max_DO_sample_rate=None, min_semiperiod_measurement=None, num_AI=0, num_AO=0, num_CI=0, ports=None, supports_buffered_AO=False, supports_buffered_DO=False, supports_semiperiod_measurement=False, supports_simultaneous_AI_sampling=False, **kwargs): """Generic class for NI_DAQmx devices. Generally over-ridden by device-specific subclasses that contain the introspected default values. Args: name (str): name to assign to the created labscript device parent_device (clockline): Parent clockline device that will clock the outputs of this device clock_terminal (str): What input on the DAQ is used for the clockline MAX_name (str): NI-MAX device name static_AO (int, optional): Number of static analog output channels. static_DO (int, optional): Number of static digital output channels. clock_mirror_terminal (str, optional): Channel string of digital output that mirrors the input clock. Useful for daisy-chaning DAQs on the same clockline. acquisiton_rate (float, optional): Default sample rate of inputs. AI_range (iterable, optional): A `[Vmin, Vmax]` pair that sets the analog input voltage range for all analog inputs. AI_range_Diff (iterable, optional): A `[Vmin, Vmax]` pair that sets the analog input voltage range for all analog inputs when using Differential termination. AI_start_delay (float, optional): Time in seconds between start of an analog input task starting and the first sample. AI_start_delay_ticks (int, optional): Time in sample clock periods between start of an analog input task starting and the first sample. To use this method, `AI_start_delay` must be set to `None`. This is necessary for DAQs that employ delta ADCs. AI_term (str, optional): Configures the analog input termination for all analog inputs. Must be supported by the device. Supported options are `'RSE'`, `'NRSE'` `'Diff'`, and '`PseudoDiff'`. AI_term_cfg (dict, optional): Dictionary of analog input channels and their supported terminations. Best to use `get_capabilities.py` to introspect these. AO_range (iterable, optional): A `[Vmin, Vmax]` pair that sets the analog output voltage range for all analog outputs. max_AI_multi_chan_rate (float, optional): Max supported analog input sampling rate when using multiple channels. max_AI_single_chan_rate (float, optional): Max supported analog input sampling rate when only using a single channel. max_AO_sample_rate (float, optional): Max supported analog output sample rate. max_DO_sample_rate (float, optional): Max supported digital output sample rate. min_sermiperiod_measurement (float, optional): Minimum measurable time for a semiperiod measurement. num_AI (int, optional): Number of analog inputs channels. num_AO (int, optional): Number of analog output channels. num_CI (int, optional): Number of counter input channels. ports (dict, optional): Dictionarly of DIO ports, which number of lines and whether port supports buffered output. supports_buffered_AO (bool, optional): True if analog outputs support buffered output supports_buffered_DO (bool, optional): True if digital outputs support buffered output supports_semiperiod_measurement (bool, optional): True if device supports semi-period measurements """ # Default static output setting based on whether the device supports buffered # output: if static_AO is None: static_AO = not supports_buffered_AO if static_DO is None: static_DO = not supports_buffered_DO # Parent is only allowed to be None if output is static: if parent_device is None and not (static_DO and static_AO): msg = """Must specify a parent clockline, unless both static_AO and static_DO are True""" raise LabscriptError(dedent(msg)) # If parent device is not None though, then clock terminal must be specified: if parent_device is not None and clock_terminal is None: msg = """If parent_device is given, then clock_terminal must be specified as well as the terminal to which the parent pseudoclock is connected.""" raise ValueError(dedent(msg)) if acquisition_rate is not None and num_AI == 0: msg = "Cannot set set acquisition rate on device with no analog inputs" raise ValueError(msg) # Acquisition rate cannot be larger than the single channel rate: if acquisition_rate is not None and acquisition_rate > max_AI_single_chan_rate: msg = """acquisition_rate %f is larger than the maximum single-channel rate %f for this device""" raise ValueError( dedent(msg) % (acquisition_rate, max_AI_single_chan_rate)) self.clock_terminal = clock_terminal self.MAX_name = MAX_name if MAX_name is not None else name self.static_AO = static_AO self.static_DO = static_DO self.acquisition_rate = acquisition_rate self.AO_range = AO_range self.max_AI_multi_chan_rate = max_AI_multi_chan_rate self.max_AI_single_chan_rate = max_AI_single_chan_rate self.max_AO_sample_rate = max_AO_sample_rate self.max_DO_sample_rate = max_DO_sample_rate self.min_semiperiod_measurement = min_semiperiod_measurement self.num_AI = num_AI # special handling for AI termination configurations self.AI_term = AI_term if AI_term_cfg == None: # assume legacy configuration if none provided AI_term_cfg = {f'ai{i:d}': ['RSE'] for i in range(num_AI)} # warn user to update their local model specs msg = """Model specifications for {} needs to be updated. Please run the `get_capabilites.py` and `generate_subclasses.py` scripts or define the `AI_Term_Cfg` kwarg for your device. """ warnings.warn(dedent(msg.format(self.description)), FutureWarning) self.AI_chans = [ key for key, val in AI_term_cfg.items() if self.AI_term in val ] if not len(self.AI_chans): msg = """AI termination {0} not supported by this device.""" raise LabscriptError(dedent(msg.format(AI_term))) if AI_term == 'Diff': self.AI_range = AI_range_Diff if AI_start_delay is None: if AI_start_delay_ticks is not None: # Tell blacs_worker to use AI_start_delay_ticks to define delay self.start_delay_ticks = True else: raise LabscriptError( "You have specified `AI_start_delay = None` but have not provided `AI_start_delay_ticks`." ) else: # Tells blacs_worker to use AI_start_delay to define delay self.start_delay_ticks = False self.num_AO = num_AO self.num_CI = num_CI self.ports = ports if ports is not None else {} self.supports_buffered_AO = supports_buffered_AO self.supports_buffered_DO = supports_buffered_DO self.supports_semiperiod_measurement = supports_semiperiod_measurement self.supports_simultaneous_AI_sampling = supports_simultaneous_AI_sampling if self.supports_buffered_DO and self.supports_buffered_AO: self.clock_limit = min(self.max_DO_sample_rate, self.max_AO_sample_rate) elif self.supports_buffered_DO: self.clock_limit = self.max_DO_sample_rate elif self.supports_buffered_AO: self.clock_limit = self.max_AO_sample_rate else: self.clock_limit = None if not (static_AO and static_DO): msg = """Device does not support buffered output, please instantiate it with static_AO=True and static_DO=True""" raise LabscriptError(dedent(msg)) self.wait_monitor_minimum_pulse_width = self.min_semiperiod_measurement self.allowed_children = [] '''Sets the allowed children types based on the capabilites.''' if self.num_AI > 0: self.allowed_children += [AnalogIn] if self.num_AO > 0 and static_AO: self.allowed_children += [StaticAnalogOut] if self.num_AO > 0 and not static_AO: self.allowed_children += [AnalogOut] if self.ports and static_DO: self.allowed_children += [StaticDigitalOut] if self.ports and not static_DO: self.allowed_children += [DigitalOut] if clock_terminal is None and not (static_AO and static_DO): msg = """Clock terminal must be specified unless static_AO and static_DO are both True""" raise LabscriptError(dedent(msg)) self.BLACS_connection = self.MAX_name # Cannot be set with set_passed_properties because of name mangling with the # initial double underscore: self.set_property('__version__', __version__, 'connection_table_properties') # This is called late since it must be called after our clock_limit attribute is # set: IntermediateDevice.__init__(self, name, parent_device, **kwargs)
def generate_code(self, hdf5_file): from rfblaster import caspr import rfblaster.rfjuice rfjuice_folder = os.path.dirname(rfblaster.rfjuice.__file__) import rfblaster.rfjuice.const as c from rfblaster.rfjuice.cython.make_diff_table import make_diff_table from rfblaster.rfjuice.cython.compile import compileD # from rfblaster.rfjuice.compile import compileD import tempfile from subprocess import Popen, PIPE # Generate clock and save raw instructions to the h5 file: PseudoclockDevice.generate_code(self, hdf5_file) dtypes = [('time',float),('amp0',float),('freq0',float),('phase0',float),('amp1',float),('freq1',float),('phase1',float)] times = self.pseudoclock.times[self._clock_line] data = np.zeros(len(times),dtype=dtypes) data['time'] = times for dds in self.direct_outputs.child_devices: prefix, connection = dds.connection.split() data['freq%s'%connection] = dds.frequency.raw_output data['amp%s'%connection] = dds.amplitude.raw_output data['phase%s'%connection] = dds.phase.raw_output group = hdf5_file['devices'].create_group(self.name) group.create_dataset('TABLE_DATA',compression=config.compression, data=data) # Quantise the data and save it to the h5 file: quantised_dtypes = [('time',np.int64), ('amp0',np.int32), ('freq0',np.int32), ('phase0',np.int32), ('amp1',np.int32), ('freq1',np.int32), ('phase1',np.int32)] quantised_data = np.zeros(len(times),dtype=quantised_dtypes) quantised_data['time'] = np.array(c.tT*1e6*data['time']+0.5) for dds in range(2): # TODO: bounds checking # Adding 0.5 to each so that casting to integer rounds: quantised_data['freq%d'%dds] = np.array(c.fF*1e-6*data['freq%d'%dds] + 0.5) quantised_data['amp%d'%dds] = np.array((2**c.bitsA - 1)*data['amp%d'%dds] + 0.5) quantised_data['phase%d'%dds] = np.array(c.pP*data['phase%d'%dds] + 0.5) group.create_dataset('QUANTISED_DATA',compression=config.compression, data=quantised_data) # Generate some assembly code and compile it to machine code: assembly_group = group.create_group('ASSEMBLY_CODE') binary_group = group.create_group('BINARY_CODE') diff_group = group.create_group('DIFF_TABLES') # When should the RFBlaster wait for a trigger? quantised_trigger_times = np.array([c.tT*1e6*t + 0.5 for t in self.trigger_times], dtype=np.int64) for dds in range(2): abs_table = np.zeros((len(times), 4),dtype=np.int64) abs_table[:,0] = quantised_data['time'] abs_table[:,1] = quantised_data['amp%d'%dds] abs_table[:,2] = quantised_data['freq%d'%dds] abs_table[:,3] = quantised_data['phase%d'%dds] # split up the table into chunks delimited by trigger times: abs_tables = [] for i, t in enumerate(quantised_trigger_times): subtable = abs_table[abs_table[:,0] >= t] try: next_trigger_time = quantised_trigger_times[i+1] except IndexError: # No next trigger time pass else: subtable = subtable[subtable[:,0] < next_trigger_time] subtable[:,0] -= t abs_tables.append(subtable) # convert to diff tables: diff_tables = [make_diff_table(tab) for tab in abs_tables] # Create temporary files, get their paths, and close them: with tempfile.NamedTemporaryFile(delete=False) as f: temp_assembly_filepath = f.name with tempfile.NamedTemporaryFile(delete=False) as f: temp_binary_filepath = f.name try: # Compile to assembly: with open(temp_assembly_filepath,'w') as assembly_file: for i, dtab in enumerate(diff_tables): compileD(dtab, assembly_file, init=(i == 0), jump_to_start=(i == 0), jump_from_end=False, close_end=(i == len(diff_tables) - 1), local_loop_pre = bytes(i) if PY2 else str(i), set_defaults = (i==0)) # Save the assembly to the h5 file: with open(temp_assembly_filepath,) as assembly_file: assembly_code = assembly_file.read() assembly_group.create_dataset('DDS%d'%dds, data=assembly_code) for i, diff_table in enumerate(diff_tables): diff_group.create_dataset('DDS%d_difftable%d'%(dds,i), compression=config.compression, data=diff_table) # compile to binary: compilation = Popen([caspr,temp_assembly_filepath,temp_binary_filepath], stdout=PIPE, stderr=PIPE, cwd=rfjuice_folder,startupinfo=startupinfo) stdout, stderr = compilation.communicate() if compilation.returncode: print(stdout) raise LabscriptError('RFBlaster compilation exited with code %d\n\n'%compilation.returncode + 'Stdout was:\n %s\n'%stdout + 'Stderr was:\n%s\n'%stderr) # Save the binary to the h5 file: with open(temp_binary_filepath,'rb') as binary_file: binary_data = binary_file.read() # has to be numpy.string_ (string_ in this namespace, # imported from pylab) as python strings get stored # as h5py as 'variable length' strings, which 'cannot # contain embedded nulls'. Presumably our binary data # must contain nulls sometimes. So this crashes if we # don't convert to a numpy 'fixes length' string: binary_group.create_dataset('DDS%d'%dds, data=np.string_(binary_data)) finally: # Delete the temporary files: os.remove(temp_assembly_filepath) os.remove(temp_binary_filepath)
def setphase(self, value, units=None): """Overridden from StaticDDS so as not to provide phase control, which is generally not supported by :obj:`SignalGenerator` devices. """ raise LabscriptError('StaticFreqAmp does not support phase control')
def __init__(self, name, parent_device, com_port='COM1', FMSignal=None, RFOnOff=None, freq_limits=None, freq_conv_class=None, freq_conv_params={}, amp_limits=None, amp_conv_class=None, amp_conv_params={}, phase_limits=None, phase_conv_class=None, phase_conv_params={}): #self.clock_type = parent_device.clock_type # Don't see that this is needed anymore IntermediateDevice.__init__(self, name, parent_device) self.BLACS_connection = com_port self.sweep_dt = 2e-6 self.RFSettings = { 'freq': 0, 'amp': 0, 'phase': 0, 'freq_dev': 0 ##EE } self.sweep_params = { # 'type': None, 'low': 0, 'high': 1, 'duration': 0, # 'falltime': 0, 'sample_rate': self.sweep_dt } self.sweep = False self.ext_in = False self.frequency = StaticAnalogQuantity(self.name + '_freq', self, 'freq', freq_limits, freq_conv_class, freq_conv_params) self.amplitude = StaticAnalogQuantity(self.name + '_amp', self, 'amp', amp_limits, amp_conv_class, amp_conv_params) self.phase = StaticAnalogQuantity(self.name + '_phase', self, 'phase', phase_limits, phase_conv_class, phase_conv_params) self.FMSignal = {} self.RFOnOff = {} if FMSignal: if 'device' in FMSignal and 'connection' in FMSignal: self.FMSignal = AnalogOut(self.name + '_FMSignal', FMSignal['device'], FMSignal['connection']) else: raise LabscriptError( 'You must specify the "device" and "connection" for the analog output FMSignal of ' + self.name) else: raise LabscriptError( 'Expected analog output for "FMSignal" control') if RFOnOff: if 'device' in RFOnOff and 'connection' in RFOnOff: self.RFOnOff = DigitalOut(self.name + '_RFOnOff', RFOnOff['device'], RFOnOff['connection']) else: raise LabscriptError( 'You must specify the "device" and "connection" for the digital output RFOnOff of ' + self.name) else: raise LabscriptError( 'Expected digital output for "RFOnOff" control')
def disable(self): """overridden from StaticDDS so as not to provide time resolution - output can be enabled or disabled only at the start of the shot""" raise LabscriptError( 'StaticFreqAmp {:s} does not support a digital gate'.format( self.name))
def init(self): """Initialization command run automatically by the BLACS tab on startup. It establishes communication and sends initial default configuration commands""" self.smart_cache = {'STATIC_DATA': None, 'TABLE_DATA': '', 'CURRENT_DATA':None} self.baud_dict = {9600:b'78', 19200:b'3c', 38400:b'1e',57600:b'14',115200:b'0a'} self.err_codes = {b'?0':'Unrecognized Command', b'?1':'Bad Frequency', b'?2':'Bad AM Command', b'?3':'Input Line Too Long', b'?4':'Bad Phase', b'?5':'Bad Time', b'?6':'Bad Mode', b'?7':'Bad Amp', b'?8':'Bad Constant', b'?f':'Bad Byte'} # total number of DDS channels on device & channel properties self.N_chan = 4 self.subchnls = ['freq','amp','phase'] # conversion dictionaries for program_static from # program_manual self.conv = {'freq':self.clk_scale*10**(-6),'amp':1023.0,'phase':16384.0/360.0}## check if things break 2019-02-22 # and from transition_to_buffered self.conv_buffered = {'freq':10**(-7),'amp':1,'phase':1} # read from device conversion, basically conv_buffered/conv self.read_conv = {'freq':1/(self.clk_scale*10.0),'amp':1/1023.0,'phase':360.0/16384.0} ## check if things break 2019-02-22 # set phase mode method phase_mode_commands = { 'aligned': b'm a', 'continuous': b'm n'} self.phase_mode_command = phase_mode_commands[self.phase_mode] self.connection = serial.Serial(self.com_port, baudrate = self.baud_rate, timeout=0.1) self.connection.readlines() # to configure baud rate, must determine current device baud rate # first check desired, since it's most likely connected, response = self.check_connection() if not connected: # not already set bauds = list(self.baud_dict) if self.baud_rate in bauds: bauds.remove(self.baud_rate) else: raise LabscriptError('%d baud rate not supported by Novatech 409B' % self.baud_rate) # iterate through other baud-rates to find current for rate in bauds: self.connection.baudrate = rate connected, response = self.check_connection() if connected: # found it! break else: raise LabscriptError('Error: Baud rate not found! Is Novatech DDS connected?') # now we can set the desired baud rate baud_string = b'Kb %s\r\n' % (self.baud_dict[self.baud_rate]) self.connection.write(baud_string) # ensure command finishes before switching rates in pyserial time.sleep(0.1) self.connection.baudrate = self.baud_rate connected, response = self.check_connection() if not connected: raise LabscriptError('Error: Failed to execute command "%s"' % baud_string.decode('utf8')) self.connection.write(b'e d\r\n') response = self.connection.readline() if response == b'e d\r\n': # if echo was enabled, then the command to disable it echos back at us! response = self.connection.readline() if response != b'OK\r\n': raise Exception('Error: Failed to execute command: "e d". Cannot connect to the device.') # set automatic updates and phase mode self.write_check(b'M 0\r\n') self.write_check(b'I a\r\n') self.write_check(b'%s\r\n'%self.phase_mode_command) # Set clock parameters if self.R_option: # Using R option. Do not send C or Kp serial commands pass else: # Pass kp value self.write_check(b'Kp %02x\r\n'%self.kp) # Pass clock setting if self.ext_clk: # Enable external clock clk_command = b'C E\r\n' else: # Or enable internal clock clk_command = b'C I\r\n' self.write_check(clk_command) # populate the 'CURRENT_DATA' dictionary self.check_remote_values()
def SetUpSweep(self, type, low, high, risetime, falltime, dt): # Set profiles to default values # !!!!! Currently assumes that: # 1. Only frequencies are swept (NOT ampl or phase) # 2. Frequency sweeps occur with amplitude = 1 and phase = 0 for profile in range(8): self.program_static('profile %i' % profile, 'freq', 13) # Arbitrary frequency self.program_static('profile %i' % profile, 'amp', 1) self.program_static('profile %i' % profile, 'phase', 0) reg0 = self.ReadRegister(0) reg0[1] |= 0x10 # Clear accumulator self.WriteRegister(0, reg0, True) time.sleep(0.1) reg0[1] &= ~0x10 self.WriteRegister(0, reg0, True) reg1 = self.ReadRegister(1) reg1[2] |= 0x08 # Enable digital ramp reg1[2] &= ~0x30 # Clear ramp destination bits reg1[2] |= type << 4 # Set ramp destination bits self.WriteRegister(1, reg1) if low >= high: raise LabscriptError( "High sweep point must be higher than low sweep point.") stepSize_rise = (high - low) * dt / risetime stepSize_fall = (high - low) * dt / falltime # print "Step sizes!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" # print stepSize_rise # print stepSize_fall reg4 = bytearray(4) reg5 = bytearray(4) reg6 = bytearray(4) reg7 = bytearray(4) if type == 0: # freq reg4 = self.CalcFTW(low) reg5 = self.CalcFTW(high) reg6 = self.CalcFTW(stepSize_rise) reg7 = self.CalcFTW(stepSize_fall) elif type == 1: # phase # Must shift over by 18 bits to fill only the 14 MSBs reg4[2:] = bytearray( struct.pack( '@H', int(struct.unpack('@H', self.CalcPOW(low))[0]) * (2**2)))[:2] reg5[2:] = bytearray( struct.pack( '@H', int(struct.unpack('@H', self.CalcPOW(high))[0]) * (2**2)))[:2] reg6[2:] = bytearray( struct.pack( '@H', int(struct.unpack('@H', self.CalcPOW(stepSize_rise))[0]) * (2**2)))[:2] reg7[2:] = bytearray( struct.pack( '@H', int(struct.unpack('@H', self.CalcPOW(stepSize_fall))[0]) * (2**2)))[:2] elif type == 2: # amp # Must shift over by 20 bits to fill only the 12 MSBs reg4[2:] = bytearray( struct.pack( '@H', int(struct.unpack('@H', self.CalcASF(low))[0]) * (2**4)))[:2] reg5[2:] = bytearray( struct.pack( '@H', int(struct.unpack('@H', self.CalcASF(high))[0]) * (2**4)))[:2] reg6[2:] = bytearray( struct.pack( '@H', int(struct.unpack('@H', self.CalcASF(stepSize_rise))[0]) * (2**4)))[:2] reg7[2:] = bytearray( struct.pack( '@H', int(struct.unpack('@H', self.CalcASF(stepSize_fall))[0]) * (2**4)))[:2] if int(struct.unpack('@I', reg6)[0]) == 0: raise LabscriptError( "Rising step size is too small - got rounded to 0") if int(struct.unpack('@I', reg7)[0]) == 0: raise LabscriptError( "Falling step size is too small - got rounded to 0") self.WriteRegister(4, reg4) self.WriteRegister(5, reg5) self.WriteRegister(6, reg6) self.WriteRegister(7, reg7) # Set the slope time interval (constant: 2e-6 s) dt_reg = bytearray(4) dt_word = int(round(dt * self.clk / 24)) if dt_word >= 2**16: raise LabscriptError("Sweep time interval dt is too long.") dt_word = bytearray(struct.pack('@H', dt_word)) dt_reg[:2] = dt_word dt_reg[2:] = dt_word self.WriteRegister(8, dt_reg) self.IOUpdate()
def generate_code(self, hdf5_file): IntermediateDevice.generate_code(self, hdf5_file) analogs = {} digitals = {} inputs = {} for device in self.child_devices: if isinstance(device, AnalogOut): analogs[device.connection] = device elif isinstance(device, DigitalOut): digitals[device.connection] = device elif isinstance(device, AnalogIn): inputs[device.connection] = device else: raise Exception('Got unexpected device.') clockline = self.parent_device pseudoclock = clockline.parent_device times = pseudoclock.times[clockline] analog_out_table = np.empty((len(times), len(analogs)), dtype=np.float32) analog_connections = analogs.keys() analog_connections.sort() analog_out_attrs = [] for i, connection in enumerate(analog_connections): output = analogs[connection] if any(output.raw_output > 10) or any(output.raw_output < -10): # Bounds checking: raise LabscriptError( '%s %s ' % (output.description, output.name) + 'can only have values between -10 and 10 Volts, ' + 'the limit imposed by %s.' % self.name) analog_out_table[:, i] = output.raw_output analog_out_attrs.append(self.MAX_name + '/' + connection) input_connections = inputs.keys() input_connections.sort() input_attrs = [] acquisitions = [] for connection in input_connections: input_attrs.append(self.MAX_name + '/' + connection) for acq in inputs[connection].acquisitions: acquisitions.append( (connection, acq['label'], acq['start_time'], acq['end_time'], acq['wait_label'], acq['scale_factor'], acq['units'])) # The 'a256' dtype below limits the string fields to 256 # characters. Can't imagine this would be an issue, but to not # specify the string length (using dtype=str) causes the strings # to all come out empty. acquisitions_table_dtypes = [('connection', 'a256'), ('label', 'a256'), ('start', float), ('stop', float), ('wait label', 'a256'), ('scale factor', float), ('units', 'a256')] acquisition_table = np.empty(len(acquisitions), dtype=acquisitions_table_dtypes) for i, acq in enumerate(acquisitions): acquisition_table[i] = acq digital_out_table = [] if digitals: digital_out_table = self.convert_bools_to_bytes(digitals.values()) grp = self.init_device_group(hdf5_file) if all(analog_out_table.shape): # Both dimensions must be nonzero grp.create_dataset('ANALOG_OUTS', compression=config.compression, data=analog_out_table) self.set_property('analog_out_channels', ', '.join(analog_out_attrs), location='device_properties') if len(digital_out_table): # Table must be non empty grp.create_dataset('DIGITAL_OUTS', compression=config.compression, data=digital_out_table) # construct a single string that has each port and line distribution separated by commas # this should coincide with the convention used by the create/write functions in the DAQmx library ports_str = "" for i in range(self.n_ports): ports_str = ports_str + self.MAX_name + '/port%d' % ( i) + '/line0:%d' % (self.n_lines - 1) + ',' ports_str = ports_str[:-1] # delete final comma in string self.set_property('digital_lines', (ports_str), location='device_properties') if len(acquisition_table): # Table must be non empty grp.create_dataset('ACQUISITIONS', compression=config.compression, data=acquisition_table) self.set_property('analog_in_channels', ', '.join(input_attrs), location='device_properties') # TODO: move this to decorator (requires ability to set positional args with @set_passed_properties) self.set_property('clock_terminal', self.clock_terminal, location='connection_table_properties')
def add_device(self, device): if isinstance(device, WaitMonitor): IntermediateDevice.add_device(self, device) else: raise LabscriptError('You can only connect an instance of WaitMonitor to the device %s.internal_wait_monitor_outputs'%(self.pseudoclock_device.name))
def setamp(self, value, units=None): raise LabscriptError('QuickSyn does not support amplitude control')
def add_device(self, device): """Error checking for adding a child device""" # Verify static/dynamic outputs compatible with configuration: if isinstance(device, StaticAnalogOut) and not self.static_AO: msg = """Cannot add StaticAnalogOut to NI_DAQmx device configured for dynamic analog output. Pass static_AO=True for static analog output""" raise LabscriptError(dedent(msg)) if isinstance(device, StaticDigitalOut) and not self.static_DO: msg = """Cannot add StaticDigitalOut to NI_DAQmx device configured for dynamic digital output. Pass static_DO=True for static digital output""" raise LabscriptError(dedent(msg)) if isinstance(device, AnalogOut) and self.static_AO: msg = """Cannot add AnalogOut to NI_DAQmx device configured for static analog output. Pass static_AO=False for dynamic analog output""" raise LabscriptError(dedent(msg)) if isinstance(device, DigitalOut) and self.static_DO: msg = """Cannot add DigitalOut to NI_DAQmx device configured for static digital output. Pass static_DO=False for dynamic digital output""" raise LabscriptError(dedent(msg)) # Verify connection string is OK: if isinstance(device, (AnalogOut, StaticAnalogOut)): ao_num = split_conn_AO(device.connection) if ao_num >= self.num_AO: msg = """Cannot add output with connection string '%s' to device with num_AO=%d""" raise ValueError( dedent(msg) % (device.connection, self.num_AO)) elif isinstance(device, (DigitalOut, StaticDigitalOut)): port, line = split_conn_DO(device.connection) port_str = 'port%d' % port if port_str not in self.ports: msg = "Parent device has no such DO port '%s'" % port_str raise ValueError(msg) nlines = self.ports[port_str]['num_lines'] if line >= nlines: msg = """Canot add output with connection string '%s' to port '%s' with only %d lines""" raise ValueError( dedent(msg) % (device.connection, port_str, nlines)) supports_buffered = self.ports[port_str]['supports_buffered'] if isinstance(device, DigitalOut) and not supports_buffered: msg = """Cannot add DigitalOut port '%s', which does not support buffered output""" raise ValueError(dedent(msg) % port_str) elif isinstance(device, AnalogIn): ai_num = split_conn_AI(device.connection) if ai_num >= self.num_AI: msg = """Cannot add analog input with connection string '%s' to device with num_AI=%d""" raise ValueError( dedent(msg) % (device.connection, self.num_AI)) if self.acquisition_rate is None: msg = """Cannot add analog input to NI_DAQmx device with acquisition_rate=None. Please set acquisition_rate as an instantiation argument to the parent NI_DAQmx device.""" raise ValueError(dedent(msg)) if self.parent_device is None: msg = """Cannot add analog input to device with no parent pseudoclock. Even if there is no buffered output, a pseudoclock is still required to trigger the start of acquisition. Please specify a parent_device and clock_terminal for device %s""" raise ValueError(dedent(msg) % self.name) IntermediateDevice.add_device(self, device)
def generate_registers(self, hdf5_file, dds_outputs): ampdicts = {} phasedicts = {} freqdicts = {} group = hdf5_file['/devices/'+self.name] dds_dict = {} for output in dds_outputs: num = int(output.connection.split()[1]) dds_dict[num] = output for num in [0,1]: if num in dds_dict: output = dds_dict[num] # Ensure that amplitudes are within bounds: if any(output.amplitude.raw_output > 1) or any(output.amplitude.raw_output < 0): raise LabscriptError('%s %s '%(output.amplitude.description, output.amplitude.name) + 'can only have values between 0 and 1, ' + 'the limit imposed by %s.'%output.name) # Ensure that frequencies are within bounds: if any(output.frequency.raw_output > 150e6 ) or any(output.frequency.raw_output < 0): raise LabscriptError('%s %s '%(output.frequency.description, output.frequency.name) + 'can only have values between 0Hz and and 150MHz, ' + 'the limit imposed by %s.'%output.name) # Ensure that phase wraps around: output.phase.raw_output %= 360 amps = set(output.amplitude.raw_output) phases = set(output.phase.raw_output) freqs = set(output.frequency.raw_output) else: # If the DDS is unused, it will use the following values # for the whole experimental run: amps = set([0]) phases = set([0]) freqs = set([0]) if len(amps) > 1024: raise LabscriptError('%s dds%d can only support 1024 amplitude registers, and %s have been requested.'%(self.name, num, str(len(amps)))) if len(phases) > 128: raise LabscriptError('%s dds%d can only support 128 phase registers, and %s have been requested.'%(self.name, num, str(len(phases)))) if len(freqs) > 1024: raise LabscriptError('%s dds%d can only support 1024 frequency registers, and %s have been requested.'%(self.name, num, str(len(freqs)))) # start counting at 1 to leave room for the dummy instruction, # which BLACS will fill in with the state of the front # panel: ampregs = range(1,len(amps)+1) freqregs = range(1,len(freqs)+1) phaseregs = range(1,len(phases)+1) ampdicts[num] = dict(zip(amps,ampregs)) freqdicts[num] = dict(zip(freqs,freqregs)) phasedicts[num] = dict(zip(phases,phaseregs)) # The zeros are the dummy instructions: freq_table = np.array([0] + list(freqs), dtype = np.float64) / 1e6 # convert to MHz amp_table = np.array([0] + list(amps), dtype = np.float32) phase_table = np.array([0] + list(phases), dtype = np.float64) subgroup = group.create_group('DDS%d'%num) subgroup.create_dataset('FREQ_REGS', compression=config.compression, data = freq_table) subgroup.create_dataset('AMP_REGS', compression=config.compression, data = amp_table) subgroup.create_dataset('PHASE_REGS', compression=config.compression, data = phase_table) return freqdicts, ampdicts, phasedicts