Feedback!

SD Navigator

Views: 6101 Difficulty: 4 Status: Development
Sd_card_baby

Display bitmap images and text files and folders with this TFT LCD screen and SD Card navigator.

SD cards can hold tons of ones and zeros. This tutorial shows how to make the Joy Gamer navigate an SD card and display plain text files and bitmap images. We build upon the standard SD card library. The code shown here forms the basis for a number of our other tutorials, including the face morpher, the thermal printer, and the time lapse camera, which all use SD cards for external memory.

Root Directory

Sd_nav_plain
The file the joystick cursor is hovering over is highlighted in yellow.

SD Navigator Video

See the SD Navigator in action! It can display the files in a directory, open bitmaps and plain text files. The cursor is the little red circle and it is controlled by the joystick.

SD Navigator Arduino Code

Here is the Arduino code behind the SD card navigator on the Joy Gamer. We are pushing right up against the edge of what the little Atmega32u4 chip can handle so we had to hack a bit to make this work. First in the JoyGamer.h file you have to comment out the defines like this:
//#define USE_ACCELEROMETER
//#define USE_COLOR
This way we don't load libraries we won't use and we can fit all the SD navigator code on the device. Otherwise you will get the nasty sketch too big error.
Okay now that the code compiles let's have a bit of explanation. This code has 3 modes: display a directory, display a bitmap or display a text file. There is a global variable cur_file which is either the directory, bitmap or text file we are currently viewing. We keep track of the modes using an enum defined at the top of the file. This is a little cleaner and easier to read than just using an int for the mode:
enum modes_t{
  DISPLAY_DIRECTORY,
  DISPLAY_BITMAP,
  DISPLAY_TEXT
};

modes_t mode = DISPLAY_DIRECTORY;
Then in the main loop we have switch statement on the current mode. In the setup function we open the root directory and set it as the cur_file in the function open_file(). This function sets the mode appropriately given the type of file fed in as an argument.
Throughout the code we check if jg.btn_pin is pressed and if so we jump back to displaying the root directory. Links to all the necessry libraries can be found at the Joy Gamer Tutorial. See the whole file below:
/* LucidTronix Joy Gamer SD Navigator
 * If the compiler gives you an error about sketch being too big make sure to
 * COMMENT OUT both the USE_ACCELEROMETER and the USE COLOR defines in the JoyGamer.h file.  
 * They should look like this:
 //#define USE_ACCELEROMETER
 //#define USE_COLOR
 * For more instructions, details and schematic, See:
 * http://www.lucidtronix.com/tutorials/21
 */

#include <JoyGamer.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library
#include <SPI.h>
#include <SD.h>

Adafruit_ST7735  tft = Adafruit_ST7735(SS, 9, 8);
JoyGamer jg = JoyGamer(&tft);

enum modes_t{
  DISPLAY_DIRECTORY,
  DISPLAY_BITMAP,
  DISPLAY_TEXT
};

modes_t mode = DISPLAY_DIRECTORY;

int cursor_x = 64;
int cursor_y = 64;

File cur_file;
File selected_file;
int cur_dir_files;
const int max_files = 12;
String files[max_files];

int line_height = 12;
int char_width = 6;
unsigned long last_switch = 0;

void setup(){
  jg.initialize();
  jg.start_sd_card();
  open_file("/");
}

void loop(){
  switch(mode){
    case DISPLAY_DIRECTORY:
      handle_cursor();
      for(int i = 0; i < cur_dir_files; i++){
        if(cursor_y > i*10+ 10 && cursor_y < i*10 + 20){
          jg.draw_string(10, i*10 + 10, files[i], ST7735_MAGENTA);
          if(digitalRead(jg.joystick_btn_pin) == HIGH) {
            open_file(files[i]);
            break;
          }
        } 
        else {
          jg.draw_string(10, i*10 + 10, files[i], ST7735_BLACK);
        }
        if(digitalRead(jg.btn_pin) == LOW && millis() - last_switch > 500) {
          open_file("/");
          break;
        }
      }
      break;
    case DISPLAY_TEXT:
      jg.clear_screen(ST7735_WHITE);
      jg.clear_screen();
      display_text_file();
      break;
    case DISPLAY_BITMAP:
      jg.bmpdraw(cur_file);
      delay(2000);
      open_file("/");
      break;
  }
}

void handle_cursor(){
  tft.fillCircle(cursor_x, cursor_y, 5, ST7735_WHITE);
  int delta_x = map(analogRead(0), 0, 1024, -6, 6); 
  int delta_y = map(analogRead(1), 0, 1024, -8, 8);
  cursor_x = constrain(cursor_x + delta_x, 5, 123);
  cursor_y = constrain(cursor_y + delta_y, 5, 155); 
  tft.fillCircle(cursor_x, cursor_y, 5, ST7735_GREEN);
}

void open_file(String filename){
  jg.clear_screen(ST7735_WHITE);
  cur_file.seek(0);
  cur_file.close();
  delay(100);
  char buffer[13];
  filename.toCharArray(buffer, 13);
  cur_file = SD.open(buffer);
  filename.toLowerCase();
  if(cur_file.isDirectory()){
    mode = DISPLAY_DIRECTORY;
    load_dir(cur_file);
  }
  else if(filename.lastIndexOf(".bmp") != -1){
    mode = DISPLAY_BITMAP;
  }
  else {
    mode = DISPLAY_TEXT;
  }
  last_switch = millis();
}

void load_dir(File dir){
  for(int i = 0; i < max_files; i++) files[i] = "";
  jg.clear_screen(ST7735_WHITE);
  dir.rewindDirectory();
  File entry =  dir.openNextFile();
  cur_dir_files = 0;
  while(entry && SD.exists(entry.name()) && max_files > cur_dir_files){
    String filename = entry.name();
    if(filename[0] != '.' && filename[0] != '_' && filename.lastIndexOf('~') == -1){ // don't show hidden files
      files[cur_dir_files] = filename;
      cur_dir_files++;
    }
    entry.close();
    entry = dir.openNextFile();
  }
  dir.rewindDirectory();
}

void display_text_file(){
  int aax = 2;
  int aay = 3;
  tft.setTextColor(ST7735_BLACK);
  while(cur_file && cur_file.available() && aay < 150){
    char cur_c = cur_file.read();
    if(cur_c == 10 || cur_c == 13 || (cur_c == ' ' && aax > 85)){
      aay += line_height;
      aax = 2;
    }
    else {   
      tft.setCursor(aax, aay);
      tft.print(cur_c);
      aax += char_width;
      if (aax >= 118) {
        aax = 2;
        aay += line_height;
      }
    }
    delay(10);
    if(digitalRead(jg.btn_pin) == LOW && millis() - last_switch > 500) {
      open_file("/");
      break;
    }
  }
  delay(1500);
}

Parts

Title Description # Cost Link Picture
Joy Gamer Joyous Arduino-Based Gaming machine! Full color LCD screen, Joystick with 3D printed knob and enclosure. 1 $79.95 Link Gamers
Permalink: http://lucidtronix.com/tutorials/21
Mixing pixels from two face images together....
Our take on the classic bouncing ball, paddle game....
Print bitmaps, cellular automata, drawings and text.....
Control the hue, value and saturation on this pocket-sized joystick drawing machine....
3d view of a face using an SD card, accelerometer and a TFT LCD Screen...
Handheld gaming machine, based on the Arduino Leonardo, equipped with joystick, SD card and more....
Play pong on a little LCD screen with two big joysticks....
Use the Joy Gamer to make a dynamic picture frame....