5 # @purpose AVR Butterfly Datalogger control for Total Current Spectroscopy Measurements
6 # The AVR responds to a limited number of RS232 commands to read ADC channels and set the DAC
7 # This script can be used to control the DAC and monitor initial energy & sample current over RS232
16 import Gnuplot, Gnuplot.funcutils
19 gnuplot = Gnuplot.Gnuplot()
21 # TODO: Insert variables for calibration purposes here.
23 "ADC_Counts" : [2**10,2**10,2**10,2**10,2**10,2**10,2**10], # Maxed counts on ADC channels (10 bits = 2**10 = 1024)
24 "DAC_Counts" : 2**12, # Maxed counts on DAC channel (12 bits = 2**12 = 4096)
26 # Calibration data for DAC and ADC
28 "ADC" : [None, None, None, None, None, None, None, None],
30 # Data used for rough calibration if above data is not present
31 "Vref" : 3.3, # The output of the voltage regulator (Vref = Vcc) relative to signal ground
32 "ADC_Rin" : [None, None, None, None, 15000, 1100, 1100, 1100],
33 "ADC_Rvar" : [None, None, None, None, 1000, 5000, 10000, 50000],
37 # TODO: Adjust aqcuisition parameters here
38 aquire = { "DAC_Sweep" : "0.0 + 10.0*int(step)", # DAC Sweep value (t is in STEPS, not seconds!)
40 #"ADC_Vi" : 5, # ADC channel to read back Vi (set by DAC) through
41 #"ADC_Is" : 4, # ADC channel to read back Is through
42 #"ADC_Ie" : 4, # ADC channel to read back Ie through
43 "DAC_Settle" : 0.0, # Time in seconds to wait for DAC to stabilise
44 #"response_wait" : 0.2, # Time to wait in seconds between sending data and reading back
49 #Setup the serial connection parameters
51 port="/dev/ttyUSB0", # Modify as needed (note: in linux need to run `sudo chmod a+rw /dev/ttyUSBX' to set permissions)
53 # Do not change the values below here (unless AVR butterfly is reprogrammed to use different values)
62 #Using an ordered dictionary, so results will be determined (or prompted for) in this order.
63 # Put things that are being changed a lot near the top of the list.
64 parameters = odict.odict([
65 ("Chamber Pressure" , None), # Chamber pressure now automatically determined
66 ("Deflection Voltage" , None),
70 ("Venault Voltage" , None),
71 ("Accelerating Voltage" , None),
72 ("Focus Voltage" , None),
74 ("Initial Voltage" , None),
75 ("Heating Current" , None),
76 ("Heating Voltage (across filament)" , None),
77 ("Heating Voltage (across power supply)", None),
78 #("610B Zero" , None),
80 #("610B Scale" , None),
82 ("602 0.1 Battery" , None),
83 ("602 0.03 Battery" , None),
84 ("602 0.01 Battery" , None),
85 ("602 0.003 Battery" , None),
86 ("602 0.001 Battery" , None),
88 ("Sample Angle", None),
89 ("ADC Regulator" , None),
91 ("Parameters last checked", None)
95 return str(datetime.datetime.now()).split(" ")[1].split(".")[0].replace(":","")
98 return str(datetime.datetime.now()).split(" ")[0]
103 p = subprocess.Popen("./pressure/get_pressure.sh", stdout=subprocess.PIPE)
104 #p = subprocess.Popen("./this_program_does_not_exist.sh", stdout=subprocess.PIPE)
106 result = float(p.stdout.readline().strip(" \r\n\t"))
112 # Used for when I press Control-C to stop things
113 def set_exit_handler(func):
117 win32api.SetConsoleCtrlHandler(func, True)
119 version = ".".join(map(str, sys.version_info[:2]))
120 raise Exception("pywin32 not installed for Python " + version)
123 signal.signal(signal.SIGTERM, func)
124 signal.signal(signal.SIGINT, func)
126 def killed_handler(signal, frame):
128 sys.stdout.write("\n# Reason for killing program? ")
129 reason = sys.stdin.readline().strip("\r\n ")
130 for out in aquire["open_files"]:
131 sys.stdout.write("# Closing file " + str(out) + "\n")
132 out.write("# Recieved KILL signal.\n# Reason: " + str(reason) + "\n")
138 for out in aquire["open_files"]:
139 out.write("# Program exits.\n")
143 result = open(a, b,0)
145 result.write("# File opened at " + str(datetime.datetime.now()) + "\n")
146 aquire["open_files"].append(result)
149 def log_close(afile):
150 if (afile in aquire["open_files"]):
151 afile.write("# File closed at " + str(datetime.datetime.now()) + "\n")
152 aquire["open_files"].remove(afile)
159 #atexit.register(cleanup)
160 set_exit_handler(killed_handler)
162 aquire["start_date"] = getDate()
169 # print("Waiting for \"# hello\" from device...")
170 # while (ser.readline().strip("\r\n") != "# hello"):
172 #while (ser.readline().strip("\r\n ") != "#"):
176 ser.write("a "+str(aquire["ADC_Averages"]) + "\r\n")
177 ser.readline().strip("\r\n")
178 ser.readline().strip("\r\n")
179 ser.readline().strip("\r\n")
180 #print(ser.readline().strip("\r\n"))
181 #print(ser.readline().strip("\r\n"))
182 #print(ser.readline().strip("\r\n"))
184 #print("Writing config information to config.dat...")
185 #output = log_open("config.dat", "w", 1)
187 #output.write("# Initialise " + str(datetime.datetime.now()) + "\n")
189 #for field in calibrate:
190 # output.write("# calibrate["+str(field)+"] = "+str(calibrate[field]) + "\n")
192 #for field in aquire:
193 # output.write("# aquire["+str(field)+"] = "+str(aquire[field]) + "\n")
195 #output.write("# Ready " + str(datetime.datetime.now()) + "\n# EOF\n")
202 # I haven't ever used calibrated results, and yet this code is still here, why???
203 #if (loadCalibration_DAC() == False):
204 # if (calibrateDAC() == False):
206 #if (loadCalibration_ADC(aquire["ADC_Is"]) == False):
207 # if (calibrateADC_usingDAC(aquire["ADC_Is"], False) == False):
208 # if (calibrateADC(aquire["ADC_Is"]) == False):
211 #if (loadCalibration_ADC(aquire["ADC_Vi"]) == False):
212 # if (calibrateADC_usingDAC(aquire["ADC_Vi"], True) == False):
213 # if (calibrateADC(aquire["ADC_Vi"]) == False):
217 # Make directory for today, backup calibration files
218 os.system("mkdir -p " + getDate())
219 #os.system("cp *.dat " + getDate() +"/")
228 # TODO: Modify data to record here
230 #for i in range(0,1):
232 os.system("mkdir -p " + getDate())
233 record_data([5], getDate()+"/"+str(getTime())+".dat", None, 4001)
237 #os.system("echo \"Sweep number " + str(sweep) + " completed\" | festival --tts")
244 os.system("echo \"Experiment complete\" | festival --tts")
251 input_file = log_open(getDate()+"/checklist", "r")
255 if (input_file != None):
256 for line in input_file:
260 item = k[0].strip("# \r\n")
261 value = k[1].strip("# \r\n")
263 if (item in parameters):
264 if item == "Chamber Pressure":
265 parameters[item] = getPressure()
267 parameters[item] = value
269 #print("Checklist found. Overwrite? [Y/n]")
270 response = "" #sys.stdin.readline().strip(" \r\n")
271 if (response == "" or response == "y" or response == "Y"):
272 input_file = log_open(getDate()+"/checklist.old", "w")
273 for item in parameters:
274 input_file.write("# " + str(item) + " = " + str(parameters[item]) + "\n")
275 input_file.write("\n")
276 log_close(input_file)
279 if (input_file == None):
280 for item in parameters:
281 if item == "Parameters last checked":
283 if item == "Chamber Pressure":
284 #sys.stdout.write("\""+str(item)+"\" = " + str(parameters[item]) + " - get new pressure... ")
285 parameters[item] = getPressure()
286 #sys.stdout.write(str(parameters[item]) + "\n")
289 sys.stdout.write("\""+str(item)+"\" = " + str(parameters[item]) + " New value?: ")
290 response = sys.stdin.readline().strip("\r\n ")
291 if (response == "!"):
294 parameters[item] = response
295 sys.stdout.write("\n")
296 parameters["Parameters last checked"] = str(datetime.datetime.now())
299 checklist = log_open(getDate()+"/checklist", "w")
300 for item in parameters:
301 checklist.write("# "+str(item) + " = " + str(parameters[item]) + "\n")
302 #output_file.write("# "+str(item) + " = " + str(parameters[item]) + "\n")
306 def record_data(ADC_channels, output, pollTime = None, dac_max = None):
309 gnuplot("set title \""+str(output)+"\"")
310 output = [log_open(output, "w"), sys.stdout]
313 gnuplot("set title \"<No file>\"")
314 output = [sys.stdout]
318 out.write("# aquire["+str(field)+"] = "+str(aquire[field]) + "\n")
321 out.write("# Parameters:\n")
323 parameters["Chamber Pressure"] = getPressure() # Update chamber pressure
325 for field in parameters:
327 out.write("# "+str(field)+" = " + str(parameters[field]) + "\n")
330 start_time = time.time()
332 gnuplot("set xlabel \"DAC (counts)\"")
333 gnuplot("set ylabel \"ADC (counts)\"")
338 out.write("# Experiment " + str(datetime.datetime.now()) + "\n")
339 out.write("# Polling for " + str(pollTime) + "s.\n")
341 out.write("# Data:\n")
342 out.write("# time\tDAC")
343 for channel in ADC_channels:
344 out.write("\tADC"+str(channel))
348 data = [] # Keep track of data for dynamic plotting
349 dacValue = int(eval(aquire["DAC_Sweep"]))
350 if (setDAC(dacValue) == False):
353 while (pollTime == None or time.time() < start_time + pollTime):
354 if (aquire["DAC_Sweep"] != None):
355 nextDacValue = int(eval(aquire["DAC_Sweep"]))
356 if (nextDacValue != dacValue):
357 dacValue = nextDacValue
364 if (dac_max != None and dacValue >= dac_max):
367 measure_start = time.time()
371 for channel in ADC_channels:
372 read = readADC(channel)
375 print("# Abort data collection due to failed ADC read")
376 if out != sys.stdout:
379 raw_adc.append((channel, read[0], read[1]))
381 end_time = time.time()
384 measure_time = measure_start + (end_time - measure_start)/2.0 - start_time
385 out.write(str(measure_time))
386 out.write("\t"+str(dacValue))
387 data.append([measure_time, dacValue])
390 out.write("\t" + str(adc[1]) + "\t" + str(adc[2]))
391 data[len(data)-1].append(adc[1])
392 data[len(data)-1].append(adc[2])
395 #gnuplot("set yrange [0:1023]")
396 #gnuplot("set xrange [0:4000]")
397 gnuplot("set xlabel \"DAC (counts)\"")
398 gnuplot("set ylabel \"Sample Current (ADC counts)\"")
399 gnuplot.plot(Gnuplot.Data(data, title="t = "+str(measure_time), with_="lp", using="2:3"))
401 if out != sys.stdout:
405 def loadCalibration_ADC(channel):
407 input_file = log_open("calibrateADC"+str(channel)+".dat")
409 print("Couldn't find calibration file for ADC " + str(channel))
412 calibrate["ADC"][channel] = []
414 l = l.split("#")[0].strip("\r\n ")
418 split_line = l.split("\t")
419 calibrate["ADC"][channel].append((float(split_line[0]), float(split_line[1])))
420 log_close(input_file)
422 if (len(calibrate["ADC"][channel]) <= 0):
423 print("Empty calibration file for ADC " + str(channel))
427 def loadCalibration_DAC():
429 input_file = log_open("calibrateDAC.dat")
431 print("Couldn't find calibration file for DAC")
434 calibrate["DAC"] = []
436 #print("Line is: "+str(l))
437 l = l.split("#")[0].strip("\r\n ")
441 split_line = l.split("\t")
442 if (len(split_line) >= 3):
443 calibrate["DAC"].append((int(split_line[0]), float(split_line[1]), float(split_line[2])))
445 calibrate["DAC"].append((int(split_line[0]), float(split_line[1])))
448 log_close(input_file)
450 if (len(calibrate["DAC"]) <= 0):
451 print("Empty calibration file for DAC")
455 def getADC_Voltage(channel, counts):
456 if (calibrate["ADC"][channel] == None or len(calibrate["ADC"][channel]) <= 0):
457 if (calibrate["ADC_Rin"][channel] != None and calibrate["ADC_Rvar"][channel] != None):
458 print("Warning: Using rough calibration for ADC"+str(channel) + " = " + str(counts))
459 ratio = float(calibrate["ADC_Rin"][channel]) / (float(calibrate["ADC_Rin"][channel]) + float(calibrate["ADC_Rvar"][channel]))
460 return ratio * (float(counts) / float(calibrate["ADC_Counts"][channel]) * Vref)
462 print("Error: No calibration for ADC"+str(channel))
465 c = calibrate["ADC"][channel]
467 for i in range(0, len(c)-1):
468 if (c[i][0] <= counts and i + 1 < len(c)):
469 grad = (float(c[i+1][valueIndex]) - float(c[i][valueIndex])) / (float(c[i+1][0]) - float(c[i][0]))
470 value = float(c[i][valueIndex]) + grad * (float(counts) - float(c[i][0]))
474 print("Warning: Extrapolating outside calibration range for DAC = " + str(counts))
476 grad = (float(c[len(c)-1][valueIndex]) - float(c[len(c)-2][valueIndex])) / (float(c[len(c)-1][0]) - float(c[len(c)-2][0]))
477 value = float(c[len(c)-1][valueIndex]) + grad * (float(counts) - float(c[len(c)-1][0]))
479 def readADC(channel):
480 #for i in range(0, aquire["ADC_Averages"]):
481 # ser.write("r "+str(channel)+"\r") # Send command to datalogger
482 #time.sleep(aquire["response_wait"])
483 # response = ser.readline().strip("#\r\n ")
484 # if (response != "r "+str(channel)):
485 # print("Received wierd response reading ADC ("+response+")")
487 #time.sleep(aquire["response_wait"])
488 # values.append(int(ser.readline().strip("\r\n ")))
489 # adc_sum += float(values[len(values)-1])
490 ser.write("r "+str(channel)+"\r")
491 response = ser.readline().strip("#\r\n")
492 if (response != "r "+str(channel)):
493 print("Received wierd response reading ADC ("+response+")")
495 return ser.readline().strip("\r\n").split(" ")
497 def getDAC_Voltage(counts, gain = True):
498 if (calibrate["DAC"] == None or len(calibrate["DAC"]) <= 0):
499 if (calibrate["DAC_Gain"] != None):
500 print("Warning: Using rough calibration for DAC = " + str(counts))
501 return float(counts) / float(calibrate["DAC_Counts"]) * float(calibrate["Vref"]) * float(calibrate["DAC_Gain"])
503 print("Error: No calibrate for DAC")
509 if (len(calibrate["DAC"][0]) < 3):
510 print("Error: No data for unamplified DAC")
515 print("Warning: Only one point in calibration data!")
516 return float(c[0][valueIndex])
518 for i in range(0, len(c)-1):
519 if (c[i][0] <= counts and i + 1 < len(c)):
520 grad = (float(c[i+1][valueIndex]) - float(c[i][valueIndex])) / (float(c[i+1][0]) - float(c[i][0]))
521 value = float(c[i][valueIndex]) + grad * (float(counts) - float(c[i][0]))
525 print("Warning: Extrapolating outside calibration range for DAC = " + str(counts))
527 grad = (float(c[len(c)-1][valueIndex]) - float(c[len(c)-2][valueIndex])) / (float(c[len(c)-1][0]) - float(c[len(c)-2][0]))
528 value = float(c[len(c)-1][valueIndex]) + grad * (float(counts) - float(c[len(c)-1][0]))
534 ser.write("d "+str(level)+"\r")
536 response = ser.readline().strip("#\r\n ")
537 if (response != "d "+str(level)):
538 print("Recieved wierd response setting DAC to "+str(level) + " ("+response+")")
540 #time.sleep(aquire["response_wait"])
541 #time.sleep(aquire["DAC_Settle"])
542 #time.sleep(aquire["DAC_Settle"])
543 response = ser.readline().strip("#\r\n ")
544 if (response != "DAC "+str(level)):
545 print("Recieved wierd response setting DAC to "+str(level) + " ("+response+")")
547 #time.sleep(aquire["response_wait"])
548 time.sleep(aquire["DAC_Settle"])
551 def calibrateADC_usingDAC(channel, gain = True):
552 if (calibrate["DAC"] == None):
553 print("ERROR: DAC is not calibrated, can't use to calibrate ADC!")
556 calibrate["ADC"][channel] = []
557 outfile = log_open("calibrateADC"+str(channel)+".dat", "w")
558 outfile.write("# Calibrate ADC " + str(channel) + "\n")
559 outfile.write("# Start " + str(datetime.datetime.now()) + "\n")
561 print("Connect DAC output to ADC " + str(channel) + " and press enter\n")
564 for dac in calibrate["DAC"]:
565 if (setDAC(dac[0]) == False):
568 value = readADC(channel)
571 canadd = (len(calibrate["ADC"][channel]) <= 0)
572 if (canadd == False):
574 for adc in calibrate["ADC"][channel]:
575 if adc[0] == value[0]:
586 #input_value = getDAC_Voltage(dac[0], gain)
587 if (input_value == False):
590 outfile.write(str(value[0]) + "\t" + str(input_value) + "\t" + str(value[1]) + "\n")
591 print(str(value[0]) + "\t" + str(input_value) + "\t" + str(value[1]))
593 calibrate["ADC"][channel].append((value[0], input_value, value[1]))
595 outfile.write("# Stop " + str(datetime.datetime.now()) + "\n# EOF\n")
597 if (setDAC(0) == False):
599 if (len(calibrate["ADC"][channel]) <= 0):
600 print("Error: No calibration points taken for ADC " + str(channel))
604 def calibrateADC(channel):
605 calibrate["ADC"][channel] = []
606 outfile = log_open("calibrateADC"+str(channel)+".dat", "w")
607 outfile.write("# Calibrate ADC " + str(channel) + "\n")
608 outfile.write("# Start " + str(datetime.datetime.now()) + "\n")
610 print("Calibrating ADC...\n")
611 print("Enter measured voltage, empty line stops.\n")
614 read = sys.stdin.readline().strip("\r\n ")
617 input_value = float(read)
618 output_value = readADC(channel)
619 if (output_value == False):
622 calibrate["ADC"][channel].append((output_value[0], input_value))
623 print(str(output_value[0]) + "\t" + str(input_value))
625 calibrate["ADC"][channel].sort(lambda e : e[1], reverse = False)
630 outfile.write("# Stop " + str(datetime.datetime.now()) + "\n# EOF\n")
632 if (len(calibrate["ADC"][channel]) <= 0):
633 print("Error: No calibration points taken for ADC " + str(channel))
639 calibrate["DAC"] = []
641 outfile = log_open("calibrateDAC.dat", "w")
642 outfile.write("# Calibrate DAC\n")
643 outfile.write("# Start " + str(datetime.datetime.now()) + "\n")
645 print("Calibrating DAC...")
646 sys.stdout.write("Number of counts to increment per step: ")
647 read = sys.stdin.readline().strip("\r\n")
650 stepSize = max(1, int(read))
651 sys.stdout.write("\n")
653 outfile.write("# Increment by " + str(stepSize) + "\n")
654 print("Input measured DAC voltage for each level; empty input ends calibration.")
655 print("You may optionally input the DAC voltage before it is amplified too.")
658 while (level < calibrate["DAC_Counts"]):
662 sys.stdout.write(str(level) + " ?")
663 read = sys.stdin.readline().strip("\r\n ")
667 read = read.split(" ")
668 if (len(calibrate["DAC"]) > 0 and len(calibrate["DAC"][len(calibrate["DAC"])-1]) != len(read)):
669 print("Either give one value for EVERY point, or two values for EVERY point. Don't mess around.")
672 calibrate["DAC"].append((level, float(read[0]), float(read[1])))
673 outfile.write(str(level) + "\t" + str(float(read[0])) + "\t" + str(float(read[1])) + "\n")
675 calibrate["DAC"].append((level, float(read[0])))
676 outfile.write(str(level) + "\t" + str(float(read[0])) + "\n")
682 outfile.write("# Stop " + str(datetime.datetime.now()) + "\n# EOF\n")
684 if (len(calibrate["DAC"]) <= 0):
685 print("Error: No calibration points taken for DAC")
689 # Run the main function
690 if __name__ == "__main__":