| 
  • If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

Andrew J Dupree - Final Project Documentation

Page history last edited by Andrew Dupree 12 years, 7 months ago

Andrew Dupree

David Sirkin

EE47: Interactive Device Design

8/13/2011

EE47 Final Project: Teensy Reader

Documentation

Introduction

 

This is the documentation for my final project for EE47. For the project, I elected to do something different than an MP3 player. My interest is in low-cost computing platforms that might be beneficial to those in developing countries. Scaled back to the MCU-level, the best thing I could think of was an e-reader. But unlike been-there-done-that projects like the Wikireader, mine would be internet connected. This would provide an element of mutability and the ability to contextualize the content.

 

Point of View

 

I tried to design from the point of view of one who had limited experience with technology and computing platforms. The player would be for offline review of documents useful to the needs of low-income people in either urban or rural areas. Design of this type requires a user experience that is simple and intuitive, something that one who’s only experience from technology might be a very basic mobile phone could understand.

 

Figure 1: Point of view description. Note the focus on simplicity, intuitiveness, and meeting basic needs of document retrieval and playback.

 

Brainstorming

 

With a point of view in mind, I began brainstorming to come up with ideas for features and an interface. This session proved most productive. I came up with many features and needs that I would choose to implement and meet if the project were more long term. But what was most useful for our particular scale is that I began to refine the user interface experience. I moved from four random buttons (Rather non-intuitive – what are the mappings for these buttons? No affordances to narrow it down.) to a direction pad (more intuitive) and two buttons.

 

Figure 2: Brainstorming session. Note the evolution of the interface/control scheme.

 

Design

 

I tried to design the e-reader with my point of view and brainstorming ideas in mind. I refined my ideas through a verplank diagram.

 

Figure 3: My verplank diagram. Sums up the usage of the device.

 

  • Idea: A simple e-reader for knowledge dissemination in places with limited access to books or internet. Internet connectivity is included to obtain additional content when internet is available.

  • Metaphor: Like a Kindle for Wikipedia and/or useful tips to increase productivity and decrease poverty.

  • Model: A small, sturdy, Gameboy-advance like device.

  • Controls: Simple direction pad and selection buttons.

  • Error: Electronics are often expensive and difficult to interface with.

  • Display: A simple menu system.

  • Tasks: Playback of text-files, internet acquisition of new files.

  • Scenario: Usage in places with limited power and/or internet connectivity.

 

Execution

 

At this point, I had a fairly strong idea of what type of device I was trying to make. Now, the work was split into three tasks: building the case, assembling the hardware, and writing the software.

 

Case

Designing the case required a significant amount of work, but was made much easier due to the helpfulness of the lab manager, Marlo. As I was setting up m files in Solidworks I realized that I could simplify the interface once more, and use only four buttons. I realized a d-pad was all I really needed to navigate the menus. With this in mind, I settled on a separated d-pad design.

 

Figure 4: Separated d-pad interface

 

The rest of the case was designing a tabbed box in Solidworks. Unfortunately, I had to make the device much larger than I was hoping. Fitting a perf-board prototype in a case is a lot different than a PCB, so I decided to leave my

GBA-size aspirations for a future revision.

 

Figure 5: Size of the prototype case. Some 7x3x2.5 inches.

 

Electronics

I started by building the entire device on a breadboard. This involved the following components:

  • 1 20x4 White on Blue character LCD from hacktronics

  • 1 Wiznet812MJ Ethernet adapter for Teensy

  • 4 buttons

  • 1 microSD card/Sd card adapter

  • Miscellaneous wires, resistors, capacitors, etc.

 

It came out like so:

 

Figure 6: Breadboard prototype

 

With the device assembled, the next step was to move everything to a perf-board. This step proved extremely time consuming. I could not use a bus-board due to the odd pinout (10x2 parallel) of the Ethernet adapter. Making every connection manually is a slow and infuriating process. After some 10 hours, it was done.

 

Figure 7: Perfboard prototype

 

Note that the device runs off a AA battery stepped up to 5V. This is because the LCD was a 5V device (for power – 3.3V was fine for communication). The battery output was divided between the LCD and the 5V V-in on the bottom of the Teensy. In the future, I would implement some rechargeable solution, perhaps designed around the prevalence of 12V car batteries in the developing world.

 

Software

The next step was writing all of the software. Major hurdles included:

  • Writing a text-based interface with scrolling menus.

  • Properly outputting a “page” of text.

  • “Flipping” through “pages” of text

  • Downloading content from the internet

 

One should not underestimate the difficulty of any of these tasks. The final (internet) task was particularly tricky because of network connectivity. Connecting to the internet on a secure network connection is difficult. One must set the MAC address of the wiznet device (done in software) and then make sure the devices IP address (also assigned in software) will be validated. This might happen automatically, if you have selected an unused IP. It also might require communicating with a network administrator.

 

There are also a variety of ways one could approach the internet portion of this project. Due to time constraints, mine was very simple. I connected to a dedicated text file on my Stanford webspace an downloaded that file to the e-reader. This, however, does not provide a way for one to keep downloaded content and not overwrite it when something new comes along.

 

On this same train of thought, I did not have time to implement a way to remove or organize files. With more time, I would have implemented a more robust file management system. This might have pushed the bounds of the microcontroller, however, which was already at some 70% code capacity.

 

The state diagram came out as such:

Figure 8: State diagram.

 

The right button controls entering deeper into the menu system, while the left pulls one out. Left and Right also flip through pages of text (though this might chance to up and down based on user trials). Up and down are used for scrolling up and down through the home menu and file list.

 

Overall

The device came out looking like this:

 

 

Evaluation

 

Design: I believe my design plan was well formulated. The interface was quite functional, fairly intuitive, and very simple. The TeensyReader accomplishes its function (reading text files, downloading content from the internet) well with a minimum of complexity.

 

Execution: My execution was middling. The device was functional, but there was plenty of room for improvement. If I had more time, I would improve on the case. I would affix the buttons more securely (with some sort of glue instead of electrical tape). I would also have minimized the case size based on how large the perfboard came out. I would also install some sort of standoffs to create a sort of stand or slot for the perfboard, and affix the button using molex connectors so that the board could be easily removed and reconnected. Additionally, I would have liked to make the case of out a sturdier plastic instead of basswood. Overall, the device functioned – but at a minimum of true robustness, ease of use, and style.

 

Point of View: It is difficult to say if the device fit the point of view without field testing. “Action research,” is what educational school CTO Dr. Paul Kim calls it. I believe the device had the potential to meet the PoV well. However, a much more refined prototype and significant field tests would be required to truly determine this.

 

Process: My process was not ideal, but more or less as good as it could have been. The project was intensive both electronics-wise and software-wise. This left little time for refining the case and overall presentation, which would have been nice. I worked toward an ambitious bottom line – a functioning prototype of an internet connected e-reader – and in that sense I was successful and managed my time well. But in a perfect world, I would have worked longer hours to give each piece of the project the time it deserved.

 

Documentation: I believe my documentation addresses well the design considerations of the device as well as the difficulties of implementation. Combined with my project code, one could surely replicate this effort and hopefully improve upon it.

 

Video

 


 

Code

 

Master file:

 

// Use 40*13 = 520 bytes of EEPROM

#define MAX_FILE_COUNT 40

#define MAX_NAME_LENGTH 13

 

#include <mp3.h>

#include <mp3conf.h>

 

// include the SD library:

#include <SD.h>

#include <EEPROM.h>

 

//include LCD control

#include <LiquidCrystal.h>

 

//include ethernet

 

#include <SPI.h>

#include <Ethernet.h>

 

// Connections:

// rs (LCD pin 4) to Teensy pin 20

// rw (LCD pin 5) to Teensy pin 19

// enable (LCD pin 6) to Teensy pin 18

// LCD pin 15 to Vcc

// LCD pins d4, d5, d6, d7 to Teensy pins 16-13

LiquidCrystal lcd(20, 19, 18, 16, 15, 14, 13);

 

long lastDebounceTime = 0;  // the last time the output pin was toggled

long debounceDelay = 350;    // the debounce time; increase if the output flickers

int xPos = 0;

int yPos = 0;

int yV = 1;

 

//String txt = "I believe that to meet the challenges of our times, human beings will have to develop a greater sense of universal responsibility. Each of us must learn to work not just for one self, one's own family or one's nation, but for the benefit of all humankind.";

char text[81];

 

int maxLength = 0;

int charStart = 0;

int charStop = 80;

 

// set up variables using the SD utility library functions:

Sd2Card card;

SdVolume volume;

SdFile root;

File doc;

int numFiles = 5;

 

#define HOME 1

#define LIST 2

#define READ 3

#define NET 4

 

//

//state control variable

// 1 = HOME

// 2 = LISTFILES

// 3 = READ

// 4 = NET

 

int state = HOME;

 

#define DEBUG // Comment this line to remove debugging features

#define sd_cs       12             // 'chip select' for SD car

#define read_buffer 512           // size of the microsd read buffer

 

//ethernet stuff

 

// Enter a MAC address and IP address for your controller below.

// The IP address will be dependent on your local network:

 

byte mac[] = {    

  0x1A, 0xAA, 0x2B, 0xBB, 0x3C, 0xCC };

byte ip[] = { 

  172,24,89,114 };

byte gateway[] = { 

  172,24,89,1 };

byte subnet[] = { 

  255,255,255,0 };

byte server[] = { 171,64,13,26 }; // stanford

 

Client client(server, 80);

/*

 * initialize the processor speed, setup the fatfs sd card (or, mms)

 * filesystem, setup mp3 playback and register the pins used (device

 * specific configuration)

 */

void setup() {

 

  //null terminate the doc-storage string

  text[80] = '\0';

 

  Serial.begin(9600);            // initialize the serial terminal

 

  pinMode(SS_PIN, OUTPUT);     // change this to 53 on a mega

  digitalWrite(SS_PIN, HIGH);

 

  // see if the card is present and can be initialized:

  if (!SD.begin(sd_cs)) {

    Serial.println("Card failed, or not present");

    // don't do anything more:

    return;

  }

 

  // we'll use the initialization code from the utility libraries

  // since we're just testing if the card is working!

  if (!card.init(SPI_HALF_SPEED, sd_cs)) {

    Serial.println("Initialization failed. Things to check:");

    Serial.println("* is a card is inserted?");

    Serial.println("* Is your wiring correct?");

    Serial.println("* did you change the SD chipSelect pin to match your setup?");

    return;

  }

 

  // Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32

  if (!volume.init(card)) {

    Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");

    return;

  }

 

  if (!root.openRoot(&volume)) {

    Serial.println("Failed to open root");

    // don't do anything more:

    return;

  }  

 

  Serial.println("Card initialized.");

  Serial.println("\nFiles found on the card (name, date and size in bytes): ");

  // list all files in the card with date and size

  root.ls(LS_R | LS_DATE | LS_SIZE);

 

  Serial.println("Checking free memory...");

  Serial.print("There are ");

  Serial.print(get_free_memory(), DEC);

  Serial.println(" bytes of free memory.");

  Serial.print("~");

  Serial.print(read_buffer, DEC);

  Serial.println(" free bytes required for mp3 playing.");

 

  // List all files to in the root directory. 

  // Maximum of MAX_FILE_COUNT files will be read  

 

  numFiles = ListFiles(&root);

  root.close();

 

  //LCD Stuff

 

  lcd.begin(20,4);              // columns, rows.  use 16,2 for a 16x2 LCD, etc.

  lcd.clear();                  // start with a blank screen

  lcd.setCursor(0,0);           // set cursor to column 0, row 0 (the first row) 

  lcd.blink();

 

  //state and UI control

 

  attachInterrupt(0, buttonL, RISING);

  attachInterrupt(1, buttonR, RISING); 

  attachInterrupt(2, buttonU, RISING);

  attachInterrupt(3, buttonD, RISING);

 

  //printTxt(txt, charStart, charStop);    

  //printLines(4);  

  home();

}

 

// Do nothing for now

void loop() {

  while (1);

 

File Management

 

/*

 * Adopted from the ladyada.net tutorial on Ethernet protocol 

 * @http://www.ladyada.net/learn/arduino/ethfiles.html

 *

 * Lists all files from a directory:

 * ListFiles(SdFile *dir, char *doubleBuf, int maxCount)

 *

 * dir is the directory to list

 * doubleBuf is the buffer to store the results in -- this should be fileNames to start

 * maxCount is the size of the buffer.

 *

 * returns the number of file names written into the doubleBuf array

 */

int ListFiles(SdFile *dir) {

  int count = 0;

  dir_t p;

 

  dir->rewind();

  while (dir->readDir(&p) > 0 && count < MAX_FILE_COUNT) {

    // done if past last used entry

    if (p.name[0] == DIR_NAME_FREE) break;

 

    // skip deleted entry and entries for . and  ..

    if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') continue;

 

    /* Uncomment to allow subdirectories to be listed (with a '/' after the name) */

    // only list subdirectories and files

    //if (!DIR_IS_FILE_OR_SUBDIR(&p)) continue;

 

    /* Uncomment to only allow files to be listed */

    // only list files

    if (!DIR_IS_FILE(&p)) continue;

 

    // print file name into string

    unsigned char pos = 0;

    for (unsigned char i = 0; i < 11; i++) {

      if (p.name[i] != ' ') {

        EEPROM.write((count*MAX_NAME_LENGTH)+pos, p.name[i]);

        pos++;

      }

    }

 

    // append slash if file is a directory

    if (DIR_IS_SUBDIR(&p)) {

      EEPROM.write((count*MAX_NAME_LENGTH)+pos, '/');

      pos++;

    }

 

    // add the end string character

    EEPROM.write((count*MAX_NAME_LENGTH)+pos, '\0');

    count++;

  }

#ifdef DEBUG

  Serial.print("Stored ");

  Serial.print(count+1);

  Serial.print(" files, using ");

  Serial.print((count+1)*MAX_NAME_LENGTH, DEC);

  Serial.println(" bytes in EEPROM.");

#endif

  return count+1;

}

 

//print out 3 files from the directory list, starting at the input variable

//does not modify the yPos variable. place the cursor where you want after the function call

void printLines(int start){  

  char name[15]; 

  int nameLength = 13;

  int count, stopAt = start+3, yTemp;

 

  Serial.print("printing lines to: ");

  Serial.println(stopAt);

 

  lcd.clear();

  lcd.home();

  lcd.print("ROOT > ");

  lcd.print(numFiles);

  lcd.print(" files");

 

  if(stopAt > numFiles-1){

    Serial.print("printlines fail");

    return;

  }  

 

  yTemp = 1; 

 

  for (count = start; count < stopAt; count++){

    Serial.print(count);

 

    lcd.setCursor(0, yTemp);    

 

    for (unsigned char i = 0; i < 11; i++){

      name[i] = EEPROM.read(count*MAX_NAME_LENGTH+i);      

      if(name[i] == '\0'){

        nameLength = i;

        break;

      }      

    }

 

    Serial.print(" - ");

    Serial.println(name);

 

    if (nameLength > 4) {

      name[nameLength+1] = '\0';

      name[nameLength] = name[nameLength-1];

      name[nameLength-1] = name[nameLength-2];

      name[nameLength-2] = name[nameLength-3];

      name[nameLength-3] = '.';

      nameLength++;

    }    

 

    lcd.print(name);

    yTemp++;

 

  }

 

  //yPos = 1;

  //lcd.setCursor(xPos, yPos);

}

 

LCD/Display Control

 

//home screen

void home(){

 

  lcd.clear(); 

  lcd.home();  

  lcd.print("HOME SCREEN");

  lcd.setCursor(0, 1);

  lcd.print("1. Read An Article");

  lcd.setCursor(0,2);

  lcd.print("2. Download Article");  

 

  xPos = 0;

  yPos = 1;

  lcd.setCursor(xPos, yPos);

}

 

//part of the interrupt handling/debouncing

int checkLegit(long time){

  if(time - lastDebounceTime > debounceDelay){

    lastDebounceTime = time;

    return HIGH;

  } 

  else {

    lastDebounceTime = time;

    return LOW;

  }    

}

 

//retrieves a portion of a file

void getDoc(int start, int stp){

 

  //clear buffer

  for (int j = 0; j < 80; j++){

    text[j] = NULL; 

  }

 

  //refill buffer with new junk  

  doc.seek(charStart);

 

  char bite = doc.read();

  for (int i = 0; i < 80 && bite != -1; i++){

    text[i] = bite;

    bite = doc.read();

  }  

 

  Serial.println(text);

 

  charStart = stp-80;

 

  Serial.println(charStart);  

}

 

//prints contents of text array to lcd

void printDoc(){

 

  lcd.clear();

 

  int y = 0, subCount = 0;

  lcd.setCursor(0, y);

 

  for (int i = 0; i < 80 && text[i] != NULL; i++){       

 

    if(subCount == 20 && y != 3){

      lcd.setCursor(0, ++y);

      subCount = 0;

    } 

    else if(subCount == 20 && y == 3) {

      lcd.clear();

      y = 0;

      lcd.home();

      subCount = 0;     

    }

    lcd.print(text[i]);

    subCount++;

  }

 

}

 

Internet Code

 

void netCon() { // start the Ethernet connection:

 

  Ethernet.begin(mac, ip, gateway, subnet);

  // start the serial library:

  Serial.begin(9600);

  // give the Ethernet shield a second to initialize:

  delay(1000);

  Serial.println("connecting...");

 

 

  lcd.clear();

  lcd.home();

  lcd.print("ETHERNET HOME");

  lcd.setCursor(0,2);

  lcd.print("connecting....");

  delay(2000);

 

  // if you get a connection, report back via serial:

  if (client.connect()) {

    Serial.println("connected");

 

    lcd.setCursor(0,2);

    lcd.print("connected! :)");

    busyWait();   

 

    delay(2000);

 

    // Make a HTTP request:

    client.println("GET /~ajdupree/daily.txt HTTP/1.0");

    client.println();

 

    // if there are incoming bytes available 

    // from the server, read them and print them:

    SD.remove("daily.txt");

 

    if(!(doc = SD.open("daily.txt", FILE_WRITE))){

      Serial.println("error opening daily");

      return;

    }

 

    doc.seek(0);

 

    int skipHeader = 0;

 

    while (client.connected()) {

 

      if(skipHeader < 240){

        client.read();

        skipHeader++;

      } 

      else {      

        char c = client.read();

        Serial.print(c);

        doc.write(c);       

      }

    }

 

    lcd.setCursor(0,2);

    lcd.print("the daily updated!");

    delay(2000);

    busyWait();

    doc.close();

 

  } 

  else {

    // kf you didn't get a connection to the server:

    Serial.println("connection failed");

    lcd.setCursor(0,2);

    lcd.print("connection failed :(");

    delay(2000);

    busyWait();

  }

 

  // if the server's disconnected, stop the client:

  if (!client.connected()) {

    Serial.println();

    Serial.println("disconnecting.");

    client.stop();

  }

}

 

void busyWait(){

  /* int bw = 25000;

   for(int i = 0; i < bw; i++){

   // Serial.println("waitin");

   } */

}

 

 

State Control

 

//controls what happens when the left button is pressed

void buttonL(){

 

  if(!(checkLegit(millis()))){

    return;

  }

 

  Serial.println("left");

 

  switch(state)

  {

  case HOME:

    break;

 

  case LIST:

    state = HOME;

    home();

    break;

 

  case READ:

 

    Serial.println("b1");

 

    if(charStart < 80) {

      doc.close();

      state = LIST;

      printLines(0);  

      yPos = 1;

      yV = 1;

      lcd.setCursor(xPos, yPos);

    } 

    else {    

      charStart -= 80;

      charStop -= 80;

      lcd.clear();

      getDoc(charStart, charStop);  

      printDoc();

    }

 

    break;

 

  case NET:

    break;

 

  } 

 

}

 

//controls what happens when the right button is pressed

void buttonR(){   

 

  if(!(checkLegit(millis()))){

    return;

  }

 

  Serial.println("right");

 

  char name[15]; 

  int nameLength = 13;

  int count; 

 

  switch(state)

  {

  case HOME:

 

    if(yPos == 1){

      state = LIST;

      printLines(0);  

      yPos = 1;

      lcd.setCursor(xPos, yPos);

    } 

    else if( yPos == 2){

      state = NET;

      netCon();

      state = HOME;

      home();

    }

    break;

 

 

  case LIST:

 

    count = yV - 1;

 

    for (unsigned char i = 0; i < 11; i++){

      name[i] = EEPROM.read(count*MAX_NAME_LENGTH+i);      

      if(name[i] == '\0'){

        nameLength = i;

        break;

      }      

    }

 

    name[nameLength+1] = '\0';

    name[nameLength] = name[nameLength-1];

    name[nameLength-1] = name[nameLength-2];

    name[nameLength-2] = name[nameLength-3];

    name[nameLength-3] = '.';

    nameLength++;

 

    Serial.print("Name: ");

    Serial.println(name);

 

    if(SD.exists(name)){      

 

      doc = SD.open(name);

      if(doc.size() == 0){

        return;

      }

      charStart = 0;

      charStop = 80;

      maxLength = doc.size();

      state = READ;

 

      Serial.print("File exists/opened. Size: ");

      Serial.println(maxLength);

 

      getDoc(charStart, charStop);

      printDoc();      

    } 

    else {

      Serial.println("file name does not exist");

    }

 

    break;

 

  case READ:

 

    Serial.print(maxLength);

    Serial.println(" - read state right");   

 

    if(charStop > maxLength){  

    } 

    else {    

      charStart += 80;

      charStop += 80;

      lcd.clear();      

      getDoc(charStart, charStop);

      printDoc();

 

      Serial.print("charStart ");

      Serial.print(charStart);

      Serial.print(" charStop ");

      Serial.println(charStop);

    }

 

    break;

 

  case NET:

    break;

 

  }

 

}

 

//when the Up button is pressed

void buttonU(){  

 

  if(!(checkLegit(millis()))){

    return;

  }

 

  Serial.println("Up");

 

  switch(state)

  {

  case HOME:

 

    if(yPos == 2){

      yPos = 1;

      lcd.setCursor(xPos, yPos);

    }

 

    break;

 

  case LIST:

 

    if(yV > 1 && yPos != 1){

      yV--;

      yPos--;

      lcd.setCursor(0, yPos);

    } 

    else if (yV > 1 && yPos == 1) {

      yV--;

      yPos = 1;

      printLines(yV-1);

      lcd.setCursor(0, yPos); 

    }

 

    break;

 

  case READ:

    break;

 

  case NET:

    break;

 

  }

 

}

 

//when the down button is pressed

void buttonD(){

 

  if(!(checkLegit(millis()))){

    return;

  }

 

  Serial.println("down");

 

  switch(state)

  {

  case HOME:

 

    if(yPos == 1){

      yPos = 2;

      lcd.setCursor(0, yPos);

    }

 

    break;

 

  case LIST:

 

    if (yV <  numFiles-1 && yPos != 3){

      yV++;

      yPos++;

      lcd.setCursor(0,yPos);      

    } 

    else if (yV < numFiles-1 && yPos == 3){

      yV++;

      yPos = 3;

      printLines(yV-3);

      lcd.setCursor(0, yPos);

 

    }  

 

    Serial.print("yPos: ");

    Serial.print(yPos);

    Serial.print(" yV: ");

    Serial.println(yV);

 

    break;

 

  case READ:

    break;

 

  case NET:

    break;

 

  }

 

}

 

 

 

 

 

 

 

 

 

 

 

Comments (1)

Akil Srinivasan said

at 9:32 pm on Aug 15, 2011

We really liked your descriptive video - very helpful for others, especially the problems you ran into. Your use scenarios, mind mapping, and interaction design were fantastic! We really like how you motivated your project with your own personal goals. Good job getting a tough new chip to work.

We wish your code could have been a little more extensively commented. It was a little hard for us to understand some things. We also wish we could see how your e-reader fit in the context of other low-cost readers (what's out there right now: One laptop per child, etc), but we do understand you're exploring the space. We also would have liked to see more description of the pinouts, protocol, and how you got it all to work.

So, if you had a chance to re-do this project, keeping in mind your intended use environment, how might you change the design or what parts might you swap out? (eg, would you use wireless ethernet instead or if so, would that blow your budget).

Overall, great work! We can see how much effort you put into it.

David, Akil, and Ben

You don't have permission to comment on this page.