#!/usr/bin/python
+#
+# calibcant - tools for thermally calibrating AFM cantilevers
+#
+# Copyright (C) 2007,2008, William Trevor King
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA.
+#
+# The author may be contacted at <wking@drexel.edu> on the Internet, or
+# write to Trevor King, Drexel University, Physics Dept., 3141 Chestnut St.,
+# Philadelphia PA 19104, USA.
"""
Aquire and analyze cantilever calibration data.
import z_piezo_utils
from splittable_kwargs import splittableKwargsFunction, \
make_splittable_kwargs_function
+import FFT_tools
import common
import config
# bump family
@splittableKwargsFunction()
-def bump_aquire(zpiezo, push_depth=200, npoints=1024, freq=100e3) :
+def bump_aquire(zpiezo, push_depth=200, npoints=1024, push_speed=1000) :
"""
Ramps closer push_depth and returns to the original position.
Inputs:
zpiezo an opened zpiezo.zpiezo instance
push_depth distance to approach, in nm
npoints number points during the approach and during the retreat
- freq rate at which data is aquired
+ push_speed piezo speed during approach and retreat, in nm/s
Returns the aquired ramp data dictionary, with data in DAC/ADC bits.
"""
# generate the bump output
+ nm_per_step = float(push_depth) / npoints
+ freq = push_speed / nm_per_step # freq is sample frequency in Hz
start_pos = zpiezo.curPos()
pos_dist = zpiezo.pos_nm2out(push_depth) - zpiezo.pos_nm2out(0)
close_pos = start_pos + pos_dist
- appr = linspace(start_pos, close_pos, npoints)
- retr = linspace(close_pos, start_pos, npoints)
- out = concatenate((appr, retr))
+ appr = numpy.linspace(start_pos, close_pos, npoints)
+ retr = numpy.linspace(close_pos, start_pos, npoints)
+ out = numpy.concatenate((appr, retr))
# run the bump, and measure deflection
if config.TEXT_VERBOSE :
- print "Bump %g nm" % push_depth
+ print "Bump %g nm at %g nm/s" % (push_depth, push_speed)
data = zpiezo.ramp(out, freq)
return data
T._splitargs(T, kwargs)
T_raw = T_aquire(**T_aquire_kwargs)
T_analyze.T_save(T_raw, **T_save_kwargs)
- T_ret = T_analyze.T_analyze(T_raw, **T_analyze_kwargs)
- return T_ret
+ T_ret = T_analyze.T_analyze(T_raw, **T_analyze_kwargs) # returns array
+ return T_ret[0]
# vib family
Record data for TIME seconds at FREQ Hz from ZPIEZO at it's current position.
"""
# round up to the nearest power of two, for efficient FFT-ing
- nsamps = ceil_pow_of_two(time*freq)
+ nsamps = FFT_tools.ceil_pow_of_two(time*freq)
time = nsamps / freq
# take some data, keeping the position voltage at it's current value
- out = ones((nsamps,), dtype=uint16) * zpiezo.curPos()
+ out = numpy.ones((nsamps,), dtype=numpy.uint16) * zpiezo.curPos()
if config.TEXT_VERBOSE :
print "get %g seconds of data" % time
data = zpiezo.ramp(out, freq)
- data['sample frequency Hz'] = freq
+ data['sample frequency Hz'] = numpy.array([freq])
return data
@splittableKwargsFunction(vib_aquire,
"""
vib_aquire_kwargs,vib_save_kwargs,vib_analyze_kwargs = \
vib._splitargs(vib, kwargs)
- data = vib_aquire(freq=freq, **vib_aquire_kwargs)
+ data = vib_aquire(**vib_aquire_kwargs)
vib_analyze.vib_save(data, **vib_save_kwargs)
freq = data['sample frequency Hz']
- Vphoto_var = vib_analyze.vib_analyze(deflection_bits=data, freq=freq,
- **vib_analyze_kwargs)
+ deflection_bits = data['Deflection input']
+ Vphoto_var = vib_analyze.vib_analyze(deflection_bits=deflection_bits,
+ freq=freq, **vib_analyze_kwargs)
return Vphoto_var
# A few positioning functions, so we can run bump_aquire() and vib_aquire()
# with proper spacing relative to the surface.
@splittableKwargsFunction()
-def move_just_onto_surface(stepper, zpiezo, Depth_nm=100, setpoint=2) :
+def move_just_onto_surface(stepper, zpiezo, Depth_nm=-50, setpoint=2) :
"""
- Uses z_piezo_utils.getSurfPos() to pinpoint the position of the surface.
- Adjusts the stepper position as required to get within stepper_tol nm
- of the surface.
- Then set Vzp to place the cantilever Depth_nm onto the surface.
-
+ Uses z_piezo_utils.getSurfPos() to pinpoint the position of the
+ surface. Adjusts the stepper position as required to get within
+ stepper_tol nm of the surface. Then set Vzp to place the
+ cantilever Depth_nm onto the surface. Negative Depth_nm values
+ will place the cantilever that many nm _off_ the surface.
+
If getSurfPos() fails to find the surface, backs off (for safety)
and steps in (without moving the zpiezo) until Vphoto > setpoint.
"""
print "get surf pos, with setpoint %g (%d)" % (setpoint, zpiezo.def_V2in(setpoint))
for i in range(20) : # HACK, keep stepping back until we get a distance
try :
- dist = zpiezo.pos_out2nm(getSurfPos(zpiezo, zpiezo.def_V2in(setpoint)))
- except (tooClose, poorFit), string :
+ dist = zpiezo.pos_out2nm( \
+ z_piezo_utils.getSurfPos(zpiezo,zpiezo.def_V2in(setpoint)))
+ except (z_piezo_utils.tooClose, z_piezo_utils.poorFit), string :
stepper.step_rel(-200)
stepper.step_rel(198)
continue
while dist < -stepper_tol : # step back if we need to
stepper.step_rel(-200)
stepper.step_rel(198)
- dist = zpiezo.pos_out2nm(getSurfPos(zpiezo, zpiezo.def_V2in(setpoint)))
+ dist = zpiezo.pos_out2nm( \
+ z_piezo_utils.getSurfPos(zpiezo, zpiezo.def_V2in(setpoint)))
if config.TEXT_VERBOSE :
print 'distance to surface ', dist, ' nm, step back'
while dist > stepper_tol : # and step forward if we need to
stepper.step_rel(2)
- dist = zpiezo.pos_out2nm(getSurfPos(zpiezo, zpiezo.def_V2in(setpoint)))
+ dist = zpiezo.pos_out2nm( \
+ z_piezo_utils.getSurfPos(zpiezo, zpiezo.def_V2in(setpoint)))
if config.TEXT_VERBOSE :
print 'distance to surface ', dist, ' nm, step closer'
# now adjust the zpiezo to place us just onto the surface
# and finally, the calib family
@splittableKwargsFunction((move_just_onto_surface, 'stepper', 'zpiezo'),
- (bump, 'zpiezo', 'freq', 'log_dir', 'Vphoto_in2V'),
+ (bump, 'zpiezo', 'log_dir', 'Vphoto_in2V'),
(move_far_from_surface, 'stepper'),
(T, 'log_dir'),
(vib, 'zpiezo', 'log_dir', 'Vphoto_in2V'),
(analyze.calib_save, 'bumps','Ts','vibs','log_dir'))
def calib_aquire(stepper, zpiezo, num_bumps=10, num_Ts=10, num_vibs=20,
- bump_freq=100e3,
- log_dir=config.LOG_DATA, Vphoto_in2V=config.Vphoto_in2V,
+ log_dir=config.LOG_DIR, Vphoto_in2V=config.Vphoto_in2V,
**kwargs):
"""
Aquire data for calibrating a cantilever in one function.
Inputs :
stepper a stepper.stepper_obj for coarse Z positioning
zpiezo a z_piezo.z_piezo for fine positioning and deflection readin
- setpoint maximum allowed deflection (in Volts) during approaches
- num_bumps number of 'a's (see Outputs)
- push_depth_nm depth of each push when generating a
- num_temps number of 'T's (see Outputs)
+ num_bumps number of 'bumps' (see Outputs)
+ num_temps number of 'Ts' (see Outputs)
num_vibs number of 'vib's (see Outputs)
log_dir directory to log data to. Default 'None' disables logging.
+ Vphoto_in2V function to convert photodiode input bits to Volts
+
+ + other kwargs. Run calib_aquire._kwargs(calib_aquire) to see
+ all options. Run calib_aquire._childSplittables to see a list
+ of kwarg functions that this function calls.
+
Outputs (all are arrays of recorded data) :
bumps measured (V_photodiode / nm_tip) proportionality constant
Ts measured temperature (K)
calib_aquire._splitargs(calib_aquire, kwargs)
# get bumps
move_just_onto_surface(stepper, zpiezo, **move_just_onto_surface_kwargs)
- bumps=zeros((num_bumps,))
+ bumps = numpy.zeros((num_bumps,), dtype=numpy.float)
for i in range(num_bumps) :
- bumps[i] = bump(zpiezo, freq=bump_freq, log_dir=log_dir,
- Vphot_in2V=Vphoto_in2V, **bump_kwargs)
+ bumps[i] = bump(zpiezo=zpiezo, log_dir=log_dir,
+ Vphoto_in2V=Vphoto_in2V, **bump_kwargs)
if config.TEXT_VERBOSE :
print bumps
move_far_from_surface(stepper, **move_far_from_surface_kwargs)
# get Ts
- Ts=zeros((num_Ts,), dtype=float)
+ Ts = numpy.zeros((num_Ts,), dtype=numpy.float)
for i in range(num_Ts) :
Ts[i] = T(**T_kwargs)
time.sleep(1) # wait a bit to get an independent temperature measure
print Ts
# get vibs
- vibs=zeros((num_vibs,))
+ vibs = numpy.zeros((num_vibs,), dtype=numpy.float)
for i in range(num_vibs) :
- vibs[i] = vib(zpiezo, log_dir=log_dir, Vphoto_in2V=Vphoto_in2V,
+ vibs[i] = vib(zpiezo=zpiezo, log_dir=log_dir, Vphoto_in2V=Vphoto_in2V,
**vib_kwargs)
print vibs
@splittableKwargsFunction( \
(calib_aquire, 'log_dir'),
(analyze.calib_analyze, 'bumps','Ts','vibs'))
-def calib(log_dir=None, **kwargs) :
+def calib(log_dir=config.LOG_DIR, **kwargs) :
"""
Calibrate a cantilever in one function.
The I-don't-care-about-the-details black box version :p.
calib._splitargs(calib, kwargs)
a, T, vib = calib_aquire(**calib_aquire_kwargs)
k,k_s,ps2_m, ps2_s,T_m,T_s,one_o_Vp2_m,one_o_Vp2_s = \
- analyze.calib_analyze(a, T, vib, log_dir=log_dir,
- **calib_analyze_kwargs)
+ analyze.calib_analyze(a, T, vib, **calib_analyze_kwargs)
analyze.calib_save_analysis(k, k_s, ps2_m, ps2_s, T_m, T_s,
one_o_Vp2_m, one_o_Vp2_s, log_dir)
return (k, k_s)