1 # Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
3 # This file is part of pyafm.
5 # pyafm is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation, either version 3 of the License, or (at your
8 # option) any later version.
10 # pyafm is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with pyafm. If not, see <http://www.gnu.org/licenses/>.
18 """Tools for controlling atomic force microscopes.
20 Provides control of AFM postition using both short-range (piezo) and
21 long range (stepper) vertical positioning. There are separate modules
22 for controlling the piezo (`pypiezo`) and stepper (`stepper`), this
23 module only contains methods that require the capabilities of both.
26 from pypiezo.base import convert_bits_to_meters as _convert_bits_to_meters
27 from pypiezo.base import convert_meters_to_bits as _convert_meters_to_bits
28 from pypiezo.base import convert_volts_to_bits as _convert_volts_to_bits
29 from pypiezo.surface import FlatFit as _FlatFit
30 from pypiezo.surface import SurfaceError as _SurfaceError
32 from . import LOG as _LOG
36 """Atomic force microscope positioning.
38 Uses a short range `piezo` and a long range `stepper` to position
39 an AFM tip relative to the surface.
43 piezo | pypiezo.afm.AFMpiezo instance
44 Fine positioning and deflection measurements.
45 stepper | stepper.Stepper instance
47 temperature | temperature.Controller instance or None
48 Optional temperature monitoring and control.
50 def __init__(self, piezo, stepper, temperature=None, axis_name='z'):
52 self.stepper = stepper
53 self.temperature = temperature
54 self.axis_name = axis_name
56 def get_temperature(self):
57 """Measure the sample temperature.
59 Return the sample temperature in Kelvin or `None` if such a
60 measurement is not possible.
62 if hasattr(self.temperature, 'get_temperature'):
63 return self.temperature.get_temperature()
65 def move_just_onto_surface(self, depth=-50e-9, setpoint=2,
66 min_slope_ratio=10, far=200):
67 """Position the AFM tip close to the surface.
69 Uses `.piezo.get_surface_position()` to pinpoint the position
70 of the surface. Adjusts the stepper position as required via
71 `.stepper.step_relative()` to get within
72 `2*.stepper.step_size` meters of the surface. Then adjusts
73 the piezo to place the cantilever `depth` meters onto the
74 surface. Negative `depth`\s place the tip off the surface
76 If `.piezo.get_surface_position()` fails to find the surface,
77 backs off `far` half steps (for safety) and steps in (without
78 moving the zpiezo) until deflection voltage is greater than
81 _LOG.info('moving to %g onto the surface' % depth)
83 stepper_tolerance = 2*self.stepper.step_size
85 axis = self.piezo.axis_by_name(self.axis_name)
86 def_config = self.piezo.config.select_config('inputs', 'deflection')
88 zero = _convert_volts_to_bits(axis.config['channel'], 0)
89 target_def = _convert_volts_to_bits(def_config, setpoint)
90 self._check_target_deflection(deflection=target_def)
92 _LOG.debug('zero the %s piezo output' % self.axis_name)
93 self.piezo.jump(axis_name=self.axis_name, position=zero)
95 _LOG.debug("see if we're starting near the surface")
97 pos = self.piezo.get_surface_position(
98 axis_name=self.axis_name, max_deflection=target_def,
99 min_slope_ratio=min_slope_ratio)
102 pos = self._stepper_approach_again(
103 target_deflection=target_def, min_slope_ratio=min_slope_ratio,
105 except _SurfaceError, e:
107 pos = self._stepper_approach_again(
108 target_deflection=target_def, min_slope_ratio=min_slope_ratio,
111 pos_m = _convert_bits_to_meters(axis.config, pos)
112 _LOG.debug('located surface at stepper %d, piezo %d (%g m)'
113 % (self.stepper.position, pos, pos_m))
115 _LOG.debug('fine tune the stepper position')
116 while pos_m < -stepper_tolerance: # step back if we need to
117 _LOG.debug('step back')
118 self.stepper.step_relative(-1, backlash_safe=True)
120 pos = self.piezo.get_surface_position(
121 axis_name=self.axis_name, max_deflection=target_def,
122 min_slope_ratio=min_slope_ratio)
126 pos_m = _convert_bits_to_meters(axis.config, pos)
127 _LOG.debug('located surface at stepper %d, piezo %d (%g m)'
128 % (self.stepper.position, pos, pos_m))
129 while pos_m > stepper_tolerance: # step forward if we need to
130 _LOG.debug('step forward')
131 self.stepper.step_relative(1)
133 pos = self.piezo.get_surface_position(
134 axis_name=self.axis_name, max_deflection=target_def,
135 min_slope_ratio=min_slope_ratio)
139 pos_m = _convert_bits_to_meters(axis.config, pos)
140 _LOG.debug('located surface at stepper %d, piezo %d (%g m)'
141 % (self.stepper.position, pos, pos_m))
143 _LOG.debug('adjust the %s piezo to place us just onto the surface'
145 target_m = pos_m + depth
146 target = _convert_meters_to_bits(axis.config, target_m)
147 self.piezo.jump(self.axis_name, target)
150 'positioned %g m into the surface at stepper %d, piezo %d (%g m)'
151 % (depth, self.stepper.position, target, target_m))
153 def _check_target_deflection(self, deflection):
154 defc = self.piezo._deflection_channel()
155 max_def = defc.get_maxdata()
156 if deflection > max_def:
157 _LOG.error(('requested setpoint ({} bits) is larger than the '
158 'maximum deflection value of {} bits'
159 ).format(deflection, max_def))
160 raise ValueError(deflection)
162 _LOG.error(('requested setpoint ({} bits) is less than the '
163 'minimum deflection value of 0 bits'
164 ).format(deflection))
165 raise ValueError(deflection)
167 def _stepper_approach_again(self, target_deflection, min_slope_ratio, far):
168 _LOG.info('back off %d half steps and approach until deflection > %g'
169 % (far, target_deflection))
171 self.stepper.step_relative(-far, backlash_safe=True)
172 self.stepper_approach(target_deflection=target_deflection)
173 for i in range(2*max(1, self.stepper.backlash)):
175 'additional surface location attempt (stepping backwards)')
177 pos = self.piezo.get_surface_position(
178 axis_name=self.axis_name, max_deflection=target_deflection,
179 min_slope_ratio=min_slope_ratio)
181 except _SurfaceError, e:
183 self.stepper.single_step(-1) # step out
184 _LOG.debug('giving up on finding the surface')
188 def stepper_approach(self, target_deflection):
189 _LOG.info('approach with stepper until deflection > {}'.format(
191 self._check_target_deflection(deflection=target_deflection)
192 cd = self.piezo.read_deflection() # cd = current deflection in bits
193 _LOG.debug('single stepping approach')
194 while cd < target_deflection:
195 _LOG.debug('deflection {} < setpoint {}. step closer'.format(
196 cd, target_deflection))
197 self.stepper.single_step(1) # step in
198 cd = self.piezo.read_deflection()