CesareBrizio
Published © CC BY

Arduino+Stepper microscope Z-stage + keypress emulation

Automation of a microscope Z-stage by Arduino + stepper 28BYJ48, controlled by serial dialogue, with emulated keypress after each movement.

IntermediateProtip4 hours1,414
Arduino+Stepper microscope Z-stage + keypress emulation

Things used in this project

Hardware components

28BYJ48 stepper motor
×1
X113647 Stepper Motor Driver
×1
Pololu Universal Aluminum Mounting Hub for 5mm Shaft
×1
UNO R4 Minima
Arduino UNO R4 Minima
×1
L-plate with holes for shaft and for the fixing eyelets of the stepper
×1
Cap for 1/2 inch plumbing pipes
×1
Male/male 1/2 inch pipe connection
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Drill / Driver, Cordless
Drill / Driver, Cordless
drill bits for metal, 3 mm, 5 mm and 7 mm (or similar)
3mm male to create the radial threads in the mounting sleeve

Story

Read more

Schematics

Connection between Arduino and stepper motor

The involved pins may vary, check the source code!

Code

Microscope Z-Stage knob plus Keyboard Emulation controlled by serial dialogue

Arduino
This is the program that lets your Arduino UNO R4 Minima control the stepper and the keyboard
/*
 
Stepper-based microscope Z-stage

Circuit and comments: 
See http://www.cesarebrizio.it/Arduino/Z-Stage-Keypress.html
Circuit is as illustrated here:
https://www.tdegypt.com/wp-content/uploads/2017/08/stepper-motor-wiring.png
the only exception being that the sketch uses digital outputs 4 - 5 - 6 - 7
while the Fritzing diagram uses digital outputs 8 - 9 - 10 - 11
 
*/

/*-----( Import needed libraries )-----*/
#include <AccelStepper.h>
#include <Keyboard.h>  // Needed only if keyboard stroke is to be sent (REQUIRES Arduino UNO R4 Minima)

/*-----( Declare Constants and Pin Numbers )-----*/
/* NEVER PUT ; AFTER A #define statement!!!! */
// motor pins
#define motorPin1 4           // Blue   - 28BYJ-48 pin 1
#define motorPin2 5           // Pink   - 28BYJ-48 pin 2
#define motorPin3 6           // Yellow - 28BYJ-48 pin 3
#define motorPin4 7           // Orange - 28BYJ-48 pin 4 \
                              // Red    - 28BYJ-48 pin 5 (VCC) \
                              // Blue   - 28BYJ-48 pin GND
#define STEPS_PER_TURN 2048   // number of steps in 360°
#define STEPS_PER_FOUR 22     // number of steps in 4°
#define STEPS_PER_FIVE 28     // number of steps in 5°
#define STEPS_PER_TEN 57      // number of steps in 10°
#define STEPS_PER_TWENTY 114  // number of steps in 20°
#define STEPS_PER_FORTY 228   // number of steps in 40°

int motorSpeed = 500;   // High speeds (800 and above) may cause erratic behavior in 28BYJ-48
int motorAccel = 400;   // As above: better avoiding extreme accelerations
int myPos = 0;          // will be used to define a starting point for 360° rotations
int LeftTurnUp = 0;     // Couple of flags to determine rotation direction
int RightTurnDown = 0;  // Couple of flags to determine rotation direction
int Continuous = 2;     // used below to discriminate single rotation commands
// Continuous will be set to 1 or 0 only when a valid command character will be received
//int incomingByte = 0;  // for incoming serial data
int STEPS_TO_DO = 0;  // to allocate the number of steps needed to perform the required rotation

int RotationsRequired = 0;           // How many rotations of the desired type are required?
int RotationsDone = 0;               // How many rotations of the desired type were completed?
int RotationsRemaining = 0;          // How many rotations remain to do?
int delay_between_rotations = 2000;  //milliseconds of delay between consecutive rotations

bool active = false;  // is the stepper active?

/*-----( Objects for stepper control )-----*/
// Set up the stepper as 4 wire bipolar on pin 4,5,6,7
// NOTE: The sequence 1-3-2-4 is required for proper sequencing of 28BYJ48
AccelStepper stepper(4, motorPin1, motorPin3, motorPin2, motorPin4);


void setup() {
  Serial.begin(9600);
  stepper.setMinPulseWidth(20);  // Advisable setting to avoid that pulses from Arduino are too quick to be decoded
  stepper.setMaxSpeed(motorSpeed);
  stepper.setSpeed(motorSpeed);
  stepper.setAcceleration(motorAccel);
  // the following two lines reset "step zero" to the current position
  stepper.setCurrentPosition(stepper.currentPosition());
  stepper.runToPosition();
  // The following is required to initialize the communication with the keyboard
  Keyboard.begin();
  // I should ask the user what he wants to do
  askUser();
}

void loop() {
  stepper.run();

  // ==================================================
  // Let's check whether:
  // A) the stepper is active;
  // B) the stepper is still running.
  // ==================================================
  if (active && (stepper.distanceToGo() == 0)) {
    //Serial.println("stepper.distanceToGo() = 0");
    // the rotation was completed
    // Send F1 to the keyboard
    Keyboard.press(KEY_F1);
    delay(100);
    Keyboard.releaseAll();
    // Delay before the next rotation
    delay(delay_between_rotations);
    Serial.print("Sent F1 Keystroke and waited ");
    Serial.print(delay_between_rotations);
    Serial.println(" milliseconds.");
    // Increase the count of rotations done
    RotationsDone = RotationsDone + 1;
    // Check how many rotations remain to do
    RotationsRemaining = RotationsRequired - RotationsDone;
    if (RotationsRemaining > 0) {
      Serial.print("Starting rotation number ");
      Serial.print(RotationsDone + 1);
      Serial.print(" of ");
      Serial.println(RotationsRequired);
      // reset current position
      myPos = stepper.currentPosition();
      // start a new rotation of the same kind as the last one
      if (LeftTurnUp == 1)  //left turn
      {
        stepper.moveTo(myPos + STEPS_TO_DO);  // number of  steps in 5, 10, 20 or 360°
      }
      if (RightTurnDown == 1)  //right turn
      {
        stepper.moveTo(myPos - STEPS_TO_DO);  // number of  steps in 5, 10, 20 or 360°
      }

      // I'm activating the stepper and I should
      // begin checking whether it has completed
      // its required rotations
      active = true;
    } else {
      // I'm inactivating the stepper and I should
      // stop checking whether it has completed
      // its required rotations
      active = false;
      // I should ask the user what he wants to do
      askUser();
    }
  }
}

void askUser() {

  // Delay to ensure that the serial port has the time to initialize correctly
  delay(delay_between_rotations);
  // ================================================================
  // The user is asked for A NUMBER OF REQUIRED ROTATIONS
  // AND THE KIND OF ROTATIONS REQUIRED
  // ================================================================

  Serial.println("===============================");
  Serial.println("How many rotations are desired?");

  while (Serial.available() == 0) {
  }

  RotationsRequired = Serial.parseInt();
  while (Serial.available()) Serial.read();  // Empty the seerial buffer from any remaining character

  RotationsDone = 0;                       // For now, no rotations has been completed
  RotationsRemaining = RotationsRequired;  // For now, no rotations has been completed

  Serial.print("You required ");
  Serial.print(RotationsRequired);
  Serial.println(" rotations.");
  Serial.println("   ");
  Serial.println("Which kind of rotation is required ?");
  Serial.println("Available commands:");
  Serial.println("====> 360° Rotation");
  Serial.println("o = 360° clockwise rotation");
  Serial.println("O = 360° counter-clockwise rotation");
  Serial.println("====> Rotation by small increments");
  Serial.println("k = rotate 4° clockwise");
  Serial.println("K = rotate 4° counter-clockwise");
  Serial.println("f = rotate 5° clockwise");
  Serial.println("F = rotate 5° counter-clockwise");
  Serial.println("t = rotate 10° clockwise");
  Serial.println("T = rotate 10° counter-clockwise");
  Serial.println("w = rotate 20° clockwise");
  Serial.println("W = rotate 20° counter-clockwise");
  Serial.println("q = rotate 40° clockwise");
  Serial.println("Q = rotate 40° counter-clockwise");


  while (Serial.available() == 0) {
  }

  String incomingByte = Serial.readString();
  while (Serial.available()) Serial.read();  // Empty the seerial buffer from any remaining character


  Serial.print("Received ");
  Serial.println(incomingByte);


  if (incomingByte == "o") {
    Serial.println("received «o» - activating single clockwise rotation");
    // The following couple of flags determines rotation direction
    LeftTurnUp = 0;
    RightTurnDown = 1;
    STEPS_TO_DO = STEPS_PER_TURN;
  }

  if (incomingByte == "O") {
    Serial.println("received «O» - activating single counter-clockwise rotation");
    // The following couple of flags determines rotation direction
    RightTurnDown = 0;
    LeftTurnUp = 1;
    STEPS_TO_DO = STEPS_PER_TURN;
  }

  if (incomingByte == "k") {
    Serial.println("received «k» - activating 4° clockwise rotation");
    // The following couple of flags determines rotation direction
    LeftTurnUp = 0;
    RightTurnDown = 1;
    STEPS_TO_DO = STEPS_PER_FOUR;
  }

  if (incomingByte == "K") {
    Serial.println("received «K» - activating 4° counter-clockwise rotation");
    // The following couple of flags determines rotation direction
    RightTurnDown = 0;
    LeftTurnUp = 1;
    STEPS_TO_DO = STEPS_PER_FOUR;
  }

  if (incomingByte == "f") {
    Serial.println("received «f» - activating 5° clockwise rotation");
    // The following couple of flags determines rotation direction
    LeftTurnUp = 0;
    RightTurnDown = 1;
    STEPS_TO_DO = STEPS_PER_FIVE;
  }

  if (incomingByte == "F") {
    Serial.println("received «F» - activating 5° counter-clockwise rotation");
    // The following couple of flags determines rotation direction
    RightTurnDown = 0;
    LeftTurnUp = 1;
    STEPS_TO_DO = STEPS_PER_FIVE;
  }

  if (incomingByte == "t") {
    Serial.println("received «t» - activating 10° clockwise rotation");
    // The following couple of flags determines rotation direction
    LeftTurnUp = 0;
    RightTurnDown = 1;
    STEPS_TO_DO = STEPS_PER_TEN;
  }

  if (incomingByte == "T") {
    Serial.println("received «T» - activating 10° counter-clockwise rotation");
    // The following couple of flags determines rotation direction
    RightTurnDown = 0;
    LeftTurnUp = 1;
    STEPS_TO_DO = STEPS_PER_TEN;
  }

  if (incomingByte == "w") {
    Serial.println("received «w» - activating 20° clockwise rotation");
    // The following couple of flags determines rotation direction
    LeftTurnUp = 0;
    RightTurnDown = 1;
    STEPS_TO_DO = STEPS_PER_TWENTY;
  }

  if (incomingByte == "W") {
    Serial.println("received «W» - activating 20° counter-clockwise rotation");
    // The following couple of flags determines rotation direction
    RightTurnDown = 0;
    LeftTurnUp = 1;
    STEPS_TO_DO = STEPS_PER_TWENTY;
  }

  if (incomingByte == "q") {
    Serial.println("received «q» - activating 40° clockwise rotation");
    // The following couple of flags determines rotation direction
    LeftTurnUp = 0;
    RightTurnDown = 1;
    STEPS_TO_DO = STEPS_PER_FORTY;
  }

  if (incomingByte == "Q") {
    Serial.println("received «Q» - activating 40° counter-clockwise rotation");
    // The following couple of flags determines rotation direction
    RightTurnDown = 0;
    LeftTurnUp = 1;
    STEPS_TO_DO = STEPS_PER_FORTY;
  }

  // The two lines that follow allow to send commands in any sequence:
  // before execution, a quick stop is performed
  stepper.stop();                                         // Stop as fast as possible: sets new target
  stepper.runToPosition();                                // Now stopped after quickstop
  stepper.setCurrentPosition(stepper.currentPosition());  // Set step 0 "here"
  stepper.setSpeed(motorSpeed);                           // Previous commands have reset the speed

  Serial.print("Starting rotation number 1 of ");
  Serial.println(RotationsRequired);

  // I store my current position as starting point of the rotation
  myPos = stepper.currentPosition();

  if (LeftTurnUp == 1)  //left turn
  {
    stepper.moveTo(myPos + STEPS_TO_DO);  // number of  steps in 5, 10, 20 or 360°
  }

  if (RightTurnDown == 1)  //right turn
  {
    stepper.moveTo(myPos - STEPS_TO_DO);  // number of  steps in 5, 10, 20 or 360°
  }

  // I'm activating the stepper and I should
  // begin checking whether it has completed
  // its required rotations
  active = true;
}

Credits

CesareBrizio

CesareBrizio

1 project • 12 followers
Retired, formerly software developer and database administrator

Comments