Created
November 8, 2023 17:45
-
-
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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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