├── .gitignore ├── .K2636_connections.png ├── TSP-scripts ├── legacy │ ├── .transfer-charact.tsp.backup.swp │ ├── iv-sweep-undoped.tsp │ ├── transfer-charact.tsp.backup │ ├── gate-leakage.tsp │ └── keithley-iv.tsp ├── .orig │ ├── iv-sweep.tsp │ ├── transfer-charact.tsp │ ├── transfer-charact-2.tsp │ └── output-charact.tsp ├── backup │ ├── transfer-charact.tsp │ ├── transfer-charact-2.tsp │ └── output-charact.tsp.backup ├── output-charact.tsp ├── inverter.tsp ├── inverter-reverse.tsp ├── iv-sweep.tsp ├── transfer-charact.tsp └── transfer-charact-2.tsp ├── tsp.py ├── LICENSE ├── README.md ├── ofetMeasureCLI.py ├── ofetMeasure.py ├── k2636.py └── ofetMeasureGUI.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.csv 3 | -------------------------------------------------------------------------------- /.K2636_connections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFMD/keithley-2636/HEAD/.K2636_connections.png -------------------------------------------------------------------------------- /TSP-scripts/legacy/.transfer-charact.tsp.backup.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFMD/keithley-2636/HEAD/TSP-scripts/legacy/.transfer-charact.tsp.backup.swp -------------------------------------------------------------------------------- /tsp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module for writing and parsing .tsp files. 3 | 4 | Author: Ross 5 | """ 6 | 7 | import csv 8 | 9 | def read_tsp: 10 | """Method for reading a tsp file line by line.""" 11 | pass 12 | 13 | def write_tsp(file2write2, string2write, lineNo): 14 | """Write a string to a tsp file at specific line number.""" 15 | with file2write2 as open(file2write2, 'w'): 16 | print(file2write2) 17 | 18 | write_tsp(file2write2, string2write, lineNo) 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Advanced Functional Materials and Devices Group @ Oxford 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /TSP-scripts/.orig/iv-sweep.tsp: -------------------------------------------------------------------------------- 1 | reset() 2 | display.clear() 3 | 4 | -- Beep in excitement 5 | beeper.beep(1, 600) 6 | 7 | 8 | -- TRANSFER CHARACTERISTICS 9 | -- Clear buffers and make sure the right thing is recorded 10 | smua.nvbuffer1.clear() 11 | smub.nvbuffer1.clear() 12 | smua.nvbuffer1.collectsourcevalues = 1 13 | smub.nvbuffer1.collectsourcevalues = 1 14 | format.data = format.ASCII 15 | smua.nvbuffer1.appendmode = 1 16 | smub.nvbuffer1.appendmode = 1 17 | smua.measure.count = 1 18 | smub.measure.count = 1 19 | 20 | 21 | -- Measurement Setup 22 | -- To adjust the delay factor. 23 | smua.measure.delayfactor = 1.0 24 | -- Set Vd 25 | smua.source.levelv = 0.0 26 | --Channel 2 (sweep Vg) 27 | smub.source.func = smub.OUTPUT_DCVOLTS 28 | smub.source.levelv = 0.0 29 | -- Channel 1 (source Vd, meas Id) 30 | smua.source.func = smua.OUTPUT_DCVOLTS 31 | smua.sense = smua.SENSE_LOCAL 32 | smua.source.autorangev = smua.AUTORANGE_ON 33 | -- Set complaince to 1uA 34 | smua.source.limiti = 1e-5 35 | smua.measure.rangei = 1e-5 36 | smub.source.limiti = 1e-6 37 | smua.measure.nplc = 10 38 | 39 | -- Measurement 40 | display.smua.measure.func = display.MEASURE_DCAMPS 41 | display.screen = display.SMUA 42 | 43 | smua.source.output = smub.OUTPUT_ON 44 | smua.source.levelv = -50 45 | delay(5) 46 | 47 | for Vd = -50, 50 do 48 | smua.source.levelv = Vd * (1) 49 | smua.measure.i(smua.nvbuffer1) 50 | smub.measure.v(smub.nvbuffer1) 51 | delay(0.2) 52 | end 53 | 54 | smua.source.output = smua.OUTPUT_OFF 55 | smub.source.output = smub.OUTPUT_OFF 56 | -------------------------------------------------------------------------------- /TSP-scripts/legacy/iv-sweep-undoped.tsp: -------------------------------------------------------------------------------- 1 | reset() 2 | display.clear() 3 | 4 | -- Beep in excitement 5 | beeper.beep(1, 600) 6 | 7 | 8 | -- TRANSFER CHARACTERISTICS 9 | -- Clear buffers and make sure the right thing is recorded 10 | smua.nvbuffer1.clear() 11 | smub.nvbuffer1.clear() 12 | smua.nvbuffer1.collectsourcevalues = 1 13 | smub.nvbuffer1.collectsourcevalues = 1 14 | format.data = format.ASCII 15 | smua.nvbuffer1.appendmode = 1 16 | smub.nvbuffer1.appendmode = 1 17 | smua.measure.count = 1 18 | smub.measure.count = 1 19 | 20 | 21 | -- Measurement Setup 22 | -- To adjust the delay factor. 23 | smua.measure.delayfactor = 1.0 24 | -- Set Vd 25 | smua.source.levelv = 0.0 26 | --Channel 2 (sweep Vg) 27 | smub.source.func = smub.OUTPUT_DCVOLTS 28 | smub.source.levelv = 0.0 29 | -- Channel 1 (source Vd, meas Id) 30 | smua.source.func = smua.OUTPUT_DCVOLTS 31 | smua.sense = smua.SENSE_LOCAL 32 | smua.source.autorangev = smua.AUTORANGE_ON 33 | -- Set complaince to 1uA 34 | smua.source.limiti = 1e-5 35 | smua.measure.rangei = 1e-5 36 | smub.source.limiti = 1e-6 37 | smua.measure.nplc = 10 38 | 39 | -- Measurement 40 | display.smua.measure.func = display.MEASURE_DCAMPS 41 | display.screen = display.SMUA 42 | 43 | smua.source.output = smub.OUTPUT_ON 44 | smua.source.levelv = -50 45 | delay(5) 46 | 47 | for Vd = -50, 50 do 48 | smua.source.levelv = Vd * (1) 49 | smua.measure.i(smua.nvbuffer1) 50 | smub.measure.v(smub.nvbuffer1) 51 | delay(0.2) 52 | end 53 | 54 | smua.source.output = smua.OUTPUT_OFF 55 | smub.source.output = smub.OUTPUT_OFF 56 | -------------------------------------------------------------------------------- /TSP-scripts/.orig/transfer-charact.tsp: -------------------------------------------------------------------------------- 1 | reset() 2 | display.clear() 3 | 4 | -- Beep in excitement 5 | beeper.beep(1, 700) 6 | 7 | -- PARAMETERS 8 | Vchan = -50 9 | 10 | Vgmin = -50 11 | Vgmax = 50 12 | Vgstep = 2 13 | 14 | 15 | -- TRANSFER CHARACTERISTICS 16 | -- Clear buffers and make sure the right thing is recorded 17 | smua.nvbuffer1.clear() 18 | smub.nvbuffer1.clear() 19 | smua.nvbuffer1.collectsourcevalues = 1 20 | smub.nvbuffer1.collectsourcevalues = 1 21 | format.data = format.ASCII 22 | smua.nvbuffer1.appendmode = 1 23 | smub.nvbuffer1.appendmode = 1 24 | smua.measure.count = 1 25 | smub.measure.count = 1 26 | 27 | 28 | -- Measurement Setup 29 | -- To adjust the delay factor. 30 | smua.measure.delayfactor = 1.0 31 | -- Set Vd 32 | smua.source.levelv = 0.0 33 | --Channel 2 (sweep Vg) 34 | smub.source.func = smub.OUTPUT_DCVOLTS 35 | smub.source.levelv = 0.0 36 | -- Channel 1 (source Vd, meas Id) 37 | smua.source.func = smua.OUTPUT_DCVOLTS 38 | smua.sense = smua.SENSE_LOCAL 39 | smua.source.autorangev = smua.AUTORANGE_ON 40 | 41 | -- COMPLIANCE 42 | smua.source.limiti = 10e-6 43 | --smua.measure.rangei = 10e-6 44 | smub.source.limiti = 10e-8 45 | smua.measure.nplc = 1.00 46 | 47 | -- MEASUREMENT 48 | 49 | display.smua.measure.func = display.MEASURE_DCAMPS 50 | display.screen = display.SMUA 51 | 52 | smua.source.output = smua.OUTPUT_ON 53 | smub.source.output = smub.OUTPUT_ON 54 | 55 | smua.source.levelv = Vchan 56 | smub.source.levelv = Vgmin * (Vgstep) 57 | delay(30) 58 | 59 | for Vg = Vgmin, Vgmax do 60 | smub.source.levelv = Vg * (Vgstep) 61 | delay(2) 62 | smua.measure.i(smua.nvbuffer1) 63 | smub.measure.i(smub.nvbuffer1) 64 | end 65 | 66 | smua.source.output = smua.OUTPUT_OFF 67 | smub.source.output = smub.OUTPUT_OFF 68 | -------------------------------------------------------------------------------- /TSP-scripts/backup/transfer-charact.tsp: -------------------------------------------------------------------------------- 1 | reset() 2 | display.clear() 3 | 4 | -- Beep in excitement 5 | beeper.beep(1, 700) 6 | 7 | -- PARAMETERS 8 | Vchan = -50 9 | 10 | Vgmin = -50 11 | Vgmax = 50 12 | Vgstep = 2 13 | 14 | 15 | -- TRANSFER CHARACTERISTICS 16 | -- Clear buffers and make sure the right thing is recorded 17 | smua.nvbuffer1.clear() 18 | smub.nvbuffer1.clear() 19 | smua.nvbuffer1.collectsourcevalues = 1 20 | smub.nvbuffer1.collectsourcevalues = 1 21 | format.data = format.ASCII 22 | smua.nvbuffer1.appendmode = 1 23 | smub.nvbuffer1.appendmode = 1 24 | smua.measure.count = 1 25 | smub.measure.count = 1 26 | 27 | 28 | -- Measurement Setup 29 | -- To adjust the delay factor. 30 | smua.measure.delayfactor = 1.0 31 | -- Set Vd 32 | smua.source.levelv = 0.0 33 | --Channel 2 (sweep Vg) 34 | smub.source.func = smub.OUTPUT_DCVOLTS 35 | smub.source.levelv = 0.0 36 | -- Channel 1 (source Vd, meas Id) 37 | smua.source.func = smua.OUTPUT_DCVOLTS 38 | smua.sense = smua.SENSE_LOCAL 39 | smua.source.autorangev = smua.AUTORANGE_ON 40 | 41 | -- COMPLIANCE 42 | smua.source.limiti = 10e-6 43 | --smua.measure.rangei = 10e-6 44 | smub.source.limiti = 10e-8 45 | smua.measure.nplc = 1.00 46 | 47 | -- MEASUREMENT 48 | 49 | display.smua.measure.func = display.MEASURE_DCAMPS 50 | display.screen = display.SMUA 51 | 52 | smua.source.output = smua.OUTPUT_ON 53 | smub.source.output = smub.OUTPUT_ON 54 | 55 | smua.source.levelv = Vchan 56 | smub.source.levelv = Vgmin * (Vgstep) 57 | delay(30) 58 | 59 | for Vg = Vgmin, Vgmax do 60 | smub.source.levelv = Vg * (Vgstep) 61 | delay(2) 62 | smua.measure.i(smua.nvbuffer1) 63 | smub.measure.i(smub.nvbuffer1) 64 | end 65 | 66 | smua.source.output = smua.OUTPUT_OFF 67 | smub.source.output = smub.OUTPUT_OFF 68 | -------------------------------------------------------------------------------- /TSP-scripts/backup/transfer-charact-2.tsp: -------------------------------------------------------------------------------- 1 | reset() 2 | display.clear() 3 | 4 | -- Beep in excitement 5 | beeper.beep(1, 700) 6 | 7 | -- PARAMETERS 8 | Vchan = -50 9 | 10 | Vgmin = -50 11 | Vgmax = 50 12 | Vgstep = -2 13 | 14 | 15 | -- TRANSFER CHARACTERISTICS 16 | -- Clear buffers and make sure the right thing is recorded 17 | smua.nvbuffer1.clear() 18 | smub.nvbuffer1.clear() 19 | smua.nvbuffer1.collectsourcevalues = 1 20 | smub.nvbuffer1.collectsourcevalues = 1 21 | format.data = format.ASCII 22 | smua.nvbuffer1.appendmode = 1 23 | smub.nvbuffer1.appendmode = 1 24 | smua.measure.count = 1 25 | smub.measure.count = 1 26 | 27 | 28 | -- Measurement Setup 29 | -- To adjust the delay factor. 30 | smua.measure.delayfactor = 1.0 31 | -- Set Vd 32 | smua.source.levelv = 0.0 33 | --Channel 2 (sweep Vg) 34 | smub.source.func = smub.OUTPUT_DCVOLTS 35 | smub.source.levelv = 0.0 36 | -- Channel 1 (source Vd, meas Id) 37 | smua.source.func = smua.OUTPUT_DCVOLTS 38 | smua.sense = smua.SENSE_LOCAL 39 | smua.source.autorangev = smua.AUTORANGE_ON 40 | 41 | -- COMPLIANCE 42 | smua.source.limiti = 10e-6 43 | --smua.measure.rangei = 10e-6 44 | smub.source.limiti = 10e-8 45 | smua.measure.nplc = 1.00 46 | 47 | -- MEASUREMENT 48 | 49 | display.smua.measure.func = display.MEASURE_DCAMPS 50 | display.screen = display.SMUA 51 | 52 | smua.source.output = smua.OUTPUT_ON 53 | smub.source.output = smub.OUTPUT_ON 54 | 55 | smua.source.levelv = Vchan 56 | smub.source.levelv = Vgmin * (Vgstep) 57 | delay(30) 58 | 59 | for Vg = Vgmin, Vgmax do 60 | smub.source.levelv = Vg * (Vgstep) 61 | delay(2) 62 | smua.measure.i(smua.nvbuffer1) 63 | smub.measure.i(smub.nvbuffer1) 64 | end 65 | 66 | smua.source.output = smua.OUTPUT_OFF 67 | smub.source.output = smub.OUTPUT_OFF 68 | -------------------------------------------------------------------------------- /TSP-scripts/legacy/transfer-charact.tsp.backup: -------------------------------------------------------------------------------- 1 | reset() 2 | display.clear() 3 | 4 | -- Beep in excitement 5 | beeper.beep(1, 700) 6 | 7 | -- PARAMETERS 8 | Vchan = -50 9 | 10 | Vgmin = -50 11 | Vgmax = 50 12 | Vgstep = 2 13 | 14 | 15 | -- TRANSFER CHARACTERISTICS 16 | -- Clear buffers and make sure the right thing is recorded 17 | smua.nvbuffer1.clear() 18 | smub.nvbuffer1.clear() 19 | smua.nvbuffer1.collectsourcevalues = 1 20 | smub.nvbuffer1.collectsourcevalues = 1 21 | format.data = format.ASCII 22 | smua.nvbuffer1.appendmode = 1 23 | smub.nvbuffer1.appendmode = 1 24 | smua.measure.count = 1 25 | smub.measure.count = 1 26 | 27 | 28 | -- Measurement Setup 29 | -- To adjust the delay factor. 30 | smua.measure.delayfactor = 1.0 31 | -- Set Vd 32 | smua.source.levelv = 0.0 33 | --Channel 2 (sweep Vg) 34 | smub.source.func = smub.OUTPUT_DCVOLTS 35 | smub.source.levelv = 0.0 36 | -- Channel 1 (source Vd, meas Id) 37 | smua.source.func = smua.OUTPUT_DCVOLTS 38 | smua.sense = smua.SENSE_LOCAL 39 | smua.source.autorangev = smua.AUTORANGE_ON 40 | 41 | -- COMPLIANCE 42 | smua.source.limiti = 10e-6 43 | --smua.measure.rangei = 10e-6 44 | smub.source.limiti = 10e-8 45 | smua.measure.nplc = 1.00 46 | 47 | -- MEASUREMENT 48 | 49 | display.smua.measure.func = display.MEASURE_DCAMPS 50 | display.screen = display.SMUA 51 | 52 | smua.source.output = smua.OUTPUT_ON 53 | smub.source.output = smub.OUTPUT_ON 54 | 55 | smua.source.levelv = Vchan 56 | smub.source.levelv = Vgmin * (Vgstep) 57 | delay(30) 58 | 59 | for Vg = Vgmin, Vgmax do 60 | smub.source.levelv = Vg * (Vgstep) 61 | delay(2) 62 | smua.measure.i(smua.nvbuffer1) 63 | smub.measure.i(smub.nvbuffer1) 64 | end 65 | 66 | smua.source.output = smua.OUTPUT_OFF 67 | smub.source.output = smub.OUTPUT_OFF 68 | -------------------------------------------------------------------------------- /TSP-scripts/.orig/transfer-charact-2.tsp: -------------------------------------------------------------------------------- 1 | reset() 2 | display.clear() 3 | 4 | -- Beep in excitement 5 | beeper.beep(1, 700) 6 | 7 | -- PARAMETERS 8 | Vchan = -0.2 9 | 10 | Vgmin = -100 11 | Vgmax = 100 12 | Vgstep = -0.01 13 | 14 | 15 | -- TRANSFER CHARACTERISTICS 16 | -- Clear buffers and make sure the right thing is recorded 17 | smua.nvbuffer1.clear() 18 | smub.nvbuffer1.clear() 19 | smua.nvbuffer1.collectsourcevalues = 1 20 | smub.nvbuffer1.collectsourcevalues = 1 21 | format.data = format.ASCII 22 | smua.nvbuffer1.appendmode = 1 23 | smub.nvbuffer1.appendmode = 1 24 | smua.measure.count = 1 25 | smub.measure.count = 1 26 | 27 | 28 | -- Measurement Setup 29 | -- To adjust the delay factor. 30 | smua.measure.delayfactor = 1.0 31 | -- Set Vd 32 | smua.source.levelv = 0.0 33 | --Channel 2 (sweep Vg) 34 | smub.source.func = smub.OUTPUT_DCVOLTS 35 | smub.source.levelv = 0.0 36 | -- Channel 1 (source Vd, meas Id) 37 | smua.source.func = smua.OUTPUT_DCVOLTS 38 | smua.sense = smua.SENSE_LOCAL 39 | smua.source.autorangev = smua.AUTORANGE_ON 40 | 41 | -- COMPLIANCE 42 | smua.source.limiti = 10e-6 43 | --smua.measure.rangei = 10e-6 44 | smub.source.limiti = 10e-8 45 | smua.measure.nplc = 1.00 46 | 47 | -- MEASUREMENT 48 | 49 | display.smub.measure.func = display.MEASURE_DCAMPS 50 | display.screen = display.SMUB 51 | 52 | smua.source.output = smua.OUTPUT_ON 53 | smub.source.output = smub.OUTPUT_ON 54 | 55 | smua.source.levelv = Vchan 56 | smub.source.levelv = Vgmin * (Vgstep) 57 | delay(2) 58 | 59 | for Vg = Vgmin, Vgmax do 60 | smub.source.levelv = Vg * (Vgstep) 61 | delay(0.2) 62 | smua.measure.i(smua.nvbuffer1) 63 | smub.measure.i(smub.nvbuffer1) 64 | end 65 | 66 | smua.source.output = smua.OUTPUT_OFF 67 | smub.source.output = smub.OUTPUT_OFF 68 | -------------------------------------------------------------------------------- /TSP-scripts/.orig/output-charact.tsp: -------------------------------------------------------------------------------- 1 | reset() 2 | display.clear() 3 | 4 | -- Beep in excitement 5 | beeper.beep(1, 700) 6 | 7 | -- PARAMETERS 8 | Vdmin = 0 9 | Vdmax = 50 10 | Vdstep = -1 11 | 12 | Vgmin = 0 13 | Vgmax = 5 14 | Vgstep = -10 15 | 16 | 17 | -- TRANSFER CHARACTERISTICS 18 | -- Clear buffers and make sure the right thing is recorded 19 | smua.nvbuffer1.clear() 20 | smub.nvbuffer1.clear() 21 | smua.nvbuffer1.collectsourcevalues = 1 22 | smub.nvbuffer1.collectsourcevalues = 1 23 | format.data = format.ASCII 24 | smua.nvbuffer1.appendmode = 1 25 | smub.nvbuffer1.appendmode = 1 26 | smua.measure.count = 1 27 | smub.measure.count = 1 28 | 29 | 30 | -- Measurement Setup 31 | -- To adjust the delay factor. 32 | smua.measure.delayfactor = 1.0 33 | -- Set Vd 34 | smua.source.levelv = 0.0 35 | --Channel 2 (sweep Vg) 36 | smub.source.func = smub.OUTPUT_DCVOLTS 37 | smub.source.levelv = 0.0 38 | -- Channel 1 (source Vd, meas Id) 39 | smua.source.func = smua.OUTPUT_DCVOLTS 40 | smua.sense = smua.SENSE_LOCAL 41 | smua.source.autorangev = smua.AUTORANGE_ON 42 | 43 | -- COMPLIANCE 44 | smua.source.limiti = 1e-5 45 | smua.measure.rangei = 10e-6 46 | smub.source.limiti = 1e-7 47 | smua.measure.nplc = 1.00 48 | 49 | -- MEASUREMENT 50 | 51 | display.smua.measure.func = display.MEASURE_DCAMPS 52 | display.screen = display.SMUA 53 | 54 | smua.source.output = smua.OUTPUT_ON 55 | smub.source.output = smub.OUTPUT_ON 56 | 57 | for Vg = Vgmin, Vgmax do 58 | smub.source.levelv = Vg * (Vgstep) 59 | smua.source.levelv = Vdmin * Vdstep 60 | delay(20) 61 | for Vd = Vdmin, Vdmax do 62 | smua.source.levelv = Vd * Vdstep 63 | delay(0.4) 64 | smua.measure.i(smua.nvbuffer1) 65 | smub.measure.i(smub.nvbuffer1) 66 | end 67 | end 68 | 69 | smua.source.output = smua.OUTPUT_OFF 70 | smub.source.output = smub.OUTPUT_OFF 71 | -------------------------------------------------------------------------------- /TSP-scripts/output-charact.tsp: -------------------------------------------------------------------------------- 1 | reset() 2 | display.clear() 3 | 4 | -- Beep in excitement 5 | beeper.beep(1, 700) 6 | 7 | -- PARAMETERS 8 | Vdmin = 0 9 | Vdmax = 40 10 | Vdstep = -2 11 | 12 | Vgmin = 0 13 | Vgmax = 5 14 | Vgstep = -10 15 | 16 | 17 | -- TRANSFER CHARACTERISTICS 18 | -- Clear buffers and make sure the right thing is recorded 19 | smua.nvbuffer1.clear() 20 | smub.nvbuffer1.clear() 21 | smua.nvbuffer1.collectsourcevalues = 1 22 | smub.nvbuffer1.collectsourcevalues = 1 23 | format.data = format.ASCII 24 | smua.nvbuffer1.appendmode = 1 25 | smub.nvbuffer1.appendmode = 1 26 | smua.measure.count = 1 27 | smub.measure.count = 1 28 | 29 | 30 | -- Measurement Setup 31 | -- To adjust the delay factor. 32 | smua.measure.delayfactor = 1.0 33 | -- Set Vd 34 | smua.source.levelv = 0.0 35 | --Channel 2 (sweep Vg) 36 | smub.source.func = smub.OUTPUT_DCVOLTS 37 | smub.source.levelv = 0.0 38 | -- Channel 1 (source Vd, meas Id) 39 | smua.source.func = smua.OUTPUT_DCVOLTS 40 | smua.sense = smua.SENSE_LOCAL 41 | smua.source.autorangev = smua.AUTORANGE_ON 42 | 43 | -- COMPLIANCE 44 | smua.source.limiti = 10e-6 45 | smua.measure.rangei = 10e-6 46 | smub.source.limiti = 10e-8 47 | smua.measure.nplc = 1.00 48 | 49 | -- MEASUREMENT 50 | 51 | display.smua.measure.func = display.MEASURE_DCAMPS 52 | display.screen = display.SMUA 53 | 54 | smua.source.output = smua.OUTPUT_ON 55 | smub.source.output = smub.OUTPUT_ON 56 | 57 | for Vg = Vgmin, Vgmax do 58 | smub.source.levelv = Vg * (Vgstep) 59 | delay(5) 60 | smua.source.levelv = Vdmin * Vdstep 61 | delay(2) 62 | for Vd = Vdmin, Vdmax do 63 | smua.source.levelv = Vd * Vdstep 64 | smua.measure.i(smua.nvbuffer1) 65 | smub.measure.i(smub.nvbuffer1) 66 | delay(0.4) 67 | end 68 | end 69 | 70 | smua.source.output = smua.OUTPUT_OFF 71 | smub.source.output = smub.OUTPUT_OFF 72 | -------------------------------------------------------------------------------- /TSP-scripts/legacy/gate-leakage.tsp: -------------------------------------------------------------------------------- 1 | reset() 2 | display.clear() 3 | 4 | -- Beep in excitement 5 | beeper.beep(0.1, 659.25) 6 | beeper.beep(0.1, 587.33) 7 | beeper.beep(0.2, 349.23) 8 | beeper.beep(0.2, 392.00) 9 | beeper.beep(0.1, 523.25) 10 | beeper.beep(0.1, 493.88) 11 | beeper.beep(0.2, 293.66) 12 | beeper.beep(0.2, 329.63) 13 | beeper.beep(0.1, 493.88) 14 | beeper.beep(0.1, 440.00) 15 | beeper.beep(0.2, 261.63) 16 | beeper.beep(0.2, 329.63) 17 | beeper.beep(0.5, 440.00) 18 | 19 | -- TRANSFER CHARACTERISTICS 20 | -- Clear buffers and make sure the right thing is recorded 21 | smua.nvbuffer1.clear() 22 | smub.nvbuffer1.clear() 23 | smua.nvbuffer1.collectsourcevalues = 1 24 | smub.nvbuffer1.collectsourcevalues = 1 25 | format.data = format.ASCII 26 | smua.nvbuffer1.appendmode = 1 27 | smub.nvbuffer1.appendmode = 1 28 | smua.measure.count = 1 29 | smub.measure.count = 1 30 | 31 | 32 | -- Measurement Setup 33 | -- To adjust the delay factor. 34 | smua.measure.delayfactor = 1.0 35 | -- Set Vd 36 | smua.source.levelv = 0.0 37 | --Channel 2 (sweep Vg) 38 | smub.source.func = smub.OUTPUT_DCVOLTS 39 | smub.source.levelv = 0.0 40 | -- Channel 1 (source Vd, meas Id) 41 | smua.source.func = smua.OUTPUT_DCVOLTS 42 | smua.sense = smua.SENSE_LOCAL 43 | smua.source.autorangev = smua.AUTORANGE_ON 44 | -- Set complaince to 1uA 45 | smua.source.limiti = 1e-4 46 | smua.measure.rangei = 1e-4 47 | smub.source.limiti = 1e-5 48 | smua.measure.nplc = 10.00 49 | 50 | -- Measurement 51 | display.smua.measure.func = display.MEASURE_DCAMPS 52 | display.screen = display.SMUA 53 | 54 | smub.source.output = smua.OUTPUT_ON 55 | smua.source.output = smub.OUTPUT_ON 56 | 57 | smua.source.levelv = -10 58 | 59 | for Vg = -10, 10 do 60 | smub.source.levelv = Vg * 5 61 | delay(0.1) 62 | smua.measure.i(smua.nvbuffer1) 63 | smub.measure.i(smub.nvbuffer1) 64 | end 65 | 66 | smua.source.output = smua.OUTPUT_OFF 67 | smub.source.output = smub.OUTPUT_OFF 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Transistor-tools-K2636 2 | A driver for controlling a keithley 2636. 3 | The K2636.py program in this folder loads a set of .tsp instructions into the memory of the Keithley. It then tells the keithley to execute the instructions before closing the connection with the instrument. 4 | 5 | # User interface 6 | To start the user interface run the following command: 7 | 8 | >python ofetMeasure.py 9 | 10 | # Example python script: 11 | You can take the driver to script your own programs: 12 | 13 | >keithley = K2636(address='ASRL/dev/ttyUSB0', read_term='\n', baudrate=57600) 14 | 15 | >sample = 'ofet1' 16 | 17 | >keithley.IVsweep(sample) 18 | 19 | >keithley.Output(sample) 20 | 21 | >keithley.Transfer(sample) 22 | 23 | >keithley.DisplayMeasurement(sample) 24 | 25 | >keithley.closeConnection() 26 | 27 | 28 | # Requirements: 29 | It is written is python3. You will need to download the following modules too: 30 | - visa 31 | - serial 32 | - matplotlib 33 | - pandas 34 | - for remote control: 35 | export XKB_DEFAULT_RULES=base 36 | export QT_XKB_CONFIG_ROOT=/usr/share/X11/xkb 37 | 38 | # How to hook up the keithley 2636 for three terminal OFET measurements: 39 | ![Alt text](.K2636_connections.png?raw=true "Setup") 40 | 41 | # Things to note 42 | - The Keithley 2636 uses 'TSP' rather than 'SCPI' which the Keithley 2400 understood. 43 | - Other than the change in syntax, the way the commands are executed have changed. Now the Keithley now loads an entire script's worth of commands into it's non-volatile memory before execution. This means it's faster than before. 44 | - The .tsp files in this repo are scripts which can be loaded into the K2636. 45 | - .tsp are written in Lua (http://www.lua.org). Comments begin with '--' 46 | - For more info on TSP check out the following links: 47 | - http://www.tek.com/sites/tek.com/files/media/document/resources/2616%20SCPI_to_TSP_AN.pdf 48 | - https://forum.tek.com/viewtopic.php?t=121440 49 | -------------------------------------------------------------------------------- /TSP-scripts/inverter.tsp: -------------------------------------------------------------------------------- 1 | ---------------- 2 | -- TSP PROGRAM FOR PERFORMING INVERTER MEASUREMENT 3 | -- Sweeps over an input voltgae range and measures an output voltage 4 | 5 | 6 | -------- PARAMETERS -------- 7 | VinStart = 0 8 | VinEnd = 120 9 | VinStep = 1 10 | 11 | DIR = 1 12 | 13 | -------- MAIN PROGRAM -------- 14 | reset() 15 | display.clear() 16 | 17 | -- Beep in excitement 18 | beeper.beep(1, 600) 19 | 20 | -- Clear buffers 21 | smua.nvbuffer1.clear() 22 | smub.nvbuffer1.clear() 23 | -- Prepare buffers 24 | smua.nvbuffer1.collectsourcevalues = 1 25 | smub.nvbuffer1.collectsourcevalues = 1 26 | format.data = format.ASCII 27 | smua.nvbuffer1.appendmode = 1 28 | smub.nvbuffer1.appendmode = 1 29 | smua.measure.count = 1 30 | smub.measure.count = 1 31 | 32 | -- SMUA setup 33 | smua.measure.delayfactor = 1.0 34 | smua.measure.nplc = 10 35 | smua.source.func = smua.OUTPUT_DCVOLTS 36 | smua.source.autorangev = smua.AUTORANGE_ON 37 | smua.source.rangev = 200 38 | -- Compliance here relates to gate leakage? 39 | smua.source.limiti = 1e-7 40 | 41 | -- SMUB setup 42 | -- source 0A current and measure voltage 43 | smub.measure.nplc = 10 44 | smub.sense = smub.SENSE_LOCAL 45 | smub.source.func = smub.OUTPUT_DCAMPS 46 | smub.measure.autorangev = smub.AUTORANGE_ON 47 | smub.source.limitv = 150 48 | smub.source.leveli = 0 49 | 50 | --DISPLAY settings 51 | display.smua.measure.func = display.MEASURE_DCVOLTS 52 | display.smub.measure.func = display.MEASURE_DCVOLTS 53 | display.screen = display.SMUA_SMUB 54 | 55 | -- MEASUREMENT ROUTINE 56 | smua.source.levelv = VinStart * VinStep 57 | smua.source.output = smua.OUTPUT_ON 58 | smub.source.output = smub.OUTPUT_ON 59 | delay(30) 60 | 61 | for V = VinStart, VinEnd do 62 | smua.source.levelv = V * VinStep 63 | delay(2) 64 | smua.measure.v(smua.nvbuffer1) 65 | smub.measure.v(smub.nvbuffer1) 66 | end 67 | 68 | smua.source.output = smua.OUTPUT_OFF 69 | smub.source.output = smub.OUTPUT_OFF 70 | 71 | waitcomplete() 72 | -------- END -------- 73 | -------------------------------------------------------------------------------- /TSP-scripts/inverter-reverse.tsp: -------------------------------------------------------------------------------- 1 | ---------------- 2 | -- TSP PROGRAM FOR PERFORMING INVERTER MEASUREMENT 3 | -- Sweeps over an input voltgae range and measures an output voltage 4 | 5 | 6 | -------- PARAMETERS -------- 7 | VinStart = -120 8 | VinEnd = 0 9 | VinStep = -1 10 | 11 | DIR = 1 12 | 13 | -------- MAIN PROGRAM -------- 14 | reset() 15 | display.clear() 16 | 17 | -- Beep in excitement 18 | beeper.beep(1, 600) 19 | 20 | -- Clear buffers 21 | smua.nvbuffer1.clear() 22 | smub.nvbuffer1.clear() 23 | -- Prepare buffers 24 | smua.nvbuffer1.collectsourcevalues = 1 25 | smub.nvbuffer1.collectsourcevalues = 1 26 | format.data = format.ASCII 27 | smua.nvbuffer1.appendmode = 1 28 | smub.nvbuffer1.appendmode = 1 29 | smua.measure.count = 1 30 | smub.measure.count = 1 31 | 32 | -- SMUA setup 33 | smua.measure.delayfactor = 1.0 34 | smua.measure.nplc = 10 35 | smua.source.func = smua.OUTPUT_DCVOLTS 36 | smua.source.autorangev = smua.AUTORANGE_ON 37 | smua.source.rangev = 200 38 | -- Compliance here relates to gate leakage? 39 | smua.source.limiti = 1e-7 40 | 41 | -- SMUB setup 42 | -- source 0A current and measure voltage 43 | smub.measure.nplc = 10 44 | smub.sense = smub.SENSE_LOCAL 45 | smub.source.func = smub.OUTPUT_DCAMPS 46 | smub.measure.autorangev = smub.AUTORANGE_ON 47 | smub.source.limitv = 150 48 | smub.source.leveli = 0 49 | 50 | --DISPLAY settings 51 | display.smua.measure.func = display.MEASURE_DCVOLTS 52 | display.smub.measure.func = display.MEASURE_DCVOLTS 53 | display.screen = display.SMUA_SMUB 54 | 55 | -- MEASUREMENT ROUTINE 56 | smua.source.levelv = VinStart * VinStep 57 | smua.source.output = smua.OUTPUT_ON 58 | smub.source.output = smub.OUTPUT_ON 59 | delay(30) 60 | 61 | for V = VinStart, VinEnd do 62 | smua.source.levelv = V * VinStep 63 | delay(2) 64 | smua.measure.v(smua.nvbuffer1) 65 | smub.measure.v(smub.nvbuffer1) 66 | end 67 | 68 | smua.source.output = smua.OUTPUT_OFF 69 | smub.source.output = smub.OUTPUT_OFF 70 | 71 | waitcomplete() 72 | -------- END -------- 73 | -------------------------------------------------------------------------------- /TSP-scripts/iv-sweep.tsp: -------------------------------------------------------------------------------- 1 | ---------------- 2 | -- TSP PROGRAM FOR PERFORMING IV SWEEP 3 | -- Sweeps over an input voltgae range and measures an output current. 4 | 5 | -- INPUT sweep start, sweep end, and absolute step size. 6 | 7 | 8 | -------- PARAMETERS -------- 9 | Vstart = -50 10 | Vend = 50 11 | Vstep = 2 12 | 13 | 14 | -------- MAIN PROGRAM -------- 15 | reset() 16 | display.clear() 17 | 18 | -- Beep in excitement 19 | beeper.beep(1, 600) 20 | 21 | -- Clear buffers 22 | smua.nvbuffer1.clear() 23 | smub.nvbuffer1.clear() 24 | -- Prepare buffers 25 | smua.nvbuffer1.collectsourcevalues = 1 26 | smub.nvbuffer1.collectsourcevalues = 1 27 | format.data = format.ASCII 28 | smua.nvbuffer1.appendmode = 1 29 | smub.nvbuffer1.appendmode = 1 30 | smua.measure.count = 1 31 | smub.measure.count = 1 32 | 33 | -- Measurement Setup 34 | -- To adjust the delay factor. 35 | smua.measure.delayfactor = 1 36 | smua.measure.nplc = 10 37 | -- SMUA setup 38 | smua.source.func = smua.OUTPUT_DCVOLTS 39 | smua.sense = smua.SENSE_LOCAL 40 | smua.source.autorangev = smua.AUTORANGE_ON 41 | smua.source.limiti = 1e-5 42 | smua.measure.rangei = 1e-5 43 | 44 | --DISPLAY settings 45 | display.smua.measure.func = display.MEASURE_DCAMPS 46 | display.screen = display.SMUA 47 | 48 | -- Measurement routine 49 | V = Vstart 50 | smua.source.output = smua.OUTPUT_ON 51 | smua.source.levelv = V 52 | delay(1) 53 | 54 | -- forwards scan direction 55 | if Vstart < Vend then 56 | while V <= Vend do 57 | smua.source.levelv = V 58 | smua.source.output = smua.OUTPUT_ON 59 | delay(0.2) 60 | smua.measure.i(smua.nvbuffer1) 61 | V = V + Vstep 62 | smua.source.output = smua.OUTPUT_OFF 63 | end 64 | 65 | -- reverse scan direction 66 | elseif Vstart > Vend then 67 | while V >= Vend do 68 | smua.source.levelv = V 69 | smua.source.output = smua.OUTPUT_ON 70 | delay(0.2) 71 | smua.measure.i(smua.nvbuffer1) 72 | V = V - Vstep 73 | smua.source.output = smua.OUTPUT_OFF 74 | end 75 | 76 | else 77 | error("Invalid sweep parameters.") 78 | end 79 | 80 | waitcomplete() 81 | -------- END -------- 82 | -------------------------------------------------------------------------------- /TSP-scripts/transfer-charact.tsp: -------------------------------------------------------------------------------- 1 | ---------------- 2 | -- TSP PROGRAM FOR PERFORMING TRANSFER SWEEPS 3 | -- Sweeps over gate voltage and measures channel current 4 | 5 | -- INPUT sweep start and end points with ABSOLUTE step size 6 | 7 | 8 | -------- PARAMETERS -------- 9 | Vchan = -50 10 | 11 | VgStart = -100 12 | VgEnd = 100 13 | VgStep = 1 14 | 15 | 16 | -------- MAIN PROGRAM -------- 17 | reset() 18 | display.clear() 19 | 20 | -- Beep in excitement 21 | beeper.beep(1, 600) 22 | 23 | -- Clear buffers 24 | smua.nvbuffer1.clear() 25 | smub.nvbuffer1.clear() 26 | -- Prepare buffers 27 | smua.nvbuffer1.collectsourcevalues = 1 28 | smub.nvbuffer1.collectsourcevalues = 1 29 | format.data = format.ASCII 30 | smua.nvbuffer1.appendmode = 1 31 | smub.nvbuffer1.appendmode = 1 32 | smua.measure.count = 1 33 | smub.measure.count = 1 34 | 35 | -- SMUA setup 36 | smua.measure.delayfactor = 1.0 37 | smua.measure.nplc = 10 38 | smua.source.func = smua.OUTPUT_DCVOLTS 39 | smua.sense = smua.SENSE_LOCAL 40 | smua.source.autorangev = smua.AUTORANGE_ON 41 | smua.source.limiti = 10e-5 42 | smua.measure.rangei = 10e-5 43 | 44 | -- SMUB setup 45 | smub.measure.delayfactor = 1.0 46 | smub.measure.nplc = 10 47 | smub.source.func = smub.OUTPUT_DCVOLTS 48 | smub.source.limiti = 10e-8 49 | 50 | --DISPLAY settings 51 | display.smua.measure.func = display.MEASURE_DCAMPS 52 | display.smub.measure.func = display.MEASURE_DCAMPS 53 | display.screen = display.SMUA_SMUB 54 | 55 | -- MEASUREMENT ROUTINE 56 | 57 | smua.source.levelv = Vchan 58 | smua.source.output = smua.OUTPUT_ON 59 | 60 | Vg = VgStart 61 | smub.source.levelv = Vg 62 | smub.source.output = smub.OUTPUT_ON 63 | delay(3) 64 | 65 | -- Forward Vg scan 66 | if VgStart < VgEnd then 67 | while Vg <= VgEnd do 68 | smub.source.levelv = Vg 69 | smub.source.output = smub.OUTPUT_ON 70 | delay(0.2) 71 | smua.measure.i(smua.nvbuffer1) 72 | smub.measure.i(smub.nvbuffer1) 73 | 74 | smub.source.output = smub.OUTPUT_OFF 75 | Vg = Vg + VgStep 76 | end 77 | 78 | -- Reverse scan 79 | elseif VgStart > VgEnd then 80 | while Vg >= VgEnd do 81 | smub.source.levelv = Vg 82 | smub.source.output = smub.OUTPUT_ON 83 | delay(0.2) 84 | smua.measure.i(smua.nvbuffer1) 85 | smub.measure.i(smub.nvbuffer1) 86 | 87 | smub.source.output = smub.OUTPUT_OFF 88 | Vg = Vg - VgStep 89 | end 90 | 91 | else 92 | error("Invalid sweep parameters.") 93 | end 94 | 95 | smua.source.output = smua.OUTPUT_OFF 96 | smub.source.output = smub.OUTPUT_OFF 97 | waitcomplete() 98 | -------- END -------- 99 | -------------------------------------------------------------------------------- /TSP-scripts/transfer-charact-2.tsp: -------------------------------------------------------------------------------- 1 | ---------------- 2 | -- TSP PROGRAM FOR PERFORMING TRANSFER SWEEPS 3 | -- Sweeps over gate voltage and measures channel current 4 | 5 | -- INPUT sweep start and end points with ABSOLUTE step size 6 | 7 | 8 | -------- PARAMETERS -------- 9 | Vchan = -50 10 | 11 | VgStart = 100 12 | VgEnd = -100 13 | VgStep = 1 14 | 15 | 16 | -------- MAIN PROGRAM -------- 17 | reset() 18 | display.clear() 19 | 20 | -- Beep in excitement 21 | beeper.beep(1, 600) 22 | 23 | -- Clear buffers 24 | smua.nvbuffer1.clear() 25 | smub.nvbuffer1.clear() 26 | -- Prepare buffers 27 | smua.nvbuffer1.collectsourcevalues = 1 28 | smub.nvbuffer1.collectsourcevalues = 1 29 | format.data = format.ASCII 30 | smua.nvbuffer1.appendmode = 1 31 | smub.nvbuffer1.appendmode = 1 32 | smua.measure.count = 1 33 | smub.measure.count = 1 34 | 35 | -- SMUA setup 36 | smua.measure.delayfactor = 1.0 37 | smua.measure.nplc = 10 38 | smua.source.func = smua.OUTPUT_DCVOLTS 39 | smua.sense = smua.SENSE_LOCAL 40 | smua.source.autorangev = smua.AUTORANGE_ON 41 | smua.source.limiti = 10e-5 42 | smua.measure.rangei = 10e-5 43 | 44 | -- SMUB setup 45 | smub.measure.delayfactor = 1.0 46 | smub.measure.nplc = 10 47 | smub.source.func = smub.OUTPUT_DCVOLTS 48 | smub.source.limiti = 10e-8 49 | 50 | --DISPLAY settings 51 | display.smua.measure.func = display.MEASURE_DCAMPS 52 | display.smub.measure.func = display.MEASURE_DCAMPS 53 | display.screen = display.SMUA_SMUB 54 | 55 | -- MEASUREMENT ROUTINE 56 | 57 | smua.source.levelv = Vchan 58 | smua.source.output = smua.OUTPUT_ON 59 | 60 | Vg = VgStart 61 | smub.source.levelv = Vg 62 | smub.source.output = smub.OUTPUT_ON 63 | delay(3) 64 | 65 | -- Forward Vg scan 66 | if VgStart < VgEnd then 67 | while Vg <= VgEnd do 68 | smub.source.levelv = Vg 69 | smub.source.output = smub.OUTPUT_ON 70 | delay(0.2) 71 | smua.measure.i(smua.nvbuffer1) 72 | smub.measure.i(smub.nvbuffer1) 73 | 74 | smub.source.output = smub.OUTPUT_OFF 75 | Vg = Vg + VgStep 76 | end 77 | 78 | -- Reverse scan 79 | elseif VgStart > VgEnd then 80 | while Vg >= VgEnd do 81 | smub.source.levelv = Vg 82 | smub.source.output = smub.OUTPUT_ON 83 | delay(0.2) 84 | smua.measure.i(smua.nvbuffer1) 85 | smub.measure.i(smub.nvbuffer1) 86 | 87 | smub.source.output = smub.OUTPUT_OFF 88 | Vg = Vg - VgStep 89 | end 90 | 91 | else 92 | error("Invalid sweep parameters.") 93 | end 94 | 95 | smua.source.output = smua.OUTPUT_OFF 96 | smub.source.output = smub.OUTPUT_OFF 97 | waitcomplete() 98 | 99 | beeper.beep(1, 600) 100 | beeper.beep(1, 600) 101 | beeper.beep(1, 600) 102 | beeper.beep(1, 600) 103 | -------- END -------- 104 | -------------------------------------------------------------------------------- /ofetMeasureCLI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding:utf-8 3 | """ 4 | Author: Ross Warren 5 | Purpose: Run OFET measurements remotely with simple CLI interface 6 | Created: 17/11/18 7 | """ 8 | 9 | import k2636 10 | import time 11 | import click 12 | import pandas as pd 13 | import matplotlib.pyplot as plt 14 | 15 | @click.command() 16 | @click.option('--sample', prompt='Please input sample name:', help='Sample name.') 17 | @click.option('--graphic', default=True, help='TRUE or FALSE display measurement in graphic format.') 18 | def main(sample, graphic=True): 19 | '''Simple program which makes all OFET measurements from CLI.''' 20 | try: 21 | print(sample) 22 | # Set up 23 | keithley = k2636.K2636() 24 | begin_measure = time.time() 25 | # Measurements 26 | keithley.IVsweep(sample) 27 | keithley.Output(sample) 28 | keithley.Transfer(sample) 29 | # Finish 30 | keithley.closeConnection() 31 | finish_measure = time.time() 32 | print('-------------------------------------------\nAll measurements complete. Total time % .2f mins.' 33 | % ((finish_measure - begin_measure) / 60)) 34 | if graphic == True: 35 | plot(sample) 36 | 37 | except ConnectionError: 38 | print('MEASUREMENT ERROR: Measurement could not be made due to connection issues.') 39 | 40 | def plot(sample): 41 | '''Creates plot of measurements''' 42 | try: 43 | df1 = pd.read_csv(str(sample + '-iv-sweep.csv'), '\t') 44 | df2 = pd.read_csv(str(sample + '-output.csv'), '\t') 45 | df3 = pd.read_csv( 46 | str(sample + '-neg-pos-transfer.csv'), '\t') 47 | df4 = pd.read_csv( 48 | str(sample + '-pos-neg-transfer.csv'), '\t') 49 | except FileNotFoundError: 50 | # If it can't find some data, dont worry :) 51 | pass 52 | 53 | fig = plt.figure() 54 | ax1 = fig.add_subplot(221) 55 | ax2 = fig.add_subplot(222) 56 | ax3 = fig.add_subplot(223) 57 | ax4 = fig.add_subplot(224) 58 | 59 | try: 60 | ax1.plot(df1['Channel Voltage [V]'], 61 | df1['Channel Current [A]'] / 1e-6, '.') 62 | ax1.set_title('I-V sweep') 63 | ax1.set_xlabel('Channel Voltage [V]') 64 | ax1.set_ylabel('Channel Current [$\mu$A]') 65 | 66 | ax2.plot(df2['Channel Voltage [V]'], 67 | df2['Channel Current [A]'] / 1e-6, '.') 68 | ax2.set_title('Output curves') 69 | ax2.set_xlabel('Channel Voltage [V]') 70 | ax2.set_ylabel('Channel Current [$\mu$A]') 71 | 72 | ax3.semilogy(df3['Gate Voltage [V]'], 73 | abs(df3['Channel Current [A]']), '.') 74 | ax3.set_title('Transfer Curves') 75 | ax3.set_xlabel('Gate Voltage [V]') 76 | ax3.set_ylabel('Channel Current [A]') 77 | 78 | ax3.semilogy(df4['Gate Voltage [V]'], 79 | abs(df4['Channel Current [A]']), '.') 80 | ax3.set_title('Transfer Curves') 81 | ax3.set_xlabel('Gate Voltage [V]') 82 | ax3.set_ylabel('Channel Current [A]') 83 | 84 | ax4.plot(df3['Gate Voltage [V]'], 85 | df3['Gate Leakage [A]'] / 1e-9, '.') 86 | ax4.set_title('Gate leakage current') 87 | ax4.set_xlabel('Gate Voltage [V]') 88 | ax4.set_ylabel('Gate Leakage [nA]') 89 | except UnboundLocalError: 90 | pass # if data isnt there, it cant be plotted 91 | 92 | fig.tight_layout() 93 | plt.show() 94 | 95 | 96 | if __name__ == '__main__': 97 | main() 98 | -------------------------------------------------------------------------------- /TSP-scripts/legacy/keithley-iv.tsp: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | coordinated sweep on smua and smub of a dual channel 2600B 4 | 5 | generates the Id-Vg curve for a FET 6 | 7 | the smu connected to the drain will not change the voltage value, but will measure 8 | drain current for each point in the sweep 9 | 10 | the smu connected to the gate will change voltage value for each. Current measurements 11 | not needed from this smu. If we were to enable measurements, they would be very small 12 | values (gate leakage) and would require appropriate settling time for the low level 13 | of current. 14 | 15 | ]] 16 | 17 | 18 | function configSweep(smu, startv, stopv, points) 19 | 20 | -- Configure source and measure settings. 21 | smu.source.output = smu.OUTPUT_OFF 22 | smu.source.func = smu.OUTPUT_DCVOLTS 23 | smu.source.levelv = 0 24 | smu.source.rangev = math.max(math.abs(startv), math.abs(stopv)) 25 | 26 | smu.measure.autozero = smu.AUTOZERO_ONCE 27 | smu.measure.lowrangei = 100e-9 -- limited auto 28 | 29 | -- Setup a buffer to store the result(s) in and start testing. 30 | smu.nvbuffer1.clear() 31 | smu.nvbuffer1.appendmode = 1 32 | smu.nvbuffer1.collecttimestamps = 1 33 | smu.nvbuffer1.collectsourcevalues = 1 34 | 35 | -- Reset trigger model 36 | smu.trigger.arm.stimulus = 0 37 | smu.trigger.source.stimulus = 0 38 | smu.trigger.measure.stimulus = 0 39 | smu.trigger.endpulse.stimulus = 0 40 | smu.trigger.arm.count = 1 41 | -- Configure the source action 42 | smu.trigger.source.linearv(startv, stopv, points) 43 | smu.trigger.source.action = smu.ENABLE 44 | smu.trigger.endpulse.action = smu.SOURCE_HOLD 45 | -- Configure the measure action 46 | smu.trigger.measure.i(smu.nvbuffer1) 47 | smu.trigger.measure.action = smu.ENABLE 48 | -- Configure the sweep count 49 | smu.trigger.count = points 50 | 51 | end -- function configSweep() definition 52 | 53 | --Prints the results from the reading buffers. 54 | function printData_IdVg(drainsmu, gatesmu) 55 | if drainsmu.nvbuffer1.n == 0 then 56 | print("No readings in buffer") 57 | else 58 | print("Vg\tId") 59 | for i = 1, drainsmu.nvbuffer1.n do 60 | print(string.format("%g\t%g", gatesmu.nvbuffer1.sourcevalues[i], 61 | drainsmu.nvbuffer1.readings[i])) 62 | end 63 | end 64 | end -- function printData definition 65 | 66 | function start_test(drainsmu, gatesmu) 67 | 68 | -- overwrite some value setup in the configSweep() function 69 | -- no current measurements on gate, but measure voltage for use in printData() 70 | gatesmu.trigger.measure.v(gatesmu.nvbuffer1) 71 | 72 | -- gate sources after drain sources the Vds bias 73 | gatesmu.trigger.source.stimulus = drainsmu.trigger.SOURCE_COMPLETE_EVENT_ID 74 | 75 | -- measure drain current after each gate voltage is sourced 76 | drainsmu.trigger.measure.stimulus = gatesmu.trigger.SOURCE_COMPLETE_EVENT_ID 77 | 78 | 79 | -- output on, run the sweep and then turn the output off. 80 | drainsmu.source.output = drainsmu.OUTPUT_ON 81 | gatesmu.source.output = gatesmu.OUTPUT_ON 82 | 83 | -- start the gate first, so that is armed and can detect drain source complete event 84 | gatesmu.trigger.initiate() 85 | drainsmu.trigger.initiate() 86 | 87 | waitcomplete() 88 | 89 | drainsmu.source.output = drainsmu.OUTPUT_OFF 90 | gatesmu.source.output = gatesmu.OUTPUT_OFF 91 | 92 | end -- function 93 | 94 | 95 | -- ****************************************** 96 | -- 97 | -- main program that calls our functions 98 | -- 99 | -- ****************************************** 100 | 101 | reset() 102 | errorqueue.clear() 103 | 104 | drain = smub 105 | gate = smua 106 | 107 | sweep_pts = 61 108 | vds_bias = 0.1 109 | 110 | --configSweep(smu, startv, stopv, points) 111 | configSweep(drain, vds_bias, vds_bias, sweep_pts) 112 | configSweep(gate, 0, 3, sweep_pts) 113 | 114 | start_test(drain, gate) 115 | 116 | printData_IdVg(drain, gate) 117 | -------------------------------------------------------------------------------- /TSP-scripts/backup/output-charact.tsp.backup: -------------------------------------------------------------------------------- 1 | ---------------- 2 | -- TSP PROGRAM FOR PERFORMING OFET OUTPUT MEASUREMENTS 3 | -- Sweeps over channel and gate input voltages and measures channel current 4 | 5 | -- INPUT sweep start and end points with ABSOLUTE step size 6 | 7 | 8 | -------- PARAMETERS -------- 9 | VdStart = 0 10 | VdEnd = -100 11 | VdStep = 2 12 | 13 | VgStart = 0 14 | VgEnd = -50 15 | VgStep = 10 16 | 17 | -------- MAIN PROGRAM -------- 18 | reset() 19 | display.clear() 20 | 21 | -- Beep in excitement 22 | beeper.beep(1, 700) 23 | 24 | -- Clear buffers 25 | smua.nvbuffer1.clear() 26 | smub.nvbuffer1.clear() 27 | -- Prepare buffers 28 | smua.nvbuffer1.collectsourcevalues = 1 29 | smub.nvbuffer1.collectsourcevalues = 1 30 | format.data = format.ASCII 31 | smua.nvbuffer1.appendmode = 1 32 | smub.nvbuffer1.appendmode = 1 33 | smua.measure.count = 1 34 | smub.measure.count = 1 35 | 36 | --SMUA setup 37 | smua.measure.delayfactor = 1.0 38 | smua.measure.nplc = 10 39 | smua.source.func = smua.OUTPUT_DCVOLTS 40 | smua.sense = smua.SENSE_LOCAL 41 | smua.source.autorangev = smua.AUTORANGE_ON 42 | smua.source.limiti = 1e-5 43 | smua.measure.rangei = 10e-6 44 | 45 | --SMUB setup 46 | smub.measure.delayfactor = 1.0 47 | smub.measure.nplc = 10 48 | smub.source.func = smub.OUTPUT_DCVOLTS 49 | smub.source.limiti = 1e-7 50 | 51 | --DISPLAY settings 52 | display.smua.measure.func = display.MEASURE_DCAMPS 53 | display.smub.measure.func = display.MEASURE_DCVOLTS 54 | display.screen = display.SMUA_SMUB 55 | 56 | -- MEASUREMENT ROUTINE 57 | Vg = VgStart 58 | 59 | -- forwards Vg scan 60 | if VgStart < VgEnd then 61 | while Vg <= VgEnd do 62 | smub.source.levelv = Vg 63 | smub.source.output = smub.OUTPUT_ON 64 | delay(0.2) 65 | 66 | Vd = VdStart 67 | smua.source.levelv = Vd 68 | smua.source.output = smua.OUTPUT_ON 69 | delay(0.2) 70 | 71 | -- forward Vd scan 72 | if VdStart < VdEnd then 73 | while Vd <= VdEnd do 74 | smua.source.levelv = Vd 75 | smua.source.output = smua.OUTPUT_ON 76 | delay(0.2) 77 | smua.measure.i(smua.nvbuffer1) 78 | smub.measure.i(smub.nvbuffer1) 79 | Vd = Vd + VdStep 80 | smua.source.output = smua.OUTPUT_OFF 81 | end 82 | -- reverse Vd scan 83 | elseif VdStart > VdEnd then 84 | while Vd >= VdEnd do 85 | smua.source.levelv = Vd 86 | smua.source.output = smua.OUTPUT_ON 87 | delay(0.2) 88 | smua.measure.i(smua.nvbuffer1) 89 | smub.measure.i(smub.nvbuffer1) 90 | Vd = Vd - VdStep 91 | smua.source.output = smua.OUTPUT_OFF 92 | end 93 | else 94 | error("Invalid sweep parameters.") 95 | end 96 | 97 | smub.source.output = smub.OUTPUT_OFF 98 | Vg = Vg + VgStep 99 | 100 | end 101 | 102 | -- Reverse Vg scan 103 | elseif VgStart > VgEnd then 104 | while Vg >= VgEnd do 105 | smub.source.levelv = Vg 106 | smub.source.output = smub.OUTPUT_ON 107 | delay(0.2) 108 | 109 | Vd = VdStart 110 | smua.source.levelv = Vd 111 | smua.source.output = smua.OUTPUT_ON 112 | delay(0.2) 113 | 114 | -- forward Vd scan 115 | if VdStart < VdEnd then 116 | while Vd <= VdEnd do 117 | smua.source.levelv = Vd 118 | smua.source.output = smua.OUTPUT_ON 119 | delay(0.2) 120 | smua.measure.i(smua.nvbuffer1) 121 | smub.measure.i(smub.nvbuffer1) 122 | Vd = Vd + VdStep 123 | smua.source.output = smua.OUTPUT_OFF 124 | end 125 | -- reverse Vd scan 126 | elseif VdStart > VdEnd then 127 | while Vd >= VdEnd do 128 | smua.source.levelv = Vd 129 | smua.source.output = smua.OUTPUT_ON 130 | delay(0.2) 131 | smua.measure.i(smua.nvbuffer1) 132 | smub.measure.i(smub.nvbuffer1) 133 | Vd = Vd - VdStep 134 | smua.source.output = smua.OUTPUT_OFF 135 | end 136 | else 137 | error("Invalid sweep parameters.") 138 | end 139 | 140 | smub.source.output = smub.OUTPUT_OFF 141 | Vg = Vg - VgStep 142 | 143 | end 144 | else 145 | error ("Invalid sweep parameters.") 146 | end 147 | waitcomplete() 148 | -------- END -------- 149 | -------------------------------------------------------------------------------- /ofetMeasure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | OFET measurement main program linking gui and measurement thread. 6 | 7 | Author: Ross 8 | """ 9 | 10 | import ofetMeasureGUI # GUI 11 | import k2636 # driver 12 | import sys 13 | import time 14 | import pandas as pd 15 | from PyQt5.QtCore import QThread, pyqtSignal 16 | from PyQt5.QtWidgets import QApplication 17 | 18 | 19 | class GUI(ofetMeasureGUI.mainWindow): 20 | """GUI linked to measurement thread.""" 21 | 22 | def __init__(self): 23 | """Take GUI and add measurement thread connection.""" 24 | super().__init__() 25 | self.params = {} # for storing parameters 26 | self.setupConnections() 27 | 28 | def setupConnections(self): 29 | """Connect the GUI to the measurement thread.""" 30 | self.buttonWidget.ivBtn.clicked.connect(self.ivSweep) 31 | self.buttonWidget.outputBtn.clicked.connect(self.outputSweep) 32 | self.buttonWidget.transferBtn.clicked.connect(self.transferSweep) 33 | self.buttonWidget.allBtn.clicked.connect(self.allMeasurements) 34 | self.buttonWidget.inverterBtn.clicked.connect(self.inverter) 35 | 36 | def ivSweep(self): 37 | """Perform IV sweep.""" 38 | try: 39 | if self.buttonWidget.SampleName is None: 40 | raise AttributeError 41 | self.params['Sample name'] = self.buttonWidget.SampleName 42 | self.statusbar.showMessage('Performing IV Sweep...') 43 | self.buttonWidget.hideButtons() 44 | self.params['Measurement'] = 'iv-sweep' 45 | self.measureThread = measureThread(self.params) 46 | self.measureThread.finishedSig.connect(self.done) 47 | self.measureThread.start() 48 | except AttributeError or KeyError: 49 | self.popupWarning.showWindow('No sample name given!') 50 | 51 | def outputSweep(self, event): 52 | """Perform output sweep.""" 53 | try: 54 | if self.buttonWidget.SampleName is None: 55 | raise AttributeError 56 | self.params['Sample name'] = self.buttonWidget.SampleName 57 | self.statusbar.showMessage('Performing Output Sweep...') 58 | self.buttonWidget.hideButtons() 59 | self.params['Measurement'] = 'output' 60 | self.measureThread = measureThread(self.params) 61 | self.measureThread.finishedSig.connect(self.done) 62 | self.measureThread.start() 63 | except AttributeError: 64 | self.popupWarning.showWindow('No sample name given!') 65 | 66 | def transferSweep(self, event): 67 | """Perform transfer sweep.""" 68 | try: 69 | if self.buttonWidget.SampleName is None: 70 | raise AttributeError 71 | self.params['Sample name'] = self.buttonWidget.SampleName 72 | self.statusbar.showMessage('Performing Transfer Sweep...') 73 | self.buttonWidget.hideButtons() 74 | self.params['Measurement'] = 'transfer' 75 | self.measureThread = measureThread(self.params) 76 | self.measureThread.finishedSig.connect(self.done) 77 | self.measureThread.errorSig.connect(self.error) 78 | self.measureThread.start() 79 | except AttributeError: 80 | self.popupWarning.showWindow('No sample name given!') 81 | 82 | def allMeasurements(self, event): 83 | """Perform all sweeps.""" 84 | try: 85 | if self.buttonWidget.SampleName is None: 86 | raise AttributeError 87 | self.params['Sample name'] = self.buttonWidget.SampleName 88 | self.statusbar.showMessage('Performing all...') 89 | self.buttonWidget.hideButtons() 90 | self.params['Measurement'] = 'all' 91 | self.measureThread = measureThread(self.params) 92 | self.measureThread.finishedSig.connect(self.done) 93 | self.measureThread.errorSig.connect(self.error) 94 | self.measureThread.start() 95 | except AttributeError: 96 | self.popupWarning.showWindow('No sample name given!') 97 | 98 | def inverter(self, event): 99 | """Perform voltage inverter measurement.""" 100 | try: 101 | if self.buttonWidget.SampleName is None: 102 | raise AttributeError 103 | self.params['Sample name'] = self.buttonWidget.SampleName 104 | self.statusbar.showMessage('Performing inverter measurement...') 105 | self.buttonWidget.hideButtons() 106 | self.params['Measurement'] = 'inverter' 107 | self.measureThread = measureThread(self.params) 108 | self.measureThread.finishedSig.connect(self.done) 109 | self.measureThread.errorSig.connect(self.error) 110 | self.measureThread.start() 111 | except AttributeError: 112 | self.popupWarning.showWindow('No sample name given!') 113 | 114 | def done(self): 115 | """Update display when finished measurement.""" 116 | self.statusbar.showMessage('Measurement(s) complete.') 117 | self.dislpayMeasurement() 118 | self.buttonWidget.showButtons() 119 | 120 | def error(self, message): 121 | """Raise error warning.""" 122 | self.popupWarning.showWindow(str(message)) 123 | self.statusbar.showMessage('Measurement error!') 124 | self.buttonWidget.hideButtons() 125 | 126 | def dislpayMeasurement(self): 127 | """Display the data on screen.""" 128 | try: 129 | # IV sweep display 130 | if self.params['Measurement'] == 'iv-sweep': 131 | df = pd.read_csv(str(self.params['Sample name'] + '-' + 132 | self.params['Measurement'] + '.csv'), '\t') 133 | self.mainWidget.drawIV(df) 134 | # OUTPUT sweep display 135 | elif self.params['Measurement'] == 'output': 136 | df = pd.read_csv(str(self.params['Sample name'] + '-' + 137 | self.params['Measurement'] + '.csv'), '\t') 138 | self.mainWidget.drawOutput(df) 139 | # TRANSFER sweep display 140 | elif self.params['Measurement'] == 'transfer': 141 | df = pd.read_csv(str(self.params['Sample name'] + '-neg-pos-' + 142 | self.params['Measurement'] + '.csv'), '\t') 143 | self.mainWidget.drawTransfer(df) 144 | # ALL sweeps display 145 | elif self.params['Measurement'] == 'all': 146 | self.mainWidget.drawAll(str(self.params['Sample name'])) 147 | # INVERTER sweep display 148 | elif self.params['Measurement'] == 'inverter': 149 | df = pd.read_csv(str(self.params['Sample name'] + '-' + 150 | self.params['Measurement'] + '.csv'), '\t') 151 | self.mainWidget.drawInverter(df) 152 | 153 | except FileNotFoundError: 154 | self.popupWarning.showWindow('Could not find data!') 155 | 156 | 157 | class measureThread(QThread): 158 | """Thread for running measurements.""" 159 | 160 | finishedSig = pyqtSignal() 161 | errorSig = pyqtSignal(str) 162 | 163 | def __init__(self, params): 164 | """Initialise threads.""" 165 | QThread.__init__(self) 166 | self.params = params 167 | 168 | def __del__(self): 169 | """When thread is deconstructed wait for porcesses to complete.""" 170 | self.wait() 171 | 172 | def run(self): 173 | """Logic to be run in background thread.""" 174 | try: 175 | keithley = k2636.K2636() 176 | begin_measure = time.time() 177 | 178 | if self.params['Measurement'] == 'iv-sweep': 179 | keithley.IVsweep(self.params['Sample name']) 180 | 181 | if self.params['Measurement'] == 'output': 182 | keithley.Output(self.params['Sample name']) 183 | 184 | if self.params['Measurement'] == 'transfer': 185 | keithley.Transfer(self.params['Sample name']) 186 | 187 | if self.params['Measurement'] == 'all': 188 | keithley.IVsweep(self.params['Sample name']) 189 | keithley.Output(self.params['Sample name']) 190 | keithley.Transfer(self.params['Sample name']) 191 | 192 | if self.params['Measurement'] == 'inverter': 193 | keithley.Inverter(self.params['Sample name']) 194 | 195 | keithley.closeConnection() 196 | self.finishedSig.emit() 197 | finish_measure = time.time() 198 | print('-------------------------------------------\nAll measurements complete. Total time % .2f mins.' 199 | % ((finish_measure - begin_measure) / 60)) 200 | 201 | except ConnectionError: 202 | self.errorSig.emit('No measurement made. Please retry.') 203 | self.quit() 204 | 205 | 206 | if __name__ == '__main__': 207 | app = QApplication(sys.argv) 208 | mainGUI = GUI() 209 | sys.exit(app.exec_()) 210 | -------------------------------------------------------------------------------- /k2636.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module for interacting with the Keithley 2636B SMU. 3 | 4 | Author: Ross 5 | """ 6 | 7 | import visa 8 | import pandas as pd 9 | import matplotlib.pyplot as plt 10 | import matplotlib.style as style 11 | import time 12 | from serial import SerialException 13 | 14 | 15 | class K2636(): 16 | """Class for Keithley control.""" 17 | 18 | def __init__(self, address='ASRL/dev/ttyUSB0', read_term='\n', 19 | baudrate=57600): 20 | """Make instrument connection instantly on calling class.""" 21 | rm = visa.ResourceManager('@py') # use py-visa backend 22 | self.makeConnection(rm, address, read_term, baudrate) 23 | 24 | def makeConnection(self, rm, address, read_term, baudrate): 25 | """Make initial connection to instrument.""" 26 | try: 27 | if 'ttyS' or 'ttyUSB' in str(address): 28 | # Connection via SERIAL 29 | self.inst = rm.open_resource(address) 30 | self.inst.read_termination = str(read_term) 31 | self.inst.baud_rate = baudrate 32 | 33 | if 'GPIB' in str(address): 34 | # Connection via GPIB 35 | print('No GPIB support. Please use serial') 36 | 37 | except SerialException: 38 | print("CONNECTION ERROR: Check instrument address.") 39 | raise ConnectionError 40 | 41 | def closeConnection(self): 42 | """Close connection to keithley.""" 43 | try: 44 | self.inst.close() 45 | 46 | except(NameError): 47 | print('CONNECTION ERROR: No connection established.') 48 | 49 | except(AttributeError): 50 | print('CONNECTION ERROR: No connection established.') 51 | 52 | def _write(self, m): 53 | """Write to instrument.""" 54 | try: 55 | assert type(m) == str 56 | self.inst.write(m) 57 | except AttributeError: 58 | print('CONNECTION ERROR: No connection established.') 59 | 60 | def _read(self): 61 | """Read instrument.""" 62 | r = self.inst.read() 63 | return r 64 | 65 | def _query(self, s): 66 | """Query instrument.""" 67 | try: 68 | r = self.inst.query(s) 69 | return r 70 | except SerialException: 71 | return ('Serial port busy, try again.') 72 | except FileNotFoundError: 73 | return ('CONNECTION ERROR: No connection established.') 74 | except AttributeError: 75 | print('CONNECTION ERROR: No connection established.') 76 | return ('CONNECTION ERROR: No connection established.') 77 | 78 | def loadTSP(self, tsp): 79 | """Load an anonymous TSP script into the K2636 nonvolatile memory.""" 80 | try: 81 | tsp_dir = 'TSP-scripts/' # Put all tsp scripts in this folder 82 | self._write('loadscript') 83 | line_count = 1 84 | for line in open(str(tsp_dir + tsp), mode='r'): 85 | self._write(line) 86 | line_count += 1 87 | self._write('endscript') 88 | print('----------------------------------------') 89 | print('Uploaded TSP script: ', tsp) 90 | 91 | except FileNotFoundError: 92 | print('ERROR: Could not find tsp script. Check path.') 93 | raise SystemExit 94 | 95 | def runTSP(self): 96 | """Run the anonymous TSP script currently loaded in the K2636 memory.""" 97 | self._write('script.anonymous.run()') 98 | print('Measurement in progress...') 99 | 100 | def readBuffer(self): 101 | """Read buffer in memory and return an array.""" 102 | try: 103 | vg = [float(x) for x in self._query('printbuffer' + 104 | '(1, smub.nvbuffer1.n, smub.nvbuffer1.sourcevalues)').split(',')] 105 | ig = [float(x) for x in self._query('printbuffer' + 106 | '(1, smub.nvbuffer1.n, smub.nvbuffer1.readings)').split(',')] 107 | vd = [float(x) for x in self._query('printbuffer' + 108 | '(1, smua.nvbuffer1.n, smua.nvbuffer1.sourcevalues)').split(',')] 109 | c = [float(x) for x in self._query('printbuffer' + 110 | '(1, smua.nvbuffer1.n, smua.nvbuffer1.readings)').split(',')] 111 | 112 | df = pd.DataFrame({'Gate Voltage [V]': vg, 113 | 'Channel Voltage [V]': vd, 114 | 'Channel Current [A]': c, 115 | 'Gate Leakage [A]': ig}) 116 | return df 117 | 118 | except SerialException: 119 | print('Cannot read buffer.') 120 | return 121 | 122 | def readBufferIV(self): 123 | """Read specified buffer in keithley memory and return an array.""" 124 | vd = [float(x) for x in self._query('printbuffer' + 125 | '(1, smua.nvbuffer1.n, smua.nvbuffer1.sourcevalues)').split(',')] 126 | c = [float(x) for x in self._query('printbuffer' + 127 | '(1, smua.nvbuffer1.n, smua.nvbuffer1.readings)').split(',')] 128 | df = pd.DataFrame({'Channel Voltage [V]': vd, 'Channel Current [A]': c}) 129 | return df 130 | 131 | def readBufferInverter(self): 132 | """Read specified buffer for inverter measurement.""" 133 | SMUAsrc = [float(x) for x in self._query('printbuffer' + 134 | '(1, smua.nvbuffer1.n, smua.nvbuffer1.sourcevalues)').split(',')] 135 | SMUAread = [float(x) for x in self._query('printbuffer' + 136 | '(1, smua.nvbuffer1.n, smua.nvbuffer1.readings)').split(',')] 137 | SMUBsrc = [float(x) for x in self._query('printbuffer' + 138 | '(1, smub.nvbuffer1.n, smub.nvbuffer1.sourcevalues)').split(',')] 139 | SMUBread = [float(x) for x in self._query('printbuffer' + 140 | '(1, smub.nvbuffer1.n, smub.nvbuffer1.readings)').split(',')] 141 | df = pd.DataFrame({'Voltage In [V]': SMUAread, 'Voltage Out [V]': SMUBread, 'SMUA source': SMUAsrc, 'SMUB source': SMUBsrc}) 142 | return df 143 | 144 | def DisplayMeasurement(self, sample): 145 | """Show graphs of measurements.""" 146 | try: 147 | style.use('ggplot') 148 | fig, ([ax1, ax2], [ax3, ax4]) = plt.subplots(2, 2, figsize=(20, 10), 149 | dpi=80, facecolor='w', 150 | edgecolor='k') 151 | 152 | df1 = pd.read_csv(str(sample+'-iv-sweep.csv'), '\t') 153 | ax1.plot(df1['Channel Voltage [V]'], 154 | df1['Channel Current [A]'], '.') 155 | ax1.set_title('I-V sweep') 156 | ax1.set_xlabel('Channel Voltage [V]') 157 | ax1.set_ylabel('Channel Current [A]') 158 | 159 | df2 = pd.read_csv(str(sample+'-output.csv'), '\t') 160 | ax2.plot(df2['Channel Voltage [V]'], 161 | df2['Channel Current [A]'], '.') 162 | ax2.set_title('Output curves') 163 | ax2.set_xlabel('Channel Voltage [V]') 164 | ax2.set_ylabel('Channel Current [A]') 165 | 166 | df3 = pd.read_csv(str(sample+'-transfer.csv'), '\t') 167 | ax3.plot(df3['Gate Voltage [V]'], 168 | df3['Channel Current [A]'], '.') 169 | ax3.set_title('Transfer Curves') 170 | ax3.set_xlabel('Gate Voltage [V]') 171 | ax3.set_ylabel('Channel Current [A]') 172 | 173 | df4 = pd.read_csv(str(sample+'-transfer.csv'), '\t') 174 | ax4.plot(df4['Gate Voltage [V]'], 175 | df4['Gate Leakage [A]'], '.') 176 | ax4.set_title('Gate leakage current') 177 | ax4.set_xlabel('Gate Voltage [V]') 178 | ax4.set_ylabel('Gate Leakage [A]') 179 | 180 | fig.tight_layout() 181 | fig.savefig(sample) 182 | plt.show() 183 | 184 | except(FileNotFoundError): 185 | print('Sample name not found.') 186 | 187 | def IVsweep(self, sample): 188 | """K2636 IV sweep.""" 189 | try: 190 | begin_time = time.time() 191 | self.loadTSP('iv-sweep.tsp') 192 | self.runTSP() 193 | df = self.readBufferIV() 194 | output_name = str(sample + '-iv-sweep.csv') 195 | df.to_csv(output_name, sep='\t', index=False) 196 | finish_time = time.time() 197 | print('IV sweep complete. Elapsed time %.2f mins.' 198 | % ((finish_time - begin_time)/60)) 199 | 200 | except(AttributeError): 201 | print('Cannot perform IV sweep: no keithley connected.') 202 | 203 | def Output(self, sample): 204 | """K2636 Output sweeps.""" 205 | try: 206 | begin_time = time.time() 207 | self.loadTSP('output-charact.tsp') 208 | self.runTSP() 209 | df = self.readBuffer() 210 | output_name = str(sample + '-output.csv') 211 | df.to_csv(output_name, sep='\t', index=False) 212 | finish_time = time.time() 213 | print('Output sweeps complete. Elapsed time %.2f mins.' 214 | % ((finish_time - begin_time) / 60)) 215 | 216 | except(AttributeError): 217 | print('Cannot perform output sweep: no keithley connected.') 218 | 219 | def Transfer(self, sample): 220 | """K2636 Transfer sweeps.""" 221 | try: 222 | begin_time = time.time() 223 | self.loadTSP('transfer-charact.tsp') 224 | self.runTSP() 225 | df = self.readBuffer() 226 | output_name = str(sample + '-neg-pos-transfer.csv') 227 | df.to_csv(output_name, sep='\t', index=False) 228 | 229 | # transfer reverse scan 230 | self.loadTSP('transfer-charact-2.tsp') 231 | self.runTSP() 232 | df = self.readBuffer() 233 | output_name = str(sample + '-pos-neg-transfer.csv') 234 | df.to_csv(output_name, sep='\t', index=False) 235 | 236 | finish_time = time.time() 237 | print('Transfer curves measured. Elapsed time %.2f mins.' 238 | % ((finish_time - begin_time) / 60)) 239 | 240 | except(AttributeError): 241 | print('Cannot perform transfer sweep: no keithley connected.') 242 | 243 | def Inverter(self, sample): 244 | """K2636 inverter measurement.""" 245 | try: 246 | begin_time = time.time() 247 | self.loadTSP('inverter.tsp') 248 | self.runTSP() 249 | df = self.readBufferInverter() 250 | output_name = str(sample + '-neg-pos-inverter.csv') 251 | df.to_csv(output_name, sep='\t', index=False) 252 | 253 | # inverter reverse scan 254 | self.loadTSP('inverter-reverse.tsp') 255 | self.runTSP() 256 | df = self.readBufferInverter() 257 | output_name = str(sample + '-pos-neg-inverter.csv') 258 | df.to_csv(output_name, sep='\t', index=False) 259 | 260 | finish_time = time.time() 261 | print('Inverter measurement complete. Elapsed time %.2f mins.' 262 | % ((finish_time - begin_time) / 60)) 263 | 264 | except(AttributeError): 265 | print('Cannot perform output sweep: no keithley connected.') 266 | ######################################################################## 267 | 268 | 269 | if __name__ == '__main__': 270 | """For testing methods in the K2636 class.""" 271 | keithley = K2636(address='ASRL/dev/ttyUSB0', read_term='\n', baudrate=57600) 272 | sample = 'blank-20-1' 273 | keithley.IVsweep(sample) 274 | # keithley.Output(sample) 275 | # keithley.Transfer(sample) 276 | # keithley.DisplayMeasurement(sample) 277 | keithley.closeConnection() 278 | -------------------------------------------------------------------------------- /ofetMeasureGUI.py: -------------------------------------------------------------------------------- 1 | """ 2 | Qt5 GUI for making OFET measurements with a Keithley 2636. 3 | 4 | Author: Ross 5 | """ 6 | 7 | import k2636 # Driver for keithley 2636 8 | import sys 9 | import fnmatch 10 | import pandas as pd 11 | from PyQt5.QtCore import pyqtSignal, Qt 12 | from PyQt5.QtWidgets import (QMainWindow, QDockWidget, QWidget, QDesktopWidget, 13 | QApplication, QGridLayout, QPushButton, QLabel, 14 | QDoubleSpinBox, QAction, qApp, QSizePolicy, 15 | QTextEdit, QFileDialog, QInputDialog, QLineEdit, 16 | QMessageBox) 17 | 18 | import matplotlib 19 | from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas 20 | from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as mplToolb 21 | import matplotlib.style as style 22 | from matplotlib.figure import Figure 23 | 24 | matplotlib.use("Qt5Agg") 25 | 26 | 27 | class mainWindow(QMainWindow): 28 | """Create mainwindow of GUI.""" 29 | 30 | def __init__(self): 31 | """Initalise mainwindow.""" 32 | super().__init__() 33 | self.initUI() 34 | 35 | def initUI(self): 36 | """Make signal connections.""" 37 | # Add central widget 38 | self.mainWidget = mplWidget() 39 | self.setCentralWidget(self.mainWidget) 40 | 41 | # Add other window widgets 42 | self.keithleySettingsWindow = keithleySettingsWindow() 43 | self.keithleyConnectionWindow = keithleyConnectionWindow() 44 | self.keithleyErrorWindow = keithleyErrorWindow() 45 | self.popupWarning = warningWindow() 46 | 47 | # Dock setup 48 | # Keithley dock widget 49 | self.buttonWidget = keithleyButtonWidget() 50 | self.dockWidget1 = QDockWidget('Keithley Control') 51 | self.dockWidget1.setWidget(self.buttonWidget) 52 | self.addDockWidget(Qt.BottomDockWidgetArea, self.dockWidget1) 53 | 54 | # Matplotlib control widget 55 | self.dockWidget2 = QDockWidget('Plotting controls') 56 | self.dockWidget2.setWidget(mplToolb(self.mainWidget, self)) 57 | self.addDockWidget(Qt.BottomDockWidgetArea, self.dockWidget2) 58 | 59 | # Menu bar setup 60 | # Shutdown program 61 | exitAction = QAction('&Exit', self) 62 | exitAction.setShortcut('Ctrl+Q') 63 | exitAction.setStatusTip('Exit application') 64 | exitAction.triggered.connect(qApp.quit) 65 | # Load old data 66 | loadAction = QAction('&Load', self) 67 | loadAction.setShortcut('Ctrl+L') 68 | loadAction.setStatusTip('Load data to be displayed') 69 | loadAction.triggered.connect(self.showFileOpen) 70 | # Load old ALL data 71 | loadALLAction = QAction('&Load ALL', self) 72 | loadALLAction.setShortcut('Ctrl+A') 73 | loadALLAction.setStatusTip( 74 | 'Load iv, output and transfer data to be displayed') 75 | loadALLAction.triggered.connect(self.showFileOpenALL) 76 | # Clear data 77 | clearAction = QAction('Clear', self) 78 | clearAction.setShortcut('Ctrl+C') 79 | clearAction.setStatusTip('Clear data on graph') 80 | clearAction.triggered.connect(self.mainWidget.clear) 81 | # Keithley settings popup 82 | keithleyAction = QAction('Settings', self) 83 | keithleyAction.setShortcut('Ctrl+K') 84 | keithleyAction.setStatusTip('Adjust scan parameters') 85 | keithleyConAction = QAction('Connect', self) 86 | keithleyConAction.setShortcut('Ctrl+J') 87 | keithleyConAction.setStatusTip('Reconnect to keithley 2636') 88 | keithleyAction.triggered.connect(self.keithleySettingsWindow.show) 89 | keithleyConAction.triggered.connect(self.keithleyConnectionWindow.show) 90 | keithleyError = QAction('Error Log', self) 91 | keithleyError.setShortcut('Ctrl+E') 92 | keithleyError.triggered.connect(self.keithleyErrorWindow.show) 93 | 94 | # Add items to menu bars 95 | menubar = self.menuBar() 96 | fileMenu = menubar.addMenu('&File') 97 | fileMenu.addAction(loadAction) 98 | fileMenu.addAction(loadALLAction) 99 | fileMenu.addAction(clearAction) 100 | fileMenu.addSeparator() 101 | fileMenu.addAction(exitAction) 102 | keithleyMenu = menubar.addMenu('&Keithley') 103 | keithleyMenu.addAction(keithleyConAction) 104 | keithleyMenu.addAction(keithleyAction) 105 | keithleyMenu.addAction(keithleyError) 106 | 107 | # Status bar setup 108 | self.statusbar = self.statusBar() 109 | 110 | # Attempt to connect to a keithley 111 | self.testKeithleyConnection() 112 | self.keithleyConnectionWindow.connectionSig.connect 113 | (self.buttonWidget.showButtons) 114 | 115 | # Window setup 116 | self.resize(800, 800) 117 | self.centre() 118 | self.setWindowTitle('K2636 - OFET Measurements') 119 | self.show() 120 | 121 | def testKeithleyConnection(self): 122 | """Connect to the keithley on initialisation.""" 123 | try: 124 | self.keithley = k2636.K2636(address='ASRL/dev/ttyUSB0', 125 | read_term='\n', baudrate=57600) 126 | self.statusbar.showMessage('Keithley found.') 127 | self.buttonWidget.showButtons() 128 | self.keithley.closeConnection() 129 | except ConnectionError: 130 | self.buttonWidget.hideButtons() 131 | self.statusbar.showMessage('No keithley connection.') 132 | 133 | def centre(self): 134 | """Find screen size and place in centre.""" 135 | screen = QDesktopWidget().screenGeometry() 136 | size = self.geometry() 137 | self.move((screen.width()-size.width())/2, 138 | (screen.height()-size.height())/2) 139 | 140 | def showFileOpen(self): 141 | """Pop up for file selection.""" 142 | filt1 = '*.csv' 143 | fname = QFileDialog.getOpenFileName(self, 'Open file', filter=filt1) 144 | if fname[0]: 145 | try: 146 | df = pd.read_csv(fname[0], '\t') 147 | if fnmatch.fnmatch(fname[0], '*iv-sweep.csv'): 148 | self.mainWidget.drawIV(df) 149 | elif fnmatch.fnmatch(fname[0], '*output.csv'): 150 | self.mainWidget.drawOutput(df) 151 | elif fnmatch.fnmatch(fname[0], '*transfer.csv'): 152 | self.mainWidget.drawTransfer(df) 153 | elif fnmatch.fnmatch(fname[0], '*gate-leakage.csv'): 154 | self.mainWidget.drawLeakage(df) 155 | elif fnmatch.fnmatch(fname[0], '*inverter.csv'): 156 | self.mainWidget.drawInverter(df) 157 | else: 158 | raise FileNotFoundError 159 | except KeyError or FileNotFoundError: 160 | self.popupWarning.showWindow('Unsupported file.') 161 | 162 | def showFileOpenALL(self): 163 | """Pop up for file selection for ALL measurements.""" 164 | filt1 = '*.csv' 165 | fname = QFileDialog.getOpenFileName(self, 'Open file', filter=filt1) 166 | if fname[0]: 167 | try: 168 | fileN = fname[0] 169 | if fnmatch.fnmatch(fname[0], '*iv-sweep.csv'): 170 | fileN = fileN[:-13] 171 | elif fnmatch.fnmatch(fname[0], '*output.csv'): 172 | fileN = fileN[:-11] 173 | elif fnmatch.fnmatch(fname[0], '*transfer.csv'): 174 | fileN = fileN[:-21] 175 | elif fnmatch.fnmatch(fname[0], '*gate-leakage.csv'): 176 | fileN = fileN[:-17] 177 | elif fnmatch.fnmatch(fname[0], '*inverter.csv'): 178 | fileN = fileN[:-13] 179 | self.mainWidget.drawAll(fileN) 180 | 181 | except KeyError or FileNotFoundError: 182 | self.popupWarning.showWindow('Unsupported file.') 183 | 184 | 185 | def updateStatusbar(slf, s): 186 | """Put text in status bar.""" 187 | self.statusbar.showMessage(s) 188 | 189 | 190 | class keithleyButtonWidget(QWidget): 191 | """Defines class with buttons controlling keithley.""" 192 | 193 | # Define signals to be emitted from widget 194 | cancelSignal = pyqtSignal() 195 | 196 | def __init__(self): 197 | """Initialise setup of widget.""" 198 | super().__init__() 199 | self.initWidget() 200 | 201 | def initWidget(self): 202 | """Initialise connections.""" 203 | # Set widget layout 204 | grid = QGridLayout() 205 | self.setLayout(grid) 206 | 207 | # Push button setup 208 | self.ivBtn = QPushButton('IV Sweep') 209 | grid.addWidget(self.ivBtn, 1, 1) 210 | self.ivBtn.clicked.connect(self.showSampleNameInput) 211 | 212 | self.outputBtn = QPushButton('Output Sweep') 213 | grid.addWidget(self.outputBtn, 1, 2) 214 | self.outputBtn.clicked.connect(self.showSampleNameInput) 215 | 216 | self.transferBtn = QPushButton('Transfer Sweep') 217 | grid.addWidget(self.transferBtn, 1, 3) 218 | self.transferBtn.clicked.connect(self.showSampleNameInput) 219 | 220 | self.allBtn = QPushButton('ALL') 221 | grid.addWidget(self.allBtn, 1, 4) 222 | self.allBtn.clicked.connect(self.showSampleNameInput) 223 | 224 | self.inverterBtn = QPushButton('Voltage Inverter') 225 | grid.addWidget(self.inverterBtn, 2, 1) 226 | self.inverterBtn.clicked.connect(self.inverterPopup) 227 | self.inverterBtn.clicked.connect(self.showSampleNameInput) 228 | 229 | def showSampleNameInput(self): 230 | """Popup for sample name input.""" 231 | samNam = QInputDialog() 232 | try: 233 | text, ok = samNam.getText(self, 'Sample Name', 234 | 'Enter sample name:', 235 | QLineEdit.Normal, 236 | str(self.SampleName)) 237 | 238 | except AttributeError: 239 | text, ok = samNam.getText(self, 'Sample Name', 240 | 'Enter sample name:') 241 | if ok: 242 | if text != '': # to catch empty input 243 | self.SampleName = str(text) 244 | else: 245 | self.SampleName = None 246 | self.cancelSignal.emit() # doesnt link to anything yet 247 | 248 | def inverterPopup(self): 249 | """Popup for inverter setup change.""" 250 | inverterWarn = QMessageBox() 251 | inverterWarn.setText('WARNING: Make sure correct wiring' + 252 | ' for this measurement') 253 | inverterWarn.exec() 254 | 255 | def hideButtons(self): 256 | """Hide control buttons.""" 257 | self.ivBtn.setEnabled(False) 258 | self.outputBtn.setEnabled(False) 259 | self.transferBtn.setEnabled(False) 260 | self.allBtn.setEnabled(False) 261 | self.inverterBtn.setEnabled(False) 262 | 263 | def showButtons(self): 264 | """Show control buttons.""" 265 | self.ivBtn.setEnabled(True) 266 | self.outputBtn.setEnabled(True) 267 | self.transferBtn.setEnabled(True) 268 | self.allBtn.setEnabled(True) 269 | self.inverterBtn.setEnabled(True) 270 | 271 | 272 | class mplWidget(FigureCanvas): 273 | """Widget for matplotlib figure.""" 274 | 275 | def __init__(self, parent=None): 276 | """Create plotting widget.""" 277 | self.initWidget() 278 | 279 | def initWidget(self, parent=None, width=5, height=4, dpi=100): 280 | """Set parameters of plotting widget.""" 281 | style.use('ggplot') # Looks the best? 282 | 283 | self.fig = Figure(figsize=(width, height), dpi=dpi) 284 | self.ax1 = self.fig.add_subplot(111) 285 | 286 | self.ax1.set_title('IV Sweep') 287 | self.ax1.set_xlabel('Channel Voltage [V]') 288 | self.ax1.set_ylabel('Channel Current [A]') 289 | 290 | FigureCanvas.__init__(self, self.fig) 291 | self.setParent(parent) 292 | FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, 293 | QSizePolicy.Expanding) 294 | FigureCanvas.updateGeometry(self) 295 | 296 | def drawIV(self, df): 297 | """Take a data frame and draw it.""" 298 | self.ax1 = self.fig.add_subplot(111) 299 | self.ax1.plot(df['Channel Voltage [V]'], df['Channel Current [A]'], 300 | '.') 301 | self.ax1.set_title('IV Sweep') 302 | self.ax1.set_xlabel('Channel Voltage [V]') 303 | self.ax1.set_ylabel('Channel Current [A]') 304 | FigureCanvas.draw(self) 305 | 306 | def drawOutput(self, df): 307 | """Take a data frame and draw it.""" 308 | self.ax1 = self.fig.add_subplot(111) 309 | self.ax1.plot(df['Channel Voltage [V]'], df['Channel Current [A]'], 310 | '.') 311 | self.ax1.set_title('Output curves') 312 | self.ax1.set_xlabel('Channel Voltage [V]') 313 | self.ax1.set_ylabel('Channel Current [A]') 314 | FigureCanvas.draw(self) 315 | 316 | def drawTransfer(self, df): 317 | """Take a data frame and draw it.""" 318 | self.ax1 = self.fig.add_subplot(111) 319 | self.ax1.semilogy(df['Gate Voltage [V]'], 320 | abs(df['Channel Current [A]']), '.') 321 | self.ax1.set_title('Transfer Curve') 322 | self.ax1.set_xlabel('Gate Voltage [V]') 323 | self.ax1.set_ylabel('Channel Current [A]') 324 | FigureCanvas.draw(self) 325 | 326 | def drawLeakage(self, df): 327 | """Take a data frame and draw it.""" 328 | self.ax1 = self.fig.add_subplot(111) 329 | self.ax1.plot(df['Gate Voltage [V]'], df['Gate Leakage [A]'], '.') 330 | self.ax1.set_title('Leakage from gate to drain') 331 | self.ax1.set_xlabel('Gate Voltage [V]') 332 | self.ax1.set_ylabel('Gate Leakage [A]') 333 | FigureCanvas.draw(self) 334 | 335 | def drawAll(self, sample): 336 | """Take all sweeps and draw them.""" 337 | try: 338 | df1 = pd.read_csv(str(sample + '-iv-sweep.csv'), '\t') 339 | df2 = pd.read_csv(str(sample + '-output.csv'), '\t') 340 | df3 = pd.read_csv( 341 | str(sample + '-neg-pos-transfer.csv'), '\t') 342 | df4 = pd.read_csv( 343 | str(sample + '-pos-neg-transfer.csv'), '\t') 344 | except FileNotFoundError: 345 | # If it can't find some data, dont worry :) 346 | pass 347 | 348 | self.fig.clear() 349 | self.ax1 = self.fig.add_subplot(221) 350 | self.ax2 = self.fig.add_subplot(222) 351 | self.ax3 = self.fig.add_subplot(223) 352 | self.ax4 = self.fig.add_subplot(224) 353 | 354 | try: 355 | self.ax1.plot(df1['Channel Voltage [V]'], 356 | df1['Channel Current [A]'] / 1e-6, '.') 357 | self.ax1.set_title('I-V sweep') 358 | self.ax1.set_xlabel('Channel Voltage [V]') 359 | self.ax1.set_ylabel('Channel Current [$\mu$A]') 360 | 361 | self.ax2.plot(df2['Channel Voltage [V]'], 362 | df2['Channel Current [A]'] / 1e-6, '.') 363 | self.ax2.set_title('Output curves') 364 | self.ax2.set_xlabel('Channel Voltage [V]') 365 | self.ax2.set_ylabel('Channel Current [$\mu$A]') 366 | 367 | self.ax3.semilogy(df3['Gate Voltage [V]'], 368 | abs(df3['Channel Current [A]']), '.') 369 | self.ax3.set_title('Transfer Curves') 370 | self.ax3.set_xlabel('Gate Voltage [V]') 371 | self.ax3.set_ylabel('Channel Current [A]') 372 | 373 | self.ax3.semilogy(df4['Gate Voltage [V]'], 374 | abs(df4['Channel Current [A]']), '.') 375 | self.ax3.set_title('Transfer Curves') 376 | self.ax3.set_xlabel('Gate Voltage [V]') 377 | self.ax3.set_ylabel('Channel Current [A]') 378 | 379 | self.ax4.plot(df3['Gate Voltage [V]'], 380 | df3['Gate Leakage [A]'] / 1e-9, '.') 381 | self.ax4.set_title('Gate leakage current') 382 | self.ax4.set_xlabel('Gate Voltage [V]') 383 | self.ax4.set_ylabel('Gate Leakage [nA]') 384 | except UnboundLocalError: 385 | pass # if data isnt there, it cant be plotted 386 | 387 | 388 | self.fig.tight_layout() 389 | FigureCanvas.draw(self) 390 | 391 | def drawInverter(self, df): 392 | """Take a data frame and draw it.""" 393 | self.ax1 = self.fig.add_subplot(111) 394 | self.ax1.plot(df['Voltage In [V]'], df['Voltage Out [V]'], 395 | '.') 396 | self.ax1.set_title('Inverter') 397 | self.ax1.set_xlabel('Voltage In [V]') 398 | self.ax1.set_ylabel('Voltage Out [V]') 399 | self.fig.tight_layout() 400 | FigureCanvas.draw(self) 401 | 402 | def clear(self): 403 | """Clear the plot.""" 404 | self.fig.clear() 405 | FigureCanvas.draw(self) 406 | 407 | 408 | class keithleySettingsWindow(QWidget): 409 | """Keithley settings popup.""" 410 | 411 | def __init__(self): 412 | """Initialise setup.""" 413 | super().__init__() 414 | self.initWidget() 415 | 416 | def initWidget(self): 417 | """Initialise connections.""" 418 | # Set widget layout 419 | grid = QGridLayout() 420 | self.setLayout(grid) 421 | # Columns 422 | col1 = QLabel('Initial Voltage') 423 | col2 = QLabel('Final Voltage') 424 | col3 = QLabel('Voltage Step') 425 | col4 = QLabel('Step Time') 426 | grid.addWidget(col1, 1, 2) 427 | grid.addWidget(col2, 1, 3) 428 | grid.addWidget(col3, 1, 4) 429 | grid.addWidget(col4, 1, 5) 430 | # Rows 431 | row1 = QLabel('IV') 432 | row2 = QLabel('Ouput') 433 | row3 = QLabel('Transfer') 434 | grid.addWidget(row1, 2, 1) 435 | grid.addWidget(row2, 3, 1) 436 | grid.addWidget(row3, 4, 1) 437 | 438 | # IV Settings 439 | ivFirstV = QDoubleSpinBox(self) 440 | grid.addWidget(ivFirstV, 2, 2) 441 | ivFirstV.setMinimum(-100) 442 | ivFirstV.setValue(-5) 443 | ivLastV = QDoubleSpinBox(self) 444 | grid.addWidget(ivLastV, 2, 3) 445 | ivLastV.setValue(5) 446 | ivStepV = QDoubleSpinBox(self) 447 | grid.addWidget(ivStepV, 2, 4) 448 | ivStepV.setValue(0.1) 449 | ivStepT = QDoubleSpinBox(self) 450 | grid.addWidget(ivStepT, 2, 5) 451 | ivStepT.setValue(0.2) 452 | 453 | # Ouptut curve Settings 454 | outputFirstV = QDoubleSpinBox(self) 455 | grid.addWidget(outputFirstV, 3, 2) 456 | outputLastV = QDoubleSpinBox(self) 457 | grid.addWidget(outputLastV, 3, 3) 458 | outputStepV = QDoubleSpinBox(self) 459 | grid.addWidget(outputStepV, 3, 4) 460 | outputStepT = QDoubleSpinBox(self) 461 | grid.addWidget(outputStepT, 3, 5) 462 | 463 | # transfer Settings 464 | transferFirstV = QDoubleSpinBox(self) 465 | grid.addWidget(transferFirstV, 4, 2) 466 | transferLastV = QDoubleSpinBox(self) 467 | grid.addWidget(transferLastV, 4, 3) 468 | transferStepV = QDoubleSpinBox(self) 469 | grid.addWidget(transferStepV, 4, 4) 470 | transferStepT = QDoubleSpinBox(self) 471 | grid.addWidget(transferStepT, 4, 5) 472 | 473 | # OK button 474 | setSettings = QPushButton('Ok') 475 | grid.addWidget(setSettings, 5, 4) 476 | setSettings.clicked.connect(self.setIVparams) 477 | 478 | # Cancel button 479 | cancelSet = QPushButton('Cancel') 480 | grid.addWidget(cancelSet, 5, 5) 481 | cancelSet.clicked.connect(self.close) 482 | 483 | # Window setup 484 | self.centre() 485 | self.setWindowTitle('K2636 - Settings') 486 | 487 | def centre(self): 488 | """Find screen size and place in centre.""" 489 | screen = QDesktopWidget().screenGeometry() 490 | size = self.geometry() 491 | self.move((screen.width()-size.width())/2, 492 | (screen.height()-size.height())/2) 493 | 494 | def setIVparams(self): 495 | """Store IV sweep settings in .tsp file.""" 496 | print('You are here') 497 | 498 | class keithleyConnectionWindow(QWidget): 499 | """Popup for connecting to instrument.""" 500 | 501 | connectionSig = pyqtSignal() 502 | 503 | def __init__(self): 504 | """Initialise setup.""" 505 | super().__init__() 506 | self.initWidget() 507 | 508 | def initWidget(self): 509 | """Initialise connections.""" 510 | # Set widget layout 511 | grid = QGridLayout() 512 | self.setLayout(grid) 513 | 514 | # Connection status box 515 | self.connStatus = QTextEdit('Push button to connect to keithley...') 516 | self.connButton = QPushButton('Connect') 517 | self.connButton.clicked.connect(self.reconnect2keithley) 518 | grid.addWidget(self.connStatus, 1, 1) 519 | grid.addWidget(self.connButton, 2, 1) 520 | 521 | # Window setup 522 | self.resize(300, 100) 523 | self.centre() 524 | self.setWindowTitle('K2636 - Connecting') 525 | 526 | def centre(self): 527 | """Find screen size and place in centre.""" 528 | screen = QDesktopWidget().screenGeometry() 529 | size = self.geometry() 530 | self.move((screen.width()-size.width()) / 2, 531 | (screen.height()-size.height()) / 2) 532 | 533 | def reconnect2keithley(self): 534 | """Reconnect to instrument.""" 535 | try: 536 | self.keithley = k2636.K2636(address='ASRL/dev/ttyUSB0', 537 | read_term='\n', baudrate=57600) 538 | self.connStatus.append('Connection successful') 539 | self.connectionSig.emit() 540 | self.keithley.closeConnection() 541 | 542 | except ConnectionError: 543 | self.connStatus.append('No Keithley can be found.') 544 | 545 | 546 | class keithleyErrorWindow(QWidget): 547 | """Popup for reading error messages.""" 548 | 549 | def __init__(self): 550 | """Initialise setup.""" 551 | super().__init__() 552 | self.initWidget() 553 | 554 | def initWidget(self): 555 | """Initialise connections.""" 556 | # Set widget layout 557 | grid = QGridLayout() 558 | self.setLayout(grid) 559 | 560 | # Connection status box 561 | self.errorStatus = QTextEdit('ERROR CODE------------------MESSAGE') 562 | self.errorButton = QPushButton('Read error') 563 | self.errorButton.clicked.connect(self.readError) 564 | grid.addWidget(self.errorStatus, 1, 1) 565 | grid.addWidget(self.errorButton, 2, 1) 566 | 567 | # Window setup 568 | self.resize(600, 300) 569 | self.centre() 570 | self.setWindowTitle('K2636 - Error Log') 571 | 572 | def centre(self): 573 | """Find screen size and place in centre.""" 574 | screen = QDesktopWidget().screenGeometry() 575 | size = self.geometry() 576 | self.move((screen.width()-size.width()) / 2, 577 | (screen.height()-size.height()) / 2) 578 | 579 | def readError(self): 580 | """Reconnect to instrument.""" 581 | self.keithley = k2636.K2636(address='ASRL/dev/ttyUSB0', 582 | read_term='\n', baudrate=57600) 583 | 584 | self.keithley._write('errorCode, message, severity, errorNode' + 585 | '= errorqueue.next()') 586 | self.keithley._write('print(errorCode, message)') 587 | error = self.keithley._query('') 588 | self.errorStatus.append(error) 589 | self.keithley.closeConnection() 590 | 591 | 592 | class warningWindow(QWidget): 593 | """Warning window popup.""" 594 | 595 | def __init__(self): 596 | """Intial setup.""" 597 | super().__init__() 598 | self.initWidget() 599 | 600 | def initWidget(self): 601 | """Initialise connections.""" 602 | # Set widget layout 603 | grid = QGridLayout() 604 | self.setLayout(grid) 605 | 606 | # Connection status box 607 | self.warning = QLabel() 608 | self.continueButton = QPushButton('Continue') 609 | self.continueButton.clicked.connect(self.hide) 610 | grid.addWidget(self.warning, 1, 1) 611 | grid.addWidget(self.continueButton, 2, 1) 612 | 613 | # Window setup 614 | self.resize(180, 80) 615 | self.centre() 616 | self.setWindowTitle('Error!') 617 | 618 | def centre(self): 619 | """Find screen size and place in centre.""" 620 | screen = QDesktopWidget().screenGeometry() 621 | size = self.geometry() 622 | self.move((screen.width()-size.width()) / 2, 623 | (screen.height()-size.height()) / 2) 624 | 625 | def showWindow(self, s): 626 | """Write error message and show window.""" 627 | self.warning.setText(s) 628 | self.show() 629 | 630 | 631 | if __name__ == '__main__': 632 | 633 | app = QApplication(sys.argv) 634 | GUI = mainWindow() 635 | sys.exit(app.exec_()) 636 | --------------------------------------------------------------------------------