TACTILE BRAILLE E-BOOK/E-TEXT READER
Rajarshi Roy (
[email protected]) Angky William (
[email protected])
December 15th, 2010
ECE 395: Advanced Digital Projects Laboratory University of Illinois at Urbana Champaign
Page 1 of 16
Abstract: This report details the design concept and prototype implementation of a tactile Braille e-book/etext reader. The text is stored in the .txt file format inside a micro-SD card. An Arduino Duemilanove micro-controller then reads this information to control a six-dot Braille cell using six servos. A mouse scroll-wheel encoder is used at the bottom of the device so that the user can scroll forward or backward through the textual information by sliding the device on a flat surface to the right or left respectively. This device opens up a much cheaper and complete platf orm for education and knowledge transfer for the blind as compared to current technologies.
Introduction: Technology has revolutionized the daily life of people but it has particularly benefited visually impaired individuals. Until recently, most print information such as newspapers, magazines, books and such were of very limited accessibility due to the lack of widespread Braille publications. Only recently have assistive technologies such as audio books and tactile Braille refreshable displays allowed the access of information to the blind through the digital medium. However, current technologies still have major drawbacks. For example, audio books only cover certain publications and any piece of text cannot be accessed through them. Furthermore, this technology inaccessible to the deaf-blind and does not provide intuitive control over the information flow. Tactile refreshable Braille displays on the other hand overcome this problem by using refreshable tactile Braille cells that are accessible to all due the use of touch. Furthermore, tactile Braille cells can be refreshed at the user's own pace allowing full control. However, most tactile Braille technologies are extremely expensive, ranging from 5000 to 10000 dollars [1]. This cost factor makes this technology impact-less in the blind community. Our motivation for this project was to have a low-cost tactile Braille reader that can make digital information widely accessible to the blind. The main cost factor of tactile Braille displays is the cost of the actuators that raise or lower each Braille dot. In all current tactile Braille devices, piezoelectric actuators are used which are very expensive [2]. Thus we based our design on using servo motors which provide a precise actuation for a very cheap price. Also we used the Arduino Duemilanove micro-controller to carry out all controls. This allows the device to be easily modified from our current portable e-text reader design to a Braille output interface from software.
Page 2 of 16
Design: The main design consists of four parts: the micro-controller (Arduino Duemilanove) [3], the encoder (a cheap old mouse scroll wheel), the Braille cell (six servos and some piano wire) and the SD card reader (micro-SD/prototyping shield PCB from SparkFun.com) [4]. All circuitry was soldered on the prototyping portion of the micro-SD/prototyping shield.
Figure 1: Top-right-back corner view of device
Figure 2: Top-left-front corner view of device Page 3 of 16
The concept of the device is simple. Since there is only one Braille cell of 6 dots, the device can only show one character in the text at a time. Thus, the user will not use the traditional method of reading a Braille text by moving his fingers from left to right over a row of Braille characters. In this design, the user will keep his finger on the Braille cell and move the device from left to right on a flat surface to read the text. As the device is moved, the encoder wheel rotates, signaling the micro-controller. After a certain threshold amount of movement, the Arduino refreshes the Braille cell to the configuration of the next character in the text that is stored in the micro-SD card. Similarly, the user can move the device from right to left to read the previous character, thus allowing a bidirectional control over the reading of the text (Figure 3).
Figure 3: Reading text using the Braille e-book reader (left) as compared to reading traditional Braille (right) Thus, the overall data flow is as such: the movement data from the encoder
Encoder
and the text file from the micro-SD card
Arduino
Servos in Braille cell
is processed by the Arduino to control the six servos in the Braille cell.
Micro-SD card
Braille Cell The most important component of the device that distinguishes it from current tactile Braille technology is the cheap Braille cell. The Braille cell is constructed using six servos (T-Pro SG90 at $2.77 each) (Appendix C), some piano wire (0.040” at negligible cost) and a section of a perfboard. Since the standard Braille specifications require that the spacing between two Braille dots to be 0.090” to 0.100” [5], it was a challenge to create a Braille cell that compact with servos that are Page 4 of 16
0.88”x 0.86”x0.45”. In our design, we made two stacks of three servos (Figure 4). Each stack controls either the left or right vertical column of Braille dots. The Braille dots are basically the rounded tips of the piano wire that is attached on the other end to the servo arm. The piano wire passes through the holes of the perfboard and can thus move in or out through the holes. This system converts the rotational motion of the servo arm into the translational motion of the Braille dots (similar to the piston system of an internal combustion engine). The slight angle change of the servo arm controlled by the Arduino determines the raised or lowered position of the Braille dot. 4.1
4.2
4.3
Figure 4: The left stack of three servos (4.1) control the left vertical column of Braille dots. The Braille dots are the rounded tips of the three .040” piano wires (a), (b), (c) which are 3.3mm, 2.2mm, and 1.1 mm long respectively. A slight change in the angle of the lowest servo’s arm (d) produces the translational displacement (e) to raise the bottom-left Braille dot (f). Image 4.2 shows the actual implementation of the design and the corresponding dot pattern (4.3).
We were very lucky that the standard perfboard has holes which are 0.100” spaced apart [6] which met the Braille specification exactly. The Braille cell was constructed by using glue-gun to attach the parts together and a Dremel tool to cut the perfboard and round the tips of the piano wire. This was the most time-consuming portion of our project. The six servos are controlled by the digital pins 4(top left), 5(middle left), 6(bottom left), 7(top right), 9(middle right), 10(bottom right). The servo library
[7] provided online in the Arduino playground was used. In the code (Appendix A), function braille() takes in a char variable and controls the servos based on the Braille code. We decided to use the ‘#’ symbol to be the special character that number follows.
Page 5 of 16
The circuit for the servo system (Figure 5) is very simple as all the servos’ power (red) and ground (brown) are connected to the Arduino’s 5V and GND pin respectively. The signal wires (yellow) of each servo are connected to their respective digital pins (10, 9, 7, 6, 5, 4).
5V
Power
GND D10
GND Signal
5V
Power
GND D9
GND Signal
5V
Power
GND D7
GND Signal
Servo: bot. left
Servo: mid left
Servo: top left
5V
Power
GND D6
GND Signal
5V
Power
GND D5
GND Signal
5V
Power
GND D4
GND Signal
Servo: bot. left
Servo: mid left
Servo: top left
Figure 5: Servo connections schematic
Encoder Encoder We attempted to use an optical mouse’s sensor to sense the movement of the device. We managed to interface a mouse’s optical chip to the Arduino. However, it was difficult to integrate it into the device due the precise height the chip and its lens system needs to be above a surface. Thus, we simplified things by using a mouse’s scroll wheel mechanical encoder. The mechanical encoder has three pins A, B and C with C being in the middle while A and B being at the sides. In the circuit, C is connected to 5V from the Arduino while A and B are pulled down to GND through 300 ohm resistors (Figure 6).
Page 6 of 16
Figure 6: Encoder circuit
When the encoder wheel is rotated, each A and B pins produce pulse patterns which are at slight offsets [8]. This pattern is actually etched into copper inside the mechanical encoder. One interesting property of this pattern is that when pin A has a rising or falling edge, pin B will either be zero or one. Depending on which way the encoder is rotated, the transition from zero to one (rising edge) of pin A will correspond to a low or high value of pin B. The Arduino code (Appendix A) uses polling to detect this. In the void loop() portion of the Arduino code that always loops, the outermost if structure checks for a rising edge by comparing the current value of A with the previous value of A. Then when a rising edge triggers the conditional, the code checks the value of B and accordingly increments or decrements the encoderPos variable. After certain threshold conditions that decide how much the device should be moved before a change in the character, the next or previous character is fetched from the SD card code nested inside the encoder code.
SD card interface The SD card circuitry was entirely provided on the Sparkfun micro-SD shield that was bought online [4]. The code uses the and libraries provided online [9] by Sparkfun to access a byte in the SD card. We decided to use a .txt format to store our text in because each character in the text has a one-to-one correspondence with each byte in the file system. Thus, reading a byte and then reading the next byte would directly mean reading a character and then reading the next character. The documentation of the library only had the .read() function that reads a byte and sets the pointer to the next byte. Thus, initially we read a certain point in the file by reading through every point until that point. However, this caused a lot of problem with large files like books because the code was not fast enough. Thus, after searching through the code of the library we found a variable CurrentPos in the SDFat library that determined which byte was being read. Luckily the library even has an internal function .seekSet() which sets this value. Thus, using this function we could directly jump to a point in the text based on the variable position that incremented or decremented based on the movement of the ebook reader. The data transfer between the SD card and Arduino uses the Arduino’s built-in SPI protocol pins 8(CS), 11(MOSI), 12(MISO), 13(SCK). The micro-SD shield was designed by sparkfun.com and its’ schematic is provided in Figure 7. Page 7 of 16
Figure 7: micro-SD shield schematic [10]
Future work: The size of the device can be made much smaller by using smaller servos and using custom PCB with the ATMega328 chip and SD card integrated. We started working on this but could not finish due to the lack of time. Also, an optical mouse chip can be used instead of the mouse encoder. This would enable another mode of use whereby the mouse chip’s image is accessed and the Arduino runs a simple optical character recognition program on it. The character recognized would determine the Braille cell content, allowing the user to read printed text such as medicine expiry labels. Finally, a feature that we could have added that we realized lately is a character number storing system whereby the character number is continuously stored at the end of the file. Thus, when the device is turned off and on again, the user can choose to continue reading from where he last left off.
Conclusion and Acknowledgements: Acknowledgements: This project was very enjoyable and we would like to thank Professor Lippold Haken and Zuofu Cheng (big-time!) for their supervision and insights. We are grateful to the University of Illinois at Urbana Champaign Department of Electrical and Computer Engineering for providin g us with every resource we needed including funding for parts, access to the ECE machine shop and ECE service store.
Page 8 of 16
Refere Refer ences [1] Accessibility: Refreshable Braille Displays [2] Refreshable Braille Now and in the Years Ahead [3] Arduino Duemilanove [4] SparkFun electronics: microSD Shield [5] Size and Spacing of Braille Characters [6] Pre-Punched IC-Spacing Perfboard [7] Arduino Playground-Servo [8] Using Encoders [9] SparkFun electronics: microSD Shield SdFat library [10] SparkFun electronics: microSD Shield Schematic
Page 9 of 16
Appendix A // // // // //
OVERALL DEVICE CONTROL crystallized into a big crystal with the help of seed crystals (example codes): http://www.sparkfun.com/tutorial/microSD_Shield/SD_SimpleExample.pde http://www.arduino.cc/playground/ComponentLib/Servo
// ECE395 - Braille e-book/e-text reader // // // // //
Developers Rajarshi Roy: servo control, sd card reading, quadrature encoder Angky William: the idea to structure the function braille() based on each pin rather than each character, shortening the code greatly
//add servo library #include //add sd #include #include #include
card library
//////sd card variables and objects Sd2Card card; SdVolume volume; SdFile root; SdFile file; char name[] = "Test.txt";
// Create an array that contains the name of our file.
//////servo variables and objects SoftwareServo servoltop; // create SoftwareServo servolmiddle; // create SoftwareServo servolbottom; // create SoftwareServo servortop; // create SoftwareServo servormiddle; // create SoftwareServo servorbottom; // create //note that all servo.write() values were //may not work for other builds //////encoder variables int encoderPinA = A0; int encoderPinB = A1; int encoderPos = 0; quadrature data*/ int encoderPinALast = LOW; int n = LOW; int position = -1; void setup() { servoltop.attach(4); servoltop.write(92); prevent damage to device*/ servolmiddle.attach(5); servolmiddle.write(95); servolbottom.attach(6); servolbottom.write(93); servortop.attach(7);
servo object to servo object to servo object to servo object to servo object to servo object to caliberated for
control left-top servo control left-middle servo control left-bottom servo control right-top servo control right-middle servo control right-bottom servo our device. the same values
// pin A of encoder hooked up to analog0 // pin B of encoder hooked up to analog1 /* variable that increments or decrements based on /* last value of pin A (used to detect rising edges)*/ /* current value of pin A (used to detect rising edges)*/ // position in the text. we noticed initially position=0 // makes it skip the first character // digital pin 4 now controls left-top servo /* quickly stabilize servo (default lowered position) to // // // // //
digital pin 5 now controls left-middle servo to stabilize servo digital pin 6 now controls left-bottom servo to stabilize servo digital pin 7 now controls right-top servo
Page 10 of 16
servortop.write(90); servormiddle.attach(9); servormiddle.write(96); servorbottom.attach(10); servorbottom.write(91);
// // // // //
to stabilize servo digital pin 9 now controls right-middle servo to stabilize servo digital pin 10 now controls right-bottom servo to stabilize servo
pinMode (encoderPinA,INPUT); // use encoder pin A as digital input pinMode (encoderPinB,INPUT); // use encoder pin B as digital input pinMode(10, OUTPUT); to work.*/ card.init(); volume.init(card); root.openRoot(volume);
/* Pin 10 must be set as an output for the SD communication // Initialize the SD card and configure the I/O pins. // Initialize a volume on the SD card. // Open the root directory in the volume.
} void loop() { // Quadrature encoding from encoder code works using polling // SD card code is nested withing this code n = digitalRead(encoderPinA); if ((encoderPinALast == LOW) && (n == HIGH)) { transition) if (digitalRead(encoderPinB) == LOW) { encoderPos--; } else { encoderPos++; } if(encoderPos == 2) {position++; encoderPos=0;} //before refreshing if(encoderPos == -2) {position--; encoderPos=0;} //before refreshing
// make n to be current value of A // if rising edge (1 to 0 // decrement if B is low // increment if B is high // determines how much to move // determines how much to move
// SD card code begins here char alphabet;
// To store the current character
file.open(root, name, O_READ); file.seekSet(position); alphabet=file.read(); file.close();
// // // //
Open the file in read mode. Position seek in file. Get the byte in the file at seek location. Close the file
// SD card code ends here braille(alphabet); //character
// Actuate braille pins based on current
} encoder0PinALast = n;
//Now last value of pin A must be set to n
}
void braille(char letter){ on that servoltop.write(92); servolmiddle.write(95); servolbottom.write(93); servortop.write(90); servormiddle.write(96); servorbottom.write(91);
//takes in a character (char letter) and initializes pins based // // // // // //
initializes initializes initializes initializes initializes initializes
pin pin pin pin pin pin
to to to to to to
Page 11 of 16
default default default default default default
lowered lowered lowered lowered lowered lowered
position position position position position position
for (d=0; d<=1; d++){ //(d=1)
// loop that sets left side (d=0) first then right side
if( ((letter=='a'||letter=='A'||letter=='1') && (d==0)) || ((letter=='b'||letter=='B'||letter=='2') && (d==0)) || ((letter=='c'||letter=='C'||letter=='3') && (d==0)) || ((letter=='c'||letter=='C'||letter=='3') && (d==1)) || ((letter=='d'||letter=='D'||letter=='4') && (d==0)) || ((letter=='d'||letter=='D'||letter=='4') && (d==1)) || ((letter=='e'||letter=='E'||letter=='5') && (d==0)) || ((letter=='f'||letter=='F'||letter=='6') && (d==0)) || ((letter=='f'||letter=='F'||letter=='6') && (d==1)) || ((letter=='g'||letter=='G'||letter=='7') && (d==0)) || ((letter=='g'||letter=='G'||letter=='7') && (d==1)) || ((letter=='h'||letter=='H'||letter=='8') && (d==0)) || ((letter=='i'||letter=='I'||letter=='9') && (d==1)) || ((letter=='j'||letter=='J'||letter=='0') && (d==1)) || ((letter=='k'||letter=='K') && (d==0)) || ((letter=='l'||letter=='L') && (d==0)) || ((letter=='m'||letter=='M') && (d==0)) || ((letter=='m'||letter=='M') && (d==1)) || ((letter=='n'||letter=='N') && (d==0)) || ((letter=='n'||letter=='N') && (d==1)) || ((letter=='o'||letter=='O') && (d==0)) || ((letter=='p'||letter=='P') && (d==0)) || ((letter=='p'||letter=='P') && (d==1)) || ((letter=='q'||letter=='Q') && (d==0)) || ((letter=='q'||letter=='Q') && (d==1)) || ((letter=='r'||letter=='R') && (d==0)) || ((letter=='s'||letter=='S') && (d==1)) || ((letter=='t'||letter=='T') && (d==1)) || ((letter=='u'||letter=='U') && (d==0)) || ((letter=='v'||letter=='V') && (d==0)) || ((letter=='w'||letter=='W') && (d==1)) || ((letter=='x'||letter=='X') && (d==0)) || ((letter=='x'||letter=='X') && (d==1)) || ((letter=='y'||letter=='Y') && (d==0)) || ((letter=='y'||letter=='Y') && (d==1)) || ((letter=='z'||letter=='Z') && (d==0)) || (letter=='#' && (d==1)) ){if(d==0){servoltop.write(82);} if(d==1){servortop.write(100);}} if( ((letter=='b'||letter=='B'||letter=='2') && (d==0)) || ((letter=='d'||letter=='D'||letter=='4') && (d==1)) || ((letter=='e'||letter=='E'||letter=='5') && (d==1)) || ((letter=='f'||letter=='F'||letter=='6') && (d==0)) || ((letter=='g'||letter=='G'||letter=='7') && (d==0)) || ((letter=='g'||letter=='G'||letter=='7') && (d==1)) || ((letter=='h'||letter=='H'||letter=='8') && (d==0)) || ((letter=='h'||letter=='H'||letter=='8') && (d==1)) || ((letter=='i'||letter=='I'||letter=='9') && (d==0)) || ((letter=='j'||letter=='J'||letter=='0') && (d==0)) || ((letter=='j'||letter=='J'||letter=='0') && (d==1)) || ((letter=='l'||letter=='L') && (d==0)) || ((letter=='n'||letter=='N') && (d==1)) || ((letter=='o'||letter=='O') && (d==1)) || ((letter=='p'||letter=='P') && (d==0)) || ((letter=='q'||letter=='Q') && (d==0)) || ((letter=='q'||letter=='Q') && (d==1)) || ((letter=='r'||letter=='R') && (d==0)) || ((letter=='r'||letter=='R') && (d==1)) || ((letter=='s'||letter=='S') && (d==0)) || ((letter=='t'||letter=='T') && (d==0)) || ((letter=='t'||letter=='T') && (d==1)) || ((letter=='v'||letter=='V') && (d==0)) ||
Page 12 of 16
//set top pins
((letter=='w'||letter=='W') && (d==0)) || ((letter=='w'||letter=='W') && (d==1)) || ((letter=='y'||letter=='Y') && (d==1)) || ((letter=='z'||letter=='Z') && (d==1)) || (letter==',' && (d==0)) || (letter=='.' && (d==0)) || (letter=='.' && (d==1)) || (letter==';' && (d==0)) || (letter=='!' && (d==0)) || (letter=='!' && (d==1)) || ((letter=='(' || letter==')') && (d==0)) || ((letter=='(' || letter==')') && (d==1)) || (letter=='?' && (d==0)) || (letter=='"' && (d==1)) || (letter=='#' && (d==1)) ){if(d==0){servolmiddle.write(85);} if(d==1){servormiddle.write(106);}} //set middle //pins if( ((letter=='k'||letter=='K') && (d==0))|| ((letter=='l'||letter=='L') && (d==0))|| ((letter=='m'||letter=='M') && (d==0))|| ((letter=='n'||letter=='N') && (d==0))|| ((letter=='o'||letter=='O') && (d==0))|| ((letter=='p'||letter=='P') && (d==0))|| ((letter=='q'||letter=='Q') && (d==0))|| ((letter=='r'||letter=='R') && (d==0))|| ((letter=='s'||letter=='S') && (d==0))|| ((letter=='t'||letter=='T') && (d==0))|| ((letter=='u'||letter=='U') && (d==0))|| ((letter=='u'||letter=='U') && (d==1))|| ((letter=='v'||letter=='V') && (d==0))|| ((letter=='v'||letter=='V') && (d==1))|| ((letter=='w'||letter=='W') && (d==1))|| ((letter=='x'||letter=='X') && (d==0))|| ((letter=='x'||letter=='X') && (d==1))|| ((letter=='y'||letter=='Y') && (d==0))|| ((letter=='y'||letter=='Y') && (d==1))|| ((letter=='z'||letter=='Z') && (d==0))|| ((letter=='z'||letter=='Z') && (d==1))|| (letter== '\''&& (d==0)) || (letter=='.' && (d==1)) || (letter==';' && (d==0)) || (letter=='!' && (d==0)) || ((letter=='('|| letter==')') && (d==0)) || ((letter=='('|| letter==')') && (d==1)) || (letter=='-' && (d==0)) || (letter=='-' && (d==1)) || (letter=='?' && (d==0)) || (letter=='?' && (d==1)) || (letter=='"' && (d==0)) || (letter=='"' && (d==1)) || (letter=='#' && (d==0)) || (letter=='#' && (d==1)) ){if(d==0){servolbottom.write(83);} if(d==1){servorbottom.write(101);}} //set bottom //pins } }
// end of for loop // end of function braille
Page 13 of 16
Appendix B Since Braille uses the same symbol for 1 and A, 2 and B and so on till 0 and J, it adds a special character before a number. This code modifies a text file in the computer to add the ‘#’ symbols in front of numbers prior to transferring the text into the device. // ADDING # CHARACTER BEFORE NUMBERS // ECE395 - Braille e-book/e-text reader //
Developer: Angky William
#include int main () { FILE * pFile; int c='a'; int n = 0; int a; int b; int d; int e=0; int f=0; pFile=fopen ("test.txt","r+"); //file name to be edited if (pFile==NULL) perror ("Error opening file"); else { for(n=0;c!=EOF;n++) { c=getc(pFile); if('0' <=c && c<= '9') {e++;} } while(f
Page 14 of 16
fputc((int)b,pFile); b=getc(pFile); fseek(pFile,-1,SEEK_CUR); //printf(" a is %d b is %d",a,b); } fseek(pFile,n+2,SEEK_SET); n++; } } //adding special code start here fseek(pFile,0,SEEK_SET); c='a'; for(n=0;c!=EOF;n++) { c=getc(pFile); if(('0'<= c && c <= '9' ) && (a < '0' || a > '9') ) { fseek ( pFile, -2 , SEEK_CUR); fputs ("#", pFile); fseek ( pFile, 1 ,SEEK_CUR); } } fclose (pFile); // printf ("The file contains %d dollar sign characters ($).\n",n); } return 0; }
Page 15 of 16
Appendix C
Page 16 of 16