Show:

File: src/io/Stepper.js

/**
 * Copyright (c) 2011-2014 Jeff Hoefs <soundanalogous@gmail.com>
 * Released under the MIT license. See LICENSE file for details.
 */

JSUTILS.namespace('BO.io.Stepper');

BO.io.Stepper = (function() {
  "use strict";

  var Stepper;
  var instanceCounter = 0;

  // private static constants
  var STEPPER = 0x72,
    CONFIG = 0,
    STEP = 1,
    MAX_STEPS = 2097151, // 21 bits (2^21 - 1)
    MAX_SPEED = 16383; // 14 bits (2^14 - 1)

  // dependencies
  var Pin = BO.Pin,
    EventDispatcher = JSUTILS.EventDispatcher,
    Event = JSUTILS.Event,
    IOBoardEvent = BO.IOBoardEvent;

  /**
   * Creates an interface to a Stepper motor. Use this object to set
   * the direction and number of steps for the motor to rotate. See
   * [Breakout/examples/actuators/stepper\_2wire.html](https://github.com/soundanalogous/Breakout/blob/master/examples/actuators/stepper_2wire.html),
   * [stepper\_4wire.html](https://github.com/soundanalogous/Breakout/blob/master/examples/actuators/stepper_4wire.html),
   * [stepper\_easydriver.html](https://github.com/soundanalogous/Breakout/blob/master/examples/actuators/stepper_easydriver.html)
   * and [stepper\_simple.html](https://github.com/soundanalogous/Breakout/blob/master/examples/actuators/stepper_simple.html) for example applications.
   *
   * @class Stepper
   * @constructor
   * @uses JSUTILS.EventDispatcher
   * @param {IOBoard} board A reference to the IOBoard instance that the
   * stepper is attached to.
   * @param {Number} driverType. The type of driver (`Stepper.DRIVER`,
   * `Stepper.DRIVER_HIGH_CURRENT`, `Stepper.TWO_WIRE`, or
   * `Stepper.FOUR_WIRE`).
   * @param {Number} numStepsPerRev The number of steps to make 1 revolution.
   * @param {Pin} directionPin If dirver interface, the pin used to control
   * the direction.
   * If 2-wire or 4-wire interface, the 1st moter pin.
   * @param {Pin} stepPin If dirver interface, the pin used to control the
   * steps.
   * If 2-wire or 4-wire interface, the 2nd moter pin.
   * @param {Pin} motorPin3 [optional] Only required for a 4-wire interface.
   * @param {Pin} motorPin4 [optional] Only required for a 4-wire interface.
   *
   * @example
   *     var Stepper = BO.io.Stepper,
   *         Event = JSUTILS.Event;
   *
   *     var stepper,
   *         stepsPerRev = 200,           // update this for your stepper
   *         numSteps = stepsPerRev * 10, // 10 revolutions (+ CW, - CCW)
   *         speed = 15.0,                // rad/sec (RPM = speed * 9.55)
   *         acceleration = 20.0,         // rad/sec^2
   *         deceleration = 20.0;         // rad/sec^2
   *
   *     stepper = new Stepper(arduino,
   *                  Stepper.TWO_WIRE, // or Stepper.DRIVER or Stepper.FOUR_WIRE
   *                  stepsPerRev,
   *                  arduino.getDigitalPin(2),
   *                  arduino.getDigitalPin(3));
   *
   *     stepper.addEventListener(Event.COMPLETE, onStepperComplete);
   *
   *     // acceleration and deceleration parameters are optional
   *     stepper.step(numSteps, speed, acceleration, deceleration);
   *
   *     function onStepperComplete(event) {
   *         // each stepper is assigned a read-only id value when instantiated
   *         console.log("stepper " + event.target.id + " sequence complete");
   *     }
   */
  Stepper = function(board, driverType, numStepsPerRev, directionPin, stepPin, motorPin3, motorPin4) {

    // create a new id each time a new instance is created
    this._id = instanceCounter++;
    if (this._id > 5) {
      console.log("Warning: A maximum of 6 Stepper instances can be created");
      return;
    }

    this.name = "Stepper";
    this._board = board;
    this._evtDispatcher = new EventDispatcher(this);

    var numStepsPerRevLSB = numStepsPerRev & 0x007F,
      numStepsPerRevMSB = (numStepsPerRev >> 7) & 0x007F,
      silent = true;

    // Setup pin mode but don't send set pin mode command to Firmata since
    // Firmata sets pin modes automatically in the Stepper implementation.
    this._board.setDigitalPinMode(directionPin.number, Pin.DOUT, silent);
    this._board.setDigitalPinMode(stepPin.number, Pin.DOUT, silent);

    this._board.addEventListener(IOBoardEvent.SYSEX_MESSAGE, this.onSysExMessage.bind(this));

    switch (driverType) {
      case Stepper.DRIVER:
      case Stepper.DRIVER_HIGH_CURRENT:
      case Stepper.TWO_WIRE:
        // configure the stepper motor
        this._board.sendSysex(STEPPER, [CONFIG,
          this._id,
          driverType,
          numStepsPerRevLSB,
          numStepsPerRevMSB,
          directionPin.number,
          stepPin.number
        ]);
        break;
      case Stepper.FOUR_WIRE:
        this._board.setDigitalPinMode(motorPin3.number, Pin.DOUT, silent);
        this._board.setDigitalPinMode(motorPin4.number, Pin.DOUT, silent);

        // configure the stepper motor
        this._board.sendSysex(STEPPER, [CONFIG,
          this._id,
          driverType,
          numStepsPerRevLSB,
          numStepsPerRevMSB,
          directionPin.number,
          stepPin.number,
          motorPin3.number,
          motorPin4.number
        ]);
        break;
    }

  };

  Stepper.prototype = {

    constructor: Stepper,

    /**
     * Move the stepper a given number of steps at the specified
     * speed (rad/sec), acceleration (rad/sec^2) and deceleration (rad/sec^2).
     * The accel and decel parameters are optional but if using, both values
     * must be passed to the function.
     *
     * @method step
     * @param {Number} numSteps The number ofsteps to move the motor (max = +/-2097151 (21 bits)).
     * Positive value is clockwise, negative value is counter clockwise.
     * @param {Number} speed Max speed in rad/sec (1 rad/sec = 9.549 RPM)
     * (max precision of 2 decimal places)
     * @param {Number} accel [optional] Acceleration in rad/sec^2 (max precision of 2 decimal places)
     * @param {Number} decel [optional] Deceleration in rad/sec^2 (max precision of 2 decimal places)
     */
    step: function(numSteps, speed, accel, decel) {
      var steps,
        speedLSB,
        speedMSB,
        accelLSB,
        accelMSB,
        decelLSB,
        decelMSB,
        direction = Stepper.CLOCKWISE;

      if (numSteps > MAX_STEPS) {
        numSteps = MAX_STEPS;
        console.log("Warning: Maximum number of steps (2097151) exceeded. Setting to step number to 2097151");
      }
      if (numSteps < -MAX_STEPS) {
        numSteps = -MAX_STEPS;
        console.log("Warning: Maximum number of steps (-2097151) exceeded. Setting to step number to -2097151");
      }

      if (numSteps > 0) {
        direction = Stepper.COUNTER_CLOCKWISE;
      }

      steps = [
        Math.abs(numSteps) & 0x0000007F, (Math.abs(numSteps) >> 7) & 0x0000007F, (Math.abs(numSteps) >> 14) & 0x0000007F
      ];

      // the stepper interface expects decimal expressed an an integer
      speed = Math.floor(speed.toFixed(2) * 100);

      if (speed > MAX_SPEED) {
        speed = MAX_SPEED;
        console.log("Warning: Maximum speed (163.83 rad/sec) exceeded. Setting speed to 163.83 rad/sec");
      }

      speedLSB = speed & 0x007F;
      speedMSB = (speed >> 7) & 0x007F;

      // make sure both accel and decel are defined
      if (accel !== undefined && decel !== undefined) {
        // the stepper interface expects decimal expressed an an integer
        accel = Math.floor(accel.toFixed(2) * 100);
        decel = Math.floor(decel.toFixed(2) * 100);

        accelLSB = accel & 0x007F;
        accelMSB = (accel >> 7) & 0x007F;

        decelLSB = decel & 0x007F;
        decelMSB = (decel >> 7) & 0x007F;

        this._board.sendSysex(STEPPER, [STEP,
          this._id,
          direction,
          steps[0],
          steps[1],
          steps[2],
          speedLSB,
          speedMSB,
          accelLSB,
          accelMSB,
          decelLSB,
          decelMSB
        ]);
      } else {
        // don't send accel and decel values
        this._board.sendSysex(STEPPER, [STEP,
          this._id,
          direction,
          steps[0],
          steps[1],
          steps[2],
          speedLSB,
          speedMSB
        ]);
      }
    },

    /**
     * Listen for stepping complete event
     *
     * @private
     * @method onSysExMessage
     */
    onSysExMessage: function(event) {
      var message = event.message;

      if (message[0] !== STEPPER) {
        return;
      } else if (message[1] !== this._id) {
        return;
      } else {
        this.dispatchEvent(new Event(Event.COMPLETE));
      }
    },

    /**
     * [read-only] The id of the Stepper object instance. Each stepper motor
     * is given a unique id upon initialization.
     * @property id
     * @type Number
     */
    get id() {
      return this._id;
    },

    /* implement EventDispatcher */

    /**
     * @param {String} type The event type
     * @param {Function} listener The function to be called when the event is fired
     */
    addEventListener: function(type, listener) {
      this._evtDispatcher.addEventListener(type, listener);
    },

    /**
     * @param {String} type The event type
     * @param {Function} listener The function to be called when the event is fired
     */
    removeEventListener: function(type, listener) {
      this._evtDispatcher.removeEventListener(type, listener);
    },

    /**
     * @param {String} type The event type
     * return {boolean} True is listener exists for this type, false if not.
     */
    hasEventListener: function(type) {
      return this._evtDispatcher.hasEventListener(type);
    },

    /**
     * @param {Event} type The Event object
     * @param {Object} optionalParams Optional parameters to assign to the event object.
     * return {boolean} True if dispatch is successful, false if not.
     */
    dispatchEvent: function(event, optionalParams) {
      return this._evtDispatcher.dispatchEvent(event, optionalParams);
    }

  };

  /**
   * @property Stepper.CLOCKWISE
   * @static
   */
  Stepper.CLOCKWISE = 0;
  /**
   * @property Stepper.COUNTER_CLOCKWISE
   * @static
   */
  Stepper.COUNTER_CLOCKWISE = 1;
  /**
   * Uses 1 microsecond delay between steps
   * @property Stepper.DRIVER
   * @static
   */
  Stepper.DRIVER = 0x01;
  /**
   * Uses 2 microsecond delay between steps
   * @property Stepper.DRIVER_HIGH_CURRENT
   * @static
   */
  Stepper.DRIVER_HIGH_CURRENT = 0x11;
  /**
   * @property Stepper.TWO_WIRE
   * @static
   */
  Stepper.TWO_WIRE = 0x02;
  /**
   * @property Stepper.FOUR_WIRE
   * @static
   */
  Stepper.FOUR_WIRE = 0x04;

  return Stepper;

})();