Skip to content

Instantly share code, notes, and snippets.

@prashant3285
Created November 8, 2023 17:45
Show Gist options
  • Save prashant3285/20c23194efad9afcc88af7f8b1e7cfe6 to your computer and use it in GitHub Desktop.
Save prashant3285/20c23194efad9afcc88af7f8b1e7cfe6 to your computer and use it in GitHub Desktop.
Live Plot Sensor Data on TFT Display ILI9486 using ESP32 - For more details read blog https://blog.kamlatech.in/2023/11/live-plot-sensor-data-on-tft-display.html
/*
This program provides cartesian type graph function
Revisions
rev date author description
1 12-24-2015 kasprzak initial creation
Updated by Bodmer to be an example for the library here:
https://github.com/Bodmer/TFT_eSPI
Updated by Prashant to be used for Live Plot Sensor Data on TFT Display ILI9486 using ESP32
https://blog.kamlatech.in/2023/11/live-plot-sensor-data-on-tft-display.html
8/11/2023
Additional Lib: https://www.arduino.cc/reference/en/libraries/circularbuffer/
*/
#include <TFT_eSPI.h> // Hardware-specific library
#include <SPI.h>
#include <CircularBuffer.h> // Ease of data handling - https://www.arduino.cc/reference/en/libraries/circularbuffer/
TFT_eSPI tft = TFT_eSPI(); // Invoke custom library with default width and height
#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 Graph() 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
bool display1 = true;
bool update1 = true; // Flag to redraw coordinate system
double ox = -999, oy = -999; // Force them to be off screen
// Buffer to store x and y axis data coming form sensor
CircularBuffer<double, 200> plotTemp;
CircularBuffer<double, 200> plotTime;
// variable to determine the min and max form buffer, which is then used to dynamicaly update the x and y axis coordinates
double minX = 0;
double maxX = 0;
double minY = 0;
double maxY = 0;
// data to be plotted on the chart
double xplot, yplot;
int h = 0;
// Test data to creat a plot as no sensor is connected in this example
double initplotTemp[81] = {33.42, 33.42, 33.46, 33.55, 33.55, 33.57, 33.57, 33.73, 33.84, 33.96, 34.16, 34.4, 34.6, 34.88, 35.17, 35.45, 35.7, 35.89, 36.28, 36.59, 36.84, 37.2, 37.51, 37.74, 38.1, 38.35, 38.7, 39, 39.34, 39.57, 39.95, 40.23, 40.57, 40.91, 41.23, 41.5, 41.91, 42.21, 42.53, 42.96, 43.24, 43.59, 44.03, 44.28, 44.65, 45.03, 45.35, 45.65, 46.13, 46.48, 46.86, 47.23, 47.67, 48.01, 48.36, 48.76, 49.16, 49.55, 49.91, 50.31, 50.74, 51.07, 51.43, 51.83, 52.3, 52.63, 52.98, 53.34, 53.69, 53.98, 54.41, 54.77, 55.09, 55.5, 55.84, 56.11, 56.43, 56.73, 57.11, 57.45, 57.71};
double initplotTime[81] = {0.002, 0.063, 0.145, 0.227, 0.309, 0.392, 0.487, 0.569, 0.652, 0.734, 0.816, 0.899, 0.981, 1.063, 1.145, 1.228, 1.31, 1.392, 1.475, 1.557, 1.638, 1.721, 1.817, 1.899, 1.98, 2.063, 2.145, 2.227, 2.31, 2.392, 2.474, 2.557, 2.639, 2.721, 2.803, 2.886, 2.968, 3.05, 3.146, 3.228, 3.31, 3.393, 3.475, 3.556, 3.638, 3.721, 3.803, 3.885, 3.969, 4.05, 4.132, 4.215, 4.297, 4.379, 4.475, 4.557, 4.639, 4.721, 4.804, 4.886, 4.967, 5.05, 5.132, 5.214, 5.296, 5.379, 5.461, 5.543, 5.626, 5.708, 5.803, 5.886, 5.968, 6.05, 6.133, 6.215, 6.297, 6.379, 6.462, 6.543, 6.625};
/*
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
&redraw = flag to redraw graph on first call only
color = plotted trace colour
axisTextColor = axis coordinates text color in case you want to hide text using a black color on a black background
*/
void Graph(TFT_eSPI &tft, double x, double y, byte dp,
double gx, double gy, double w, double h,
double xlo, double xhi, double xinc,
double ylo, double yhi, double yinc,
char *title, char *xlabel, char *ylabel,
bool &redraw, unsigned int color, unsigned int axisTextColor)
{
double ydiv, xdiv;
double i;
double temp;
int rot, newrot;
// gcolor = graph grid colors
// acolor = axes line colors
// pcolor = color of your plotted data
// tcolor = text color
// bcolor = background color
unsigned int gcolor = DKBLUE;
unsigned int acolor = RED;
unsigned int pcolor = color;
unsigned int tcolor = WHITE;
unsigned int bcolor = BLACK;
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;
tft.setTextDatum(MR_DATUM);
// draw y scale
for (i = ylo; i <= yhi; i += yinc)
{
// compute the transform
temp = (i - ylo) * (gy - h - gy) / (yhi - ylo) + gy;
if (i == ylo) // 0 will not work if sensor data do not start from 0 so keep ylo the first point
{
tft.drawLine(gx, temp, gx + w, temp, acolor);
tft.setTextColor(acolor, bcolor);
// tft.drawString(xlabel, (int)(gx + w) , (int)temp, 2); //
tft.drawString(xlabel, (int)(gx + w + 35 + 20), (320 - 10), 2); // using static value to adjust as per need
}
else
{
// tft.drawLine(gx, temp, gx + w, temp, gcolor); // Uncomment this to turn on grid
}
// draw the axis labels
tft.setTextColor(tcolor, bcolor); // replace tcolor with axisTextColor to pass black color and hide text
// precision is default Arduino--this could really use some format control
// tft.drawFloat(i, dp, gx - 4, temp, 1); // use "dp" to pass decimal value in axis text
tft.drawFloat(i, 0, gx - 4, temp, 1); // disabled decimal in axis text
}
// draw x scale
for (i = xlo; i <= xhi; i += xinc)
{
// compute the transform
temp = (i - xlo) * (w) / (xhi - xlo) + gx;
if (i == xlo) // 0 will not work if reading do not start from 0 so keep ylo the first point
{
tft.drawLine(temp, gy, temp, gy - h, acolor);
tft.setTextColor(acolor, bcolor);
tft.setTextDatum(BC_DATUM);
// tft.drawString(ylabel, (int)temp, (int)(gy - h - 8) , 2);
tft.drawString(ylabel, (int)temp + 10, (int)(gy - h - 8), 2); // using static value to adjust as per need
}
else
{
// tft.drawLine(temp, gy, temp, gy - h, gcolor); // Uncomment this to turn on grid
}
// draw the axis labels
tft.setTextColor(axisTextColor, bcolor);
tft.setTextDatum(TC_DATUM);
// precision is default Arduino--this could really use some format control
// tft.drawFloat(i, dp, temp, gy + 7, 1); // use "dp" to pass decimal value in axis text
tft.drawFloat(i, 0, temp, gy + 7, 1); // disabled decimal in axis text
}
// now draw the graph labels
tft.setTextColor(tcolor, bcolor);
tft.drawString(title, (int)(gx + w / 2), (int)(gy - h - 30), 4); // Not tested the positioning, black fill refresh in chart area may distort or hide this
}
}
void Trace(TFT_eSPI &tft, double x, double y, byte dp,
double gx, double gy,
double w, double h,
double xlo, double xhi, double xinc,
double ylo, double yhi, double yinc,
char *title, char *xlabel, char *ylabel,
bool &update1, unsigned int color, unsigned int axisTextColor)
{
double ydiv, xdiv;
double i;
double temp;
int rot, newrot;
unsigned int gcolor = DKBLUE; // gcolor = graph grid color
unsigned int acolor = RED; // acolor = main axes and label color
unsigned int pcolor = color; // pcolor = color of your plotted data
unsigned int tcolor = WHITE; // tcolor = text color
unsigned int bcolor = BLACK; // bcolor = background color
// 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
if (update1)
{
update1 = false;
ox = (x - xlo) * (w) / (xhi - xlo) + gx;
oy = (y - ylo) * (gy - h - gy) / (yhi - ylo) + gy;
if ((ox < gx) || (ox > gx + w))
{
update1 = true;
return;
}
if ((oy < gy - h) || (oy > gy))
{
update1 = true;
return;
}
tft.setTextDatum(MR_DATUM);
// draw y scale
for (i = ylo; i <= yhi; i += yinc)
{
// compute the transform
temp = (i - ylo) * (gy - h - gy) / (yhi - ylo) + gy;
if (i == ylo) // 0 will not work if reading do not start from 0 so keep ylo the first point
{
tft.setTextColor(acolor, bcolor);
// tft.drawString(xlabel, (int)(gx + w) , (int)temp, 2);
tft.drawString(xlabel, (int)(gx + w + 35 + 20), (320 - 10), 2); // Due to x axis padding moving away from axis
}
// draw the axis labels
tft.setTextColor(tcolor, bcolor);
// precision is default Arduino--this could really use some format control
// tft.drawFloat(i, dp, gx - 4, temp, 1);
tft.drawFloat(i, 0, gx - 4, temp, 1); // Changed to remove decimal places
}
// draw x scale
for (i = xlo; i <= xhi; i += xinc)
{
// compute the transform
temp = (i - xlo) * (w) / (xhi - xlo) + gx;
if (i == xlo)
{ // 0 will not work if reading do not start from 0 so keep ylo the first point
tft.setTextColor(acolor, bcolor);
tft.setTextDatum(BC_DATUM);
// tft.drawString(ylabel, (int)temp, (int)(gy - h - 8) , 2);
tft.drawString(ylabel, (int)temp + 10, (int)(gy - h - 8), 2); // Custom value for best fit
}
// draw the axis labels
tft.setTextColor(axisTextColor, bcolor);
tft.setTextDatum(TC_DATUM);
// precision is default Arduino--this could really use some format control
// tft.drawFloat(i, dp, temp, gy + 7, 1);
tft.drawFloat(i, 0, temp, gy + 7, 1); // remove decimal by using 0
}
// now draw the graph labels
tft.setTextColor(tcolor, bcolor);
tft.drawString(title, (int)(gx + w / 2), (int)(gy - h - 30), 4);
}
// 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;
if ((x < gx) || (x > gx + w))
{
update1 = true;
return;
}
if ((y < gy - h) || (y > gy))
{
update1 = true;
return;
}
tft.drawLine(ox, oy, x, y, pcolor);
// it's up to you but drawing 2 more lines to give the graph some thickness
tft.drawLine(ox, oy + 1, x, y + 1, pcolor); // thickness added
tft.drawLine(ox, oy - 1, x, y - 1, pcolor); // thickness added
ox = x;
oy = y;
}
/*
End of graphing function
*/
void setup()
{
tft.begin();
tft.fillScreen(BLACK);
tft.setRotation(1);
Serial.begin(115200);
Graph(tft, xplot, yplot, 1, 20, 300, 300, 192, 0, 1, 1, 20, 60, 5, "", "Time(s)", "Temp(C)", display1, YELLOW, WHITE);
// Just a pause to have a look at graph layout
delay(2000);
// In my case the Y axis is static so minY is not required. If you axis do not start at zero then you need minY declared, else it will start from 0
// minY = 25.0; // for axis which do not need a 0 at start point - net needed for fix Y axis
// In my case the X axis is dynamic but its starting value is not changing and is zero. if in you case its not zero then minX must be declared.
}
// Graph Plotting Function, This should be called as soon as you get a new sensor value, to update the graph.
void plotGraphP()
{
// Iteratig through the buffer to find the min and max values to be used in creating the axis limits dumanically
for (int z = 0; z < plotTemp.size(); z++)
{
minX = min(minX, plotTime[z]);
maxX = max(maxX, plotTime[z]);
// minY = min(minY, plotTemp[z]); // my y axis is static so dont need to find the min max to generate the axis limit
// maxY = max(maxY, plotTemp[z]); // my y axis is static so dont need to find the min max to generate the axis limit
}
update1 = true;
tft.fillRect(21, 100, 310, 199, BLACK); // need to wipe graph before each update else old traces will show, calculate or trial and error your coordinates
tft.fillRect(25, 301, 300, 20, BLACK); // x axis wiping is required as my x axis is dynamic
// tft.fillRect(0, 100, 17, 195, BLUE ); // y axis is stable for me so no wiping: Hint - use visible color to wipe to see the area that is wiped (for testing)
for (int u = 0; u < plotTemp.size(); u++)
{
yplot = plotTemp[u];
xplot = plotTime[u];
Trace(tft, xplot, yplot, 1, 20, 300, 300, 192, minX, (maxX + 1), 1, 20, 60, 5, "", "", "", update1, PINK, WHITE);
// Textcolor BLACK can be passed to hide it from viewing
// Do not update axis lables if not necessary
}
}
void loop(void)
{
if (h < 81)
{
plotTemp.push(initplotTemp[h]);
plotTime.push(initplotTime[h]);
delay(83);
plotGraphP();
}
h++;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment