├── .gitignore ├── LICENSE ├── README.md ├── cap-log-01.txt ├── conf-output.txt ├── dmm-modes.py ├── dmm.py ├── example.py ├── gui.py ├── psu.py ├── read-amps.py ├── reform-v2.py ├── reform.py ├── set-amps.py ├── update.sh └── volts.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Shelby Jueden 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GPIB-Capacitor-Reforming 2 | 3 | Controlling GPIB test equipment to automate reforming capacitors 4 | 5 | 6 | ## Hardware 7 | 8 | - HP 6633A ([Manual](https://archive.org/details/hp-6632-a-33-a-34-a-operating)) - Programmable power supply with 0-50V, 0-2A range 9 | - HP 34401A ([Manual](https://archive.org/details/manuallib-id-2600272)) - 6.5 digit multimeter 10 | 11 | ## Theory 12 | 13 | Capcitors need to be slowly brought up to their rated voltage at a limited current (10m max per [this guide](https://web.archive.org/web/20220514225249/https://www.hb9aik.ch/notes/MIL-HDBK-1131C.pdf)). The goal is to rebuild the oxide layer acting as a dialectric that forms on the aluminium foil. Too much current or too high a voltage on a weakened capacitor will short through the dialectric causing catestrophic failure. The amount of oxide is relative to the maximum voltage the capacitor can handle 14 | 15 | 16 | ## Method 17 | 18 | Using the programmable power supply to automate this will allow the process to be more efficiently handled for larger quantities of capacitors. 19 | 20 | The basic steps that need to be taken are as follows: 21 | 1. Connect weak capctitor at zero voltage and current to PSU 22 | 2. Increase voltage by a small percentage of maximum rated value, with current limited 23 | 3. Watch current taken in by capacitor drop as capacitor reforms 24 | 4. When current drops significantly it has been formed up to that voltage, go back to step 2. 25 | 5. When the maximum volage rating has been reached and current rating reduces the capacitor reforming is complete 26 | 27 | 28 | ## Interfacing 29 | 30 | ### HP 6633A 31 | The 6633A uses a unique GP-IB command format. The basic control commands: 32 | - `VSET #` : Sets the output voltage on the PSU 33 | - `ISET #` : Sets the current limit on the PSU 34 | - `OUT [0,1]` : Enables or disables the output 35 | - `VOUT?` : Returns the current voltage measured at the output 36 | - `IOUT?` : Returns the current amprage measured at the output 37 | 38 | ### HP 34401A 39 | The 34401A uses SCPI for programming which is a robust standard with many options. The following are relevant to this project: 40 | - `CONF:VOLT:DC [range upper],[resolution minimum]` : Set DC voltage measurement mode and configure range setting 41 | - `CONF:RES [range upper],[resolution minimum]` : Set resistance measurement mode and configure range setting 42 | - `READ?` : Take a measurement and read it back 43 | - `SYST:BEEP` : Make short beep sound 44 | 45 | -------------------------------------------------------------------------------- /cap-log-01.txt: -------------------------------------------------------------------------------- 1 | Preforming: 2 | 3 | 1: 280kohm 4 | 2: 260kohm 5 | -------------------------------------------------------------------------------- /conf-output.txt: -------------------------------------------------------------------------------- 1 | CONF:VOLT:DC 50,0.0005 2 | b'"VOLT +1.000000E+02,+3.000000E-04"\n' 3 | CONF:VOLT:AC 50,0.0005 4 | b'"VOLT:AC +1.000000E+02,+1.000000E-04"\n' 5 | CONF:CURR:DC 50,0.0005 6 | b'"VOLT:AC +1.000000E+02,+1.000000E-04"\n' 7 | CONF:CURR:AC 50,0.0005 8 | b'"VOLT:AC +1.000000E+02,+1.000000E-04"\n' 9 | CONF:RES 50,0.0005 10 | b'"RES +1.000000E+02,+3.000000E-04"\n' 11 | CONF:FRES 50,0.0005 12 | b'"FRES +1.000000E+02,+3.000000E-04"\n' 13 | CONF:FREQ 50,0.0005 14 | b'"FREQ +3.000000E+00,+3.000000E-05"\n' 15 | CONF:PER 50,0.0005 16 | b'"FREQ +3.000000E+00,+3.000000E-05"\n' 17 | CONF:CONT 18 | b'"CONT"\n' 19 | CONF:DIOD 20 | b'"DIOD"\n' 21 | -------------------------------------------------------------------------------- /dmm-modes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys 4 | import time 5 | import Gpib 6 | 7 | ## General GP-IB Setup ## 8 | interface_id = 0 # Interface number of the GP-IB 9 | device_address = 7 # Address of the test insturment 10 | 11 | device = Gpib.Gpib(interface_id,device_address) 12 | 13 | # write a command 14 | #device.write("*IDN?") # Standard SCPI identification command 15 | # read data back and print 16 | #print(device.read(100)) # Read up to 100 bytes 17 | 18 | 19 | ## PSU Example ## 20 | #volts = 13.37 21 | current = 0.01 22 | # Set voltage and current from python variable 23 | #device.write("IOUT?") 24 | #print(float(device.read(100))) 25 | #device.write("ISET "+str(sys.argv[1])) 26 | 27 | modes=["CONF:VOLT:DC 50,0.0005", 28 | "CONF:VOLT:AC 50,0.0005", 29 | "CONF:CURR:DC 50,0.0005", 30 | "CONF:CURR:AC 50,0.0005", 31 | "CONF:RES 50,0.0005", 32 | "CONF:FRES 50,0.0005", 33 | "CONF:FREQ 50,0.0005", 34 | "CONF:PER 50,0.0005", 35 | "CONF:CONT", 36 | "CONF:DIOD"] 37 | 38 | ## DMM Example ## 39 | # set DC voltage for PSU range 40 | for mode in modes: 41 | device.write(mode) 42 | print(mode) 43 | time.sleep(0.1) 44 | device.write("CONF?") 45 | print(device.read(100)) 46 | 47 | # read measurement back converted from scientific notation 48 | #device.write("READ?") 49 | #print(float(device.read(100))) 50 | -------------------------------------------------------------------------------- /dmm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import Gpib 4 | import sys 5 | import time 6 | 7 | class DMM34401A: 8 | 9 | def __init__(self,gpib_address,gpib_interface=0): 10 | self.device = Gpib.Gpib(gpib_interface,gpib_address) 11 | self.modes = { 12 | "VOLTDC" : "VOLT", 13 | "VOLTAC" : "VOLT:AC", 14 | "CURRDC" : "CURR", 15 | "CURRAC" : "CURR:AC", 16 | "RES" : "RES", 17 | "FRES" : "FRES", 18 | "FREQ" : "FREQ", 19 | "CONT" : "CONT", 20 | "DIOD" : "DIOD" 21 | } 22 | 23 | def getMode(self): 24 | self.device.write("CONF?") 25 | return str(self.device.read(100)) 26 | 27 | def readVoltDC(self): 28 | if not self.getMode().startswith(self.modes["VOLTDC"]): 29 | return -1 30 | return self.read() 31 | 32 | def readCurrDC(self): 33 | if not self.getMode().startswith(self.modes["CURRDC"]): 34 | return -1 35 | return self.read() 36 | 37 | def setVolt(self, max_range, resolution): 38 | self.device.write("CONF:VOLT:DC "+str(max_range)+","+str(resolution)) 39 | 40 | def setCurrent(self, max_range, resolution): 41 | self.device.write("CONF:CURR:DC "+str(max_range)+","+str(resolution)) 42 | 43 | def read(self): 44 | self.device.write("READ?") 45 | return float(self.device.read(100)) 46 | 47 | def beep(self): 48 | self.device.write("SYST:BEEP") 49 | 50 | 51 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import Gpib 3 | 4 | ## General GP-IB Setup ## 5 | interface_id = 0 # Interface number of the GP-IB 6 | device_address = 7 # Address of the test insturment 7 | 8 | device = Gpib.Gpib(interface_id,device_address) 9 | 10 | # write a command 11 | device.write("*IDN?") # Standard SCPI identification command 12 | # read data back and print 13 | print(device.read(100)) # Read up to 100 bytes 14 | 15 | 16 | ## PSU Example ## 17 | volts = 13.37 18 | current = 0.512 19 | # Set voltage and current from python variable 20 | device.write("VSET "+str(volts)) 21 | device.write("ISET "+str(current)) 22 | 23 | ## DMM Example ## 24 | # set DC voltage for PSU range 25 | device.write("CONF:VOLT:DC 50,0.0005") 26 | 27 | # read measurement back converted from scientific notation 28 | device.write("READ?") 29 | print(float(device.read(100))) 30 | -------------------------------------------------------------------------------- /gui.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys,os 4 | import curses 5 | import time 6 | import datetime 7 | import csv 8 | from collections import deque 9 | 10 | class GUI: 11 | 12 | 13 | def __init__(self): 14 | # Initial screen and define settings for curses 15 | self.s=curses.initscr() 16 | curses.start_color() 17 | curses.use_default_colors() 18 | curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_WHITE) 19 | 20 | # Data values to build displays for 21 | self.readouts=[] 22 | self.logtables=[] 23 | self.readWid = 30 24 | self.readX = 0 25 | 26 | # Add a readout window to the screen. Returns id for readout 27 | # Optional unit to postpend to value 28 | # Optional group to combine multiple readouts 29 | def addReadout(self, value, label,unit="",group=""): 30 | self.readouts.append({"value":str(value),"label":label,"group":group,"unit":unit}) 31 | return len(self.readouts)-1 32 | 33 | # Update data for readout by id 34 | def updateReadout(self,index, value): 35 | self.readouts[index]["value"]=str(value) 36 | 37 | # Add a logging table window to the screen. Returns id for the readout 38 | # Optional lines to limit size of window 39 | def addLogTable(self,headers,filepath,lines=None): 40 | # Add time header with padded length for table 41 | headers.insert(0,"time ") 42 | self.logtables.append({"headers":headers,"lines":lines,"filepath":filepath}) 43 | 44 | # Create logfile 45 | with open(filepath,"w") as f: 46 | wr = csv.writer(f, quoting=csv.QUOTE_ALL) 47 | wr.writerow(headers) 48 | 49 | return len(self.logtables)-1 50 | 51 | # Update data for logging table 52 | def updateLogTable(self,index,data): 53 | # Add timestamp to data 54 | data.insert(0,str(datetime.datetime.now())) 55 | 56 | # Write line to logfile 57 | with open(self.logtables[index]["filepath"],"a") as f: 58 | wr = csv.writer(f, quoting=csv.QUOTE_ALL) 59 | wr.writerow(data) 60 | 61 | # Gets last few lines from logfile as list 62 | def getLogData(self, index,lines): 63 | data=[] 64 | with open(self.logtables[index]["filepath"]) as f: 65 | for line in deque(f, lines): 66 | data.append(list(csv.reader([line]))[0]) 67 | return data 68 | 69 | 70 | # Refresh and redraw GUI 71 | def update(self): 72 | # Positioning variables 73 | self.nextX = 0 74 | self.nextY = 1 75 | self.lastY = 0 76 | self.maxReadY = 0 77 | 78 | # Reset and redaw 79 | self.s.clear() 80 | self.s.refresh() 81 | height, width = self.s.getmaxyx() # get the window size 82 | 83 | # Header 84 | self.s.addstr(0, 0, " " * width, curses.color_pair(2)) 85 | self.s.addstr(0, 0, "Capacitor Reforming GP-IB Automation", curses.color_pair(2)) 86 | 87 | # Create all sub windows 88 | wins={} 89 | # Create readouts 90 | for index , readout in enumerate(self.readouts): 91 | # Check for a group and use that as the index 92 | group=False 93 | if readout["group"] == "": 94 | winid=index 95 | else: 96 | winid=readout["group"] 97 | group=True 98 | 99 | # curses.newwin(height, width, begin_y, begin_x) 100 | # Create new window to use for display 101 | if winid not in wins: 102 | wins[winid]={"win":curses.newwin(3, self.readWid, 1+self.nextY, 1+self.nextX),"values":1} 103 | self.nextX+=self.readWid 104 | 105 | # Update position for next "line" 106 | if self.maxReadY < 3: 107 | self.maxReadY = 3 108 | 109 | # Check if at the end of the window and drop to next "line" 110 | if self.nextX+self.readWid +2 > width: 111 | self.nextX = 0 112 | self.nextY += self.maxReadY 113 | self.maxReadY = 0 114 | # Adding to existing group window 115 | else: 116 | # Update stored values for "line" positioning 117 | wins[winid]["values"]+=1 118 | 119 | if self.maxReadY < 2+wins[winid]["values"]: 120 | self.maxReadY = 2+wins[winid]["values"] 121 | 122 | # Clear bottom border 123 | wins[winid]["win"].addstr(0+wins[winid]["values"],0," " * (self.readWid-1)) 124 | # Resize window before border is redrawn 125 | wins[winid]["win"].resize(2+wins[winid]["values"],self.readWid) 126 | 127 | 128 | # Draw border before text to add title 129 | wins[winid]["win"].border(0) 130 | if group: 131 | # Group as title 132 | wins[winid]["win"].addstr(0,2,readout["group"]) 133 | # Label and value in window 134 | wins[winid]["win"].addstr(wins[winid]["values"],1,readout["label"]+" : " +readout["value"]+readout["unit"]) 135 | else: 136 | # Label as title 137 | wins[winid]["win"].addstr(0,2,readout["label"]) 138 | # Value in window 139 | wins[winid]["win"].addstr(wins[winid]["values"],1,readout["value"]+readout["unit"]) 140 | 141 | # Reset position for next window group 142 | self.nextX = 0 143 | self.nextY += self.maxReadY+1 144 | 145 | # Determine height of dynamic tables 146 | tablespace=(height-self.nextY)/len(self.logtables) 147 | # Create all tables 148 | for index , logtable in enumerate(self.logtables): 149 | # Check iff table uses dynamic or static height 150 | if logtable["lines"] is None: 151 | lines = int(tablespace) 152 | else: 153 | lines = logtable["lines"] 154 | 155 | # Use log-# for window index 156 | i="log-"+str(index) 157 | # Create new window 158 | wins[i]={"win":curses.newwin(lines, width-2, self.nextY, 1+self.nextX),"values":1} 159 | # Draw border and use filename as title 160 | wins[i]["win"].border(0) 161 | wins[i]["win"].addstr(0,2,logtable["filepath"]) 162 | # Update position 163 | self.nextY += lines 164 | 165 | # Begin position tracking in table 166 | tx=1 167 | ty=1 168 | 169 | # Print all headers 170 | for hindex , header in enumerate(logtable["headers"]): 171 | wins[i]["win"].addstr(ty,tx,header,curses.color_pair(2)) 172 | wins[i]["win"].addstr(ty,tx+len(header),"|") 173 | tx+=len(header)+1 174 | 175 | # Update position 176 | tx=1 177 | ty=2 178 | 179 | # Get data from file and add to table 180 | for lindex , log in enumerate(self.getLogData(index,lines-3)): 181 | for dindex , data in enumerate(log): 182 | # Print data and limit to length of header for fixed with 183 | wins[i]["win"].addstr(ty,tx,str(data)[:len(self.logtables[index]["headers"][dindex])]) 184 | # Add deliniator 185 | wins[i]["win"].addstr(ty,tx+len(self.logtables[index]["headers"][dindex]),"|") 186 | # Update position for next data point 187 | tx+=len(self.logtables[index]["headers"][dindex])+1 188 | 189 | # Update position for next line 190 | ty+=1 191 | tx=1 192 | 193 | # Draw all windows to buffer 194 | for index in wins: 195 | wins[index]["win"].refresh() 196 | 197 | # Draw to screen 198 | self.s.refresh() 199 | 200 | 201 | def end(self): 202 | curses.endwin() # restore the terminal settings back to the original 203 | 204 | -------------------------------------------------------------------------------- /psu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import Gpib 4 | import sys 5 | import time 6 | 7 | class PSU6633A: 8 | 9 | def __init__(self,gpib_address,gpib_interface=0): 10 | self.device = Gpib.Gpib(gpib_interface,gpib_address) 11 | 12 | def getOutput(self,value): 13 | self.device.write("OUT?") 14 | return float(self.device.read(100)) 15 | 16 | def setOutput(self,value): 17 | self.device.write("OUT "+str(value)) 18 | 19 | def setVolt(self, value): 20 | self.device.write("VSET "+str(value)) 21 | 22 | def setCurrent(self, value): 23 | self.device.write("ISET "+str(value)) 24 | 25 | def getVolt(self): 26 | self.device.write("VOUT?") 27 | return float(self.device.read(100)) 28 | 29 | def getCurrent(self): 30 | self.device.write("IOUT?") 31 | return float(self.device.read(100)) 32 | 33 | 34 | -------------------------------------------------------------------------------- /read-amps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import Gpib 3 | 4 | ## General GP-IB Setup ## 5 | interface_id = 0 # Interface number of the GP-IB 6 | device_address = 12 # Address of the test insturment 7 | 8 | device = Gpib.Gpib(interface_id,device_address) 9 | 10 | # write a command 11 | #device.write("*IDN?") # Standard SCPI identification command 12 | # read data back and print 13 | #print(device.read(100)) # Read up to 100 bytes 14 | 15 | 16 | ## PSU Example ## 17 | #volts = 13.37 18 | #current = 0.512 19 | # Set voltage and current from python variable 20 | device.write("IOUT?") 21 | print(float(device.read(100))) 22 | #device.write("ISET "+str(current)) 23 | 24 | ## DMM Example ## 25 | # set DC voltage for PSU range 26 | #device.write("CONF:VOLT:DC 50,0.0005") 27 | 28 | # read measurement back converted from scientific notation 29 | #device.write("READ?") 30 | #print(float(device.read(100))) 31 | -------------------------------------------------------------------------------- /reform-v2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys, os, fnmatch, shutil 4 | import signal 5 | import time 6 | import random 7 | 8 | from dmm import DMM34401A 9 | from psu import PSU6633A 10 | from gui import GUI 11 | 12 | gui = GUI() 13 | psu = PSU6633A(12) 14 | dmm = DMM34401A(7) 15 | 16 | psu.setCurrent(0.01) 17 | psu.setVolt(1) 18 | 19 | dmm.setCurrent(0.01,0.000001) 20 | 21 | # Handle Ctrl+C closes of program 22 | def signal_handler(sig, frame): 23 | gui.end() 24 | # Add PSU output disable 25 | psu.setCurrent(0) 26 | psu.setVolt(0) 27 | print('You pressed Ctrl+C!') 28 | sys.exit(0) 29 | 30 | signal.signal(signal.SIGINT, signal_handler) 31 | 32 | 33 | def getPSUVolts(): 34 | return psu.getVolt() 35 | 36 | def getPSUCurrent(): 37 | return psu.getCurrent() 38 | 39 | def getDMMCurrent(): 40 | i=dmm.read()*1000000 41 | if i > 0: 42 | return i 43 | else: 44 | return 0 45 | 46 | 47 | d={} 48 | d["psu"]={} 49 | d["dmm"]={} 50 | d["r_limit"]={} 51 | d["cap"]={} 52 | d["calc"]={} 53 | 54 | d["psu"]["v"]=0 55 | d["psu"]["i"]=0 56 | d["dmm"]["i"]=0 57 | d["calc"]["v"] = 0 58 | d["calc"]["v_max"] = 16 59 | d["r_limit"]["r"]=220 60 | d["cap"]["i_max"]=2000 # Calculate next jump to 2mA 61 | d["cap"]["i_min"]=150 # Calculate next jump to 2mA 62 | psu_v=12 63 | psu_i=2 64 | dmm_i=240 65 | 66 | d["psu"]["v_id"]=gui.addReadout(psu_v,"Voltage","V","PSU") 67 | d["psu"]["i_id"]=gui.addReadout(psu_i,"Current","mA","PSU") 68 | 69 | 70 | d["calc"]["v_id"]=gui.addReadout(dmm_i,"Target Voltage","V","Controls") 71 | d["calc"]["v_max_id"]=gui.addReadout(dmm_i,"Max Voltage","V","Controls") 72 | d["cap"]["i_max_id"]=gui.addReadout(dmm_i,"Max Current","uA","Controls") 73 | d["cap"]["i_min_id"]=gui.addReadout(dmm_i,"Min Current","uA","Controls") 74 | 75 | d["dmm"]["v_id"]=gui.addReadout(dmm_i,"Current","uA","DMM") 76 | 77 | d["cap"]["v_id"]=gui.addReadout(dmm_i,"Voltage","V","Capacitor") 78 | d["cap"]["r_id"]=gui.addReadout(dmm_i,"Resistance","Ohms","Capacitor") 79 | 80 | 81 | t = time.localtime() 82 | log_i=gui.addLogTable(["PSU Voltage","PSU Current","Target Voltage","DMM Current","Cap Voltage", "Cap Resistance"],time.strftime('%Y-%m-%d_%H-%M-%S', t)+"-CompuProDisk1-c3-001.csv") 83 | 84 | settle=10 85 | while(d["psu"]["v"] < d["calc"]["v_max"]): 86 | 87 | # Get new instrument readings 88 | d["psu"]["v"]=getPSUVolts() 89 | d["psu"]["i"]=getPSUCurrent() 90 | d["dmm"]["i"]=getDMMCurrent() 91 | 92 | d["r_limit"]["v_drop"]=(d["dmm"]["i"]/1000000)*d["r_limit"]["r"] 93 | d["cap"]["v"]=d["psu"]["v"]-d["r_limit"]["v_drop"] 94 | 95 | try: 96 | d["cap"]["r"]=d["cap"]["v"]/(d["dmm"]["i"]/1000000) 97 | except ZeroDivisionError: 98 | d["cap"]["r"]=0 99 | 100 | # Check for min current and calc voltage jump 101 | if d["dmm"]["i"] < d["cap"]["i_min"]: 102 | d["calc"]["v"] = d["cap"]["v"] + ((d["cap"]["i_max"]/1000000) * d["r_limit"]["r"]) 103 | psu.setVolt(d["calc"]["v"]) 104 | 105 | 106 | gui.update() 107 | time.sleep(0.1) 108 | dmm_i+=5 109 | gui.updateReadout(d["psu"]["v_id"],d["psu"]["v"]) 110 | gui.updateReadout(d["psu"]["i_id"],d["psu"]["i"]) 111 | gui.updateReadout(d["calc"]["v_id"],d["calc"]["v"]) 112 | gui.updateReadout(d["calc"]["v_max_id"],d["calc"]["v_max"]) 113 | gui.updateReadout(d["cap"]["i_max_id"],d["cap"]["i_max"]) 114 | gui.updateReadout(d["cap"]["i_min_id"],d["cap"]["i_min"]) 115 | gui.updateReadout(d["calc"]["v_id"],d["calc"]["v"]) 116 | gui.updateReadout(d["dmm"]["v_id"],d["dmm"]["i"]) 117 | gui.updateReadout(d["cap"]["v_id"],d["cap"]["v"]) 118 | gui.updateReadout(d["cap"]["r_id"],str(d["cap"]["r"])[:6]) 119 | gui.updateLogTable(log_i,[d["psu"]["v"],d["psu"]["i"],d["calc"]["v"],d["dmm"]["i"],d["cap"]["v"],d["cap"]["r"]]) 120 | 121 | # Runaway current protection 122 | if d["dmm"]["i"] > 2*d["cap"]["i_max"]: 123 | if settle: 124 | settle-=1 125 | else: 126 | break 127 | 128 | 129 | 130 | psu.setCurrent(0) 131 | psu.setVolt(0) 132 | dmm.beep() 133 | 134 | -------------------------------------------------------------------------------- /reform.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys 4 | import Gpib 5 | 6 | cap_v_max=45 7 | cap_i_max=0.003 8 | cap_i_min=0.000045 9 | r_limit=218 # 220 value measured at 218 10 | step_low=0.01 11 | 12 | psu_i_now=0.0 13 | psu_v_now=0.0 14 | cap_v=0 15 | cap_i_nowc=0.0 16 | cap_rpsu=0 # calculated 17 | cap_rcalc=0 # calculated 18 | 19 | 20 | ## General GP-IB Setup ## 21 | interface_id = 0 # Interface number of the GP-IB 22 | dmm_address = 7 # Address of the test insturment 23 | psu_address = 12 # Address of the test insturment 24 | 25 | dmm = Gpib.Gpib(interface_id,dmm_address) 26 | psu = Gpib.Gpib(interface_id,psu_address) 27 | 28 | 29 | # Base Setup 30 | dmm.write("CONF:VOLT:DC 50,0.0005") 31 | psu.write("VSET 0") # Will be adjusted as program runs 32 | psu.write("ISET 0.01") # Fixed max value for protection 33 | 34 | print("cap_v \t cap_i_nowc \t cap_rpsu \t cap_rcalc \t psu_v_now \t psu_i_now") 35 | 36 | while (cap_v < cap_v_max): 37 | # Get current readings 38 | dmm.write("READ?") 39 | cap_v = float(dmm.read(100)) 40 | 41 | psu.write("IOUT?") 42 | psu_i_now = float(psu.read(100)) 43 | psu.write("VOUT?") 44 | psu_v_now = float(psu.read(100)) 45 | cap_i_nowc = (psu_v_now - cap_v)/r_limit # preferred value 46 | 47 | cap_rpsu = cap_v / psu_i_now 48 | cap_rcalc = cap_v / cap_i_nowc 49 | 50 | 51 | # get target voltage 52 | target_v = cap_v + (cap_i_max*r_limit) 53 | 54 | # Check if target is higher than max 55 | #if (target_volts > volt_max): 56 | # print ("Max Voltage Reached") 57 | # break 58 | d="\t" 59 | # set psu voltage 60 | print(str(cap_v)+d+ str(cap_i_nowc)+d+ str(cap_rpsu)+d+ str(cap_rcalc)+d+ str(psu_v_now)+d+ str(psu_i_now)) 61 | 62 | if(cap_i_nowc < cap_i_min): 63 | psu.write("VSET "+str(target_v)) # Will be adjusted as program runs 64 | 65 | dmm.write("SYST:BEEP") 66 | psu.write("VSET 0") # Will be adjusted as program runs 67 | psu.write("ISET 0") # Fixed max value for protection 68 | -------------------------------------------------------------------------------- /set-amps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys 4 | import Gpib 5 | 6 | ## General GP-IB Setup ## 7 | interface_id = 0 # Interface number of the GP-IB 8 | device_address = 12 # Address of the test insturment 9 | 10 | device = Gpib.Gpib(interface_id,device_address) 11 | 12 | # write a command 13 | #device.write("*IDN?") # Standard SCPI identification command 14 | # read data back and print 15 | #print(device.read(100)) # Read up to 100 bytes 16 | 17 | 18 | ## PSU Example ## 19 | #volts = 13.37 20 | current = 0.01 21 | # Set voltage and current from python variable 22 | #device.write("IOUT?") 23 | #print(float(device.read(100))) 24 | device.write("ISET "+str(sys.argv[1])) 25 | 26 | ## DMM Example ## 27 | # set DC voltage for PSU range 28 | #device.write("CONF:VOLT:DC 50,0.0005") 29 | 30 | # read measurement back converted from scientific notation 31 | #device.write("READ?") 32 | #print(float(device.read(100))) 33 | -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./read-amps.py > volts.txt && scp volts.txt 192.168.12.216:/media/akbkuku/A068/ 4 | -------------------------------------------------------------------------------- /volts.txt: -------------------------------------------------------------------------------- 1 | 0.0028 2 | --------------------------------------------------------------------------------