Import missing FlatFit from pypiezo.surface.
[pyafm.git] / pyafm / afm.py
1 # Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of pyafm.
4 #
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.
9 #
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.
14 #
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/>.
17
18 """Tools for controlling atomic force microscopes.
19
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.
24 """
25
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
31 from . import LOG as _LOG
32
33
34 class AFM (object):
35     """Atomic force microscope positioning.
36
37     Uses a short range `piezo` and a long range `stepper` to position
38     an AFM tip relative to the surface.
39
40     Parameters
41     ----------
42     piezo | pypiezo.afm.AFMpiezo instance
43         Fine positioning and deflection measurements.
44     stepper | stepper.Stepper instance
45         Coarse positioning.
46     temperature | temperature.Controller instance or None
47         Optional temperature monitoring and control.
48     """
49     def __init__(self, piezo, stepper, temperature=None, axis_name='z'):
50         self.piezo = piezo
51         self.stepper = stepper
52         self.temperature = temperature
53         self.axis_name = axis_name
54
55     def get_temperature(self):
56         """Measure the sample temperature.
57
58         Return the sample temperature in Kelvin or `None` if such a
59         measurement is not possible.
60         """
61         if hasattr(self.temperature, 'get'):
62             return self.temperature.get_temperature()
63
64     def move_just_onto_surface(self, depth=-50e-9, setpoint=2, far=200):
65         """Position the AFM tip close to the surface.
66
67         Uses `.piezo.get_surface_position()` to pinpoint the position
68         of the surface.  Adjusts the stepper position as required via
69         `.stepper.step_relative()` to get within
70         `2*.stepper.step_size` meters of the surface.  Then adjusts
71         the piezo to place the cantilever `depth` meters onto the
72         surface.  Negative `depth`\s place the tip off the surface
73
74         If `.piezo.get_surface_position()` fails to find the surface,
75         backs off `far` half steps (for safety) and steps in (without
76         moving the zpiezo) until deflection voltage is greater than
77         `setpoint`.
78         """
79         _LOG.info('moving to %g onto the surface' % depth)
80
81         stepper_tolerance = 2*self.stepper.step_size
82
83         axis = self.piezo.axis_by_name(self.axis_name)
84
85         zero = _convert_volts_to_bits(axis.config['channel'], 0)
86         target_def = _convert_volts_to_bits(axis.config['channel'], setpoint)
87
88         _LOG.debug('zero the %s piezo output' % self.axis_name)
89         self.piezo.jump(axis_name=self.axis_name, position=zero)
90
91         _LOG.debug("see if we're starting near the surface")
92         try:
93             pos = self.piezo.get_surface_position(
94                 axis_name=self.axis_name, max_deflection=target_def)
95         except _FlatFit, e:
96             _LOG.info(e)
97             pos = self._stepper_approach_again(
98                 target_deflection=target_def, far=far)
99         except _SurfaceError, e:
100             _LOG.info(e)
101             pos = self._stepper_approach_again(
102                 target_deflection=target_def, far=far)
103
104         pos_m = _convert_bits_to_meters(axis.config, pos)
105         _LOG.debug('located surface at stepper %d, piezo %d (%g m)'
106                   % (self.stepper.position, pos, pos_m))
107
108         _LOG.debug('fine tune the stepper position')
109         while pos_m < -stepper_tolerance:  # step back if we need to
110             _LOG.debug('step back')
111             self.stepper.step_relative(-1, backlash_safe=True)
112             try:
113                 pos = self.piezo.get_surface_position(
114                     axis_name=self.axis_name, max_deflection=target_def)
115             except _FlatFit, e:
116                 _LOG.debug(e)
117                 continue
118             pos_m = _convert_bits_to_meters(axis.config, pos)
119             _LOG.debug('located surface at stepper %d, piezo %d (%g m)'
120                       % (self.stepper.position, pos, pos_m))
121         while pos_m > stepper_tolerance:  # step forward if we need to
122             _LOG.debug('step forward')
123             self.stepper.step_relative(1)
124             try:
125                 pos = self.piezo.get_surface_position(
126                     axis_name=self.axis_name, max_deflection=target_def)
127             except _FlatFit, e:
128                 _LOG.debug(e)
129                 continue
130             pos_m = _convert_bits_to_meters(axis.config, pos)
131             _LOG.debug('located surface at stepper %d, piezo %d (%g m)'
132                       % (self.stepper.position, pos, pos_m))
133
134         _LOG.debug('adjust the %s piezo to place us just onto the surface'
135                   % self.axis_name)
136         target_m = pos_m + depth
137         target = _convert_meters_to_bits(axis.config, target_m)
138         self.piezo.jump(self.axis_name, target)
139
140         _LOG.debug(
141             'positioned %g m into the surface at stepper %d, piezo %d (%g m)'
142             % (depth, self.stepper.position, target, target_m))
143
144
145     def _stepper_approach_again(self, target_deflection, far):
146         _LOG.info('back off %d half steps and approach until deflection > %g'
147                  % (far, target_deflection))
148
149         # back away
150         self.stepper.step_relative(-far, backlash_safe=True)
151
152         cd = self.piezo.read_deflection()  # cd = current deflection in bits
153         _LOG.debug('single stepping approach')
154         while cd < target_deflection:
155             _LOG.debug('deflection %g < setpoint %g.  step closer'
156                       % (cd, target_deflection))
157             self.stepper.single_step(1)  # step in
158             cd = self.piezo.read_deflection()
159
160         for i in range(2*max(1, self.stepper.backlash)):
161             _LOG.debug(
162                 'additional surface location attempt (stepping backwards)')
163             try:
164                 pos = self.piezo.get_surface_position(
165                     axis_name=self.axis_name, max_deflection=target_deflection)
166                 return pos
167             except _SurfaceError, e:
168                 _LOG.info(e)
169             self.stepper.single_step(-1)  # step out
170         _LOG.debug('giving up on finding the surface')
171         _LOG.warn(e)
172         raise e