Thursday, 17 January 2013

Programming the 2.8" TFTLCD / Touch Screen / uSD Shield

The Adafruit 2.8" TFT Touch Shield for Arduino provides some challenges in geometry, since the touchscreen coordinates don't match the TFT coordinates. The scale is different and the rotation frame is 180 degrees off. It also includes a microSD (uSD) slot hidden underneath, wired to the SPI pins 11,12,13 with the card select wired to pin 5. The TFT seems to use pins 4, 5, 6, 7, 8, 9, 10, 11, 13, A0, A1, A2, and A3. The touchscreen uses pins 6, 7, A1, and A2. Getting them to play nice while sharing all those pins looks like a challenge, but the sample software works fine for me right out of the box on an UNO.

Reading the touch screen and writing to the TFT are both slow processes with the supplied software libraries.

These are the coordinate systems for the shield with tft.setRotation(0), which is the default. It may make a little more sense if you rotate the entire picture 180 degrees so that the USB end of the Arduino is the top and the TFT coords will run left to right and top to bottom.

The square buttons can go anywhere on the screen,
with 16 bit colors and text overlays in color of your choice.  
The test application helps find the limits of the touch
screen for calibrating the library call to get the mapping right.
It would be nice to have some way of scrolling a section of the screen, e.g. for a scrolling chart recorder, but for now I can make do drawing lines and boxes. I'm working on a library that defines a single TFT/TS class and does some other cool stuff to draw buttons, graphs and otherwise make using the combination a little more seamless. Now posted on github, including an example program that follows the steps below to get all three features working together.

Getting the SD Card to work alongside the TFT and Touch Screen

Adafruit provides an example for copying bitmap files to the screen from a uSD card, that doesn't use the touch screen. They also provide an example for a really simple paint program that uses the TFT and the touch screen, but not the uSD. I wanted to be able to use the TFT and the touch screen and record data from the program to the uSD for a log of what happened. I found that really hard to accomplish because I wasn't paying enough attention to the hardware conflicts. In addition to getting the pin directions right, the status of the SPI control register has to be preserved separately for TFT operations and uSD operations. The sketch below is a copy of the Adafruit paint example that does that to record a log file to the uSD. I hope the insight saves you some time.

Testing also shows that you can open multiple files for output simultaneously by following the obvious steps in parallel. (1 SD object, two separate File objects.) Watch out because these three libraries together take a lot of code space, putting you up against the 32K barrier on the UNO.

// Paint example specifically for the TFTLCD Arduino shield.
// If using the breakout board, use the tftpaint.pde sketch instead!
//************************************************************
// Modified by RWS to record data to a log file on the SD card
// Crucial stuff about saving the SPI control register state
// marked with asterisks like these ones....
//************************************************************

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_TFTLCD.h> // Hardware-specific library
#include <TouchScreen.h>
#include <SD.h>

#define SD_CS 5 // Card select for shield use

// These are the pins for the shield!
#define YP A1  // must be an analog pin, use "An" notation!
#define XM A2  // must be an analog pin, use "An" notation!
#define YM 7   // can be a digital pin
#define XP 6   // can be a digital pin

#define TS_MINX 150
#define TS_MINY 120
#define TS_MAXX 920
#define TS_MAXY 940

// For better pressure precision, we need to know the resistance
// between X+ and X- Use any multimeter to read it
// For the one we're using, its 300 ohms across the X plate
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);

//#define LCD_CS A3          // these defines don't seem to be needed in the sketch
//#define LCD_CD A2          // although they are a hint that the TFTLCD may use those pins
//#define LCD_WR A1
//#define LCD_RD A0

// Assign human-readable names to some common 16-bit color values:
#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF


Adafruit_TFTLCD tft;
uint8_t         spi_save_tft,spi_save_sd;

#define BOXSIZE   40
#define PENRADIUS  4
int oldcolor, currentcolor;

File logfile;

void setup(void) {
  Serial.begin(9600);
  progmemPrintln(PSTR("Paint!"));

  tft.reset();

  uint16_t identifier = tft.readID();

  if(identifier == 0x9325) {
    progmemPrintln(PSTR("Found ILI9325 LCD driver"));
  } else if(identifier == 0x9328) {
    progmemPrintln(PSTR("Found ILI9328 LCD driver"));
  } else if(identifier == 0x7575) {
    progmemPrintln(PSTR("Found HX8347G LCD driver"));
  } else {
    progmemPrint(PSTR("Unknown LCD driver chip: "));
    Serial.println(identifier, HEX);
    return;
  }

  tft.begin(identifier);

  tft.fillScreen(BLACK);

  tft.fillRect(0, 0, BOXSIZE, BOXSIZE, RED);
  tft.fillRect(BOXSIZE, 0, BOXSIZE, BOXSIZE, YELLOW);
  tft.fillRect(BOXSIZE*2, 0, BOXSIZE, BOXSIZE, GREEN);
  tft.fillRect(BOXSIZE*3, 0, BOXSIZE, BOXSIZE, CYAN);
  tft.fillRect(BOXSIZE*4, 0, BOXSIZE, BOXSIZE, BLUE);
  tft.fillRect(BOXSIZE*5, 0, BOXSIZE, BOXSIZE, MAGENTA);
  // tft.fillRect(BOXSIZE*6, 0, BOXSIZE, BOXSIZE, WHITE);
  tft.drawRect(0, 0, BOXSIZE, BOXSIZE, WHITE);
  currentcolor = RED;
                                //*******************************************************************
  spi_save_tft = SPCR;          // save the state of the SPI control register for TFT before SD stuff
                                //*******************************************************************
  progmemPrint(PSTR("Initializing SD card...")); //*****************************************************
  if (!SD.begin(SD_CS)) {                        // SD code from the Adafruit data logger shield example
    progmemPrintln(PSTR("failed!"));             //*****************************************************
    return;
  }
  progmemPrintln(PSTR("OK!"));
  // create a new file
  char filename[] = "TSTLOG00.TXT";
  for (uint8_t i = 0; i < 100; i++) {
    filename[6] = i/10 + '0';
    filename[7] = i%10 + '0';
    if (! SD.exists(filename)) {
      // only open a new file if it doesn't exist
      logfile = SD.open(filename, FILE_WRITE); 
      break;  // leave the loop!
    }
  }
  if (! logfile) {
    Serial.println("bad file");
  }
  else{
    Serial.print("Log to: ");
    Serial.println(filename);
  }
                               //**********************************************************
  spi_save_sd = SPCR;          // save the state of the SPI control register after SD stuff
                               //**********************************************************

  pinMode(13, OUTPUT);      // maybe this pin 13 stuff has something to do with the SD card on SPI?
                            // or maybe it's just about blinking
}

#define MINPRESSURE 10
#define MAXPRESSURE 1000

void loop()
{
  digitalWrite(13, HIGH);
  Point p = ts.getPoint();
  digitalWrite(13, LOW);

  // if sharing pins, you'll need to fix the directions of the touchscreen pins
  // the touchscreen swaps them back and forth between input and output multiple times
  pinMode(XP, OUTPUT);
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);
  pinMode(YM, OUTPUT);
  
  // we have some minimum pressure we consider 'valid'
  // pressure of 0 means no pressing!

  if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {

    /*
    Serial.print("X = "); Serial.print(p.x);
    Serial.print("\tY = "); Serial.print(p.y);
    Serial.print("\tPressure = "); Serial.println(p.z);
    */
                             //********************************************************
    SPCR = spi_save_sd;      // reset to the SD saved state of the SPI control register
                             //********************************************************
    logfile.print("X = "); logfile.print(p.x);
    logfile.print("\tY = "); logfile.print(p.y);
    logfile.print("\tPressure = "); logfile.println(p.z);
    logfile.flush();
                              //************************************************************
    SPCR = spi_save_tft;      // reset to the non-SD saved state of the SPI control register
                              //************************************************************
    if (p.y < (TS_MINY-5)) {
      Serial.println("erase");
      // press the bottom of the screen to erase 
      tft.fillRect(0, BOXSIZE, tft.width(), tft.height()-BOXSIZE, BLACK);
    }
    // scale from 0->1023 to tft.width
    p.x = map(p.x, TS_MINX, TS_MAXX, tft.width(), 0);
    p.y = map(p.y, TS_MINY, TS_MAXY, tft.height(), 0);
    
    /*
    Serial.print("("); Serial.print(p.x);
    Serial.print(", "); Serial.print(p.y);
    Serial.println(")");
    */
    if (p.y < BOXSIZE) {
       oldcolor = currentcolor;

       if (p.x < BOXSIZE) { 
         currentcolor = RED; 
         tft.drawRect(0, 0, BOXSIZE, BOXSIZE, WHITE);
       } else if (p.x < BOXSIZE*2) {
         currentcolor = YELLOW;
         tft.drawRect(BOXSIZE, 0, BOXSIZE, BOXSIZE, WHITE);
       } else if (p.x < BOXSIZE*3) {
         currentcolor = GREEN;
         tft.drawRect(BOXSIZE*2, 0, BOXSIZE, BOXSIZE, WHITE);
       } else if (p.x < BOXSIZE*4) {
         currentcolor = CYAN;
         tft.drawRect(BOXSIZE*3, 0, BOXSIZE, BOXSIZE, WHITE);
       } else if (p.x < BOXSIZE*5) {
         currentcolor = BLUE;
         tft.drawRect(BOXSIZE*4, 0, BOXSIZE, BOXSIZE, WHITE);
       } else if (p.x < BOXSIZE*6) {
         currentcolor = MAGENTA;
         tft.drawRect(BOXSIZE*5, 0, BOXSIZE, BOXSIZE, WHITE);
       }

       if (oldcolor != currentcolor) {
          if (oldcolor == RED) tft.fillRect(0, 0, BOXSIZE, BOXSIZE, RED);
          if (oldcolor == YELLOW) tft.fillRect(BOXSIZE, 0, BOXSIZE, BOXSIZE, YELLOW);
          if (oldcolor == GREEN) tft.fillRect(BOXSIZE*2, 0, BOXSIZE, BOXSIZE, GREEN);
          if (oldcolor == CYAN) tft.fillRect(BOXSIZE*3, 0, BOXSIZE, BOXSIZE, CYAN);
          if (oldcolor == BLUE) tft.fillRect(BOXSIZE*4, 0, BOXSIZE, BOXSIZE, BLUE);
          if (oldcolor == MAGENTA) tft.fillRect(BOXSIZE*5, 0, BOXSIZE, BOXSIZE, MAGENTA);
       }
    }
    if (((p.y-PENRADIUS) > BOXSIZE) && ((p.y+PENRADIUS) < tft.height())) {
      tft.fillCircle(p.x, p.y, PENRADIUS, currentcolor);
    }
  }
}

// Copy string from flash to serial port
// Source string MUST be inside a PSTR() declaration!
void progmemPrint(const char *str) {
  char c;
  while(c = pgm_read_byte(str++)) Serial.print(c);
}

// Same as above, with trailing newline
void progmemPrintln(const char *str) {
  progmemPrint(str);
  Serial.println();
}