Week 10. Mechanical design & Machine Design

Group assignment:

Collaboratively design a part of a machine that performs a specific task.

  • Week 10 Group Assignment: Machine Building
  • Introduction of this Machine

       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.

    My Contribution to the Machine

    1. Purchase the part of the Matieials

    Description of image Description of image

    2. Assemble It

    Assemble the machine with all my group members.

    Description of image Description of image Description of image Description of image Description of image

    The belt was not long enough, so I went to buy a new belt.

    Description of image

    The final Assemble!

    Description of image

    3. Embedded Programming

    Original Code

    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");
            }
          

    Analysis of the Original Arduino Code

    The code begins with the inclusion of the "Servo.h" library and the definition of several constants and variables. The `SPU` constant is meticulously calculated based on specific parameters such as the number of teeth on the pulley, the microstep setting, and the belt's tooth size. This calculation is crucial for accurately determining the step count per unit movement. A `Servo` object is instantiated, and pins for different motor control and other functionalities like enabling are declared. These pins will be used to send signals for controlling the connected hardware components. The `setup` function is where the initial configuration of the Arduino board takes place. The servo is attached to a specific pin, serial communication is initiated at a given baud rate, and the direction and output modes of the motor pins are set. Additionally, various event handlers are registered for different operations like handling light control, motor activation/deactivation, and movement towards the origin. The `loop` function mainly delegates the processing to the `readSerial` function. `readSerial` is responsible for reading incoming serial data. It processes the received bytes, decodes the messages, parses the payloads, and triggers the appropriate event handlers based on the message content. Functions like `onLight`, `motorsOn`, `motorsOff`, `moveTowardsOrigin`, and `setOrigin` handle specific operations based on the payloads they receive. These functions control the LED, enable or disable the motors, and update the position information. The code further includes functions related to communication protocols like COBS encoding and decoding. It also has functions for converting byte arrays to float and integer values and converting byte arrays to strings. The `goTo` function is a key component for controlling the movement of the motors. It calculates the target distances for each motor, determines the direction based on the target values, and implements a loop to control the motor steps until the target positions are reached. The `go` and `moveServo` functions handle specific movement instructions for the motors and the servo based on the received payloads. Overall, this code represents a comprehensive system for precise control and communication in an Arduino-based setup, involving motor movements, serial data handling, and event-driven operations to manage various functionalities.

    Apply to our own code

    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!

      KK Rocks!