Week 10: Machine Design
This week's assignment began with a collaborative brainstorming session to decide on the machine we would design and build, integrating mechanisms, actuation, automation, and application.
Among the ideas deliberated were CNC machines, each equipped with different toolheads. While initially drawn to the concept of a CNC with a spindle, we also considered alternative types of toolheads. Subsequently, the laser module emerged as a viable alternative. However, concerns regarding our constrained timeline prompted a shift in focus towards a CNC pyrographer.
The rationale behind selecting a pyrographer stemmed from its perceived manageability within the given timeframe. Moreover, we discerned a promising niche for such a device within the startups, particularly within the realm of small-scale ice cream and pastry enterprises. Envisioned as a tool to automate the branding process, our pyrographer would facilitate the efficient engraving of company logos onto spoons and popsicles, thereby alleviating the manual burden associated with this task.
For this step we decided to use a Arduino Uno Board with a CNC Shield V3. We used Drv8825 silent drivers for this task running at 32 microsteps with 12V supply. Also, we used a 12V Power Supply and a DC-DC Buck Converter to step down the voltage from 12V to 5V for the Arduino Uno. We used an SSR Relay to PWM the 220V Pyrographer for enhanced temperature control. All the electronics where place in a control box and wired properly.
For wiring the control box, pinout for the Arduino Uno R3 and the Cnc Shield V3 where needed.
For this step, I decided to utilize Arduino Libraries to make the motor control much smoother. I used the AccelStepper library for smooth acceleration and deceleration of the stepper motor by sending pulses to the Step, Dir, and Enable pins of the stepper driver. I needed to read the AccelStepper documentation to know how to input the correct values for achieving the perfect speed and acceleration for this kind of machine.The final code was a blend of examples generated by ChatGPT and my own adjustments. ChatGPT proved to be very useful in offering examples and code for a variety of applications.
Stepper Motor Control: The code employs the AccelStepper library to manage two stepper motors: one for the R-axis (rotational movement) and one for the Z-axis (linear movement). This library facilitates seamless acceleration and deceleration, thereby refining the precision of motor control.
G-code Interpretation: The code processes G-code commands received via the serial monitor ('G1' for linear movement, 'G28' for homing, etc.), making the machine compatible with standard CNC control commands. This functionality expands the versatility of the machine, allowing users to execute complex engraving tasks with ease.
Stock Positioning: The code enables the selection of different stock positions for the rotary axis, represented by positions 1 to 6, using the 'T' command. This functionality allows users to conveniently switch between various engraving stages.
Current Position Monitoring: The code includes a command ('P') to provide real-time feedback on the Z-axis's current position in millimeters, enabling users to monitor the machine's state.
Run Command: The 'Run' command automates a sequence of engraving operations for six different positions using a predefined Z-axis position ('ENGRAVE_Z') and hop distance ('ZHOP'). This automation enhances the efficiency of the engraving process, streamlining workflow.
Limit Switches: Incorporating a limit switch (Z_LIMIT_SWITCH_PIN) on the Z-axis prevents the axis from surpassing its maximum travel limits. During the Z-axis homing procedure, this limit switch accurately determines the zero position.
Homing Functionality: Both the R-axis and the Z-axis feature homing procedures to ensure the machine initiates from a known position, thereby augmenting accuracy and repeatability.
Motor Disabling: Activating the 'M18' command disables the stepper motors by setting the ENABLE_PIN high, enhancing safety during periods of inactivity.
Error Handling: The code features error messages to manage scenarios such as unrecognized commands ('Error: Unrecognized command.') and attempts to execute actions without homing the respective axes ('Error: Z-axis not homed.', 'Error: R-axis not homed.'), promoting operational efficiency and user-friendliness.
Safety against Exceeding Maximum Axis Length: A safety feature has been implemented to prevent crashes caused by movement commands that exceed the maximum length of the axis. If a movement command sent via serial input exceeds the maximum length of the axis, the movement will not be executed, ensuring the safety of the machine and preventing potential crashes.
//PyroEngraver Code//
//Written by: Jorge Suarez de Freitas + ChatGPT//
#include <AccelStepper.h>
//R-AXIS PINS
#define DIR_R_PIN 6
#define STEP_R_PIN 3
//Z-AXIS PINS
#define DIR_Z_PIN 5
#define STEP_Z_PIN 2
//ENABLE STEPPERS PIN
#define ENABLE_PIN 8
//R-AXIS HALL MAGNETIC SENSOR PIN
#define HALL_PIN 10
//Z-AXIS LIMIT SWITCH
#define Z_LIMIT_SWITCH_PIN 9
//STEPSxREV/(R-AXIS) & STEPSxMM/(Z-AXIS)
#define STEPS_PER_REVOLUTION 6400 // 200 STEPS *32 MICROSTEPS
#define STEPS_PER_MM_Z 800 // ( 200STEPS * 32 MICROSTEPS ) / 8MM PITCH T8 LEADSCREW
//DEFINE ACCELSTEPPER OBJECTS
AccelStepper stepperR(AccelStepper::DRIVER, STEP_R_PIN, DIR_R_PIN);
AccelStepper stepperZ(AccelStepper::DRIVER, STEP_Z_PIN, DIR_Z_PIN);
//HOMING SAFETY CHECK
bool RAxisHomed = false; // FLAG TO INDICATE IF THE R AXIS IS HOMED
bool ZAxisHomed = false; // FLAG TO INDICATE IF THE Z AXIS IS HOMED
// SET LINEAR MOVE SPEED AND ACCELERATION FOR THE Z-AXIS & SET ROTATING SPEED AND ACCELERATION FOR THE R-AXIS
int stepperZsetSpeed = 5000; // MAXIMUM SPEED IN STEPS PER SECOND
int stepperZsetAcceleration = 10000; // ACCELERATION IN STEPS PER SECOND SQUARED
int stepperRsetSpeed = 5000;
int stepperRsetAcceleration = 10000;
// SET HOMING SPEED AND ACCELERATION FOR THE Z-AXIS AND R-AXIS
int stepperZsetHSpeed = 1700; // MAXIMUM SPEED IN STEPS PER SECOND
int stepperZsetHAcceleration = 8000; // ACCELERATION IN STEPS PER SECOND SQUARED
int stepperRsetHSpeed = 500;
int stepperRsetHAcceleration = 600;
//CURRENT R-AXIS POSITION NUMBER
int positionNumber = 0;
//Z ENGRAVING POSITION
int ENGRAVE_Z= 14;
int ZHOP= 8;
// MAXIMUN LIMITS FOR Z-AXIS SAFETY CHECK
#define Z_MAX_LIMIT 35 // MAXIMUM LIMIT FOR THE Z-AXIS MOVEMENT
void setup() {
// SET CONTROL PINS AS OUTPUTS
pinMode(DIR_Z_PIN, OUTPUT);
pinMode(STEP_Z_PIN, OUTPUT);
pinMode(ENABLE_PIN, OUTPUT);
// SET HALL SENSOR DIGITAL PIN AS INPUT
pinMode(HALL_PIN, INPUT);
// SET LIMIT SWITCH PIN AS INPUT WITH PULL-UP
pinMode(Z_LIMIT_SWITCH_PIN, INPUT_PULLUP);
// DISABLE THE MOTORS AT STARTUP
digitalWrite(ENABLE_PIN, HIGH);
// SET MAXIMUM SPEED AND ACCELERATION FOR THE Z-AXIS AND R-AXIS
stepperZ.setMaxSpeed(3000); // MAXIMUM SPEED IN STEPS PER SECOND
stepperZ.setAcceleration(5000); // ACCELERATION IN STEPS PER SECOND SQUARED
stepperR.setMaxSpeed(2000);
stepperR.setAcceleration(500);
//INITIALIZE SERIAL COMUNICATION
Serial.begin(115200);
while (!Serial) {} // Espera a que se conecte el puerto serie
}
//FUNTIONS
void homeR() {
// HOMING SPEED AND ACCELERATION
stepperR.setMaxSpeed(stepperRsetHSpeed);
stepperR.setAcceleration(stepperRsetHAcceleration);
// CHECK IF THE SENSOR IS ALREADY ACTIVATED
if (digitalRead(HALL_PIN) == LOW) {
// MOVE AWAY FROM THE SENSOR UNTIL DEACTIVATES
stepperR.moveTo(1000);
while (digitalRead(HALL_PIN) == LOW) {
stepperR.run();
delay(0.05); // SMALL DELAY TO ALLOW THE MOTOR TO MOVE
}
}
// MOVE MOTOR TWARDS THE HOME POSITION (CLOCKWISE)
stepperR.moveTo(1000);
// CONTINUE MOVING UNTIL THE MAGNETIC SENSOR IS TRIGGERED
while (stepperR.distanceToGo() != 0) {
stepperR.run();
delay(0.05); // SMALL DELAY TO ALLOW THE MOTOR TO MOVE
if (digitalRead(HALL_PIN) == LOW) {
stepperR.setCurrentPosition(0); // SET CURRENT POSITION AS 0(HOME)
stepperR.stop(); // STOP THE MOTOR
break;
}
}
// MOVE THE MOTOR FOR THE HALL SENSOR TO BE CENTERED WITH THE MAGNET
stepperR.setMaxSpeed(stepperRsetHSpeed);
stepperR.setAcceleration(stepperRsetHAcceleration);
stepperR.moveTo(140);
delay(100);
stepperR.runToPosition(); // WAIT UNTIL THE MOTOR REACHES THE TARGET POSITION
stepperR.setCurrentPosition(0); // SET THE CURRENT POSITION AS THE HOME POSITION
// SET THE FLAG TO INDICATE THAT THE R AXIS IS HOMED
RAxisHomed = true;
}
void selectTool(String input) {
positionNumber = input.substring(1).toInt(); // EXTRACT THE POSITION NUMBER FROM THE INPUT
// CALCULATE THE MOTOR STEP POSITION FOR THE SELECTED TOOL
int toolPosition = map(positionNumber, 1, 7, 0, STEPS_PER_REVOLUTION);
// CALCULATE THE CURRENT MOTOR POSITION
int currentPosition = stepperR.currentPosition();
// CALCULATE THE DISTANCE TO MOVE CLOCKWISE AND COUNTERCLOCKWISE
int distanceClockwise = (toolPosition - currentPosition + STEPS_PER_REVOLUTION) % STEPS_PER_REVOLUTION;
int distanceCounterclockwise = (currentPosition - toolPosition + STEPS_PER_REVOLUTION) % STEPS_PER_REVOLUTION;
// DETERMINE THE DIRECTION TO MOVE THE MOTOR
if (distanceClockwise <= distanceCounterclockwise) {
stepperR.move(distanceClockwise);
} else {
stepperR.move(-distanceCounterclockwise);
}
stepperR.setMaxSpeed(stepperRsetSpeed);
stepperR.setAcceleration(stepperRsetAcceleration);
// RUN THE MOTOR TO THE SELECTED POSITION
stepperR.runToPosition();
}
void executeLinearMove(String input) {
// PARSE THE G1 COMMAND TO EXTRACT THE TARGET POSITION FOR THE Z-AXIS MOVE
double zPosition = 0.0;
int index = input.indexOf('Z');
if (index != -1) {
String zValueString = input.substring(index + 1);
zPosition = zValueString.toDouble();
}
// CHECK IF THE TARGET POSITION EXCEEDS THE LIMITS
if (zPosition < 0 || zPosition > Z_MAX_LIMIT) {
Serial.println("Error: Movement exceeds limits.");
return;
}
// MOVE THE Z-AXIS TO THE SPECIFIED POSITION
moveAxis('Z', zPosition, stepperZsetSpeed, stepperZsetAcceleration);
}
void moveAxis(char axis, double position_mm, float speed, float accel) {
AccelStepper* stepper;
double steps_per_mm;
delay(100); // SMALL DELAY TO AVOID SERIAL COMMUNICATION ISSUES
// DETERMINE WHICH MOTOR TO CONTROL AND ITS STEPS PER MILLIMETER
if (axis == 'Z') {
stepper = &stepperZ;
steps_per_mm = STEPS_PER_MM_Z;
} else {
return; // EXIT THE METHOD IF THE AXIS LETTER IS INVALID
}
// Convert position from millimeters to steps
double target_steps = position_mm * steps_per_mm;
// Move the motor to the target position
digitalWrite(ENABLE_PIN, LOW); // ENABLE STEPPERS
delay(100); // SHORT DELAY FOR STABILITY
// Set maximum speed and acceleration
if (axis == 'Z') {
stepper->setMaxSpeed(speed);
stepper->setAcceleration(accel);
}
// Move the motor to the target position
stepper->moveTo(target_steps);
// Wait for the motor to reach the target position
while (stepper->distanceToGo() != 0) {
stepper->run();
}
}
void homeZ() {
// Set acceleration for homing
stepperZ.setAcceleration(stepperZsetAcceleration); // ACCELERATION IN STEPS PER SECOND SQUARED
digitalWrite(ENABLE_PIN, LOW); // Enable the motor
delay(100); // Short delay for stability
// PERFORM HOMING TOWARDS THE MINIMUM LIMIT UNTIL THE LIMIT SWITCH IS ACTIVATED
while (digitalRead(Z_LIMIT_SWITCH_PIN)) {
stepperZ.move(-100); // MOVE TOWARDS THE MINIMUM LIMIT
stepperZ.setSpeed(-stepperZsetSpeed); // MAX SPEED IN STEPS PER SECOND
stepperZ.setAcceleration(-stepperZsetAcceleration); // ACCELERATION IN STEPS PER SECOND SQUARED
stepperZ.runSpeed(); // EXECUTE THE MOVEMENT
delay(0.05); // SMALL DELAY TO ALLOW THE MOTOR TO MOVE
}
// STOP THE MOTOR AND SET IT TO THE MINIMUM LIMIT POSITION
stepperZ.stop();
// MOVE AWAY FROM THE LIMITSWITCH
stepperZ.move(800); // Move an additional 20 steps
stepperZ.runToPosition(); // Wait until the motor reaches the target position
stepperZ.setCurrentPosition(0);
// UPDATE HOMING STATUS
ZAxisHomed = true;
}
void loop() {
// INPUT G-CODE WITH SERIAL MONITOR FOR COMMUNICATION
if (Serial.available() > 0) {
String input = Serial.readStringUntil('\n');
input.trim(); // REMOVE LEADING AND TRAILING WHITESPACE
processCommand(input);
}
}
void processCommand(String input) {
// Print the received command for debugging
Serial.print("Received command: ");
Serial.println(input);
// Execute corresponding G-code command
if (input.startsWith("G28")) {
// HOME Z-AXIS
homeZ();
homeR();
Serial.println("ok"); // Acknowledge successful execution
} else if (input.startsWith("G1")) {
// Execute linear movement on the Z-axis if Z-axis is homed
if (ZAxisHomed) {
executeLinearMove(input);
Serial.println("ok"); // Acknowledge successful execution
} else {
Serial.println("Error: Z-axis not homed.");
}
} else if (input.startsWith("T")) {
// Select a tool if the R axis is homed
int toolPosition = input.substring(1).toInt();
if (toolPosition >= 1 && toolPosition <= 6) {
if (RAxisHomed) {
selectTool(input);
Serial.println("ok"); // Acknowledge successful execution
} else {
Serial.println("Error: R-axis not homed.");
}
} else {
Serial.println("Error: Choose an existing position (1-6)");
}
} else if (input.startsWith("M18")) {
// Disable steppers
digitalWrite(ENABLE_PIN, HIGH);
ZAxisHomed = false;
RAxisHomed = false;
Serial.println("ok"); // Acknowledge successful execution
} else if (input.startsWith("P")) {
// Output current position in millimeters
double position_mm = stepperZ.currentPosition() / (double)STEPS_PER_MM_Z;
Serial.print("Current position Z-Axis (mm): ");
Serial.println(position_mm, 2);
Serial.print("Current stock position R-Axis: ");
Serial.println(positionNumber);
Serial.println("ok"); // Acknowledge successful execution
} else if (input.startsWith("Run")) {
// Make 1 Run for Engrave in 6 positions
homeZ();
homeR();
selectTool("T1");
moveAxis('Z', ENGRAVE_Z, stepperZsetSpeed, stepperZsetAcceleration);
moveAxis('Z', (ENGRAVE_Z-ZHOP), stepperZsetSpeed, stepperZsetAcceleration);
selectTool("T2");
moveAxis('Z', ENGRAVE_Z, stepperZsetSpeed, stepperZsetAcceleration);
moveAxis('Z', (ENGRAVE_Z-ZHOP), stepperZsetSpeed, stepperZsetAcceleration);
selectTool("T3");
moveAxis('Z', ENGRAVE_Z, stepperZsetSpeed, stepperZsetAcceleration);
moveAxis('Z', (ENGRAVE_Z-ZHOP), stepperZsetSpeed, stepperZsetAcceleration);
selectTool("T4");
moveAxis('Z', ENGRAVE_Z, stepperZsetSpeed, stepperZsetAcceleration);
moveAxis('Z', (ENGRAVE_Z-ZHOP), stepperZsetSpeed, stepperZsetAcceleration);
selectTool("T5");
moveAxis('Z', ENGRAVE_Z, stepperZsetSpeed, stepperZsetAcceleration);
moveAxis('Z', (ENGRAVE_Z-ZHOP), stepperZsetSpeed, stepperZsetAcceleration);
selectTool("T6");
moveAxis('Z', ENGRAVE_Z, stepperZsetSpeed, stepperZsetAcceleration);
moveAxis('Z', (2), stepperZsetSpeed, stepperZsetAcceleration);
digitalWrite(ENABLE_PIN, HIGH);
} else {
// Handle unrecognized commands
Serial.println("Error: Unrecognized command.");
}
}