ATTENZIONE!!! RICHIEDE ARDUINO UNO R4 MINIMA O ALTRA VERSIONE COMPATIBILE CON LIBRERIA KEYBOARD |
ATTENTION!!! REQUIRES ARDUINO UNO R4 MINIMA OR OTHER VERSION COMPATIBLE WITH KEYBOARD LIBRARY |
VEDI Miglioramento del sistema di accoppiamento meccanico |
SEE Improvement of the mechanical coupling system |
VEDI Microscope Z-Stage knob controlled by serial dialogue |
SEE Microscope Z-Stage knob controlled by serial dialogue |
Due importanti sviluppiLa precedente realizzazione (Microscope Z-Stage knob controlled by serial dialogue) è stata radicalmente migliorata in diversi aspetti, che la portano a essere un'alternativa a una slitta micrometrica motorizzata: rispetto a prodotti come quelli MJKZZ, che però non sempre sono integrabili con applicazzioni di cattura immagini basate su personal computer.
|
Two important developmentsThe previous creation (Microscope Z-Stage knob controlled by serial dialogue)has been radically improved in various aspects, which lead it to be an alternative to a motorized micrometric slide: compared to products such as the MJKZZ ones, which however cannot always be integrated with personal computer-based image capture applications.
|
/*
Stepper-based microscope Z-stage control, with image capture keypress emulation
Version 29 November 2024
-----------------------------------------------
Last interventions:
- added vibration-dumping delay after emulated F1 keypress
- added slow-capture delay to avoid that rotation is activated during the image capture phase
- added micro-delay between counter-clockwise rotation to avoid saturating the com port with too many messages
- improved reminder of available kinds of rotation
-----------------------------------------------
Previous versions of this program were only aimed at elevating or depressing the automated Z-stage
on user demand, one time, by rotating the knob for a nuber of degrees based on a keypress.
Each movement required a separate keypress.
This version was drastically improved on two fronts:
1) besides the desired amount of knob rotation in degrees, the user is also asked how many times he
wants to perform the rotation;
2) after each separate rotation, a simulated keypress is sent to the keyboard buffer, to activate image capture.
IN THE EXAMPLE, THE INTERACTION WITH DELTAPIX INSIGHT REQUIRES TO SIMULATE THE F1 KEYPRESS TO PERFORM AN IMAGE
CAPTURE AFTER EACH ROTATION.
It's important to remember that, immediately after input of the user choices in the Arduino IDE,
the focus should be transfered to the image capture window (a mouse click suffices), so that the
simulated keypresses are processed by the image capture application, otherwise the simulated
keypresses would be sent to the Arduino IDE itself.
After all the required rotations are performed, the Arduino IDE window should be put in focus again
for successive image capture cycles or for ending the program.
-----------------------------------------------
Circuit and comments:
See https://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
// ================================
// ONLY FOR CLOCKWISE MOVEMENTS!!!
// ================================
// Do the following:
// 1) Apply delay after last capture
// 2) Send F1 to the keyboard
// 3) Apply delay before next capture
if (RightTurnDown == 1) //right turn
{
// Delay after the last rotation
// >>> AFTER MOVING !!! <<<
// (allows vibrations dumping)
delay(delay_between_rotations);
Serial.print("Vibration dumping: waited ");
Serial.print(delay_between_rotations);
Serial.println(" milliseconds before sending F1.");
Keyboard.press(KEY_F1);
delay(100);
Keyboard.releaseAll();
// Delay before the next rotation
// >>> BEFORE MOVING !!! <<<
// (allows slow capture to take place)
delay(delay_between_rotations);
Serial.print("Sent F1 Keystroke and waited ");
Serial.print(delay_between_rotations);
Serial.println(" milliseconds to avoid motion during slow capture .");
} else {
delay(200);
Serial.println("Delayed 200 millisecond to enhance serial communication.");
}
// 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 serial 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("-------------------");
Serial.println("|KINDS OF ROTATION|");
Serial.println("| CW=Clockwise |");
Serial.println("| CCW=Counter-CW |");
Serial.println("|-----------------|");
Serial.println("| CW | Degs.| CCW |");
Serial.println("|-----------------|");
Serial.println("| k | 4° | K |");
Serial.println("| f | 5° | F |");
Serial.println("| t | 10° | T |");
Serial.println("| w | 20° | W |");
Serial.println("| q | 40° | Q |");
Serial.println("| o | 360° | O |");
Serial.println("-------------------");
Serial.println(" ");
Serial.println("Which kind of rotation is required (CCW rotations don't include keypress nor delays)?");
while (Serial.available() == 0) {
}
String incomingByte = Serial.readString();
while (Serial.available()) Serial.read(); // Empty the serial 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;
}
// Send a warning message
// BUT ONLY FOR CLOCKWISE MOVEMENTS
if (RightTurnDown == 1) //right turn
{
Serial.println("*******************************************************");
Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
Serial.println(" I M P O R T A N T !!! !!! !!!");
Serial.println(" Click IMMEDIATELY on the open program window that");
Serial.println(" should receive the image capture keystroke, otherwise");
Serial.println(" the keystroke will be sent to the Arduino IDE.");
Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
Serial.println("*******************************************************");
}
// 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;
}
❦