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
INVENTOR
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:

Prototyping
Prototyping


PCB Milling:

Prototyping


Soldering:

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:


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.


#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

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

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