Project Management
Fab academy

Final Project

Text-to-Braille Printer: Coquiduino Braille Printer

Slide:

Slide


Video:


BILL OF MATERIALS (BOM):

BOM


Drafting, 3D Designing and Prototyping:

Stepper Motor Positioning:

In this step, I focused on positioning the stepper motors in the optimal configuration for a compact and efficient design. Initially, I faced challenges in finding the right positions for a compact layout. My first attempt was to position the solenoid facing down, with the stepper motor shaft of the X-axis facing up. However, this design proved problematic for a few reasons:

DRAFT

DRAFT


First Ideas 3D Modeling:

INVENTOR
INVENTOR
INVENTOR
INVENTOR


Final Distribution 3D Modeling: PROTOTYPING MOTOR MOUNT-IRSENSOR MOUNT

In Autodesk Inventor Professional, I used the Derive tool to import the stepper motor STEP file for accurate referencing. Then, I utilized the Direct tool to move and rotate the solids until I achieved the desired positioning. This process ensured precise alignment and integration of the stepper motors within the overall design.

INVENTOR
INVENTOR


The IR sensor used is from my Input Devices Assignment. The sensor features a 6mm gap between the top and bottom LEDs for the dual purpose of detecting the X carriage and paper feeding into the system.

INVENTOR
INVENTOR


3D-Printed Prototype for Test Fitting Components:

Prototyping

The IR sensor didn't fit correctly because the pinheads were interfering during installation. I modified the 3D model to adjust the measurements. Additionally, the mounting hole for an M3 screw broke, so it was reinforced for greater reliability and longevity.

Prototyping
Prototyping
Prototyping

I also tested the alignment of the Gates GT2 6mm belts to ensure smooth operation. Proper alignment is crucial; if the belts are not aligned correctly, they can rub against other components. Ensuring the correct distances will contribute to a well-built X carriage assembly.

Prototyping


FINAL ITERATION:

INVENTOR
INVENTOR


FINAL MOTOR MOUNT ASSEMBLY:

INVENTOR


Final Distribution 3D Modeling: PROTOTYPING X-CARRIAGE, PAPER FEEDER MECHANISM AND LATERAL MOUNT:

Final Distribution 3D Modeling: X-Carriage (Solenoid Mount)

For the X carriage assembly, I wanted the IR sensor to serve dual functions: acting as a limit switch for the solenoid axis and as a paper feeder detector. To achieve this, I added a flap to the carriage that blocks the light of the IR LED when homing to the maximum position. Once homed, it moves to the zero position, clearing the IR sensor for the paper feeder procedure. This design choice simplified the machine, making it more affordable and easier to build.

INVENTOR
Solenoid Reference Model:
INVENTOR
Carriage 3D Design:
INVENTOR


For the belt system, I used GT2 6mm Gates belts and attached them to the carriage with a piece of filament. This approach significantly reduced the complexity of the assembly and made it super compact. It worked well because there is no load on the toolhead, so the belt attachment system performed as expected, providing a reliable and efficient solution.

INVENTOR

INVENTOR

3D-Printed Prototype for Test Fitting Components:
Prototyping

Prototyping

Prototyping

Final X-Carriage Design:
INVENTOR

INVENTOR



Final Distribution 3D Modeling: Paper Feeder Mechanism:

The paper feeder mechanism is responsible for precisely and efficiently feeding an A4 sheet of paper to ensure consistent results during operation. The roller feeds the paper through two guides on both sides to maintain alignment. These guides must be smooth and have gentle transitions to prevent paper jamming.

To achieve smooth and precise paper feeding, I added spring rollers above the feeder rod. This creates additional friction between the paper and the feeder rod. Initially, I planned to use metal springs for the spring roller mechanism. However, I opted for the natural flexibility of the thermoplastics used in 3D printing to act as springs and hold the rollers in place. This decision was made to reduce the number of components, resulting in a cheaper, simpler, and more effective design.

3D-Printed Prototype for Test Fitting Components:

For this 3D model, I decided to print only the sections that interface with other components. This approach promotes responsible material management by minimizing plastic usage without compromising the workflow. Printing just the necessary sections allows for quicker iterations and reduces plastic consumption during the prototyping stage.

INVENTOR

IR Sensor Dual Funtion:


3D-Printed Prototype for Test Fitting Components:

Testing Spring Rollers:

Prototyping


Testing Spring Rollers Paper Feeding:

At this stage, I was testing the friction between the feeder rod and the spring rollers, as well as the alignment of the IR sensor with the paper guides.

Prototyping

Prototyping


Testing Paper Guide and Spring Rollers:

Prototyping


Final Paper Feeder Design:

PaperFeederFinal



Final Distribution 3D Modeling: Lateral Mount with Paper Guide:

LateralMount

3D-Printed Prototype for Test Fitting Components:
LateralMount

Final Lateral Mount Design:
LateralMount


Braille Embosser Plate

3D Designing:

Embosser

To minimize the flexing of the acrylic during operation, I added a rib for reinforcement. This rib was fabricated using 3D printing.

Embosser


Laser Cutting:


CNC Machining:


Mechanical Assembly Completed!!!!!


Electronics:

Electronics Prototyping and Testing:

Milling Roland Acrylic Base:

For machining large PCBs, it is essential to ensure that the base on which the PCB is mounted is perfectly flat. The milling process only removes 0.1 mm of material, so any variations in the flatness of the PCB will result in poor quality. To achieve the necessary flatness, I decided to mill the entire acrylic base of the Roland machine using an 8 mm flat end mill. After milling, I sanded the surface to remove any machining residue. With the base now perfectly flat, it is ready for PCB milling.

Always remember to use personal protective equipment (PPE) such as safety glasses and a mask during these procedures to protect yourself from dust and debris.

Prototyping
Prototyping


PCB Milling:

Prototyping


Soldering:

For this prototype board, I encountered issues with improper placement and connections. The drivers were inverted, so I had to mirror them since all the through-hole components will be placed on the back of the PCB. For testing purposes, I soldered them on the top of the PCB, which was very uncomfortable. Additionally, I made a poor connection with the power jack, necessitating the soldering of a piece of wire to jump the 12V input.

Prototyping

Prototyping


Testing:

Testing

Testing

Testing


Applying Corrections:

Testing


Final Iteration: Coquiduino CNC Braille PCB


Schematic:

Schematic


PCB Design:

PCBDesign


PCB Fabrication:

PCBMilling


Soldering:

Soldering


Protective Coating:

Coating

Coating


Final Result:

COQUIDUINOCNCBRAILLE

COQUIDUINOCNCBRAILLE


Electronics Completed!!!!!


Integrating Electronics into Mechanical Design:

For the integration of the electronics, I decided to place them near the stepper motors in a strategic position. This placement ensures good accessibility to the power input for 12V and the USB-C port of the Seeeduino Xiao for serial communication. My aim was to make the design as compact as possible, and I believe I achieved that goal. The electronics are positioned to facilitate easy access while maintaining a clean and efficient layout, contributing to the overall compactness and functionality of the system.

INVENTOR

For that purpose, I imported the motor mount .ipt file and used it as a derived part to ensure compatibility with already designed components. This allowed me to take into consideration the existing design elements while positioning the new components. I then positioned the PCB 3D model using the direct tool until I found an appropriate position.

INVENTOR


Final PCB Position:

INVENTOR

INVENTOR


Electronics Box Design:

For the integration of the electronics, I designed an electronics box with several key features to ensure functionality and ease of use. The box includes mounting holes and spacers for securely holding the PCB in place, ensuring stability and proper positioning. Additionally, the front part of the box features a removable lid, allowing for easy access to the electronics and stepper motors. This design facilitates maintenance and adjustments without needing to disassemble the entire system. The box also includes feet on the bottom to provide stability and prevent sliding or tipping over, ensuring that the electronics remain secure during operation.

These features contribute to a compact and efficient design, providing good accessibility to critical components while maintaining a clean and organized layout. This approach achieves a streamlined and practical integration of the electronics into the overall system.

INVENTOR

INVENTOR

INVENTOR

INVENTOR

INVENTOR

INVENTOR


Integration of the Mechanical Design and Electronics Box:

The reason why I use derived parts while designing is to ensure that my designs fit correctly and do not have any interference. This approach allows me to consider existing components and their dimensions accurately. Above, you can see some photos of the fully integrated mechanical and electronics box assemblies.

INVENTOR

INVENTOR

INVENTOR

INVENTOR


Back Plate:

3D Design:

BackPlate


Paper Guide:

3D Design:

PaperGuide

PaperGuide



Final 3D Assembly: Coquiduino Braille Printer

FinalAssembly



Final Project Assembly:

PCB Mounting:

FinalAssembly

FinalAssembly

FinalAssembly


Wiring:

Wiring

Wiring



Final Project Completed!!!

3D Render:

Wiring


Final Product:

Wiring



Programming: Arduino IDE

Arduino Code:

I developed a program that enables G-code control via serial communication. The G-code command G28 is designated for homing the axes. When this command is received, the X-carriage first homes to its maximum position and then moves to position 0. Once the X-carriage is homed and positioned at 0, the paper feeder activates. It stops when the paper is detected, then feeds the paper to the Y-axis 0 position. When all axes are in position, the machine enables linear movements using the G1 command: G1 X for the X carriage and G1 Y for the paper feeder. The solenoid activates with the P command.

The machine also includes safety features. It sends serial messages indicating if the axes are not homed or if movement exceeds limits. Additionally, it reports when the axes are homed and ready after a G28 command.

For the embossing process, I created macros that can be called via serial commands to execute specific movements and punches. The idea is for the master PC to send G-code commands through the serial interface. The PC processes the text, translates it using a matrix, and generates the corresponding G-code for the Coquiduino Braille printer to emboss.

Currently, the machine prints sample procedures pre-loaded into the Seeeduino Xiao. However, the machine is fully functional and capable of receiving G-code commands from a PC. I used ChatGPT to assist me in creating parts of the code, which I then integrated and adapted to meet my requirements and preferences.


    // Program Code: Coquiduino CNC Braille Printer//
    
    // Written by: Jorge Suarez de Freitas + ChatGPT//
    
    #include <queue>
    #include <AccelStepper.h>

    // Define pins for Motor 1 (Linear Axis with Homing)
    #define STEP_PIN1 4
    #define DIR_PIN1 3

    // Define pins for Motor 2 (Paper Feeder)
    #define STEP_PIN2 2
    #define DIR_PIN2 1

    // Shared enable pin
    #define ENABLE_PIN 0
    #define LIMIT_SWITCH_PIN 10
    #define SOLENOID_PIN 9

    // Motor configuration
    AccelStepper stepper1(AccelStepper::DRIVER, STEP_PIN1, DIR_PIN1);
    AccelStepper stepper2(AccelStepper::DRIVER, STEP_PIN2, DIR_PIN2);

    // Variables for homing
    bool homingDoneX = false;
    bool homingDoneY = false;

    // Configuration for paper feeder advancement
    const float rollerDiameter = 8.0; // Roller diameter in mm
    const float rollerPerimeter = 3.14159 * rollerDiameter; // Roller perimeter
    const float stepsPerRevolution = 200.0;
    const float microsteps = 16.0;
    const float distancePerMicrostep = rollerPerimeter / (stepsPerRevolution * microsteps);

    // Configuration for linear axis advancement
    const float distancePerMicrostepX = 40.0 / (stepsPerRevolution * microsteps); // 40 mm per revolution with 20 teeth and GT2 (2 mm per tooth)
    const float maxXPosition = 184.8; // Maximum position for X-axis in mm
    const float maxYPosition = 1000.0; // Maximum position for X-axis in mm

    // Define a struct to hold move commands
    struct MoveCommand {
      char axis;
      float position;
      bool solenoid;
    };

    // Queue to hold move commands
    std::queue<MoveCommand> commandQueue;

    void setup() {
      // Initialize the limit switch and solenoid
      pinMode(LIMIT_SWITCH_PIN, INPUT);
      pinMode(SOLENOID_PIN, OUTPUT);

      // Initialize motors
      stepper1.setMaxSpeed(1000);
      stepper1.setAcceleration(20000);
      stepper2.setMaxSpeed(500);
      stepper2.setAcceleration(10000);

      // Enable motors
      pinMode(ENABLE_PIN, OUTPUT);
      digitalWrite(ENABLE_PIN, LOW);
      delay(100);
      Serial.begin(115200); // Set baud rate to match the one used in the Arduino IDE
      Serial.println("System ready. Sending to home...");
    }

    void loop() {
      // Read G-code commands from serial port
      if (Serial.available()) {
        String command = Serial.readStringUntil('\n');
        processGCode(command);    
      }

      // Process the command queue
      processCommandQueue();
    }

    // Function to perform homing of both axes
    void homeAllAxes() {
      homeLinearAxis();
      while (!homingDoneX) {
        delay(10); // Wait until homing of the linear axis is complete
      }
      MoveXAxis0();
      homePaperFeeder();

    }

    void MoveXAxis0() {
      while (stepper1.distanceToGo() != 0) {
        stepper1.run();
      }
      stepper1.moveTo(0); // Move to position 0
      while (stepper1.distanceToGo() != 0) {
        stepper1.run();
      }
    }

    // Function to perform homing of the linear axis
    void homeLinearAxis() {
      stepper1.setMaxSpeed(8000); // Set maximum speed
      stepper1.setSpeed(10000); // Set custom homing speed
      while (digitalRead(LIMIT_SWITCH_PIN) == LOW) { // Adjusted limit switch condition
        stepper1.moveTo(stepper1.currentPosition() + 20); // Move in the positive direction
        stepper1.run();
      }
      stepper1.setCurrentPosition(maxXPosition / distancePerMicrostepX); // Set position to 200 mm
      homingDoneX = true;
      Serial.println("X-Axis homing completed.");
    }

    // Function to perform homing of the paper feeder
    void homePaperFeeder() {
      stepper2.setMaxSpeed(4000); // Set maximum speed
      stepper2.setSpeed(3000); // Set custom homing speed
      while (digitalRead(LIMIT_SWITCH_PIN) == LOW) { // Adjusted limit switch condition
        stepper2.moveTo(stepper2.currentPosition() + 20); // Move in the positive direction
        stepper2.run();
      }
      // Move an additional 10mm in the positive direction
      stepper2.move(10 / distancePerMicrostep); // Convert 10mm to steps
      stepper2.runToPosition(); // Wait for the movement to complete
      delay(200);
       stepper2.move(20 / distancePerMicrostep); // Convert 10mm to steps
      stepper2.runToPosition(); // Wait for the movement to complete

      // Set position to 0 mm
      stepper2.setCurrentPosition(0);

      homingDoneY = true;
      Serial.println("Paper feeder homing completed.");
    }


    // Function to process G-code commands
    void processGCode(String command) {
      if (command.startsWith("G28")) {
        // Command to perform homing
        homeAllAxes();
      } else if (command.startsWith("G1")) {
        // Move linear axis
        int indexX = command.indexOf('X');
        int indexY = command.indexOf('Y');

        if (indexX >= 0) {
          float posX = command.substring(indexX + 1).toFloat();
          // Check if movement is within safe bounds for X-axis
         if (posX >= 0 && posX <= maxXPosition) {
            commandQueue.push({'X', posX, false});
          } else {
            Serial.println("Error: Movement out of bounds for X-axis.");
          }
        }

        // Move paper feeder
        if (indexY >= 0) {
          float posY = command.substring(indexY + 1).toFloat();
          // Check if movement is within safe bounds for Y-axis
          if (posX >= 0 && posX <= maxYPosition) {
            commandQueue.push({'Y', posY, false});
          } else {
            Serial.println("Error: Movement out of bounds for Y-axis.");
          }
        }
      } else if (command.startsWith("P")) { 
        commandQueue.push({'P', 0, true});
      } else if (command.startsWith("HELLOWORLD")) {
        HELLOWORLD();
      } else if (command.startsWith("NEIL")) {
        NEIL();
      } else if (command.startsWith("FAB")) {
        THISISFABACADEMY2024();
      }
    }



    void HELLOWORLD() {
      // Define starting positions
      int startX = 0;
      int startY = 0;


      // Print "H"
      commandQueue.push({'X', startX, false});
      commandQueue.push({'Y', startY, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX, false});
      commandQueue.push({'Y', startY + 5, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY + 5, false});
      commandQueue.push({'P', 0, true});

      startX += 10;

      // Print "E" 
      commandQueue.push({'X', startX, false});
      commandQueue.push({'Y', startY, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY + 5, false});
      commandQueue.push({'P', 0, true});

      startX += 10;

      // Print "L"
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY + 5, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY + 10, false});
      commandQueue.push({'P', 0, true});

      startX += 10;

      // Print "L"
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY + 5, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY + 10, false});
      commandQueue.push({'P', 0, true});

      startX += 10;

      // Print "O" 
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 10, false});
      commandQueue.push({'Y', startY + 5, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY + 10, false});
      commandQueue.push({'P', 0, true});

      startX += 15;

      // Print "W" 
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY + 5, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 10, false});
      commandQueue.push({'Y', startY, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 10, false});
      commandQueue.push({'Y', startY + 5, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 10, false});
      commandQueue.push({'Y', startY + 10, false});
      commandQueue.push({'P', 0, true});

      startX += 10;

      // Print "O"
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 10, false});
      commandQueue.push({'Y', startY + 5, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY + 10, false});
      commandQueue.push({'P', 0, true});

      startX += 10;

      // Print "R" 
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY + 5, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY + 10, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 10, false});
      commandQueue.push({'Y', startY + 5, false});
      commandQueue.push({'P', 0, true});

      startX += 10;

      // Print "L"
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY + 5, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY + 10, false});
      commandQueue.push({'P', 0, true});

      startX += 10;

      // Print "D" 
      commandQueue.push({'X', startX + 5, false});
      commandQueue.push({'Y', startY, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 10, false});
      commandQueue.push({'Y', startY, false});
      commandQueue.push({'P', 0, true});
      commandQueue.push({'X', startX + 10, false});
      commandQueue.push({'Y', startY + 5, false});
      commandQueue.push({'P', 0, true});

      NEIL();

      THISISFABACADEMY2024();



      // Move pen away from drawing area
      commandQueue.push({'Y', 300, false});
      commandQueue.push({'X', 180, false});
      }



    void NEIL() {
        // Define starting positions
        int startX = 0;
        int startY = 30;

        // Print "H" 
        commandQueue.push({'X', startX + 0, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 0, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});

        startX += 10;

        // Print "I" 
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});

        startX += 15;

        // Print "N" 
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 10, false});
        commandQueue.push({'P', 0, true});

        startX += 10;

        // Print "E" 
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});

        startX += 10;

        // Print "I" (2nd and 3rd dots)
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY , false});
        commandQueue.push({'P', 0, true});

        startX += 10;

        // Print "L" 
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 10, false});
        commandQueue.push({'P', 0, true});





        // // Move pen away from drawing area
        // commandQueue.push({'Y', startY + 300, false});
        // commandQueue.push({'X', startX + 180, false});
        }


    void THISISFABACADEMY2024() {
        // Define starting positions
        int startX = 0;
        int startY = 60;

        // Print "T" 
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY , false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 10, false});
        commandQueue.push({'P', 0, true});
        startX += 10;

        // Print "H"
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        startX += 10;

        // Print "I" (2nd and 4th dots)
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        startX += 10;

        // Print "S" (1st, 2nd, 4th, and 5th dots)
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 10, false});
        commandQueue.push({'P', 0, true});
        startX += 10;

        // Print space (move pen to the next character position)
        startX += 10;

        // Print "I" (2nd and 4th dots)
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        startX += 10;

        // Print "S" (1st, 2nd, 4th, and 5th dots)
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 10, false});
        commandQueue.push({'P', 0, true});

        // Move to the next line
        startX = 0;
        startY += 20;

        // Print "F" (1st, 2nd, 3rd, and 5th dots)
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX , false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        startX += 10;

        // Print "A" (1st dot)
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        startX += 10;

        // Print "B" (1st and 2nd dots)
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        startX += 10;

        // Print space (move pen to the next character position)
        startX += 10;

        // Print "A" 
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        startX += 10;

        // Print "C" 
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        startX += 10;

        // Print "A" 
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        startX += 10;

        // Print "D" (1st, 4th, and 5th dots)
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        startX += 10;

        // Print "E" (1st and 5th dots)
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        startX += 10;

        // Print "M" (1st, 3rd, and 4th dots)
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 10, false});
        commandQueue.push({'P', 0, true});
        startX += 10;

        // Print "Y" 
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY + 5, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX, false});
        commandQueue.push({'Y', startY + 10, false});
        commandQueue.push({'P', 0, true});
        commandQueue.push({'X', startX + 5, false});
        commandQueue.push({'Y', startY + 10, false});
        commandQueue.push({'P', 0, true});

        // // Move pen away from drawing area
        // commandQueue.push({'Y', 300, false});
        // commandQueue.push({'X', 180, false});
      }



    // Function to move the linear axis

    void moveLinearAxis(float position) {
      if (homingDoneX) {
        long steps = position / distancePerMicrostepX; // Convert mm to steps
        stepper1.moveTo(steps);
      } else {
        Serial.println("Error: Linear axis homing not completed.");
      }
    }

    // Function to move the paper feeder
    void movePaperFeeder(float distance) {
      if (homingDoneY) {
        long steps = distance / distancePerMicrostep; // Convert mm to steps
        stepper2.moveTo(steps);
      } else {
        Serial.println("Error: Paper feeder homing not completed.");
      }
    }

    // Function to activate the solenoid
    void activateSolenoid() {
      digitalWrite(SOLENOID_PIN, HIGH);
      delay(300);
      digitalWrite(SOLENOID_PIN, LOW);
      delay(100);
    }

    // Function to process the command queue
    void processCommandQueue() {
      if (!commandQueue.empty()) {
        MoveCommand cmd = commandQueue.front();

        if (stepper1.distanceToGo() == 0 && stepper2.distanceToGo() == 0) {
          if (cmd.axis == 'X') {
            moveLinearAxis(cmd.position);
          } else if (cmd.axis == 'Y') {
            movePaperFeeder(cmd.position);
          } else if (cmd.solenoid) {
            activateSolenoid();
          }
          commandQueue.pop();
        }
      }

      // Run steppers
      stepper1.run();
      stepper2.run();
    }
        
        



Final Project Final Result:

Coquiduino Braille Printer by Jorge Alejandro Suarez de Freitas Ferrari is licensed under CC BY-NC 4.0


Hope you enjoyed my Final Project Assigment! Keep on creating!

You can download the PCB Design-STL-SVG-DXF files HERE!

You can download the Arduino Code (.ino) HERE!

You can download the Inventor Professional complete assembly and files HERE!