1 """Tools for controlling atomic force microscopes.
3 Provides control of AFM postition using both short-range (piezo) and
4 long range (stepper) vertical positioning. There are separate modules
5 for controlling the piezo (`pypiezo`) and stepper (`stepper`), this
6 module only contains methods that require the capabilities of both.
9 import logging as _logging
11 from pypiezo.base import convert_volts_to_bits as _convert_volts_to_bits
12 from pypiezo.base import convert_meters_to_bits as _convert_meters_to_bits
13 from pypiezo.base import convert_bits_to_meters as _convert_bits_to_meters
14 from pypiezo.surface import SurfaceError as _SurfaceError
15 from pypiezo.surface import FlatFit as _FlatFit
21 LOG = _logging.getLogger('pyafm')
24 LOG.setLevel(_logging.DEBUG)
25 h = _logging.StreamHandler()
26 f = _logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
33 """Atomic force microscope positioning.
35 Uses a short range `piezo` and a long range `stepper` to position
36 an AFM tip relative to the surface.
40 piezo | pypiezo.afm.AFMpiezo instance
41 Fine positioning and deflection measurements.
42 stepper | stepper.Stepper instance
44 temperature | temperature.Controller instance or None
45 Optional temperature monitoring and control.
47 def __init__(self, piezo, stepper, temperature=None, axis_name='z'):
49 self.stepper = stepper
50 self.temperature = temperature
51 self.axis_name = axis_name
53 def get_temperature(self):
54 """Measure the sample temperature.
56 Return the sample temperature in Kelvin or `None` if such a
57 measurement is not possible.
59 if hasattr(self.temperature, 'get'):
60 return self.temperature.get_temperature()
62 def move_just_onto_surface(self, depth=-50e-9, setpoint=2, far=200):
63 """Position the AFM tip close to the surface.
65 Uses `.piezo.get_surface_position()` to pinpoint the position
66 of the surface. Adjusts the stepper position as required via
67 `.stepper.step_relative()` to get within
68 `2*.stepper.step_size` meters of the surface. Then adjusts
69 the piezo to place the cantilever `depth` meters onto the
70 surface. Negative `depth`\s place the tip off the surface
72 If `.piezo.get_surface_position()` fails to find the surface,
73 backs off `far` half steps (for safety) and steps in (without
74 moving the zpiezo) until deflection voltage is greater than
77 LOG.info('moving to %g onto the surface' % depth)
79 stepper_tolerance = 2*self.stepper.step_size
81 axis = self.piezo.axis_by_name(self.axis_name)
83 zero = _convert_volts_to_bits(axis.axis_channel_config, 0)
84 target_def = _convert_volts_to_bits(axis.axis_channel_config, setpoint)
86 LOG.debug('zero the %s piezo output' % self.axis_name)
87 self.piezo.jump(axis_name=self.axis_name, position=zero)
89 LOG.debug("see if we're starting near the surface")
91 pos = self.piezo.get_surface_position(
92 axis_name=self.axis_name, max_deflection=target_def)
95 pos = self._stepper_approach_again(
96 target_deflection=target_def, far=far)
97 except _SurfaceError, e:
99 pos = self._stepper_approach_again(
100 target_deflection=target_def, far=far)
102 pos_m = _convert_bits_to_meters(
103 axis.axis_channel_config, axis.axis_config, pos)
104 LOG.debug('located surface at stepper %d, piezo %d (%g m)'
105 % (self.stepper.position, pos, pos_m))
107 LOG.debug('fine tune the stepper position')
108 while pos_m < -stepper_tolerance: # step back if we need to
109 LOG.debug('step back')
110 self.stepper.step_relative(-1, backlash_safe=True)
112 pos = self.piezo.get_surface_position(
113 axis_name=self.axis_name, max_deflection=target_def)
117 pos_m = _convert_bits_to_meters(
118 axis.axis_channel_config, axis.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)
125 pos = self.piezo.get_surface_position(
126 axis_name=self.axis_name, max_deflection=target_def)
130 pos_m = _convert_bits_to_meters(
131 axis.axis_channel_config, axis.axis_config, pos)
132 LOG.debug('located surface at stepper %d, piezo %d (%g m)'
133 % (self.stepper.position, pos, pos_m))
135 LOG.debug('adjust the %s piezo to place us just onto the surface'
137 target_m = pos_m + depth
138 target = _convert_meters_to_bits(
139 axis.axis_channel_config, axis.axis_config, target_m)
140 self.piezo.jump(self.axis_name, target)
143 'positioned %g m into the surface at stepper %d, piezo %d (%g m)'
144 % (depth, self.stepper.position, target, target_m))
147 def _stepper_approach_again(self, target_deflection, far):
148 LOG.info('back off %d half steps and approach until deflection > %g'
149 % (far, target_deflection))
152 self.stepper.step_relative(-far, backlash_safe=True)
154 cd = self.piezo.read_deflection() # cd = current deflection in bits
155 LOG.debug('single stepping approach')
156 while cd < target_deflection:
157 LOG.debug('deflection %g < setpoint %g. step closer'
158 % (cd, target_deflection))
159 self.stepper.single_step(1) # step in
160 cd = self.piezo.read_deflection()
162 for i in range(2*max(1, self.stepper.backlash)):
163 LOG.debug('additional surface location attempt')
165 pos = self.piezo.get_surface_position(
166 axis_name=self.axis_name, max_deflection=target_deflection)
168 except _SurfaceError, e:
170 self.stepper.single_step(-1) # step out
171 LOG.debug('giving up on finding the surface')