3.5" 480x320 TFT

The TFT display, SD card and touch screen use the SPI interface.  

Adafruit product #3651

Adafruit tutorial

   

Hardware

Feather TFT_CS Pin TFT_DC Pin TFT_RT Pin SD Card Pin
ESP8266 #0 #15 #16 #2
ESP32 #15 #33 #32 #14
Atmega32u4,
ATmega328p,
M4,
M0
#9 #10 #6 #5
Teensy #4 #10 #3 #8
WICED PA15 PB4 PC7 PC5
nRF52 #31 #11 #30 #27

 

Software

In the Arduino IDE, go to the Library Manager and install the following libraries:

  • Adafruit HX8357 Library   (search for "HX8357")
  • Adafruit GFX Library
  • Adafruit ImageReader Library   (search for "Adafruit_ImageReader")

 

Loading Your Own BMP Images

Scale your image width and height to be compatible with the display, and format as a 24 bit color BMP file.   Save to the micro SD card with a 8.3 filename format.  

 

The SD Card

Writing without flush on Feather M0 with SD card on FeatherWing 3.5" 480x320 TFT took only 0.43 ms/sample or 2.3 kHz.   Test code

 

Data Visualization

I found two data visualization libraries based on the Adafruit GFX library.   The first one by Kris Kasprzak built a function that plots x/y data to a 2D Cartesian coordinate chart.   The advantage of the 'Kris Kasprzak' function is it labels the X & Y axis scale, adds an axis label, and a title for the chart.   The disadvantage is that if you don't know the max/min of your X and Y data, you will need to do the work to figure that out before plotting the data.  

The second data visualization library I found based on the Adafruit GFX library is called "Grafici' by Marco Cattani.   This is a very powerful library, but unfortunately poorly documented.   You need to spend a fair amount of time looking over the examples provided, and the code itself in order to figure out how to use the library for your application.   The demo / example I provide is derived from the examples that came with the library, but assembled in a way to better understand how the library functions work, and how they might be applied to another project.  

 

Kris Kasprzak X-Y Chart Plotting Function


/*

  Data visualization library based on Adafruit GFX

  https://forum.arduino.cc/t/another-free-graph-function-for-plotting-in-cartesian-space/354751
  
  
 */

#include "Adafruit_GFX.h" // Hardware-specific library
#include "Adafruit_HX8357.h"
Adafruit_HX8357 tft = Adafruit_HX8357(9, 10, -1);
#include "Grafici.h"


#define LTBLUE    0xB6DF
#define LTTEAL    0xBF5F
#define LTGREEN   0xBFF7
#define LTCYAN    0xC7FF
#define LTRED     0xFD34
#define LTMAGENTA 0xFD5F
#define LTYELLOW  0xFFF8
#define LTORANGE  0xFE73
#define LTPINK    0xFDDF
#define LTPURPLE  0xCCFF
#define LTGREY    0xE71C

#define BLUE      0x001F
#define TEAL      0x0438
#define GREEN     0x07E0
#define CYAN      0x07FF
#define RED       0xF800
#define MAGENTA   0xF81F
#define YELLOW    0xFFE0
#define ORANGE    0xFC00
#define PINK      0xF81F
#define PURPLE    0x8010
#define GREY      0xC618
#define WHITE     0xFFFF
#define BLACK     0x0000

#define DKBLUE    0x000D
#define DKTEAL    0x020C
#define DKGREEN   0x03E0
#define DKCYAN    0x03EF
#define DKRED     0x6000
#define DKMAGENTA 0x8008
#define DKYELLOW  0x8400
#define DKORANGE  0x8200
#define DKPINK    0x9009
#define DKPURPLE  0x4010
#define DKGREY    0x4A49

// these are the only external variables used by the graph function
// it's a flag to draw the coordinate system only on the first call to the ChtXYplot() function
// and will mimize flicker
// also create some variables to store the old x and y, if you draw 2 graphs on the same display
// you will need to store ox and oy per each display
boolean display1 = true;
double ox, oy ;

void ChtXYplot(Adafruit_HX8357 &d, double x, double y, double gx, double gy, double w, double h, double xlo, double xhi, double xinc, double ylo, double yhi, double yinc, String title, String xlabel, String ylabel, unsigned int gcolor, unsigned int acolor, unsigned int pcolor, unsigned int tcolor, unsigned int bcolor, boolean &redraw);


void ChtPlotSin(void) {
  display1 = true;
  double x, y;
  for (x = 0; x <= 6.3; x += .1) {
    y = sin(x);
    // ChtXYplot(display, x, y, gx, gy, w, h, xlo, xhi, xinc, ylo, yhi, yinc, title, xlabel, ylabel, gcolor, acolor, pcolor, tcolor, bcolor, boolean &redraw)
    ChtXYplot(tft, x, y, 60, 290, 390, 260, 0, 6.5, 1, -1, 1, .25, "Sin Function", "x", "sin(x)", DKBLUE, RED, YELLOW, WHITE, BLACK, display1);
  }  
} // ChtPlotSin()


void ChtPlotXYdata() {
  double x, y, xlo, xhi, xinc, ylo, yhi, yinc;
  xlo = 0.0; xhi = 6.5; xinc = 1.0;
  ylo = -2.0; yhi = 2.0; yinc = 0.5;
  display1 = true;
  for (x = 0; x <= 6.3; x += .1) {
    y = sin(iGetRandSignedInt());
    // ChtXYplot(display, x, y, gx, gy, w, h, xlo, xhi, xinc, ylo, yhi, yinc, title, xlabel, ylabel, gcolor, acolor, pcolor, tcolor, bcolor, boolean &redraw)
    ChtXYplot(tft, x, y, 60, 290, 390, 260, xlo, xhi, xinc, ylo, yhi, yinc, "Random Y", "x", "y", DKBLUE, RED, YELLOW, WHITE, BLACK, display1);
  }  
} // ChtPlotXYdata()


void setup() {
  randomSeed(millis());

  tft.begin();
  tft.fillScreen(BLACK);
  tft.setRotation(1);

  ChtPlotSin();
  delay(3000); tft.fillScreen(BLACK);

  ChtPlotXYdata();
  
} // setup()


void loop() {
} // loop()


/*

  function to draw a cartesian coordinate system and plot whatever data you want
  just pass x and y and the graph will be drawn

  huge arguement list
  &d name of your display object
  x = x data point
  y = y datapont
  gx = x graph location (lower left)
  gy = y graph location (lower left)
  w = width of graph
  h = height of graph
  xlo = lower bound of x axis
  xhi = upper bound of x asis
  xinc = division of x axis (distance not count)
  ylo = lower bound of y axis
  yhi = upper bound of y asis
  yinc = division of y axis (distance not count)
  title = title of graph
  xlabel = x asis label
  ylabel = y asis label
  gcolor = graph line colors
  acolor = axi ine colors
  pcolor = color of your plotted data
  tcolor = text color
  bcolor = background color
  &redraw = flag to redraw graph on fist call only
*/


void ChtXYplot(Adafruit_HX8357 &d, double x, double y, double gx, double gy, double w, double h, double xlo, double xhi, double xinc, double ylo, double yhi, double yinc, String title, String xlabel, String ylabel, unsigned int gcolor, unsigned int acolor, unsigned int pcolor, unsigned int tcolor, unsigned int bcolor, boolean &redraw) {

  double ydiv, xdiv;
  double i;
  double temp;
  int rot, newrot;

  if (redraw == true) {

    redraw = false;
    // initialize old x and old y in order to draw the first point of the graph
    // but save the transformed value
    // note my transform funcition is the same as the map function, except the map uses long and we need doubles
    ox = (x - xlo) * ( w) / (xhi - xlo) + gx;
    oy = (y - ylo) * (gy - h - gy) / (yhi - ylo) + gy;
    // draw y scale
    for ( i = ylo; i <= yhi; i += yinc) {
      // compute the transform
      temp =  (i - ylo) * (gy - h - gy) / (yhi - ylo) + gy;

      if (i == 0) {
        d.drawLine(gx, temp, gx + w, temp, acolor);
      }
      else {
        d.drawLine(gx, temp, gx + w, temp, gcolor);
      }
      // draw the axis labels
      d.setTextSize(1);
      d.setTextColor(tcolor, bcolor);
      d.setCursor(gx - 40, temp);
      // precision is default Arduino--this could really use some format control
      d.println(i);
    }
    // draw x scale
    for (i = xlo; i <= xhi; i += xinc) {

      // compute the transform
      temp =  (i - xlo) * ( w) / (xhi - xlo) + gx;
      if (i == 0) {
        d.drawLine(temp, gy, temp, gy - h, acolor);
      }
      else {
        d.drawLine(temp, gy, temp, gy - h, gcolor);
      }
      // draw the axis labels
      d.setTextSize(1);
      d.setTextColor(tcolor, bcolor);
      d.setCursor(temp, gy + 10);
      // precision is default Arduino--this could really use some format control
      d.println(i);
    }

    //now draw the graph labels
    d.setTextSize(2);
    d.setTextColor(tcolor, bcolor);
    d.setCursor(gx , gy - h - 30);
    d.println(title);

    d.setTextSize(1);
    d.setTextColor(acolor, bcolor);
    d.setCursor(gx , gy + 20);
    d.println(xlabel);

    d.setTextSize(1);
    d.setTextColor(acolor, bcolor);
    d.setCursor(gx - 30, gy - h - 10);
    d.println(ylabel);

  }

  // the coordinates are now drawn, plot the data
  // the entire plotting code are these few lines...
  // recall that ox and oy are initialized above
  x =  (x - xlo) * ( w) / (xhi - xlo) + gx;
  y =  (y - ylo) * (gy - h - gy) / (yhi - ylo) + gy;
  d.drawLine(ox, oy, x, y, pcolor);
  // it's up to you but drawing 2 more lines to give the graph some thickness
  d.drawLine(ox, oy + 1, x, y + 1, pcolor);
  d.drawLine(ox, oy - 1, x, y - 1, pcolor);
  ox = x;
  oy = y;

} // ChtXYplot()


int iGetRandSignedInt() {
  //int  (2 bytes)
  //  signed -32768 to 32767
  //  unsigned  (uint8_t):   
  int myInt = random(32767);
  int sign = random(10);
  if (sign <= 5) {
    myInt = myInt * (-1);
  }
  return myInt;
}


float fGetLgSignedRandFloat() {
  long myLong = random(100000000, 999999999);
  long divisor;
  int expnt = random(2,6);
  switch (expnt) {
    case 2:
      divisor = 100;
      break;
    case 3:
      divisor = 1000;
      break;
    case 4:
      divisor = 10000;
      break;
    case 5:
      divisor = 100000;
    default:
      divisor = 10;
  }
  float f = (float)myLong / divisor;
  int sign = random(10);
  if (sign <= 5) {
    f = f * (-1.0);
  }
  
  return f;
} // fGetLgSignedRandFloat()

 

Grafici


/*

  Data visualization library based on Adafruit GFX
  Search for Arduino library "Grafici"
  https://github.com/cattanimarco/Grafici-GFX
  https://github.com/cattanimarco/Grafici-GFX/wiki/Examples
  
 */

#include "Adafruit_GFX.h" // Hardware-specific library
#include "Adafruit_HX8357.h"
Adafruit_HX8357 tft = Adafruit_HX8357(9, 10, -1);
#include "Grafici.h"


void ChtColors() {
  constexpr size_t data_size = 240;
  const ColorMap *colorPlots[] = { &Colors::rainbow,
                                 &Colors::temperature,
                                 &Colors::blackAndWhite,
                                 &Colors::cmy,
                                 &Colors::heat,
                                 &Colors::bright,
                                 &Colors::semaphore,
                                 &Colors::parula };
  Linear x(data_size);
  Constant y(data_size, 1.0);
  Constant opt(data_size, 1.0);

  for (size_t idx = 0; idx < sizeof(colorPlots) / sizeof(ColorMap *); idx++)
  {
    Boundary boundary;
    grafici.colorMap(*colorPlots[idx]);
    boundary.cropGridCartesian(8, 1, idx).cropAbsoluteCartesian({ 0.01, 0.01 }, { 0.01, 0.01 });
    grafici.plot(bar, x, y, x, opt, boundary);
  }
  
} // ChtColors()


void ChtSharedAxis() {
  constexpr size_t num_elem = 5;
  float array_y1[num_elem] = { 1, 2, 3, 4, 5 };
  float array_y2[num_elem] = { 2, 3, 4, 5, 6 };
  float array_x1[num_elem] = { 2, 3, 4, 5, 6 };
  float array_x2[num_elem] = { 0, 1, 2, 3, 4 };
  Linear xAxis(7);
  Linear yAxis(6, -1, 1);
  ArrayFloat yy1(array_y1, num_elem);
  ArrayFloat yy2(array_y2, num_elem);
  ArrayFloat xx1(array_x1, num_elem);
  ArrayFloat xx2(array_x2, num_elem);

  // This is the important part:  
  /* set each limit as the union (using + sign) of all the limits*/
  xx1.limits() = xx2.limits() = xAxis.limits() = (xx1.limits() + xx2.limits() + xAxis.limits());
  yy1.limits() = yy2.limits() = yAxis.limits() = (yy1.limits() + yy2.limits() + yAxis.limits());

  // Plot chart axis / grid
  grafici.colorMap(Colors::blackAndWhite);
  grafici.plot(axis, xAxis, yAxis, Constant(8, 0.1));

  // Plot first line
  grafici.colorMap(Colors::semaphore);
  grafici.plot(line, xx1, yy1, yy1);

  // Plot 2nd line
  grafici.colorMap(Colors::cmy);
  grafici.plot(line, xx2, yy2, yy2);
  
} // ChtSharedAxis()


void ChtLinearInterpolation() {
  constexpr size_t num_elem = 5;
  float array[num_elem] = { 1, 0, 2, 1, 2 };
  Linear x(num_elem);
  ArrayFloat y(array, num_elem);
  // Chose a big enough number for the samples. Too small and you risk sub-sampling issues
  // Even better chose samples = n + (n-1) * x
  LinearInterpolator dataLinear{ x, y, y, y, 85 };
  Boundary leftBoundary;

  // plot chart axis & grid
  grafici.colorMap(Colors::blackAndWhite);
  //plot(Display, DataSet, Boundary, ColorMap);
  grafici.plot(axis, Linear(25), Linear(10), Constant(25, 0.1), leftBoundary);

  grafici.colorMap(Colors::rainbow);
  grafici.plot(line, dataLinear);
  
} // ChtLinearInterpolation()


void ChtBar() {
  constexpr size_t source_data_size = 9;
  float array[source_data_size] = { 0.1, 2.1, 0.2, 1.5, 0.3, 0.6, 0.3, 1.1, 0.0 };
  ArrayFloat y(array, source_data_size);
  Boundary leftBoundary;
  
  // Boundary &cropGridCartesian(rows, columns, row, column)
  // below results in full screen width chart
  leftBoundary.cropGridCartesian(1, 1, 0, 0);
  // below results in 1/2 screen width chart
  //leftBoundary.cropGridCartesian(1, 2, 0, 0);

  // plot chart axis & grid
  grafici.colorMap(Colors::blackAndWhite);
  //plot(Display, DataSet, Boundary, ColorMap);
  grafici.plot(axis, Linear(25), Linear(10), Constant(25, 0.1), leftBoundary);
  
  grafici.colorMap(Colors::parula);
  //plot(Display, DataSet, Boundary, ColorMap);
  grafici.plot(bar, Linear(source_data_size), y, Constant(source_data_size, 1.0));
    
} // ChtBar


void ChtPolar() {
  constexpr size_t source_data_size = 9;
  float array[source_data_size] = { 0.1, 2.1, 0.2, 1.5, 0.3, 0.6, 0.3, 1.1, 0.0 };
  Linear x(source_data_size);
  ArrayFloat y(array, source_data_size);
  Constant opt(source_data_size, 0.5);
  constexpr size_t spline_size = 33;
  SplineInterpolator dataSpline{ x, y, y, opt, spline_size };
  Boundary leftBoundary;
  PolarBoundary rightBoundary;
  
  // plot polar grid
  grafici.colorMap(Colors::blackAndWhite);
  grafici.plot(axis, Linear(25), Linear(10), Constant(25, 0.1), rightBoundary);
  
  grafici.colorMap(Colors::heat);
  grafici.plot(bar, dataSpline, rightBoundary);

} // ChtPolar()


void ChtScatter() {
  constexpr size_t source_data_size = 6;
  constexpr size_t spline_size = 86;
  float array[source_data_size] = { 1, 0, 2, 1, 2, 2 };
  Linear x(source_data_size);
  ArrayFloat y(array, source_data_size);
  SplineInterpolator dataSpline{ x, y, y, y, spline_size };
  Boundary lineBoundary;  
  
  // plot chart axis & grid
  grafici.colorMap(Colors::blackAndWhite);
  //plot(Display, DataSet, Boundary, ColorMap);
  grafici.plot(axis, Linear(25), Linear(10), Constant(25, 0.1), lineBoundary);

  // plot x/y points as small circles
  grafici.colorMap(Colors::parula);
  lineBoundary.cropRelativeCartesian({ 0.04, 0.04 }, { 0.04, 0.04 });
  grafici.plot(scatter, dataSpline.x(), dataSpline.y(), dataSpline.y(), Constant(spline_size, 0.015), lineBoundary);

} // ChtScatter()


void ChtSpline() {
  constexpr size_t num_elem = 5;
  float array[num_elem] = { 1, 0, 2, 1, 2 };
  Linear x(num_elem);
  ArrayFloat y(array, num_elem);
  SplineInterpolator dataSpline{ x, y, y, y, 85 };
  Boundary lineBoundary;  

  // plot chart axis & grid
  grafici.colorMap(Colors::blackAndWhite);
  //plot(Display, DataSet, Boundary, ColorMap);
  grafici.plot(axis, Linear(25), Linear(10), Constant(25, 0.1), lineBoundary);

  grafici.colorMap(Colors::rainbow);
  grafici.plot(line, dataSpline);
  
} // ChtSpline()


void ChtRotatedBar() {
  constexpr size_t source_data_size = 6;
  constexpr size_t spline_size = 86;
  constexpr size_t histogram_size = 20;
  float array[source_data_size] = { 1, 0, 2, 1, 2, 2 };
  Linear x(source_data_size);
  ArrayFloat y(array, source_data_size);
  SplineInterpolator dataSpline{ x, y, y, y, spline_size };
  Histogram dataHistogram{ dataSpline.y(), histogram_size };
  Boundary barBoundary;
  
  barBoundary.cropRelativeCartesian({ 0.04, 0.04 }, { 0.04, 0.04 }).boundaryRotation() = BoundaryRotation::clockWise90;
  grafici.plot(bar, BarIndex(histogram_size), dataHistogram, BarIndex(histogram_size), Constant(histogram_size, 0.0), barBoundary);
  
} // ChtRotatedBar()

void ChtHeatMaps() {
  constexpr size_t num_elem = 6;
  float arrayX[num_elem] = { 0, 1, 2, 2, 3, 4 };
  float arrayY[num_elem] = { 1, 0, 2, 1, 1, 2 };
  ArrayFloat x(arrayX, num_elem);
  ArrayFloat y(arrayY, num_elem);
  Constant opt(num_elem, 0.02);
  Constant c(num_elem, 0);
  Boundary bl;
  Boundary br;
  Boundary tl;
  Boundary tr;  

  grafici.colorMap(Colors::parula);
  bl.cropGridCartesian(2, 2, 0, 0);
  bl.cropAbsoluteCartesian({ 0.01, 0.01 }, { 0.01, 0.01 });
  grafici.plot(heatmap, x, y, c, opt, bl);
  grafici.plot(scatter, x, y, c, opt, bl);

  br.cropGridCartesian(2, 2, 0, 1);
  br.cropAbsoluteCartesian({ 0.01, 0.01 }, { 0.01, 0.01 });
  grafici.plot(bubblemap, x, y, c, opt, br);
  grafici.plot(scatter, x, y, c, opt, br);

  tl.cropGridCartesian(2, 2, 1, 0);
  tl.cropAbsoluteCartesian({ 0.01, 0.01 }, { 0.01, 0.01 });
  grafici.plot(cellmap, x, y, x, opt, tl);
  grafici.plot(scatter, x, y, c, opt, tl);

  tr.cropGridCartesian(2, 2, 1, 1);
  tr.cropAbsoluteCartesian({ 0.01, 0.01 }, { 0.01, 0.01 });
  grafici.plot(cliquegraph, x, y, x, opt, tr);
  grafici.plot(scatter, x, y, c, opt, tr);
  
} // ChtHeatMaps()


void setup(void) {
  
  tft.begin();
  tft.setRotation(1);

  //grafici.begin(tft, Colors::blackAndWhite);
  grafici.begin(tft, Colors::parula);
  //grafici.begin(tft, Colors::temperature);
  //grafici.begin(tft, Colors::heat);
  //grafici.begin(tft, Colors::rainbow);
  //grafici.begin(tft, Colors::cmy);
  //grafici.begin(tft, Colors::bright);
  //grafici.begin(tft, Colors::semaphore);
  
  grafici.clear();

  ChtLinearInterpolation();
  delay(3000);  grafici.clear();

  ChtSpline();
  delay(3000);  grafici.clear();

  ChtScatter();
  delay(3000);  grafici.clear();

  ChtBar();
  delay(3000);  grafici.clear();
  
  ChtRotatedBar();
  delay(3000);  grafici.clear();
  
  ChtPolar();
  delay(3000);  grafici.clear();

  ChtHeatMaps();
  delay(3000);  grafici.clear();

  ChtSharedAxis();
  delay(3000);  grafici.clear();

  ChtColors();
  
} // setup()

void loop(void) {
}

 

 


Do you need help developing or customizing a IoT product for your needs?   Send me an email requesting a free one hour phone / web share consultation.  

 

The information presented on this website is for the author's use only.   Use of this information by anyone other than the author is offered as guidelines and non-professional advice only.   No liability is assumed by the author or this web site.