Collaboratively design a part of a machine that performs a specific task.
   Our assignment this week is to replicate a Bolt machine made by a previous MIT student, which can be used to draw very beautiful patterns by machine. This is a super awesome project and I'm really looking forward to its effect. Its principle is similar to that of a CNC. Both make the machine operate to draw patterns by running gcode.
Assemble the machine with all my group members.
The belt was not long enough, so I went to buy a new belt.
The final Assemble!
I have tried to read the orignal code and abstract the part that could apply to our case.
The original code is as below:
#include <Servo.h>
#define SPU 80 // 20 teeth pulley, 16 microstep, 2mm a tooth belt, 16*200 microsteps per turn == (16*200)/(20*2) == 80
#define PIN_SERVO D6
Servo servo;
typedef uint8_t (*CallbackFunction)(uint8_t*, int, uint8_t*);
struct EventCallback {
String event;
CallbackFunction callback;
};
float pos[] = {0, 0};
const int motor1StepPin = D10;
const int motor1DirPin = D9;
const int motor2StepPin = D8;
const int motor2DirPin = D7;
const int enablePin = D1;
void setup() {
servo.attach(PIN_SERVO);
Serial.begin(9600);
// on("light", onLight);
on("go", go);
on("servo", moveServo);
on("motorsOn", motorsOn);
on("motorsOff", motorsOff);
on("moveTowardsOrigin", moveTowardsOrigin);
on("setOrigin", setOrigin);
pinMode(motor1StepPin, OUTPUT);
pinMode(motor1DirPin, OUTPUT);
pinMode(motor2StepPin, OUTPUT);
pinMode(motor2DirPin, OUTPUT);
pinMode(enablePin, OUTPUT); // enable pin
// pinMode(PIN_LED, OUTPUT);
// startupIndicator(); // once startup is finished, indicate to user
}
void loop() {
readSerial();
}
void startupIndicator() {
// flash LED to indicate board is initialized
for (int i = 0; i < 3; i++) {
digitalWrite(PIN_LED, 0);
delay(200);
digitalWrite(PIN_LED, 1);
delay(200);
}
}
uint8_t onLight(uint8_t* payload, int length, uint8_t* reply) {
uint8_t value = payload[0];
// Serial.println("light it up");
digitalWrite(PIN_LED, value);
return 0;
}
uint8_t motorsOn(uint8_t* payload, int length, uint8_t* reply) {
digitalWrite(enablePin, 0);
return 0;
}
uint8_t motorsOff(uint8_t* payload, int length, uint8_t* reply) {
digitalWrite(enablePin, 1);
return 0;
}
uint8_t moveTowardsOrigin(uint8_t* payload, int length, uint8_t* reply) {
float x = pos[0];
float y = pos[1];
// this ternary may not be neccesary
goTo(
x + ( x < 0 ? 10 : -10),
y + ( y < 0 ? 10 : -10)
);
return 0;
}
uint8_t setOrigin(uint8_t* payload, int length, uint8_t* reply) {
pos[0] = 0;
pos[1] = 0;
return 0;
}
/* ------------------------------------------------------------ */
int bufferIndex = 0;
uint8_t msgBuffer[100];
void readSerial() {
while (Serial.available() > 0) {
uint8_t incoming = Serial.read();
msgBuffer[bufferIndex] = incoming;
if (incoming != 0) {
bufferIndex++;
continue; // Proceed to the next byte if current byte is not null
}
// Serial.print("RECEIVED: ");
// for (int i = 0; i < bufferIndex; i++) {
// Serial.print(msgBuffer[i]);
// Serial.print(", ");
// }
// Serial.println("DONE");
// Now we have a full message, perform COBS decoding
uint8_t decoded[bufferIndex];
cobs_decode(decoded, msgBuffer, bufferIndex);
// Serial.print("DECODED: ");
// for (int i = 0; i < bufferIndex; i++) {
// Serial.print(decoded[i]);
// Serial.print(", ");
// }
// Serial.println("DONE");
// Parse the decoded message
int i = 0;
uint8_t msgLength = decoded[i];
// Serial.print("MSG-LENGTH: ");
// Serial.println(msgLength);
uint8_t msgArr[msgLength];
i++;
while (i < 1 + msgLength) {
msgArr[i-1] = decoded[i];
i++;
}
// Serial.print("MSGARR: ");
// for (int i = 0; i < msgLength; i++) {
// Serial.print(msgArr[i]);
// Serial.print(", ");
// }
// Serial.println("MSGARR-END");
uint8_t payloadLength = decoded[i];
uint8_t payload[payloadLength];
i++;
while (i < 1 + msgLength + 1 + payloadLength) {
payload[i-1-msgLength-1] = decoded[i];
i++;
}
uint8_t msgCount = decoded[i];
// String msg = String((char*)msgArr);
String msg = byteArrayToString(msgArr, msgLength);
// Serial.println(msg);
// printArray("PAYLOAD", payload, payloadLength);
// Serial.print("MSGCOUNT: ");
// Serial.println(msgCount);
bool triggered = triggerEvent(msg, payload, payloadLength, msgCount);
bufferIndex = 0; // Reset the buffer for the next message
}
}
/* ------------------------------------------------------------ */
const int MAX_EVENTS = 255; // Maximum number of events to store, adjust as needed
EventCallback eventCallbacks[MAX_EVENTS];
int eventCount = 0;
const int REPLY_PAYLOAD_LENGTH = 255;
uint8_t reply[REPLY_PAYLOAD_LENGTH];
void on(String event, CallbackFunction callback) {
if (eventCount < MAX_EVENTS) {
eventCallbacks[eventCount].event = event;
eventCallbacks[eventCount].callback = callback;
eventCount = (eventCount + 1) % MAX_EVENTS;
} else {
// Serial.println("Max number of events reached. Wrapping events.");
}
}
bool triggerEvent(String event, uint8_t* payload, int payloadLength, uint8_t msgCount) {
for (int i = 0; i < eventCount; i++) {
if (eventCallbacks[i].event == event) {
// want to pass payload and payloadLength, need to get response payload
uint8_t reply_length = eventCallbacks[i].callback(payload, payloadLength, reply);
sendAck(msgCount, reply, reply_length);
return true;
}
}
// Serial.println(" No event registered.");
return false;
}
const int arrayLength = 7; // + length;
uint8_t byteArray[arrayLength];
void sendAck(uint8_t msgCount, uint8_t* reply, uint8_t length) {
// Serial.println("SEND ACK");
// int arrayLength = 7; // + length;
// static uint8_t byteArray[arrayLength];
byteArray[0] = 0x03;
byteArray[1] = 0x61;
byteArray[2] = 0x63;
byteArray[3] = 0x6B;
byteArray[4] = 0x00;
byteArray[5] = msgCount;
byteArray[6] = 0x0A;
// byteArray[4] = length;
// for (int i = 0; i < length; i++) {
// byteArray[i+5] = reply[i];
// }
// byteArray[5+length] = msgCount;
// byteArray[6+length] = 0x0A;
// Serial.println(msgCount);
// printArray("ACK", byteArray, 5+length);
Serial.write(byteArray, arrayLength);
// uint8_t byteArrayEncoded[arrayLength + 2]; // +2 for possible COBS overhead
// cobs_encode(byteArrayEncoded, byteArray, arrayLength);
// Serial.write(byteArrayEncoded, arrayLength + 2);
// printArray("ENCODED-ACK", byteArray, 5+length);
}
/* ------------------------------------------------------------ */
void cobs_encode(uint8_t *dst, const uint8_t *src, size_t len) {
size_t read_index = 0;
size_t write_index = 1;
size_t code_index = 0;
uint8_t code = 1;
while (read_index < len) {
if (src[read_index] == 0) {
dst[code_index] = code;
code = 1;
code_index = write_index++;
read_index++;
} else {
dst[write_index++] = src[read_index++];
code++;
if (code == 0xFF) {
dst[code_index] = code;
code = 1;
code_index = write_index++;
}
}
}
dst[code_index] = code;
// Add trailing zero
if (write_index < len + 2) {
dst[write_index] = 0;
}
}
void cobs_decode(uint8_t *dst, const uint8_t *src, size_t len) {
size_t i, j, dst_i = 0;
for (i = 0; i < len;) {
uint8_t code = src[i++];
for (j = 1; j < code && i < len; j++) {
dst[dst_i++] = src[i++];
}
if (code < 0xFF && dst_i < len) {
dst[dst_i++] = 0;
}
}
}
/* ------------------------------------------------------------ */
// TODO: THINK THIS IS BUGGY
void cobs_print(const String& message) {
// Convert the message to a byte array
int length = message.length();
uint8_t byteArray[length + 1]; // +1 for the null terminator
message.getBytes(byteArray, length + 1);
// Prepare the buffer for the encoded message
uint8_t encoded[length + 2]; // +2 for possible COBS overhead
// Perform COBS encoding
cobs_encode(encoded, byteArray, length + 1);
// Send the encoded message
Serial.write(encoded, length + 2); // Write the encoded bytes
}
/* ------------------------------------------------------------ */
float read_float(uint8_t* buffer, int index) {
uint8_t byte0 = buffer[index];
uint8_t byte1 = buffer[index+1];
uint8_t byte2 = buffer[index+2];
uint8_t byte3 = buffer[index+3];
uint8_t byteArray[] = {byte0, byte1, byte2, byte3};
float floatValue;
memcpy(&floatValue, &byteArray, sizeof(floatValue));
return floatValue;
}
int read_int(uint8_t* buffer, int index) {
uint8_t byte0 = buffer[index];
uint8_t byte1 = buffer[index+1];
uint8_t byte2 = buffer[index+2];
uint8_t byte3 = buffer[index+3];
uint8_t byteArray[] = {byte0, byte1, byte2, byte3};
int value;
memcpy(&value, &byteArray, sizeof(value));
return value;
}
String byteArrayToString(byte arr[], int length) {
String result = "";
for (int i = 0; i < length; i++) {
result += (char)arr[i]; // Convert each byte to a character and append it to the string
}
return result;
}
/* ------------------------------------------------------------------------ */
#define EPSILON 0.01
void goTo(float x, float y) {
// Serial.println("START GOTO");
// Set your target distances for each motor (in steps)
float motor1Target = (x + y) - pos[0];
float motor2Target = (y - x) - pos[1];
// Set motor direction based on target values
digitalWrite(motor1DirPin, motor1Target >= 0 ? HIGH : LOW);
digitalWrite(motor2DirPin, motor2Target >= 0 ? HIGH : LOW);
// Calculate the relative speeds and maximum duration for both motors
float maxSteps = max(abs(motor1Target), abs(motor2Target));
float motor1Speed = abs(motor1Target) / maxSteps;
float motor2Speed = abs(motor2Target) / maxSteps;
unsigned long stepDuration = 500; // The time it takes to perform one step in microseconds
unsigned long motor1StepInterval = stepDuration / motor1Speed;
unsigned long motor2StepInterval = stepDuration / motor2Speed;
// Initialize variables for step timing
unsigned long motor1PrevStepTime = 0;
unsigned long motor2PrevStepTime = 0;
float motor1Step = 0;
float motor2Step = 0;
// Loop until both motors reach their target steps
while (abs(motor1Target - motor1Step) > EPSILON || abs(motor2Target - motor2Step) > EPSILON) {
unsigned long currentTime = micros();
// Motor 1
if (abs(motor1Target - motor1Step) > EPSILON && ((currentTime - motor1PrevStepTime) >= motor1StepInterval)) {
digitalWrite(motor1StepPin, HIGH);
delayMicroseconds(1);
digitalWrite(motor1StepPin, LOW);
delayMicroseconds(1);
motor1Step += (motor1Target >= 0 ? 1.0 : -1.0)/SPU;
motor1PrevStepTime = currentTime;
}
// Motor 2
if (abs(motor2Target - motor2Step) > EPSILON && ((currentTime - motor2PrevStepTime) >= motor2StepInterval)) {
digitalWrite(motor2StepPin, HIGH);
delayMicroseconds(1);
digitalWrite(motor2StepPin, LOW);
delayMicroseconds(1);
motor2Step += (motor2Target >= 0 ? 1.0 : -1.0)/SPU;
motor2PrevStepTime = currentTime;
}
}
// Serial.println("END GOTO");
pos[0] += motor1Step;
pos[1] += motor2Step;
}
uint8_t go(uint8_t* payload, int length, uint8_t* reply) {
float x = read_float(payload, 0);
float y = read_float(payload, 4);
goTo(x, y);
return 0;
}
uint8_t moveServo(uint8_t* payload, int length, uint8_t* reply) {
int angle = read_int(payload, 0);
servo.writeMicroseconds(angle);
return 0;
}
/* ------ */
void printArray(String label, uint8_t* arr, int arrSize) {
Serial.print(label);
Serial.print("-BEGIN: ");
for (int i = 0; i < arrSize; i++) {
Serial.print(arr[i]);
Serial.print(", ");
}
Serial.print(label);
Serial.println("-END");
}
After analyzing the original code, it was found that basically retaining the part of motor initialization and only modifying the goTo code would be sufficient for our machine.
The following is our code with complete annotations.
#include <Arduino.h>
// Define the pins for the X-axis stepper motor steps and direction
const int STEP_PIN_X = D10;
const int DIR_PIN_X = D9;
// Define the pins for the Y-axis stepper motor steps and direction
const int STEP_PIN_Y = D8;
const int DIR_PIN_Y = D7;
// Define the number of steps per millimeter for the stepper motors
const float STEPS_PER_MM = 80.0; // Adjust this value based on your stepper motor's steps per millimeter
// Define the length of the line to be drawn in millimeters
const float LINE_LENGTH_MM = 100.0; // Length of the line in millimeters
/**
* The setup function is called once at the beginning of the program.
* It initializes the pins for the stepper motors as outputs.
*/
void setup() {
pinMode( STEP_PIN_X, OUTPUT);
pinMode(DIR_PIN_X, OUTPUT);
pinMode(STEP_PIN_Y, OUTPUT);
pinMode(DIR_PIN_Y, OUTPUT);
}
/**
* The loop function is called repeatedly after the setup function.
* It performs the drawing operations of different lines.
*/
void loop() {
// Draw a line along the X-axis
drawLine(0, 0, LINE_LENGTH_MM, 0);
delay(1000); // Pause for 1 second
// Draw a diagonal line
drawLine(0, 0, LINE_LENGTH_MM, LINE_LENGTH_MM);
delay(1000); // Pause for 1 second
// Move back to the origin
drawLine(LINE_LENGTH_MM, LINE_LENGTH_MM, 0, 0);
delay(1000); // Pause for 1 second before repeating
}
/**
* Function to draw a line from the starting point (x0, y0) to the ending point (x1, y1).
*
* @param x0 The starting X-coordinate in millimeters.
* @param y0 The starting Y-coordinate in millimeters.
* @param x1 The ending X-coordinate in millimeters.
* @param y1 The ending Y-coordinate in millimeters.
*/
void drawLine(float x0, float y0, float x1, float y1) {
// Convert the millimeter coordinates of the starting and ending points to the corresponding number of steps
int x0Steps = round(x0 * STEPS_PER_MM);
// Multiply by the number of steps per millimeter and use the `round` function for rounding to the nearest integer to obtain the number of steps on the X-axis at the starting point
int y0Steps = round(y0 * STEPS_PER_MM);
int x1Steps = round(x1 * STEPS_PER_MM);
int y1Steps = round(y1 * STEPS_PER_MM);
// Calculate the difference in steps in the X and Y directions
int dx = abs(x1Steps - x0Steps);
int dy = abs(y1Steps - y0Steps);
// Determine the moving direction on the X-axis (positive or negative) based on the relationship between the starting and ending points
int sx = (x0Steps < x1Steps)? 1 : -1;
// Similarly, determine the moving direction on the Y-axis
int sy = (y0Steps < y1Steps)? 1 : -1;
// Initialize the error variable
int err = dx - dy;
// Enter the loop until the ending point is reached
while (true) {
// If the ending point has been reached (both on the X and Y axes), exit the loop
if (x0Steps == x1Steps && y0Steps == y1Steps) break;
// Calculate twice the error
int e2 = 2 * err;
// If the error is greater than the negative step difference in the Y direction
if (e2 > -dy) {
err -= dy; // Update the error
x0Steps += sx; // Increase the number of steps on the X-axis (depending on the direction)
digitalWrite(DIR_PIN_X, (sx > 0)? HIGH : LOW); // Set the direction pin on the X-axis
digitalWrite(STEP_PIN_X, HIGH); // Enable the step pin on the X-axis to high level
delayMicroseconds(500); // Short delay
digitalWrite(STEP_PIN_X, LOW); // Enable the step pin on the X-axis to low level
delayMicroseconds(500); // Short delay
}
// If the error is less than the step difference in the X direction
if (e2 < dx) {
err += dx; // Update the error
y0Steps += sy; // Increase the number of steps on the Y-axis (depending on the direction)
digitalWrite(DIR_PIN_Y, (sy > 0)? HIGH : LOW); // Set the direction pin on the Y-axis
digitalWrite(STEP_PIN_Y, HIGH); // Enable the step pin on the Y-axis to high level
delayMicroseconds(500); // Short delay
digitalWrite(STEP_PIN_Y, LOW); // Enable the step pin on the Y-axis to low level
delayMicroseconds(500); // Short delay
}
}
}
Finally It works!