1 """The z_piezo module controls the one-dimensional displacement of a
2 piezoelectric actuator, while allowing simultaneous data aquisition on
3 two channels. The module is called z_piezo, because for force spectroscopy,
4 the axis that needs simultaneous aquisition is the Z axis, although this
5 module could be used to provide control in any "fast" axis.
7 There are a few globals controlling the behavior of the entire module.
9 USE_ABCD_DEFLECTION (default False)
10 selects between using a preprocessed vertical deflection signal
11 and using the 'raw' 4 photodiode output segments.
12 TEXT_VERBOSE (default False)
13 print text messages to stderr displaying actions taken
14 PYLAB_INTERACTIVE_VERBOSE (default False)
15 display pylab graphs of any ramps taken
16 BASE_FIG_NUM (default 50)
17 the figure number for z_piezo pylab figures will be this number + {0,1,..}
18 LOG_RAMPS (default False)
19 save the input and output of any ramps to the LOG_DIR directory
20 LOG_DIR = '/home/wking/rsrch/data/z_piezo'
21 where the ramp logs are saved to
22 DEFAULT_ZP_CHAN (default 0)
23 output channel controlling the z piezo
24 DEFAULT_ZP_MON_CHAN (default 1)
25 input channel monitoring the z piezo signal
26 DEFAULT_DEF_CHAN (default 0, or in ABCD mode (2,3,4,5))
27 input channel(s) monitoring deflection (or some other feedback signal)
30 USE_ABCD_DEFLECTION = False
32 PYLAB_INTERACTIVE_VERBOSE = False
35 LOG_DIR = '/home/wking/rsrch/data/z_piezo'
37 # Hackish defaults for the calibcant.config module
38 DEFAULT_GAIN = 20 # Vpiezo / Voutput
39 DEFAULT_SENSITIVITY = 7.41 # nm_surface/Volt_piezo
40 DEFAULT_ZERO_PHOTODIODE_BITS = 2**15
41 DEFAULT_VPHOTO_IN_2_VOLTS = lambda vbits : (vbits-DEFAULT_ZERO_PHOTODIODE_BITS)/3276.8
42 DEFAULT_VZP_OUT_2_VOLTS = lambda vbits : (vbits-DEFAULT_ZERO_PHOTODIODE_BITS)/3276.8
45 DEFAULT_ZP_MON_CHAN = 1
47 if USE_ABCD_DEFLECTION :
48 DEFAULT_DEF_CHAN = (2,3,4,5)
49 # The 4 photodiode segments are
52 # looking in along the laser
54 class NoComedi (ImportError):
55 "Missing comedi. Don't initialize a z_piezo instance."
59 import pycomedi.single_aio as single_aio
60 import pycomedi.simult_aio as simult_aio
62 except ImportError, e:
65 from numpy import array, arange, ones, zeros, linspace, uint16, float, sin, pi
66 from scipy.stats import linregress
69 if PYLAB_INTERACTIVE_VERBOSE :
70 from pylab import figure, plot, title, legend, hold, subplot
71 import time # for timestamping lines on plots
73 class error (Exception) :
75 class outOfRange (error) :
81 def __init__(self, zp_chan=DEFAULT_ZP_CHAN,
82 zp_mon_chan=DEFAULT_ZP_MON_CHAN,
83 def_chan=DEFAULT_DEF_CHAN) :
84 if HAS_COMEDI is not True:
86 self.sensitivity = DEFAULT_SENSITIVITY
87 self.gain = DEFAULT_GAIN
88 self.nm2Vout = self.sensitivity * self.gain # nm_surface / V_output
89 self.chan_info = _chan_info(zp_chan, zp_mon_chan, def_chan)
91 self.curIn = array([0]*self.chan_info.numIn, dtype=uint16)
92 self.curOut = array([0]*self.chan_info.numOut, dtype=uint16)
93 self._jump = _z_piezo_jump(self.chan_info)
94 self._ramp = _z_piezo_ramp(self.chan_info)
95 self.zpMax = int(self.pos_nm2out(1000)) # limits at 1 micron
96 self.zpMin = int(self.pos_nm2out(-1000)) # and -1 micron
97 # define default value changed callbacks
98 self.setExternalZPiezo = None
99 self.setExternalZPiezoMon = None
100 self.setExternalDeflection = None
104 return int(self.curOut[self.chan_info.zp_ind]) # cast as int so arithmetic is signed
105 def curPosMon(self) :
106 return int(self.curIn[self.chan_info.zp_mon_ind])
108 if USE_ABCD_DEFLECTION :
109 A = int(self.curIn[self.chan_info.def_ind[0]])
110 B = int(self.curIn[self.chan_info.def_ind[1]])
111 C = int(self.curIn[self.chan_info.def_ind[2]])
112 D = int(self.curIn[self.chan_info.def_ind[3]])
113 df = float((A+B)-(C+D))/(A+B+C+D)
114 dfout = int(df * 2**15) + 2**15
116 print "Current deflection %d (%d, %d, %d, %d)" \
117 % (dfout, A, B, C, D)
119 else : # reading the deflection voltage directly
120 return int(self.curIn[self.chan_info.def_ind])
121 def pos_out2V(self, out) :
122 return self._jump.out_to_phys(out)
123 def pos_V2out(self, V) :
124 # Vout is Volts output by the DAQ card
125 # and Vpiezo is Volts applied across the piezo
126 return self._jump.phys_to_out(V)
127 def pos_nm2out(self, nm) :
128 # Vout is Volts output by the DAQ card
129 # and Vpiezo is Volts applied across the piezo
130 return self.pos_V2out(nm / self.nm2Vout)
131 def pos_out2nm(self, out) :
132 return self.pos_out2V(out) * self.nm2Vout
133 def posMon_nm2out(self, nm) :
134 return self._jump.phys_to_out(nm / self.nm2Vout)
135 def posMon_out2nm(self, out) :
136 return self._jump.out_to_phys(out) * self.nm2Vout
137 def def_in2V(self, input) :
138 return self._jump.out_to_phys(input)
139 def def_V2in(self, physical) :
140 print physical, type(physical) # temporary debugging printout
141 return self._jump.phys_to_out(physical)
143 return {'Z piezo output': self.curPos(),
144 'Deflection input':self.curDef(),
145 'Z piezo input':self.curPosMon()}
146 def pCurVals(self, curVals=None) :
148 curVals = self.curVals()
149 print "Z piezo output : %6d" % curVals["Z piezo output"]
150 print "Z piezo input : %6d" % curVals["Z piezo input"]
151 print "Deflection input: %6d" % curVals["Deflection input"]
152 def updateInputs(self) :
153 self.curIn = self._jump.updateInputs()
154 if self.setExternalZPiezoMon != None :
155 self.setExternalZPiezoMon(self.posMon_out2nm(self.curPosMon()))
156 if self.setExternalDeflection != None :
157 self.setExternalDeflection(self.def_in2V(self.curDef()))
158 def jumpToPos(self, pos) :
159 self._check_range(pos)
160 self.curOut[self.chan_info.zp_ind] = pos
161 self.curIn = self._jump.jumpToPos(self.curOut)
162 if self.setExternalZPiezo != None :
163 self.setExternalZPiezo(self.pos_out2nm(self.curPos()))
167 return self.curVals()
168 def ramp(self, out, freq) :
170 self._check_range(pos)
171 out = self._ramp.ramp(out, freq)
172 self.curOut[self.chan_info.zp_ind] = out["Z piezo output"][-1]
173 self.curIn[self.chan_info.zp_mon_ind] = out["Z piezo input"][-1]
174 if USE_ABCD_DEFLECTION :
175 for i in range(4) : # i is the photodiode element (0->A, 1->B, ...)
176 self.curIn[i] = out["Deflection segment"][i][-1]
178 self.curIn[self.chan_info.def_ind] = out["Deflection input"][-1]
180 if self.setExternalZPiezo != None :
181 self.setExternalZPiezo(self.pos_out2nm(self.curPos()))
182 if self.setExternalZPiezoMon != None :
183 self.setExternalZPiezoMon(self.posMon_out2nm(self.curPosMon()))
184 if self.setExternalDeflection != None :
185 self.setExternalDeflection(self.def_in2V(self.curDef()))
187 log = data_logger.data_log(LOG_DIR, noclobber_logsubdir=False,
189 if USE_ABCD_DEFLECTION :
190 v = out.pop("Deflection segment")
191 log.write_dict_of_arrays(out)
192 out["Deflection segment"] = v
194 log.write_dict_of_arrays(out)
196 print "Ramped from (%g, %g) to (%g, %g) in %d points" \
197 % (out["Z piezo output"][0], out["Deflection input"][0],
198 out["Z piezo output"][-1], out["Deflection input"][-1],
199 len(out["Z piezo output"]))
200 if PYLAB_INTERACTIVE_VERBOSE :
203 title('ramp (def_in vs zp_out)')
204 timestamp = time.strftime('%H%M%S')
205 plot(out["Z piezo output"], out["Deflection input"], '.', label=timestamp)
207 title('zp_in vs zp_out')
208 plot(out["Z piezo output"], out["Z piezo input"], '.', label=timestamp)
211 def _check_range(self, pos) :
212 if pos > self.zpMax :
213 raise outOfRange, "Z piezo pos = %d > %d = max" % (pos, self.zpMax)
214 if pos < self.zpMin :
215 raise outOfRange, "Z piezo pos = %d < %d = min" % (pos, self.zpMin)
219 class _z_piezo_jump :
220 def __init__(self, chan_info) :
221 self.chan_info = chan_info
222 self.reserved = False
223 self.AI = single_aio.AI(chan=self.chan_info.inChan)
225 self.AO = single_aio.AO(chan=self.chan_info.outChan)
234 self.reserved = False
235 def updateInputs(self) :
236 if self.reserved == False :
239 self.reserved = False
241 if self.reserved == False :
245 def jumpToPos(self, out) :
246 if self.reserved == False :
248 self.reserved = False # set to true inside reserve(), which we don't want
250 if self.reserved == False :
252 return self.updateInputs()
253 def out_to_phys(self, output) :
254 return self.AO.comedi_to_phys(0, output)
255 def phys_to_out(self, physical) :
256 return self.AO.phys_to_comedi(0, physical)
258 class _z_piezo_ramp :
259 def __init__(self, chan_info) :
260 self.chan_info = chan_info
264 def ramp(self, outArray, freq) :
265 # allocate and run the task
266 npoints = int(len(outArray)/self.chan_info.numOut)
267 in_data = array([0]*npoints*self.chan_info.numIn, dtype=uint16)
268 if type(outArray) != type(in_data) or outArray.dtype != uint16 :
269 out_data = array([0]*npoints*self.chan_info.numOut, dtype=uint16)
270 for i in range(npoints) :
271 out_data[i] = outArray[i]
274 if simult_aio.DONT_OUTPUT_LAST_SAMPLE_HACK == True:
275 # duplicate the last output point
277 out_hack = array([0]*npoints*self.chan_info.numOut, dtype=uint16)
278 for i in range(npoints-1) :
279 out_hack[i] = out_data[i]
280 out_hack[-1] = out_data[-1]
282 in_data = array([0]*npoints*self.chan_info.numIn, dtype=uint16)
286 while not correlated :
287 AIO = simult_aio.AIO(in_chan=self.chan_info.inChan,
288 out_chan=self.chan_info.outChan)
291 AIO.setup(freq=freq, out_buffer=out_data)
297 AIO.start_read(in_data)
304 ramp["Z piezo output"] = out_data[self.chan_info.zp_ind::self.chan_info.numOut]
305 ramp["Z piezo input"] = in_data[self.chan_info.zp_mon_ind::self.chan_info.numIn]
306 ramp["Deflection input"] = in_data[self.chan_info.def_ind::self.chan_info.numIn]
308 gradient, intercept, r_value, p_value, std_err = linregress(ramp["Z piezo output"],
309 ramp["Z piezo input"])
310 rnge = ramp["Z piezo output"].max()-ramp["Z piezo output"].min()
311 if gradient < .8 and rnge > 100 :
312 if PYLAB_INTERACTIVE_VERBOSE :
313 figure(BASE_FIG_NUM+3)
315 title('ramp (def_in vs zp_out)')
316 timestamp = time.strftime('%H%M%S')
317 plot(ramp["Z piezo output"], ramp["Deflection input"], '.', label=timestamp)
319 title('zp_in vs zp_out')
320 plot(ramp["Z piezo output"], ramp["Z piezo input"], '.', label=timestamp)
322 print "ramp failed slope (%g, %g), try again" % (gradient, rnge)
325 if simult_aio.DONT_OUTPUT_LAST_SAMPLE_HACK == True:
326 # remove the duplicated last output point
327 out_data = out_data[:-self.chan_info.numOut]
328 in_data = in_data[:-self.chan_info.numIn]
329 ramp["Z piezo output"] = out_data[self.chan_info.zp_ind::self.chan_info.numOut]
330 ramp["Z piezo input"] = in_data[self.chan_info.zp_mon_ind::self.chan_info.numIn]
331 ramp["Deflection input"] = in_data[self.chan_info.def_ind::self.chan_info.numIn]
333 if USE_ABCD_DEFLECTION :
334 ramp["Deflection segment"] = []
335 for c_ind in self.chan_info.def_ind :
336 ramp["Deflection segment"].append(in_data[c_ind::self.chan_info.numIn])
337 A = ramp["Deflection segment"][0].astype(float)
338 B = ramp["Deflection segment"][1].astype(float)
339 C = ramp["Deflection segment"][2].astype(float)
340 D = ramp["Deflection segment"][3].astype(float)
341 ramp["Deflection input"] = (((A+B)-(C+D))/(A+B+C+D) * 2**15).astype(uint16)
342 ramp["Deflection input"] += 2**15 # HACK, comedi uses unsigned ints...
344 ramp["Deflection input"] = in_data[self.chan_info.def_ind::self.chan_info.numIn]
346 if PYLAB_INTERACTIVE_VERBOSE :
347 figure(BASE_FIG_NUM+1)
348 timestamp = time.strftime('%H%M%S')
349 plot(ramp["Z piezo output"], ramp["Z piezo input"], '.', label=timestamp)
354 if PYLAB_INTERACTIVE_VERBOSE :
355 figure(BASE_FIG_NUM+2)
356 timestamp = time.strftime('%H%M%S')
357 plot(ramp["Z piezo output"], ramp["Z piezo input"], '.', label=timestamp)
361 #AIO = simAIO.simAIOobj(npoints,
363 # self.chan_info.inChan,
364 # self.chan_info.outChan)
365 #err = AIO.configure()
367 #err = AIO.write(outArray)
369 #(err, inArray) = AIO.read()
371 # raise error, "Error in simAIO"
372 #if len(inArray)*self.chan_info.numOut != len(outArray)*self.chan_info.numIn :
373 # raise error, "Input array of wrong length"
376 #ramp["Z piezo output"] = outArray[self.chan_info.zp_ind::self.chan_info.numOut]
377 #ramp["Z piezo input"] = inArray[self.chan_info.zp_mon_ind::self.chan_info.numIn]
378 #ramp["Deflection input"] = inArray[self.chan_info.def_ind::self.chan_info.numIn]
380 # task automatically freed as AIO goes out of scope
384 def __init__(self, zp_chan, zp_mon_chan, def_chan) :
386 if USE_ABCD_DEFLECTION :
387 self.def_ind = [] # index in inChan array
389 for chan in def_chan :
390 self.inChan.append(chan)
391 self.def_ind.append(i)
394 self.inChan.append(def_chan)
395 self.def_ind = 0 # index in inChan array
396 self.inChan.append(zp_mon_chan)
397 self.zp_mon_ind = zp_mon_chan
398 self.numIn = len(self.inChan)
400 self.outChan = [zp_chan]
401 self.zp_ind = 0 # index in outChan array
402 self.numOut = len(self.outChan)
406 print "test z_piezo()"
409 zp.jumpToPos(zp.pos_nm2out(0))
413 curPosNm = zp.pos_out2nm(curPos)
414 curPos2 = zp.pos_nm2out(curPosNm)
415 if curPos != curPos2 :
416 raise error, "Conversion %d -> %g -> %d not round trip" % (curPos, curPosNm, curPos2)
417 ramp_positions = linspace(zp.curPos(), zp.pos_nm2out(100), 20)
418 out = zp.ramp(ramp_positions, 10000)
419 ramp_positions = linspace(zp.curPos(), zp.pos_nm2out(0), 20)
420 out2 = zp.ramp(ramp_positions, 10000)
422 if __name__ == "__main__" :