module only contains methods that require the capabilities of both.
"""
+from pypiezo.afm import AFMPiezo as _AFMPiezo
from pypiezo.base import convert_bits_to_meters as _convert_bits_to_meters
from pypiezo.base import convert_meters_to_bits as _convert_meters_to_bits
from pypiezo.base import convert_volts_to_bits as _convert_volts_to_bits
from pypiezo.surface import SurfaceError as _SurfaceError
from . import LOG as _LOG
+from .stepper import Stepper as _Stepper
+from .temperature import Temperature as _Temperature
class AFM (object):
Coarse positioning.
temperature | temperature.Controller instance or None
Optional temperature monitoring and control.
+
+ >>> import os
+ >>> import tempfile
+ >>> from pycomedi import constant
+ >>> import pypiezo.config
+ >>> import pyafm.config
+ >>> import pyafm.storage
+ >>> from h5config.storage.hdf5 import pprint_HDF5
+
+ >>> fd,filename = tempfile.mkstemp(suffix='.h5', prefix='pyafm-')
+ >>> os.close(fd)
+
+ >>> devices = []
+
+ >>> config = pyafm.config.AFMConfig()
+ >>> config['piezo'] = pypiezo.config.PiezoConfig()
+ >>> config['piezo']['name'] = 'test piezo'
+ >>> config['piezo']['axes'] = [pypiezo.config.AxisConfig()]
+ >>> config['piezo']['axes'][0]['channel'] = (
+ ... pypiezo.config.OutputChannelConfig())
+ >>> config['piezo']['axes'][0]['channel']['name'] = 'z'
+ >>> config['piezo']['inputs'] = [pypiezo.config.InputChannelConfig()]
+ >>> config['piezo']['inputs'][0]['name'] = 'deflection'
+ >>> config['stepper'] = pyafm.config.StepperConfig()
+ >>> config['stepper']['port'] = pyafm.config.DigitalPortConfig()
+ >>> config['stepper']['port']['channels'] = [1, 2, 3, 4]
+ >>> config['stepper']['port']['direction'] = constant.IO_DIRECTION.output
+ >>> config['stepper']['port']['name'] = 'stepper port'
+ >>> config['stepper']['name'] = 'test stepper'
+ >>> config['temperature'] = pyafm.config.TemperatureConfig()
+ >>> config['temperature']['name'] = 'test temperature'
+
+ >>> afm = AFM(config=config, devices=devices)
+ >>> afm.setup_config()
+
+ >>> afm.get_temperature() # doctest: +SKIP
+ 297.37
+
+ >>> print(afm.config.dump()) # doctest: +REPORT_UDIFF
+ name:
+ main-axis:
+ piezo:
+ name: test piezo
+ axes:
+ 0:
+ gain: 1.0
+ sensitivity: 1.0
+ minimum: -10.0
+ maximum: 10.0
+ channel:
+ name: z
+ device: /dev/comedi0
+ subdevice: 1
+ channel: 0
+ maxdata: 65535
+ range: 0
+ analog-reference: ground
+ conversion-coefficients: -10.0,0.000305180437934
+ conversion-origin: 0.0
+ inverse-conversion-coefficients: 0.0,3276.75
+ inverse-conversion-origin: -10.0
+ monitor:
+ inputs:
+ 0:
+ name: deflection
+ device: /dev/comedi0
+ subdevice: 0
+ channel: 0
+ maxdata: 65535
+ range: 0
+ analog-reference: ground
+ conversion-coefficients: -10.0,0.000305180437934
+ conversion-origin: 0.0
+ inverse-conversion-coefficients: 0.0,3276.75
+ inverse-conversion-origin: -10.0
+ stepper:
+ name: test stepper
+ full-step: yes
+ logic: yes
+ delay: 0.01
+ step-size: 1.7e-07
+ backlash: 100
+ port:
+ name: stepper port
+ device: /dev/comedi0
+ subdevice: 2
+ subdevice-type: dio
+ channels: 1,2,3,4
+ direction: output
+ temperature:
+ name: test temperature
+ units: Celsius
+ controller: 1
+ device: /dev/ttyS0
+ baudrate: 9600
+ max-current: 0.0
+ fallback-temperature: 295.15
+ far: 3e-05
+
+ >>> pyafm.storage.save_afm(afm=afm, filename=filename)
+ >>> pprint_HDF5(filename=filename) # doctest: +REPORT_UDIFF
+ /
+ <HDF5 dataset "fallback-temperature": shape (), type "<f8">
+ 295.15
+ <HDF5 dataset "far": shape (), type "<f8">
+ 3e-05
+ <HDF5 dataset "main-axis": shape (), type "|S1">
+ <BLANKLINE>
+ <HDF5 dataset "name": shape (), type "|S1">
+ <BLANKLINE>
+ /piezo
+ /piezo/axes
+ /piezo/axes/0
+ /piezo/axes/0/channel
+ <HDF5 dataset "analog-reference": shape (), type "|S6">
+ ground
+ <HDF5 dataset "channel": shape (), type "<i4">
+ 0
+ <HDF5 dataset "conversion-coefficients": shape (2,), type "<f8">
+ [ -1.00000000e+01 3.05180438e-04]
+ <HDF5 dataset "conversion-origin": shape (), type "<f8">
+ 0.0
+ <HDF5 dataset "device": shape (), type "|S12">
+ /dev/comedi0
+ <HDF5 dataset "inverse-conversion-coefficients": shape (2,), type "<f8">
+ [ 0. 3276.75]
+ <HDF5 dataset "inverse-conversion-origin": shape (), type "<f8">
+ -10.0
+ <HDF5 dataset "maxdata": shape (), type "<i8">
+ 65535
+ <HDF5 dataset "name": shape (), type "|S1">
+ z
+ <HDF5 dataset "range": shape (), type "<i4">
+ 0
+ <HDF5 dataset "subdevice": shape (), type "<i4">
+ 1
+ <HDF5 dataset "gain": shape (), type "<f8">
+ 1.0
+ <HDF5 dataset "maximum": shape (), type "<f8">
+ 10.0
+ <HDF5 dataset "minimum": shape (), type "<f8">
+ -10.0
+ <HDF5 dataset "monitor": shape (), type "|S1">
+ <BLANKLINE>
+ <HDF5 dataset "sensitivity": shape (), type "<f8">
+ 1.0
+ /piezo/inputs
+ /piezo/inputs/0
+ <HDF5 dataset "analog-reference": shape (), type "|S6">
+ ground
+ <HDF5 dataset "channel": shape (), type "<i4">
+ 0
+ <HDF5 dataset "conversion-coefficients": shape (2,), type "<f8">
+ [ -1.00000000e+01 3.05180438e-04]
+ <HDF5 dataset "conversion-origin": shape (), type "<f8">
+ 0.0
+ <HDF5 dataset "device": shape (), type "|S12">
+ /dev/comedi0
+ <HDF5 dataset "inverse-conversion-coefficients": shape (2,), type "<f8">
+ [ 0. 3276.75]
+ <HDF5 dataset "inverse-conversion-origin": shape (), type "<f8">
+ -10.0
+ <HDF5 dataset "maxdata": shape (), type "<i8">
+ 65535
+ <HDF5 dataset "name": shape (), type "|S10">
+ deflection
+ <HDF5 dataset "range": shape (), type "<i4">
+ 0
+ <HDF5 dataset "subdevice": shape (), type "<i4">
+ 0
+ <HDF5 dataset "name": shape (), type "|S10">
+ test piezo
+ /stepper
+ <HDF5 dataset "backlash": shape (), type "<i4">
+ 100
+ <HDF5 dataset "delay": shape (), type "<f8">
+ 0.01
+ <HDF5 dataset "full-step": shape (), type "|b1">
+ True
+ <HDF5 dataset "logic": shape (), type "|b1">
+ True
+ <HDF5 dataset "name": shape (), type "|S12">
+ test stepper
+ /stepper/port
+ <HDF5 dataset "channels": shape (4,), type "<i4">
+ [1 2 3 4]
+ <HDF5 dataset "device": shape (), type "|S12">
+ /dev/comedi0
+ <HDF5 dataset "direction": shape (), type "|S6">
+ output
+ <HDF5 dataset "name": shape (), type "|S12">
+ stepper port
+ <HDF5 dataset "subdevice": shape (), type "<i4">
+ 2
+ <HDF5 dataset "subdevice-type": shape (), type "|S3">
+ dio
+ <HDF5 dataset "step-size": shape (), type "<f8">
+ 1.7e-07
+ /temperature
+ <HDF5 dataset "baudrate": shape (), type "<i4">
+ 9600
+ <HDF5 dataset "controller": shape (), type "<i4">
+ 1
+ <HDF5 dataset "device": shape (), type "|S10">
+ /dev/ttyS0
+ <HDF5 dataset "max-current": shape (), type "<f8">
+ 0.0
+ <HDF5 dataset "name": shape (), type "|S16">
+ test temperature
+ <HDF5 dataset "units": shape (), type "|S7">
+ Celsius
+ >>> afm2 = pyafm.storage.load_afm(filename=filename, devices=devices)
+
+ >>> afm2.get_temperature() # doctest: +SKIP
+ 297.37
+
+ It's hard to test anything else without pugging into an actual AFM.
+
+ >>> for device in devices:
+ ... device.close()
+
+ Cleanup our temporary config file.
+
+ >>> os.remove(filename)
"""
- def __init__(self, piezo, stepper, temperature=None, axis_name='z'):
+ def __init__(self, config, piezo=None, stepper=None, temperature=None,
+ devices=None):
+ self.config = config
self.piezo = piezo
self.stepper = stepper
self.temperature = temperature
- self.axis_name = axis_name
+ self.load_from_config(devices=devices)
+
+ def load_from_config(self, devices):
+ c = self.config # reduce verbosity
+ if self.piezo is None and c['piezo']:
+ self.piezo = _AFMPiezo(config=c['piezo'], devices=devices)
+ if self.stepper is None and c['stepper']:
+ self.stepper = _Stepper(config=c['stepper'], devices=devices)
+ if self.temperature is None and c['temperature']:
+ self.temperature = _Temperature(config=c['temperature'])
+
+ def setup_config(self):
+ if self.piezo:
+ self.piezo.setup_config()
+ self.config['piezo'] = self.piezo.config
+ else:
+ self.config['piezo'] = None
+ if self.stepper:
+ self.stepper.setup_config()
+ self.config['stepper'] = self.stepper.config
+ else:
+ self.config['stepper'] = None
+ if self.temperature:
+ self.temperature.setup_config()
+ self.config['temperature'] = self.temperature.config
+ else:
+ self.config['temperature'] = None
def get_temperature(self):
"""Measure the sample temperature.
"""
if hasattr(self.temperature, 'get_temperature'):
return self.temperature.get_temperature()
+ return self.config['default-temperature']
def move_just_onto_surface(self, depth=-50e-9, setpoint=2,
- min_slope_ratio=10, far=200):
+ min_slope_ratio=10, far=200, steps=20,
+ sleep=0.0001):
"""Position the AFM tip close to the surface.
Uses `.piezo.get_surface_position()` to pinpoint the position
of the surface. Adjusts the stepper position as required via
- `.stepper.step_relative()` to get within
+ `.stepper.single_step()` to get within
`2*.stepper.step_size` meters of the surface. Then adjusts
the piezo to place the cantilever `depth` meters onto the
surface. Negative `depth`\s place the tip off the surface
stepper_tolerance = 2*self.stepper.step_size
axis = self.piezo.axis_by_name(self.axis_name)
- defc = self.piezo._deflection_channel()
+ def_config = self.piezo.config.select_config('inputs', 'deflection')
zero = _convert_volts_to_bits(axis.config['channel'], 0)
- target_def = _convert_volts_to_bits(defc.config, setpoint)
+ target_def = _convert_volts_to_bits(def_config, setpoint)
self._check_target_deflection(deflection=target_def)
_LOG.debug('zero the %s piezo output' % self.axis_name)
- self.piezo.jump(axis_name=self.axis_name, position=zero)
+ self.piezo.jump(
+ axis_name=self.axis_name, position=zero, steps=steps, sleep=sleep)
_LOG.debug("see if we're starting near the surface")
try:
_LOG.debug('fine tune the stepper position')
while pos_m < -stepper_tolerance: # step back if we need to
_LOG.debug('step back')
- self.stepper.step_relative(-1, backlash_safe=True)
+ self.stepper.single_step(-1)
try:
pos = self.piezo.get_surface_position(
axis_name=self.axis_name, max_deflection=target_def,
% (self.stepper.position, pos, pos_m))
while pos_m > stepper_tolerance: # step forward if we need to
_LOG.debug('step forward')
- self.stepper.step_relative(1)
+ self.stepper.single_step(1)
try:
pos = self.piezo.get_surface_position(
axis_name=self.axis_name, max_deflection=target_def,
% self.axis_name)
target_m = pos_m + depth
target = _convert_meters_to_bits(axis.config, target_m)
- self.piezo.jump(self.axis_name, target)
+ self.piezo.jump(self.axis_name, target, steps=steps, sleep=sleep)
_LOG.debug(
'positioned %g m into the surface at stepper %d, piezo %d (%g m)'
def _check_target_deflection(self, deflection):
defc = self.piezo._deflection_channel()
max_def = defc.get_maxdata()
- if target_deflection > max_def:
- _LOG.error(('requested setpoint ({:g} V = {:d} bits) is larger '
- 'than the maximum deflection value of {:d} bits'
- ).format(setpoint, target_deflection, max_def))
- raise ValueError(setpoint)
- elif target_deflection < 0:
- _LOG.error(('requested setpoint ({:g} V = {:d} bits) is less '
- 'than the minimum deflection value of 0 bits'
- ).format(setpoint, target_deflection))
- raise ValueError(setpoint)
+ if deflection > max_def:
+ _LOG.error(('requested setpoint ({} bits) is larger than the '
+ 'maximum deflection value of {} bits'
+ ).format(deflection, max_def))
+ raise ValueError(deflection)
+ elif deflection < 0:
+ _LOG.error(('requested setpoint ({} bits) is less than the '
+ 'minimum deflection value of 0 bits'
+ ).format(deflection))
+ raise ValueError(deflection)
def _stepper_approach_again(self, target_deflection, min_slope_ratio, far):
_LOG.info('back off %d half steps and approach until deflection > %g'
cd, target_deflection))
self.stepper.single_step(1) # step in
cd = self.piezo.read_deflection()
+
+ def move_away_from_surface(stepper, distance=None):
+ """Step back approximately `distance` meters.
+ """
+ if distance is None:
+ distance = self.config['far']
+ steps = int(distance/self.stepper.step_size)
+ _LOG.info('step back {} steps (~{} m)' % (steps, distance))
+ self.stepper.step_relative(-steps)