Software

To use the controller motorshield sketch you'll want to make sure you're using the latest version of the Arduino IDE (1.6.5 at the time of this writing).

If you're totally new to Arduino take a little time to go through some introductory tutorials like how to make a LED blink.  This will help you understand how to use the IDE, load a sketch, and upload code.

Next you'll need to make sure the libraries used by the sketch are installed.  With the latest Arduino IDE you can use its library manager to easily install libraries, or check out this guide on how to manually install a library.  You'll want to install the following libraries:

  • Adafruit Motor Shield V2 Library
  • Adafruit Adafruit BluefruitLE nRF51

Search for the libraries in the library manager and they should be easy to find and install:

If you already have one or more of these libraries installed then make sure to update it to the latest version.

Uploading Sketches

To load the sketch make sure the libraries above are installed, and the Arduino is connected to the computer through a USB cable.  Make sure under the Tools -> Board menu the Arduino Uno is selected, and under the Tools -> Port menu the serial port for the Arduino is selected (it should say Arduino Uno).  

Then press the upload button or click the Sketch -> Upload item to send the code to the Arduino.  Woo-hoo the sketch should be running!

Advanced Controller Motor Shield Sketch

This sketch features several functions for moving the slider. You can set speed and choose linear or easing movements.

// Smartphone- or tablet-activated timelapse camera slider.
// Uses the following Adafruit parts:
//
// Arduino Uno R3 or similar (adafruit.com/product/50 or #2488)
// Bluefruit LE SPI Friend (#2633)
// Motor/Stepper/Servo Shield v2 (#1438)
// NEMA-17 Stepper motor (#324)
// Miscellaneous hardware and 3D-printed parts; see guide for full list.
//
// Needs Adafruit_BluefruitLE_nRF51 and Adafruit_MotorShield libs:
// github.com/adafruit
// Use Adafruit Bluefruit LE app for iOS or Android to control timing.
// Buttons 1-4 select interpolation mode (linear vs. various ease in/out).
// Up/down select speed.  Left = home.  Right = start slider.

#include <SPI.h>
#include <Wire.h>
#include <SoftwareSerial.h>
#include <Adafruit_BluefruitLE_SPI.h>
#include <Adafruit_MotorShield.h>

#define LED 13 // Built-in LED on pin 13

// Bluefruit config --------------------------------------------------------
Adafruit_BluefruitLE_SPI ble(8, 7, 6); // CS, IRQ, RST pins

// Stepper motor config ----------------------------------------------------
#define STEPPER_STEPS 1100 // Length of slider
#define STEPPER_RPM     20

Adafruit_MotorShield AFMS = Adafruit_MotorShield();

// Stepper motor w/200 steps/revolution (1.8 degree) on port 2 (M3 & M4)
Adafruit_StepperMotor *motor = AFMS.getStepper(200, 2);

// Global stuff ------------------------------------------------------------

// Four motion interpolation modes are supported (corresponding to buttons
// 1-4 in the Bluefruit LE app for iOS & Android): linear is constant speed,
// ease_in_out accelerates/decelerates at the ends (fastest in middle),
// ease_in accelerates (fastest at end) and ease_out decelerates (fastest
// at beginning):
typedef enum interp { linear, ease_in_out, ease_in, ease_out };

// A few different time periods are supported, but not a whole lot.  Since
// there's no display connected, we just blink the onboard LED to indicate
// which setting is active, and there's only so many speed changes one can
// reliably 'read' this way.  Feel free to edit, but keep in mind there are
// upper and lower limits to the time interval -- the stepper can only move
// so fast and if you try to press beyond that it'll just move linearly
// regardless of the selected interpolation mode, and the micros() function
// rolls over about every 70 minutes, so the duration can't exceed that.
// Blink rate is similarly constrained due to BLE comm; about 2 Hz max.
struct {
  uint32_t totalTime;    // Total duration of movement along slider
  uint32_t LEDflashTime; // LED blink rate indicates selected speed
} speed[] = {
   5 * 60 * 1000000L, 1000000L / 2, //  5 min slide,   2 Hz blink
  10 * 60 * 1000000L, 1000000L,     // 10 min slide,   1 Hz blink
  20 * 60 * 1000000L, 1000000L * 2, // 20 min slide, 1/2 Hz blink
  60 * 60 * 1000000L, 1000000L * 3  // 60 min slide, 1/3 Hz blink
};
#define N_SPEEDS (sizeof(speed) / sizeof(speed[0]))

uint32_t startTime = 0;      // micros() value when slider movement started
interp   mode      = linear; // Selected interpolation mode
uint8_t  speedIdx  = 0;      // Selected speed index (0 to N_SPEEDS-1)
boolean  moving    = false;  // True if motor active & interpolating

// Setup function - runs once at startup -----------------------------------

void setup(void) {
  Serial.begin(57600);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, HIGH);       // LED steady on during init
  if(!ble.begin(false)) for(;;); // BLE init error? LED on forever
  ble.echo(false);
  AFMS.begin();
  motor->setSpeed(STEPPER_RPM);
  motor->release();              // Allow manual positioning at start
  digitalWrite(LED, LOW);        // LED off = successful init
}

// Loop function - repeats forever -----------------------------------------

void loop(void) {
  static uint16_t prevSliderPos = 0; // Slider position on prior call
  static uint8_t  led;               // Blink status on/off

  if(moving) { // Motor currently engaged?
    float p, t = (float)(micros() - startTime) /
                 (float)speed[speedIdx].totalTime; // Time 0.0 - 1.0

    switch(mode) { // Cubic easing functions from http://gizma.com/easing/
     case linear:
      p = (float)STEPPER_STEPS * t;
      break;
     case ease_in_out:
      t *= 2.0;
      if(t < 1.0) {
        p  = (float)STEPPER_STEPS * 0.5 * t * t * t;
      } else {
        t -= 2.0;
        p  = (float)STEPPER_STEPS * 0.5 * (t * t * t + 2.0);
      }
      break;
     case ease_in:
      p = (float)STEPPER_STEPS * t * t * t;
      break;
     case ease_out:
      t -= 1.0;
      p  = (float)STEPPER_STEPS * (t * t * t + 1.0);
      break;
    }

    // Move stepper to new position (if required)
    uint16_t sliderPos = (int)(p + 0.5);
    if(sliderPos > prevSliderPos) {
      // Microstepping is used to reduce vibration
      motor->step(sliderPos - prevSliderPos, FORWARD, MICROSTEP);
      prevSliderPos = sliderPos;
      if(sliderPos >= STEPPER_STEPS) { // At end of motion?
        motor->release();              // Turn off motor
        moving = false;
      }
    }
  } else {
    if((micros() - startTime) > (speed[speedIdx].LEDflashTime / 2)) {
      digitalWrite(LED, led++ & 1);
      startTime = micros();
    }
  }

  // Process any pending Bluetooth input
  if(ble.isConnected()) {
    ble.println(F("AT+BLEUARTRX"));     // Request string from BLE module
    ble.readline();                     // Read outcome
    if(!strncmp(ble.buffer, "!B", 2) && // Controller button command
       checkCRC(255-'!'-'B', 4)      && // Verify checksum
       (ble.buffer[3] == '1')) {        // Button press? 1=press 0=release
      switch(ble.buffer[2]) {
       case '1': if(!moving) mode = linear;      break;
       case '2': if(!moving) mode = ease_in_out; break;
       case '3': if(!moving) mode = ease_in;     break;
       case '4': if(!moving) mode = ease_out;    break;
       case '5': // Up (faster)
        if((!moving) && speedIdx) speedIdx--;
        break;
       case '6': // Down (slower)
        if((!moving) && (speedIdx < (N_SPEEDS-1))) speedIdx++;
        break;
       case '7': // Left (home)
        digitalWrite(LED, LOW);
        motor->step(prevSliderPos, BACKWARD, DOUBLE);
        motor->release();
        prevSliderPos = 0;
        moving        = false;
        break;
       case '8': // Right (start/pause)
        if(!moving) { // Don't double-start
          digitalWrite(LED, LOW);
          moving    = true;
          startTime = micros();
        }
        break;
      }
    }
  }
}

boolean checkCRC(uint8_t sum, uint8_t CRCindex) {
  for(uint8_t i=2; i<CRCindex; i++) sum -= (uint8_t)ble.buffer[i];
  return ((uint8_t)ble.buffer[CRCindex] == sum);
}

Simple Controller Motor Shield Sketch

This sketch do not feature any easing functions. It moves linearly and do not have any periodic stops. This sketch is ideally for heavier cameras that want to use double steps. The controls are easier to modify and understand.

This guide was first published on Jan 02, 2018. It was last updated on Oct 17, 2018. This page (Software) was last updated on Jan 07, 2018.