Source code for grove.motor.i2c_motor_driver

#!/usr/bin/env python
#
# Grove Library for I2C-Motor-Driver with Stepper Motor

from __future__ import division
from grove.i2c import Bus

__all__ = ["StepperMotor", "I2CStepperMotor"]

[docs] class StepperMotor(object): DC_SPEED_MAX = 100 _DIR_ANTI_CLKWISE = 0 _DIR_CLKWISE = 1 def __init__(self, arguments): # default Dirction self._dir = self._DIR_CLKWISE # Speed Varation Ratio, 1.0 is unapplicable # Most time it's reductor. self._var_ratio = arguments["var-ratio"] # Stride Angle self._stride_angle = arguments["stride-angle"] self._rpm_max = arguments.get("rpm-max", 1.0) self._name = arguments.get("name", "StepperMotor") @property def speed_max(self): return self._rpm_max @property def name(self): return self._name # To be derived # def _rotate(self, angle = None): # return None
[docs] def rotate(self, angle = None): "specify the stepper motor rotating angle," " query the angle left if angle missed" " angle --- 1 cycle = 360 degrees, positive float or int acceptable" " return angle left" return self._rotate(angle)
# To be derived def _speed(self, rpm): pass
[docs] def speed(self, rpm = None): "set or get stepper motor speed & direction" " rpm --- revolutions per minute, float or int acceptable" " > 0 clockwise, < 0 anti-clockwise" " return rpm value stored" if not rpm is None: self._rpm = rpm self._speed(rpm) return self._rpm
# To be derived def _enable(self, en): pass
[docs] def enable(self, en = None): "enable or disable the stepper motor" " en --- True for enable, False for disable" " return disable or enable state stored" if not en is None: self._en = en self._enable(en) return self._en
def _steps2angle(self, steps): angle = steps * self._stride_angle / self._var_ratio return angle def _angle2steps(self, angle): steps = angle * self._var_ratio / self._stride_angle return int(steps)
import time
[docs] class I2CStepperMotor(StepperMotor): __REG_GET_PID = 0x00 __REG_GET_VID = 0x01 __REG_GET_VER = 0x02 __REG_STP_EN = 0x1A __REG_STP_DIS = 0x1B __REG_STP_RUN = 0x1C __REG_STP_INTERVAL = 0x1D __REG_SEQ_LEN = 0x20 __REG_SEQ_XET = 0x21 __REG_SET_SPEED = 0x82 __REG_SET_FREQ = 0x84 __REG_SET_A = 0xA1 __REG_SET_B = 0xA5 __REG_SET_DIR = 0xAA def __init__(self, arguments, address = 0x0F): super(I2CStepperMotor, self).__init__(arguments) self._addr = address self._bus = Bus() self._ang_left = 0 self._load_seq(arguments["sequences"]) def __del__(self): self.set_speed(0, 0) pass #Maps speed from 0-100 to 0-255 def _map_vals(self,value, leftMin, leftMax, rightMin, rightMax): #http://stackoverflow.com/questions/1969240/mapping-a-range-of-values-to-another # Figure out how 'wide' each range is leftSpan = leftMax - leftMin rightSpan = rightMax - rightMin # Convert the left range into a 0-1 range (float) valueScaled = float(value - leftMin) / float(leftSpan) # Convert the 0-1 range into a value in the right range. return int(rightMin + (valueScaled * rightSpan)) #Set motor speed def set_speed(self, speed1 = 0, speed2 = 0): s1 = self._map_vals(speed1, 0, 100, 0, 255) s2 = self._map_vals(speed2, 0, 100, 0, 255) self._bus.write_i2c_block_data(self._addr, self.__REG_SET_SPEED, [s1, s2]) time.sleep(.02) #Set motor direction def set_dir(self, clock_wise1 = True, clock_wise2 = True): dir1 = 0b10 if clock_wise1 else 0b01 dir2 = 0b10 if clock_wise2 else 0b01 dir = (dir2 << 2) | dir1 self._bus.write_i2c_block_data(self._addr, self.__REG_SET_DIR, [dir, 0]) time.sleep(.02) def _load_seq(self, seq): length = len(seq) self._bus.write_word_data(self._addr, self.__REG_SEQ_LEN, length) for i in range(len(seq)): self._bus.write_word_data(self._addr, self.__REG_SEQ_XET, seq[i]) def _enable(self, en): cmd = self.__REG_STP_EN if en else self.__REG_STP_DIS if en: self.set_speed(self.DC_SPEED_MAX, self.DC_SPEED_MAX) else: self.set_speed(0, 0) self._bus.write_i2c_block_data(self._addr, cmd, [self._dir, 0]) time.sleep(0.001) def _speed(self, rpm): self._dir = self._DIR_CLKWISE if rpm >= 0 else self._DIR_ANTI_CLKWISE # absolute angle per second aps = abs(rpm) * 360.0 / 60.0 # steps per second, include reductor ratio. sps = self._angle2steps(aps) period = int(1000000 / sps) # us period = period // 10 # STP_INTERVAL, 10 us # print("period = %d us" % (period * 10)) self._bus.write_word_data(self._addr, self.__REG_STP_INTERVAL, period) time.sleep(0.001) def _rotate(self, angle = None): if not angle is None: angle = abs(angle) self._ang_left = angle steps = self._angle2steps(angle) # print("steps set = {}".format(steps)) self._bus.write_word_data(self._addr, self.__REG_STP_RUN, steps) time.sleep(0.001) return angle while True: # reading interface unstable when working try: steps = self._bus.read_word_data(self._addr, self.__REG_STP_RUN) # print("steps left = {}".format(steps)) ang_left = self._steps2angle(steps) if ang_left > self._ang_left: time.sleep(0.01) continue self._ang_left = ang_left return ang_left except IOError: continue
def main(): print("Make sure I2C-Motor-Driver inserted") print(" in one I2C slot of Grove-Base-Hat") arguments_28BYJ48 = { 'var-ratio' : 64, 'stride-angle': 5.625, 'rpm-max' : 12, 'sequences' : # clockwise [ 0b0001, 0b0011, 0b0010, 0b0110, 0b0100, 0b1100, 0b1000, 0b1001 ] # [ 0b1110, 0b1101, 0b1011, 0b0111 ] # anti-clockwise # [ 0b1001, 0b1000, 0b1100, 0b0100, 0b0110, 0b0010, 0b0011, 0b0001 ] } # motor = I2CStepperMotor(arguments_YH42BYGH40) motor = I2CStepperMotor(arguments_28BYJ48) ANGLE = 360 # rotate 360 degrees = 1 cycle motor.rotate(ANGLE) SPEED = 12 # speed set to 12 rpm, clockwise # anti-clockwise use negative value (-12) motor.speed(SPEED) # enable the motor, begin to run motor.enable(True) seconds = ANGLE / 360 / SPEED * 60 print("Motor rotate {} degrees, time = {:.2f}".format(ANGLE, time.time())) print(" with speed {} RPM".format(SPEED)) print(" will be stop after {:4.2f} seconds".format(seconds)) while True: left = motor.rotate() if left < 1e-5: break print("Angle left {:6.2f} degrees, time = {:.2f}".format(left, time.time())) time.sleep(1.0) print("Motor run ended, time = {:.2f} !".format(time.time())) if __name__ == '__main__': main()