1
+ --[[Last Updated: $Date: 2020 /03 /06 $ $Change: 189427 $
2
+
3
+ Copyright © Keithley Instruments, LLC. All rights reserved.
4
+
5
+ Part of the Keithley Instruments Potentiostat System.
6
+ Users are permitted to modify but not distribute the software without prior written permission from Keithley.
7
+
8
+ THIS SOFTWARE IS PROVIDED “AS-IS,” WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES OF ANY KIND, INCLUDING
9
+ BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
10
+ NON-INFRINGEMENT OF INTELLECTUAL PROPERTY. IN NO EVENT SHALL KEITHLEY INSTRUMENTS, ITS AFFILIATES,
11
+ OFFICERS, EMPLOYEES, DIRECTORS, AGENTS, SUPPLIERS, OR OTHER THIRD PARTIES BE LIABLE FOR ANY DIRECT,
12
+ INDIRECT, INCIDENTAL, PUNITIVE, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT
13
+ OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
+
15
+ This program measures the open circuit potential of a battery over time.
16
+
17
+ All recordings must be 4 -wire measurements.
18
+
19
+ Using this test script requires the Model 2450 , 2460 , or 2461 to have firmware 1.5 .0 or greater.
20
+ The latest firmware can be found on the Keithley website, tek.com/keithley.
21
+
22
+ ]]
23
+
24
+ --[General variables]--
25
+ changelist = "$Change: 189427 $"
26
+ local MIN_EC_FRAMEWORK_VERSION = "1.5"
27
+ stats = {}
28
+ local eventNums = {2203 , 2728 , 5076 , 5084 }
29
+ SAVED_SETUP_BASE_FILENAME = "KI_" ..localnode.model .."-EC_OP_Setup"
30
+ SCRIPT_TITLE = "Open Circuit Potential"
31
+ X_UNIT = "s"
32
+ Y_UNIT = "V"
33
+
34
+ local measperiod = {min = 0.75 , val = 1 , max = 100 } --s/pt
35
+ local numsamples = {min = 1 , val = 1 , max = 100000 }
36
+
37
+ ---------------------------------------------------------------------------------------------------------------------------------
38
+ --All these functions are related to the USB drive and saving data to that drive
39
+
40
+ --Save data points with their relative timestamps from the buffer to a file that the user names
41
+ function saveData()
42
+ local stringData = { }
43
+
44
+ table.insert(stringData, "\n Sampling Period," ..data.meas_interval..",s/pt" )
45
+ table.insert(stringData, "Potential Range," ..V_RANGES.range[data.v_range]..",V" )
46
+ table.insert(stringData, "\n Quick Stats" )
47
+ table.insert(stringData, stats.numPts..",points recorded,," ..stats.avg..",V average" )
48
+ table.insert(stringData, stats.min..",V minimum,," ..stats.stdev..",V standard deviation" )
49
+ table.insert(stringData, stats.median..",V median,," ..stats.time..",seconds to run experiment" )
50
+ table.insert(stringData, stats.max..",V maximum" )
51
+ table.insert(stringData, "\n Raw Data" )
52
+ table.insert(stringData, "Time(s),Current(A),Potential(V)" )
53
+ for i = 1 , defbuffer1.n do
54
+ table.insert(stringData, defbuffer1.relativetimestamps[i].."," ..defbuffer1.sourcevalues[i].."," ..defbuffer1.readings[i])
55
+ end
56
+
57
+ save_data_to_usb_with_prompt(stringData)
58
+ end
59
+
60
+ --------------------------------------------------------------------------------------------------------------------------
61
+ function basicSettings()
62
+ --Setup the instrument source and measure settings for Eoc measurement
63
+ smu.source.func = smu.FUNC_DC_CURRENT
64
+ smu.measure.func = smu.FUNC_DC_VOLTAGE
65
+ smu.source.offmode = smu.OFFMODE_HIGHZ
66
+ smu.measure.sense = smu.SENSE_4WIRE
67
+ smu.source.range = 1e-6
68
+ smu.source.level = 0
69
+ smu.source.autodelay = smu.ON
70
+ smu.measure.range = V_RANGES.range[data.v_range]
71
+ smu.source.vlimit.level = V_RANGES.range[data.v_range]
72
+ smu.measure.nplc = 10
73
+ smu.measure.autozero.enable = smu.ON
74
+ end
75
+ ------------------------------------------------------------------------------------------------------------------------------
76
+ --This function measures the open circuit potential, providing the user with information about the battery potential directly from the Main Menu.
77
+ function runTest()
78
+ defbuffer1.clear()
79
+ defbuffer1.capacity = 100000
80
+
81
+ display.graph.removeall(ui.home.graph)
82
+ display.graph.add(ui.home.graph, defbuffer1, display.ELEMENT_DATA, defbuffer1, display.ELEMENT_TIME)
83
+ display.graph.drawstyle(ui.home.graph, display.STYLE_BOTH)
84
+ display.graph.scalex(ui.home.graph, display.XSCALE_SMART)
85
+ display.graph.scaley(ui.home.graph, display.YSCALE_SMART)
86
+
87
+ --[[Initialize the trigger timer that creates events which cause the instrument to take a measurement.
88
+ The max amount of events that will be created is 100 ,000 , which matches the max capacity of the buffer]]
89
+ trigger.timer[1 ].reset()
90
+ trigger.timer[1 ].start.generate = trigger.ON
91
+ trigger.timer[1 ].delaylist = {data.meas_interval, data.meas_interval}
92
+ trigger.timer[1 ].count = 100000
93
+
94
+ --Initialize the trigger timer that creates events which cause the animation to change.
95
+ trigger.timer[2 ].reset()
96
+ trigger.timer[2 ].start.generate = trigger.OFF
97
+ trigger.timer[2 ].delaylist = {0.5 , 0.5 } --s
98
+ trigger.timer[2 ].count = 500000
99
+
100
+ --[Experiment]--
101
+ trigger.model .setblock (1 , trigger .BLOCK_BUFFER_CLEAR )
102
+ trigger.model.setblock (2 , trigger .BLOCK_SOURCE_OUTPUT , smu .ON )
103
+ trigger.model.setblock (3 , trigger .BLOCK_WAIT , trigger .EVENT_TIMER1 )
104
+ trigger.model.setblock (4 , trigger .BLOCK_MEASURE , defbuffer1 )
105
+ trigger.model.setblock (5 , trigger .BLOCK_BRANCH_LIMIT_CONSTANT , trigger .LIMIT_ABOVE , 0 , V_RANGES .range [data .v_range ], 7 , 4 )
106
+ trigger.model.setblock (6 , trigger .BLOCK_BRANCH_COUNTER , data .num_samples , 3 )
107
+ trigger.model.setblock (7 , trigger .BLOCK_SOURCE_OUTPUT , smu .OFF ) --Done , turn the source output off.
108
+
109
+ display.setstate(ui.home.start_stop, display.STATE_ENABLE)
110
+ display.waitevent(0.001 ) --Consume any previous events
111
+
112
+ --start the trigger model and timers
113
+ trigger .timer [1 ].enable = trigger .ON
114
+ timer.cleartime ()
115
+ trigger.model.initiate ()
116
+
117
+ while trigger.model.state () == trigger .STATE_RUNNING or trigger.model.state () == trigger .STATE_WAITING do
118
+ update_progress_bar (defbuffer1 .n / data .num_samples * 100 )
119
+ delay (0.5 )
120
+ if display.waitevent (0.001 ) == ui .home .start_stop then
121
+ trigger.model.abort ()
122
+ stats .time = timer.gettime ()
123
+ test_cleanup ()
124
+ return
125
+ end
126
+ end
127
+
128
+ waitcomplete ()
129
+ --Determine how long the test lasted
130
+ stats .time = timer.gettime ()
131
+ if defbuffer1 .n < data .num_samples then
132
+ display.input.prompt(display.BUTTONS_OK, "Experiment terminated because compliance was reached.")
133
+ end
134
+ calculateStats()
135
+ update_stats (stats_table, stats )
136
+ end
137
+
138
+ -----------------------------------------------------------------------------------------------------------------
139
+ --The following functions are related to statistics of the results
140
+
141
+ --This function calculates the number of points, the minimum potential, the maximum potential, the median potential, the average potential, and the standard deviation of the potential
142
+ function calculateStats()
143
+ local prompt
144
+ if defbuffer1.n >= 10000 then prompt = display.prompt (display .BUTTONS_NONE , "Calculating Statistics..." ) end
145
+ bufferCopy = {}
146
+ local bufStats = buffer.getstats(defbuffer1)
147
+ if bufStats.n > 0 then
148
+
149
+ --copy the buffer
150
+ for i=1 , defbuffer1.n do
151
+ bufferCopy[i] = defbuffer1.readings[i]
152
+ end
153
+
154
+ --number of data points
155
+ stats.numPts = defbuffer1.n
156
+
157
+ --find average
158
+ stats.avg = bufStats.mean
159
+
160
+ --find standard deviation
161
+ stats.stdev = bufStats.stddev or 0
162
+
163
+ table.sort(bufferCopy)
164
+ --find minimum
165
+ stats.min = bufStats.min.reading
166
+ --find maximum
167
+ stats.max = bufStats.max.reading
168
+ --find median
169
+ if math.mod(table.getn(bufferCopy), 2 ) == 1 then
170
+ stats.median = bufferCopy[math.ceil(table.getn(bufferCopy)/2 )]
171
+ elseif table.getn(bufferCopy) > 0 then
172
+ stats.median = (bufferCopy[table.getn(bufferCopy)/2 ] + bufferCopy[table.getn(bufferCopy)/2 + 1 ])/2
173
+ else
174
+ stats.median = 0
175
+ end
176
+
177
+ if prompt ~= nil then display.delete(prompt) end
178
+ else
179
+ stats = { }
180
+ end
181
+ end
182
+
183
+ function measure_event_handler(eventID, value)
184
+ sync_ui_to_data(ui_data_event_table, eventID)
185
+ end
186
+
187
+ function start_stop_event_handler(eventID)
188
+ test_start()
189
+ basicSettings()
190
+ runTest()
191
+ test_cleanup()
192
+ end
193
+
194
+ local function create_screens()
195
+ ui.measure = { }
196
+ ui.measure.root = display.create(display.ROOT, display.OBJ_SCREEN, "Measure Settings" )
197
+
198
+ end
199
+
200
+ local function create_controls()
201
+ --Setup ui.home controls
202
+ display.settext(ui.home.version, "version " ..get_version_number(changelist))
203
+ display.settext(ui.home.root, SCRIPT_TITLE)
204
+ display.setevent(ui.home.start_stop, display.EVENT_PRESS, "start_stop_event_handler(%id)" )
205
+ hide_home_stats() --We're not using stats here , so hide them
206
+ display.setevent(ui.home.save, display.EVENT_PRESS, "saveData()" )
207
+
208
+ --Create ui.menu controls
209
+ local offset = 0 -- button offset
210
+ if display.EVENT_ENDAPP then offset = 1 end
211
+ --ui.menu.root = display.create(display.ROOT, display.OBJ_SCREEN_MENU, "Settings" , "Views" , "Save/Load" , "System" )
212
+ display.create(ui.menu.root, display.OBJ_BUTTON_MENU, 0 + offset, 0 + offset, ui.measure.root, "Measure Settings" , "meas_settings" )
213
+
214
+ display.create(ui.menu.root, display.OBJ_BUTTON_MENU, 1 + offset, 0 + offset, ui.home.root, "Run/Graph" , "graph" )
215
+ display.create(ui.menu.root, display.OBJ_BUTTON_MENU, 1 + offset, 1 + offset, display.SCREEN_READING_TABLE, "Reading Table" )
216
+ display.create(ui.menu.root, display.OBJ_BUTTON_MENU, 1 + offset, 2 + offset, ui.stats.root, "Statistics" , "stats" )
217
+
218
+ display.create(ui.menu.root, display.OBJ_BUTTON_MENU, 2 + offset, 0 + offset, ui.save.root, "Save Settings" , "save" )
219
+ display.create(ui.menu.root, display.OBJ_BUTTON_MENU, 2 + offset, 1 + offset, ui.load.root, "Load Settings" , "load" )
220
+
221
+ display.create(ui.menu.root, display.OBJ_BUTTON_MENU, 3 + offset, 0 + offset, display.SCREEN_SYS_EVENT_LOG, "Event Log" )
222
+
223
+ --Create ui.measure controls
224
+ ui.measure.potential_range = create_edit_option(ui.measure.root, 200 , option_height*0 + 20 , "Potential Range" , V_RANGES.value[1 ].."..." ..V_RANGES.value[table.getn(V_RANGES.value)], V_RANGES)
225
+ table.insert(ui_data_event_table, {ui = "ui.measure.potential_range" , data = "data.v_range" , event = "measure_event_handler" })
226
+ display.setvalue(ui.measure.potential_range, get_index_from_range(V_RANGES, 2 )) --Set default range to 2V
227
+ measure_event_handler(ui.measure.potential_range, 2 ) --Set default range to 2V
228
+ display.setevent(ui.measure.potential_range, display.EVENT_PRESS, "measure_event_handler(%id, %value)" )
229
+
230
+ ui.measure.num_samples = display.create(ui.measure.root, display.OBJ_EDIT_NUMBER, 600 , option_height*0 + 20 , "Sample Count" , numsamples.min.." to " ..numsamples.max, display.NFORMAT_INTEGER, numsamples.val, numsamples.min, numsamples.max)
231
+ display.setevent(ui.measure.num_samples, display.EVENT_PRESS, "measure_event_handler(%id, %value)" )
232
+ table.insert(ui_data_event_table, {ui = "ui.measure.num_samples" , data = "data.num_samples" , event = "measure_event_handler" })
233
+
234
+ ui.measure.meas_interval = display.create(ui.measure.root, display.OBJ_EDIT_NUMBER, 200 , option_height*1 + 20 , "Sample Interval (s/pt)" , display.format(measperiod.min, "s" , display.NFORMAT_USER, 4 ).." to " ..display.format(measperiod.max, "s" , display.NFORMAT_USER, 4 ), display.NFORMAT_USER, measperiod.val, measperiod.min, measperiod.max, "s" )
235
+ display.setevent(ui.measure.meas_interval, display.EVENT_PRESS, "measure_event_handler(%id, %value)" )
236
+ table.insert(ui_data_event_table, {ui = "ui.measure.meas_interval" , data = "data.meas_interval" , event = "measure_event_handler" })
237
+
238
+ sync_ui_to_data(ui_data_event_table)
239
+
240
+ --Create ui.stats controls
241
+ tbl_settings = { }
242
+ tbl_settings.screen_id = ui.stats.root
243
+ tbl_settings.rows = {"-Time" , "Points" , "-Minimum" , "Maximum" , "Median" , "Average" , "Stddev-" }
244
+ tbl_settings.cols = {"-" , "-" }
245
+ tbl_settings.table = {x = 200 -14 *2 , y = 75 }
246
+ tbl_settings.cell = { width = 200 }
247
+ tbl_settings.font = display.FONT_MEDIUM
248
+
249
+ --Create ui.stats controls
250
+ stats_table = tbl.make(tbl_settings)
251
+
252
+ sync_ui_to_data(ui_data_event_table)
253
+ end
254
+
255
+ local function create_global_tables()
256
+ --NOTE: Use find and replace when changing *.value strings to avoid breaking lookups
257
+
258
+
259
+ end
260
+
261
+ --[[------------------------------------------------------------------------------------------------------------------------------
262
+ =============================
263
+ --Program is executed here
264
+ =============================
265
+ ]]
266
+
267
+ local function main()
268
+ if not EC_FRAMEWORK_VERSION then
269
+ if EC_Framework then
270
+ EC_Framework.run()
271
+ elseif file.usbdriveexists() == 1 then
272
+ EC_Framework = script.load("/usb1/EC_Framework.tsp" )
273
+ if EC_Framework then
274
+ EC_Framework.run()
275
+ end
276
+ end
277
+
278
+ if not EC_Framework then
279
+ print("Unable to load EC_Framework.tsp!" )
280
+ display.input.prompt(display.BUTTONS_OK, "Unable to load EC_Framework.tsp!" )
281
+ exit()
282
+ end
283
+ end
284
+
285
+ if EC_FRAMEWORK_VERSION < MIN_EC_FRAMEWORK_VERSION then
286
+ print("EC_Framework version " ..MIN_EC_FRAMEWORK_VERSION.." or greater required, please update." )
287
+ display.input.prompt(display.BUTTONS_OK, "EC_Framework version " ..MIN_EC_FRAMEWORK_VERSION.." or greater required, please update." )
288
+ exit()
289
+ end
290
+
291
+ reset()
292
+ for i = 1 ,table.getn(eventNums) do eventlog.suppress(eventNums[i]) end
293
+ smu.source.offmode = smu.OFFMODE_HIGHZ
294
+ smu.measure.sense = smu.SENSE_4WIRE
295
+
296
+ init_toplevel()
297
+ data.vertexPotential = { }
298
+ create_global_tables()
299
+
300
+ create_top_screens()
301
+ create_screens()
302
+ create_loading_screen()
303
+
304
+ create_top_controls()
305
+ create_controls()
306
+
307
+ destroy_loading_screen()
308
+ end
309
+ main()
0 commit comments