X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;f=pyafm%2Fafm.py;h=3093a07997cc0d47890cd36b0882aed80c798e7a;hb=9d59696a88b0783167beec25127bad320b7852c9;hp=69d22c93e73bbb0b91901b61ebc0d97eadc625d2;hpb=d5b21e3ae7e9d0e09b0be028882cc7c179a7392f;p=pyafm.git diff --git a/pyafm/afm.py b/pyafm/afm.py index 69d22c9..3093a07 100644 --- a/pyafm/afm.py +++ b/pyafm/afm.py @@ -23,6 +23,7 @@ for controlling the piezo (`pypiezo`) and stepper (`stepper`), this 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 @@ -30,6 +31,8 @@ from pypiezo.surface import FlatFit as _FlatFit 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): @@ -46,12 +49,264 @@ 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 + / + + 295.15 + + 3e-05 + + + + + /piezo + /piezo/axes + /piezo/axes/0 + /piezo/axes/0/channel + + ground + + 0 + + [ -1.00000000e+01 3.05180438e-04] + + 0.0 + + /dev/comedi0 + + [ 0. 3276.75] + + -10.0 + + 65535 + + z + + 0 + + 1 + + 1.0 + + 10.0 + + -10.0 + + + + 1.0 + /piezo/inputs + /piezo/inputs/0 + + ground + + 0 + + [ -1.00000000e+01 3.05180438e-04] + + 0.0 + + /dev/comedi0 + + [ 0. 3276.75] + + -10.0 + + 65535 + + deflection + + 0 + + 0 + + test piezo + /stepper + + 100 + + 0.01 + + True + + True + + test stepper + /stepper/port + + [1 2 3 4] + + /dev/comedi0 + + output + + stepper port + + 2 + + dio + + 1.7e-07 + /temperature + + 9600 + + 1 + + /dev/ttyS0 + + 0.0 + + test temperature + + 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. @@ -61,14 +316,16 @@ class AFM (object): """ 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 @@ -83,14 +340,15 @@ class AFM (object): 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: @@ -115,7 +373,7 @@ class AFM (object): _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, @@ -128,7 +386,7 @@ class AFM (object): % (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, @@ -144,7 +402,7 @@ class AFM (object): % 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)' @@ -153,16 +411,16 @@ class AFM (object): 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' @@ -196,3 +454,12 @@ class AFM (object): 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)