def __init__(self): QMainWindow.__init__(self) self.instrumentDetectThread = instrumentDetectThread() self.instrumentDetectThread.foundInstruments.connect(self.catchList) self.settings = QSettings("greyltc", "ivSweeper") #how long status messages show for self.messageDuration = 1000 #ms #variables to keep track of what we're sourcing/sensing self.source = "voltage" self.sense = "current" self.senseUnit = 'A' self.sourceUnit = 'V' #variable to keep track of if a sweep is ongoing self.sweeping = False # Set up the user interface from Designer. self.ui = Ui_IVSweeper() self.ui.setupUi(self) if self.settings.contains('lastFolder'): self.ui.dirEdit.setText( self.settings.value('lastFolder').toString()) #connect signals generated by gui elements to proper functions self.ui.sweepButton.clicked.connect(self.manageSweep) self.ui.shutterButton.clicked.connect(self.handleShutter) self.ui.frontRadio.toggled.connect(self.setTerminals) self.ui.sourceVRadio.toggled.connect(self.setMode) self.ui.twowireRadio.toggled.connect(self.setWires) self.ui.zeroCheck.toggled.connect(self.setZero) self.ui.speedCombo.currentIndexChanged.connect(self.setSpeed) self.ui.averageSpin.valueChanged.connect(self.setAverage) self.ui.complianceSpin.valueChanged.connect(self.setCompliance) self.ui.startSpin.valueChanged.connect(self.setStart) self.ui.endSpin.valueChanged.connect(self.setSourceRange) self.ui.outputCheck.toggled.connect(self.setOutput) self.ui.delaySpinBox.valueChanged.connect(self.updateDeltaText) self.ui.totalPointsSpin.valueChanged.connect(self.updateDeltaText) self.ui.reverseButton.clicked.connect(self.reverseCall) self.ui.actionRun_Test_Code.triggered.connect(self.testArea) self.ui.instrumentCombo.activated.connect(self.handleICombo) self.ui.saveModeCombo.currentIndexChanged.connect(self.handleModeCombo) self.ui.browseButton.clicked.connect(self.browseButtonCall) self.ui.outputCheck.clicked.connect(self.toggleWhatUserWants)
def __init__(self): QMainWindow.__init__(self) self.instrumentDetectThread = instrumentDetectThread() self.instrumentDetectThread.foundInstruments.connect(self.catchList) self.settings = QSettings("greyltc", "ivSweeper") #how long status messages show for self.messageDuration = 1000#ms #variables to keep track of what we're sourcing/sensing self.source = "voltage" self.sense = "current" self.senseUnit = 'A' self.sourceUnit = 'V' #variable to keep track of if a sweep is ongoing self.sweeping = False # Set up the user interface from Designer. self.ui = Ui_IVSweeper() self.ui.setupUi(self) if self.settings.contains('lastFolder'): self.ui.dirEdit.setText(self.settings.value('lastFolder').toString()) #connect signals generated by gui elements to proper functions self.ui.sweepButton.clicked.connect(self.manageSweep) self.ui.shutterButton.clicked.connect(self.handleShutter) self.ui.frontRadio.toggled.connect(self.setTerminals) self.ui.sourceVRadio.toggled.connect(self.setMode) self.ui.twowireRadio.toggled.connect(self.setWires) self.ui.zeroCheck.toggled.connect(self.setZero) self.ui.speedCombo.currentIndexChanged.connect(self.setSpeed) self.ui.averageSpin.valueChanged.connect(self.setAverage) self.ui.complianceSpin.valueChanged.connect(self.setCompliance) self.ui.startSpin.valueChanged.connect(self.setStart) self.ui.endSpin.valueChanged.connect(self.setSourceRange) self.ui.outputCheck.toggled.connect(self.setOutput) self.ui.delaySpinBox.valueChanged.connect(self.updateDeltaText) self.ui.totalPointsSpin.valueChanged.connect(self.updateDeltaText) self.ui.reverseButton.clicked.connect(self.reverseCall) self.ui.actionRun_Test_Code.triggered.connect(self.testArea) self.ui.instrumentCombo.activated.connect(self.handleICombo) self.ui.saveModeCombo.currentIndexChanged.connect(self.handleModeCombo) self.ui.browseButton.clicked.connect(self.browseButtonCall) self.ui.outputCheck.clicked.connect(self.toggleWhatUserWants)
class MainWindow(QMainWindow): sweepVaribles = pyqtSignal(float, np.ndarray, str) #killSweepNow = pyqtSignal() sweepUp = True userWantsOn = False #the user wants the output off def __init__(self): QMainWindow.__init__(self) self.instrumentDetectThread = instrumentDetectThread() self.instrumentDetectThread.foundInstruments.connect(self.catchList) self.settings = QSettings("greyltc", "ivSweeper") #how long status messages show for self.messageDuration = 1000 #ms #variables to keep track of what we're sourcing/sensing self.source = "voltage" self.sense = "current" self.senseUnit = 'A' self.sourceUnit = 'V' #variable to keep track of if a sweep is ongoing self.sweeping = False # Set up the user interface from Designer. self.ui = Ui_IVSweeper() self.ui.setupUi(self) if self.settings.contains('lastFolder'): self.ui.dirEdit.setText( self.settings.value('lastFolder').toString()) #connect signals generated by gui elements to proper functions self.ui.sweepButton.clicked.connect(self.manageSweep) self.ui.shutterButton.clicked.connect(self.handleShutter) self.ui.frontRadio.toggled.connect(self.setTerminals) self.ui.sourceVRadio.toggled.connect(self.setMode) self.ui.twowireRadio.toggled.connect(self.setWires) self.ui.zeroCheck.toggled.connect(self.setZero) self.ui.speedCombo.currentIndexChanged.connect(self.setSpeed) self.ui.averageSpin.valueChanged.connect(self.setAverage) self.ui.complianceSpin.valueChanged.connect(self.setCompliance) self.ui.startSpin.valueChanged.connect(self.setStart) self.ui.endSpin.valueChanged.connect(self.setSourceRange) self.ui.outputCheck.toggled.connect(self.setOutput) self.ui.delaySpinBox.valueChanged.connect(self.updateDeltaText) self.ui.totalPointsSpin.valueChanged.connect(self.updateDeltaText) self.ui.reverseButton.clicked.connect(self.reverseCall) self.ui.actionRun_Test_Code.triggered.connect(self.testArea) self.ui.instrumentCombo.activated.connect(self.handleICombo) self.ui.saveModeCombo.currentIndexChanged.connect(self.handleModeCombo) self.ui.browseButton.clicked.connect(self.browseButtonCall) self.ui.outputCheck.clicked.connect(self.toggleWhatUserWants) #TODO: load state here #self.restoreState(self.settings.value('guiState').toByteArray()) #this keeps track of clicks on the output/on off box def toggleWhatUserWants(self): self.userWantsOn = not self.userWantsOn def handleModeCombo(self): dt = self.ui.delaySpinBox.value() if self.ui.saveModeCombo.currentIndex() == 0: #i,v vs t mode startValue = float(self.ui.startSpin.value()) endValue = float(self.ui.endSpin.value()) span = abs(endValue - startValue) + 1 self.ui.totalPointsSpin.setMaximum(span) self.ui.totalPointsSpin.setMinimum(1) self.sendCmd(":source:delay 0") self.sendCmd(':source:' + self.source + ':mode fixed') self.sendCmd(':trigger:count 1') #fast response and no auto zeroing in i,v vs t mode self.ui.speedCombo.setCurrentIndex(0) self.ui.zeroCheck.setChecked(False) else: #traditional i vs v mode #TODO: figure out why this mode is double scanning for 70 points 1 sec delay self.ui.totalPointsSpin.setMaximum( 2500 ) #standard sweeps can be at most 2500 points long (keithley limitation) self.ui.totalPointsSpin.setMinimum(2) self.ui.speedCombo.setCurrentIndex(3) #go slow here self.sendCmd(":source:delay {0:0.3f}".format(dt)) self.sendCmd(':source:' + self.source + ':mode sweep') nPoints = float(self.ui.totalPointsSpin.value()) self.sendCmd(':trigger:count {0:d}'.format(int(nPoints))) #high accuracy and auto zeroing in i vs v mode self.ui.speedCombo.setCurrentIndex(3) self.ui.zeroCheck.setChecked(True) def catchList(self, resourceNames): for i in range(self.ui.instrumentCombo.count()): self.ui.instrumentCombo.removeItem(0) self.ui.instrumentCombo.setEnabled(True) self.ui.instrumentCombo.insertItem(0, 'Select Instrument') self.ui.instrumentCombo.insertItem(1, '(Re)Scan for Instruments') i = 2 for instrument in resourceNames: self.ui.instrumentCombo.insertItem(i, instrument) i = i + 1 self.ui.instrumentCombo.setCurrentIndex(0) def handleICombo(self, index): #index = self.ui.instrumentCombo.currentIndex() thisString = str(self.ui.instrumentCombo.currentText()) scanString = '(Re)Scan for Instruments' selectString = 'Select Instrument' noneString = "None found" if thisString == scanString: self.ui.instrumentCombo.setItemText(index, 'Searching...') self.ui.instrumentCombo.setEnabled(False) self.instrumentDetectThread.start() elif (thisString == selectString) or (thisString == noneString): pass else: self.initialConnect(thisString) def browseButtonCall(self): dirName = QFileDialog.getExistingDirectory( directory=self.ui.dirEdit.text()) self.settings.setValue('lastFolder', dirName) self.ui.dirEdit.setText(dirName) #return -1 * the power of the device at a given voltage or current def invPower(self, request): request = request[0] #TODO: remove this testing current fudge value #currentFudge = 0.004; currentFudge = 0 try: print request self.sendCmd('source:' + self.source + ' {0:.3f}'.format(request)) self.k.task_queue.put( ('read_raw', ())) #TODO: this should be split to another function data = qBinRead(self.k.done_queue) return (data[0] * (data[1] - currentFudge)) except: self.ui.statusbar.showMessage("Error: Not connected", self.messageDuration) return np.nan def handleShutter(self): shutterOnValue = '14' shutterOffValue = '15' self.k.task_queue.put( ('ask', (':source2:ttl:actual?', ) )) #TODO: this should be split to another function outStatus = self.k.done_queue.get() if outStatus == shutterOnValue: self.sendCmd(":source2:ttl " + shutterOffValue) else: self.sendCmd(":source2:ttl " + shutterOnValue) #TODO: move this to its own thread def maxPowerDwell(self): voltageSourceRange = 3 # operate between +/- 3V currentSourceRange = 0.1 # operate between +/- 100ma if self.sourceUnit == 'V': self.sendCmd(':source:' + self.source + ':range {0:.3f}'.format(voltageSourceRange)) initialGuess = 0.7 else: self.sendCmd(':source:' + self.source + ':range {0:.3f}'.format(currentSourceRange)) initialGuess = 0.01 # no idea if this is right dt = self.ui.delaySpinBox.value() nPoints = float(self.ui.totalPointsSpin.value()) self.ui.outputCheck.setChecked(True) oldSpeedIndex = self.ui.speedCombo.currentIndex() self.ui.speedCombo.setCurrentIndex(2) for i in range(int(nPoints)): optResults = optimize.minimize(self.invPower, initialGuess, method='COBYLA', tol=1e-4, options={'rhobeg': 0.2}) print optResults.message print optResults.status answer = float(optResults.x) initialGuess = answer self.sendCmd( ":SYST:KEY 23") #go into local mode for live display update print "Optimized! Mpp Voltage: {0:.3f}".format(answer) print "Now sleeping for {0:.1f} seconds".format(dt) time.sleep(dt) self.k.task_queue.put(('read_raw', ())) data = qBinRead(self.k.done_queue) #vi = (data[0], data[1], data[1]*data[0]*1000/.4*-1) print 'Max Power: {0:.3f}% '.format( data[0] * data[1] * 1000 / float(self.ui.deviceAreaEdit.text())) self.ui.outputCheck.setChecked(self.userWantsOn) self.ui.speedCombo.setCurrentIndex(oldSpeedIndex) def testArea(self): print('Running test code now') #self.maxPowerDwell() #x = np.random.randn(10000) #np.hist(x, 100) def closeEvent(self, event): #TODO: save state here #self.settings.setValue('guiState',self.saveState()) self.closeInstrument() QMainWindow.closeEvent(self, event) #do these things when a sweep completes (or is canceled by the user) def doSweepComplete(self): if self.ui.sweepContinuallyGroup.isChecked( ) and self.sweeping: #in continual sweep mode, perform another sweep self.sendCmd( ':source:' + self.source + ' {0:.4f}'.format(float(self.ui.startSpin.value()) / 1000)) if self.ui.displayBlankCheck.isChecked(): self.sendCmd(':display:enable off' ) #this makes the device more responsive else: self.sendCmd(':display:enable on' ) #this makes the device more responsive sleepMS = int(self.ui.scanRecoverySpin.value() * 1000) if sleepMS > 0: #start these after the user specified delay self.timerA = QTimer() self.timerA.timeout.connect(self.initiateNewSweep) self.timerA.setSingleShot(True) self.timerA.start(sleepMS) self.ui.statusbar.showMessage( "Sleeping for {0:.1f} s before next scan".format( float(sleepMS) / 1000), sleepMS) else: #no delay, don't use timers self.initiateNewSweep() else: #we're done sweeping self.sweeping = False self.ui.progress.setValue(0) #enable controls now that the sweep is complete self.ui.terminalsGroup.setEnabled(True) self.ui.wiresGroup.setEnabled(True) self.ui.modeGroup.setEnabled(True) self.ui.complianceGroup.setEnabled(True) self.ui.sweepGroup.setEnabled(True) self.ui.daqGroup.setEnabled(True) self.ui.outputCheck.setEnabled(True) self.ui.addressGroup.setEnabled(True) self.ui.sweepButton.setText('Start Sweep') self.ui.outputCheck.setChecked(self.userWantsOn) self.sendCmd( ':source:' + self.source + ' {0:.4f}'.format(float(self.ui.startSpin.value()) / 1000)) #self.sendCmd(":SYST:KEY 23") #update progress bar def updateProgress(self, value): self.ui.progress.setValue(value) #reverse sweep start and end points def reverseCall(self): startValue = self.ui.startSpin.value() endValue = self.ui.endSpin.value() #block signals momentarily while we swap things self.ui.endSpin.blockSignals(True) self.ui.startSpin.blockSignals(True) self.ui.endSpin.setValue(startValue) self.ui.startSpin.setValue(endValue) self.ui.endSpin.blockSignals(False) self.ui.startSpin.blockSignals(False) #update associated elements now that the swap is complete self.setStart() #turn output on or off when output box in gui changes state def setOutput(self): if self.ui.outputCheck.isChecked(): self.sendCmd(":output on") #self.sendCmd(":SYST:KEY 23") #go into local mode for live display update else: self.sendCmd(":output off") def initiateNewSweep(self): if self.ui.saveModeCombo.currentIndex( ) == 0: #this is an I,V vs t sweep #start sweeping and measuring self.readRealTimeDataThread.start() self.measureThread.start() self.sweepThread.start() else: # this is an I vs V sweep self.sendCmd(':source:' + self.source + ':mode sweep') self.ivDataThread.start() #do these things when the user presses the sweep button def manageSweep(self): if self.ui.maxPowerCheck.isChecked(): self.maxPowerDwell() #TODO this should go into the background else: if not self.sweeping: #disallow user from f*****g shit up while the sweep is taking place self.ui.terminalsGroup.setEnabled(False) self.ui.wiresGroup.setEnabled(False) self.ui.modeGroup.setEnabled(False) self.ui.complianceGroup.setEnabled(False) self.ui.sweepGroup.setEnabled(False) self.ui.daqGroup.setEnabled(False) self.ui.outputCheck.setEnabled(False) self.ui.addressGroup.setEnabled(False) #calculate sweep parameters from data in gui elements self.ui.outputCheck.setChecked(True) nPoints = float(self.ui.totalPointsSpin.value()) start = float(self.ui.startSpin.value()) / 1000 end = float(self.ui.endSpin.value()) / 1000 step = (end - start) / nPoints if start <= end: self.sweepUp = True else: self.sweepUp = False #sweep parameters dt = self.ui.delaySpinBox.value() sweepValues = np.linspace(start, end, nPoints) if self.ui.displayBlankCheck.isChecked(): self.sendCmd(':display:enable off' ) #this makes the device more responsive #send sweep parameters to the sweep thread self.sweepVaribles.emit(dt, sweepValues, self.source) self.sweeping = True self.initiateNewSweep() self.ui.sweepButton.setText('Abort Sweep') else: #sweep cancelled mid-run by user self.sweeping = False self.ui.statusbar.showMessage("Sweep aborted", self.messageDuration) self.ui.sweepButton.setEnabled(False) if hasattr(self, 'timerA') and self.timerA.isActive(): self.timerA.stop() self.timerB.stop() self.timerC.stop() else: #sweep dealy tiemrs are not running, we're mid-sweep, send the kill signal if self.ui.saveModeCombo.currentIndex( ) == 0: # we're in I,V vs t mode self.sweepThread.terminate() self.measureThread.timeToDie() else: # we're in I vs V mode self.k.clearInterface() self.doSweepComplete() def saveOutputFile(self): #TODO: save sweep direction if self.ui.saveModeCombo.currentIndex() == 0: # we're in I,V vs t mode self.postProcessThread.saveTime = True else: # we're in I vs V mode self.postProcessThread.saveTime = False self.postProcessThread.area = str(self.ui.deviceAreaEdit.text()) self.postProcessThread.savePath = os.path.join( str(self.ui.dirEdit.text()), str(self.ui.fileEdit.text())) self.postProcessThread.tempFile = QTemporaryFile() self.postProcessThread.sweepUp = self.sweepUp self.postProcessThread.start() def processingDone(self): self.ui.sweepButton.setEnabled(True) def initialSetup(self): try: #create the post processing thread and give it the keithley's done queue so that it can pull data from it self.postProcessThread = postProcessThread() self.ivDataThread = ivDataThread(self.k.task_queue, self.k.done_queue) self.ivDataThread.postData.connect( self.postProcessThread.acceptNewData) self.ivDataThread.postData.connect(self.doSweepComplete) #create the measurement thread and give it the keithley's task queue so that it can issue commands to it self.measureThread = measureThread(self.k.task_queue) #create the data reading thread and give it the keithley's done queue so that it can grab data from it self.readRealTimeDataThread = readRealTimeDataThread( self.k.done_queue) #create the sweep thread and give it the keithley's task queue so that it can issue commands to it self.sweepThread = sweepThread(self.k.task_queue) #self.measureThread.measureDone.connect(self.collectDataThread.catchPointNumber) self.measureThread.measureDone.connect( self.readRealTimeDataThread.updatePoints) #self.collectDataThread.readyToCollect.connect(self.collectDataThread.start) #now connect all the signals associated with these threads: #update the progress bar during the sweep self.sweepThread.updateProgress.connect(self.updateProgress) #update gui and shut off the output only when the last data point has been collected properly #self.collectAndSaveDataThread.dataCollectionDone.connect(self.doSweepComplete) #here the collected data is sent to the post processing thread self.readRealTimeDataThread.postData.connect( self.postProcessThread.acceptNewData) self.readRealTimeDataThread.postData.connect(self.doSweepComplete) #here the post process thread signals that it has the data and it's ready to start processing self.postProcessThread.readyToProcess.connect(self.saveOutputFile) #tell the measurement to stop when the sweep is done self.sweepThread.sweepComplete.connect( self.measureThread.timeToDie) #give the new user entered sweep variables to the sweep thread self.sweepVaribles.connect(self.sweepThread.updateVariables) self.postProcessThread.postProcessingComplete.connect( self.processingDone) #kill sweep early on user request #self.killSweepNow.connect(self.sweepThread.earlyKill) #self.killSweepNow.connect(self.collectAndSaveDataThread.earlyKill) #TODO: should immediately stop threads and purge queue on user cancel self.sendCmd(":format:data sreal") self.sendCmd(':system:beeper:state 0') #make this quiet #always measure current and voltage self.sendCmd(':sense:function:concurrent on') self.setTerminals() self.setWires() self.sendCmd( ":trace:feed:control never") #don't ever store data in buffer self.setZero() self.sendCmd(':sense:average:tcontrol repeat' ) #repeating averaging (not moving) self.setAverage() self.sendCmd(':format:elements time,voltage,current,status' ) #set data measurement elements self.sendCmd(':trigger:delay 0') self.sendCmd(':source:sweep:spacing linear') self.sendCmd(':source:sweep:ranging best') self.setMode() #sets output mode (current or voltage) self.setOutput() return True except: return False #do these things right after the user chooses on an instrument address def initialConnect(self, instrumentAddress): #this prevents the user from hammering this function through the GUI self.ui.sweepButton.setFocus() self.ui.sweepButton.setEnabled(False) try: #now that the user has selected an address for the keithley, let's connect to it. we'll use the thread safe version of the visa/gpib interface since we have multiple threads here self.k = gpib(instrumentAddress, useQueues=True, timeout=None) #self.k.task_queue.put(('clear',())) #self.sendCmd(':abort') self.sendCmd("*rst") #self.sendCmd('*cls') self.k.task_queue.put(('ask', ('*idn?', ))) try: ident = self.k.done_queue.get(block=True, timeout=10) self.ui.statusbar.showMessage("Connected to " + ident, self.messageDuration) except: ident = [] # let's be sure the firmware and model are what we expect (and what's tested to work) modelString = "MODEL 2400" firmwareString = "C33" if ident.__contains__(modelString): if ident.__contains__(firmwareString): self.k.task_queue.put(('ask', (':system:mep:state?', ))) isSCPI = self.k.done_queue.get() if isSCPI == '0': if self.initialSetup(): self.ui.sweepButton.setEnabled(True) self.ui.sweepButton.setFocus() self.ui.sweepButton.setDefault(True) else: self.closeInstrument() self.ui.statusbar.showMessage("Setup failed") else: self.closeInstrument() self.ui.statusbar.showMessage( "SCPI comms mode detected") msgBox = QMessageBox() msgBox.setWindowTitle( "SCPI mode detected. Please sqitch to 488.1 mode.") message488 = \ "Perform the following steps using the buttons on your sourcemeter to select the 488.1 protocol:\n" + \ "1. Press MENU to display the MAIN MENU.\n" + \ "2. Place the cursor on COMMUNICATION and press ENTER to display the COMMUNICATIONS SETUP menu.\n" + \ "3. Place the cursor on GPIB and press ENTER to display the present GPIB address.\n" + \ "4. Press ENTER to display the GPIB PROTOCOL menu.\n" + \ "5. Place the cursor on 488.1 and press ENTER.\n" + \ "6. Use the EXIT key to back out of the menu structure." msgBox.setText(message488) msgBox.exec_() else: self.closeInstrument() self.ui.statusbar.showMessage( '{0:s} found, firmware {1:s} not detected. Please upgrade firmware to continue.' .format(modelString, firmwareString)) else: self.closeInstrument() self.ui.statusbar.showMessage( 'Could not detect instrument with "{0:s}"'.format( modelString)) except: self.closeInstrument() self.ui.statusbar.showMessage("Connection failed") #tell keithely to change compliance when on gui compliance change events def setCompliance(self): self.ui.outputCheck.setChecked(False) value = float(self.ui.complianceSpin.value()) self.sendCmd(':sense:' + self.sense + ':protection {0:.3f}'.format(value / 1000)) self.sendCmd(':sense:' + self.sense + ':range {0:.3f}'.format(value / 1000)) self.ui.outputCheck.setChecked(self.userWantsOn) #tell keithely to change nplc and digits displayed when on gui speed change events def setSpeed(self): value = self.ui.speedCombo.currentIndex() if value is 0: #fast self.sendCmd(':sense:' + self.sense + ':nplcycles 0.01') self.sendCmd(':display:digits 4') elif value is 1: #med self.sendCmd(':sense:' + self.sense + ':nplcycles 0.1') self.sendCmd(':display:digits 5') elif value is 2: #normal self.sendCmd(':sense:' + self.sense + ':nplcycles 1') self.sendCmd(':display:digits 6') elif value is 3: #hi accuracy self.sendCmd(':sense:' + self.sense + ':nplcycles 10') self.sendCmd(':display:digits 7') #tell keithely to change the internal averaging it does on gui average change events def setAverage(self): value = self.ui.averageSpin.value() if value is 0: #no averaging self.sendCmd(':sense:average off') else: self.sendCmd(':sense:average on') self.sendCmd(':sense:average:count {0}'.format(value)) #tell keithley to enable/disable auto zero when the gui auto zero check box changes state def setZero(self): if self.ui.zeroCheck.isChecked(): self.sendCmd(":system:azero on") else: self.sendCmd(":system:azero off") #do all the things needed when the source sweep range is changed def setSourceRange(self): startValue = float(self.ui.startSpin.value()) endValue = float(self.ui.endSpin.value()) if self.ui.saveModeCombo.currentIndex( ) == 0: #only set max here if we're in i,v vs t mode span = abs(endValue - startValue) + 1 self.ui.totalPointsSpin.setMaximum(span) self.updateDeltaText() maxAbs = max(abs(startValue), abs(endValue)) self.sendCmd(':source:' + self.source + ':range {0:.3f}'.format(maxAbs / 1000)) #do what needs to be done when the sweep start value is modified def setStart(self): startValue = float(self.ui.startSpin.value()) self.setSourceRange() self.sendCmd('source:' + self.source + ' {0:.3f}'.format(startValue / 1000)) #do these things when the user changes the sweep mode (from voltage to current or the reverse) def setMode(self): self.ui.outputCheck.setChecked( False) #output gets shut off during source change if self.ui.sourceVRadio.isChecked(): #sweep in voltage self.source = "voltage" else: #sweep in current self.source = "current" self.ui.outputCheck.setChecked(self.userWantsOn) self.sendCmd(":source:function " + self.source) if self.ui.sourceVRadio.isChecked(): #sweep in voltage self.sense = "current" self.sourceUnit = 'V' self.complianceUnit = 'A' self.ui.startSpin.setRange(-20000, 20000) self.ui.endSpin.setRange(-20000, 20000) self.ui.complianceSpin.setRange(1, 1000) self.sendCmd(':sense:function "current:dc", "voltage:dc"') self.sendCmd(":source:" + self.source + ":mode fixed") #fixed output mode else: #sweep in current self.sense = "voltage" self.sourceUnit = 'A' self.complianceUnit = 'V' self.ui.startSpin.setRange(-1000, 1000) self.ui.endSpin.setRange(-1000, 1000) self.ui.complianceSpin.setRange(1, 20000) self.sendCmd(':sense:function "voltage:dc","current:dc"') self.sendCmd(":source:" + self.source + ":mode fixed") #fixed output mode self.ui.startSpin.setSuffix(' m{0:}'.format(self.sourceUnit)) self.ui.endSpin.setSuffix(' m{0:}'.format(self.sourceUnit)) self.ui.complianceSpin.setSuffix(' m{0:}'.format(self.complianceUnit)) self.setStart() self.setCompliance() self.setSpeed() self.handleModeCombo() #sets i vs v or i,v vs t mode #do these things just before program termination to ensure the computer and instrument are left in a friendly state def closeInstrument(self): try: self.sweeping = False self.measureThread.timeToDie() except: pass try: #self.killSweepNow.emit() self.sweepThread.terminate() except: pass try: self.k.task_queue.put(('clear', ())) except: pass #TODO: reenable this #gpib().clearInterface() self.sendCmd(':abort') self.sendCmd(':arm:count 1') self.sendCmd(":display:enable on") self.sendCmd(":display:window1:text:state off") self.sendCmd(":display:window2:text:state off") self.sendCmd('*rst') self.sendCmd('*cls') self.sendCmd(':system:key 23') try: self.k.__del__() #cleanup del self.k #remove except: pass def setTerminals(self): self.ui.outputCheck.setChecked(False) if self.ui.frontRadio.isChecked(): self.sendCmd(":route:terminals front") else: self.sendCmd(":route:terminals rear") self.ui.outputCheck.setChecked(self.userWantsOn) def updateDeltaText(self): #tTot = float(self.ui.totalTimeSpin.value()) dt = self.ui.delaySpinBox.value() nPoints = float(self.ui.totalPointsSpin.value()) start = float(self.ui.startSpin.value()) end = float(self.ui.endSpin.value()) span = end - start tTot = dt * nPoints if self.ui.saveModeCombo.currentIndex() == 1: # we're in i vs v mode self.sendCmd(':trigger:count {0:d}'.format(int(nPoints))) self.sendCmd(":source:delay {0:0.3f}".format(dt)) self.sendCmd(':source:sweep:points {0:d}'.format(int(nPoints))) self.sendCmd(':source:' + self.source + ':start {0:.3f}'.format(start / 1000)) self.sendCmd(':source:' + self.source + ':stop {0:.3f}'.format(end / 1000)) #self.sendCmd(':source:'+self.source+':step {0:.3f}'.format(step)) if tTot >= 60: timeText = QString(u'tot={0:.1f} min'.format(tTot / 60)) else: timeText = QString(u'tot={0:.3f} s'.format(tTot)) self.ui.totalLabel.setText(timeText) if nPoints == 1: stepText = QString(u'Δ=NaN m{0:}'.format(self.sourceUnit)) else: stepText = QString(u'Δ={0:.0f} m{1:}'.format( span / (nPoints - 1), self.sourceUnit)) self.ui.deltaStep.setText(stepText) def setWires(self): self.ui.outputCheck.setChecked(False) if self.ui.twowireRadio.isChecked(): self.sendCmd(":system:rsense OFF") else: self.sendCmd(":system:rsense ON") self.ui.outputCheck.setChecked(self.userWantsOn) def sendCmd(self, cmdString): try: self.k.write(cmdString) #self.k.write(":system:key 23") #go into local mode for live display update AFTER EVERY COMMAND! except: self.ui.statusbar.showMessage("Command failed", self.messageDuration)
class MainWindow(QMainWindow): sweepVaribles = pyqtSignal(float,np.ndarray,str) #killSweepNow = pyqtSignal() sweepUp = True userWantsOn = False #the user wants the output off def __init__(self): QMainWindow.__init__(self) self.instrumentDetectThread = instrumentDetectThread() self.instrumentDetectThread.foundInstruments.connect(self.catchList) self.settings = QSettings("greyltc", "ivSweeper") #how long status messages show for self.messageDuration = 1000#ms #variables to keep track of what we're sourcing/sensing self.source = "voltage" self.sense = "current" self.senseUnit = 'A' self.sourceUnit = 'V' #variable to keep track of if a sweep is ongoing self.sweeping = False # Set up the user interface from Designer. self.ui = Ui_IVSweeper() self.ui.setupUi(self) if self.settings.contains('lastFolder'): self.ui.dirEdit.setText(self.settings.value('lastFolder').toString()) #connect signals generated by gui elements to proper functions self.ui.sweepButton.clicked.connect(self.manageSweep) self.ui.shutterButton.clicked.connect(self.handleShutter) self.ui.frontRadio.toggled.connect(self.setTerminals) self.ui.sourceVRadio.toggled.connect(self.setMode) self.ui.twowireRadio.toggled.connect(self.setWires) self.ui.zeroCheck.toggled.connect(self.setZero) self.ui.speedCombo.currentIndexChanged.connect(self.setSpeed) self.ui.averageSpin.valueChanged.connect(self.setAverage) self.ui.complianceSpin.valueChanged.connect(self.setCompliance) self.ui.startSpin.valueChanged.connect(self.setStart) self.ui.endSpin.valueChanged.connect(self.setSourceRange) self.ui.outputCheck.toggled.connect(self.setOutput) self.ui.delaySpinBox.valueChanged.connect(self.updateDeltaText) self.ui.totalPointsSpin.valueChanged.connect(self.updateDeltaText) self.ui.reverseButton.clicked.connect(self.reverseCall) self.ui.actionRun_Test_Code.triggered.connect(self.testArea) self.ui.instrumentCombo.activated.connect(self.handleICombo) self.ui.saveModeCombo.currentIndexChanged.connect(self.handleModeCombo) self.ui.browseButton.clicked.connect(self.browseButtonCall) self.ui.outputCheck.clicked.connect(self.toggleWhatUserWants) #TODO: load state here #self.restoreState(self.settings.value('guiState').toByteArray()) #this keeps track of clicks on the output/on off box def toggleWhatUserWants(self): self.userWantsOn = not self.userWantsOn def handleModeCombo(self): dt = self.ui.delaySpinBox.value() if self.ui.saveModeCombo.currentIndex() == 0: #i,v vs t mode startValue = float(self.ui.startSpin.value()) endValue = float(self.ui.endSpin.value()) span = abs(endValue-startValue)+1 self.ui.totalPointsSpin.setMaximum(span) self.ui.totalPointsSpin.setMinimum(1) self.sendCmd(":source:delay 0") self.sendCmd(':source:'+self.source+':mode fixed') self.sendCmd(':trigger:count 1') #fast response and no auto zeroing in i,v vs t mode self.ui.speedCombo.setCurrentIndex(0) self.ui.zeroCheck.setChecked(False) else: #traditional i vs v mode #TODO: figure out why this mode is double scanning for 70 points 1 sec delay self.ui.totalPointsSpin.setMaximum(2500) #standard sweeps can be at most 2500 points long (keithley limitation) self.ui.totalPointsSpin.setMinimum(2) self.ui.speedCombo.setCurrentIndex(3)#go slow here self.sendCmd(":source:delay {0:0.3f}".format(dt)) self.sendCmd(':source:'+self.source+':mode sweep') nPoints = float(self.ui.totalPointsSpin.value()) self.sendCmd(':trigger:count {0:d}'.format(int(nPoints))) #high accuracy and auto zeroing in i vs v mode self.ui.speedCombo.setCurrentIndex(3) self.ui.zeroCheck.setChecked(True) def catchList(self,resourceNames): for i in range(self.ui.instrumentCombo.count()): self.ui.instrumentCombo.removeItem(0) self.ui.instrumentCombo.setEnabled(True) self.ui.instrumentCombo.insertItem(0,'Select Instrument') self.ui.instrumentCombo.insertItem(1,'(Re)Scan for Instruments') i = 2 for instrument in resourceNames: self.ui.instrumentCombo.insertItem(i,instrument) i = i +1 self.ui.instrumentCombo.setCurrentIndex(0) def handleICombo(self,index): #index = self.ui.instrumentCombo.currentIndex() thisString = str(self.ui.instrumentCombo.currentText()) scanString = '(Re)Scan for Instruments' selectString = 'Select Instrument' noneString = "None found" if thisString == scanString: self.ui.instrumentCombo.setItemText(index,'Searching...') self.ui.instrumentCombo.setEnabled(False) self.instrumentDetectThread.start() elif (thisString == selectString) or (thisString == noneString): pass else: self.initialConnect(thisString) def browseButtonCall(self): dirName = QFileDialog.getExistingDirectory(directory=self.ui.dirEdit.text()) self.settings.setValue('lastFolder',dirName) self.ui.dirEdit.setText(dirName) #return -1 * the power of the device at a given voltage or current def invPower(self,request): request = request[0] #TODO: remove this testing current fudge value #currentFudge = 0.004; currentFudge = 0; try: print request self.sendCmd('source:'+self.source+' {0:.3f}'.format(request)) self.k.task_queue.put(('read_raw',())) #TODO: this should be split to another function data = qBinRead(self.k.done_queue) return (data[0]*(data[1]-currentFudge)) except: self.ui.statusbar.showMessage("Error: Not connected",self.messageDuration); return np.nan def handleShutter(self): shutterOnValue = '14' shutterOffValue = '15' self.k.task_queue.put(('ask',(':source2:ttl:actual?',))) #TODO: this should be split to another function outStatus = self.k.done_queue.get() if outStatus == shutterOnValue: self.sendCmd(":source2:ttl " + shutterOffValue) else: self.sendCmd(":source2:ttl " + shutterOnValue) #TODO: move this to its own thread def maxPowerDwell(self): voltageSourceRange = 3 # operate between +/- 3V currentSourceRange = 0.1 # operate between +/- 100ma if self.sourceUnit == 'V': self.sendCmd(':source:'+self.source+':range {0:.3f}'.format(voltageSourceRange)) initialGuess = 0.7 else: self.sendCmd(':source:'+self.source+':range {0:.3f}'.format(currentSourceRange)) initialGuess = 0.01 # no idea if this is right dt = self.ui.delaySpinBox.value() nPoints = float(self.ui.totalPointsSpin.value()) self.ui.outputCheck.setChecked(True) oldSpeedIndex = self.ui.speedCombo.currentIndex() self.ui.speedCombo.setCurrentIndex(2) for i in range(int(nPoints)): optResults = optimize.minimize(self.invPower,initialGuess,method='COBYLA',tol=1e-4,options={'rhobeg':0.2}) print optResults.message print optResults.status answer = float(optResults.x) initialGuess = answer self.sendCmd(":SYST:KEY 23") #go into local mode for live display update print "Optimized! Mpp Voltage: {0:.3f}".format(answer) print "Now sleeping for {0:.1f} seconds".format(dt) time.sleep(dt) self.k.task_queue.put(('read_raw',())) data = qBinRead(self.k.done_queue) #vi = (data[0], data[1], data[1]*data[0]*1000/.4*-1) print 'Max Power: {0:.3f}% '.format(data[0]*data[1]*1000/float(self.ui.deviceAreaEdit.text())) self.ui.outputCheck.setChecked(self.userWantsOn) self.ui.speedCombo.setCurrentIndex(oldSpeedIndex) def testArea(self): print('Running test code now') #self.maxPowerDwell() #x = np.random.randn(10000) #np.hist(x, 100) def closeEvent(self,event): #TODO: save state here #self.settings.setValue('guiState',self.saveState()) self.closeInstrument() QMainWindow.closeEvent(self,event) #do these things when a sweep completes (or is canceled by the user) def doSweepComplete(self): if self.ui.sweepContinuallyGroup.isChecked() and self.sweeping: #in continual sweep mode, perform another sweep self.sendCmd(':source:' + self.source + ' {0:.4f}'.format(float(self.ui.startSpin.value())/1000)) if self.ui.displayBlankCheck.isChecked(): self.sendCmd(':display:enable off')#this makes the device more responsive else: self.sendCmd(':display:enable on')#this makes the device more responsive sleepMS = int(self.ui.scanRecoverySpin.value()*1000) if sleepMS > 0: #start these after the user specified delay self.timerA = QTimer() self.timerA.timeout.connect(self.initiateNewSweep) self.timerA.setSingleShot(True) self.timerA.start(sleepMS) self.ui.statusbar.showMessage("Sleeping for {0:.1f} s before next scan".format(float(sleepMS)/1000),sleepMS) else: #no delay, don't use timers self.initiateNewSweep() else:#we're done sweeping self.sweeping = False self.ui.progress.setValue(0) #enable controls now that the sweep is complete self.ui.terminalsGroup.setEnabled(True) self.ui.wiresGroup.setEnabled(True) self.ui.modeGroup.setEnabled(True) self.ui.complianceGroup.setEnabled(True) self.ui.sweepGroup.setEnabled(True) self.ui.daqGroup.setEnabled(True) self.ui.outputCheck.setEnabled(True) self.ui.addressGroup.setEnabled(True) self.ui.sweepButton.setText('Start Sweep') self.ui.outputCheck.setChecked(self.userWantsOn) self.sendCmd(':source:' + self.source + ' {0:.4f}'.format(float(self.ui.startSpin.value())/1000)) #self.sendCmd(":SYST:KEY 23") #update progress bar def updateProgress(self, value): self.ui.progress.setValue(value) #reverse sweep start and end points def reverseCall(self): startValue = self.ui.startSpin.value() endValue = self.ui.endSpin.value() #block signals momentarily while we swap things self.ui.endSpin.blockSignals(True) self.ui.startSpin.blockSignals(True) self.ui.endSpin.setValue(startValue) self.ui.startSpin.setValue(endValue) self.ui.endSpin.blockSignals(False) self.ui.startSpin.blockSignals(False) #update associated elements now that the swap is complete self.setStart() #turn output on or off when output box in gui changes state def setOutput(self): if self.ui.outputCheck.isChecked(): self.sendCmd(":output on") #self.sendCmd(":SYST:KEY 23") #go into local mode for live display update else: self.sendCmd(":output off") def initiateNewSweep(self): if self.ui.saveModeCombo.currentIndex() == 0: #this is an I,V vs t sweep #start sweeping and measuring self.readRealTimeDataThread.start() self.measureThread.start() self.sweepThread.start() else: # this is an I vs V sweep self.sendCmd(':source:'+self.source+':mode sweep') self.ivDataThread.start() #do these things when the user presses the sweep button def manageSweep(self): if self.ui.maxPowerCheck.isChecked(): self.maxPowerDwell() #TODO this should go into the background else: if not self.sweeping: #disallow user from f*****g shit up while the sweep is taking place self.ui.terminalsGroup.setEnabled(False) self.ui.wiresGroup.setEnabled(False) self.ui.modeGroup.setEnabled(False) self.ui.complianceGroup.setEnabled(False) self.ui.sweepGroup.setEnabled(False) self.ui.daqGroup.setEnabled(False) self.ui.outputCheck.setEnabled(False) self.ui.addressGroup.setEnabled(False) #calculate sweep parameters from data in gui elements self.ui.outputCheck.setChecked(True) nPoints = float(self.ui.totalPointsSpin.value()) start = float(self.ui.startSpin.value())/1000 end = float(self.ui.endSpin.value())/1000 step = (end - start)/nPoints if start <= end: self.sweepUp = True else: self.sweepUp = False #sweep parameters dt = self.ui.delaySpinBox.value() sweepValues = np.linspace(start,end,nPoints) if self.ui.displayBlankCheck.isChecked(): self.sendCmd(':display:enable off')#this makes the device more responsive #send sweep parameters to the sweep thread self.sweepVaribles.emit(dt,sweepValues,self.source) self.sweeping = True self.initiateNewSweep() self.ui.sweepButton.setText('Abort Sweep') else:#sweep cancelled mid-run by user self.sweeping = False self.ui.statusbar.showMessage("Sweep aborted",self.messageDuration) self.ui.sweepButton.setEnabled(False) if hasattr(self,'timerA') and self.timerA.isActive(): self.timerA.stop() self.timerB.stop() self.timerC.stop() else:#sweep dealy tiemrs are not running, we're mid-sweep, send the kill signal if self.ui.saveModeCombo.currentIndex() == 0:# we're in I,V vs t mode self.sweepThread.terminate() self.measureThread.timeToDie() else: # we're in I vs V mode self.k.clearInterface() self.doSweepComplete() def saveOutputFile(self): #TODO: save sweep direction if self.ui.saveModeCombo.currentIndex() == 0:# we're in I,V vs t mode self.postProcessThread.saveTime = True else: # we're in I vs V mode self.postProcessThread.saveTime = False self.postProcessThread.area = str(self.ui.deviceAreaEdit.text()) self.postProcessThread.savePath = os.path.join(str(self.ui.dirEdit.text()),str(self.ui.fileEdit.text())) self.postProcessThread.tempFile = QTemporaryFile() self.postProcessThread.sweepUp = self.sweepUp self.postProcessThread.start() def processingDone(self): self.ui.sweepButton.setEnabled(True) def initialSetup(self): try: #create the post processing thread and give it the keithley's done queue so that it can pull data from it self.postProcessThread = postProcessThread() self.ivDataThread = ivDataThread(self.k.task_queue,self.k.done_queue) self.ivDataThread.postData.connect(self.postProcessThread.acceptNewData) self.ivDataThread.postData.connect(self.doSweepComplete) #create the measurement thread and give it the keithley's task queue so that it can issue commands to it self.measureThread = measureThread(self.k.task_queue) #create the data reading thread and give it the keithley's done queue so that it can grab data from it self.readRealTimeDataThread = readRealTimeDataThread(self.k.done_queue) #create the sweep thread and give it the keithley's task queue so that it can issue commands to it self.sweepThread = sweepThread(self.k.task_queue) #self.measureThread.measureDone.connect(self.collectDataThread.catchPointNumber) self.measureThread.measureDone.connect(self.readRealTimeDataThread.updatePoints) #self.collectDataThread.readyToCollect.connect(self.collectDataThread.start) #now connect all the signals associated with these threads: #update the progress bar during the sweep self.sweepThread.updateProgress.connect(self.updateProgress) #update gui and shut off the output only when the last data point has been collected properly #self.collectAndSaveDataThread.dataCollectionDone.connect(self.doSweepComplete) #here the collected data is sent to the post processing thread self.readRealTimeDataThread.postData.connect(self.postProcessThread.acceptNewData) self.readRealTimeDataThread.postData.connect(self.doSweepComplete) #here the post process thread signals that it has the data and it's ready to start processing self.postProcessThread.readyToProcess.connect(self.saveOutputFile) #tell the measurement to stop when the sweep is done self.sweepThread.sweepComplete.connect(self.measureThread.timeToDie) #give the new user entered sweep variables to the sweep thread self.sweepVaribles.connect(self.sweepThread.updateVariables) self.postProcessThread.postProcessingComplete.connect(self.processingDone) #kill sweep early on user request #self.killSweepNow.connect(self.sweepThread.earlyKill) #self.killSweepNow.connect(self.collectAndSaveDataThread.earlyKill) #TODO: should immediately stop threads and purge queue on user cancel self.sendCmd(":format:data sreal") self.sendCmd(':system:beeper:state 0') #make this quiet #always measure current and voltage self.sendCmd(':sense:function:concurrent on') self.setTerminals() self.setWires() self.sendCmd(":trace:feed:control never") #don't ever store data in buffer self.setZero() self.sendCmd(':sense:average:tcontrol repeat') #repeating averaging (not moving) self.setAverage() self.sendCmd(':format:elements time,voltage,current,status') #set data measurement elements self.sendCmd(':trigger:delay 0') self.sendCmd(':source:sweep:spacing linear') self.sendCmd(':source:sweep:ranging best') self.setMode() #sets output mode (current or voltage) self.setOutput() return True except: return False #do these things right after the user chooses on an instrument address def initialConnect(self,instrumentAddress): #this prevents the user from hammering this function through the GUI self.ui.sweepButton.setFocus() self.ui.sweepButton.setEnabled(False) try: #now that the user has selected an address for the keithley, let's connect to it. we'll use the thread safe version of the visa/gpib interface since we have multiple threads here self.k = gpib(instrumentAddress,useQueues=True,timeout=None) #self.k.task_queue.put(('clear',())) #self.sendCmd(':abort') self.sendCmd("*rst") #self.sendCmd('*cls') self.k.task_queue.put(('ask',('*idn?',))) try: ident = self.k.done_queue.get(block=True,timeout=10) self.ui.statusbar.showMessage("Connected to " + ident,self.messageDuration) except: ident = [] # let's be sure the firmware and model are what we expect (and what's tested to work) modelString = "MODEL 2400" firmwareString = "C33" if ident.__contains__(modelString): if ident.__contains__(firmwareString): self.k.task_queue.put(('ask',(':system:mep:state?',))) isSCPI = self.k.done_queue.get() if isSCPI == '0': if self.initialSetup(): self.ui.sweepButton.setEnabled(True) self.ui.sweepButton.setFocus() self.ui.sweepButton.setDefault(True) else: self.closeInstrument() self.ui.statusbar.showMessage("Setup failed") else: self.closeInstrument() self.ui.statusbar.showMessage("SCPI comms mode detected") msgBox = QMessageBox() msgBox.setWindowTitle("SCPI mode detected. Please sqitch to 488.1 mode.") message488 = \ "Perform the following steps to select the 488.1 protocol:\n" + \ "1. Press MENU to display the MAIN MENU.\n" + \ "2. Place the cursor on COMMUNICATION and press ENTER to display the COMMUNICATIONS SETUP menu.\n" + \ "3. Place the cursor on GPIB and press ENTER to display the present GPIB address.\n" + \ "4. Press ENTER to display the GPIB PROTOCOL menu.\n" + \ "5. Place the cursor on 488.1 and press ENTER.\n" + \ "6. Use the EXIT key to back out of the menu structure." msgBox.setText(message488); msgBox.exec_(); else: self.closeInstrument() self.ui.statusbar.showMessage('{0:s} found, firmware {1:s} not detected. Please upgrade firmware to continue.'.format(modelString,firmwareString)) else: self.closeInstrument() self.ui.statusbar.showMessage('Could not detect instrument with "{0:s}"'.format(modelString)) except: self.closeInstrument() self.ui.statusbar.showMessage("Connection failed") #tell keithely to change compliance when on gui compliance change events def setCompliance(self): self.ui.outputCheck.setChecked(False) value = float(self.ui.complianceSpin.value()) self.sendCmd(':sense:'+self.sense+':protection {0:.3f}'.format(value/1000)) self.sendCmd(':sense:'+self.sense+':range {0:.3f}'.format(value/1000)) self.ui.outputCheck.setChecked(self.userWantsOn) #tell keithely to change nplc and digits displayed when on gui speed change events def setSpeed(self): value = self.ui.speedCombo.currentIndex() if value is 0: #fast self.sendCmd(':sense:'+self.sense+':nplcycles 0.01') self.sendCmd(':display:digits 4') elif value is 1: #med self.sendCmd(':sense:'+self.sense+':nplcycles 0.1') self.sendCmd(':display:digits 5') elif value is 2: #normal self.sendCmd(':sense:'+self.sense+':nplcycles 1') self.sendCmd(':display:digits 6') elif value is 3: #hi accuracy self.sendCmd(':sense:'+self.sense+':nplcycles 10') self.sendCmd(':display:digits 7') #tell keithely to change the internal averaging it does on gui average change events def setAverage(self): value = self.ui.averageSpin.value() if value is 0: #no averaging self.sendCmd(':sense:average off') else: self.sendCmd(':sense:average on') self.sendCmd(':sense:average:count {0}'.format(value)) #tell keithley to enable/disable auto zero when the gui auto zero check box changes state def setZero(self): if self.ui.zeroCheck.isChecked(): self.sendCmd(":system:azero on") else: self.sendCmd(":system:azero off") #do all the things needed when the source sweep range is changed def setSourceRange(self): startValue = float(self.ui.startSpin.value()) endValue = float(self.ui.endSpin.value()) if self.ui.saveModeCombo.currentIndex() == 0: #only set max here if we're in i,v vs t mode span = abs(endValue-startValue)+1 self.ui.totalPointsSpin.setMaximum(span) self.updateDeltaText() maxAbs = max(abs(startValue),abs(endValue)) self.sendCmd(':source:'+self.source+':range {0:.3f}'.format(maxAbs/1000)) #do what needs to be done when the sweep start value is modified def setStart(self): startValue = float(self.ui.startSpin.value()) self.setSourceRange() self.sendCmd('source:'+self.source+' {0:.3f}'.format(startValue/1000)) #do these things when the user changes the sweep mode (from voltage to current or the reverse) def setMode(self): self.ui.outputCheck.setChecked(False) #output gets shut off during source change if self.ui.sourceVRadio.isChecked(): #sweep in voltage self.source = "voltage" else:#sweep in current self.source = "current" self.ui.outputCheck.setChecked(self.userWantsOn) self.sendCmd(":source:function " + self.source) if self.ui.sourceVRadio.isChecked(): #sweep in voltage self.sense = "current" self.sourceUnit = 'V' self.complianceUnit = 'A' self.ui.startSpin.setRange(-20000,20000) self.ui.endSpin.setRange(-20000,20000) self.ui.complianceSpin.setRange(1,1000) self.sendCmd(':sense:function "current:dc", "voltage:dc"') self.sendCmd(":source:"+self.source+":mode fixed") #fixed output mode else: #sweep in current self.sense = "voltage" self.sourceUnit = 'A' self.complianceUnit = 'V' self.ui.startSpin.setRange(-1000,1000) self.ui.endSpin.setRange(-1000,1000) self.ui.complianceSpin.setRange(1,20000) self.sendCmd(':sense:function "voltage:dc","current:dc"') self.sendCmd(":source:"+self.source+":mode fixed") #fixed output mode self.ui.startSpin.setSuffix(' m{0:}'.format(self.sourceUnit)) self.ui.endSpin.setSuffix(' m{0:}'.format(self.sourceUnit)) self.ui.complianceSpin.setSuffix(' m{0:}'.format(self.complianceUnit)) self.setStart() self.setCompliance() self.setSpeed() self.handleModeCombo() #sets i vs v or i,v vs t mode #do these things just before program termination to ensure the computer and instrument are left in a friendly state def closeInstrument(self): try: self.sweeping = False self.measureThread.timeToDie() except: pass try: #self.killSweepNow.emit() self.sweepThread.terminate() except: pass try: self.k.task_queue.put(('clear',())) except: pass #TODO: reenable this #gpib().clearInterface() self.sendCmd(':abort') self.sendCmd(':arm:count 1') self.sendCmd(":display:enable on") self.sendCmd(":display:window1:text:state off") self.sendCmd(":display:window2:text:state off") self.sendCmd('*rst') self.sendCmd('*cls') self.sendCmd(':system:key 23') try: self.k.__del__() #cleanup del self.k #remove except: pass def setTerminals(self): self.ui.outputCheck.setChecked(False) if self.ui.frontRadio.isChecked(): self.sendCmd(":route:terminals front") else: self.sendCmd(":route:terminals rear") self.ui.outputCheck.setChecked(self.userWantsOn) def updateDeltaText(self): #tTot = float(self.ui.totalTimeSpin.value()) dt = self.ui.delaySpinBox.value() nPoints = float(self.ui.totalPointsSpin.value()) start = float(self.ui.startSpin.value()) end = float(self.ui.endSpin.value()) span = end-start tTot = dt*nPoints if self.ui.saveModeCombo.currentIndex() == 1:# we're in i vs v mode self.sendCmd(':trigger:count {0:d}'.format(int(nPoints))) self.sendCmd(":source:delay {0:0.3f}".format(dt)) self.sendCmd(':source:sweep:points {0:d}'.format(int(nPoints))) self.sendCmd(':source:'+self.source+':start {0:.3f}'.format(start/1000)) self.sendCmd(':source:'+self.source+':stop {0:.3f}'.format(end/1000)) #self.sendCmd(':source:'+self.source+':step {0:.3f}'.format(step)) if tTot >= 60: timeText = QString(u'tot={0:.1f} min'.format(tTot/60)) else: timeText = QString(u'tot={0:.3f} s'.format(tTot)) self.ui.totalLabel.setText(timeText) if nPoints == 1: stepText = QString(u'Δ=NaN m{0:}'.format(self.sourceUnit)) else: stepText = QString(u'Δ={0:.0f} m{1:}'.format(span/(nPoints-1),self.sourceUnit)) self.ui.deltaStep.setText(stepText) def setWires(self): self.ui.outputCheck.setChecked(False) if self.ui.twowireRadio.isChecked(): self.sendCmd(":system:rsense OFF") else: self.sendCmd(":system:rsense ON") self.ui.outputCheck.setChecked(self.userWantsOn) def sendCmd(self,cmdString): try: self.k.write(cmdString) #self.k.write(":system:key 23") #go into local mode for live display update AFTER EVERY COMMAND! except: self.ui.statusbar.showMessage("Command failed",self.messageDuration);