Braille Printer

We have made a ‘Braille printer’ that will convert text input into braille and create tactile cells correspondingly on a regular A4-sized paper, so that people with visual impairment can feel it and will thus be able to access printed text. The printer will be useful for creating materials for literacy and education of people with visual impairment.

Presentation Slide

May 4, 2018 - May 18, 2018

Abhilash SP, Akila Surendran, Akhil Hari , Amith G Nair, Aby Michael , Rinoy Suvarnadas, Vishnu V raj

Brainstorming

We looked at the machines that were made by the Academy students from previous years in our lab and also at webpages of other fablabs. Several ideas emerged in the brainstorming session and we had to shortlist possibilities:

  1. Tactograph - Tactile image printer by extruding fevicol
  2. Object tracking camera mount with roll, tilt and pan
  3. Braille Printer/Embosser
  4. Object Shooter
  5. Wall printer
  6. Automated drink mixer
  7. Dosa Maker

From the detailed introduction on assistive technologies by Akila and after considering the possibilities at our lab and the time constraint, we narrowed down to Abhilash 's idea of making a Braille printer.

Braille Printer

Braille is a tactile writing system used by people who are visually impaired. Each character is a 3x2 matrix of bumps(or dots) separated by standard distances. In the image below are English alphabets in braille and the standard distances, taken from this source.

Braille alphabets
Standard Distances

Image Source

To better understand the working of a printer , we decided to open up an old dot matrix printer. Abhilash and Akhil opened up the old dot matrix printer and we all are familiarized with overall mechanisms inside a printer, especially the paper feed mechanism. We noted that the solenoid that got from dot-matrxic printer , pins are too small to punch holes for Braille standard dimensions. We also looked at the other machines that were made at our lab by previous academy students. This helped us to get familiarize with the integral parts of a machine like the linear bearings, belt and pulley, smooth rods, motor coupler, limit switch, types of arrangement of screws etc. With the help of our instructor Yadu , we broke down our project into four parts or processes.

  1. The paper feed mechanism, which has to feed in the paper, corresponding to the standard Braille dimensions. We decided to use a bipolar stepper motor for accomplishing this.
  2. Mechanism to move the printer head to specified points on a horizontal axis. We decided to use another bipolar stepper motor for this.
  3. The end effector, which punches holes on a paper at specified points to print braille. We decided to use a solenoid push-pull.
  4. Machine actuation and automation using Arduino Mega 2560, RAMPS 1.4 shield and Marlin firmware.

Amith then came up with the initial sketch.

Initial Sketch

Mechanical Design

We divided responsibilities to make sure that everyone has a role to play and the project moves forward smoothly. Aby prepared this task flow sheet.

Task division

We collected the reusable parts like the limit switches, rollers from the opened printer and the disposed materials at our lab. Amith , Akhil and Rinoy worked on an inventory, which listed out the following components that we had to gather or purchase:

  1. Solenoid Push Pull
  2. Stepper motors - 2Nos
  3. GT2 belt with 1.4mm tooth
  4. Linear bearings -2 Nos
  5. Motorshaft-Belt Coupler - 1 Nos
  6. Pulley -1 Nos
  7. 8mm smooth rod - 2 Nos
  8. Paper feeder rod with the rubber bush
  9. Limit switch

Except the solenoid and the linear bearing, everything else in the list was available in our lab. Refer to the ‘Bill of materials’ in this page . Since the solenoid push pull was the end effector and the most critical part, we had to ensure timely procurement of the same. Rinoy , Aby , Abhilash and Vishnu were assigned with the task of procuring parts from the local shops. We couldn’t get the solenoid push pull but bought the linear bearings. We ordered a solenoid Push Pull( Linear Actuator) over the internet. We had to wait for its arrival to design the printer head which would accommodate the solenoid.

Yadu got an old printer for us which Rinoy , Aby and Abhilash opened to get the roller with rubber bushes for the paper feed mechanism. The initial sketch had two smooth rods over which the printer head will move horizontally. Aby designed and made a prototype in acrylic. The smooth rods are supported over vertically placed acrylic sheets. Rinoy took care of the laser-cutting process.

Prototype of mechanical design

From the dimensions of the linear bearings, we could start designing the print head which would move over the smooth rods on the bearing. Akila's task was to design and 3D print the printer head. She added a belt clamp in the printer head for the GT2 belt with 1.4mm tooth. The first printer head design was of two halves which had to be screwed together. We decided against this design because we were concerned about the difference in alignment of the rods due to unequal screwing at the two ends of the printer head. We decided to make a printer head in a single piece. Akila designed and 3D-printed the final design as per the image below.

From the acrylic prototype, we started to note the new requirements that our design demanded. The smooth rods were placed on either side of the belt. We were then in need of a suitable arrangement to hold the driven pulley. A pulley was designed by Akhil and 3D printed . We started to assemble the parts and the working was simulated to an extent.

We realised that the horizontally stacked smooth rods were making things difficult for us to keep the solenoid push pull in between without interfering with the movement of the belt. So change the arrangement of rods in horizontally to vertical manner. Vishnu is assigned for drawing the improved Sketch

Improved Sketch

Aby , Amith and Vishnu started designing a support for the stepper motors. Vishnu improved the initial sketch by keeping the rods vertically stacked and the stepper motors were kept on either side of the machine to balance weight and to give stability.

Vishnu drew a new sketch with all the latest improvements that we made to the design. Amith and Aby started 3D designing the entire model in Fusion 360. Akhil and Rinoy joined to give feedback from the existing design and also gave the input measurements for the design. For more details about Model design refer to this link After the completion of designing, Rinoy collected the design files and proceeded to 3D print the parts in Dimension printer and assembled them .After assembling we realised that the X- axis motor does not have support. So Amith designed a motor-holder and we fitted the motor on that.

Fusion Design
Assembled Model

We read about the ‘braille slate’ which is used along with the stylus to manually write in Braille and thought of adding it to our machine. Rinoy downloaded this file from the internet and 3D printed. However, the 3d printed part was not suitable for our application and Rinoy took up making a braille slate having holes at the standard distance of the braille script. This was done to facilitate the smooth piercing of the holes and to act as a support for the paper around the holes to avoid damaging the paper during the piercing of holes. Though this exercise familiarized us with the standard Braille dimensions, we did not use the braille slate in our machine.

Acrylic cutted
3D printed

Machine Actuation

We have designed all the mechanical elements of the braille printer. Now we need to control them electronically and make the braille printer function. Since electronics is new to all of us, making a machine in such a short time was a huge task to us. We broke down the process into the following steps:

Choosing the microcontroller board

We could use a commercial board to talk to the machine. We chose to use Arduino Mega 2560 with RAMPS 1.4 shield and two A4988 stepper driver boards to control the two stepper motors in our machine.

Arduino Mega source
Ramps 1.4 source

Ramps 14 and A4988 Stepper Driver

Ramps is short for reprap Arduino mega pololu shield, it is mainly designed for the purpose of using pololu stepper driven board (similar to 4988 driven board). Ramps can only work when connected to its mother board Mega 2560 and 4988/DRV8825.

A4988 microstepping bipolar stepper motor adjustable current limiting, over-current and over-temperature protection, and five different microstep resolutions (down to 1/16-step). It operates from 8 V to 35 V and can deliver up to approximately 1 A per phase without a heat sink.

Major features of A4988 stepper driver is Simple step and direction control interface, Five different step resolutions: full-step, half-step, quarter-step, eighth-step, and sixteenth-step.Adjustable current control lets you set the maximum current output with a potentiometer, which lets you use voltages above your stepper motor’s rated voltage to achieve higher step rates.

Stepper controll with A4988 stepper driver

Connection diagram

Details from this source

Controlling the stepper motors from the board

We connected a stepper motor to the part labelled ‘x-axis’ (1A, 1B, 2A and 2B) on the RAMPS board. According to RAMPS 1.4 schematic, the stepping, direction and enable pins for x-axis are connected to pins 54, 55 and 38 respectively of Mega2560.

Amith worked on calculating how much distance the printer head and paper feed rod move for one rotation of the stepper motor. Rinoy helping him to support in writing the code .This is important to control the movement of the printer head and paper feed accurately in the machine. He found that the stepper driver boards are by default in 1/16 micro-stepping mode and hence he wrote a program to rotate the stepper motor clockwise or anti-clockwise by 3200 steps or one rotation. He made pen marks at the start point of rotation on the motor and the starting point of the printer head. With his program, he made the stepper motor move by one rotation and measured that the printer head moved to a distance of 31mm. Likewise, he calculated that the rod that holds down the paper moves by a range of 37.05mm for one complete rotation of the stepper motor.

Code for running Stepper forward ( x axis -which head moves ) and back ward when serially giving input 1 and 0 through arduino serial monitor

  // code for check the working of sepper:
// Code edited by Amith Gopakumar :
#define X_STEP_PIN         54
#define X_DIR_PIN          55
#define X_ENABLE_PIN       38

void setup() {

Serial.begin(9600);//Start the Serial port.
pinMode(X_STEP_PIN   , OUTPUT);
pinMode(X_DIR_PIN     , OUTPUT);
pinMode(X_ENABLE_PIN      , OUTPUT);

}
void loop()
{
if (Serial.available())
{ //if there is data on the Serial buffer
  while (Serial.available() > 0)
  { //continues reading the values on the buffer.
    data = Serial.read();
    Serial.print(data);
    if (data == '1') {
      forward(200); // if i recieves make single rotoation clock wise
    }
     if (data == '0') {
        backward(200);  // if i recieves make single rotoation anti clock wise

      }

    }
  }
}
void forward(int number) {
  digitalWrite(X_DIR_PIN    , HIGH);
  for (int i = 0; i <= number; i++)
  {
    digitalWrite(X_STEP_PIN    , HIGH);
    _delay_us(500);
    digitalWrite(X_STEP_PIN   , LOW);
    _delay_us(500);
  }
}
void backward(int number) {
  digitalWrite(X_DIR_PIN    , LOW);
  for (int i = 0; i <= number; i++)
  {
    digitalWrite(X_STEP_PIN    , HIGH);
    _delay_us(500);
    digitalWrite(X_STEP_PIN    , LOW);
    _delay_us(500);
  }
}

Based on Amith ’s calculations, we have these values : to move the printer head by 1mm, we need to run the x-axis stepper motor by 104 steps. To move the paper feed by 1mm, we need to move the y-axis stepper motor by 86 steps .Refer Amith ’s page to Know more abou the controlling Stepper motor and also how he calculates the steps per rotation

Choosing the end effector to be attached to the printer head

We had a lot of difficulty with this one. We needed an end effector to punch holes in regular paper. The holes have to be of a size close to 1.44mm diameter , which is the standard Braille dimension. We had initially purchased a solenoid with a stroke length of 4mm, which had insufficient force to punch holes in paper. Abhilash , Akhil and Aby jumped into a full-fledged ‘Hunt for the end-effector’.

Solenoid that was not effective for our purpose

Akhil tried to increase the force of the solenoid by using a lever and fulcrum. Abhilash and Akhil worked on making a ‘DIY solenoid’, using copper wire coiled around plastic. By pulsing 12V through the wire, a metal piece in the middle could be pushed or pulled alternatively. Rinoy joined with Abhilash in improving the solenoid by testing it with varying current and voltage. While the duo worked on perfecting the force and stabilising this DIY apparatus, Aby started experimenting with the server motor coupled to a mechanical cam to convert the rotational motion of the motor into linear motion of the needle. His idea was to connect a pen to the servo motor and move it up and down to make holes. Aby checked Servo code while Amith combined it with serial communication code and made a new one that accepts serial input (1 makes a dot , 0 if a zero was received just advances, if a "3" was received, pull the paper to begin a new line). This works effectively

  // code is copied from Reprap
  // edited by Amith gopakumar  Nair
  #include 
  Servo myservo;

  #define Y_STEP_PIN         60  // Atmega pins
  #define Y_DIR_PIN          61  //  Atmega pins
  #define Y_ENABLE_PIN       56  // Atmega pins
  #define X_STEP_PIN         54 //   Atmega pins
  #define X_DIR_PIN          55 //   Atmega pins
  #define X_ENABLE_PIN       38 //  Atmega pins

  char data=0;//byte to receive and check data from computer
  void setup() {
    myservo.attach(11);
    Serial.begin(9600);//Start the Serial port.
    pinMode(Y_STEP_PIN   , OUTPUT);
    pinMode(Y_DIR_PIN     , OUTPUT);
    pinMode(Y_ENABLE_PIN      , OUTPUT);
    backward(0);
    }
   void loop()
   {
    if(Serial.available())
    {//if there is data on the Serial buffer
      while(Serial.available()>0)
     {//continues reading the values on the buffer.
        data=Serial.read();
        Serial.print(data);
        if(data=='1'){
          braille();//if a one was received, makes a dot.
          forward(3200);//avances one time (four steps)
        }

        if(data=='0'){
          forward(3200);// if a zero was received just avances.
        }
        if(data=='3'){//if a "L" was received, pull the paper to begin a new line.
          backward(3200);//return the car.
        }
       }
    }
  }
  void forward(int number){
  digitalWrite(X_DIR_PIN    ,HIGH);
    for(int i=0;i<=number;i++)
    {
     digitalWrite(X_STEP_PIN    , HIGH);
      _delay_us(500);
      digitalWrite(X_STEP_PIN    , LOW);
      _delay_us(500);
    }
    }
   void backward(int number){
   digitalWrite(X_DIR_PIN    , LOW);
    for(int i=0;i<=number;i++)
    {
      digitalWrite(X_STEP_PIN    , HIGH);
      _delay_us(500);
      digitalWrite(X_STEP_PIN    , LOW);
      _delay_us(500);
    }
    }
  void paperroll(int number){
   digitalWrite(Y_DIR_PIN    , LOW);
    for(int i=0;i<=number;i++)
    {
      digitalWrite(Y_STEP_PIN    , HIGH);
      _delay_us(500);
      digitalWrite(Y_STEP_PIN    , LOW);
      _delay_us(500);
    }
    }
   void braille(void){
    myservo.write(0);
    delay(100);
    myservo.write(50);
  }
While we were all learning a lot from these experiments, Yadu had procured another bigger solenoid the stroke length of 10mm arrived on Monday and worked perfectly for our machine.

Solenoid that works

Choosing and configuring the firmware to control the machine

We needed a firmware to be loaded in the Mega2560 board that would control the machine in real-time. Akila was trying out Grbl, Repetier, Reprap and found that Marlin was simple to be used with RAMPS 1.4. She referred to some tutorials to make the following changes to the configuration. H file in Marlin. These are explained in detail in her page

marlin firmware can downoad from this link.

Changes made in Marlin fimrware is described below

   #define BAUDRATE 115200
  

The serial communication speed should be as fast as it can manage without generating errors. In most cases, 115200 gives a good balance between speed and stability.End Stops :We need to configure the end stops in marline all the edit function are making on Configuration.h file. The main aim of end stops is to find the origin of the head we connect end stop on the right side of the machine.End stops are switches that trigger before an axis reaches its limit. In other words: It will prevent the head from trying to move out of its frame. The printer also uses end stops as a reference position. The Marlin firmware allows one to configure each limit switch individually.

   #define X_MIN_ENDSTOP_INVERTING false // set to true to invert the logic of the endstop
 

Define the number of axis It is the total number of axis 2

        #define NUM_AXIS 2
  

Axis steps per unit The stepper motor receives step by step moving command from the controller. The controller needs to know the steps/mm ratio to send the appropriate steps to reach the required distance. How many steps are needed to move an axis by 1 mm. here we use the calculations find Amith

 DEFAULT_AXIS_STEPS_PER_UNIT   {X,Y,Z,E1}
 #define DEFAULT_AXIS_STEPS_PER_UNIT   { 104, 86, 4000, 500 } //104 stands for 1mm moement in x axis number of steps is 104
                                                               //and for y axis 1mm movement steps is  86 other values are not usefull to us
 

She then downloaded Pronterface which could talk to the Marlin firmware in our board. We tried moving our stepper motors from Printrun. Since the printhead was moving forward when she pressed the backward button, we edited line 495 of configuration.h file to invert the logic of the end stop.

Controlling the end effector, which is the solenoid

Akila and Rinoy figured out that the solenoid push pull could be connected to the fan port D9 of RAMPS1.4 shield, which can be switched on and off with the Gcodes M106 and M107 respectively.

Integrating the machine

Now that we have a fair idea of how to connect the different elements of our machine, we needed to place them all together and integrate the machine in such a way that the wirings don’t interfere with the working of the machine. Rinoy and Vishnu worked on placing the board and motors and routing the wires so that they don’t affect the moving parts of the machine. The printer head and the paper feed mechanism could now move smoothly, and the machine was well-integrated.

Our instructor Yadu wrote a Python code that reads text(.txt file) file and generate the G-code , and send it serially to the machine. Vishnu helped Yadu by creating the json files to create braille matrix ( of '0' and '1') for the 26 English alphabets, numbers 0 to 9 and some punctuation marks. Yadu’s code would compare the text input against the data in json file and generate the G-code.

Amith made a GUI which has an input panel for text and by click the save button it save that text on .txt file. After that we can click the ‘Print’ button to execute Yadu python code.

  //created by : Amith G Nair : May 13,2018
  from Tkinter import *
  import sys
  import os

  def save():
    text = e.get() + "\n"
    with open("text.txt", "w") as f:
        f.write(text)        # Write the input text to text.txt file

  def printer():
    os.system('python text_to_braille.py')  #executing the shell commands
     # execute the python code to covert text to g-code and send it serially

  master = Tk()
  master.title("Braille Printer")
  master.geometry('800x100')
  Label(master, text="Enter the text").grid(row=0)
  e = Entry(master,width=70)
  e.grid(row=0, column=10)


  Button(master, text='Save', command=save).grid(row=3, column=0, sticky=W, pady=4)
  Button(master, text='Print', command= printer).grid(row=3, column=1, sticky=W, pady=4)
  Button(master, text='Quit', command=master.quit).grid(row=3, column=3, sticky=W, pady=4)

  mainloop( )

Alphabet to braille conversion in .json file

Python code written by Yadhu

# Author Yadu Sharon :
#Commented by amith
import  json   # call the jason library
import serial  # call the serial library

ser_path = '/dev/ttyACM0'  # set the serial communication port
# paper size A4
width = 210
height = 297
x_ofst = 10
y_ofst = 10

dx_intra = 2.5
dx_inter = 6
dy_intra = 2.5
dy_inter = 10
dy_l_f   = dy_inter-(2*dy_intra)

x_char_max = (width-(2*x_ofst))/6
y_char_max = (height-(2*y_ofst))/10


text_file = open("text.txt","r")  # open the text.txt file and have read function
text= text_file.read()
#print text

with open('braille_data.json') as f:
    braille = json.load(f)        # open the jason file

txt_len = len(text)
y_char = txt_len/x_char_max
x_char = x_char_max
if ((txt_len%x_char_max)!=0):
    y_char+=1
if txt_len= x_char_max):

            txt_array.append(txt_row)
            txt_row = ["" for x in range(x_char_max)]
            i=0
            txt_row[i]=text[n].lower()
            i+=1
            txt_array[0][0]+=1
    txt_array[0][0]+=1


def y_code():
    global x_char_max
    global txt_array
    global y_code_array
    y_code_array = []

    for p in range (1,txt_array[0][0]):
        for q in range(3):
            y_pos = ((p-1)*dy_inter)+y_ofst+(q*dy_intra)
            y_code_array += ["Y"+str(y_pos)]
            for r in range(x_char_max):
                try:
                    print txt_array[p][r]
                    if braille[txt_array[p][r]][q][0] == 1:
                        pos = (r*dx_inter)+x_ofst
                        y_code_array+= ["EX"+str(pos)]
                    if braille[txt_array[p][r]][q][1] == 1:
                        pos = (r*dx_inter)+x_ofst+dx_intra
                        y_code_array+= ["EX"+str(pos)]
                except IndexError:
                    #pass
                    print "error"

def g_code():
    global x_char_max
    global txt_array
    global g_code_array
    g_code_array = []
    g_code_array+=["SA"]
    g_code_array+=["PAUSE"]
    g_code_array+=["MS,40,25"]
    g_code_array+=["JZ,20"]
    g_code_array+=["J2,0,0"]



    for p in range (1,txt_array[0][0]):
        for q in range(3):
            y_pos = ((p-1)*dy_inter)+y_ofst+(q*dy_intra)
            #y_code_array += ["Y"+str(y_pos)]
            for r in range(x_char_max):
                try:
                    if braille[txt_array[p][r]][q][0] == 1:
                        pos = (r*dx_inter)+x_ofst
                        g_code_array+= ["J2,-"+str(pos)+",-"+str(y_pos)]
                        g_code_array+= ["JZ,-1"]
                        g_code_array+= ["JZ,10"]

                    if braille[txt_array[p][r]][q][1] == 1:
                        pos = (r*dx_inter)+x_ofst+dx_intra
                        g_code_array+= ["J2,-"+str(pos)+",-"+str(y_pos)]
                        g_code_array+= ["JZ,-1"]
                        g_code_array+= ["JZ,10"]

                except IndexError:
                    #pass
                    print "error"
    g_code_array+= ["JZ,20"]
    g_code_array+= ["J2,0,0"]


def marlin():
    global x_char_max
    global txt_array
    global g_code_array
    g_code_array = []
    g_code_array+=["G21"]  # Set units to millimeters.
    g_code_array+=["G90"]  #  Absolute Positioning In absolute mode all coordinates given in G-code are interpreted as positions in the logical coordinate space
    g_code_array+=["G28 X"]  #  Auto Home moves the head to origin
    g_code_array+=["G0 X20 Y20"]  # The G0 and G1 commands add a linear move to the queue to be performed after all previous steps completed.
    g_code_array+=["G92 X0 Y0"] #
    g_code_array+=["G0 X5"]  #   The G0 and G1 commands add a linear move to the queue to be performed after all previous steps completed.


    for p in range (1,txt_array[0][0]):
        for q in range(3):
            y_pos = ((p-1)*dy_inter)+y_ofst+(q*dy_intra)
            #y_code_array += ["Y"+str(y_pos)]
            for r in range(x_char_max):
                try:
                    if braille[txt_array[p][r]][q][0] == 1:
                        pos = (r*dx_inter)+x_ofst
                        g_code_array+= ["G0 X"+str(pos)+" Y"+str(y_pos)]
                        g_code_array+= ["M106"]  these are used to off the fan using this we can operate solenoid push pull.
                        g_code_array+= ["G4 P200"]
                        g_code_array+= ["M107"]   these are used to  off the fan using this we can operate solenoid push pull.

                    if braille[txt_array[p][r]][q][1] == 1:
                        pos = (r*dx_inter)+x_ofst+dx_intra
                        g_code_array+= ["G0 X"+str(pos)+" Y"+str(y_pos)]
                        g_code_array+= ["M106"] these are used to on the fan using this we can operate solenoid push pull.
                        g_code_array+= ["G4 P200"]
                        g_code_array+= ["M107"]   these are used to  off the fan using this we can operate solenoid push pull.

                except IndexError:
                    #pass
                    print "error"
    g_code_array+= ["G0 X0"]
    g_code_array+= ["G92 Y0"]

def write_g_code():
    global g_code_file
    global g_code_array
    g_code_file=open("g_code.sbp",'w')
    for lines in g_code_array:
    #    g_code_file.write("%s\n",g_code_array[lines])
        print>>g_code_file, lines
    print "Done"

def write_marlin():
    global g_code_file
    global g_code_array
    g_code_file=open("g_code_marlin.gcode",'w')
    for lines in g_code_array:
    #    g_code_file.write("%s\n",g_code_array[lines])
        print>>g_code_file, lines
    print "Done"

#def wait_line(ser):
#    while(True):


def sender(ser_path,gcode):
    try:
        ser=serial.Serial(ser_path,baudrate =115200,timeout =1)
        ser.isOpen()
        read = ser.readall()
        print "<< "+read
        ser.timeout= None
        for cmd in gcode:
            ser.write(cmd+"\n")
            print ">> "+cmd
            while(True):
                read = ser.readline().strip()
                print "<< "+read
                if read=='ok':
                    break
        ser.close()
    except Exception as e:
        print e

make_txt_array()
#print txt_array
marlin()
write_marlin()
#print y_code_array

input_text = raw_input("Send gcode? (y/n) \n")
if input_text.lower()== 'y':
    sender(ser_path,g_code_array)




Final look
Presentation video

License

This work is licensed under Creative Commons Attribution-NonCommercial 4.0 International License.This licence allows anyone to copy, change and use this work without any restrictions.

Future Scope

We were able to make a braille printer in Fablab in a span of two weeks, for a few thousands of Indian rupees. The commercial version costs around $2000. Our project is a proof-of-concept that complex machines can be made in a fablab. Since we are open-sourcing our work, anyone can reproduce this or improve upon it. At present, in India, there is a scarcity for educational materials in Braille. Since the printer is expensive, smaller institutions cannot afford to buy it. Students rely on audiobooks or other people to read out the text material to them. We hope that in the long run, our work would reach the end-users. This would improve independence and accessibility to literacy for people with visual impairment.