1 # Python control of stepper motors.
3 # Copyright (C) 2008-2011 W. Trevor King
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 import logging as _logging
19 import logging.handlers as _logging_handlers
20 from time import sleep as _sleep
26 LOG = _logging.getLogger('stepper')
29 LOG.setLevel(_logging.WARN)
30 _formatter = _logging.Formatter(
31 '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
33 _stream_handler = _logging.StreamHandler()
34 _stream_handler.setLevel(_logging.DEBUG)
35 _stream_handler.setFormatter(_formatter)
36 LOG.addHandler(_stream_handler)
39 def _binary(i, width=4):
40 """Convert `i` to a binary string of width `width`.
48 return '0'*(width-len(str)) + str
51 class Stepper (object):
52 """Stepper motor control
56 write (fn(value)) write the 4-bit integer `value` to
57 appropriate digital output channels.
58 full_step (boolean) select full or half stepping
59 logic (boolean) select active high (True) or active low (False)
60 delay (float) time delay between steps in seconds, in case the
61 motor response is slower than the drigital output
63 step_size (float) approximate step size in meters
66 >>> from pycomedi.device import Device
67 >>> from pycomedi.channel import DigitalChannel
68 >>> from pycomedi.constant import SUBDEVICE_TYPE, IO_DIRECTION
70 >>> device = Device('/dev/comedi0')
73 >>> subdevice = device.find_subdevice_by_type(SUBDEVICE_TYPE.dio)
74 >>> channels = [subdevice.channel(i, factory=DigitalChannel)
75 ... for i in (0, 1, 2, 3)]
76 >>> for chan in channels:
77 ... chan.dio_config(IO_DIRECTION.output)
80 ... subdevice.dio_bitfield(bits=value, write_mask=2**4-1)
82 >>> s = Stepper(write=write)
100 >>> s.single_step(-1)
103 >>> s.full_step = False
104 >>> s.single_step(-1)
107 >>> s.single_step(-1)
110 >>> s.single_step(-1)
128 >>> s.step_relative(1000)
134 def __init__(self, write, full_step=True, logic=True, delay=1e-5,
137 self.full_step = full_step
140 self.step_size = step_size
141 self.port_values = [1, # binary ---1 setup for logic == True
149 self._set_position(0)
151 def _get_output(self, position):
152 """Get the port value that places the stepper in `position`.
154 >>> s = Stepper(write=lambda value: value, logic=True)
155 >>> _binary(s._get_output(0))
157 >>> _binary(s._get_output(1))
159 >>> _binary(s._get_output(2))
161 >>> _binary(s._get_output(-79))
163 >>> _binary(s._get_output(81))
166 >>> _binary(s._get_output(0))
168 >>> _binary(s._get_output(1))
170 >>> _binary(s._get_output(2))
173 value = self.port_values[position % len(self.port_values)]
175 value = 2**4 - 1 - value
178 def _set_position(self, position):
179 self.position = position # current stepper index in half steps
180 output = self._get_output(position)
181 LOG.debug('set postition to %d (%s)' % (position, _binary(output)))
184 def single_step(self, direction):
185 LOG.debug('single step')
186 if self.full_step and self.position % 2 == 1:
187 self.position -= 1 # round down to a full step
193 raise ValueError(direction) # no step
196 self._set_position(self.position + step)
200 def step_to(self, target_position):
201 if target_position != int(target_position):
203 'target_position %s must be an int' % target_position)
204 if self.full_step and target_position % 2 == 1:
205 target_position -= 1 # round down to a full step
206 if target_position > self.position:
210 while self.position != target_position:
211 LOG.debug('stepping %s -> %s (%s)' % (target_position, self.position, direction))
212 self.single_step(direction)
214 def step_relative(self, relative_target_position):
215 return self.step_to(self.position + relative_target_position)